From a0c22b8216bde7f47aaf2ee06be941ae3cfa620a Mon Sep 17 00:00:00 2001 From: Seth Pollack Date: Sun, 20 Jan 2019 00:28:03 -0500 Subject: [PATCH] add add secret command --- pkg/commands/edit/add/all.go | 6 +- pkg/commands/edit/add/configmap.go | 22 +-- pkg/commands/edit/add/configmap_test.go | 12 +- .../{cmapflagsandargs.go => flagsandargs.go} | 10 +- ...gsandargs_test.go => flagsandargs_test.go} | 44 +++--- pkg/commands/edit/add/secret.go | 146 ++++++++++++++++++ pkg/commands/edit/add/secret_test.go | 131 ++++++++++++++++ 7 files changed, 327 insertions(+), 44 deletions(-) rename pkg/commands/edit/add/{cmapflagsandargs.go => flagsandargs.go} (86%) rename pkg/commands/edit/add/{cmapflagsandargs_test.go => flagsandargs_test.go} (66%) create mode 100644 pkg/commands/edit/add/secret.go create mode 100644 pkg/commands/edit/add/secret_test.go diff --git a/pkg/commands/edit/add/all.go b/pkg/commands/edit/add/all.go index da48ce400..25d791663 100644 --- a/pkg/commands/edit/add/all.go +++ b/pkg/commands/edit/add/all.go @@ -26,9 +26,12 @@ import ( func NewCmdAdd(fsys fs.FileSystem, v ifc.Validator, kf ifc.KunstructuredFactory) *cobra.Command { c := &cobra.Command{ Use: "add", - Short: "Adds configmap/resource/patch/base to the kustomization file.", + Short: "Adds an item to the kustomization file.", Long: "", Example: ` + # Adds a secret to the kustomization file + kustomize edit add secret NAME --from-literal=k=v + # Adds a configmap to the kustomization file kustomize edit add configmap NAME --from-literal=k=v @@ -53,6 +56,7 @@ func NewCmdAdd(fsys fs.FileSystem, v ifc.Validator, kf ifc.KunstructuredFactory) c.AddCommand( newCmdAddResource(fsys), newCmdAddPatch(fsys), + newCmdAddSecret(fsys, kf), newCmdAddConfigMap(fsys, kf), newCmdAddBase(fsys), newCmdAddLabel(fsys, v.MakeLabelValidator()), diff --git a/pkg/commands/edit/add/configmap.go b/pkg/commands/edit/add/configmap.go index 013967fd4..7eee21faf 100644 --- a/pkg/commands/edit/add/configmap.go +++ b/pkg/commands/edit/add/configmap.go @@ -29,7 +29,7 @@ import ( // newCmdAddConfigMap returns a new command. func newCmdAddConfigMap(fSys fs.FileSystem, kf ifc.KunstructuredFactory) *cobra.Command { - var flagsAndArgs cMapFlagsAndArgs + var flags flagsAndArgs cmd := &cobra.Command{ Use: "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1]", Short: "Adds a configmap to the kustomization file.", @@ -45,12 +45,12 @@ func newCmdAddConfigMap(fSys fs.FileSystem, kf ifc.KunstructuredFactory) *cobra. kustomize edit add configmap my-configmap --from-env-file=env/path.env `, RunE: func(_ *cobra.Command, args []string) error { - err := flagsAndArgs.ExpandFileSource(fSys) + err := flags.ExpandFileSource(fSys) if err != nil { return err } - err = flagsAndArgs.Validate(args) + err = flags.Validate(args) if err != nil { return err } @@ -68,7 +68,7 @@ func newCmdAddConfigMap(fSys fs.FileSystem, kf ifc.KunstructuredFactory) *cobra. // Add the flagsAndArgs map to the kustomization file. kf.Set(loader.NewFileLoaderAtCwd(fSys)) - err = addConfigMap(kustomization, flagsAndArgs, kf) + err = addConfigMap(kustomization, flags, kf) if err != nil { return err } @@ -79,19 +79,19 @@ func newCmdAddConfigMap(fSys fs.FileSystem, kf ifc.KunstructuredFactory) *cobra. } cmd.Flags().StringSliceVar( - &flagsAndArgs.FileSources, + &flags.FileSources, "from-file", []string{}, "Key file can be specified using its file path, in which case file basename will be used as configmap "+ "key, or optionally with a key and file path, in which case the given key will be used. Specifying a "+ "directory will iterate each named file in the directory whose basename is a valid configmap key.") cmd.Flags().StringArrayVar( - &flagsAndArgs.LiteralSources, + &flags.LiteralSources, "from-literal", []string{}, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)") cmd.Flags().StringVar( - &flagsAndArgs.EnvFileSource, + &flags.EnvFileSource, "from-env-file", "", "Specify the path to a file to read lines of key=val pairs to create a configmap (i.e. a Docker .env file).") @@ -104,9 +104,9 @@ func newCmdAddConfigMap(fSys fs.FileSystem, kf ifc.KunstructuredFactory) *cobra. // Suggest passing a copy of kustomization file. func addConfigMap( k *types.Kustomization, - flagsAndArgs cMapFlagsAndArgs, kf ifc.KunstructuredFactory) error { - cmArgs := makeConfigMapArgs(k, flagsAndArgs.Name) - err := mergeFlagsIntoCmArgs(&cmArgs.DataSources, flagsAndArgs) + flags flagsAndArgs, kf ifc.KunstructuredFactory) error { + cmArgs := makeConfigMapArgs(k, flags.Name) + err := mergeFlagsIntoCmArgs(&cmArgs.DataSources, flags) if err != nil { return err } @@ -130,7 +130,7 @@ func makeConfigMapArgs(m *types.Kustomization, name string) *types.ConfigMapArgs return &m.ConfigMapGenerator[len(m.ConfigMapGenerator)-1] } -func mergeFlagsIntoCmArgs(src *types.DataSources, flags cMapFlagsAndArgs) error { +func mergeFlagsIntoCmArgs(src *types.DataSources, flags flagsAndArgs) error { src.LiteralSources = append(src.LiteralSources, flags.LiteralSources...) src.FileSources = append(src.FileSources, flags.FileSources...) if src.EnvSource != "" && src.EnvSource != flags.EnvFileSource { diff --git a/pkg/commands/edit/add/configmap_test.go b/pkg/commands/edit/add/configmap_test.go index 93c280d47..f55d1198a 100644 --- a/pkg/commands/edit/add/configmap_test.go +++ b/pkg/commands/edit/add/configmap_test.go @@ -67,7 +67,7 @@ func TestMakeConfigMapArgs(t *testing.T) { func TestMergeFlagsIntoCmArgs_LiteralSources(t *testing.T) { ds := &types.DataSources{} - err := mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{LiteralSources: []string{"k1=v1"}}) + err := mergeFlagsIntoCmArgs(ds, flagsAndArgs{LiteralSources: []string{"k1=v1"}}) if err != nil { t.Fatalf("Merge initial literal source should not return error") } @@ -76,7 +76,7 @@ func TestMergeFlagsIntoCmArgs_LiteralSources(t *testing.T) { t.Fatalf("Initial literal source should have been added") } - err = mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{LiteralSources: []string{"k2=v2"}}) + err = mergeFlagsIntoCmArgs(ds, flagsAndArgs{LiteralSources: []string{"k2=v2"}}) if err != nil { t.Fatalf("Merge second literal source should not return error") } @@ -89,7 +89,7 @@ func TestMergeFlagsIntoCmArgs_LiteralSources(t *testing.T) { func TestMergeFlagsIntoCmArgs_FileSources(t *testing.T) { ds := &types.DataSources{} - err := mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{FileSources: []string{"file1"}}) + err := mergeFlagsIntoCmArgs(ds, flagsAndArgs{FileSources: []string{"file1"}}) if err != nil { t.Fatalf("Merge initial file source should not return error") } @@ -98,7 +98,7 @@ func TestMergeFlagsIntoCmArgs_FileSources(t *testing.T) { t.Fatalf("Initial file source should have been added") } - err = mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{FileSources: []string{"file2"}}) + err = mergeFlagsIntoCmArgs(ds, flagsAndArgs{FileSources: []string{"file2"}}) if err != nil { t.Fatalf("Merge second file source should not return error") } @@ -113,7 +113,7 @@ func TestMergeFlagsIntoCmArgs_EnvSource(t *testing.T) { envFileName2 := "env2" ds := &types.DataSources{} - err := mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{EnvFileSource: envFileName}) + err := mergeFlagsIntoCmArgs(ds, flagsAndArgs{EnvFileSource: envFileName}) if err != nil { t.Fatalf("Merge initial env source should not return error") } @@ -122,7 +122,7 @@ func TestMergeFlagsIntoCmArgs_EnvSource(t *testing.T) { t.Fatalf("Initial env source filename should have been added") } - err = mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{EnvFileSource: envFileName2}) + err = mergeFlagsIntoCmArgs(ds, flagsAndArgs{EnvFileSource: envFileName2}) if err == nil { t.Fatalf("Updating env source should return an error") } diff --git a/pkg/commands/edit/add/cmapflagsandargs.go b/pkg/commands/edit/add/flagsandargs.go similarity index 86% rename from pkg/commands/edit/add/cmapflagsandargs.go rename to pkg/commands/edit/add/flagsandargs.go index 6575d2bde..77bcc6b61 100644 --- a/pkg/commands/edit/add/cmapflagsandargs.go +++ b/pkg/commands/edit/add/flagsandargs.go @@ -22,8 +22,8 @@ import ( "sigs.k8s.io/kustomize/pkg/fs" ) -// cMapFlagsAndArgs encapsulates the options for add configmap commands. -type cMapFlagsAndArgs struct { +// flagsAndArgs encapsulates the options for add secret/configmap commands. +type flagsAndArgs struct { // Name of configMap/Secret (required) Name string // FileSources to derive the configMap/Secret from (optional) @@ -33,10 +33,12 @@ type cMapFlagsAndArgs struct { // EnvFileSource to derive the configMap/Secret from (optional) // TODO: Rationalize this name with Generic.EnvSource EnvFileSource string + // Type of secret to create + Type string } // Validate validates required fields are set to support structured generation. -func (a *cMapFlagsAndArgs) Validate(args []string) error { +func (a *flagsAndArgs) Validate(args []string) error { if len(args) != 1 { return fmt.Errorf("name must be specified once") } @@ -51,7 +53,7 @@ func (a *cMapFlagsAndArgs) Validate(args []string) error { return nil } -func (a *cMapFlagsAndArgs) ExpandFileSource(fSys fs.FileSystem) error { +func (a *flagsAndArgs) ExpandFileSource(fSys fs.FileSystem) error { result, err := globPatterns(fSys, a.FileSources) if err != nil { return err diff --git a/pkg/commands/edit/add/cmapflagsandargs_test.go b/pkg/commands/edit/add/flagsandargs_test.go similarity index 66% rename from pkg/commands/edit/add/cmapflagsandargs_test.go rename to pkg/commands/edit/add/flagsandargs_test.go index ce5ed57ee..5638fe71f 100644 --- a/pkg/commands/edit/add/cmapflagsandargs_test.go +++ b/pkg/commands/edit/add/flagsandargs_test.go @@ -23,18 +23,18 @@ import ( "sigs.k8s.io/kustomize/pkg/fs" ) -func TestDataConfigValidation_NoName(t *testing.T) { - config := cMapFlagsAndArgs{} +func TestDataValidation_NoName(t *testing.T) { + fa := flagsAndArgs{} - if config.Validate([]string{}) == nil { + if fa.Validate([]string{}) == nil { t.Fatal("Validation should fail if no name is specified") } } -func TestDataConfigValidation_MoreThanOneName(t *testing.T) { - config := cMapFlagsAndArgs{} +func TestDataValidation_MoreThanOneName(t *testing.T) { + fa := flagsAndArgs{} - if config.Validate([]string{"name", "othername"}) == nil { + if fa.Validate([]string{"name", "othername"}) == nil { t.Fatal("Validation should fail if more than one name is specified") } } @@ -42,12 +42,12 @@ func TestDataConfigValidation_MoreThanOneName(t *testing.T) { func TestDataConfigValidation_Flags(t *testing.T) { tests := []struct { name string - config cMapFlagsAndArgs + fa flagsAndArgs shouldFail bool }{ { name: "env-file-source and literal are both set", - config: cMapFlagsAndArgs{ + fa: flagsAndArgs{ LiteralSources: []string{"one", "two"}, EnvFileSource: "three", }, @@ -55,7 +55,7 @@ func TestDataConfigValidation_Flags(t *testing.T) { }, { name: "env-file-source and from-file are both set", - config: cMapFlagsAndArgs{ + fa: flagsAndArgs{ FileSources: []string{"one", "two"}, EnvFileSource: "three", }, @@ -63,12 +63,12 @@ func TestDataConfigValidation_Flags(t *testing.T) { }, { name: "we don't have any option set", - config: cMapFlagsAndArgs{}, + fa: flagsAndArgs{}, shouldFail: true, }, { name: "we have from-file and literal ", - config: cMapFlagsAndArgs{ + fa: flagsAndArgs{ LiteralSources: []string{"one", "two"}, FileSources: []string{"three", "four"}, }, @@ -77,9 +77,9 @@ func TestDataConfigValidation_Flags(t *testing.T) { } for _, test := range tests { - if test.config.Validate([]string{"name"}) == nil && test.shouldFail { + if test.fa.Validate([]string{"name"}) == nil && test.shouldFail { t.Fatalf("Validation should fail if %s", test.name) - } else if test.config.Validate([]string{"name"}) != nil && !test.shouldFail { + } else if test.fa.Validate([]string{"name"}) != nil && !test.shouldFail { t.Fatalf("Validation should succeed if %s", test.name) } } @@ -87,18 +87,18 @@ func TestDataConfigValidation_Flags(t *testing.T) { func TestExpandFileSource(t *testing.T) { fakeFS := fs.MakeFakeFS() - fakeFS.Create("dir/config1") - fakeFS.Create("dir/config2") + fakeFS.Create("dir/fa1") + fakeFS.Create("dir/fa2") fakeFS.Create("dir/reademe") - config := cMapFlagsAndArgs{ - FileSources: []string{"dir/config*"}, + fa := flagsAndArgs{ + FileSources: []string{"dir/fa*"}, } - config.ExpandFileSource(fakeFS) + fa.ExpandFileSource(fakeFS) expected := []string{ - "dir/config1", - "dir/config2", + "dir/fa1", + "dir/fa2", } - if !reflect.DeepEqual(config.FileSources, expected) { - t.Fatalf("FileSources is not correctly expanded: %v", config.FileSources) + if !reflect.DeepEqual(fa.FileSources, expected) { + t.Fatalf("FileSources is not correctly expanded: %v", fa.FileSources) } } diff --git a/pkg/commands/edit/add/secret.go b/pkg/commands/edit/add/secret.go new file mode 100644 index 000000000..53f93dbd0 --- /dev/null +++ b/pkg/commands/edit/add/secret.go @@ -0,0 +1,146 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package add + +import ( + "fmt" + + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/pkg/commands/kustfile" + "sigs.k8s.io/kustomize/pkg/fs" + "sigs.k8s.io/kustomize/pkg/ifc" + "sigs.k8s.io/kustomize/pkg/loader" + "sigs.k8s.io/kustomize/pkg/types" +) + +// newCmdAddSecret returns a new command. +func newCmdAddSecret(fSys fs.FileSystem, kf ifc.KunstructuredFactory) *cobra.Command { + var flags flagsAndArgs + cmd := &cobra.Command{ + Use: "secret NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--type=Opaque|kubernetes.io/tls]", + Short: "Adds a secret to the kustomization file.", + Long: "", + Example: ` + # Adds a secret to the kustomization file (with a specified key) + kustomize edit add secret my-secret --from-file=my-key=file/path --from-literal=my-literal=12345 + + # Adds a secret to the kustomization file (key is the filename) + kustomize edit add secret my-secret --from-file=file/path + + # Adds a secret from env-file + kustomize edit add secret my-secret --from-env-file=env/path.env +`, + RunE: func(_ *cobra.Command, args []string) error { + err := flags.ExpandFileSource(fSys) + if err != nil { + return err + } + + err = flags.Validate(args) + if err != nil { + return err + } + + // Load the kustomization file. + mf, err := kustfile.NewKustomizationFile(fSys) + if err != nil { + return err + } + + kustomization, err := mf.Read() + if err != nil { + return err + } + + // Add the flagsAndArgs map to the kustomization file. + kf.Set(loader.NewFileLoaderAtCwd(fSys)) + err = addSecret(kustomization, flags, kf) + if err != nil { + return err + } + + // Write out the kustomization file with added secret. + return mf.Write(kustomization) + }, + } + + cmd.Flags().StringSliceVar( + &flags.FileSources, + "from-file", + []string{}, + "Key file can be specified using its file path, in which case file basename will be used as secret "+ + "key, or optionally with a key and file path, in which case the given key will be used. Specifying a "+ + "directory will iterate each named file in the directory whose basename is a valid secret key.") + cmd.Flags().StringArrayVar( + &flags.LiteralSources, + "from-literal", + []string{}, + "Specify a key and literal value to insert in secret (i.e. mykey=somevalue)") + cmd.Flags().StringVar( + &flags.EnvFileSource, + "from-env-file", + "", + "Specify the path to a file to read lines of key=val pairs to create a secret (i.e. a Docker .env file).") + cmd.Flags().StringVar( + &flags.Type, + "type", + "Opaque", + "Specify the secret type this can be 'Opaque' (default), or 'kubernetes.io/tls'") + + return cmd +} + +// addSecret adds a secret to a kustomization file. +// Note: error may leave kustomization file in an undefined state. +// Suggest passing a copy of kustomization file. +func addSecret( + k *types.Kustomization, + flags flagsAndArgs, kf ifc.KunstructuredFactory) error { + secretArgs := makeSecretArgs(k, flags.Name, flags.Type) + err := mergeFlagsIntoSecretArgs(&secretArgs.DataSources, flags) + if err != nil { + return err + } + // Validate by trying to create corev1.secret. + _, err = kf.MakeSecret(secretArgs, k.GeneratorOptions) + if err != nil { + return err + } + return nil +} + +func makeSecretArgs(m *types.Kustomization, name, secretType string) *types.SecretArgs { + for i, v := range m.SecretGenerator { + if name == v.Name { + return &m.SecretGenerator[i] + } + } + // secret not found, create new one and add it to the kustomization file. + secret := &types.SecretArgs{GeneratorArgs: types.GeneratorArgs{Name: name}, Type: secretType} + m.SecretGenerator = append(m.SecretGenerator, *secret) + return &m.SecretGenerator[len(m.SecretGenerator)-1] +} + +func mergeFlagsIntoSecretArgs(src *types.DataSources, flags flagsAndArgs) error { + src.LiteralSources = append(src.LiteralSources, flags.LiteralSources...) + src.FileSources = append(src.FileSources, flags.FileSources...) + if src.EnvSource != "" && src.EnvSource != flags.EnvFileSource { + return fmt.Errorf("updating existing env source '%s' not allowed", src.EnvSource) + } + src.EnvSource = flags.EnvFileSource + return nil +} diff --git a/pkg/commands/edit/add/secret_test.go b/pkg/commands/edit/add/secret_test.go new file mode 100644 index 000000000..47ba26489 --- /dev/null +++ b/pkg/commands/edit/add/secret_test.go @@ -0,0 +1,131 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package add + +import ( + "testing" + + "sigs.k8s.io/kustomize/pkg/fs" + "sigs.k8s.io/kustomize/pkg/types" +) + +func TestNewCmdAddSecretIsNotNil(t *testing.T) { + if newCmdAddSecret(fs.MakeFakeFS(), nil) == nil { + t.Fatal("newCmdAddSecret shouldn't be nil") + } +} + +func TestMakeSecretArgs(t *testing.T) { + secretName := "test-secret-name" + + kustomization := &types.Kustomization{ + NamePrefix: "test-name-prefix", + } + + secretType := "Opaque" + + if len(kustomization.SecretGenerator) != 0 { + t.Fatal("Initial kustomization should not have any secrets") + } + args := makeSecretArgs(kustomization, secretName, secretType) + + if args == nil { + t.Fatalf("args should always be non-nil") + } + + if len(kustomization.SecretGenerator) != 1 { + t.Fatalf("Kustomization should have newly created secret") + } + + if &kustomization.SecretGenerator[len(kustomization.SecretGenerator)-1] != args { + t.Fatalf("Pointer address for newly inserted secret generator should be same") + } + + args2 := makeSecretArgs(kustomization, secretName, secretType) + + if args2 != args { + t.Fatalf("should have returned an existing args with name: %v", secretName) + } + + if len(kustomization.SecretGenerator) != 1 { + t.Fatalf("Should not insert secret for an existing name: %v", secretName) + } +} + +func TestMergeFlagsIntoSecretArgs_LiteralSources(t *testing.T) { + ds := &types.DataSources{} + + err := mergeFlagsIntoSecretArgs(ds, flagsAndArgs{LiteralSources: []string{"k1=v1"}}) + if err != nil { + t.Fatalf("Merge initial literal source should not return error") + } + + if len(ds.LiteralSources) != 1 { + t.Fatalf("Initial literal source should have been added") + } + + err = mergeFlagsIntoSecretArgs(ds, flagsAndArgs{LiteralSources: []string{"k2=v2"}}) + if err != nil { + t.Fatalf("Merge second literal source should not return error") + } + + if len(ds.LiteralSources) != 2 { + t.Fatalf("Second literal source should have been added") + } +} + +func TestMergeFlagsIntoSecretArgs_FileSources(t *testing.T) { + ds := &types.DataSources{} + + err := mergeFlagsIntoSecretArgs(ds, flagsAndArgs{FileSources: []string{"file1"}}) + if err != nil { + t.Fatalf("Merge initial file source should not return error") + } + + if len(ds.FileSources) != 1 { + t.Fatalf("Initial file source should have been added") + } + + err = mergeFlagsIntoSecretArgs(ds, flagsAndArgs{FileSources: []string{"file2"}}) + if err != nil { + t.Fatalf("Merge second file source should not return error") + } + + if len(ds.FileSources) != 2 { + t.Fatalf("Second file source should have been added") + } +} + +func TestMergeFlagsIntoSecretArgs_EnvSource(t *testing.T) { + envFileName := "env1" + envFileName2 := "env2" + ds := &types.DataSources{} + + err := mergeFlagsIntoSecretArgs(ds, flagsAndArgs{EnvFileSource: envFileName}) + if err != nil { + t.Fatalf("Merge initial env source should not return error") + } + + if ds.EnvSource != envFileName { + t.Fatalf("Initial env source filename should have been added") + } + + err = mergeFlagsIntoSecretArgs(ds, flagsAndArgs{EnvFileSource: envFileName2}) + if err == nil { + t.Fatalf("Updating env source should return an error") + } +}