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{}) +}