diff --git a/cmd/config/internal/commands/grep.go b/cmd/config/internal/commands/grep.go index cc430d810..ac0f61857 100644 --- a/cmd/config/internal/commands/grep.go +++ b/cmd/config/internal/commands/grep.go @@ -5,10 +5,12 @@ package commands import ( "fmt" + "io" "strings" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/api/resource" + "sigs.k8s.io/kustomize/cmd/config/ext" "sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands" "sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/kio/filters" @@ -18,13 +20,13 @@ import ( func GetGrepRunner(name string) *GrepRunner { r := &GrepRunner{} c := &cobra.Command{ - Use: "grep QUERY [DIR]...", + Use: "grep QUERY [DIR]", Short: commands.GrepShort, Long: commands.GrepLong, Example: commands.GrepExamples, PreRunE: r.preRunE, RunE: r.runE, - Args: cobra.MinimumNArgs(1), + Args: cobra.MaximumNArgs(2), } fixDocs(name, c) c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true, @@ -33,7 +35,8 @@ func GetGrepRunner(name string) *GrepRunner { "annotate resources with their file origins.") c.Flags().BoolVarP(&r.InvertMatch, "invert-match", "", false, "Selected Resources are those not matching any of the specified patterns..") - + c.Flags().BoolVarP(&r.RecurseSubPackages, "recurse-subpackages", "R", true, + "also print resources recursively in all the nested subpackages") r.Command = c return r } @@ -48,7 +51,8 @@ type GrepRunner struct { KeepAnnotations bool Command *cobra.Command filters.GrepFilter - Format bool + Format bool + RecurseSubPackages bool } func (r *GrepRunner) preRunE(c *cobra.Command, args []string) error { @@ -101,25 +105,56 @@ func (r *GrepRunner) preRunE(c *cobra.Command, args []string) error { } func (r *GrepRunner) runE(c *cobra.Command, args []string) error { - var filters = []kio.Filter{r.GrepFilter} - - var inputs []kio.Reader - for _, a := range args[1:] { - inputs = append(inputs, kio.LocalPackageReader{ - PackagePath: a, - IncludeSubpackages: r.IncludeSubpackages, - }) - } - if len(inputs) == 0 { - inputs = append(inputs, &kio.ByteReader{Reader: c.InOrStdin()}) + if len(args) == 1 { + input := &kio.ByteReader{Reader: c.InOrStdin()} + return handleError(c, kio.Pipeline{ + Inputs: []kio.Reader{input}, + Filters: []kio.Filter{r.GrepFilter}, + Outputs: []kio.Writer{kio.ByteWriter{ + Writer: c.OutOrStdout(), + KeepReaderAnnotations: r.KeepAnnotations, + }}, + }.Execute()) } - return handleError(c, kio.Pipeline{ - Inputs: inputs, - Filters: filters, + e := executeCmdOnPkgs{ + writer: c.OutOrStdout(), + needOpenAPI: false, + recurseSubPackages: r.RecurseSubPackages, + cmdRunner: r, + rootPkgPath: args[1], + } + + return e.execute() + +} + +func (r *GrepRunner) executeCmd(w io.Writer, pkgPath string) error { + openAPIFileName, err := ext.OpenAPIFileName() + if err != nil { + return err + } + + input := kio.LocalPackageReader{PackagePath: pkgPath, PackageFileName: openAPIFileName} + + fmt.Fprintf(w, "%q:\n", pkgPath) + err = kio.Pipeline{ + Inputs: []kio.Reader{input}, + Filters: []kio.Filter{r.GrepFilter}, Outputs: []kio.Writer{kio.ByteWriter{ - Writer: c.OutOrStdout(), + Writer: w, KeepReaderAnnotations: r.KeepAnnotations, }}, - }.Execute()) + }.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) + } + } + return nil } diff --git a/cmd/config/internal/commands/grep_test.go b/cmd/config/internal/commands/grep_test.go index 6c6f15f86..c1f2d8d47 100644 --- a/cmd/config/internal/commands/grep_test.go +++ b/cmd/config/internal/commands/grep_test.go @@ -8,10 +8,12 @@ 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" ) // TestGrepCommand_files verifies grep reads the files and filters them @@ -68,7 +70,7 @@ spec: return } - if !assert.Equal(t, `kind: Deployment + if !assert.Contains(t, b.String(), `kind: Deployment metadata: labels: app: nginx2 @@ -90,7 +92,7 @@ metadata: spec: selector: app: nginx -`, b.String()) { +`) { return } } @@ -136,7 +138,7 @@ spec: return } - if !assert.Equal(t, `kind: Deployment + if !assert.Contains(t, b.String(), `kind: Deployment metadata: labels: app: nginx2 @@ -156,7 +158,7 @@ metadata: spec: selector: app: nginx -`, b.String()) { +`) { return } } @@ -250,7 +252,7 @@ spec: if !assert.NoError(t, err) { return } - if !assert.Equal(t, `kind: Deployment + if !assert.Contains(t, b.String(), `kind: Deployment metadata: labels: app: nginx1.7 @@ -264,7 +266,146 @@ spec: containers: - name: nginx image: nginx:1.7.9 -`, b.String()) { +`) { return } } + +func TestGrepSubPackages(t *testing.T) { + var tests = []struct { + name string + dataset string + packagePath string + args []string + expected string + }{ + { + name: "grep-recurse-subpackages", + dataset: "dataset-without-setters", + args: []string{"kind=Deployment"}, + expected: ` +"${baseDir}/mysql": +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: myspace + name: mysql-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'deployment.yaml' +spec: + replicas: 3 + template: + spec: + containers: + - name: mysql + image: mysql:1.7.9 +"${baseDir}/mysql/storage": +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: myspace + name: storage-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'deployment.yaml' +spec: + replicas: 4 + template: + spec: + containers: + - name: storage + image: storage:1.7.7 +`, + }, + { + name: "grep-top-level-pkg-no-recurse-subpackages", + dataset: "dataset-without-setters", + args: []string{"kind=Deployment", "-R=false"}, + packagePath: "mysql", + expected: ` +"${baseDir}/mysql": +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: myspace + name: mysql-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'deployment.yaml' +spec: + replicas: 3 + template: + spec: + containers: + - name: mysql + image: mysql:1.7.9`, + }, + { + name: "grep-nested-pkg-no-recurse-subpackages", + dataset: "dataset-without-setters", + packagePath: "mysql/storage", + args: []string{"kind=Deployment", "-R=false"}, + expected: ` +"${baseDir}/mysql/storage": +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: myspace + name: storage-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'deployment.yaml' +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) { + 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.GetGrepRunner("") + actual := &bytes.Buffer{} + runner.Command.SetOut(actual) + runner.Command.SetArgs(append(test.args, filepath.Join(baseDir, test.packagePath))) + 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() + } + }) + } +}