diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/BUILD b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/BUILD index 22b34de0087..7224780e3ea 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/BUILD +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/BUILD @@ -35,12 +35,16 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library", + "//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps:go_default_library", "//staging/src/k8s.io/client-go/discovery:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library", "//staging/src/k8s.io/client-go/rest:go_default_library", "//staging/src/k8s.io/client-go/restmapper:go_default_library", "//vendor/golang.org/x/text/encoding/unicode:go_default_library", "//vendor/golang.org/x/text/transform:go_default_library", + "//vendor/sigs.k8s.io/kustomize/pkg/commands/build:go_default_library", + "//vendor/sigs.k8s.io/kustomize/pkg/constants:go_default_library", + "//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library", ], ) diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder.go index 42f660a4e53..c67be8651f7 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder.go +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder.go @@ -130,8 +130,9 @@ func IsUsageError(err error) bool { } type FilenameOptions struct { - Filenames []string - Recursive bool + Filenames []string + Recursive bool + EnableKustomization bool } type resourceTuple struct { @@ -197,6 +198,7 @@ func (b *Builder) AddError(err error) *Builder { // recognized will be ignored (but logged at V(2)). func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *FilenameOptions) *Builder { recursive := filenameOptions.Recursive + enableKustomization := filenameOptions.EnableKustomization paths := filenameOptions.Filenames for _, s := range paths { switch { @@ -213,7 +215,7 @@ func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *Filename if !recursive { b.singleItemImplied = true } - b.Path(recursive, s) + b.Path(recursive, enableKustomization, s) } } @@ -332,7 +334,7 @@ func (b *Builder) Stream(r io.Reader, name string) *Builder { // FileVisitor is streaming the content to a StreamVisitor. If ContinueOnError() is set // prior to this method being called, objects on the path that are unrecognized will be // ignored (but logged at V(2)). -func (b *Builder) Path(recursive bool, paths ...string) *Builder { +func (b *Builder) Path(recursive, enableKustomization bool, paths ...string) *Builder { for _, p := range paths { _, err := os.Stat(p) if os.IsNotExist(err) { @@ -344,7 +346,7 @@ func (b *Builder) Path(recursive bool, paths ...string) *Builder { continue } - visitors, err := ExpandPathsToFileVisitors(b.mapper, p, recursive, FileExtensions, b.schema) + visitors, err := ExpandPathsToFileVisitors(b.mapper, p, recursive, enableKustomization, FileExtensions, b.schema) if err != nil { b.errs = append(b.errs, fmt.Errorf("error reading %q: %v", p, err)) } diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go index 32c1a691a5a..1b21c38d394 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go @@ -20,6 +20,7 @@ import ( "bytes" "fmt" "io" + "io/ioutil" "net/http" "net/url" "os" @@ -38,6 +39,10 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apimachinery/pkg/watch" + "k8s.io/cli-runtime/pkg/kustomize/k8sdeps" + "sigs.k8s.io/kustomize/pkg/commands/build" + "sigs.k8s.io/kustomize/pkg/constants" + "sigs.k8s.io/kustomize/pkg/fs" ) const ( @@ -446,13 +451,16 @@ func FileVisitorForSTDIN(mapper *mapper, schema ContentValidator) Visitor { // ExpandPathsToFileVisitors will return a slice of FileVisitors that will handle files from the provided path. // After FileVisitors open the files, they will pass an io.Reader to a StreamVisitor to do the reading. (stdin // is also taken care of). Paths argument also accepts a single file, and will return a single visitor -func ExpandPathsToFileVisitors(mapper *mapper, paths string, recursive bool, extensions []string, schema ContentValidator) ([]Visitor, error) { +func ExpandPathsToFileVisitors(mapper *mapper, paths string, recursive bool, enableKustomize bool, extensions []string, schema ContentValidator) ([]Visitor, error) { var visitors []Visitor err := filepath.Walk(paths, func(path string, fi os.FileInfo, err error) error { if err != nil { return err } - + if enableKustomize && isKustomizationDir(path) { + visitors = append(visitors, NewKustomizationVisitor(mapper, path, schema)) + return filepath.SkipDir + } if fi.IsDir() { if path != paths && !recursive { return filepath.SkipDir @@ -463,7 +471,10 @@ func ExpandPathsToFileVisitors(mapper *mapper, paths string, recursive bool, ext if path != paths && ignoreFile(path, extensions) { return nil } - + if enableKustomize && filepath.Base(path) == constants.KustomizationFileName { + visitors = append(visitors, NewKustomizationVisitor(mapper, filepath.Dir(path), schema)) + return nil + } visitor := &FileVisitor{ Path: path, StreamVisitor: NewStreamVisitor(nil, mapper, path, schema), @@ -479,6 +490,13 @@ func ExpandPathsToFileVisitors(mapper *mapper, paths string, recursive bool, ext return visitors, nil } +func isKustomizationDir(path string) bool { + if _, err := os.Stat(filepath.Join(path, constants.KustomizationFileName)); err == nil { + return true + } + return false +} + // FileVisitor is wrapping around a StreamVisitor, to handle open/close files type FileVisitor struct { Path string @@ -507,6 +525,37 @@ func (v *FileVisitor) Visit(fn VisitorFunc) error { return v.StreamVisitor.Visit(fn) } +// KustomizationVisitor prorvides the output of kustomization build +type KustomizationVisitor struct { + Path string + *StreamVisitor +} + +// Visit in a KustomizationVisitor build the kustomization output +func (v *KustomizationVisitor) Visit(fn VisitorFunc) error { + fSys := fs.MakeRealFS() + f := k8sdeps.NewFactory() + var out bytes.Buffer + cmd := build.NewCmdBuild(&out, fSys, f.ResmapF, f.TransformerF) + cmd.SetArgs([]string{v.Path}) + // we want to silence usage, error output, and any future output from cobra + // we will get error output as a golang error from execute + cmd.SetOutput(ioutil.Discard) + _, err := cmd.ExecuteC() + if err != nil { + return err + } + v.StreamVisitor.Reader = bytes.NewReader(out.Bytes()) + return v.StreamVisitor.Visit(fn) +} + +func NewKustomizationVisitor(mapper *mapper, path string, schema ContentValidator) *KustomizationVisitor { + return &KustomizationVisitor{ + Path: path, + StreamVisitor: NewStreamVisitor(nil, mapper, path, schema), + } +} + // StreamVisitor reads objects from an io.Reader and walks them. A stream visitor can only be // visited once. // TODO: depends on objects being in JSON format before being passed to decode - need to implement