diff --git a/pkg/chartutil/load.go b/pkg/chartutil/load.go index 6d70f97b4..c5246b8d7 100644 --- a/pkg/chartutil/load.go +++ b/pkg/chartutil/load.go @@ -32,6 +32,7 @@ import ( "k8s.io/helm/pkg/ignore" "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/sympath" ) // Load takes a string name, tries to resolve it to a file or directory, and then loads it. @@ -279,44 +280,9 @@ func LoadDir(dir string) (*chart.Chart, error) { files = append(files, &BufferedFile{Name: n, Data: data}) return nil } - if err = filepath.Walk(topdir, symWalk(topdir, "", walk)); err != nil { + if err = sympath.Walk(topdir, walk); err != nil { return c, err } return LoadFiles(files) } - -// symWalk walks topdir with optional symbolic link dir, symdir. The symdir will -// be used as the path name sent to walkFn. -func symWalk(topdir, symdir string, walkFn filepath.WalkFunc) filepath.WalkFunc { - return func(name string, fi os.FileInfo, err error) error { - // Recover the symbolic path instead of the real path. - if symdir != "" { - relative, err := filepath.Rel(topdir, name) - if err != nil { - return err - } - name = filepath.Join(symdir, relative) - } - - // Recursively walk symlinked directories. - if isSymlink(fi) { - resolved, err := filepath.EvalSymlinks(name) - if err != nil { - return fmt.Errorf("error evaluating symlink %s: %s", name, err) - } - if fi, err = os.Lstat(resolved); err != nil { - return err - } - if fi.IsDir() { - return filepath.Walk(resolved, symWalk(resolved, name, walkFn)) - } - } - - return walkFn(name, fi, err) - } -} - -func isSymlink(fi os.FileInfo) bool { - return fi.Mode()&os.ModeSymlink != 0 -} diff --git a/pkg/sympath/walk.go b/pkg/sympath/walk.go new file mode 100644 index 000000000..bada1dbfb --- /dev/null +++ b/pkg/sympath/walk.go @@ -0,0 +1,110 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sympath + +import ( + "fmt" + "os" + "path/filepath" + "sort" +) + +// Walk walks the file tree rooted at root, calling walkFn for each file or directory +// in the tree, including root. All errors that arise visiting files and directories +// are filtered by walkFn. The files are walked in lexical order, which makes the +// output deterministic but means that for very large directories Walk can be +// inefficient. Walk follows symbolic links. +func Walk(root string, walkFn filepath.WalkFunc) error { + info, err := os.Lstat(root) + if err != nil { + err = walkFn(root, nil, err) + } else { + err = symwalk(root, info, walkFn) + } + if err == filepath.SkipDir { + return nil + } + return err +} + +// readDirNames reads the directory named by dirname and returns +// a sorted list of directory entries. +func readDirNames(dirname string) ([]string, error) { + f, err := os.Open(dirname) + if err != nil { + return nil, err + } + names, err := f.Readdirnames(-1) + f.Close() + if err != nil { + return nil, err + } + sort.Strings(names) + return names, nil +} + +// symwalk recursively descends path, calling walkFn. +func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { + // Recursively walk symlinked directories. + if IsSymlink(info) { + resolved, err := filepath.EvalSymlinks(path) + if err != nil { + return fmt.Errorf("error evaluating symlink %s: %s", path, err) + } + if info, err = os.Lstat(resolved); err != nil { + return err + } + if err := symwalk(resolved, info, walkFn); err != nil && err != filepath.SkipDir { + return err + } + } + + if err := walkFn(path, info, nil); err != nil { + return err + } + + if !info.IsDir() { + return nil + } + + names, err := readDirNames(path) + if err != nil { + return walkFn(path, info, err) + } + + for _, name := range names { + filename := filepath.Join(path, name) + fileInfo, err := os.Lstat(filename) + if err != nil { + if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { + return err + } + } else { + err = symwalk(filename, fileInfo, walkFn) + if err != nil { + if (!fileInfo.IsDir() && !IsSymlink(fileInfo)) || err != filepath.SkipDir { + return err + } + } + } + } + return nil +} + +// IsSymlink is used to determine if the fileinfo is a symbolic link. +func IsSymlink(fi os.FileInfo) bool { + return fi.Mode()&os.ModeSymlink != 0 +} diff --git a/pkg/sympath/walk_test.go b/pkg/sympath/walk_test.go new file mode 100644 index 000000000..298042281 --- /dev/null +++ b/pkg/sympath/walk_test.go @@ -0,0 +1,131 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sympath + +import ( + "os" + "path/filepath" + "testing" +) + +type Node struct { + name string + entries []*Node // nil if the entry is a file + mark int +} + +var tree = &Node{ + "testdata", + []*Node{ + {"a", nil, 0}, + {"b", []*Node{}, 0}, + {"c", nil, 0}, + { + "d", + []*Node{ + {"x", nil, 0}, + {"y", []*Node{}, 0}, + { + "z", + []*Node{ + {"u", nil, 0}, + {"v", nil, 0}, + {"w", nil, 0}, + }, + 0, + }, + }, + 0, + }, + }, + 0, +} + +func walkTree(n *Node, path string, f func(path string, n *Node)) { + f(path, n) + for _, e := range n.entries { + walkTree(e, filepath.Join(path, e.name), f) + } +} + +func makeTree(t *testing.T) { + walkTree(tree, tree.name, func(path string, n *Node) { + if n.entries == nil { + fd, err := os.Create(path) + if err != nil { + t.Errorf("makeTree: %v", err) + return + } + fd.Close() + } else { + os.Mkdir(path, 0770) + } + }) +} + +func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) } + +func checkMarks(t *testing.T, report bool) { + walkTree(tree, tree.name, func(path string, n *Node) { + if n.mark != 1 && report { + t.Errorf("node %s mark = %d; expected 1", path, n.mark) + } + n.mark = 0 + }) +} + +// Assumes that each node name is unique. Good enough for a test. +// If clear is true, any incoming error is cleared before return. The errors +// are always accumulated, though. +func mark(info os.FileInfo, err error, errors *[]error, clear bool) error { + if err != nil { + *errors = append(*errors, err) + if clear { + return nil + } + return err + } + name := info.Name() + walkTree(tree, tree.name, func(path string, n *Node) { + if n.name == name { + n.mark++ + } + }) + return nil +} + +func TestWalk(t *testing.T) { + makeTree(t) + errors := make([]error, 0, 10) + clear := true + markFn := func(path string, info os.FileInfo, err error) error { + return mark(info, err, &errors, clear) + } + // Expect no errors. + err := Walk(tree.name, markFn) + if err != nil { + t.Fatalf("no error expected, found: %s", err) + } + if len(errors) != 0 { + t.Fatalf("unexpected errors: %s", errors) + } + checkMarks(t, true) + + // cleanup + if err := os.RemoveAll(tree.name); err != nil { + t.Errorf("removeTree: %v", err) + } +}