diff --git a/cmd/config/configcobra/cmds.go b/cmd/config/configcobra/cmds.go index 04de41914..82754e74d 100644 --- a/cmd/config/configcobra/cmds.go +++ b/cmd/config/configcobra/cmds.go @@ -45,22 +45,23 @@ Advanced Documentation Topics: // Export commands publicly for composition var ( - Annotate = commands.AnnotateCommand - Cat = commands.CatCommand - Count = commands.CountCommand - CreateSetter = commands.CreateSetterCommand - Fmt = commands.FmtCommand - Grep = commands.GrepCommand - ListSetters = commands.ListSettersCommand - Merge = commands.MergeCommand - Merge3 = commands.Merge3Command - RunFn = commands.RunFnCommand - Set = commands.SetCommand - Sink = commands.SinkCommand - Source = commands.SourceCommand - Tree = commands.TreeCommand - Wrap = commands.WrapCommand - XArgs = commands.XArgsCommand + Annotate = commands.AnnotateCommand + Cat = commands.CatCommand + Count = commands.CountCommand + CreateSetter = commands.CreateSetterCommand + CreateSubstitution = commands.CreateSubstitutionCommand + Fmt = commands.FmtCommand + Grep = commands.GrepCommand + ListSetters = commands.ListSettersCommand + Merge = commands.MergeCommand + Merge3 = commands.Merge3Command + RunFn = commands.RunFnCommand + Set = commands.SetCommand + Sink = commands.SinkCommand + Source = commands.SourceCommand + Tree = commands.TreeCommand + Wrap = commands.WrapCommand + XArgs = commands.XArgsCommand StackOnError = &commands.StackOnError ExitOnError = &commands.ExitOnError @@ -107,6 +108,7 @@ func NewConfigCommand(name string) *cobra.Command { root.AddCommand(commands.SetCommand(name)) root.AddCommand(commands.ListSettersCommand(name)) root.AddCommand(commands.CreateSetterCommand(name)) + root.AddCommand(commands.CreateSubstitutionCommand(name)) root.AddCommand(commands.SinkCommand(name)) root.AddCommand(commands.SourceCommand(name)) diff --git a/cmd/config/internal/commands/cmdcreatesubstitution.go b/cmd/config/internal/commands/cmdcreatesubstitution.go new file mode 100644 index 000000000..c7d5e63c6 --- /dev/null +++ b/cmd/config/internal/commands/cmdcreatesubstitution.go @@ -0,0 +1,80 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package commands + +import ( + "strings" + + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/setters2" + "sigs.k8s.io/kustomize/kyaml/setters2/settersutil" +) + +// NewCreateSubstitutionRunner returns a command runner. +func NewCreateSubstitutionRunner(parent string) *CreateSubstitutionRunner { + r := &CreateSubstitutionRunner{} + cs := &cobra.Command{ + Use: "create-subst DIR NAME VALUE", + Args: cobra.ExactArgs(3), + PreRunE: r.preRunE, + RunE: r.runE, + } + cs.Flags().StringVar(&r.CreateSubstitution.FieldName, "field", "", + "name of the field to set -- e.g. --field port") + cs.Flags().StringVar(&r.CreateSubstitution.Pattern, "pattern", "", + "substitution pattern") + cs.Flags().StringSliceVar(&r.Values, "value", []string{""}, + "substitution values for the pattern. format is PATTERN_MARKER=SETTER_NAME"+ + "where PATTERN_MARKER is the pattern substring to replace, and SETTER_NAME is the"+ + "setter from which to take the replacement value.") + _ = cs.MarkFlagRequired("pattern") + fixDocs(parent, cs) + r.Command = cs + return r +} + +func CreateSubstitutionCommand(parent string) *cobra.Command { + return NewCreateSubstitutionRunner(parent).Command +} + +type CreateSubstitutionRunner struct { + Command *cobra.Command + CreateSubstitution settersutil.SubstitutionCreator + OpenAPIFile string + Values []string +} + +func (r *CreateSubstitutionRunner) runE(c *cobra.Command, args []string) error { + return handleError(c, r.CreateSubstitution.Create(r.OpenAPIFile, args[0])) +} + +func (r *CreateSubstitutionRunner) preRunE(c *cobra.Command, args []string) error { + var err error + r.CreateSubstitution.Name = args[1] + r.CreateSubstitution.FieldValue = args[2] + if err != nil { + return err + } + + r.OpenAPIFile, err = GetOpenAPIFile(args) + if err != nil { + return err + } + + // parse the marker values + for i := range r.Values { + parts := strings.SplitN(r.Values[i], "=", 2) + if len(parts) < 2 { + return errors.Errorf("values must be specified as PATTERN_MARKER=SETTER_NAME") + } + ref := setters2.DefinitionsPrefix + setters2.SetterDefinitionPrefix + parts[1] + r.CreateSubstitution.Values = append( + r.CreateSubstitution.Values, + setters2.Value{Marker: parts[0], Ref: ref}, + ) + } + + return nil +} diff --git a/cmd/config/internal/commands/cmdcreatesubstitution_test.go b/cmd/config/internal/commands/cmdcreatesubstitution_test.go new file mode 100644 index 000000000..73bd77fd9 --- /dev/null +++ b/cmd/config/internal/commands/cmdcreatesubstitution_test.go @@ -0,0 +1,171 @@ +// 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 TestCreateSubstitutionCommand(t *testing.T) { + var tests = []struct { + name string + inputOpenAPI string + input string + args []string + out string + expectedOpenAPI string + expectedResources string + }{ + { + name: "substitution replicas", + args: []string{ + "image", "nginx:1.7.9", "--pattern", "IMAGE:TAG", + "--value", "IMAGE=image", "--value", "TAG=tag"}, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 + template: + spec: + containers: + - name: nginx + image: nginx:1.7.9 + - name: sidecar + image: sidecar:1.7.9 + `, + 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" + `, + 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.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' + `, + expectedResources: ` +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 + `, + }, + } + 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.NewCreateSubstitutionRunner("") + 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() + } + }) + } +}