diff --git a/pkg/deploy/controller.go b/pkg/deploy/controller.go index 932feb1839a..d7dbf8c94e0 100644 --- a/pkg/deploy/controller.go +++ b/pkg/deploy/controller.go @@ -114,34 +114,8 @@ func (w *watcher) listFiles(force bool) error { // listFilesIn recursively processes all files within a path, and checks them against the disable and skip lists. Files found that // are not on either list are loaded as Addons and applied to the cluster. func (w *watcher) listFilesIn(base string, force bool) error { - files := map[string]os.FileInfo{} - if err := filepath.Walk(base, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - // Descend into symlinked directories, however, only top-level links are followed - if info.Mode()&os.ModeSymlink != 0 { - linkInfo, err := os.Stat(path) - if err != nil { - return err - } - if linkInfo.IsDir() { - evalPath, err := filepath.EvalSymlinks(path) - if err != nil { - return err - } - filepath.Walk(evalPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - files[path] = info - return nil - }) - } - } - files[path] = info - return nil - }); err != nil { + files, err := walkFiles(base) + if err != nil { return err } @@ -162,6 +136,9 @@ func (w *watcher) listFilesIn(base string, force bool) error { var errs []error for _, path := range keys { + if files[path].IsDir() { + continue + } // Disabled files are not just skipped, but actively deleted from the filesystem if shouldDisableFile(base, path, w.disables) { if err := w.delete(path); err != nil { @@ -187,6 +164,44 @@ func (w *watcher) listFilesIn(base string, force bool) error { return errors.Join(errs...) } +func walkFiles(base string) (map[string]os.FileInfo, error) { + files := map[string]os.FileInfo{} + if err := filepath.Walk(base, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + // Descend into symlinked directories, however, only top-level links are followed + if info.Mode()&os.ModeSymlink != 0 { + linkInfo, err := os.Stat(path) + if err != nil { + return err + } + if linkInfo.IsDir() { + evalPath, err := filepath.EvalSymlinks(path) + if err != nil { + return err + } + return filepath.Walk(evalPath, func(linkPath string, linkInfo os.FileInfo, err error) error { + if err != nil { + return err + } + rel, err := filepath.Rel(evalPath, linkPath) + if err != nil { + return err + } + files[filepath.Join(path, rel)] = linkInfo + return nil + }) + } + } + files[path] = info + return nil + }); err != nil { + return nil, err + } + return files, nil +} + // deploy loads yaml from a manifest on disk, creates an AddOn resource to track its application, and then applies // all resources contained within to the cluster. func (w *watcher) deploy(path string, compareChecksum bool) error { diff --git a/pkg/deploy/controller_test.go b/pkg/deploy/controller_test.go new file mode 100644 index 00000000000..b1435c18a52 --- /dev/null +++ b/pkg/deploy/controller_test.go @@ -0,0 +1,49 @@ +package deploy + +import ( + "os" + "path/filepath" + "testing" +) + +func Test_UnitWalkFilesSymlinkedDirectoryUsesLogicalPaths(t *testing.T) { + base := t.TempDir() + target := t.TempDir() + + manifest := filepath.Join(target, "nested", "manifest.yaml") + if err := os.MkdirAll(filepath.Dir(manifest), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(manifest, []byte("apiVersion: v1\nkind: Namespace\nmetadata:\n name: test\n"), 0644); err != nil { + t.Fatal(err) + } + + link := filepath.Join(base, "linked") + if err := os.Symlink(target, link); err != nil { + t.Fatal(err) + } + + files, err := walkFiles(base) + if err != nil { + t.Fatal(err) + } + + logicalPath := filepath.Join(link, "nested", "manifest.yaml") + if _, ok := files[logicalPath]; !ok { + t.Fatalf("expected symlinked manifest to use logical path %q; got %v", logicalPath, fileKeys(files)) + } + if _, ok := files[manifest]; ok { + t.Fatalf("expected resolved target path %q to be hidden by logical symlink path", manifest) + } + if !shouldDisableFile(base, logicalPath, map[string]bool{"linked": true}) { + t.Fatalf("expected logical path %q to match disabled symlink directory", logicalPath) + } +} + +func fileKeys(files map[string]os.FileInfo) []string { + keys := make([]string, 0, len(files)) + for key := range files { + keys = append(keys, key) + } + return keys +}