From b8c1601a93e348f2627fc05ebddb358347b7c5a3 Mon Sep 17 00:00:00 2001 From: Phani Teja Marupaka Date: Tue, 8 Sep 2020 18:42:31 -0700 Subject: [PATCH] Cat with subpackages --- cmd/config/internal/commands/cat.go | 132 +++++++++++++++------- cmd/config/internal/commands/cat_test.go | 138 +++++++++++++++++++++++ 2 files changed, 227 insertions(+), 43 deletions(-) diff --git a/cmd/config/internal/commands/cat.go b/cmd/config/internal/commands/cat.go index 5db747fdb..954e9544d 100644 --- a/cmd/config/internal/commands/cat.go +++ b/cmd/config/internal/commands/cat.go @@ -5,9 +5,11 @@ package commands import ( "fmt" + "io" "os" "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/cmd/config/ext" "sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands" "sigs.k8s.io/kustomize/kyaml/errors" "sigs.k8s.io/kustomize/kyaml/kio" @@ -19,15 +21,14 @@ import ( func GetCatRunner(name string) *CatRunner { r := &CatRunner{} c := &cobra.Command{ - Use: "cat DIR...", + Use: "cat DIR", Short: commands.CatShort, Long: commands.CatLong, Example: commands.CatExamples, RunE: r.runE, + Args: cobra.MaximumNArgs(1), } fixDocs(name, c) - c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true, - "also print resources from subpackages.") c.Flags().BoolVar(&r.Format, "format", true, "format resource config yaml before printing.") c.Flags().BoolVar(&r.KeepAnnotations, "annotate", false, @@ -49,6 +50,8 @@ func GetCatRunner(name string) *CatRunner { "if true, exclude non-local-config in the output.") c.Flags().StringVar(&r.OutputDest, "dest", "", "if specified, write output to a file rather than stdout") + c.Flags().BoolVarP(&r.RecurseSubPackages, "recurse-subpackages", "R", true, + "print resources recursively in all the nested subpackages") r.Command = c return r } @@ -59,7 +62,6 @@ func CatCommand(name string) *cobra.Command { // CatRunner contains the run function type CatRunner struct { - IncludeSubpackages bool Format bool KeepAnnotations bool WrapKind string @@ -71,56 +73,101 @@ type CatRunner struct { IncludeLocal bool ExcludeNonLocal bool Command *cobra.Command + RecurseSubPackages bool } func (r *CatRunner) runE(c *cobra.Command, args []string) error { - // if there is a function-config specified, emit it + var writer = c.OutOrStdout() + if r.OutputDest != "" { + o, err := os.Create(r.OutputDest) + if err != nil { + return errors.Wrap(err) + } + defer o.Close() + writer = o + } + if len(args) == 0 { + input := &kio.ByteReader{Reader: c.InOrStdin()} + // if there is a function-config specified, emit it + outputs, err := r.out(writer) + if err != nil { + return err + } + return handleError(c, kio.Pipeline{Inputs: []kio.Reader{input}, Filters: r.catFilters(), Outputs: outputs}.Execute()) + } + + e := executeCmdOnPkgs{ + writer: writer, + needOpenAPI: false, + recurseSubPackages: r.RecurseSubPackages, + cmdRunner: r, + rootPkgPath: args[0], + } + + return e.execute() +} + +func (r *CatRunner) executeCmd(w io.Writer, pkgPath string) error { + openAPIFileName, err := ext.OpenAPIFileName() + if err != nil { + return err + } + + input := kio.LocalPackageReader{PackagePath: pkgPath, PackageFileName: openAPIFileName} + outputs, err := r.out(w) + if err != nil { + return err + } + err = kio.Pipeline{ + Inputs: []kio.Reader{input}, + Filters: r.catFilters(), + Outputs: outputs, + }.Execute() + + if err != nil { + // return err if there is only package + if !r.RecurseSubPackages { + return err + } else { + // print error message and continue if there are multiple packages to annotate + fmt.Fprintf(w, "%s in package %q\n", err.Error(), pkgPath) + } + } + fmt.Fprintf(w, "---\n") + return nil +} + +func (r *CatRunner) catFilters() []kio.Filter { + var fltrs []kio.Filter + // don't include reconcilers + fltrs = append(fltrs, &filters.IsLocalConfig{ + IncludeLocalConfig: r.IncludeLocal, + ExcludeNonLocalConfig: r.ExcludeNonLocal, + }) + if r.Format { + fltrs = append(fltrs, filters.FormatFilter{}) + } + if r.StripComments { + fltrs = append(fltrs, filters.StripCommentsFilter{}) + } + return fltrs +} + +func (r *CatRunner) out(w io.Writer) ([]kio.Writer, error) { + var outputs []kio.Writer var functionConfig *yaml.RNode if r.FunctionConfig != "" { configs, err := kio.LocalPackageReader{PackagePath: r.FunctionConfig, OmitReaderAnnotations: !r.KeepAnnotations}.Read() if err != nil { - return err + return outputs, err } if len(configs) != 1 { - return fmt.Errorf("expected exactly 1 functionConfig, found %d", len(configs)) + return outputs, fmt.Errorf("expected exactly 1 functionConfig, found %d", len(configs)) } functionConfig = configs[0] } - var inputs []kio.Reader - for _, a := range args { - inputs = append(inputs, kio.LocalPackageReader{ - PackagePath: a, - IncludeSubpackages: r.IncludeSubpackages, - }) - } - if len(inputs) == 0 { - inputs = append(inputs, &kio.ByteReader{Reader: c.InOrStdin()}) - } - var fltr []kio.Filter - // don't include reconcilers - fltr = append(fltr, &filters.IsLocalConfig{ - IncludeLocalConfig: r.IncludeLocal, - ExcludeNonLocalConfig: r.ExcludeNonLocal, - }) - if r.Format { - fltr = append(fltr, filters.FormatFilter{}) - } - if r.StripComments { - fltr = append(fltr, filters.StripCommentsFilter{}) - } - - var out = c.OutOrStdout() - if r.OutputDest != "" { - o, err := os.Create(r.OutputDest) - if err != nil { - return handleError(c, errors.Wrap(err)) - } - defer o.Close() - out = o - } - // remove this annotation explicitly, the ByteWriter won't clear it by // default because it doesn't set it clear := []string{"config.kubernetes.io/path"} @@ -128,9 +175,8 @@ func (r *CatRunner) runE(c *cobra.Command, args []string) error { clear = nil } - var outputs []kio.Writer outputs = append(outputs, kio.ByteWriter{ - Writer: out, + Writer: w, KeepReaderAnnotations: r.KeepAnnotations, WrappingKind: r.WrapKind, WrappingAPIVersion: r.WrapApiVersion, @@ -139,5 +185,5 @@ func (r *CatRunner) runE(c *cobra.Command, args []string) error { ClearAnnotations: clear, }) - return handleError(c, kio.Pipeline{Inputs: inputs, Filters: fltr, Outputs: outputs}.Execute()) + return outputs, nil } diff --git a/cmd/config/internal/commands/cat_test.go b/cmd/config/internal/commands/cat_test.go index 089d8c9b5..3035cde68 100644 --- a/cmd/config/internal/commands/cat_test.go +++ b/cmd/config/internal/commands/cat_test.go @@ -8,10 +8,13 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "testing" "github.com/stretchr/testify/assert" "sigs.k8s.io/kustomize/cmd/config/internal/commands" + "sigs.k8s.io/kustomize/kyaml/copyutil" + "sigs.k8s.io/kustomize/kyaml/openapi" ) // TODO(pwittrock): write tests for reading / writing ResourceLists @@ -112,6 +115,7 @@ metadata: app: nginx spec: replicas: 3 +--- `, b.String()) { return } @@ -223,6 +227,7 @@ metadata: app: nginx spec: replicas: 3 +--- `, b.String()) { return } @@ -305,6 +310,7 @@ metadata: image: gcr.io/example/reconciler:v1 spec: replicas: 3 +--- `, b.String()) { return } @@ -420,6 +426,7 @@ metadata: app: nginx spec: replicas: 3 +--- `, string(actual)) { return } @@ -536,7 +543,138 @@ metadata: app: nginx spec: replicas: 3 +--- `, string(actual)) { return } } + +func TestCatSubPackages(t *testing.T) { + var tests = []struct { + name string + dataset string + packagePath string + args []string + expected string + }{ + { + name: "cat-recurse-subpackages", + dataset: "dataset-without-setters", + expected: ` +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mysql-deployment + namespace: myspace +spec: + replicas: 3 + template: + spec: + containers: + - name: mysql + image: mysql:1.7.9 +--- +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: storage-deployment + namespace: myspace +spec: + replicas: 4 + template: + spec: + containers: + - name: storage + image: storage:1.7.7 +--- +`, + }, + { + name: "cat-top-level-pkg-no-recurse-subpackages", + dataset: "dataset-without-setters", + args: []string{"-R=false"}, + packagePath: "mysql", + expected: ` +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mysql-deployment + namespace: myspace +spec: + replicas: 3 + template: + spec: + containers: + - name: mysql + image: mysql:1.7.9 +--- +`, + }, + { + name: "cat-nested-pkg-no-recurse-subpackages", + dataset: "dataset-without-setters", + packagePath: "mysql/storage", + args: []string{"-R=false"}, + expected: ` +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: storage-deployment + namespace: myspace +spec: + replicas: 4 + template: + spec: + containers: + - name: storage + image: storage:1.7.7 +---`, + }, + } + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + // reset the openAPI afterward + openapi.ResetOpenAPI() + defer openapi.ResetOpenAPI() + sourceDir := filepath.Join("test", "testdata", test.dataset) + baseDir, err := ioutil.TempDir("", "") + if !assert.NoError(t, err) { + t.FailNow() + } + copyutil.CopyDir(sourceDir, baseDir) + defer os.RemoveAll(baseDir) + runner := commands.GetCatRunner("") + actual := &bytes.Buffer{} + runner.Command.SetOut(actual) + runner.Command.SetArgs(append([]string{filepath.Join(baseDir, test.packagePath)}, test.args...)) + err = runner.Command.Execute() + if !assert.NoError(t, err) { + t.FailNow() + } + + // normalize path format for windows + actualNormalized := strings.Replace( + strings.Replace(actual.String(), "\\", "/", -1), + "//", "/", -1) + + expected := strings.Replace(test.expected, "${baseDir}", baseDir, -1) + expectedNormalized := strings.Replace(expected, "\\", "/", -1) + if !assert.Equal(t, strings.TrimSpace(expectedNormalized), strings.TrimSpace(actualNormalized)) { + t.FailNow() + } + }) + } +}