diff --git a/pkg/configmapandsecret/secretfactory.go b/pkg/configmapandsecret/secretfactory.go index 6204e2439..cf0e7a44b 100644 --- a/pkg/configmapandsecret/secretfactory.go +++ b/pkg/configmapandsecret/secretfactory.go @@ -1,3 +1,19 @@ +/* +Copyright 2018 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 configmapandsecret import ( @@ -5,12 +21,14 @@ import ( "fmt" "os/exec" "path/filepath" + "strings" "time" "github.com/kubernetes-sigs/kustomize/pkg/fs" "github.com/kubernetes-sigs/kustomize/pkg/types" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/validation" ) // SecretFactory makes Secrets. @@ -24,8 +42,7 @@ func NewSecretFactory(fSys fs.FileSystem, wd string) *SecretFactory { return &SecretFactory{fSys: fSys, wd: wd} } -// MakeSecret returns a new secret. -func (f *SecretFactory) MakeSecret(args types.SecretArgs) (*corev1.Secret, error) { +func (f *SecretFactory) makeFreshSecret(args *types.SecretArgs) *corev1.Secret { s := &corev1.Secret{} s.APIVersion = "v1" s.Kind = "Secret" @@ -35,17 +52,73 @@ func (f *SecretFactory) MakeSecret(args types.SecretArgs) (*corev1.Secret, error s.Type = corev1.SecretTypeOpaque } s.Data = map[string][]byte{} - for k, v := range args.Commands { - out, err := f.createSecretKey(v) - if err != nil { - errMsg := fmt.Sprintf("createSecretKey: couldn't make secret %s for key %s", s.Name, k) - return nil, errors.Wrap(err, errMsg) - } - s.Data[k] = out + return s +} + +// MakeSecret returns a new secret. +func (f *SecretFactory) MakeSecret(args *types.SecretArgs) (*corev1.Secret, error) { + var all []kvPair + var err error + s := f.makeFreshSecret(args) + + pairs, err := f.keyValuesFromEnvFileCommand(args.EnvCommand) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf( + "env source file: %s", + args.EnvCommand)) } + all = append(all, pairs...) + + pairs, err = f.keyValuesFromCommands(args.Commands) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf( + "commands %v", args.Commands)) + } + all = append(all, pairs...) + + for _, kv := range all { + err = addKvToSecret(s, kv.key, kv.value) + if err != nil { + return nil, err + } + } + return s, nil } +func addKvToSecret(secret *corev1.Secret, keyName, data string) error { + // Note, the rules for SecretKeys keys are the exact same as the ones for ConfigMap. + if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 { + return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ";")) + } + if _, entryExists := secret.Data[keyName]; entryExists { + return fmt.Errorf("cannot add key %s, another key by that name already exists: %v", keyName, secret.Data) + } + secret.Data[keyName] = []byte(data) + return nil +} + +func (f *SecretFactory) keyValuesFromEnvFileCommand(cmd string) ([]kvPair, error) { + content, err := f.createSecretKey(cmd) + if err != nil { + return nil, err + } + return keyValuesFromLines(content) +} + +func (f *SecretFactory) keyValuesFromCommands(sources map[string]string) ([]kvPair, error) { + var kvs []kvPair + for k, cmd := range sources { + content, err := f.createSecretKey(cmd) + fmt.Println("createSecretKey:", content) + 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) ([]byte, error) { if !f.fSys.IsDir(f.wd) { diff --git a/pkg/resmap/secret.go b/pkg/resmap/secret.go index c0436bf19..662e7dbf6 100644 --- a/pkg/resmap/secret.go +++ b/pkg/resmap/secret.go @@ -30,7 +30,7 @@ func NewResMapFromSecretArgs( secretList []types.SecretArgs) (ResMap, error) { var allResources []*resource.Resource for _, args := range secretList { - s, err := f.MakeSecret(args) + s, err := f.MakeSecret(&args) if err != nil { return nil, errors.Wrap(err, "makeSecret") } diff --git a/pkg/resmap/secret_test.go b/pkg/resmap/secret_test.go index 7af1b5705..b43a1fcc2 100644 --- a/pkg/resmap/secret_test.go +++ b/pkg/resmap/secret_test.go @@ -35,9 +35,18 @@ func TestNewResMapFromSecretArgs(t *testing.T) { secrets := []types.SecretArgs{ { Name: "apple", - Commands: map[string]string{ - "DB_USERNAME": "printf admin", - "DB_PASSWORD": "printf somepw", + CommandSources: types.CommandSources{ + Commands: map[string]string{ + "DB_USERNAME": "printf admin", + "DB_PASSWORD": "printf somepw", + }, + }, + Type: "Opaque", + }, + { + Name: "peanuts", + CommandSources: types.CommandSources{ + EnvCommand: "printf \"DB_USERNAME=admin\nDB_PASSWORD=somepw\"", }, Type: "Opaque", }, @@ -66,6 +75,20 @@ func TestNewResMapFromSecretArgs(t *testing.T) { "DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")), }, }).SetBehavior(resource.BehaviorCreate), + resource.NewResId(secret, "peanuts"): resource.NewResourceFromMap( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": "peanuts", + "creationTimestamp": nil, + }, + "type": string(corev1.SecretTypeOpaque), + "data": map[string]interface{}{ + "DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")), + "DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")), + }, + }).SetBehavior(resource.BehaviorCreate), } if !reflect.DeepEqual(actual, expected) { t.Fatalf("%#v\ndoesn't match expected:\n%#v", actual, expected) diff --git a/pkg/types/kustomization.go b/pkg/types/kustomization.go index 136c366c5..cb896b89a 100644 --- a/pkg/types/kustomization.go +++ b/pkg/types/kustomization.go @@ -115,11 +115,21 @@ type SecretArgs struct { // keys: "tls.key" and "tls.crt" Type string `json:"type,omitempty" yaml:"type,omitempty"` - // Map of keys to commands to generate the values - Commands map[string]string `json:",commands,omitempty" yaml:",inline,omitempty"` + // CommandSources for secret. + CommandSources `json:",inline,omitempty" yaml:",inline,omitempty"` } -// DataSources contains some generic sources for configmap or secret. +// CommandSources contains some generic sources for secrets. +// Only one field can be set. +type CommandSources struct { + // Map of keys to commands to generate the values + Commands map[string]string `json:"commands,omitempty" yaml:"literals,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:"env,omitempty"` +} + +// DataSources contains some generic sources for configmaps. // Only one field can be set. type DataSources struct { // LiteralSources is a list of literal sources.