diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go index c2de72413..6fe5848de 100644 --- a/pkg/commands/commands.go +++ b/pkg/commands/commands.go @@ -13,6 +13,7 @@ import ( "sigs.k8s.io/kustomize/v3/k8sdeps/transformer" "sigs.k8s.io/kustomize/v3/k8sdeps/validator" "sigs.k8s.io/kustomize/v3/pkg/commands/build" + "sigs.k8s.io/kustomize/v3/pkg/commands/create" "sigs.k8s.io/kustomize/v3/pkg/commands/edit" "sigs.k8s.io/kustomize/v3/pkg/commands/misc" "sigs.k8s.io/kustomize/v3/pkg/fs" @@ -44,6 +45,7 @@ See https://sigs.k8s.io/kustomize stdOut, fSys, v, rf, pf), edit.NewCmdEdit(fSys, v, uf), + create.NewCmdCreate(fSys, uf), misc.NewCmdConfig(fSys), misc.NewCmdVersion(stdOut), ) diff --git a/pkg/commands/create/create.go b/pkg/commands/create/create.go new file mode 100644 index 000000000..4dcbb516d --- /dev/null +++ b/pkg/commands/create/create.go @@ -0,0 +1,181 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package create + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + + "sigs.k8s.io/kustomize/v3/pkg/commands/kustfile" + "sigs.k8s.io/kustomize/v3/pkg/commands/util" + "sigs.k8s.io/kustomize/v3/pkg/fs" + "sigs.k8s.io/kustomize/v3/pkg/ifc" + "sigs.k8s.io/kustomize/v3/pkg/pgmconfig" +) + +type createFlags struct { + resources string + namespace string + annotations string + labels string + prefix string + suffix string + detectResources bool + detectRecursive bool + path string +} + +// NewCmdCreate returns an instance of 'create' subcommand. +func NewCmdCreate(fSys fs.FileSystem, uf ifc.KunstructuredFactory) *cobra.Command { + opts := createFlags{path: "."} + c := &cobra.Command{ + Use: "create", + Short: "Create a new kustomization in the current directory", + Long: "", + Example: ` + # Create a new overlay from the base '../base". + kustomize create --resources ../base + + # Create a new kustomization detecting resources in the current directory. + kustomize create --autodetect + + # Create a new kustomization with multiple resources and fields set. + kustomize create --resources deployment.yaml,service.yaml,../base --namespace staging --nameprefix acme- +`, + RunE: func(cmd *cobra.Command, args []string) error { + return runCreate(opts, fSys, uf) + }, + } + c.Flags().StringVar( + &opts.resources, + "resources", + "", + "Name of a file containing a file to add to the kustomization file.") + c.Flags().StringVar( + &opts.namespace, + "namespace", + "", + "Set the value of the namespace field in the customization file.") + c.Flags().StringVar( + &opts.annotations, + "annotation", + "", + "Add one or more common annotations.") + c.Flags().StringVar( + &opts.labels, + "label", + "", + "Add one or more common labels.") + c.Flags().StringVar( + &opts.prefix, + "nameprefix", + "", + "Sets the value of the namePrefix field in the kustomization file.") + c.Flags().StringVar( + &opts.suffix, + "namesuffix", + "", + "Sets the value of the nameSuffix field in the kustomization file.") + c.Flags().BoolVar( + &opts.detectResources, + "autodetect", + false, + "Search for kubernetes resources in the current directory to be added to the kustomization file.") + c.Flags().BoolVar( + &opts.detectRecursive, + "recursive", + false, + "Enable recursive directory searching for resource auto-detection.") + return c +} + +func runCreate(opts createFlags, fSys fs.FileSystem, uf ifc.KunstructuredFactory) error { + resources, err := util.GlobPatterns(fSys, strings.Split(opts.resources, ",")) + if err != nil { + return err + } + if _, err := kustfile.NewKustomizationFile(fSys); err == nil { + return fmt.Errorf("kustomization file already exists") + } + if opts.detectResources { + detected, err := detectResources(fSys, uf, opts.path, opts.detectRecursive) + if err != nil { + return err + } + for _, resource := range detected { + if kustfile.StringInSlice(resource, resources) { + continue + } + resources = append(resources, resource) + } + } + f, err := fSys.Create("kustomization.yaml") + if err != nil { + return err + } + f.Close() + mf, err := kustfile.NewKustomizationFile(fSys) + if err != nil { + return err + } + m, err := mf.Read() + if err != nil { + return err + } + m.Resources = resources + m.Namespace = opts.namespace + m.NamePrefix = opts.prefix + m.NameSuffix = opts.suffix + annotations, err := util.ConvertToMap(opts.annotations, "annotation") + if err != nil { + return err + } + m.CommonAnnotations = annotations + labels, err := util.ConvertToMap(opts.labels, "label") + if err != nil { + return err + } + m.CommonLabels = labels + return mf.Write(m) +} + +func detectResources(fSys fs.FileSystem, uf ifc.KunstructuredFactory, base string, recursive bool) ([]string, error) { + var paths []string + err := fSys.Walk(base, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if path == base { + return nil + } + if info.IsDir() { + if !recursive { + return filepath.SkipDir + } + // If a sub-directory contains an existing kustomization file add the + // directory as a resource and do not decend into it. + for _, kfilename := range pgmconfig.KustomizationFileNames { + if fSys.Exists(filepath.Join(path, kfilename)) { + paths = append(paths, path) + return filepath.SkipDir + } + } + return nil + } + fContents, err := fSys.ReadFile(path) + if err != nil { + return err + } + if _, err := uf.SliceFromBytes(fContents); err != nil { + return nil + } + paths = append(paths, path) + return nil + }) + return paths, err +} diff --git a/pkg/commands/create/create_test.go b/pkg/commands/create/create_test.go new file mode 100644 index 000000000..6bf1840db --- /dev/null +++ b/pkg/commands/create/create_test.go @@ -0,0 +1,183 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package create + +import ( + "reflect" + "testing" + + "sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct" + "sigs.k8s.io/kustomize/v3/pkg/commands/kustfile" + "sigs.k8s.io/kustomize/v3/pkg/fs" + "sigs.k8s.io/kustomize/v3/pkg/types" +) + +var factory = kunstruct.NewKunstructuredFactoryImpl() + +func readKustomizationFS(t *testing.T, fakeFS fs.FileSystem) *types.Kustomization { + kf, err := kustfile.NewKustomizationFile(fakeFS) + if err != nil { + t.Errorf("unexpected new error %v", err) + } + m, err := kf.Read() + if err != nil { + t.Errorf("unexpected read error %v", err) + } + return m +} +func TestCreateNoArgs(t *testing.T) { + fakeFS := fs.MakeFakeFS() + cmd := NewCmdCreate(fakeFS, factory) + err := cmd.RunE(cmd, []string{}) + if err != nil { + t.Errorf("unexpected cmd error: %v", err) + } + readKustomizationFS(t, fakeFS) +} + +func TestCreateWithResources(t *testing.T) { + fakeFS := fs.MakeFakeFS() + fakeFS.WriteFile("foo.yaml", []byte("")) + fakeFS.WriteFile("bar.yaml", []byte("")) + opts := createFlags{resources: "foo.yaml,bar.yaml"} + err := runCreate(opts, fakeFS, factory) + if err != nil { + t.Errorf("unexpected cmd error: %v", err) + } + m := readKustomizationFS(t, fakeFS) + expected := []string{"foo.yaml", "bar.yaml"} + if !reflect.DeepEqual(m.Resources, expected) { + t.Fatalf("expected %+v but got %+v", expected, m.Resources) + } +} + +func TestCreateWithNamespace(t *testing.T) { + fakeFS := fs.MakeFakeFS() + want := "foo" + opts := createFlags{namespace: want} + err := runCreate(opts, fakeFS, factory) + if err != nil { + t.Errorf("unexpected cmd error: %v", err) + } + m := readKustomizationFS(t, fakeFS) + got := m.Namespace + if got != want { + t.Errorf("want: %s, got: %s", want, got) + } +} + +func TestCreateWithLabels(t *testing.T) { + fakeFS := fs.MakeFakeFS() + opts := createFlags{labels: "foo:bar"} + err := runCreate(opts, fakeFS, factory) + if err != nil { + t.Errorf("unexpected cmd error: %v", err) + } + m := readKustomizationFS(t, fakeFS) + expected := map[string]string{"foo": "bar"} + if !reflect.DeepEqual(m.CommonLabels, expected) { + t.Fatalf("expected %+v but got %+v", expected, m.CommonLabels) + } +} + +func TestCreateWithAnnotations(t *testing.T) { + fakeFS := fs.MakeFakeFS() + opts := createFlags{annotations: "foo:bar"} + err := runCreate(opts, fakeFS, factory) + if err != nil { + t.Errorf("unexpected cmd error: %v", err) + } + m := readKustomizationFS(t, fakeFS) + expected := map[string]string{"foo": "bar"} + if !reflect.DeepEqual(m.CommonAnnotations, expected) { + t.Fatalf("expected %+v but got %+v", expected, m.CommonAnnotations) + } +} + +func TestCreateWithNamePrefix(t *testing.T) { + fakeFS := fs.MakeFakeFS() + want := "foo-" + opts := createFlags{prefix: want} + err := runCreate(opts, fakeFS, factory) + if err != nil { + t.Errorf("unexpected cmd error: %v", err) + } + m := readKustomizationFS(t, fakeFS) + got := m.NamePrefix + if got != want { + t.Errorf("want: %s, got: %s", want, got) + } +} + +func TestCreateWithNameSuffix(t *testing.T) { + fakeFS := fs.MakeFakeFS() + opts := createFlags{suffix: "-foo"} + err := runCreate(opts, fakeFS, factory) + if err != nil { + t.Errorf("unexpected cmd error: %v", err) + } + m := readKustomizationFS(t, fakeFS) + if m.NameSuffix != "-foo" { + t.Errorf("want: -foo, got: %s", m.NameSuffix) + } +} + +func writeDetectContent(fakeFS fs.FileSystem) { + fakeFS.WriteFile("/test.yaml", []byte(` +apiVersion: v1 +kind: Service +metadata: + name: test`)) + fakeFS.WriteFile("/README.md", []byte(` +# Not a k8s resource +This file is not a valid kubernetes object.`)) + fakeFS.Mkdir("/sub") + fakeFS.WriteFile("/sub/test.yaml", []byte(` +apiVersion: v1 +kind: Service +metadata: + name: test2`)) + fakeFS.WriteFile("/sub/README.md", []byte(` +# Not a k8s resource +This file in a subdirectory is not a valid kubernetes object.`)) + fakeFS.Mkdir("/overlay") + fakeFS.WriteFile("/overlay/test.yaml", []byte(` +apiVersion: v1 +kind: Service +metadata: + name: test3`)) + fakeFS.WriteFile("/overlay/kustomization.yaml", []byte(` +resources: +- test.yaml`)) +} + +func TestCreateWithDetect(t *testing.T) { + fakeFS := fs.MakeFakeFS() + writeDetectContent(fakeFS) + opts := createFlags{path: "/", detectResources: true} + err := runCreate(opts, fakeFS, factory) + if err != nil { + t.Fatalf("unexpected cmd error: %v", err) + } + m := readKustomizationFS(t, fakeFS) + expected := []string{"/test.yaml"} + if !reflect.DeepEqual(m.Resources, expected) { + t.Fatalf("expected %+v but got %+v", expected, m.Resources) + } +} + +func TestCreateWithDetectRecursive(t *testing.T) { + fakeFS := fs.MakeFakeFS() + writeDetectContent(fakeFS) + opts := createFlags{path: "/", detectResources: true, detectRecursive: true} + err := runCreate(opts, fakeFS, factory) + if err != nil { + t.Fatalf("unexpected cmd error: %v", err) + } + m := readKustomizationFS(t, fakeFS) + expected := []string{"/overlay", "/sub/test.yaml", "/test.yaml"} + if !reflect.DeepEqual(m.Resources, expected) { + t.Fatalf("expected %+v but got %+v", expected, m.Resources) + } +} diff --git a/pkg/commands/edit/add/addmetadata.go b/pkg/commands/edit/add/addmetadata.go index 87099c16f..85e7e600f 100644 --- a/pkg/commands/edit/add/addmetadata.go +++ b/pkg/commands/edit/add/addmetadata.go @@ -18,10 +18,10 @@ package add import ( "fmt" - "strings" "github.com/spf13/cobra" "sigs.k8s.io/kustomize/v3/pkg/commands/kustfile" + "sigs.k8s.io/kustomize/v3/pkg/commands/util" "sigs.k8s.io/kustomize/v3/pkg/fs" "sigs.k8s.io/kustomize/v3/pkg/pgmconfig" "sigs.k8s.io/kustomize/v3/pkg/types" @@ -122,7 +122,7 @@ func (o *addMetadataOptions) validateAndParse(args []string) error { if len(args) > 1 { return fmt.Errorf("%ss must be comma-separated, with no spaces", o.kind) } - m, err := o.convertToMap(args[0]) + m, err := util.ConvertToMap(args[0], o.kind.String()) if err != nil { return err } @@ -133,27 +133,6 @@ func (o *addMetadataOptions) validateAndParse(args []string) error { return nil } -func (o *addMetadataOptions) convertToMap(arg string) (map[string]string, error) { - result := make(map[string]string) - inputs := strings.Split(arg, ",") - for _, input := range inputs { - c := strings.Index(input, ":") - if c == 0 { - // key is not passed - return nil, o.makeError(input, "need k:v pair where v may be quoted") - } else if c < 0 { - // only key passed - result[input] = "" - } else { - // both key and value passed - key := input[:c] - value := trimQuotes(input[c+1:]) - result[key] = value - } - } - return result, nil -} - func (o *addMetadataOptions) addAnnotations(m *types.Kustomization) error { if m.CommonAnnotations == nil { m.CommonAnnotations = make(map[string]string) @@ -177,16 +156,3 @@ func (o *addMetadataOptions) writeToMap(m map[string]string, kind kindOfAdd) err } return nil } - -func (o *addMetadataOptions) makeError(input string, message string) error { - return fmt.Errorf("invalid %s: '%s' (%s)", o.kind, input, message) -} - -func trimQuotes(s string) string { - if len(s) >= 2 { - if s[0] == '"' && s[len(s)-1] == '"' { - return s[1 : len(s)-1] - } - } - return s -} diff --git a/pkg/commands/edit/add/addmetadata_test.go b/pkg/commands/edit/add/addmetadata_test.go index cee138687..de26f8c18 100644 --- a/pkg/commands/edit/add/addmetadata_test.go +++ b/pkg/commands/edit/add/addmetadata_test.go @@ -17,7 +17,6 @@ limitations under the License. package add import ( - "reflect" "testing" "sigs.k8s.io/kustomize/v3/pkg/commands/kustfile" @@ -362,36 +361,3 @@ func TestAddLabelForce(t *testing.T) { t.Errorf("unexpected error: %v", err.Error()) } } - -func TestConvertToMap(t *testing.T) { - var o addMetadataOptions - args := "a:b,c:\"d\",e:\"f:g\",g:h:k" - expected := make(map[string]string) - expected["a"] = "b" - expected["c"] = "d" - expected["e"] = "f:g" - expected["g"] = "h:k" - - result, err := o.convertToMap(args) - if err != nil { - t.Errorf("unexpected error: %v", err.Error()) - } - - eq := reflect.DeepEqual(expected, result) - if !eq { - t.Errorf("Converted map does not match expected, expected: %v, result: %v\n", expected, result) - } -} - -func TestConvertToMapError(t *testing.T) { - var o addMetadataOptions - args := "a:b,c:\"d\",:f:g" - - _, err := o.convertToMap(args) - if err == nil { - t.Errorf("expected an error") - } - if err.Error() != "invalid annotation: ':f:g' (need k:v pair where v may be quoted)" { - t.Errorf("incorrect error: %v", err.Error()) - } -} diff --git a/pkg/commands/edit/add/addpatch.go b/pkg/commands/edit/add/addpatch.go index 7ab1e871b..4eb1ae64a 100644 --- a/pkg/commands/edit/add/addpatch.go +++ b/pkg/commands/edit/add/addpatch.go @@ -21,8 +21,8 @@ import ( "log" "github.com/spf13/cobra" - "sigs.k8s.io/kustomize/v3/pkg/commands/edit/util" "sigs.k8s.io/kustomize/v3/pkg/commands/kustfile" + "sigs.k8s.io/kustomize/v3/pkg/commands/util" "sigs.k8s.io/kustomize/v3/pkg/fs" "sigs.k8s.io/kustomize/v3/pkg/patch" ) diff --git a/pkg/commands/edit/add/addresource.go b/pkg/commands/edit/add/addresource.go index 37c63a5ea..28b950eeb 100644 --- a/pkg/commands/edit/add/addresource.go +++ b/pkg/commands/edit/add/addresource.go @@ -21,8 +21,8 @@ import ( "log" "github.com/spf13/cobra" - "sigs.k8s.io/kustomize/v3/pkg/commands/edit/util" "sigs.k8s.io/kustomize/v3/pkg/commands/kustfile" + "sigs.k8s.io/kustomize/v3/pkg/commands/util" "sigs.k8s.io/kustomize/v3/pkg/fs" ) diff --git a/pkg/commands/edit/add/flagsandargs.go b/pkg/commands/edit/add/flagsandargs.go index 85807c2ac..60aab3ae1 100644 --- a/pkg/commands/edit/add/flagsandargs.go +++ b/pkg/commands/edit/add/flagsandargs.go @@ -20,7 +20,7 @@ import ( "fmt" "strings" - "sigs.k8s.io/kustomize/v3/pkg/commands/edit/util" + "sigs.k8s.io/kustomize/v3/pkg/commands/util" "sigs.k8s.io/kustomize/v3/pkg/fs" ) diff --git a/pkg/commands/edit/remove/removepatch.go b/pkg/commands/edit/remove/removepatch.go index f40995332..cdc353e6e 100644 --- a/pkg/commands/edit/remove/removepatch.go +++ b/pkg/commands/edit/remove/removepatch.go @@ -8,8 +8,8 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" - "sigs.k8s.io/kustomize/v3/pkg/commands/edit/util" "sigs.k8s.io/kustomize/v3/pkg/commands/kustfile" + "sigs.k8s.io/kustomize/v3/pkg/commands/util" "sigs.k8s.io/kustomize/v3/pkg/fs" "sigs.k8s.io/kustomize/v3/pkg/patch" "sigs.k8s.io/kustomize/v3/pkg/pgmconfig" diff --git a/pkg/commands/edit/util/functions.go b/pkg/commands/edit/util/functions.go deleted file mode 100644 index 407e13027..000000000 --- a/pkg/commands/edit/util/functions.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package util - -import ( - "log" - - "sigs.k8s.io/kustomize/v3/pkg/fs" -) - -func GlobPatterns(fsys fs.FileSystem, patterns []string) ([]string, error) { - var result []string - for _, pattern := range patterns { - files, err := fsys.Glob(pattern) - if err != nil { - return nil, err - } - if len(files) == 0 { - log.Printf("%s has no match", pattern) - continue - } - result = append(result, files...) - } - return result, nil -} diff --git a/pkg/commands/util/util.go b/pkg/commands/util/util.go new file mode 100644 index 000000000..cc75353b0 --- /dev/null +++ b/pkg/commands/util/util.go @@ -0,0 +1,62 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package util + +import ( + "fmt" + "log" + "strings" + + "sigs.k8s.io/kustomize/v3/pkg/fs" +) + +// GlobPatterns accepts a slice of glob strings and returns the set of +// matching file paths. +func GlobPatterns(fsys fs.FileSystem, patterns []string) ([]string, error) { + var result []string + for _, pattern := range patterns { + files, err := fsys.Glob(pattern) + if err != nil { + return nil, err + } + if len(files) == 0 { + log.Printf("%s has no match", pattern) + continue + } + result = append(result, files...) + } + return result, nil +} + +// ConvertToMap converts a slice of strings in the form of +// `key:value` into a map. +func ConvertToMap(input string, kind string) (map[string]string, error) { + result := make(map[string]string) + inputs := strings.Split(input, ",") + for _, input := range inputs { + c := strings.Index(input, ":") + if c == 0 { + // key is not passed + return nil, fmt.Errorf("invalid %s: '%s' (%s)", kind, input, "need k:v pair where v may be quoted") + } else if c < 0 { + // only key passed + result[input] = "" + } else { + // both key and value passed + key := input[:c] + value := trimQuotes(input[c+1:]) + result[key] = value + } + } + return result, nil +} + +func trimQuotes(s string) string { + if len(s) >= 2 { + if s[0] == '"' && s[len(s)-1] == '"' { + return s[1 : len(s)-1] + } + } + return s +} diff --git a/pkg/commands/util/util_test.go b/pkg/commands/util/util_test.go new file mode 100644 index 000000000..5828de090 --- /dev/null +++ b/pkg/commands/util/util_test.go @@ -0,0 +1,37 @@ +package util + +import ( + "reflect" + "testing" +) + +func TestConvertToMap(t *testing.T) { + args := "a:b,c:\"d\",e:\"f:g\",g:h:k" + expected := make(map[string]string) + expected["a"] = "b" + expected["c"] = "d" + expected["e"] = "f:g" + expected["g"] = "h:k" + + result, err := ConvertToMap(args, "annotation") + if err != nil { + t.Errorf("unexpected error: %v", err.Error()) + } + + eq := reflect.DeepEqual(expected, result) + if !eq { + t.Errorf("Converted map does not match expected, expected: %v, result: %v\n", expected, result) + } +} + +func TestConvertToMapError(t *testing.T) { + args := "a:b,c:\"d\",:f:g" + + _, err := ConvertToMap(args, "annotation") + if err == nil { + t.Errorf("expected an error") + } + if err.Error() != "invalid annotation: ':f:g' (need k:v pair where v may be quoted)" { + t.Errorf("incorrect error: %v", err.Error()) + } +} diff --git a/pkg/fs/fakefs.go b/pkg/fs/fakefs.go index 05d124b72..516fa3098 100644 --- a/pkg/fs/fakefs.go +++ b/pkg/fs/fakefs.go @@ -18,6 +18,7 @@ package fs import ( "fmt" + "os" "path/filepath" "sort" "strings" @@ -179,7 +180,87 @@ func (fs *fakeFs) WriteTestKustomizationWith(bytes []byte) { fs.WriteFile(pgmconfig.KustomizationFileNames[0], bytes) } +// Walk implements filepath.Walk using the fake filesystem. +func (fs *fakeFs) Walk(path string, walkFn filepath.WalkFunc) error { + info, err := fs.lstat(path) + if err != nil { + err = walkFn(path, info, err) + } else { + err = fs.walk(path, info, walkFn) + } + if err == filepath.SkipDir { + return nil + } + return err +} + func (fs *fakeFs) pathMatch(path, pattern string) bool { match, _ := filepath.Match(pattern, path) return match } + +func (fs *fakeFs) lstat(path string) (*Fakefileinfo, error) { + f, found := fs.m[path] + if !found { + return nil, os.ErrNotExist + } + return &Fakefileinfo{f}, nil +} + +func (fs *fakeFs) join(elem ...string) string { + for i, e := range elem { + if e != "" { + return strings.Replace(strings.Join(elem[i:], "/"), "//", "/", -1) + } + } + return "" +} + +func (fs *fakeFs) readDirNames(path string) []string { + var names []string + if !strings.HasSuffix(path, "/") { + path += "/" + } + pathSegments := strings.Count(path, "/") + for name := range fs.m { + if name == path { + continue + } + if strings.Count(name, "/") > pathSegments { + continue + } + if strings.HasPrefix(name, path) { + names = append(names, filepath.Base(name)) + } + } + sort.Strings(names) + return names +} + +func (fs *fakeFs) walk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { + if !info.IsDir() { + return walkFn(path, info, nil) + } + + names := fs.readDirNames(path) + if err := walkFn(path, info, nil); err != nil { + return err + } + for _, name := range names { + filename := fs.join(path, name) + fileInfo, err := fs.lstat(filename) + if err != nil { + if err := walkFn(filename, fileInfo, os.ErrNotExist); err != nil && err != filepath.SkipDir { + return err + } + } else { + err = fs.walk(filename, fileInfo, walkFn) + if err != nil { + if !fileInfo.IsDir() || err != filepath.SkipDir { + return err + } + } + } + } + return nil +} diff --git a/pkg/fs/fs.go b/pkg/fs/fs.go index 4b47dba64..f66037057 100644 --- a/pkg/fs/fs.go +++ b/pkg/fs/fs.go @@ -20,6 +20,7 @@ package fs import ( "io" "os" + "path/filepath" ) // FileSystem groups basic os filesystem methods. @@ -35,6 +36,7 @@ type FileSystem interface { Glob(pattern string) ([]string, error) ReadFile(name string) ([]byte, error) WriteFile(name string, data []byte) error + Walk(path string, walkFn filepath.WalkFunc) error } // File groups the basic os.File methods. diff --git a/pkg/fs/realfs.go b/pkg/fs/realfs.go index 11e5813b7..0fce64f3a 100644 --- a/pkg/fs/realfs.go +++ b/pkg/fs/realfs.go @@ -120,3 +120,8 @@ func (realFS) ReadFile(name string) ([]byte, error) { return ioutil.ReadFile(nam func (realFS) WriteFile(name string, c []byte) error { return ioutil.WriteFile(name, c, 0666) } + +// Walk delegates to filepath.Walk. +func (realFS) Walk(path string, walkFn filepath.WalkFunc) error { + return filepath.Walk(path, walkFn) +}