create secret from-env-file

This commit is contained in:
Michael Fraenkel 2017-01-12 07:35:46 -08:00
parent 7eb49628c6
commit f2815156b0
5 changed files with 129 additions and 2 deletions

View File

@ -19,6 +19,7 @@ go_library(
"configmap.go",
"deployment.go",
"doc.go",
"env_file.go",
"explain.go",
"generate.go",
"history.go",

View File

@ -64,7 +64,10 @@ var (
kubectl create secret generic my-secret --from-file=ssh-privatekey=~/.ssh/id_rsa --from-file=ssh-publickey=~/.ssh/id_rsa.pub
# Create a new secret named my-secret with key1=supersecret and key2=topsecret
kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret`)
kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret
# Create a new secret named my-secret from an env file
kubectl create secret generic my-secret --from-env-file=path/to/bar.env`)
)
// NewCmdCreateSecretGeneric is a command to create generic secrets from files, directories, or literal values
@ -85,6 +88,7 @@ func NewCmdCreateSecretGeneric(f cmdutil.Factory, cmdOut io.Writer) *cobra.Comma
cmdutil.AddGeneratorFlags(cmd, cmdutil.SecretV1GeneratorName)
cmd.Flags().StringSlice("from-file", []string{}, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid secret key.")
cmd.Flags().StringArray("from-literal", []string{}, "Specify a key and literal value to insert in secret (i.e. mykey=somevalue)")
cmd.Flags().String("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().String("type", "", i18n.T("The type of secret to create"))
return cmd
}
@ -103,6 +107,7 @@ func CreateSecretGeneric(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command
Type: cmdutil.GetFlagString(cmd, "type"),
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
LiteralSources: cmdutil.GetFlagStringArray(cmd, "from-literal"),
EnvFileSource: cmdutil.GetFlagString(cmd, "from-env-file"),
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))

View File

@ -221,7 +221,10 @@ func handleConfigMapFromEnvFileSource(configMap *api.ConfigMap, envFileSource st
if info.IsDir() {
return fmt.Errorf("must be a file")
}
return addFromEnvFileToConfigMap(configMap, envFileSource)
return addFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToConfigMap(configMap, key, value)
})
}
// addKeyFromFileToConfigMap adds a key with the given name to a ConfigMap, populating

View File

@ -38,6 +38,8 @@ type SecretGeneratorV1 struct {
FileSources []string
// LiteralSources to derive the secret from (optional)
LiteralSources []string
// EnvFileSource to derive the secret from (optional)
EnvFileSource string
}
// Ensure it supports the generator pattern that uses parameter injection
@ -71,6 +73,15 @@ func (s SecretGeneratorV1) Generate(genericParams map[string]interface{}) (runti
delegate.LiteralSources = fromLiteralArray
delete(genericParams, "from-literal")
}
fromEnvFileString, found := genericParams["from-env-file"]
if found {
fromEnvFile, isString := fromEnvFileString.(string)
if !isString {
return nil, fmt.Errorf("expected string, found :%v", fromEnvFileString)
}
delegate.EnvFileSource = fromEnvFile
delete(genericParams, "from-env-file")
}
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
@ -91,6 +102,7 @@ func (s SecretGeneratorV1) ParamNames() []GeneratorParam {
{"type", false},
{"from-file", false},
{"from-literal", false},
{"from-env-file", false},
{"force", false},
}
}
@ -116,6 +128,11 @@ func (s SecretGeneratorV1) StructuredGenerate() (runtime.Object, error) {
return nil, err
}
}
if len(s.EnvFileSource) > 0 {
if err := handleFromEnvFileSource(secret, s.EnvFileSource); err != nil {
return nil, err
}
}
return secret, nil
}
@ -124,6 +141,9 @@ func (s SecretGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.EnvFileSource) > 0 && (len(s.FileSources) > 0 || len(s.LiteralSources) > 0) {
return fmt.Errorf("from-env-file cannot be combined with from-file or from-literal")
}
return nil
}
@ -187,6 +207,27 @@ func handleFromFileSources(secret *api.Secret, fileSources []string) error {
return nil
}
// handleFromEnvFileSource adds the specified env file source information
// into the provided secret
func handleFromEnvFileSource(secret *api.Secret, envFileSource string) error {
info, err := os.Stat(envFileSource)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
default:
return fmt.Errorf("error reading %s: %v", envFileSource, err)
}
}
if info.IsDir() {
return fmt.Errorf("must be a file")
}
return addFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToSecret(secret, key, []byte(value))
})
}
func addKeyFromFileToSecret(secret *api.Secret, keyName, filePath string) error {
data, err := ioutil.ReadFile(filePath)
if err != nil {

View File

@ -17,6 +17,7 @@ limitations under the License.
package kubectl
import (
"os"
"reflect"
"testing"
@ -26,6 +27,7 @@ import (
func TestSecretGenerate(t *testing.T) {
tests := []struct {
setup func(t *testing.T, params map[string]interface{}) func()
params map[string]interface{}
expected *api.Secret
expectErr bool
@ -108,9 +110,84 @@ func TestSecretGenerate(t *testing.T) {
},
expectErr: false,
},
{
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
params: map[string]interface{}{
"name": "valid_env",
"from-env-file": "file.env",
},
expected: &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "valid_env",
},
Data: map[string][]byte{
"key1": []byte("value1"),
"key2": []byte("value2"),
},
},
expectErr: false,
},
{
setup: func() func(t *testing.T, params map[string]interface{}) func() {
os.Setenv("g_key1", "1")
os.Setenv("g_key2", "2")
return setupEnvFile("g_key1", "g_key2=")
}(),
params: map[string]interface{}{
"name": "getenv",
"from-env-file": "file.env",
},
expected: &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "getenv",
},
Data: map[string][]byte{
"g_key1": []byte("1"),
"g_key2": []byte(""),
},
},
expectErr: false,
},
{
params: map[string]interface{}{
"name": "too_many_args",
"from-literal": []string{"key1=value1"},
"from-env-file": "file.env",
},
expectErr: true,
},
{
setup: setupEnvFile("key.1=value1"),
params: map[string]interface{}{
"name": "invalid_key",
"from-env-file": "file.env",
},
expectErr: true,
},
{
setup: setupEnvFile(" key1= value1"),
params: map[string]interface{}{
"name": "with_spaces",
"from-env-file": "file.env",
},
expected: &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "with_spaces",
},
Data: map[string][]byte{
"key1": []byte(" value1"),
},
},
expectErr: false,
},
}
generator := SecretGeneratorV1{}
for _, test := range tests {
if test.setup != nil {
if teardown := test.setup(t, test.params); teardown != nil {
defer teardown()
}
}
obj, err := generator.Generate(test.params)
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)