From 28830c4d86ffc4a09d36ca4ab25fb87f619f5515 Mon Sep 17 00:00:00 2001 From: Mauren Berti Date: Sun, 15 Oct 2023 14:46:56 -0400 Subject: [PATCH] feat: edit set configmap Create new 'kustomize edit set configmap' command with two options: * --from-literal to allow changing a literal source. * --new-namespace to allow changing a generator namespace. --- go.work.sum | 58 +----- kustomize/commands/edit/add/addconfigmap.go | 2 +- kustomize/commands/edit/add/addsecret.go | 2 +- kustomize/commands/edit/all.go | 3 +- kustomize/commands/edit/set/all.go | 9 +- kustomize/commands/edit/set/setconfigmap.go | 142 ++++++++++++--- .../commands/edit/set/setconfigmap_test.go | 170 ++++++++++++++++++ .../util/configmapSecretFlagsAndArgs.go | 66 ++++++- .../util/configmapSecretFlagsAndArgs_test.go | 6 +- kustomize/go.mod | 3 +- kustomize/go.sum | 3 + 11 files changed, 376 insertions(+), 88 deletions(-) create mode 100644 kustomize/commands/edit/set/setconfigmap_test.go diff --git a/go.work.sum b/go.work.sum index 869f7cfa1a..6aa47f2a35 100644 --- a/go.work.sum +++ b/go.work.sum @@ -202,60 +202,14 @@ go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= -gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485 h1:OB/uP/Puiu5vS5QMRPrXCDWUPb+kt8f1KW8oQzFejQw= -gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e h1:jRyg0XfpwWlhEV8mDfdNGBeSJM2fuyh9Yjrnd8kF2Ts= -google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= -google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 h1:Et6SkiuvnBn+SgrSYXs/BrUpGB4mbdwt4R3vaPIlicA= -google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= -gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= -gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= -gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= -k8s.io/apiserver v0.17.0 h1:XhUix+FKFDcBygWkQNp7wKKvZL030QUlH1o8vFeSgZA= -k8s.io/code-generator v0.17.0 h1:y+KWtDWNqlJzJu/kUy8goJZO0X71PGIpAHLX8a0JYk0= -k8s.io/component-base v0.17.0 h1:BnDFcmBDq+RPpxXjmuYnZXb59XNN9CaFrX8ba9+3xrA= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c h1:GohjlNKauSai7gN4wsJkeZ3WAJx4Sh+oT/b5IYn5suA= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -modernc.org/cc v1.0.0 h1:nPibNuDEx6tvYrUAtvDTTw98rx5juGsa5zuDnKwEEQQ= -modernc.org/golex v1.0.0 h1:wWpDlbK8ejRfSyi0frMyhilD3JBvtcx2AdGDnU+JtsE= -modernc.org/mathutil v1.0.0 h1:93vKjrJopTPrtTNpZ8XIovER7iCIH1QU7wNbOQXC60I= -modernc.org/strutil v1.0.0 h1:XVFtQwFVwc02Wk+0L/Z/zDDXO81r5Lhe6iMKmGX3KhE= -modernc.org/xc v1.0.0 h1:7ccXrupWZIS3twbUGrtKmHS2DXY6xegFua+6O3xgAFU= -rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= -rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= -rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= -sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU= diff --git a/kustomize/commands/edit/add/addconfigmap.go b/kustomize/commands/edit/add/addconfigmap.go index 147f6c4b33..39f0ec3e77 100644 --- a/kustomize/commands/edit/add/addconfigmap.go +++ b/kustomize/commands/edit/add/addconfigmap.go @@ -97,7 +97,7 @@ func runEditAddConfigMap( return fmt.Errorf("failed to expand file source: %w", err) } - err = flags.Validate(args) + err = flags.ValidateAdd(args) if err != nil { return fmt.Errorf("failed to validate flags: %w", err) } diff --git a/kustomize/commands/edit/add/addsecret.go b/kustomize/commands/edit/add/addsecret.go index aee0cde169..b232b2caae 100644 --- a/kustomize/commands/edit/add/addsecret.go +++ b/kustomize/commands/edit/add/addsecret.go @@ -89,7 +89,7 @@ func runEditAddSecret( return fmt.Errorf("failed to expand file source: %w", err) } - err = flags.Validate(args) + err = flags.ValidateAdd(args) if err != nil { return fmt.Errorf("failed to validate flags: %w", err) } diff --git a/kustomize/commands/edit/all.go b/kustomize/commands/edit/all.go index ccd7f8e320..ee30dbfb11 100644 --- a/kustomize/commands/edit/all.go +++ b/kustomize/commands/edit/all.go @@ -48,7 +48,8 @@ func NewCmdEdit( set.NewCmdSet( fSys, kv.NewLoader(ldrhelper.NewFileLoaderAtCwd(fSys), v), - v), + v, + rf), fix.NewCmdFix(fSys, w), remove.NewCmdRemove(fSys, v), listbuiltin.NewCmdListBuiltinPlugin(), diff --git a/kustomize/commands/edit/set/all.go b/kustomize/commands/edit/set/all.go index 6979665af6..0c716c91b2 100644 --- a/kustomize/commands/edit/set/all.go +++ b/kustomize/commands/edit/set/all.go @@ -6,11 +6,17 @@ package set import ( "github.com/spf13/cobra" "sigs.k8s.io/kustomize/api/ifc" + "sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/kyaml/filesys" ) // NewCmdSet returns an instance of 'set' subcommand. -func NewCmdSet(fSys filesys.FileSystem, ldr ifc.KvLoader, v ifc.Validator) *cobra.Command { +func NewCmdSet( + fSys filesys.FileSystem, + ldr ifc.KvLoader, + v ifc.Validator, + rf *resource.Factory, +) *cobra.Command { c := &cobra.Command{ Use: "set", Short: "Sets the value of different fields in kustomization file", @@ -26,6 +32,7 @@ func NewCmdSet(fSys filesys.FileSystem, ldr ifc.KvLoader, v ifc.Validator) *cobr } c.AddCommand( + newCmdSetConfigMap(fSys, ldr, rf), newCmdSetNamePrefix(fSys), newCmdSetNameSuffix(fSys), newCmdSetNamespace(fSys, v), diff --git a/kustomize/commands/edit/set/setconfigmap.go b/kustomize/commands/edit/set/setconfigmap.go index 3727df813d..daa47557a4 100644 --- a/kustomize/commands/edit/set/setconfigmap.go +++ b/kustomize/commands/edit/set/setconfigmap.go @@ -4,43 +4,143 @@ package set import ( + "fmt" "github.com/spf13/cobra" + "golang.org/x/exp/slices" "sigs.k8s.io/kustomize/api/ifc" "sigs.k8s.io/kustomize/api/resource" + "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kustomize/v5/commands/internal/kustfile" + "sigs.k8s.io/kustomize/kustomize/v5/commands/internal/util" "sigs.k8s.io/kustomize/kyaml/filesys" ) -type setConfigMapOptions struct { - // Name of ConfigMap to be edited - Name string - - // Namespace of ConfigMap - Namespace string - - // LiteralSources to derive the ConfigMap from - LiteralSources []string -} - func newCmdSetConfigMap( fSys filesys.FileSystem, ldr ifc.KvLoader, rf *resource.Factory, ) *cobra.Command { - // var flags setConfigMapOptions - return &cobra.Command{ + var flags util.ConfigMapSecretFlagsAndArgs + cmd := &cobra.Command{ Use: "configmap NAME [--from-literal=key1=value1]", - Short: "Edits an existing key for a configmap in the kustomization file", - Long: "", + Short: "Edits the value for an existing key for a configmap in the kustomization file", + Long: `Edits the value for an existing key in an existing configmap in the kustomization file. +Both configmap name and key name must exist for this command to succeed.`, Example: ` - # Edits an existing configmap in the kustomization file, changing value of key1 to 2 - kustomize edit set configmap my-configmap --from-literal=key1=2 + # Edits an existing configmap in the kustomization file, changing value of key1 to 2 + kustomize edit set configmap my-configmap --from-literal=key1=2 - # Edits an existing configmap in the kustomization file, changing namespace to 'new-test-ns' - kustomize edit set configmap my-configmap --namespace=new-test-ns + # Edits an existing configmap in the kustomization file, changing namespace to 'new-test-ns' + kustomize edit set configmap my-configmap --namespace=current-test-ns --new-namespace=new-test-ns `, RunE: func(_ *cobra.Command, args []string) error { - return nil - + return runEditSetConfigMap(flags, fSys, args, ldr, rf) }, } + + cmd.Flags().StringArrayVar( + &flags.LiteralSources, + util.FromLiteralFlag, + []string{}, + "Specify an existing key and a new value to update a ConfigMap (i.e. mykey=newvalue)") + cmd.Flags().StringVar( + &flags.Namespace, + "namespace", + "", + "Current namespace of the target ConfigMap") + cmd.Flags().StringVar( + &flags.NewNamespace, + "new-namespace", + "", + "New namespace value for the target ConfigMap") + + return cmd +} + +func runEditSetConfigMap( + flags util.ConfigMapSecretFlagsAndArgs, + fSys filesys.FileSystem, + args []string, + ldr ifc.KvLoader, + rf *resource.Factory, +) error { + err := flags.ExpandFileSource(fSys) + if err != nil { + return fmt.Errorf("failed to expand file source: %w", err) + } + + err = flags.ValidateSet(args) + if err != nil { + return fmt.Errorf("failed to validate flags: %w", err) + } + + // Load the kustomization file. + mf, err := kustfile.NewKustomizationFile(fSys) + if err != nil { + return fmt.Errorf("failed to load kustomization file: %w", err) + } + + kustomization, err := mf.Read() + if err != nil { + return fmt.Errorf("failed to read kustomization file: %w", err) + } + + // Updates the existing ConfigMap + err = setConfigMap(ldr, kustomization, flags, rf) + if err != nil { + return fmt.Errorf("failed to create configmap: %w", err) + } + + // Write out the kustomization file with added configmap. + err = mf.Write(kustomization) + if err != nil { + return fmt.Errorf("failed to write kustomization file: %w", err) + } + + return nil +} + +func setConfigMap( + ldr ifc.KvLoader, + k *types.Kustomization, + flags util.ConfigMapSecretFlagsAndArgs, + rf *resource.Factory, +) error { + args, err := findConfigMapArgs(k, flags.Name, flags.Namespace) + if err != nil { + return fmt.Errorf("could not set new ConfigMap value: %w", err) + } + + if len(flags.LiteralSources) > 0 { + err := util.UpdateLiteralSources(&args.GeneratorArgs, flags) + if err != nil { + return err + } + } + + // update namespace to new one + if flags.NewNamespace != "" { + args.Namespace = flags.NewNamespace + } + + // Validate by trying to create corev1.configmap. + args.Options = types.MergeGlobalOptionsIntoLocal( + args.Options, k.GeneratorOptions) + + _, err = rf.MakeConfigMap(ldr, args) + return err +} + +// findConfigMapArgs finds the generator arguments corresponding to the specified +// ConfigMap name. ConfigMap must exist for this command to be successful. +func findConfigMapArgs(m *types.Kustomization, name, namespace string) (*types.ConfigMapArgs, error) { + cmIndex := slices.IndexFunc(m.ConfigMapGenerator, func(cmArgs types.ConfigMapArgs) bool { + return name == cmArgs.Name && namespace == cmArgs.Namespace + }) + + if cmIndex == -1 { + return nil, fmt.Errorf("unable to find ConfigMap with name '%s'", name) + } + + return &m.ConfigMapGenerator[cmIndex], nil } diff --git a/kustomize/commands/edit/set/setconfigmap_test.go b/kustomize/commands/edit/set/setconfigmap_test.go new file mode 100644 index 0000000000..9ae3b3ca20 --- /dev/null +++ b/kustomize/commands/edit/set/setconfigmap_test.go @@ -0,0 +1,170 @@ +package set + +import ( + "github.com/stretchr/testify/require" + "sigs.k8s.io/kustomize/api/kv" + "sigs.k8s.io/kustomize/api/pkg/loader" + "sigs.k8s.io/kustomize/api/provider" + "sigs.k8s.io/kustomize/kustomize/v5/commands/internal/kustfile" + testutils_test "sigs.k8s.io/kustomize/kustomize/v5/commands/internal/testutils" + "sigs.k8s.io/kustomize/kyaml/filesys" + "testing" +) + +func TestEditSetConfigMapError(t *testing.T) { + fSys := filesys.MakeFsInMemory() + pvd := provider.NewDefaultDepProvider() + + testCases := []struct { + name string + input string + args []string + expectedErrorMsg string + }{ + { + name: "fail to set a value because key doesn't exist", + input: `--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +configMapGenerator: +- literals: + - test=value + - key1=val1 + name: test-cm +`, + args: []string{"test-cm", "--from-literal=key3=val2"}, + expectedErrorMsg: "key 'key3' not found in resource", + }, + { + name: "fail to set a value because configmap doesn't exist", + input: `--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +configMapGenerator: +- literals: + - test=value + - key1=val1 + name: test-cm +`, + args: []string{"test-cm2", "--from-literal=key3=val2"}, + expectedErrorMsg: "unable to find ConfigMap with name 'test-cm2'", + }, + { + name: "error on validate because no attributes are being changed", + input: `--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +configMapGenerator: +- literals: + - test=value + - key1=val1 + name: test-cm + namespace: test-ns +`, + args: []string{"test-cm", "--namespace=test-ns"}, + expectedErrorMsg: "at least one of [--from-literal, --new-namespace] must be specified", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cmd := newCmdSetConfigMap( + fSys, + kv.NewLoader( + loader.NewFileLoaderAtCwd(fSys), + pvd.GetFieldValidator()), + pvd.GetResourceFactory(), + ) + + testutils_test.WriteTestKustomizationWith(fSys, []byte(tc.input)) + + cmd.SetArgs(tc.args) + err := cmd.Execute() + + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectedErrorMsg) + }) + } +} + +func TestEditSetConfigMapSuccess(t *testing.T) { + fSys := filesys.MakeFsInMemory() + pvd := provider.NewDefaultDepProvider() + testCases := []struct { + name string + input string + args []string + expectedLiterals []string + expectedNamespace string + }{ + { + name: "set a value successfully", + input: `--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +configMapGenerator: +- literals: + - key1=val1 + - test=value + name: test-cm +`, + expectedLiterals: []string{"key1=val2", "test=value"}, + args: []string{"test-cm", "--from-literal=key1=val2"}, + }, + { + name: "successfully update namespace of target configmap", + input: `--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +configMapGenerator: +- literals: + - test=value + - key1=val1 + name: test-cm + namespace: test-ns +`, + args: []string{"test-cm", "--namespace=test-ns", "--new-namespace=test-new-ns"}, + expectedNamespace: "test-new-ns", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cmd := newCmdSetConfigMap( + fSys, + kv.NewLoader( + loader.NewFileLoaderAtCwd(fSys), + pvd.GetFieldValidator()), + pvd.GetResourceFactory(), + ) + + testutils_test.WriteTestKustomizationWith(fSys, []byte(tc.input)) + + cmd.SetArgs(tc.args) + err := cmd.Execute() + + require.NoError(t, err) + + _, err = testutils_test.ReadTestKustomization(fSys) + require.NoError(t, err) + + mf, err := kustfile.NewKustomizationFile(fSys) + require.NoError(t, err) + + kustomization, err := mf.Read() + require.NoError(t, err) + + require.NotNil(t, kustomization) + require.NotEmpty(t, kustomization.ConfigMapGenerator) + require.Greater(t, len(kustomization.ConfigMapGenerator), 0) + + if tc.expectedNamespace != "" { + require.Equal(t, tc.expectedNamespace, kustomization.ConfigMapGenerator[0].Namespace) + } + + if len(tc.expectedLiterals) > 0 { + require.ElementsMatch(t, tc.expectedLiterals, kustomization.ConfigMapGenerator[0].LiteralSources) + } + }) + } +} diff --git a/kustomize/commands/internal/util/configmapSecretFlagsAndArgs.go b/kustomize/commands/internal/util/configmapSecretFlagsAndArgs.go index 8d52e5f741..0a198f6c89 100644 --- a/kustomize/commands/internal/util/configmapSecretFlagsAndArgs.go +++ b/kustomize/commands/internal/util/configmapSecretFlagsAndArgs.go @@ -23,31 +23,35 @@ const ( // ConfigMapSecretFlagsAndArgs encapsulates the options for add secret/configmap commands. type ConfigMapSecretFlagsAndArgs struct { - // Name of configMap/Secret (required) + // Name of ConfigMap/Secret (required) Name string - // FileSources to derive the configMap/Secret from (optional) + // FileSources to derive the ConfigMap/Secret from (optional) FileSources []string - // LiteralSources to derive the configMap/Secret from (optional) + // LiteralSources to derive the ConfigMap/Secret from (optional) LiteralSources []string - // EnvFileSource to derive the configMap/Secret from (optional) + // EnvFileSource to derive the ConfigMap/Secret from (optional) // TODO: Rationalize this name with Generic.EnvSource EnvFileSource string // Resource generation behavior (optional) Behavior string // Type of secret to create Type string - // Namespace of secret + // Namespace of ConfigMap/Secret (optional) -- if unspecified, default is assumed Namespace string // Disable name suffix DisableNameSuffixHash bool + // NewNamespace for ConfigMap/Secret (optional) -- only for 'edit set' command + NewNamespace string } -// Validate validates required fields are set to support structured generation. -func (a *ConfigMapSecretFlagsAndArgs) Validate(args []string) error { +// ValidateAdd validates required fields are set to support structured generation for the +// edit add command. +func (a *ConfigMapSecretFlagsAndArgs) ValidateAdd(args []string) error { if len(args) != 1 { return fmt.Errorf("name must be specified once") } a.Name = args[0] + if len(a.EnvFileSource) == 0 && len(a.FileSources) == 0 && len(a.LiteralSources) == 0 { return fmt.Errorf("at least from-env-file, or from-file or from-literal must be set") } @@ -62,6 +66,21 @@ func (a *ConfigMapSecretFlagsAndArgs) Validate(args []string) error { return nil } +// ValidateSet validates required fields are set to support structured generation for the +// edit set command. +func (a *ConfigMapSecretFlagsAndArgs) ValidateSet(args []string) error { + if len(args) != 1 { + return fmt.Errorf("name must be specified once") + } + a.Name = args[0] + + if len(a.LiteralSources) == 0 && a.NewNamespace == "" { + return fmt.Errorf("at least one of [--from-literal, --new-namespace] must be specified") + } + + return nil +} + // ExpandFileSource normalizes a string list, possibly // containing globs, into a validated, globless list. // For example, this list: @@ -118,6 +137,39 @@ func (a *ConfigMapSecretFlagsAndArgs) ExpandFileSource(fSys filesys.FileSystem) return nil } +// UpdateLiteralSources looks for literal sources that already exist and tries +// to replace their values with new values. +// The key specified must exist in the target resource (ConfigMap or Secret). +func UpdateLiteralSources( + args *types.GeneratorArgs, + flags ConfigMapSecretFlagsAndArgs, +) error { + sources := make(map[string]any) + for _, val := range args.LiteralSources { + key, value, _ := strings.Cut(val, "=") + sources[key] = value + } + + for _, val := range flags.LiteralSources { + key, value, _ := strings.Cut(val, "=") + if _, ok := sources[key]; !ok { + return fmt.Errorf("key '%s' not found in resource", key) + } + + sources[key] = value + } + + // re-assemble key-pairs + newLiteralSources := make([]string, 0) + for key, val := range sources { + newLiteralSources = append(newLiteralSources, fmt.Sprintf("%s=%s", key, val)) + } + + args.LiteralSources = newLiteralSources + + return nil +} + func MergeFlagsIntoGeneratorArgs(args *types.GeneratorArgs, flags ConfigMapSecretFlagsAndArgs) { if len(flags.LiteralSources) > 0 { args.LiteralSources = append( diff --git a/kustomize/commands/internal/util/configmapSecretFlagsAndArgs_test.go b/kustomize/commands/internal/util/configmapSecretFlagsAndArgs_test.go index c736eccfac..a8b7947fbc 100644 --- a/kustomize/commands/internal/util/configmapSecretFlagsAndArgs_test.go +++ b/kustomize/commands/internal/util/configmapSecretFlagsAndArgs_test.go @@ -13,13 +13,13 @@ import ( func TestDataValidation_NoName(t *testing.T) { fa := ConfigMapSecretFlagsAndArgs{} - require.Error(t, fa.Validate([]string{})) + require.Error(t, fa.ValidateAdd([]string{})) } func TestDataValidation_MoreThanOneName(t *testing.T) { fa := ConfigMapSecretFlagsAndArgs{} - require.Error(t, fa.Validate([]string{"name", "othername"})) + require.Error(t, fa.ValidateAdd([]string{"name", "othername"})) } func TestDataConfigValidation_Flags(t *testing.T) { @@ -76,7 +76,7 @@ func TestDataConfigValidation_Flags(t *testing.T) { } for _, test := range tests { - err := test.fa.Validate([]string{"name"}) + err := test.fa.ValidateAdd([]string{"name"}) if test.shouldFail { require.Error(t, err) } else { diff --git a/kustomize/go.mod b/kustomize/go.mod index 3e9340e7c6..8a37934b7d 100644 --- a/kustomize/go.mod +++ b/kustomize/go.mod @@ -33,7 +33,8 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/sys v0.13.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/evanphx/json-patch.v5 v5.6.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/kustomize/go.sum b/kustomize/go.sum index ef9e76ebd0..6a8044139e 100644 --- a/kustomize/go.sum +++ b/kustomize/go.sum @@ -66,9 +66,12 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=