fix: support namespace flag on edit remove secret/configmap

Fix the 'edit remove secret'/'edit remove configmap' commands that were previously
missing support to specifying a namespace.
This commit is contained in:
Mauren Berti
2023-11-18 18:10:31 -05:00
parent bfb00ecb27
commit 228d22cff0
4 changed files with 343 additions and 100 deletions

View File

@@ -13,33 +13,49 @@ import (
"sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kustomize/v5/commands/internal/kustfile" "sigs.k8s.io/kustomize/kustomize/v5/commands/internal/kustfile"
"sigs.k8s.io/kustomize/kustomize/v5/commands/internal/util"
"sigs.k8s.io/kustomize/kyaml/filesys" "sigs.k8s.io/kustomize/kyaml/filesys"
) )
type removeConfigMapOptions struct { type removeConfigMapOptions struct {
configMapNamesToRemove []string configMapNamesToRemove []string
namespace string
} }
// newCmdRemoveConfigMap removes configMapGenerator(s) with the specified name(s). // newCmdRemoveConfigMap removes configMapGenerator(s) with the specified name(s).
func newCmdRemoveConfigMap(fSys filesys.FileSystem) *cobra.Command { func newCmdRemoveConfigMap(fSys filesys.FileSystem) *cobra.Command {
var o removeConfigMapOptions var flags removeConfigMapOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "configmap", Use: "configmap NAME [,NAME] [--namespace=namespace-name]",
Short: "Removes the specified configmap(s) from " + Short: "Removes the specified configmap(s) from " +
konfig.DefaultKustomizationFileName(), konfig.DefaultKustomizationFileName(),
Long: "", Long: `Removes the specified configmap(s) from the ` + konfig.DefaultKustomizationFileName() + ` file in the specified namespace.
If multiple configmap names are specified, the command will not fail on secret names that were not found in the file,
but will issue a warning for each name that wasn't found.`,
Example: ` Example: `
remove configmap my-configmap # Removes a single configmap named 'my-configmap' in the default namespace from the ` + konfig.DefaultKustomizationFileName() + ` file
`, kustomize edit remove configmap my-configmap
# Removes configmaps named 'my-configmap' and 'other-configmap' in namespace 'test-namespace' from the ` + konfig.DefaultKustomizationFileName() + ` file
kustomize edit remove configmap my-configmap,other-configmap --namespace=test-namespace
`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args) err := flags.Validate(args)
if err != nil { if err != nil {
return err return err
} }
return o.RunRemoveConfigMap(fSys) return flags.RunRemoveConfigMap(fSys)
}, },
} }
cmd.Flags().StringVar(
&flags.namespace,
util.NamespaceFlag,
"",
"Namespace to remove ConfigMap(s) from",
)
return cmd return cmd
} }
@@ -69,14 +85,16 @@ func (o *removeConfigMapOptions) RunRemoveConfigMap(fSys filesys.FileSystem) err
} }
foundConfigMaps := make(map[string]struct{}) foundConfigMaps := make(map[string]struct{})
remainingConfigMaps := make([]types.ConfigMapArgs, 0, len(m.ConfigMapGenerator))
newConfigMaps := make([]types.ConfigMapArgs, 0, len(m.ConfigMapGenerator))
for _, currentConfigMap := range m.ConfigMapGenerator { for _, currentConfigMap := range m.ConfigMapGenerator {
if kustfile.StringInSlice(currentConfigMap.Name, o.configMapNamesToRemove) { if kustfile.StringInSlice(currentConfigMap.Name, o.configMapNamesToRemove) &&
util.NamespaceEqual(currentConfigMap.Namespace, o.namespace) {
foundConfigMaps[currentConfigMap.Name] = struct{}{} foundConfigMaps[currentConfigMap.Name] = struct{}{}
continue continue
} }
newConfigMaps = append(newConfigMaps, currentConfigMap)
remainingConfigMaps = append(remainingConfigMaps, currentConfigMap)
} }
if len(foundConfigMaps) == 0 { if len(foundConfigMaps) == 0 {
@@ -90,7 +108,7 @@ func (o *removeConfigMapOptions) RunRemoveConfigMap(fSys filesys.FileSystem) err
} }
} }
m.ConfigMapGenerator = newConfigMaps m.ConfigMapGenerator = remainingConfigMaps
err = mf.Write(m) err = mf.Write(m)
if err != nil { if err != nil {
return fmt.Errorf("failed to write kustomization file: %w", err) return fmt.Errorf("failed to write kustomization file: %w", err)

View File

@@ -1,10 +1,9 @@
// Copyright 2023 The Kubernetes Authors. // Copyright 2023 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package remove //nolint:testpackage package remove
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -13,84 +12,188 @@ import (
) )
func TestRemoveConfigMap(t *testing.T) { func TestRemoveConfigMap(t *testing.T) {
const configMapName01 = "example-configmap-01"
const configMapName02 = "example-configmap-02"
tests := map[string]struct { tests := map[string]struct {
input string input string
args []string args []string
expectedOutput string expectedOutput string
wantErr bool
expectedErr string expectedErr string
}{ }{
"happy path": { "removes a configmap successfully": {
input: fmt.Sprintf(` input: `
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
configMapGenerator: configMapGenerator:
- name: %s - name: test-cm-1
files: files:
- application.properties - application.properties
`, configMapName01), `,
args: []string{configMapName01}, args: []string{"test-cm-1"},
expectedOutput: ` expectedOutput: `
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
`, `,
}, },
"multiple": { "removes multiple configmaps successfully": {
input: fmt.Sprintf(` input: `
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
configMapGenerator: configMapGenerator:
- name: %s - name: test-cm-1
files: files:
- application.properties - application.properties
- name: %s - name: test-cm-2
files: files:
- application.properties - application.properties
`, configMapName01, configMapName02), `,
args: []string{ args: []string{"test-cm-1,test-cm-2"},
fmt.Sprintf("%s,%s", configMapName01, configMapName02),
},
expectedOutput: ` expectedOutput: `
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
`, `,
}, },
"miss": { "removes a configmap successfully when single configmap name is specified, it exists, but is not the only configmap present": {
input: fmt.Sprintf(` input: `
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
configMapGenerator: configMapGenerator:
- name: %s - name: test-cm-1
namespace: test-ns
files: files:
- application.properties - application.properties
`, configMapName01), - name: test-cm-2
namespace: default
literals:
- test-key=test-value
`,
args: []string{"test-cm-1", "--namespace=test-ns"},
wantErr: false,
expectedOutput: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
- literals:
- test-key=test-value
name: test-cm-2
namespace: default
`,
},
"succeeds when one configmap name exists and one doesn't exist": {
input: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
- name: test-cm-1
files:
- application.properties
`,
args: []string{"test-cm-1,foo"},
expectedOutput: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`,
},
"succeeds when one configmap name exists in the specified namespace": {
input: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
- name: test-cm
namespace: test-ns
files:
- application.properties
`,
expectedOutput: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`,
wantErr: false,
args: []string{"test-cm", "--namespace=test-ns"},
},
"handles empty namespace as default in the args": {
input: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
- name: test-cm
namespace: default
files:
- application.properties
`,
expectedOutput: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`,
wantErr: false,
args: []string{"test-cm"},
},
"handles empty namespace as default in the kustomization file": {
input: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
- name: test-cm
files:
- application.properties
`,
expectedOutput: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`,
wantErr: false,
args: []string{"test-cm", "--namespace=default"},
},
"fails when single configmap name is specified and doesn't exist": {
input: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
- name: test-cm-1
files:
- application.properties
`,
args: []string{"foo"}, args: []string{"foo"},
wantErr: true,
expectedErr: "no specified configmap(s) were found", expectedErr: "no specified configmap(s) were found",
}, },
"no configmap name specified": { "fails when single configmap name is specified and doesn't exist in the specified namespace": {
args: []string{}, input: `
expectedErr: "at least one configmap name must be specified",
},
"too many configmap names specified": {
args: []string{"test1", "test2"},
expectedErr: "too many arguments",
},
"one existing and one non-existing": {
input: fmt.Sprintf(`
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
configMapGenerator: configMapGenerator:
- name: %s - name: test-cm-1
namespace: test-ns
files: files:
- application.properties - application.properties
`, configMapName01), `,
args: []string{fmt.Sprintf("%s,%s", configMapName01, "foo")}, args: []string{"test-cm-1"},
expectedOutput: ` wantErr: true,
expectedErr: "no specified configmap(s) were found",
},
"fails when single configmap name is specified and doesn't exist in the specified namespace, and neither namespace is the default one": {
input: `
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
configMapGenerator:
- name: test-cm-1
namespace: test-ns
files:
- application.properties
`, `,
args: []string{"test-cm-1", "--namespace=other-ns"},
wantErr: true,
expectedErr: "no specified configmap(s) were found",
},
"fails when no configmap name is specified": {
args: []string{},
wantErr: true,
expectedErr: "at least one configmap name must be specified",
},
"fails when too many configmap names are specified": {
args: []string{"test1", "test2"},
wantErr: true,
expectedErr: "too many arguments",
}, },
} }
@@ -99,9 +202,10 @@ kind: Kustomization
fSys := filesys.MakeFsInMemory() fSys := filesys.MakeFsInMemory()
testutils_test.WriteTestKustomizationWith(fSys, []byte(tc.input)) testutils_test.WriteTestKustomizationWith(fSys, []byte(tc.input))
cmd := newCmdRemoveConfigMap(fSys) cmd := newCmdRemoveConfigMap(fSys)
err := cmd.RunE(cmd, tc.args) cmd.SetArgs(tc.args)
err := cmd.Execute()
if tc.expectedErr != "" { if tc.wantErr {
require.Error(t, err) require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedErr) require.Contains(t, err.Error(), tc.expectedErr)
return return

View File

@@ -13,33 +13,50 @@ import (
"sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kustomize/v5/commands/internal/kustfile" "sigs.k8s.io/kustomize/kustomize/v5/commands/internal/kustfile"
"sigs.k8s.io/kustomize/kustomize/v5/commands/internal/util"
"sigs.k8s.io/kustomize/kyaml/filesys" "sigs.k8s.io/kustomize/kyaml/filesys"
) )
type removeSecretOptions struct { type removeSecretOptions struct {
secretNamesToRemove []string secretNamesToRemove []string
namespace string
} }
// newCmdRemoveSecret removes secretGenerator(s) with the specified name(s). // newCmdRemoveSecret removes secretGenerator(s) with the specified name(s).
func newCmdRemoveSecret(fSys filesys.FileSystem) *cobra.Command { func newCmdRemoveSecret(fSys filesys.FileSystem) *cobra.Command {
var o removeSecretOptions var flags removeSecretOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "secret", Use: "secret NAME [,NAME] [--namespace=namespace-name]",
Short: "Removes the specified secret(s) from " + Short: "Removes the specified secret(s) from " +
konfig.DefaultKustomizationFileName(), konfig.DefaultKustomizationFileName(),
Long: "", Long: `Removes the specified secret(s) from the ` + konfig.DefaultKustomizationFileName() + ` file in the specified namespace.
If multiple secret names are specified, the command will not fail on secret names that were not found in the file,
but will issue a warning for each name that wasn't found.`,
Example: ` Example: `
remove secret my-secret # Removes a single secret named 'my-secret' in the default namespace from the ` + konfig.DefaultKustomizationFileName() + ` file
`, kustomize edit remove secret my-secret
# Removes secrets named 'my-secret' and 'other-secret' in namespace 'test-namespace' from the ` + konfig.DefaultKustomizationFileName() + ` file
kustomize edit remove secret my-secret,other-secret --namespace=test-namespace
`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args) err := flags.Validate(args)
if err != nil { if err != nil {
return err return err
} }
return o.RunRemoveSecret(fSys) return flags.RunRemoveSecret(fSys)
}, },
} }
cmd.Flags().StringVar(
&flags.namespace,
util.NamespaceFlag,
"",
"Namespace to remove Secret(s) from",
)
return cmd return cmd
} }
@@ -69,14 +86,15 @@ func (o *removeSecretOptions) RunRemoveSecret(fSys filesys.FileSystem) error {
} }
foundSecrets := make(map[string]struct{}) foundSecrets := make(map[string]struct{})
remainingSecrets := make([]types.SecretArgs, 0, len(m.SecretGenerator))
newSecrets := make([]types.SecretArgs, 0, len(m.SecretGenerator))
for _, currentSecret := range m.SecretGenerator { for _, currentSecret := range m.SecretGenerator {
if kustfile.StringInSlice(currentSecret.Name, o.secretNamesToRemove) { if kustfile.StringInSlice(currentSecret.Name, o.secretNamesToRemove) &&
util.NamespaceEqual(currentSecret.Namespace, o.namespace) {
foundSecrets[currentSecret.Name] = struct{}{} foundSecrets[currentSecret.Name] = struct{}{}
continue continue
} }
newSecrets = append(newSecrets, currentSecret) remainingSecrets = append(remainingSecrets, currentSecret)
} }
if len(foundSecrets) == 0 { if len(foundSecrets) == 0 {
@@ -89,8 +107,8 @@ func (o *removeSecretOptions) RunRemoveSecret(fSys filesys.FileSystem) error {
log.Printf("secret %s doesn't exist in kustomization file", name) log.Printf("secret %s doesn't exist in kustomization file", name)
} }
} }
m.SecretGenerator = newSecrets
m.SecretGenerator = remainingSecrets
err = mf.Write(m) err = mf.Write(m)
if err != nil { if err != nil {
return fmt.Errorf("failed to write kustomization file: %w", err) return fmt.Errorf("failed to write kustomization file: %w", err)

View File

@@ -1,10 +1,9 @@
// Copyright 2023 The Kubernetes Authors. // Copyright 2023 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package remove //nolint:testpackage package remove
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -13,85 +12,188 @@ import (
) )
func TestRemoveSecret(t *testing.T) { func TestRemoveSecret(t *testing.T) {
const secretName01 = "example-secret-01"
const secretName02 = "example-secret-02"
tests := map[string]struct { tests := map[string]struct {
input string input string
args []string args []string
expectedOutput string expectedOutput string
wantErr bool
expectedErr string expectedErr string
}{ }{
"happy path": { "removes a secret successfully": {
input: fmt.Sprintf(` input: `
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
secretGenerator: secretGenerator:
- name: %s - name: test-secret-1
files: files:
- longsecret.txt - longsecret.txt
`, secretName01), `,
args: []string{secretName01}, args: []string{"test-secret-1"},
expectedOutput: ` expectedOutput: `
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
`, `,
}, },
"multiple": { "removes multiple secrets successfully": {
input: fmt.Sprintf(` input: `
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
secretGenerator: secretGenerator:
- name: %s - name: test-secret-1
files: files:
- longsecret.txt - longsecret.txt
- name: %s - name: test-secret-2
files: files:
- longsecret.txt - longsecret.txt
`, secretName01, secretName02), `,
args: []string{ args: []string{"test-secret-1,test-secret-2"},
fmt.Sprintf("%s,%s", secretName01, secretName02),
},
expectedOutput: ` expectedOutput: `
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
`, `,
}, },
"miss": { "removes a secret successfully when single secret name is specified, it exists, but is not the only secret present": {
input: fmt.Sprintf(` input: `
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
secretGenerator: secretGenerator:
- name: %s - name: test-secret-1
namespace: test-ns
files: files:
- longsecret.txt - longsecret.txt
`, secretName01), - name: test-secret-2
args: []string{"foo"}, namespace: default
expectedErr: "no specified secret(s) were found", literals:
}, - test-key=test-secret
"no secret name specified": { `,
args: []string{}, args: []string{"test-secret-1", "--namespace=test-ns"},
expectedErr: "at least one secret name must be specified", wantErr: false,
}, expectedOutput: `
"too many secret names specified": {
args: []string{"test1", "test2"},
expectedErr: "too many arguments",
},
"one existing and one non-existing": {
input: fmt.Sprintf(`
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
secretGenerator: secretGenerator:
- name: %s - literals:
- test-key=test-secret
name: test-secret-2
namespace: default
`,
},
"succeeds when one secret name exists and one doesn't exist": {
input: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
secretGenerator:
- name: test-secret-1
files: files:
- application.properties - application.properties
`, secretName01), `,
args: []string{fmt.Sprintf("%s,%s", secretName01, "foo")}, args: []string{"test-secret-1,test-secret-2"},
expectedOutput: ` expectedOutput: `
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
`, `,
}, },
"succeeds when one secret name exists in the specified namespace": {
input: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
secretGenerator:
- name: test-secret
namespace: test-ns
files:
- application.properties
`,
args: []string{"test-secret", "--namespace=test-ns"},
expectedOutput: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`,
},
"handles empty namespace as default in the args": {
input: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
secretGenerator:
- name: test-secret
namespace: default
files:
- application.properties
`,
expectedOutput: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`,
wantErr: false,
args: []string{"test-secret"},
},
"handles empty namespace as default in the kustomization file": {
input: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
secretGenerator:
- name: test-secret
files:
- application.properties
`,
expectedOutput: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`,
wantErr: false,
args: []string{"test-secret", "--namespace=default"},
},
"fails when single secret name is specified and doesn't exist": {
input: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
secretGenerator:
- name: test-secret-1
files:
- longsecret.txt
`,
args: []string{"foo"},
wantErr: true,
expectedErr: "no specified secret(s) were found",
},
"fails when single secret name is specified and doesn't exist in the specified namespace": {
input: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
secretGenerator:
- name: test-secret-1
namespace: test-ns
files:
- longsecret.txt
`,
args: []string{"test-secret-1"},
wantErr: true,
expectedErr: "no specified secret(s) were found",
},
"fails when single secret name is specified and doesn't exist in the specified namespace, and neither namespace is the default one": {
input: `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
secretGenerator:
- name: test-secret-1
namespace: test-ns
files:
- longsecret.txt
`,
args: []string{"test-secret-1", "--namespace=other-ns"},
wantErr: true,
expectedErr: "no specified secret(s) were found",
},
"fails when no secret name is specified": {
args: []string{},
wantErr: true,
expectedErr: "at least one secret name must be specified",
},
"fails when too many secret names are specified": {
args: []string{"test1", "test2"},
wantErr: true,
expectedErr: "too many arguments",
},
} }
for name, tc := range tests { for name, tc := range tests {
@@ -99,9 +201,10 @@ kind: Kustomization
fSys := filesys.MakeFsInMemory() fSys := filesys.MakeFsInMemory()
testutils_test.WriteTestKustomizationWith(fSys, []byte(tc.input)) testutils_test.WriteTestKustomizationWith(fSys, []byte(tc.input))
cmd := newCmdRemoveSecret(fSys) cmd := newCmdRemoveSecret(fSys)
err := cmd.RunE(cmd, tc.args) cmd.SetArgs(tc.args)
err := cmd.Execute()
if tc.expectedErr != "" { if tc.wantErr {
require.Error(t, err) require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedErr) require.Contains(t, err.Error(), tc.expectedErr)
return return