diff --git a/cmd/kosli/fingerprint.go b/cmd/kosli/fingerprint.go index 137d23da5..ad3867f73 100644 --- a/cmd/kosli/fingerprint.go +++ b/cmd/kosli/fingerprint.go @@ -16,6 +16,10 @@ exclude ^zam/file.txt^ which is relative to the DIR-PATH. The supported glob pattern syntax is what is documented here: https://pkg.go.dev/path/filepath#Match , plus the ability to use recursive globs "**" +If the directory structure contains symbolic links to a file the content of the file it points to is included in the +fingerprint calculation. If a symbolic link points to a directory the path it is pointing to is include in the +fingerprint calculation. + ` + kosliIgnoreDesc const fingerprintLongDesc = fingerprintShortDesc + ` diff --git a/cmd/kosli/fingerprint_test.go b/cmd/kosli/fingerprint_test.go index 5f3d36e0c..09e0bf649 100644 --- a/cmd/kosli/fingerprint_test.go +++ b/cmd/kosli/fingerprint_test.go @@ -44,6 +44,18 @@ func (suite *FingerprintTestSuite) TestFingerprintCmd() { cmd: "fingerprint --artifact-type dir testdata/folder1-with-ignore", golden: "038897ea5334462098d65125380d58a493671fb3b8bdbbee1e75ec8bd4a65c23\n", }, + { + name: "dir fingerprint with symbolic links 1", + cmd: "fingerprint --artifact-type dir testdata/folder-with-symlinks-1", + golden: "1c0740265b38509fb9ce7babf00d7eb57e7e08b59dad8be66897c1ebb5b36409\n", + }, + { + // The two folders contains the same files and folder, but a/b/c/point-to-dir points + // different directories + name: "dir fingerprint with symbolic links 2", + cmd: "fingerprint --artifact-type dir testdata/folder-with-symlinks-2", + golden: "2a5fe76bc616a97b2ff6f30f46380f1230b98a58df8aec6e96c1fb0e03b41fd9\n", + }, { name: "fails if type is directory but the argument is not a dir", cmd: "fingerprint --artifact-type dir testdata/file1", diff --git a/cmd/kosli/testdata/folder-with-symlinks-1/a/b/bob.txt b/cmd/kosli/testdata/folder-with-symlinks-1/a/b/bob.txt new file mode 100644 index 000000000..696fb6baa --- /dev/null +++ b/cmd/kosli/testdata/folder-with-symlinks-1/a/b/bob.txt @@ -0,0 +1 @@ +bob diff --git a/cmd/kosli/testdata/folder-with-symlinks-1/a/b/c/d/dorris.txt b/cmd/kosli/testdata/folder-with-symlinks-1/a/b/c/d/dorris.txt new file mode 100644 index 000000000..118bd49f4 --- /dev/null +++ b/cmd/kosli/testdata/folder-with-symlinks-1/a/b/c/d/dorris.txt @@ -0,0 +1 @@ +dorris diff --git a/cmd/kosli/testdata/folder-with-symlinks-1/a/b/c/point-to-dir b/cmd/kosli/testdata/folder-with-symlinks-1/a/b/c/point-to-dir new file mode 120000 index 000000000..717bb189b --- /dev/null +++ b/cmd/kosli/testdata/folder-with-symlinks-1/a/b/c/point-to-dir @@ -0,0 +1 @@ +../../../a \ No newline at end of file diff --git a/cmd/kosli/testdata/folder-with-symlinks-1/a/b/point-to-dorris b/cmd/kosli/testdata/folder-with-symlinks-1/a/b/point-to-dorris new file mode 120000 index 000000000..267973ee4 --- /dev/null +++ b/cmd/kosli/testdata/folder-with-symlinks-1/a/b/point-to-dorris @@ -0,0 +1 @@ +c/d/dorris.txt \ No newline at end of file diff --git a/cmd/kosli/testdata/folder-with-symlinks-2/a/b/bob.txt b/cmd/kosli/testdata/folder-with-symlinks-2/a/b/bob.txt new file mode 100644 index 000000000..696fb6baa --- /dev/null +++ b/cmd/kosli/testdata/folder-with-symlinks-2/a/b/bob.txt @@ -0,0 +1 @@ +bob diff --git a/cmd/kosli/testdata/folder-with-symlinks-2/a/b/c/d/dorris.txt b/cmd/kosli/testdata/folder-with-symlinks-2/a/b/c/d/dorris.txt new file mode 100644 index 000000000..118bd49f4 --- /dev/null +++ b/cmd/kosli/testdata/folder-with-symlinks-2/a/b/c/d/dorris.txt @@ -0,0 +1 @@ +dorris diff --git a/cmd/kosli/testdata/folder-with-symlinks-2/a/b/c/point-to-dir b/cmd/kosli/testdata/folder-with-symlinks-2/a/b/c/point-to-dir new file mode 120000 index 000000000..221372d1c --- /dev/null +++ b/cmd/kosli/testdata/folder-with-symlinks-2/a/b/c/point-to-dir @@ -0,0 +1 @@ +../../b \ No newline at end of file diff --git a/cmd/kosli/testdata/folder-with-symlinks-2/a/b/point-to-dorris b/cmd/kosli/testdata/folder-with-symlinks-2/a/b/point-to-dorris new file mode 120000 index 000000000..267973ee4 --- /dev/null +++ b/cmd/kosli/testdata/folder-with-symlinks-2/a/b/point-to-dorris @@ -0,0 +1 @@ +c/d/dorris.txt \ No newline at end of file diff --git a/internal/digest/digest.go b/internal/digest/digest.go index a2c262b3c..f6f2acbcd 100644 --- a/internal/digest/digest.go +++ b/internal/digest/digest.go @@ -131,14 +131,50 @@ func calculateDirContentSha256(digestsFile *os.File, dirPath, tmpDir string, exc return nil } + // If it's a symlink, resolve the target + var stat fs.FileInfo + if info.Type()&os.ModeSymlink != 0 { + resolved, err := os.Stat(path) // follows the symlink + if err != nil { + return err + } + stat = resolved + } else { + // Convert fs.DirEntry to fs.FileInfo for consistency + resolved, err := info.Info() + if err != nil { + return err + } + stat = resolved + } + nameSha256, err := addNameDigest(tmpDir, info.Name(), digestsFile) if err != nil { return err } - if info.IsDir() { - logger.Debug("dir path: %s -- dirname digest: %v", path, nameSha256) + if stat.IsDir() { + if info.Type()&os.ModeSymlink != 0 { + // This is a symlink to a directory + logger.Debug("symlink path: %s -- linkname digest: %v", path, nameSha256) + + targetPath, err := os.Readlink(path) + if err != nil { + return err + } + + // Calculate fingerprint of what link points to (for this: a -> c/d calculate the fingerprint of c/d) + targetSha256, err := addNameDigest(tmpDir, targetPath, digestsFile) + if err != nil { + return err + } + logger.Debug("symlink: %s (points to %s) -- digest: %v", path, targetPath, targetSha256) + } else { + // Normal directory + logger.Debug("dir path: %s -- dirname digest: %v", path, nameSha256) + } } else { + // File or symlink -> file logger.Debug("file path: %s -- filename digest: %s", path, nameSha256) fileContentSha256, err := FileSha256(path) if err != nil {