diff --git a/cmd/config/internal/commands/cmdset.go b/cmd/config/internal/commands/cmdset.go index 0b40a3c4d..a646fd1de 100644 --- a/cmd/config/internal/commands/cmdset.go +++ b/cmd/config/internal/commands/cmdset.go @@ -6,19 +6,21 @@ package commands import ( "fmt" "os" + "path/filepath" "github.com/olekukonko/tablewriter" "github.com/spf13/cobra" "sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands" "sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/setters" + "sigs.k8s.io/kustomize/kyaml/setters2/settersutil" ) // NewSetRunner returns a command runner. func NewSetRunner(parent string) *SetRunner { r := &SetRunner{} c := &cobra.Command{ - Use: "set DIR [NAME] [VALUE]", + Use: "set DIR NAME [VALUE]", Args: cobra.RangeArgs(1, 3), Short: commands.SetShort, Long: commands.SetLong, @@ -32,18 +34,48 @@ func NewSetRunner(parent string) *SetRunner { "annotate the field with who set it") c.Flags().StringVar(&r.Perform.Description, "description", "", "annotate the field with a description of its value") + c.Flags().StringVar(&setterVersion, "version", "", + "use this version of the setter format") + c.Flags().MarkHidden("version") return r } +var setterVersion string + +var GetOpenAPIFile = func(args []string) (string, error) { + return filepath.Join(args[0], "kustomization"), nil +} + func SetCommand(parent string) *cobra.Command { return NewSetRunner(parent).Command } type SetRunner struct { - Command *cobra.Command - Lookup setters.LookupSetters - Perform setters.PerformSetters + Command *cobra.Command + Lookup setters.LookupSetters + Perform setters.PerformSetters + Set settersutil.FieldSetter + OpenAPIFile string +} + +func initSetterVersion(c *cobra.Command, args []string) error { + setterVersion = "v2" + l := setters.LookupSetters{} + + // backwards compatibility for resources with setter v1 + err := kio.Pipeline{ + Inputs: []kio.Reader{&kio.LocalPackageReader{PackagePath: args[0]}}, + Filters: []kio.Filter{&l}, + }.Execute() + if err != nil { + return err + } + if len(l.SetterCounts) > 0 { + setterVersion = "v1" + } + + return nil } func (r *SetRunner) preRunE(c *cobra.Command, args []string) error { @@ -55,15 +87,37 @@ func (r *SetRunner) preRunE(c *cobra.Command, args []string) error { r.Perform.Value = args[2] } + if setterVersion == "" { + if len(args) < 3 { + setterVersion = "v1" + } else if err := initSetterVersion(c, args); err != nil { + return err + } + } + if setterVersion == "v2" { + var err error + r.Set.Name = args[1] + r.Set.Value = args[2] + r.Set.Description = r.Perform.Description + r.Set.SetBy = r.Perform.SetBy + r.OpenAPIFile, err = GetOpenAPIFile(args) + if err != nil { + return err + } + } + return nil } func (r *SetRunner) runE(c *cobra.Command, args []string) error { - + if setterVersion == "v2" { + count, err := r.Set.Set(r.OpenAPIFile, args[0]) + fmt.Fprintf(c.OutOrStdout(), "set %d fields\n", count) + return handleError(c, err) + } if len(args) == 3 { return handleError(c, r.perform(c, args)) } - return handleError(c, lookup(r.Lookup, c, args)) } diff --git a/cmd/config/internal/commands/cmdset_test.go b/cmd/config/internal/commands/cmdset_test.go new file mode 100644 index 000000000..97b45fba5 --- /dev/null +++ b/cmd/config/internal/commands/cmdset_test.go @@ -0,0 +1,275 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package commands_test + +import ( + "bytes" + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/cmd/config/internal/commands" + "sigs.k8s.io/kustomize/kyaml/openapi" +) + +func TestSetCommand(t *testing.T) { + var tests = []struct { + name string + inputOpenAPI string + input string + args []string + out string + expectedOpenAPI string + expectedResources string + }{ + { + name: "set replicas", + args: []string{"replicas", "4", "--description", "hi there", "--set-by", "pw"}, + out: "set 1 fields\n", + inputOpenAPI: ` +apiVersion: v1alpha1 +kind: Example +openAPI: + definitions: + io.k8s.cli.setters.replicas: + description: hello world + x-k8s-cli: + setter: + name: replicas + value: "3" + setBy: me + `, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} + `, + expectedOpenAPI: ` +apiVersion: v1alpha1 +kind: Example +openAPI: + definitions: + io.k8s.cli.setters.replicas: + description: hi there + x-k8s-cli: + setter: + name: replicas + value: "4" + setBy: pw + `, + expectedResources: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 4 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} + `, + }, + { + name: "set replicas no description", + args: []string{"replicas", "4"}, + out: "set 1 fields\n", + inputOpenAPI: ` +apiVersion: v1alpha1 +kind: Example +openAPI: + definitions: + io.k8s.cli.setters.replicas: + description: hello world + x-k8s-cli: + setter: + name: replicas + value: "3" + setBy: me + `, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} + `, + expectedOpenAPI: ` +apiVersion: v1alpha1 +kind: Example +openAPI: + definitions: + io.k8s.cli.setters.replicas: + description: hello world + x-k8s-cli: + setter: + name: replicas + value: "4" + setBy: me + `, + expectedResources: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 4 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} + `, + }, + { + name: "set image", + args: []string{"tag", "1.8.1"}, + out: "set 1 fields\n", + inputOpenAPI: ` +apiVersion: v1alpha1 +kind: Example +openAPI: + definitions: + io.k8s.cli.setters.image: + x-k8s-cli: + setter: + name: image + value: "nginx" + io.k8s.cli.setters.tag: + x-k8s-cli: + setter: + name: tag + value: "1.7.9" + io.k8s.cli.substitutions.image: + x-k8s-cli: + substitution: + name: image + pattern: IMAGE:TAG + values: + - marker: IMAGE + ref: '#/definitions/io.k8s.cli.setters.image' + - marker: TAG + ref: '#/definitions/io.k8s.cli.setters.tag' + `, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 + template: + spec: + containers: + - name: nginx + image: nginx:1.7.9 # {"$ref":"#/definitions/io.k8s.cli.substitutions.image"} + - name: sidecar + image: sidecar:1.7.9 + `, + expectedOpenAPI: ` +apiVersion: v1alpha1 +kind: Example +openAPI: + definitions: + io.k8s.cli.setters.image: + x-k8s-cli: + setter: + name: image + value: "nginx" + io.k8s.cli.setters.tag: + x-k8s-cli: + setter: + name: tag + value: "1.8.1" + io.k8s.cli.substitutions.image: + x-k8s-cli: + substitution: + name: image + pattern: IMAGE:TAG + values: + - marker: IMAGE + ref: '#/definitions/io.k8s.cli.setters.image' + - marker: TAG + ref: '#/definitions/io.k8s.cli.setters.tag' + + `, + expectedResources: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 + template: + spec: + containers: + - name: nginx + image: nginx:1.8.1 # {"$ref":"#/definitions/io.k8s.cli.substitutions.image"} + - name: sidecar + image: sidecar:1.7.9 +`, + }, + } + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + // reset the openAPI afterward + openapi.ResetOpenAPI() + defer openapi.ResetOpenAPI() + + f, err := ioutil.TempFile("", "k8s-cli-") + if !assert.NoError(t, err) { + t.FailNow() + } + defer os.Remove(f.Name()) + err = ioutil.WriteFile(f.Name(), []byte(test.inputOpenAPI), 0600) + if !assert.NoError(t, err) { + t.FailNow() + } + commands.GetOpenAPIFile = func(args []string) (s string, err error) { + return f.Name(), nil + } + + r, err := ioutil.TempFile("", "k8s-cli-*.yaml") + if !assert.NoError(t, err) { + t.FailNow() + } + defer os.Remove(r.Name()) + err = ioutil.WriteFile(r.Name(), []byte(test.input), 0600) + if !assert.NoError(t, err) { + t.FailNow() + } + + runner := commands.NewSetRunner("") + out := &bytes.Buffer{} + runner.Command.SetOut(out) + runner.Command.SetArgs(append([]string{r.Name()}, test.args...)) + err = runner.Command.Execute() + if !assert.NoError(t, err) { + t.FailNow() + } + + if !assert.Equal(t, test.out, out.String()) { + t.FailNow() + } + + actualResources, err := ioutil.ReadFile(r.Name()) + if !assert.NoError(t, err) { + t.FailNow() + } + if !assert.Equal(t, + strings.TrimSpace(test.expectedResources), + strings.TrimSpace(string(actualResources))) { + t.FailNow() + } + + actualOpenAPI, err := ioutil.ReadFile(f.Name()) + if !assert.NoError(t, err) { + t.FailNow() + } + if !assert.Equal(t, + strings.TrimSpace(test.expectedOpenAPI), + strings.TrimSpace(string(actualOpenAPI))) { + t.FailNow() + } + }) + } +} diff --git a/kyaml/setters2/set.go b/kyaml/setters2/set.go index f671c4026..ec242d805 100644 --- a/kyaml/setters2/set.go +++ b/kyaml/setters2/set.go @@ -125,6 +125,10 @@ type SetOpenAPI struct { Name string `yaml:"name"` // Value is the current value of the setter Value string `yaml:"value"` + + Description string `yaml:"description"` + + SetBy string `yaml:"setBy"` } // UpdateFile updates the OpenAPI definitions in a file with the given setter value. @@ -146,5 +150,22 @@ func (s SetOpenAPI) Filter(object *yaml.RNode) (*yaml.RNode, error) { return nil, err } + if s.SetBy != "" { + if err := def.PipeE(&yaml.FieldSetter{Name: "setBy", StringValue: s.SetBy}); err != nil { + return nil, err + } + } + + if s.Description != "" { + d, err := object.Pipe(yaml.LookupCreate( + yaml.MappingNode, "openAPI", "definitions", key)) + if err != nil { + return nil, err + } + if err := d.PipeE(&yaml.FieldSetter{Name: "description", StringValue: s.Description}); err != nil { + return nil, err + } + } + return object, nil } diff --git a/kyaml/setters2/settersutil/fieldsetter.go b/kyaml/setters2/settersutil/fieldsetter.go index fdaa7c84c..bf2b44fa6 100644 --- a/kyaml/setters2/settersutil/fieldsetter.go +++ b/kyaml/setters2/settersutil/fieldsetter.go @@ -16,12 +16,17 @@ type FieldSetter struct { // Value is the value to set Value string + + Description string + + SetBy string } // Set updates the OpenAPI definitions and resources with the new setter value func (fs FieldSetter) Set(openAPIPath, resourcesPath string) (int, error) { // Update the OpenAPI definitions - soa := setters2.SetOpenAPI{Name: fs.Name, Value: fs.Value} + soa := setters2.SetOpenAPI{ + Name: fs.Name, Value: fs.Value, Description: fs.Description, SetBy: fs.SetBy} if err := soa.UpdateFile(openAPIPath); err != nil { return 0, err }