diff --git a/docs/kustomization.yaml b/docs/kustomization.yaml index 74cd29794..7cc821c7b 100644 --- a/docs/kustomization.yaml +++ b/docs/kustomization.yaml @@ -85,35 +85,23 @@ configMapGenerator: # Each entry in this list results in the creation of # one Secret resource (it's a generator of n secrets). -# A command can do anything to get a secret, -# e.g. prompt the user directly, start a webserver to -# initate an oauth dance, etc. secretGenerator: - name: app-tls - commands: - tls.crt: "cat secret/tls.cert" - tls.key: "cat secret/tls.key" + files: + - secret/tls.cert + - secret/tls.key type: "kubernetes.io/tls" - name: app-tls-namespaced # you can define a namespace to generate secret in, defaults to: "default" namespace: apps - commands: - tls.crt: "cat secret/tls.cert" - tls.key: "cat secret/tls.key" + files: + - tls.crt=catsecret/tls.cert + - tls.key=secret/tls.key type: "kubernetes.io/tls" -- name: downloaded_secret - # timeoutSeconds specifies the number of seconds to - # wait for the commands below. It defaults to 5 seconds. - timeoutSeconds: 30 - commands: - username: "curl -s https://path/to/secrets/username.yaml" - password: "curl -s https://path/to/secrets/password.yaml" - type: Opaque - name: env_file_secret - # envCommand is similar to command but outputs lines of key=val pairs - # i.e. a Docker .env file or a .ini file. - # you can only specify one envCommand per secret. - envCommand: printf \"DB_USERNAME=admin\nDB_PASSWORD=somepw\" + # env is a path to a file to read lines of key=val + # you can only specify one env file per secret. + env: env.txt type: Opaque # generatorOptions modify behavior of all ConfigMap and Secret generators @@ -124,11 +112,6 @@ generatorOptions: # annotations to add to all generated resources annotations: kustomize.generated.resource: somevalue - # timeoutSeconds specifies the timeout for commands - timeoutSeconds: 30 - # shell and arguments to use as a context for commands used in resource - # generation. Default at time of writing: ["sh", "-c"] - shell: ["sh", "-c"] # disableNameSuffixHash is true disables the default behavior of adding a # suffix to the names of generated resources that is a hash of # the resource contents. diff --git a/examples/combineConfigs.md b/examples/combineConfigs.md index 86e46d34d..99090f7e8 100644 --- a/examples/combineConfigs.md +++ b/examples/combineConfigs.md @@ -92,9 +92,9 @@ secret holding them (not covering that here). diff --git a/examples/generatorOptions.md b/examples/generatorOptions.md index a9fc3c3c0..4fff9ac26 100644 --- a/examples/generatorOptions.md +++ b/examples/generatorOptions.md @@ -5,8 +5,6 @@ Kustomize provides options to modify the behavior of ConfigMap and Secret genera - disable appending a content hash suffix to the names of generated resources - adding labels to generated resources - adding annotations to generated resources - - changing shell and arguments for getting data from commands - - changing timeout for executing commands This demo shows how to use these options. First create a workspace. ``` diff --git a/k8sdeps/configmapandsecret/secretfactory.go b/k8sdeps/configmapandsecret/secretfactory.go index fd1766fcb..ab6c67d1c 100644 --- a/k8sdeps/configmapandsecret/secretfactory.go +++ b/k8sdeps/configmapandsecret/secretfactory.go @@ -17,34 +17,26 @@ limitations under the License. package configmapandsecret import ( - "context" "fmt" - "log" - "os/exec" - "path/filepath" "strings" - "time" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/validation" "sigs.k8s.io/kustomize/pkg/fs" + "sigs.k8s.io/kustomize/pkg/ifc" "sigs.k8s.io/kustomize/pkg/types" ) -const ( - defaultCommandTimeout = 5 * time.Second -) - // SecretFactory makes Secrets. type SecretFactory struct { fSys fs.FileSystem - wd string + ldr ifc.Loader } // NewSecretFactory returns a new SecretFactory. -func NewSecretFactory(fSys fs.FileSystem, wd string) *SecretFactory { - return &SecretFactory{fSys: fSys, wd: wd} +func NewSecretFactory(fSys fs.FileSystem, ldr ifc.Loader) *SecretFactory { + return &SecretFactory{fSys: fSys, ldr: ldr} } func (f *SecretFactory) makeFreshSecret(args *types.SecretArgs) *corev1.Secret { @@ -67,28 +59,28 @@ func (f *SecretFactory) MakeSecret(args *types.SecretArgs, options *types.Genera var err error s := f.makeFreshSecret(args) - timeout := defaultCommandTimeout - if args.TimeoutSeconds != nil { - log.Println("SecretArgs.TimeoutSeconds will be deprected in next release. Please use GeneratorOptions.TimeoutSeconds instread.") - timeout = time.Duration(*args.TimeoutSeconds) * time.Second + pairs, err := keyValuesFromEnvFile(f.ldr, args.EnvSource) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf( + "env source file: %s", + args.EnvSource)) } - if args.EnvCommand != "" { - pairs, err := f.keyValuesFromEnvFileCommand(args.EnvCommand, timeout, options) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf( - "env source file: %s", - args.EnvCommand)) - } - all = append(all, pairs...) + all = append(all, pairs...) + + pairs, err = keyValuesFromLiteralSources(args.LiteralSources) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf( + "literal sources %v", args.LiteralSources)) } - if len(args.Commands) != 0 { - pairs, err := f.keyValuesFromCommands(args.Commands, timeout, options) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf( - "commands %v", args.Commands)) - } - all = append(all, pairs...) + all = append(all, pairs...) + + pairs, err = keyValuesFromFileSources(f.ldr, args.FileSources) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf( + "file sources: %v", args.FileSources)) } + all = append(all, pairs...) + for _, kv := range all { err = addKvToSecret(s, kv.key, kv.value) if err != nil { @@ -113,52 +105,3 @@ func addKvToSecret(secret *corev1.Secret, keyName, data string) error { secret.Data[keyName] = []byte(data) return nil } - -func (f *SecretFactory) keyValuesFromEnvFileCommand(cmd string, timeout time.Duration, options *types.GeneratorOptions) ([]kvPair, error) { - content, err := f.createSecretKey(cmd, timeout, options) - if err != nil { - return nil, err - } - return keyValuesFromLines(content) -} - -func (f *SecretFactory) keyValuesFromCommands(sources map[string]string, timeout time.Duration, options *types.GeneratorOptions) ([]kvPair, error) { - var kvs []kvPair - for k, cmd := range sources { - content, err := f.createSecretKey(cmd, timeout, options) - if err != nil { - return nil, err - } - kvs = append(kvs, kvPair{key: k, value: string(content)}) - } - return kvs, nil -} - -// Run a command, return its output as the secret. -func (f *SecretFactory) createSecretKey(command string, timeout time.Duration, options *types.GeneratorOptions) ([]byte, error) { - if !f.fSys.IsDir(f.wd) { - f.wd = filepath.Dir(f.wd) - if !f.fSys.IsDir(f.wd) { - return nil, errors.New("not a directory: " + f.wd) - } - } - - if options != nil && options.TimeoutSeconds != nil { - t := time.Duration(*options.TimeoutSeconds) * time.Second - if t > timeout { - timeout = t - } - } - - var commands []string - if options == nil || len(options.Shell) == 0 { - commands = []string{"sh", "-c", command} - } else { - commands = append(options.Shell, command) - } - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - cmd := exec.CommandContext(ctx, commands[0], commands[1:]...) - cmd.Dir = f.wd - return cmd.Output() -} diff --git a/k8sdeps/configmapandsecret/secretfactory_test.go b/k8sdeps/configmapandsecret/secretfactory_test.go index a2c50870a..c299a960d 100644 --- a/k8sdeps/configmapandsecret/secretfactory_test.go +++ b/k8sdeps/configmapandsecret/secretfactory_test.go @@ -17,94 +17,129 @@ limitations under the License. package configmapandsecret import ( + "reflect" "testing" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/kustomize/pkg/fs" + "sigs.k8s.io/kustomize/pkg/loader" "sigs.k8s.io/kustomize/pkg/types" ) -func TestMakeSecretNoCommands(t *testing.T) { - factory := NewSecretFactory(fs.MakeFakeFS(), "/") - args := types.SecretArgs{ - GeneratorArgs: types.GeneratorArgs{Name: "apple"}, - Type: "Opaque", - CommandSources: types.CommandSources{ - Commands: nil, - EnvCommand: "", - }} - s, err := factory.MakeSecret(&args, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if s.ObjectMeta.Name != "apple" { - t.Fatalf("unexpected name: %v", s.ObjectMeta.Name) - } - if len(s.Data) > 0 || len(s.StringData) > 0 { - t.Fatalf("unexpected data: %v", s) +func makeEnvSecret(name string) *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Data: map[string][]byte{ + "DB_PASSWORD": []byte("somepw"), + "DB_USERNAME": []byte("admin"), + }, + Type: "Opaque", } } -func TestMakeSecretNoCommandsBadDir(t *testing.T) { - factory := NewSecretFactory(fs.MakeFakeFS(), "/does/not/exist") - args := types.SecretArgs{ - GeneratorArgs: types.GeneratorArgs{Name: "envConfigMap"}, - Type: "Opaque", - CommandSources: types.CommandSources{ - Commands: nil, - EnvCommand: "", - }} - _, err := factory.MakeSecret(&args, nil) - if err != nil { - t.Fatalf("Unexpected error: %v", err) +func makeFileSecret(name string) *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Data: map[string][]byte{ + "app-init.ini": []byte(`FOO=bar +BAR=baz +`), + }, + Type: "Opaque", } } -func TestMakeSecretEmptyCommandMap(t *testing.T) { - factory := NewSecretFactory(fs.MakeFakeFS(), "/") - args := types.SecretArgs{ - GeneratorArgs: types.GeneratorArgs{Name: "envConfigMap"}, - Type: "Opaque", - CommandSources: types.CommandSources{ - // TODO try: map[string]string{"commandName": "bogusCommand bogusArg"}, - Commands: nil, - EnvCommand: "echo beans", - }} - s, err := factory.MakeSecret(&args, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if s == nil { - t.Fatalf("nil result") - } - v, ok := s.Data["beans"] - if !ok { - t.Fatalf("expected beans") - } - if len(v) > 0 { - t.Fatalf("unexpected data") +func makeLiteralSecret(name string) *corev1.Secret { + s := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Data: map[string][]byte{ + "a": []byte("x"), + "b": []byte("y"), + }, + Type: "Opaque", } + s.SetLabels(map[string]string{"foo": "bar"}) + return s } -func TestMakeSecretWithCommandMap(t *testing.T) { - factory := NewSecretFactory(fs.MakeFakeFS(), "/") - args := types.SecretArgs{ - GeneratorArgs: types.GeneratorArgs{Name: "envConfigMap"}, - Type: "Opaque", - CommandSources: types.CommandSources{ - Commands: map[string]string{"commandName": "echo beans"}, - }} - s, err := factory.MakeSecret(&args, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) +func TestConstructSecret(t *testing.T) { + type testCase struct { + description string + input types.SecretArgs + options *types.GeneratorOptions + expected *corev1.Secret } - if s == nil { - t.Fatalf("nil result") + + testCases := []testCase{ + { + description: "construct secret from env", + input: types.SecretArgs{ + GeneratorArgs: types.GeneratorArgs{Name: "envSecret"}, + DataSources: types.DataSources{ + EnvSource: "secret/app.env", + }, + }, + options: nil, + expected: makeEnvSecret("envSecret"), + }, + { + description: "construct secret from file", + input: types.SecretArgs{ + GeneratorArgs: types.GeneratorArgs{Name: "fileSecret"}, + DataSources: types.DataSources{ + FileSources: []string{"secret/app-init.ini"}, + }, + }, + options: nil, + expected: makeFileSecret("fileSecret"), + }, + { + description: "construct secret from literal", + input: types.SecretArgs{ + GeneratorArgs: types.GeneratorArgs{Name: "literalSecret"}, + DataSources: types.DataSources{ + LiteralSources: []string{"a=x", "b=y"}, + }, + }, + options: &types.GeneratorOptions{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + expected: makeLiteralSecret("literalSecret"), + }, } - v, ok := s.Data["commandName"] - if !ok { - t.Fatalf("expected something for commandName") - } - if string(v) != "beans\n" { - t.Fatalf("unexpected data: %s", string(v)) + + fSys := fs.MakeFakeFS() + fSys.WriteFile("/secret/app.env", []byte("DB_USERNAME=admin\nDB_PASSWORD=somepw\n")) + fSys.WriteFile("/secret/app-init.ini", []byte("FOO=bar\nBAR=baz\n")) + f := NewSecretFactory(fSys, loader.NewFileLoaderAtRoot(fSys)) + for _, tc := range testCases { + cm, err := f.MakeSecret(&tc.input, tc.options) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(*cm, *tc.expected) { + t.Fatalf("in testcase: %q updated:\n%#v\ndoesn't match expected:\n%#v\n", tc.description, *cm, tc.expected) + } } } diff --git a/k8sdeps/kunstruct/factory.go b/k8sdeps/kunstruct/factory.go index 386789130..432bf4ee5 100644 --- a/k8sdeps/kunstruct/factory.go +++ b/k8sdeps/kunstruct/factory.go @@ -97,7 +97,7 @@ func (kf *KunstructuredFactoryImpl) MakeSecret(args *types.SecretArgs, options * // Set sets loader, filesystem and workdirectory func (kf *KunstructuredFactoryImpl) Set(fs fs.FileSystem, ldr ifc.Loader) { kf.cmFactory = configmapandsecret.NewConfigMapFactory(fs, ldr) - kf.secretFactory = configmapandsecret.NewSecretFactory(fs, ldr.Root()) + kf.secretFactory = configmapandsecret.NewSecretFactory(fs, ldr) } // validate validates that u has kind and name diff --git a/pkg/resmap/factory_test.go b/pkg/resmap/factory_test.go index 5d41548eb..16daccfc4 100644 --- a/pkg/resmap/factory_test.go +++ b/pkg/resmap/factory_test.go @@ -252,21 +252,14 @@ func TestNewResMapFromSecretArgs(t *testing.T) { secrets := []types.SecretArgs{ { GeneratorArgs: types.GeneratorArgs{Name: "apple"}, - CommandSources: types.CommandSources{ - Commands: map[string]string{ - "DB_USERNAME": "printf admin", - "DB_PASSWORD": "printf somepw", + DataSources: types.DataSources{ + LiteralSources: []string{ + "DB_USERNAME=admin", + "DB_PASSWORD=somepw", }, }, Type: ifc.SecretTypeOpaque, }, - { - GeneratorArgs: types.GeneratorArgs{Name: "peanuts"}, - CommandSources: types.CommandSources{ - EnvCommand: "printf \"DB_USERNAME=admin\nDB_PASSWORD=somepw\"", - }, - Type: ifc.SecretTypeOpaque, - }, } fakeFs := fs.MakeFakeFS() fakeFs.Mkdir(".") @@ -291,45 +284,8 @@ func TestNewResMapFromSecretArgs(t *testing.T) { "DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")), }, }).SetBehavior(ifc.BehaviorCreate), - resid.NewResId(secret, "peanuts"): rf.FromMap( - map[string]interface{}{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": map[string]interface{}{ - "name": "peanuts", - }, - "type": ifc.SecretTypeOpaque, - "data": map[string]interface{}{ - "DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")), - "DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")), - }, - }).SetBehavior(ifc.BehaviorCreate), } if !reflect.DeepEqual(actual, expected) { t.Fatalf("%#v\ndoesn't match expected:\n%#v", actual, expected) } } - -func TestSecretTimeout(t *testing.T) { - timeout := int64(1) - secrets := []types.SecretArgs{ - { - GeneratorArgs: types.GeneratorArgs{Name: "slow"}, - TimeoutSeconds: &timeout, - CommandSources: types.CommandSources{ - Commands: map[string]string{ - "USER": "sleep 2", - }, - }, - Type: ifc.SecretTypeOpaque, - }, - } - fakeFs := fs.MakeFakeFS() - fakeFs.Mkdir(".") - rmF.Set(fakeFs, loader.NewFileLoaderAtRoot(fakeFs)) - _, err := rmF.NewResMapFromSecretArgs(secrets, nil) - - if err == nil { - t.Fatal("didn't get the expected timeout error", err) - } -} diff --git a/pkg/target/generatormergeandreplace_test.go b/pkg/target/generatormergeandreplace_test.go index 365b41006..a08ad37dc 100644 --- a/pkg/target/generatormergeandreplace_test.go +++ b/pkg/target/generatormergeandreplace_test.go @@ -181,9 +181,9 @@ configMapGenerator: - foo=bar secretGenerator: - name: secret-in-base - commands: - username: "printf admin" - password: "printf somepw" + literals: + - username=admin + - password=somepw `) th.writeF("/app/deployment.yaml", ` apiVersion: apps/v1beta2 @@ -362,8 +362,8 @@ configMapGenerator: secretGenerator: - name: secret-in-base behavior: merge - commands: - proxy: "printf haproxy" + literals: + - proxy=haproxy `) m, err := th.makeKustTarget().MakeCustomizedResMap() if err != nil { diff --git a/pkg/target/kusttarget_test.go b/pkg/target/kusttarget_test.go index b23dafef1..7d720c666 100644 --- a/pkg/target/kusttarget_test.go +++ b/pkg/target/kusttarget_test.go @@ -52,9 +52,9 @@ configMapGenerator: - DB_PASSWORD=somepw secretGenerator: - name: secret - commands: - DB_USERNAME: "printf admin" - DB_PASSWORD: "printf somepw" + literals: + - DB_USERNAME=admin + - DB_PASSWORD=somepw type: Opaque patchesJson6902: - target: @@ -63,16 +63,6 @@ patchesJson6902: kind: Deployment name: dply1 path: jsonpatch.json -` - kustomizationContent2 = ` -apiVersion: v1beta1 -kind: Kustomization -secretGenerator: -- name: secret - timeoutSeconds: 1 - commands: - USER: "sleep 2" - type: Opaque ` deploymentContent = ` apiVersion: apps/v1 @@ -217,18 +207,6 @@ func TestResourceNotFound(t *testing.T) { } } -func TestSecretTimeout(t *testing.T) { - th := NewKustTestHarness(t, "/whatever") - th.writeK("/whatever", kustomizationContent2) - _, err := th.makeKustTarget().MakeCustomizedResMap() - if err == nil { - t.Fatalf("Didn't get the expected error for an unknown resource") - } - if !strings.Contains(err.Error(), "killed") { - t.Fatalf("unexpected error: %q", err) - } -} - func findSecret(m resmap.ResMap) *resource.Resource { for id, res := range m { if id.Gvk().Kind == "Secret" { diff --git a/pkg/target/namespacedgenerators_test.go b/pkg/target/namespacedgenerators_test.go index 61d3cd2db..7977d6737 100644 --- a/pkg/target/namespacedgenerators_test.go +++ b/pkg/target/namespacedgenerators_test.go @@ -39,11 +39,11 @@ configMapGenerator: secretGenerator: - name: the-non-default-namespace-secret namespace: non-default - commands: - password.txt: "echo verySecret" + literals: + - password.txt=verySecret - name: the-secret - commands: - password.txt: "echo anotherSecret" + literals: + - password.txt=anotherSecret `) m, err := th.makeKustTarget().MakeCustomizedResMap() if err != nil { @@ -69,19 +69,19 @@ metadata: --- apiVersion: v1 data: - password.txt: dmVyeVNlY3JldAo= + password.txt: dmVyeVNlY3JldA== kind: Secret metadata: - name: the-non-default-namespace-secret-9fgdmbbk5c + name: the-non-default-namespace-secret-h8d9hkgtb9 namespace: non-default type: Opaque --- apiVersion: v1 data: - password.txt: YW5vdGhlclNlY3JldAo= + password.txt: YW5vdGhlclNlY3JldA== kind: Secret metadata: - name: the-secret-7dd8hcgfhk + name: the-secret-fgb45h45bh type: Opaque `) } diff --git a/pkg/types/kustomization.go b/pkg/types/kustomization.go index 28dff3469..43fb106f7 100644 --- a/pkg/types/kustomization.go +++ b/pkg/types/kustomization.go @@ -216,26 +216,12 @@ type SecretArgs struct { // This is the same field as the secret type field in v1/Secret: // It can be "Opaque" (default), or "kubernetes.io/tls". // - // If type is "kubernetes.io/tls", then "Commands" must have exactly two + // If type is "kubernetes.io/tls", then "literals" or "files" must have exactly two // keys: "tls.key" and "tls.crt" Type string `json:"type,omitempty" yaml:"type,omitempty"` - // CommandSources for secret. - CommandSources `json:",inline,omitempty" yaml:",inline,omitempty"` - - // Deprecated. - // Replaced by GeneratorOptions.TimeoutSeconds - // TimeoutSeconds specifies the timeout for commands. - TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty" yaml:"timeoutSeconds,omitempty"` -} - -// CommandSources contains some generic sources for secrets. -type CommandSources struct { - // Map of keys to commands to generate the values - Commands map[string]string `json:"commands,omitempty" yaml:"commands,omitempty"` - // EnvCommand to output lines of key=val pairs to create a secret. - // i.e. a Docker .env file or a .ini file. - EnvCommand string `json:"envCommand,omitempty" yaml:"envCommand,omitempty"` + // DataSources for secret. + DataSources `json:",inline,omitempty" yaml:",inline,omitempty"` } // DataSources contains some generic sources for configmaps. @@ -282,15 +268,6 @@ type GeneratorOptions struct { // Annotations to add to all generated resources. Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"` - // TimeoutSeconds specifies the timeout for commands, if any, - // used in resource generation. At time of writing, the default - // was specified in configmapandsecret.defaultCommandTimeout. - TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty" yaml:"timeoutSeconds,omitempty"` - - // Shell and arguments to use as a context for commands used in - // resource generation. Default at time of writing: {'sh', '-c'}. - Shell []string `json:"shell,omitempty" yaml:"shell,omitempty"` - // DisableNameSuffixHash if true disables the default behavior of adding a // suffix to the names of generated resources that is a hash of the // resource contents.