diff --git a/internal/filesystem/precondition.go b/internal/filesystem/precondition.go index 54a86d7..3d2e79d 100644 --- a/internal/filesystem/precondition.go +++ b/internal/filesystem/precondition.go @@ -24,6 +24,9 @@ const ( KindSpecial ) +// Valid reports whether the kind is one of the known filesystem entry kinds. +func (k EntryKind) Valid() bool { return k >= KindAbsent && k <= KindSpecial } + // ContentToken is an immutable digest of exact bytes; equal tokens prove // equal content without retaining the bytes themselves. type ContentToken [32]byte diff --git a/internal/reconcile/source_snapshot.go b/internal/reconcile/source_snapshot.go index f488e2b..93cb7a3 100644 --- a/internal/reconcile/source_snapshot.go +++ b/internal/reconcile/source_snapshot.go @@ -8,6 +8,7 @@ import ( "os" "github.com/alyraffauf/cattery/internal/deployment" + "github.com/alyraffauf/cattery/internal/filesystem" "github.com/alyraffauf/cattery/internal/pathsafe" "github.com/alyraffauf/cattery/internal/secrets" ) @@ -94,7 +95,7 @@ func validateCapture(input captureInput) error { } func requireRegularSource(path string, identity pathsafe.Identity) error { - if KindOfIdentity(identity) != KindFile { + if filesystem.KindOfIdentity(identity) != KindFile { return fmt.Errorf("reconcile: source %s is not a regular file", path) } return nil @@ -184,7 +185,7 @@ func checkStableSource(read sourceRead) error { if !pathsafe.SameIdentity(read.before, read.after) { return fmt.Errorf("reconcile: source %s replaced while reading", read.path) } - if KindOfIdentity(read.after) != KindFile { + if filesystem.KindOfIdentity(read.after) != KindFile { return fmt.Errorf("reconcile: source %s is no longer a regular file", read.path) } if read.after.Size() != int64(len(read.data)) { @@ -212,7 +213,7 @@ func checkSecretEnvelope(path string, data []byte) error { func sourceFacts(file deployment.ManagedFile, identity pathsafe.Identity, data []byte) SourceSnapshot { snapshot := SourceSnapshot{ path: file.SourceAbsolutePath, identity: identity, kind: KindFile, - token: TokenOfContent(data), executable: identity.Mode().Perm() & deployment.ExecutableBitMask, + token: filesystem.TokenOfContent(data), executable: identity.Mode().Perm() & deployment.ExecutableBitMask, } if file.Kind == deployment.FileOrdinary { snapshot.semantic = deployment.Ordinary(data) diff --git a/internal/reconcile/target_snapshot.go b/internal/reconcile/target_snapshot.go index c4db377..825157c 100644 --- a/internal/reconcile/target_snapshot.go +++ b/internal/reconcile/target_snapshot.go @@ -9,6 +9,7 @@ import ( "path/filepath" "github.com/alyraffauf/cattery/internal/deployment" + "github.com/alyraffauf/cattery/internal/filesystem" "github.com/alyraffauf/cattery/internal/pathsafe" ) @@ -72,7 +73,7 @@ func captureEntry(destination Destination, parent pathsafe.Identity) (TargetSnap return TargetSnapshot{}, err } snapshot := TargetSnapshot{ - destination: destination, parent: parent, kind: KindOfIdentity(identity), + destination: destination, parent: parent, kind: filesystem.KindOfIdentity(identity), identity: identity, mode: identity.Mode().Perm(), } switch snapshot.kind { @@ -102,7 +103,7 @@ func captureFileFacts(path string, snapshot TargetSnapshot) (TargetSnapshot, err if !pathsafe.SameIdentity(snapshot.identity, live) || live.Mode().Perm() != snapshot.mode { return TargetSnapshot{}, fmt.Errorf("reconcile: target changed while reading %s", path) } - snapshot.token = TokenOfContent(content) + snapshot.token = filesystem.TokenOfContent(content) snapshot.digest = deployment.Ordinary(content) return snapshot, nil } @@ -150,14 +151,5 @@ func validateOpenedFile(snapshot TargetSnapshot, info os.FileInfo) error { // KindOfIdentity classifies an existing identity without touching the path. func KindOfIdentity(identity pathsafe.Identity) EntryKind { - mode := identity.Mode() - switch { - case mode.IsDir(): - return KindDirectory - case mode&os.ModeSymlink != 0: - return KindSymlink - case mode.IsRegular(): - return KindFile - } - return KindSpecial + return filesystem.KindOfIdentity(identity) } diff --git a/internal/reconcile/types.go b/internal/reconcile/types.go index 8e5dd86..f03ed58 100644 --- a/internal/reconcile/types.go +++ b/internal/reconcile/types.go @@ -4,40 +4,32 @@ package reconcile import ( - "crypto/sha256" "fmt" "io/fs" "time" "github.com/alyraffauf/cattery/internal/deployment" + "github.com/alyraffauf/cattery/internal/filesystem" "github.com/alyraffauf/cattery/internal/pathsafe" "github.com/alyraffauf/cattery/internal/state" ) -// Destination names one HOME-relative target beneath a canonical root. -type Destination struct { - Root string - Relative string -} - -// EntryKind names the object type observed at one destination path. -type EntryKind int +// These low-level values are owned by filesystem. Aliases keep reconciliation +// records and their exported vocabulary readable without duplicating types. +type Destination = filesystem.Destination +type EntryKind = filesystem.EntryKind +type ContentToken = filesystem.ContentToken const ( - KindAbsent EntryKind = iota - KindFile - KindDirectory - KindSymlink - KindSpecial + KindAbsent = filesystem.KindAbsent + KindFile = filesystem.KindFile + KindDirectory = filesystem.KindDirectory + KindSymlink = filesystem.KindSymlink + KindSpecial = filesystem.KindSpecial ) -func (k EntryKind) Valid() bool { return k >= KindAbsent && k <= KindSpecial } - -// ContentToken is an immutable digest of exact bytes. -type ContentToken [32]byte - -// TokenOfContent derives the content token of data. -func TokenOfContent(data []byte) ContentToken { return sha256.Sum256(data) } +// TokenOfContent preserves the reconcile API while delegating to filesystem. +func TokenOfContent(data []byte) ContentToken { return filesystem.TokenOfContent(data) } // Action names the reconciliation step a classified representation requires. type Action int