From 27a8ff9d23d54d5f796a3f01774db8849a820009 Mon Sep 17 00:00:00 2001 From: Mauren Berti Date: Sun, 17 Dec 2023 21:27:31 -0500 Subject: [PATCH 1/7] feat: edit set secret * Add new functionality to allow editing a secret in a kustomization file. Initial implementation supports only --from-literal option. * Refactor edit set configmap to reuse some testing bits. --- kustomize/commands/edit/set/all.go | 7 + .../commands/edit/set/setconfigmap_test.go | 205 +++++++---------- kustomize/commands/edit/set/setsecret.go | 152 +++++++++++++ kustomize/commands/edit/set/setsecret_test.go | 213 ++++++++++++++++++ .../configmapsecret_testutils.go | 96 ++++++++ 5 files changed, 550 insertions(+), 123 deletions(-) create mode 100644 kustomize/commands/edit/set/setsecret.go create mode 100644 kustomize/commands/edit/set/setsecret_test.go create mode 100644 kustomize/commands/internal/configmapsecret/configmapsecret_testutils.go diff --git a/kustomize/commands/edit/set/all.go b/kustomize/commands/edit/set/all.go index 0c716c91b2..c4923d3a63 100644 --- a/kustomize/commands/edit/set/all.go +++ b/kustomize/commands/edit/set/all.go @@ -27,12 +27,19 @@ func NewCmdSet( # Sets the namesuffix field kustomize edit set namesuffix + + # Edits a field in an existing configmap in the kustomization file + kustomize edit set configmap my-configmap --from-literal=key1=value1 + + # Edits a field in an existing secret in the kustomization file + kustomize edit set secret my-secret --from-literal=key1=value1 `, Args: cobra.MinimumNArgs(1), } c.AddCommand( newCmdSetConfigMap(fSys, ldr, rf), + newCmdSetSecret(fSys, ldr, rf), newCmdSetNamePrefix(fSys), newCmdSetNameSuffix(fSys), newCmdSetNamespace(fSys, v), diff --git a/kustomize/commands/edit/set/setconfigmap_test.go b/kustomize/commands/edit/set/setconfigmap_test.go index e7f1939a07..cabe5668ad 100644 --- a/kustomize/commands/edit/set/setconfigmap_test.go +++ b/kustomize/commands/edit/set/setconfigmap_test.go @@ -1,245 +1,204 @@ // Copyright 2023 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 +// The duplicate linter is reporting that setsecret_test.go and this file are duplicates, which is not true. +// Disabling lint for these two files specifically to work around that. +// +//nolint:dupl package set import ( "testing" "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" + testutils_test "sigs.k8s.io/kustomize/kustomize/v5/commands/internal/configmapsecret" ) -func TestEditSetConfigMapError(t *testing.T) { - fSys := filesys.MakeFsInMemory() - pvd := provider.NewDefaultDepProvider() - - testCases := []struct { - name string - input string - args []string - expectedErrorMsg string - }{ +func TestErrorCasesEditSetConfigMap(t *testing.T) { + testCases := []testutils_test.FailureCase{ { - name: "fails to set a value because key doesn't exist", - input: `--- + Name: "fails to set a value because key doesn't exist", + KustomizationFileContent: ` apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization configMapGenerator: - literals: - - test=value + - test-cm-key=value - key1=val1 name: test-cm +- literals: + - key3=val1 + name: test-cm-2 + namespace: test-ns `, - args: []string{"test-cm", "--from-literal=key3=val2"}, - expectedErrorMsg: "key 'key3' not found in resource", + Args: []string{"test-cm", "--from-literal=test-key=test-value"}, + ExpectedErrorMsg: "key 'test-key' not found in resource", }, { - name: "fails to set a value because configmap doesn't exist", - input: `--- + Name: "fails to set a value because configmap doesn't exist", + KustomizationFileContent: ` apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization configMapGenerator: - literals: - - test=value - key1=val1 + - key2=val2 name: test-cm `, - args: []string{"test-cm2", "--from-literal=key3=val2"}, - expectedErrorMsg: "unable to find ConfigMap with name '\"test-cm2\"'", + Args: []string{"test-cm2", "--from-literal=test-key=test-value"}, + ExpectedErrorMsg: "unable to find ConfigMap with name '\"test-cm2\"'", }, { - name: "fails validation because no attributes are being changed", - input: `--- + Name: "fails validation because no attributes are being changed", + KustomizationFileContent: ` apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization configMapGenerator: - literals: - - test=value - - key1=val1 + - test-key=test-value 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", + Args: []string{"test-cm", "--namespace=test-ns"}, + ExpectedErrorMsg: "at least one of [--from-literal, --new-namespace] must be specified", }, { - name: "fails when a literal source doesn't have a key", - input: `--- + Name: "fails when a literal source doesn't have a key", + KustomizationFileContent: ` apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization configMapGenerator: - literals: - - test=value - - key1=val1 + - test-key=test-value + - key3=other-value name: test-cm `, - args: []string{"test-cm", "--from-literal=value"}, - expectedErrorMsg: "literal values must be specified in the key=value format", + Args: []string{"test-cm", "--from-literal=value"}, + ExpectedErrorMsg: "literal values must be specified in the key=value format", }, } 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() + t.Run(tc.Name, func(t *testing.T) { + kustomization, err := testutils_test.SetupEditSetConfigMapSecretTest(t, newCmdSetConfigMap, tc.KustomizationFileContent, tc.Args) + require.Nil(t, kustomization) require.Error(t, err) - require.Contains(t, err.Error(), tc.expectedErrorMsg) + 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 - }{ +func TestSuccessCasesEditSetConfigMap(t *testing.T) { + testCases := []testutils_test.SuccessCase{ { - name: "set a value successfully", - input: `--- + Name: "set a value successfully", + KustomizationFileContent: ` apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization configMapGenerator: - literals: + - a-key=a-value - key1=val1 - - test=value name: test-cm +- literals: + - another-key=another-value + - key1=value-from-cm-2 + name: test-cm-2 + namespace: another-ns `, - expectedLiterals: []string{"key1=val2", "test=value"}, - args: []string{"test-cm", "--from-literal=key1=val2"}, + ExpectedLiterals: []string{"a-key=a-value", "key1=val2"}, + Args: []string{"test-cm", "--from-literal=key1=val2"}, }, { - name: "successfully update namespace of target configmap", - input: `--- + Name: "successfully update namespace of target configmap", + KustomizationFileContent: ` apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization configMapGenerator: - literals: - - test=value - - key1=val1 + - yet-another-key=value name: test-cm namespace: test-ns `, - args: []string{"test-cm", "--namespace=test-ns", "--new-namespace=test-new-ns"}, - expectedNamespace: "test-new-ns", + Args: []string{"test-cm", "--namespace=test-ns", "--new-namespace=test-new-ns"}, + ExpectedNamespace: "test-new-ns", }, { - name: "successfully update namespace of target configmap with empty namespace in file and namespace specified in command", - input: `--- + Name: "successfully update namespace of target configmap with empty namespace in file and namespace specified in command", + KustomizationFileContent: ` apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization configMapGenerator: - literals: - - test=value - - key1=val1 + - found-key=found-value + - a-key=a-value name: test-cm `, - args: []string{"test-cm", "--namespace=default", "--new-namespace=test-new-ns"}, - expectedNamespace: "test-new-ns", + Args: []string{"test-cm", "--namespace=default", "--new-namespace=test-new-ns"}, + ExpectedNamespace: "test-new-ns", }, { - name: "successfully update namespace of target configmap with default namespace and no namespace specified in command", - input: `--- + Name: "successfully update namespace of target configmap with default namespace and no namespace specified in command", + KustomizationFileContent: ` apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization configMapGenerator: - literals: - - test=value - - key1=val1 + - a-different-key=a-different-value + - key2=value name: test-cm namespace: default `, - args: []string{"test-cm", "--new-namespace=test-new-ns"}, - expectedNamespace: "test-new-ns", + Args: []string{"test-cm", "--new-namespace=test-new-ns"}, + ExpectedNamespace: "test-new-ns", }, { - name: "successfully update literal source of target configmap with empty namespace in file and namespace specified in command", - input: `--- + Name: "successfully update literal source of target configmap with empty namespace in file and namespace specified in command", + KustomizationFileContent: ` apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization configMapGenerator: - literals: - - test=value - - key1=val1 + - key1=value + - a-separate-key=a-separate-value name: test-cm `, - args: []string{"test-cm", "--namespace=default", "--from-literal=key1=val2"}, - expectedLiterals: []string{"test=value", "key1=val2"}, + Args: []string{"test-cm", "--namespace=default", "--from-literal=key1=val2"}, + ExpectedLiterals: []string{"key1=val2", "a-separate-key=a-separate-value"}, }, { - name: "successfully update namespace of target configmap with default namespace and no namespace specified in command", - input: `--- + Name: "successfully update namespace of target configmap with default namespace and no namespace specified in command", + KustomizationFileContent: ` apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization configMapGenerator: - literals: - - test=value + - a-random-key=a-random-value - key1=val1 name: test-cm namespace: default `, - args: []string{"test-cm", "--namespace=default", "--from-literal=key1=val2"}, - expectedLiterals: []string{"test=value", "key1=val2"}, + Args: []string{"test-cm", "--from-literal=key1=val2"}, + ExpectedLiterals: []string{"a-random-key=a-random-value", "key1=val2"}, }, } 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) + t.Run(tc.Name, func(t *testing.T) { + kustomization, err := testutils_test.SetupEditSetConfigMapSecretTest(t, newCmdSetConfigMap, tc.KustomizationFileContent, tc.Args) - _, 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 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) + if len(tc.ExpectedLiterals) > 0 { + require.ElementsMatch(t, tc.ExpectedLiterals, kustomization.ConfigMapGenerator[0].LiteralSources) } }) } diff --git a/kustomize/commands/edit/set/setsecret.go b/kustomize/commands/edit/set/setsecret.go new file mode 100644 index 0000000000..a7e067e5f6 --- /dev/null +++ b/kustomize/commands/edit/set/setsecret.go @@ -0,0 +1,152 @@ +// Copyright 2023 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package set + +import ( + "fmt" + + "github.com/spf13/cobra" + "golang.org/x/exp/slices" + "sigs.k8s.io/kustomize/api/ifc" + "sigs.k8s.io/kustomize/api/konfig" + "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" +) + +func newCmdSetSecret( + fSys filesys.FileSystem, + ldr ifc.KvLoader, + rf *resource.Factory, +) *cobra.Command { + var flags util.ConfigMapSecretFlagsAndArgs + cmd := &cobra.Command{ + Use: "secret NAME [--from-literal=key1=value1] [--namespace=namespace-name] [--new-namespace=new-namespace-name]", + Short: fmt.Sprintf("Edits the value for an existing key for a secret in the %s file", konfig.DefaultKustomizationFileName()), + Long: fmt.Sprintf(`Edits the value for an existing key in an existing secret in the %s file. +Both secret name and key name must exist for this command to succeed.`, konfig.DefaultKustomizationFileName()), + Example: fmt.Sprintf(` + # Edits an existing secret in the %[1]s file, changing the value of key1 to 2 + kustomize edit set secret my-secret --from-literal=key1=2 + + # Edits an existing secret in the %[1]s file, changing namespace to 'new-namespace' + kustomize edit set secret my-secret --namespace=current-namespace --new-namespace=new-namespace +`, konfig.DefaultKustomizationFileName()), + RunE: func(_ *cobra.Command, args []string) error { + return runEditSetSecret(flags, fSys, args, ldr, rf) + }, + } + + cmd.Flags().StringArrayVar( + &flags.LiteralSources, + util.FromLiteralFlag, + []string{}, + "Specify an existing key and a new value to update a Secret (i.e. mykey=newvalue)") + cmd.Flags().StringVar( + &flags.Namespace, + util.NamespaceFlag, + "", + "Current namespace of the target Secret") + cmd.Flags().StringVar( + &flags.NewNamespace, + util.NewNamespaceFlag, + "", + "New namespace value for the target Secret") + + return cmd +} + +func runEditSetSecret( + 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 Secret + err = setSecret(ldr, kustomization, flags, rf) + if err != nil { + return fmt.Errorf("failed to create secret: %w", err) + } + + // Write out the kustomization file with added secret. + err = mf.Write(kustomization) + if err != nil { + return fmt.Errorf("failed to write kustomization file: %w", err) + } + + return nil +} + +func setSecret( + ldr ifc.KvLoader, + k *types.Kustomization, + flags util.ConfigMapSecretFlagsAndArgs, + rf *resource.Factory, +) error { + args, err := findSecretArgs(k, flags.Name, flags.Namespace) + if err != nil { + return fmt.Errorf("could not set new Secret value: %w", err) + } + + if len(flags.LiteralSources) > 0 { + err := util.UpdateLiteralSources(&args.GeneratorArgs, flags) + if err != nil { + return fmt.Errorf("failed to update literal sources: %w", err) + } + } + + // update namespace to new one + if flags.NewNamespace != "" { + args.Namespace = flags.NewNamespace + } + + // Validate by trying to create corev1.secret. + args.Options = types.MergeGlobalOptionsIntoLocal( + args.Options, k.GeneratorOptions) + + _, err = rf.MakeSecret(ldr, args) + if err != nil { + return fmt.Errorf("failed to validate Secret structure: %w", err) + } + + return nil +} + +// findSecretArgs finds the generator arguments corresponding to the specified +// Secret name. Secret must exist for this command to be successful. +func findSecretArgs(m *types.Kustomization, name, namespace string) (*types.SecretArgs, error) { + cmIndex := slices.IndexFunc(m.SecretGenerator, func(cmArgs types.SecretArgs) bool { + return name == cmArgs.Name && util.NamespaceEqual(namespace, cmArgs.Namespace) + }) + + if cmIndex == -1 { + return nil, fmt.Errorf("unable to find Secret with name '%q'", name) + } + + return &m.SecretGenerator[cmIndex], nil +} diff --git a/kustomize/commands/edit/set/setsecret_test.go b/kustomize/commands/edit/set/setsecret_test.go new file mode 100644 index 0000000000..c9207d8ab8 --- /dev/null +++ b/kustomize/commands/edit/set/setsecret_test.go @@ -0,0 +1,213 @@ +// Copyright 2023 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// The duplicate linter is reporting that setconfigmap_test.go and this file are duplicates, which is not true. +// Disabling lint for these two files specifically to work around that. +// +//nolint:dupl +package set + +import ( + "testing" + + "github.com/stretchr/testify/require" + testutils_test "sigs.k8s.io/kustomize/kustomize/v5/commands/internal/configmapsecret" +) + +func TestFailureCasesEditSetSecret(t *testing.T) { + testCases := []testutils_test.FailureCase{ + { + Name: "fails to set a value because key doesn't exist", + KustomizationFileContent: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +secretGenerator: +- literals: + - key1=val1 + name: test-secret + type: Opaque +- literals: + - key3=val1 + name: test-secret-2 + namespace: test-ns + type: Opaque +`, + Args: []string{"test-secret", "--from-literal=key3=val2"}, + ExpectedErrorMsg: "key 'key3' not found in resource", + }, + { + Name: "fails to set a value because secret doesn't exist", + KustomizationFileContent: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +secretGenerator: +- literals: + - key1=val1 + name: test-secret + type: Opaque +- literals: + - key2=value + name: another-secret + namespace: a-namespace + type: Opaque +`, + Args: []string{"test-secret2", "--from-literal=key3=val2"}, + ExpectedErrorMsg: "unable to find Secret with name '\"test-secret2\"'", + }, + { + Name: "fails validation because no attributes are being changed", + KustomizationFileContent: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +secretGenerator: +- literals: + - a-test-key=a-test-value + name: test-secret + namespace: test-ns + type: Opaque +`, + Args: []string{"test-secret", "--namespace=test-ns"}, + ExpectedErrorMsg: "at least one of [--from-literal, --new-namespace] must be specified", + }, + { + Name: "fails when a literal source doesn't have a key", + KustomizationFileContent: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +secretGenerator: +- literals: + - some-key=some-value + name: test-secret + type: Opaque +`, + Args: []string{"test-secret", "--from-literal=value"}, + ExpectedErrorMsg: "literal values must be specified in the key=value format", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + kustomization, err := testutils_test.SetupEditSetConfigMapSecretTest(t, newCmdSetSecret, tc.KustomizationFileContent, tc.Args) + + require.Nil(t, kustomization) + require.Error(t, err) + require.Contains(t, err.Error(), tc.ExpectedErrorMsg) + }) + } +} + +func TestSuccessCasesEditSetSecret(t *testing.T) { + testCases := []testutils_test.SuccessCase{ + { + Name: "set a value successfully", + KustomizationFileContent: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +secretGenerator: +- literals: + - random-key=random-value + - key1=value + name: test-secret + type: Opaque +`, + ExpectedLiterals: []string{"key1=val2", "random-key=random-value"}, + Args: []string{"test-secret", "--from-literal=key1=val2"}, + }, + { + Name: "successfully update namespace of target secret", + KustomizationFileContent: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +secretGenerator: +- literals: + - a-key=test + - another-key=value + name: test-secret + namespace: test-ns + type: Opaque +`, + Args: []string{"test-secret", "--namespace=test-ns", "--new-namespace=test-new-ns"}, + ExpectedNamespace: "test-new-ns", + }, + { + Name: "successfully update namespace of target secret with empty namespace in file and namespace specified in command", + KustomizationFileContent: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +secretGenerator: +- literals: + - key1=value1 + - another-key=another-value + name: test-secret + type: Opaque +`, + Args: []string{"test-secret", "--namespace=default", "--new-namespace=test-new-ns"}, + ExpectedNamespace: "test-new-ns", + }, + { + Name: "successfully update namespace of target secret with default namespace and no namespace specified in command", + KustomizationFileContent: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +secretGenerator: +- literals: + - random-key=random-value + name: test-secret + namespace: default + type: Opaque +`, + Args: []string{"test-secret", "--new-namespace=test-new-ns"}, + ExpectedNamespace: "test-new-ns", + }, + { + Name: "successfully update literal source of target secret with empty namespace in file and namespace specified in command", + KustomizationFileContent: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +secretGenerator: +- literals: + - a-key=a-value + - key1=value + name: test-secret + type: Opaque +`, + Args: []string{"test-secret", "--namespace=default", "--from-literal=key1=val2"}, + ExpectedLiterals: []string{"a-key=a-value", "key1=val2"}, + }, + { + Name: "successfully update namespace of target secret with default namespace and no namespace specified in command", + KustomizationFileContent: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +secretGenerator: +- literals: + - test-secret-key=value + - key1=val1 + name: test-secret + namespace: default + type: Opaque +`, + Args: []string{"test-secret", "--from-literal=key1=val2"}, + ExpectedLiterals: []string{"test-secret-key=value", "key1=val2"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + kustomization, err := testutils_test.SetupEditSetConfigMapSecretTest(t, newCmdSetSecret, tc.KustomizationFileContent, tc.Args) + + require.NoError(t, err) + require.NotNil(t, kustomization) + require.NotEmpty(t, kustomization.SecretGenerator) + require.Greater(t, len(kustomization.SecretGenerator), 0) + + if tc.ExpectedNamespace != "" { + require.Equal(t, tc.ExpectedNamespace, kustomization.SecretGenerator[0].Namespace) + } + + if len(tc.ExpectedLiterals) > 0 { + require.ElementsMatch(t, tc.ExpectedLiterals, kustomization.SecretGenerator[0].LiteralSources) + } + }) + } +} diff --git a/kustomize/commands/internal/configmapsecret/configmapsecret_testutils.go b/kustomize/commands/internal/configmapsecret/configmapsecret_testutils.go new file mode 100644 index 0000000000..b4f763e0a5 --- /dev/null +++ b/kustomize/commands/internal/configmapsecret/configmapsecret_testutils.go @@ -0,0 +1,96 @@ +// Copyright 2023 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package configmapsecret_test + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" + "sigs.k8s.io/kustomize/api/ifc" + "sigs.k8s.io/kustomize/api/kv" + "sigs.k8s.io/kustomize/api/pkg/loader" + "sigs.k8s.io/kustomize/api/provider" + "sigs.k8s.io/kustomize/api/resource" + "sigs.k8s.io/kustomize/api/types" + "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" +) + +// FailureCase specifies a test case for a failure in the 'edit set configmap'/'edit set secret' commands. +type FailureCase struct { + // The name of the test case + Name string + + // The kustomization file content for the test case in YAML format + KustomizationFileContent string + + // Arguments passed to the test case command + Args []string + + // The expected error message for the test case + ExpectedErrorMsg string +} + +// SuccessCase specifies a test case for a success in the 'edit set configmap'/'edit set secret' commands. +type SuccessCase struct { + // The name of the test case + Name string + + // The kustomization file content for the test case in YAML format + KustomizationFileContent string + + // Arguments passed to the test case command + Args []string + + // List of expected literals for the result of the test case + ExpectedLiterals []string + + // The expected namespace for the result of the test case + ExpectedNamespace string +} + +func SetupEditSetConfigMapSecretTest( + t *testing.T, + command func(filesys.FileSystem, ifc.KvLoader, *resource.Factory) *cobra.Command, + input string, + args []string, +) (*types.Kustomization, error) { + t.Helper() + fSys := filesys.MakeFsInMemory() + pvd := provider.NewDefaultDepProvider() + + cmd := command( + fSys, + kv.NewLoader( + loader.NewFileLoaderAtCwd(fSys), + pvd.GetFieldValidator()), + pvd.GetResourceFactory(), + ) + + testutils_test.WriteTestKustomizationWith(fSys, []byte(input)) + + cmd.SetArgs(args) + err := cmd.Execute() + + //nolint: wrapcheck + // this needs to be bubbled up for checking in the test + if err != nil { + return nil, err + } + + 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) + + return kustomization, nil +} From 23fbdd2ab5c9afa72541ed7cc97017a06627c68b Mon Sep 17 00:00:00 2001 From: Mauren Berti Date: Mon, 15 Jan 2024 21:05:24 -0500 Subject: [PATCH 2/7] chore: fix spacing in added description --- kustomize/commands/edit/set/all.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kustomize/commands/edit/set/all.go b/kustomize/commands/edit/set/all.go index c4923d3a63..961aee7d41 100644 --- a/kustomize/commands/edit/set/all.go +++ b/kustomize/commands/edit/set/all.go @@ -28,11 +28,11 @@ func NewCmdSet( # Sets the namesuffix field kustomize edit set namesuffix - # Edits a field in an existing configmap in the kustomization file - kustomize edit set configmap my-configmap --from-literal=key1=value1 + # Edits a field in an existing configmap in the kustomization file + kustomize edit set configmap my-configmap --from-literal=key1=value1 - # Edits a field in an existing secret in the kustomization file - kustomize edit set secret my-secret --from-literal=key1=value1 + # Edits a field in an existing secret in the kustomization file + kustomize edit set secret my-secret --from-literal=key1=value1 `, Args: cobra.MinimumNArgs(1), } From fd09a6ed5066d05992fc3d03c83766997a69a8c4 Mon Sep 17 00:00:00 2001 From: Mauren Berti Date: Sun, 28 Jan 2024 16:13:12 -0500 Subject: [PATCH 3/7] chore: changes from code review Remove error checks from the utility function and bubble them up instead. --- .../internal/configmapsecret/configmapsecret_testutils.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/kustomize/commands/internal/configmapsecret/configmapsecret_testutils.go b/kustomize/commands/internal/configmapsecret/configmapsecret_testutils.go index b4f763e0a5..376b79c1f5 100644 --- a/kustomize/commands/internal/configmapsecret/configmapsecret_testutils.go +++ b/kustomize/commands/internal/configmapsecret/configmapsecret_testutils.go @@ -81,16 +81,12 @@ func SetupEditSetConfigMapSecretTest( return nil, err } - 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) - - return kustomization, nil + //nolint: wrapcheck + return mf.Read() } From ca8d6292304b84cd4573c520b36490274b452449 Mon Sep 17 00:00:00 2001 From: Mauren Berti Date: Mon, 29 Jan 2024 22:48:00 -0500 Subject: [PATCH 4/7] chore: update help to include bit about default namespace Add a blurb to the help output for both 'edit set configmap' and 'edit set secret' to clarify that whenever a namespace is not specified, the default namespace is implicitly defined as part of the commands. --- kustomize/commands/edit/set/setconfigmap.go | 23 ++++++++++++++------- kustomize/commands/edit/set/setsecret.go | 21 ++++++++++++------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/kustomize/commands/edit/set/setconfigmap.go b/kustomize/commands/edit/set/setconfigmap.go index 71aae88da1..ff71b8886b 100644 --- a/kustomize/commands/edit/set/setconfigmap.go +++ b/kustomize/commands/edit/set/setconfigmap.go @@ -1,11 +1,14 @@ // Copyright 2023 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 - +// +//nolint:dupl package set import ( "fmt" + "sigs.k8s.io/kustomize/api/konfig" + "github.com/spf13/cobra" "golang.org/x/exp/slices" "sigs.k8s.io/kustomize/api/ifc" @@ -24,16 +27,20 @@ func newCmdSetConfigMap( var flags util.ConfigMapSecretFlagsAndArgs cmd := &cobra.Command{ Use: "configmap NAME [--from-literal=key1=value1] [--namespace=namespace-name] [--new-namespace=new-namespace-name]", - 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 + Short: fmt.Sprintf("Edits the value for an existing key for a ConfigMap in the %s file", konfig.DefaultKustomizationFileName()), + Long: fmt.Sprintf(`Edits the value for an existing key in an existing ConfigMap in the %[1]s file. +ConfigMap name, ConfigMap namespace, and key name must match an existing entry in the %[1]s file for this command to succeed. +When namespace is omitted, the default namespace is used.`, konfig.DefaultKustomizationFileName()), + Example: fmt.Sprintf(` + # Edits an existing ConfigMap in the %[1]s file, changing value of key1 to 2, and namespace is implicitly defined as "default" kustomize edit set configmap my-configmap --from-literal=key1=2 - # Edits an existing configmap in the kustomization file, changing namespace to 'new-namespace' + # Edits an existing ConfigMap in the %[1]s file, changing value of key1 to 2, and explicitly define namespace as "default" + kustomize edit set configmap my-configmap --from-literal=key1=2 --namespace default + + # Edits an existing ConfigMap in the %[1]s file, changing namespace to "new-namespace" kustomize edit set configmap my-configmap --namespace=current-namespace --new-namespace=new-namespace -`, +`, konfig.DefaultKustomizationFileName()), RunE: func(_ *cobra.Command, args []string) error { return runEditSetConfigMap(flags, fSys, args, ldr, rf) }, diff --git a/kustomize/commands/edit/set/setsecret.go b/kustomize/commands/edit/set/setsecret.go index a7e067e5f6..ad91634b2f 100644 --- a/kustomize/commands/edit/set/setsecret.go +++ b/kustomize/commands/edit/set/setsecret.go @@ -1,6 +1,7 @@ // Copyright 2023 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 - +// +//nolint:dupl package set import ( @@ -25,15 +26,19 @@ func newCmdSetSecret( var flags util.ConfigMapSecretFlagsAndArgs cmd := &cobra.Command{ Use: "secret NAME [--from-literal=key1=value1] [--namespace=namespace-name] [--new-namespace=new-namespace-name]", - Short: fmt.Sprintf("Edits the value for an existing key for a secret in the %s file", konfig.DefaultKustomizationFileName()), - Long: fmt.Sprintf(`Edits the value for an existing key in an existing secret in the %s file. -Both secret name and key name must exist for this command to succeed.`, konfig.DefaultKustomizationFileName()), + Short: fmt.Sprintf("Edits the value for an existing key for a Secret in the %s file", konfig.DefaultKustomizationFileName()), + Long: fmt.Sprintf(`Edits the value for an existing key in an existing Secret in the %[1]s file. +Secret name, Secret namespace, and key name must match an existing entry in the %[1]s file for this command to succeed. +When namespace is omitted, the default namespace is used.`, konfig.DefaultKustomizationFileName()), Example: fmt.Sprintf(` - # Edits an existing secret in the %[1]s file, changing the value of key1 to 2 - kustomize edit set secret my-secret --from-literal=key1=2 + # Edits an existing Secret in the %[1]s file, changing the value of key1 to 2, and namespace is implicitly defined as "default" + kustomize edit set secret my-secret --from-literal=key1=2 + + # Edits an existing Secret in the %[1]s file, changing the value of key1 to 2, and explicitly define namespace as "default" + kustomize edit set secret my-secret --from-literal=key1=2 --namespace default - # Edits an existing secret in the %[1]s file, changing namespace to 'new-namespace' - kustomize edit set secret my-secret --namespace=current-namespace --new-namespace=new-namespace + # Edits an existing Secret in the %[1]s file, changing namespace to "new-namespace" + kustomize edit set secret my-secret --namespace=current-namespace --new-namespace=new-namespace `, konfig.DefaultKustomizationFileName()), RunE: func(_ *cobra.Command, args []string) error { return runEditSetSecret(flags, fSys, args, ldr, rf) From 6c1fea79ed322a8e15f38d1eccbdcf83d3244223 Mon Sep 17 00:00:00 2001 From: Mauren Berti Date: Mon, 29 Jan 2024 22:56:59 -0500 Subject: [PATCH 5/7] chore: add failure test case for empty generator --- kustomize/commands/edit/set/setconfigmap_test.go | 12 +++++++++++- kustomize/commands/edit/set/setsecret_test.go | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/kustomize/commands/edit/set/setconfigmap_test.go b/kustomize/commands/edit/set/setconfigmap_test.go index cabe5668ad..7f96933803 100644 --- a/kustomize/commands/edit/set/setconfigmap_test.go +++ b/kustomize/commands/edit/set/setconfigmap_test.go @@ -46,7 +46,7 @@ configMapGenerator: name: test-cm `, Args: []string{"test-cm2", "--from-literal=test-key=test-value"}, - ExpectedErrorMsg: "unable to find ConfigMap with name '\"test-cm2\"'", + ExpectedErrorMsg: "unable to find ConfigMap with name \"test-cm2\"", }, { Name: "fails validation because no attributes are being changed", @@ -76,6 +76,16 @@ configMapGenerator: Args: []string{"test-cm", "--from-literal=value"}, ExpectedErrorMsg: "literal values must be specified in the key=value format", }, + { + Name: "fails when the configMapGenerator field has no items", + KustomizationFileContent: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +configMapGenerator: [] +`, + Args: []string{"test-cm", "--from-literal=value"}, + ExpectedErrorMsg: "unable to find ConfigMap with name \"test-cm\"", + }, } for _, tc := range testCases { diff --git a/kustomize/commands/edit/set/setsecret_test.go b/kustomize/commands/edit/set/setsecret_test.go index c9207d8ab8..bb27c8d17b 100644 --- a/kustomize/commands/edit/set/setsecret_test.go +++ b/kustomize/commands/edit/set/setsecret_test.go @@ -52,7 +52,7 @@ secretGenerator: type: Opaque `, Args: []string{"test-secret2", "--from-literal=key3=val2"}, - ExpectedErrorMsg: "unable to find Secret with name '\"test-secret2\"'", + ExpectedErrorMsg: "unable to find Secret with name \"test-secret2\"", }, { Name: "fails validation because no attributes are being changed", @@ -83,6 +83,16 @@ secretGenerator: Args: []string{"test-secret", "--from-literal=value"}, ExpectedErrorMsg: "literal values must be specified in the key=value format", }, + { + Name: "fails when the secretGenerator field has no items", + KustomizationFileContent: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +secretGenerator: [] +`, + Args: []string{"test-secret", "--from-literal=value"}, + ExpectedErrorMsg: "unable to find Secret with name \"test-secret\"", + }, } for _, tc := range testCases { From 14c091aec7914b2f59bf3fd45f479552b76aa413 Mon Sep 17 00:00:00 2001 From: Mauren Berti Date: Mon, 29 Jan 2024 22:57:30 -0500 Subject: [PATCH 6/7] fix: remove excessive quoting from error messages --- kustomize/commands/edit/set/setconfigmap.go | 2 +- kustomize/commands/edit/set/setsecret.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kustomize/commands/edit/set/setconfigmap.go b/kustomize/commands/edit/set/setconfigmap.go index ff71b8886b..b7a86ad747 100644 --- a/kustomize/commands/edit/set/setconfigmap.go +++ b/kustomize/commands/edit/set/setconfigmap.go @@ -151,7 +151,7 @@ func findConfigMapArgs(m *types.Kustomization, name, namespace string) (*types.C }) if cmIndex == -1 { - return nil, fmt.Errorf("unable to find ConfigMap with name '%q'", name) + return nil, fmt.Errorf("unable to find ConfigMap with name %q", name) } return &m.ConfigMapGenerator[cmIndex], nil diff --git a/kustomize/commands/edit/set/setsecret.go b/kustomize/commands/edit/set/setsecret.go index ad91634b2f..c9cca6f36c 100644 --- a/kustomize/commands/edit/set/setsecret.go +++ b/kustomize/commands/edit/set/setsecret.go @@ -150,7 +150,7 @@ func findSecretArgs(m *types.Kustomization, name, namespace string) (*types.Secr }) if cmIndex == -1 { - return nil, fmt.Errorf("unable to find Secret with name '%q'", name) + return nil, fmt.Errorf("unable to find Secret with name %q", name) } return &m.SecretGenerator[cmIndex], nil From 3bb9a6d4147aef835e075847bd2778b0fff38646 Mon Sep 17 00:00:00 2001 From: Mauren Berti Date: Tue, 6 Feb 2024 21:11:55 -0500 Subject: [PATCH 7/7] fix: update long description as per code review request --- kustomize/commands/edit/set/setconfigmap.go | 4 +++- kustomize/commands/edit/set/setsecret.go | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/kustomize/commands/edit/set/setconfigmap.go b/kustomize/commands/edit/set/setconfigmap.go index b7a86ad747..d8ed892e25 100644 --- a/kustomize/commands/edit/set/setconfigmap.go +++ b/kustomize/commands/edit/set/setconfigmap.go @@ -30,7 +30,9 @@ func newCmdSetConfigMap( Short: fmt.Sprintf("Edits the value for an existing key for a ConfigMap in the %s file", konfig.DefaultKustomizationFileName()), Long: fmt.Sprintf(`Edits the value for an existing key in an existing ConfigMap in the %[1]s file. ConfigMap name, ConfigMap namespace, and key name must match an existing entry in the %[1]s file for this command to succeed. -When namespace is omitted, the default namespace is used.`, konfig.DefaultKustomizationFileName()), +When namespace is omitted, the default namespace is used. Conversely, when an entry without a specified namespace exists +in the %[1]s file, it can be updated by either omitting the namespace on the kustomize edit set configmap invocation or by +specifying --namespace=default.`, konfig.DefaultKustomizationFileName()), Example: fmt.Sprintf(` # Edits an existing ConfigMap in the %[1]s file, changing value of key1 to 2, and namespace is implicitly defined as "default" kustomize edit set configmap my-configmap --from-literal=key1=2 diff --git a/kustomize/commands/edit/set/setsecret.go b/kustomize/commands/edit/set/setsecret.go index c9cca6f36c..ac9ee6f96b 100644 --- a/kustomize/commands/edit/set/setsecret.go +++ b/kustomize/commands/edit/set/setsecret.go @@ -29,7 +29,9 @@ func newCmdSetSecret( Short: fmt.Sprintf("Edits the value for an existing key for a Secret in the %s file", konfig.DefaultKustomizationFileName()), Long: fmt.Sprintf(`Edits the value for an existing key in an existing Secret in the %[1]s file. Secret name, Secret namespace, and key name must match an existing entry in the %[1]s file for this command to succeed. -When namespace is omitted, the default namespace is used.`, konfig.DefaultKustomizationFileName()), +When namespace is omitted, the default namespace is used. Conversely, when an entry without a specified namespace exists +in the %[1]s file, it can be updated by either omitting the namespace on the kustomize edit set secret invocation or by +specifying --namespace=default.`, konfig.DefaultKustomizationFileName()), Example: fmt.Sprintf(` # Edits an existing Secret in the %[1]s file, changing the value of key1 to 2, and namespace is implicitly defined as "default" kustomize edit set secret my-secret --from-literal=key1=2