diff --git a/cmd/config/ext/ext.go b/cmd/config/ext/ext.go index 72235269b..4331e73d1 100644 --- a/cmd/config/ext/ext.go +++ b/cmd/config/ext/ext.go @@ -12,3 +12,13 @@ import ( var GetOpenAPIFile = func(args []string) (string, error) { return filepath.Join(args[0], "Krmfile"), nil } + +// OpenAPIFileName returns the name of the file with openAPI definitions +// uses OpenAPIFile function to derive it +func OpenAPIFileName() (string, error) { + openAPIFileName, err := GetOpenAPIFile([]string{"."}) + if err != nil { + return "", err + } + return openAPIFileName, nil +} diff --git a/cmd/config/internal/commands/cmdlistsetters.go b/cmd/config/internal/commands/cmdlistsetters.go index 2d6b641f7..a53a89a52 100644 --- a/cmd/config/internal/commands/cmdlistsetters.go +++ b/cmd/config/internal/commands/cmdlistsetters.go @@ -13,7 +13,9 @@ import ( "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/fieldmeta" + "sigs.k8s.io/kustomize/kyaml/pathutil" "sigs.k8s.io/kustomize/kyaml/setters" "sigs.k8s.io/kustomize/kyaml/setters2" ) @@ -63,11 +65,35 @@ func (r *ListSettersRunner) preRunE(c *cobra.Command, args []string) error { func (r *ListSettersRunner) runE(c *cobra.Command, args []string) error { if setterVersion == "v2" { - if err := r.ListSetters(c, args); err != nil { + openAPIFileName, err := ext.OpenAPIFileName() + if err != nil { return err } - if r.IncludeSubst { - return r.ListSubstitutions(c, args) + + openAPIPaths, err := pathutil.SubDirsWithFile(args[0], openAPIFileName) + if err != nil { + return err + } + if len(openAPIPaths) == 0 { + return errors.Errorf("unable to find %s in %s", openAPIFileName, args[0]) + } + + // list setters for all the subpackages with openAPI file paths + for _, openAPIPath := range openAPIPaths { + r.List = setters2.List{ + Name: r.List.Name, + OpenAPIFileName: openAPIFileName, + } + resourcePath := strings.TrimSuffix(openAPIPath, openAPIFileName) + fmt.Fprintf(c.OutOrStdout(), "%s\n", resourcePath) + if err := r.ListSetters(c, openAPIPath, resourcePath); err != nil { + return err + } + if r.IncludeSubst { + if err := r.ListSubstitutions(c, openAPIPath); err != nil { + return err + } + } } return nil } @@ -75,13 +101,9 @@ func (r *ListSettersRunner) runE(c *cobra.Command, args []string) error { return handleError(c, lookup(r.Lookup, c, args)) } -func (r *ListSettersRunner) ListSetters(c *cobra.Command, args []string) error { +func (r *ListSettersRunner) ListSetters(c *cobra.Command, openAPIPath, resourcePath string) error { // use setters v2 - path, err := ext.GetOpenAPIFile(args) - if err != nil { - return err - } - if err := r.List.ListSetters(path, args[0]); err != nil { + if err := r.List.ListSetters(openAPIPath, resourcePath); err != nil { return err } table := newTable(c.OutOrStdout(), r.Markdown) @@ -115,13 +137,9 @@ func (r *ListSettersRunner) ListSetters(c *cobra.Command, args []string) error { return nil } -func (r *ListSettersRunner) ListSubstitutions(c *cobra.Command, args []string) error { +func (r *ListSettersRunner) ListSubstitutions(c *cobra.Command, openAPIPath string) error { // use setters v2 - path, err := ext.GetOpenAPIFile(args) - if err != nil { - return err - } - if err := r.List.ListSubst(path); err != nil { + if err := r.List.ListSubst(openAPIPath); err != nil { return err } table := newTable(c.OutOrStdout(), r.Markdown) diff --git a/cmd/config/internal/commands/cmdlistsetters_test.go b/cmd/config/internal/commands/cmdlistsetters_test.go index 606550242..0b785e68a 100644 --- a/cmd/config/internal/commands/cmdlistsetters_test.go +++ b/cmd/config/internal/commands/cmdlistsetters_test.go @@ -7,11 +7,11 @@ import ( "bytes" "io/ioutil" "os" + "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" - "sigs.k8s.io/kustomize/cmd/config/ext" "sigs.k8s.io/kustomize/cmd/config/internal/commands" "sigs.k8s.io/kustomize/kyaml/openapi" ) @@ -408,27 +408,19 @@ openAPI: openapi.ResetOpenAPI() defer openapi.ResetOpenAPI() - f, err := ioutil.TempFile("", "k8s-cli-") + dir, err := ioutil.TempDir("", "") if !assert.NoError(t, err) { t.FailNow() } - defer os.Remove(f.Name()) - old := ext.GetOpenAPIFile - defer func() { ext.GetOpenAPIFile = old }() - ext.GetOpenAPIFile = func(args []string) (s string, err error) { - err = ioutil.WriteFile(f.Name(), []byte(test.openapi), 0600) - if !assert.NoError(t, err) { - t.FailNow() - } - return f.Name(), nil - } - r, err := ioutil.TempFile("", "k8s-cli-*.yaml") + defer os.RemoveAll(dir) + + err = ioutil.WriteFile(filepath.Join(dir, "Krmfile"), []byte(test.openapi), 0600) if !assert.NoError(t, err) { t.FailNow() } - defer os.Remove(r.Name()) - err = ioutil.WriteFile(r.Name(), []byte(test.input), 0600) + + err = ioutil.WriteFile(filepath.Join(dir, "deployment.yaml"), []byte(test.input), 0600) if !assert.NoError(t, err) { t.FailNow() } @@ -436,18 +428,18 @@ openAPI: runner := commands.NewListSettersRunner("") actual := &bytes.Buffer{} runner.Command.SetOut(actual) - runner.Command.SetArgs(append([]string{r.Name()}, test.args...)) + runner.Command.SetArgs(append([]string{dir}, test.args...)) err = runner.Command.Execute() if !assert.NoError(t, err) { t.FailNow() } - if !assert.Equal(t, test.expected, actual.String()) { + if !assert.Contains(t, actual.String(), test.expected) { t.FailNow() } // make sure that the resources are not altered - actualResources, err := ioutil.ReadFile(r.Name()) + actualResources, err := ioutil.ReadFile(filepath.Join(dir, "deployment.yaml")) if !assert.NoError(t, err) { t.FailNow() } @@ -457,7 +449,7 @@ openAPI: t.FailNow() } - actualOpenAPI, err := ioutil.ReadFile(f.Name()) + actualOpenAPI, err := ioutil.ReadFile(filepath.Join(dir, "Krmfile")) if !assert.NoError(t, err) { t.FailNow() } @@ -469,3 +461,57 @@ openAPI: }) } } + +func TestListSettersSubPackages(t *testing.T) { + var tests = []struct { + name string + dataset string + args []string + expected string + }{ + { + name: "list-replicas", + dataset: "dataset1", + args: []string{"--include-subst"}, + expected: `test/testdata/dataset1/mysql/ + NAME VALUE SET BY DESCRIPTION COUNT REQUIRED + image mysql 1 No + namespace myspace 1 No + tag 1.7.9 1 No +--------------- ----------------- -------------- + SUBSTITUTION PATTERN REFERENCES + image-tag ${image}:${tag} [image,tag] +test/testdata/dataset1/mysql/nosetters/ + NAME VALUE SET BY DESCRIPTION COUNT REQUIRED +test/testdata/dataset1/mysql/storage/ + NAME VALUE SET BY DESCRIPTION COUNT REQUIRED + namespace myspace 1 No +`, + }, + } + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + // reset the openAPI afterward + openapi.ResetOpenAPI() + defer openapi.ResetOpenAPI() + dir := filepath.Join("test", "testdata", test.dataset) + + runner := commands.NewListSettersRunner("") + actual := &bytes.Buffer{} + runner.Command.SetOut(actual) + runner.Command.SetArgs(append([]string{dir}, test.args...)) + err := runner.Command.Execute() + if !assert.NoError(t, err) { + t.FailNow() + } + + // normalize path format for windows + actualNormalized := strings.Replace(actual.String(), "\\", "/", -1) + + if !assert.Equal(t, test.expected, actualNormalized) { + t.FailNow() + } + }) + } +} diff --git a/cmd/config/internal/commands/cmdwrap_test.go b/cmd/config/internal/commands/cmdwrap_test.go index adc8e6611..49b37d5e9 100644 --- a/cmd/config/internal/commands/cmdwrap_test.go +++ b/cmd/config/internal/commands/cmdwrap_test.go @@ -178,6 +178,51 @@ items: outputOverride = `apiVersion: config.kubernetes.io/v1alpha1 kind: ResourceList items: +- apiVersion: apps/v1 + kind: Deployment + metadata: + name: mysql-deployment + namespace: myspace # {"$openapi":"namespace"} + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'config/mysql-deployment_deployment.yaml' + spec: + replicas: 3 + template: + spec: + containers: + - name: mysql + image: mysql:1.7.9 # {"$openapi":"image-tag"} +- apiVersion: apps/v1 + kind: Deployment + metadata: + name: nosetters-deployment + namespace: myspace + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'config/nosetters-deployment_deployment.yaml' + spec: + replicas: 4 + template: + spec: + containers: + - name: nosetters + image: nosetters:1.7.7 +- apiVersion: apps/v1 + kind: Deployment + metadata: + name: storage-deployment + namespace: myspace # {"$openapi":"namespace"} + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'config/storage-deployment_deployment.yaml' + spec: + replicas: 4 + template: + spec: + containers: + - name: storage + image: storage:1.7.7 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/cmd/config/internal/commands/e2e/list_setters_test.go b/cmd/config/internal/commands/e2e/list_setters_test.go index 0b8cd541f..0d73132a3 100644 --- a/cmd/config/internal/commands/e2e/list_setters_test.go +++ b/cmd/config/internal/commands/e2e/list_setters_test.go @@ -34,7 +34,8 @@ openAPI: `, }, expectedStdOut: ` -NAME VALUE SET BY DESCRIPTION COUNT REQUIRED +./ + NAME VALUE SET BY DESCRIPTION COUNT REQUIRED replicas 3 1 No `, }, diff --git a/cmd/config/internal/commands/test/testdata/dataset1/mysql/Krmfile b/cmd/config/internal/commands/test/testdata/dataset1/mysql/Krmfile new file mode 100644 index 000000000..7b8f41d87 --- /dev/null +++ b/cmd/config/internal/commands/test/testdata/dataset1/mysql/Krmfile @@ -0,0 +1,33 @@ +apiVersion: krm.dev/v1alpha1 +kind: Krmfile +metadata: + name: mysql +packageMetadata: + shortDescription: sample description +openAPI: + definitions: + io.k8s.cli.setters.namespace: + x-k8s-cli: + setter: + name: namespace + value: myspace + io.k8s.cli.substitutions.image-tag: + x-k8s-cli: + substitution: + name: image-tag + pattern: ${image}:${tag} + values: + - marker: ${image} + ref: '#/definitions/io.k8s.cli.setters.image' + - marker: ${tag} + ref: '#/definitions/io.k8s.cli.setters.tag' + io.k8s.cli.setters.image: + x-k8s-cli: + setter: + name: image + value: mysql + io.k8s.cli.setters.tag: + x-k8s-cli: + setter: + name: tag + value: 1.7.9 diff --git a/cmd/config/internal/commands/test/testdata/dataset1/mysql/deployment.yaml b/cmd/config/internal/commands/test/testdata/dataset1/mysql/deployment.yaml new file mode 100644 index 000000000..54e5f760c --- /dev/null +++ b/cmd/config/internal/commands/test/testdata/dataset1/mysql/deployment.yaml @@ -0,0 +1,12 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: myspace # {"$openapi":"namespace"} + name: mysql-deployment +spec: + replicas: 3 + template: + spec: + containers: + - name: mysql + image: mysql:1.7.9 # {"$openapi":"image-tag"} diff --git a/cmd/config/internal/commands/test/testdata/dataset1/mysql/nosetters/Krmfile b/cmd/config/internal/commands/test/testdata/dataset1/mysql/nosetters/Krmfile new file mode 100644 index 000000000..71a4c6ca1 --- /dev/null +++ b/cmd/config/internal/commands/test/testdata/dataset1/mysql/nosetters/Krmfile @@ -0,0 +1,6 @@ +apiVersion: krm.dev/v1alpha1 +kind: Krmfile +metadata: + name: storage +packageMetadata: + shortDescription: sample description diff --git a/cmd/config/internal/commands/test/testdata/dataset1/mysql/nosetters/deployment.yaml b/cmd/config/internal/commands/test/testdata/dataset1/mysql/nosetters/deployment.yaml new file mode 100644 index 000000000..8f5843f8d --- /dev/null +++ b/cmd/config/internal/commands/test/testdata/dataset1/mysql/nosetters/deployment.yaml @@ -0,0 +1,12 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: myspace + name: nosetters-deployment +spec: + replicas: 4 + template: + spec: + containers: + - name: nosetters + image: nosetters:1.7.7 diff --git a/cmd/config/internal/commands/test/testdata/dataset1/mysql/storage/Krmfile b/cmd/config/internal/commands/test/testdata/dataset1/mysql/storage/Krmfile new file mode 100644 index 000000000..5a01ae68c --- /dev/null +++ b/cmd/config/internal/commands/test/testdata/dataset1/mysql/storage/Krmfile @@ -0,0 +1,13 @@ +apiVersion: krm.dev/v1alpha1 +kind: Krmfile +metadata: + name: storage +packageMetadata: + shortDescription: sample description +openAPI: + definitions: + io.k8s.cli.setters.namespace: + x-k8s-cli: + setter: + name: namespace + value: myspace diff --git a/cmd/config/internal/commands/test/testdata/dataset1/mysql/storage/deployment.yaml b/cmd/config/internal/commands/test/testdata/dataset1/mysql/storage/deployment.yaml new file mode 100644 index 000000000..5a9c78941 --- /dev/null +++ b/cmd/config/internal/commands/test/testdata/dataset1/mysql/storage/deployment.yaml @@ -0,0 +1,12 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: myspace # {"$openapi":"namespace"} + name: storage-deployment +spec: + replicas: 4 + template: + spec: + containers: + - name: storage + image: storage:1.7.7 diff --git a/kyaml/go.mod b/kyaml/go.mod index 6b683303b..208e40b0b 100644 --- a/kyaml/go.mod +++ b/kyaml/go.mod @@ -15,6 +15,7 @@ require ( github.com/stretchr/testify v1.4.0 github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 + golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 // indirect gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 ) diff --git a/kyaml/go.sum b/kyaml/go.sum index d4af2875b..8bea636fc 100644 --- a/kyaml/go.sum +++ b/kyaml/go.sum @@ -231,6 +231,8 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/kyaml/pathutil/pathutil.go b/kyaml/pathutil/pathutil.go new file mode 100644 index 000000000..53f816237 --- /dev/null +++ b/kyaml/pathutil/pathutil.go @@ -0,0 +1,33 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package pathutil + +import ( + "os" + "path/filepath" + "strings" +) + +// SubDirsWithFile takes the root directory path and returns all the paths of +// sub-directories which contain file with input fileName including itself +func SubDirsWithFile(root, fileName string) ([]string, error) { + var res []string + err := filepath.Walk(root, + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if strings.HasSuffix(path, fileName) { + if root == "." { + path = root + "/" + path + } + res = append(res, path) + } + return nil + }) + if err != nil { + return res, err + } + return res, nil +} diff --git a/kyaml/pathutil/pathutil_test.go b/kyaml/pathutil/pathutil_test.go new file mode 100644 index 000000000..00fdf9ab2 --- /dev/null +++ b/kyaml/pathutil/pathutil_test.go @@ -0,0 +1,83 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package pathutil + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSubDirsWithFile(t *testing.T) { + dir, err := ioutil.TempDir("", "") + if !assert.NoError(t, err) { + t.FailNow() + } + defer os.RemoveAll(dir) + err = createTestDirStructure(dir) + if !assert.NoError(t, err) { + t.FailNow() + } + + res, err := SubDirsWithFile(dir, "Krmfile") + if !assert.NoError(t, err) { + t.FailNow() + } + if !assert.Equal(t, 3, len(res)) { + t.FailNow() + } +} + +func TestSubDirsWithFileNoMatch(t *testing.T) { + dir, err := ioutil.TempDir("", "") + if !assert.NoError(t, err) { + t.FailNow() + } + defer os.RemoveAll(dir) + res, err := SubDirsWithFile(dir, "non-existent-file.txt") + if !assert.NoError(t, err) { + t.FailNow() + } + var expected []string + if !assert.Equal(t, expected, res) { + t.FailNow() + } +} + +func createTestDirStructure(dir string) error { + /* + Adds the folders to the input dir with following structure + dir + ├── Krmfile + ├── subpkg1 + │   ├── Krmfile + │   └── subdir1 + └── subpkg2 + └── Krmfile + */ + err := os.MkdirAll(filepath.Join(dir, "subpkg1/subdir1"), 0777|os.ModeDir) + if err != nil { + return err + } + err = os.MkdirAll(filepath.Join(dir, "subpkg2"), 0777|os.ModeDir) + if err != nil { + return err + } + err = ioutil.WriteFile(filepath.Join(dir, "subpkg1", "Krmfile"), []byte(""), 0777) + if err != nil { + return err + } + err = ioutil.WriteFile(filepath.Join(dir, "subpkg2", "Krmfile"), []byte(""), 0777) + if err != nil { + return err + } + err = ioutil.WriteFile(filepath.Join(dir, "Krmfile"), []byte(""), 0777) + if err != nil { + return err + } + return nil +} diff --git a/kyaml/setters2/list.go b/kyaml/setters2/list.go index 5116500a7..0dda187cb 100644 --- a/kyaml/setters2/list.go +++ b/kyaml/setters2/list.go @@ -15,9 +15,13 @@ import ( ) // List lists the setters specified in the OpenAPI +// excludes the subpackages which contain file with +// name OpenAPIFileName in them type List struct { Name string + OpenAPIFileName string + Setters []SetterDefinition Substitutions []SubstitutionDefinition @@ -181,12 +185,13 @@ func (l *List) listSubst(object *yaml.RNode) error { } // count returns the number of fields set by the setter with name +// this excludes all the subpackages with openAPI file in them // set filter is leveraged for this but the resources are not written // back to files as only LocalPackageReader is invoked and not writer func (l *List) count(path, name string) (int, error) { s := &Set{Name: name} err := kio.Pipeline{ - Inputs: []kio.Reader{&kio.LocalPackageReader{PackagePath: path}}, + Inputs: []kio.Reader{&kio.LocalPackageReader{PackagePath: path, PackageFileName: l.OpenAPIFileName}}, Filters: []kio.Filter{kio.FilterAll(s)}, }.Execute()