From 8cf886f4317a5f1d23763686bd36cbf02de36f0f Mon Sep 17 00:00:00 2001 From: Boaz Date: Sun, 22 Aug 2021 13:28:59 +0300 Subject: [PATCH] Add option to allow enviornment variables using file * Add `GetEnvsFromFile` to `util` * Add to `podspec` flags `--env-file` and `EnvFile` to `PodSpec`. * If `env-file` is specified load env vars from file to memory, convert them into ordered map and pass them to UpdateEnvVars function by setting custom args for each one of them instead of using command line args. Signed-off-by: Boaz --- CHANGELOG.adoc | 4 +++ docs/cmd/kn_container_add.md | 1 + docs/cmd/kn_service_apply.md | 1 + docs/cmd/kn_service_create.md | 1 + docs/cmd/kn_service_update.md | 1 + docs/cmd/kn_source_container_create.md | 1 + docs/cmd/kn_source_container_update.md | 1 + pkg/kn/flags/podspec.go | 25 +++++++++++++-- pkg/kn/flags/podspec_helper.go | 23 ++++++++++++-- pkg/kn/flags/podspec_helper_test.go | 22 ++++++------- pkg/kn/flags/podspec_test.go | 43 ++++++++++++++++++++++++++ pkg/util/parsing_helper.go | 21 +++++++++++++ pkg/util/parsing_helper_test.go | 24 ++++++++++++++ 13 files changed, 153 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 962c2da9d3..8ac464998d 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -17,6 +17,10 @@ |=== | | Description | PR +| 🎁 +| Add `--env-file` option to function create/update command +| https://github.com/knative/client/pull/1423[#1419] + | ✨ | Reuse conflict retry loop from client-go/util/retry | https://github.com/knative/client/pull/1441[#1441] diff --git a/docs/cmd/kn_container_add.md b/docs/cmd/kn_container_add.md index 63ff4180cd..808983ee0a 100644 --- a/docs/cmd/kn_container_add.md +++ b/docs/cmd/kn_container_add.md @@ -31,6 +31,7 @@ kn container add NAME --arg stringArray Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times. --cmd stringArray Specify command to be used as entrypoint instead of default one. Example: --cmd /app/start or --cmd sh --cmd /app/start.sh or --cmd /app/start --arg myArg to pass additional arguments. -e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-). + --env-file string Path to a file containing environment variables (e.g. --env-file=/home/knative/service1/env). --env-from stringArray Add environment variables from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret:). Example: --env-from cm:myconfigmap or --env-from secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --env-from cm:myconfigmap-. --env-value-from stringArray Add environment variable from a value of key in ConfigMap (prefix cm: or config-map:) or a Secret (prefix sc: or secret:). Example: --env-value-from NAME=cm:myconfigmap:key or --env-value-from NAME=secret:mysecret:key. You can use this flag multiple times. To unset a value from a ConfigMap/Secret key reference, append "-" to the key, e.g. --env-value-from ENV-. --extra-containers string Specify path to file including definition for additional containers, alternatively use '-' to read from stdin. Example: --extra-containers ./containers.yaml or --extra-containers -. diff --git a/docs/cmd/kn_service_apply.md b/docs/cmd/kn_service_apply.md index 92825518d1..eb80c84987 100644 --- a/docs/cmd/kn_service_apply.md +++ b/docs/cmd/kn_service_apply.md @@ -40,6 +40,7 @@ kn service apply s0 --filename my-svc.yml --concurrency-target int Recommendation for when to scale up based on the concurrent number of incoming request. Defaults to --concurrency-limit when given. --concurrency-utilization int Percentage of concurrent requests utilization before scaling up. (default 70) -e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-). + --env-file string Path to a file containing environment variables (e.g. --env-file=/home/knative/service1/env). --env-from stringArray Add environment variables from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret:). Example: --env-from cm:myconfigmap or --env-from secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --env-from cm:myconfigmap-. --env-value-from stringArray Add environment variable from a value of key in ConfigMap (prefix cm: or config-map:) or a Secret (prefix sc: or secret:). Example: --env-value-from NAME=cm:myconfigmap:key or --env-value-from NAME=secret:mysecret:key. You can use this flag multiple times. To unset a value from a ConfigMap/Secret key reference, append "-" to the key, e.g. --env-value-from ENV-. --extra-containers string Specify path to file including definition for additional containers, alternatively use '-' to read from stdin. Example: --extra-containers ./containers.yaml or --extra-containers -. diff --git a/docs/cmd/kn_service_create.md b/docs/cmd/kn_service_create.md index 6b7c9a00fb..1848248fe3 100644 --- a/docs/cmd/kn_service_create.md +++ b/docs/cmd/kn_service_create.md @@ -65,6 +65,7 @@ kn service create NAME --image IMAGE --concurrency-target int Recommendation for when to scale up based on the concurrent number of incoming request. Defaults to --concurrency-limit when given. --concurrency-utilization int Percentage of concurrent requests utilization before scaling up. (default 70) -e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-). + --env-file string Path to a file containing environment variables (e.g. --env-file=/home/knative/service1/env). --env-from stringArray Add environment variables from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret:). Example: --env-from cm:myconfigmap or --env-from secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --env-from cm:myconfigmap-. --env-value-from stringArray Add environment variable from a value of key in ConfigMap (prefix cm: or config-map:) or a Secret (prefix sc: or secret:). Example: --env-value-from NAME=cm:myconfigmap:key or --env-value-from NAME=secret:mysecret:key. You can use this flag multiple times. To unset a value from a ConfigMap/Secret key reference, append "-" to the key, e.g. --env-value-from ENV-. --extra-containers string Specify path to file including definition for additional containers, alternatively use '-' to read from stdin. Example: --extra-containers ./containers.yaml or --extra-containers -. diff --git a/docs/cmd/kn_service_update.md b/docs/cmd/kn_service_update.md index 2c92504d86..ace06bcfbd 100644 --- a/docs/cmd/kn_service_update.md +++ b/docs/cmd/kn_service_update.md @@ -50,6 +50,7 @@ kn service update NAME --concurrency-target int Recommendation for when to scale up based on the concurrent number of incoming request. Defaults to --concurrency-limit when given. --concurrency-utilization int Percentage of concurrent requests utilization before scaling up. (default 70) -e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-). + --env-file string Path to a file containing environment variables (e.g. --env-file=/home/knative/service1/env). --env-from stringArray Add environment variables from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret:). Example: --env-from cm:myconfigmap or --env-from secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --env-from cm:myconfigmap-. --env-value-from stringArray Add environment variable from a value of key in ConfigMap (prefix cm: or config-map:) or a Secret (prefix sc: or secret:). Example: --env-value-from NAME=cm:myconfigmap:key or --env-value-from NAME=secret:mysecret:key. You can use this flag multiple times. To unset a value from a ConfigMap/Secret key reference, append "-" to the key, e.g. --env-value-from ENV-. --extra-containers string Specify path to file including definition for additional containers, alternatively use '-' to read from stdin. Example: --extra-containers ./containers.yaml or --extra-containers -. diff --git a/docs/cmd/kn_source_container_create.md b/docs/cmd/kn_source_container_create.md index 840f30cee1..71a2054b2f 100644 --- a/docs/cmd/kn_source_container_create.md +++ b/docs/cmd/kn_source_container_create.md @@ -20,6 +20,7 @@ kn source container create NAME --image IMAGE --sink SINK --arg stringArray Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times. --cmd stringArray Specify command to be used as entrypoint instead of default one. Example: --cmd /app/start or --cmd sh --cmd /app/start.sh or --cmd /app/start --arg myArg to pass additional arguments. -e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-). + --env-file string Path to a file containing environment variables (e.g. --env-file=/home/knative/service1/env). --env-from stringArray Add environment variables from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret:). Example: --env-from cm:myconfigmap or --env-from secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --env-from cm:myconfigmap-. --env-value-from stringArray Add environment variable from a value of key in ConfigMap (prefix cm: or config-map:) or a Secret (prefix sc: or secret:). Example: --env-value-from NAME=cm:myconfigmap:key or --env-value-from NAME=secret:mysecret:key. You can use this flag multiple times. To unset a value from a ConfigMap/Secret key reference, append "-" to the key, e.g. --env-value-from ENV-. --extra-containers string Specify path to file including definition for additional containers, alternatively use '-' to read from stdin. Example: --extra-containers ./containers.yaml or --extra-containers -. diff --git a/docs/cmd/kn_source_container_update.md b/docs/cmd/kn_source_container_update.md index 3ad7703bbc..e86dc41458 100644 --- a/docs/cmd/kn_source_container_update.md +++ b/docs/cmd/kn_source_container_update.md @@ -20,6 +20,7 @@ kn source container update NAME --image IMAGE --arg stringArray Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times. --cmd stringArray Specify command to be used as entrypoint instead of default one. Example: --cmd /app/start or --cmd sh --cmd /app/start.sh or --cmd /app/start --arg myArg to pass additional arguments. -e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-). + --env-file string Path to a file containing environment variables (e.g. --env-file=/home/knative/service1/env). --env-from stringArray Add environment variables from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret:). Example: --env-from cm:myconfigmap or --env-from secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --env-from cm:myconfigmap-. --env-value-from stringArray Add environment variable from a value of key in ConfigMap (prefix cm: or config-map:) or a Secret (prefix sc: or secret:). Example: --env-value-from NAME=cm:myconfigmap:key or --env-value-from NAME=secret:mysecret:key. You can use this flag multiple times. To unset a value from a ConfigMap/Secret key reference, append "-" to the key, e.g. --env-value-from ENV-. --extra-containers string Specify path to file including definition for additional containers, alternatively use '-' to read from stdin. Example: --extra-containers ./containers.yaml or --extra-containers -. diff --git a/pkg/kn/flags/podspec.go b/pkg/kn/flags/podspec.go index 84880a2457..071ae0eab8 100644 --- a/pkg/kn/flags/podspec.go +++ b/pkg/kn/flags/podspec.go @@ -32,6 +32,7 @@ type PodSpecFlags struct { Env []string EnvFrom []string EnvValueFrom []string + EnvFile string Mount []string Volume []string @@ -85,6 +86,9 @@ func (p *PodSpecFlags) AddFlags(flagset *pflag.FlagSet) []string { "To unset, specify the environment variable name followed by a \"-\" (e.g., NAME-).") flagNames = append(flagNames, "env") + flagset.StringVarP(&p.EnvFile, "env-file", "", "", "Path to a file containing environment variables (e.g. --env-file=/home/knative/service1/env).") + flagNames = append(flagNames, "env-file") + flagset.StringArrayVarP(&p.EnvValueFrom, "env-value-from", "", []string{}, "Add environment variable from a value of key in ConfigMap (prefix cm: or config-map:) or a Secret (prefix sc: or secret:). "+ "Example: --env-value-from NAME=cm:myconfigmap:key or --env-value-from NAME=secret:mysecret:key. "+ @@ -169,7 +173,7 @@ func (p *PodSpecFlags) AddFlags(flagset *pflag.FlagSet) []string { func (p *PodSpecFlags) ResolvePodSpec(podSpec *corev1.PodSpec, flags *pflag.FlagSet, allArgs []string) error { var err error - if flags.Changed("env") || flags.Changed("env-value-from") { + if flags.Changed("env") || flags.Changed("env-value-from") || flags.Changed("env-file") { envToUpdate, envToRemove, err := util.OrderedMapAndRemovalListFromArray(p.Env, "=") if err != nil { return fmt.Errorf("Invalid --env: %w", err) @@ -180,7 +184,24 @@ func (p *PodSpecFlags) ResolvePodSpec(podSpec *corev1.PodSpec, flags *pflag.Flag return fmt.Errorf("Invalid --env-value-from: %w", err) } - err = UpdateEnvVars(podSpec, allArgs, envToUpdate, envToRemove, envValueFromToUpdate, envValueFromToRemove) + envsFileToUpdate := util.NewOrderedMap() + envsFileToRemove := []string{} + if p.EnvFile != "" { + envsFromFile, err := util.GetEnvsFromFile(p.EnvFile, "=") + if err != nil { + return fmt.Errorf("Invalid --env-file: %w", err) + } + envsFileToUpdate, envsFileToRemove, err = util.OrderedMapAndRemovalListFromArray(envsFromFile, "=") + if err != nil { + return fmt.Errorf("Invalid --env: %w", err) + } + } + + err = UpdateEnvVars( + podSpec, allArgs, envToUpdate, envToRemove, + envValueFromToUpdate, envValueFromToRemove, + p.EnvFile, envsFileToUpdate, envsFileToRemove, + ) if err != nil { return err } diff --git a/pkg/kn/flags/podspec_helper.go b/pkg/kn/flags/podspec_helper.go index 6ecda65da2..c35853cd16 100644 --- a/pkg/kn/flags/podspec_helper.go +++ b/pkg/kn/flags/podspec_helper.go @@ -56,17 +56,22 @@ func containerOfPodSpec(spec *corev1.PodSpec) *corev1.Container { // UpdateEnvVars gives the configuration all the env var values listed in the given map of // vars. Does not touch any environment variables not mentioned, but it can add // new env vars and change the values of existing ones. -func UpdateEnvVars(spec *corev1.PodSpec, - allArgs []string, envToUpdate *util.OrderedMap, envToRemove []string, envValueFromToUpdate *util.OrderedMap, envValueFromToRemove []string) error { +func UpdateEnvVars(spec *corev1.PodSpec, allArgs []string, + envToUpdate *util.OrderedMap, envToRemove []string, + envValueFromToUpdate *util.OrderedMap, envValueFromToRemove []string, + envFileName string, envValueFileToUpdate *util.OrderedMap, envValueFileToRemove []string, +) error { container := containerOfPodSpec(spec) allEnvsToUpdate := util.NewOrderedMap() envIterator := envToUpdate.Iterator() envValueFromIterator := envValueFromToUpdate.Iterator() + envValueFileIterator := envValueFileToUpdate.Iterator() envKey, envValue, envExists := envIterator.NextString() envValueFromKey, envValueFromValue, envValueFromExists := envValueFromIterator.NextString() + envValueFileKey, envValueFileValue, envValueFileExists := envValueFileIterator.NextString() for _, arg := range allArgs { // envs are stored as NAME=value if envExists && isValidEnvArg(arg, envKey, envValue) { @@ -86,6 +91,14 @@ func UpdateEnvVars(spec *corev1.PodSpec, ValueFrom: envVarSource, }) envValueFromKey, envValueFromValue, envValueFromExists = envValueFromIterator.NextString() + } else if envValueFileExists && isValidEnvValueFileArg(arg, envFileName) { + for envValueFileExists { + allEnvsToUpdate.Set(envValueFileKey, corev1.EnvVar{ + Name: envValueFileKey, + Value: envValueFileValue, + }) + envValueFileKey, envValueFileValue, envValueFileExists = envValueFileIterator.NextString() + } } } @@ -109,6 +122,12 @@ func isValidEnvValueFromArg(arg, envValueFromKey, envValueFromValue string) bool return strings.HasPrefix(arg, envValueFromKey+"="+envValueFromValue) || strings.HasPrefix(arg, "--env-value-from="+envValueFromKey+"="+envValueFromValue) } +// isValidEnvValueFileArg checks that the input arg is a valid argument for specifying env from value, +// ie. stored as NAME=secret:sercretName:key or NAME=config-map:cmName:key +func isValidEnvValueFileArg(arg, envFileName string) bool { + return strings.HasPrefix(arg, envFileName) || strings.HasPrefix(arg, "--env-file="+envFileName) +} + // UpdateEnvFrom updates envFrom func UpdateEnvFrom(spec *corev1.PodSpec, toUpdate []string, toRemove []string) error { container := containerOfPodSpec(spec) diff --git a/pkg/kn/flags/podspec_helper_test.go b/pkg/kn/flags/podspec_helper_test.go index 28fb1c078e..5fbd60f951 100644 --- a/pkg/kn/flags/podspec_helper_test.go +++ b/pkg/kn/flags/podspec_helper_test.go @@ -51,7 +51,7 @@ func TestUpdateEnvVarsNew(t *testing.T) { envToUpdate, envToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnv, "=") assert.NilError(t, err) args := append([]string{"command"}, argsEnv...) - err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, util.NewOrderedMap(), []string{}) + err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, util.NewOrderedMap(), []string{}, "", util.NewOrderedMap(), []string{}) assert.NilError(t, err) assert.DeepEqual(t, expected, spec.Containers[0].Env) } @@ -71,7 +71,7 @@ func TestUpdateEnvVarsMixedEnvOrder(t *testing.T) { envToUpdate, envToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnv, "=") assert.NilError(t, err) args := append([]string{"command"}, argsEnv...) - err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, util.NewOrderedMap(), []string{}) + err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, util.NewOrderedMap(), []string{}, "", util.NewOrderedMap(), []string{}) assert.NilError(t, err) assert.DeepEqual(t, expected, spec.Containers[0].Env) } @@ -121,7 +121,7 @@ func TestUpdateEnvVarsValueFromNew(t *testing.T) { args := append([]string{"command"}, argsEnvValueFrom...) envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnvValueFrom, "=") assert.NilError(t, err) - err = UpdateEnvVars(spec, args, util.NewOrderedMap(), []string{}, envValueFromToUpdate, envValueFromToRemove) + err = UpdateEnvVars(spec, args, util.NewOrderedMap(), []string{}, envValueFromToUpdate, envValueFromToRemove, "", util.NewOrderedMap(), []string{}) assert.NilError(t, err) assert.DeepEqual(t, expected, spec.Containers[0].Env) } @@ -162,7 +162,7 @@ func TestUpdateEnvVarsAllNew(t *testing.T) { assert.NilError(t, err) envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnvValueFrom, "=") assert.NilError(t, err) - err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, envValueFromToUpdate, envValueFromToRemove) + err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, envValueFromToUpdate, envValueFromToRemove, "", util.NewOrderedMap(), []string{}) assert.NilError(t, err) assert.DeepEqual(t, expected, spec.Containers[0].Env) } @@ -197,7 +197,7 @@ func TestUpdateEnvVarsValueFromValidate(t *testing.T) { args := append([]string{"command"}, input...) envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(input, "=") assert.NilError(t, err) - err = UpdateEnvVars(spec, args, util.NewOrderedMap(), []string{}, envValueFromToUpdate, envValueFromToRemove) + err = UpdateEnvVars(spec, args, util.NewOrderedMap(), []string{}, envValueFromToUpdate, envValueFromToRemove, "", util.NewOrderedMap(), []string{}) fmt.Println() msg := fmt.Sprintf("input \"%s\" should fail, as it is not valid entry for containers.env.valueFrom", input[0]) assert.ErrorContains(t, err, " ", msg) @@ -482,7 +482,7 @@ func TestUpdateEnvVarsModify(t *testing.T) { args := append([]string{"command"}, argsEnv...) envToUpdate, envToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnv, "=") assert.NilError(t, err) - err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, util.NewOrderedMap(), []string{}) + err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, util.NewOrderedMap(), []string{}, "", util.NewOrderedMap(), []string{}) assert.NilError(t, err) assert.DeepEqual(t, expected, container.Env) } @@ -516,7 +516,7 @@ func TestUpdateEnvVarsValueFromModify(t *testing.T) { args := append([]string{"command"}, argsEnvValueFrom...) envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnvValueFrom, "=") assert.NilError(t, err) - err = UpdateEnvVars(spec, args, util.NewOrderedMap(), []string{}, envValueFromToUpdate, envValueFromToRemove) + err = UpdateEnvVars(spec, args, util.NewOrderedMap(), []string{}, envValueFromToUpdate, envValueFromToRemove, "", util.NewOrderedMap(), []string{}) assert.NilError(t, err) assert.DeepEqual(t, expected, container.Env) } @@ -557,7 +557,7 @@ func TestUpdateEnvVarsAllModify(t *testing.T) { assert.NilError(t, err) envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnvValueFrom, "=") assert.NilError(t, err) - err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, envValueFromToUpdate, envValueFromToRemove) + err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, envValueFromToUpdate, envValueFromToRemove, "", util.NewOrderedMap(), []string{}) assert.NilError(t, err) assert.DeepEqual(t, expected, container.Env) } @@ -572,7 +572,7 @@ func TestUpdateEnvVarsRemove(t *testing.T) { args := append([]string{"command"}, remove...) envToUpdate, envToRemove, err := util.OrderedMapAndRemovalListFromArray(remove, "=") assert.NilError(t, err) - err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, util.NewOrderedMap(), []string{}) + err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, util.NewOrderedMap(), []string{}, "", util.NewOrderedMap(), []string{}) assert.NilError(t, err) expected := []corev1.EnvVar{ @@ -606,7 +606,7 @@ func TestUpdateEnvVarsValueFromRemove(t *testing.T) { args := append([]string{"command"}, remove...) envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(remove, "=") assert.NilError(t, err) - err = UpdateEnvVars(spec, args, util.NewOrderedMap(), []string{}, envValueFromToUpdate, envValueFromToRemove) + err = UpdateEnvVars(spec, args, util.NewOrderedMap(), []string{}, envValueFromToUpdate, envValueFromToRemove, "", util.NewOrderedMap(), []string{}) assert.NilError(t, err) expected := []corev1.EnvVar{ @@ -659,7 +659,7 @@ func TestUpdateEnvVarsAllRemove(t *testing.T) { assert.NilError(t, err) envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnvValueFrom, "=") assert.NilError(t, err) - err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, envValueFromToUpdate, envValueFromToRemove) + err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, envValueFromToUpdate, envValueFromToRemove, "", util.NewOrderedMap(), []string{}) assert.NilError(t, err) expected := []corev1.EnvVar{ diff --git a/pkg/kn/flags/podspec_test.go b/pkg/kn/flags/podspec_test.go index c0ab6fc8a3..69f09ff112 100644 --- a/pkg/kn/flags/podspec_test.go +++ b/pkg/kn/flags/podspec_test.go @@ -28,6 +28,7 @@ import ( "github.com/spf13/cobra" "gotest.tools/v3/assert" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" "knative.dev/client/pkg/util" "knative.dev/pkg/ptr" ) @@ -285,3 +286,45 @@ func TestPodSpecResolveReturnError(t *testing.T) { out := outBuf.String() assert.Assert(t, util.ContainsAll(out, "Invalid", "mount")) } + +func TestPodSpecResolveWithEnvFile(t *testing.T) { + file, err := ioutil.TempFile("", "envfile.env") + assert.NilError(t, err) + file.WriteString("svcOwner=James\nsvcAuthor=James") + defer os.Remove(file.Name()) + + inputArgs := []string{"--image", "repo/user/imageID:tag", "--env", "svcOwner=David", + "--env-file", file.Name(), "--port", "8080", "--cmd", "/app/start", "--arg", "myArg1"} + expectedPodSpec := corev1.PodSpec{ + Containers: []corev1.Container{ + { + Image: "repo/user/imageID:tag", + Command: []string{"/app/start"}, + Args: []string{"myArg1"}, + Ports: []corev1.ContainerPort{ + { + ContainerPort: 8080, + }, + }, + Env: []corev1.EnvVar{{Name: "svcOwner", Value: "James"}, {Name: "svcAuthor", Value: "James"}}, + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{}, + Requests: v1.ResourceList{}, + }, + }, + }, + } + flags := &PodSpecFlags{} + testCmd := &cobra.Command{ + Use: "test", + Run: func(cmd *cobra.Command, args []string) { + podSpec := &corev1.PodSpec{Containers: []corev1.Container{{}}} + err := flags.ResolvePodSpec(podSpec, cmd.Flags(), inputArgs) + assert.NilError(t, err, "PodSpec cannot be resolved.") + assert.DeepEqual(t, expectedPodSpec, *podSpec) + }, + } + testCmd.SetArgs(inputArgs) + flags.AddFlags(testCmd.Flags()) + testCmd.Execute() +} diff --git a/pkg/util/parsing_helper.go b/pkg/util/parsing_helper.go index e35a7f3665..c10bdbd256 100644 --- a/pkg/util/parsing_helper.go +++ b/pkg/util/parsing_helper.go @@ -15,7 +15,9 @@ package util import ( + "bufio" "fmt" + "os" "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -23,6 +25,25 @@ import ( "knative.dev/pkg/tracker" ) +// GetEnvsFromFile transform a path to a file containing environment variable into a list of key-value pair. +// If there is an issue reading the file or parsing the content in the file an error value will be returned. +func GetEnvsFromFile(filepath string, delimiter string) ([]string, error) { + envs := []string{} + file, err := os.Open(filepath) + if err != nil { + return envs, err + } + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if len(line) == 0 { + continue + } + envs = append(envs, scanner.Text()) + } + return envs, nil +} + // OrderedMapAndRemovalListFromArray creates a list of key-value pair using MapFromArrayAllowingSingles, and a list of removal entries func OrderedMapAndRemovalListFromArray(arr []string, delimiter string) (*OrderedMap, []string, error) { orderedMap := NewOrderedMap() diff --git a/pkg/util/parsing_helper_test.go b/pkg/util/parsing_helper_test.go index 35889b3ee9..9a85c13672 100644 --- a/pkg/util/parsing_helper_test.go +++ b/pkg/util/parsing_helper_test.go @@ -16,6 +16,8 @@ package util import ( "fmt" + "io/ioutil" + "os" "testing" "gotest.tools/v3/assert" @@ -186,3 +188,25 @@ func testToTrackerReference(t *testing.T, input, namespace string, expected *tra func funcRef(ref func(t *testing.T, err error)) *func(*testing.T, error) { return &ref } + +func TestGetEnvsFromFile(t *testing.T) { + file, err := ioutil.TempFile("", "test-1") + assert.NilError(t, err) + file.WriteString(` +name=service-1 + +target=hello-world + +`) + defer os.Remove(file.Name()) + + envs, err := GetEnvsFromFile(file.Name(), "=") + assert.NilError(t, err) + assert.DeepEqual(t, envs, []string{"name=service-1", "target=hello-world"}) +} + +func TestGetEnvsFromFileErrorNotFound(t *testing.T) { + envs, err := GetEnvsFromFile("/tmp/somerandom/path/bla/bla", "=") + assert.ErrorContains(t, err, "no such file or directory") + assert.DeepEqual(t, envs, []string{}) +}