Add `env-value-from` flag & keep order of env vars in created/updated services (#1328)

* Add --env-value-from & preserve env var order

Signed-off-by: Zbynek Roubalik <zroubali@redhat.com>

* add e2e test for checking Service Env Vars

Signed-off-by: Zbynek Roubalik <zroubali@redhat.com>

* update changelog

Signed-off-by: Zbynek Roubalik <zroubali@redhat.com>

* refactor UpdateEnvVars()

Signed-off-by: Zbynek Roubalik <zroubali@redhat.com>

* apply review suggestions

Signed-off-by: Zbynek Roubalik <zroubali@redhat.com>

* fix typo

Signed-off-by: Zbynek Roubalik <zroubali@redhat.com>

* improve test cases names

Signed-off-by: Zbynek Roubalik <zroubali@redhat.com>
This commit is contained in:
Zbynek Roubalik 2021-06-29 10:48:13 +02:00 committed by GitHub
parent ab537bc731
commit a086cc9707
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 763 additions and 111 deletions

View File

@ -20,6 +20,10 @@
| Prettify printing of webhook warnings
| https://github.com/knative/client/pull/1353[#1353]
| 🎁
| Add `env-value-from` flag & keep order of env vars in created/updated Services
| https://github.com/knative/client/pull/1328[#1328]
## v0.23.0 (2021-05-18)
[cols="1,10,3", options="header", width="100%"]
|===
@ -1350,6 +1354,5 @@ Ignore PRs:
[cols="1,10,3", options="header", width="100%"]
|===
| | Description | PR
|===
////

View File

@ -41,6 +41,7 @@ kn service apply s0 --filename my-svc.yml
--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-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-.
-f, --filename string Create a service from file. The created service can be further modified by combining with other options. For example, -f /path/to/file --env NAME=value adds also an environment variable.
--force Create service forcefully, replaces existing service if any.
-h, --help help for apply

View File

@ -66,6 +66,7 @@ kn service create NAME --image IMAGE
--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-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-.
-f, --filename string Create a service from file. The created service can be further modified by combining with other options. For example, -f /path/to/file --env NAME=value adds also an environment variable.
--force Create service forcefully, replaces existing service if any.
-h, --help help for create

View File

@ -51,6 +51,7 @@ kn service update NAME
--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-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-.
-h, --help help for update
--image string Image to run.
-l, --label stringArray Labels to set for both Service and Revision. name=value; you may provide this flag any number of times to set multiple labels. To unset, specify the label name followed by a "-" (e.g., name-).

View File

@ -17,22 +17,23 @@ kn source container create NAME --image IMAGE --sink SINK
### Options
```
--arg stringArray Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times.
--cmd string Specify command to be used as entrypoint instead of default one. Example: --cmd /app/start 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-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-.
-h, --help help for create
--image string Image to run.
--limit strings The resource requirement limits for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource limit, append "-" to the resource name, e.g. '--limit memory-'.
--mount stringArray Mount a ConfigMap (prefix cm: or config-map:), a Secret (prefix secret: or sc:), or an existing Volume (without any prefix) on the specified directory. Example: --mount /mydir=cm:myconfigmap, --mount /mydir=secret:mysecret, or --mount /mydir=myvolume. When a configmap or a secret is specified, a corresponding volume is automatically generated. You can use this flag multiple times. For unmounting a directory, append "-", e.g. --mount /mydir-, which also removes any auto-generated volume.
-n, --namespace string Specify the namespace to operate in.
-p, --port string The port where application listens on, in the format 'NAME:PORT', where 'NAME' is optional. Examples: '--port h2c:8080' , '--port 8080'.
--pull-secret string Image pull secret to set. An empty argument ("") clears the pull secret. The referenced secret must exist in the service's namespace.
--request strings The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource request, append "-" to the resource name, e.g. '--request cpu-'.
--service-account string Service account name to set. An empty argument ("") clears the service account. The referenced service account must exist in the service's namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. If a prefix is not provided, it is considered as a Knative service in the current namespace. If referring to a Knative service in another namespace, 'ksvc:name:namespace' combination must be provided explicitly.
--user int The user ID to run the container (e.g., 1001).
--volume stringArray Add a volume from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret: or sc:). Example: --volume myvolume=cm:myconfigmap or --volume myvolume=secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --volume myvolume-.
--arg stringArray Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times.
--cmd string Specify command to be used as entrypoint instead of default one. Example: --cmd /app/start 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-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-.
-h, --help help for create
--image string Image to run.
--limit strings The resource requirement limits for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource limit, append "-" to the resource name, e.g. '--limit memory-'.
--mount stringArray Mount a ConfigMap (prefix cm: or config-map:), a Secret (prefix secret: or sc:), or an existing Volume (without any prefix) on the specified directory. Example: --mount /mydir=cm:myconfigmap, --mount /mydir=secret:mysecret, or --mount /mydir=myvolume. When a configmap or a secret is specified, a corresponding volume is automatically generated. You can use this flag multiple times. For unmounting a directory, append "-", e.g. --mount /mydir-, which also removes any auto-generated volume.
-n, --namespace string Specify the namespace to operate in.
-p, --port string The port where application listens on, in the format 'NAME:PORT', where 'NAME' is optional. Examples: '--port h2c:8080' , '--port 8080'.
--pull-secret string Image pull secret to set. An empty argument ("") clears the pull secret. The referenced secret must exist in the service's namespace.
--request strings The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource request, append "-" to the resource name, e.g. '--request cpu-'.
--service-account string Service account name to set. An empty argument ("") clears the service account. The referenced service account must exist in the service's namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. If a prefix is not provided, it is considered as a Knative service in the current namespace. If referring to a Knative service in another namespace, 'ksvc:name:namespace' combination must be provided explicitly.
--user int The user ID to run the container (e.g., 1001).
--volume stringArray Add a volume from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret: or sc:). Example: --volume myvolume=cm:myconfigmap or --volume myvolume=secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --volume myvolume-.
```
### Options inherited from parent commands

View File

@ -17,22 +17,23 @@ kn source container update NAME --image IMAGE
### Options
```
--arg stringArray Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times.
--cmd string Specify command to be used as entrypoint instead of default one. Example: --cmd /app/start 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-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-.
-h, --help help for update
--image string Image to run.
--limit strings The resource requirement limits for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource limit, append "-" to the resource name, e.g. '--limit memory-'.
--mount stringArray Mount a ConfigMap (prefix cm: or config-map:), a Secret (prefix secret: or sc:), or an existing Volume (without any prefix) on the specified directory. Example: --mount /mydir=cm:myconfigmap, --mount /mydir=secret:mysecret, or --mount /mydir=myvolume. When a configmap or a secret is specified, a corresponding volume is automatically generated. You can use this flag multiple times. For unmounting a directory, append "-", e.g. --mount /mydir-, which also removes any auto-generated volume.
-n, --namespace string Specify the namespace to operate in.
-p, --port string The port where application listens on, in the format 'NAME:PORT', where 'NAME' is optional. Examples: '--port h2c:8080' , '--port 8080'.
--pull-secret string Image pull secret to set. An empty argument ("") clears the pull secret. The referenced secret must exist in the service's namespace.
--request strings The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource request, append "-" to the resource name, e.g. '--request cpu-'.
--service-account string Service account name to set. An empty argument ("") clears the service account. The referenced service account must exist in the service's namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. If a prefix is not provided, it is considered as a Knative service in the current namespace. If referring to a Knative service in another namespace, 'ksvc:name:namespace' combination must be provided explicitly.
--user int The user ID to run the container (e.g., 1001).
--volume stringArray Add a volume from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret: or sc:). Example: --volume myvolume=cm:myconfigmap or --volume myvolume=secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --volume myvolume-.
--arg stringArray Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times.
--cmd string Specify command to be used as entrypoint instead of default one. Example: --cmd /app/start 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-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-.
-h, --help help for update
--image string Image to run.
--limit strings The resource requirement limits for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource limit, append "-" to the resource name, e.g. '--limit memory-'.
--mount stringArray Mount a ConfigMap (prefix cm: or config-map:), a Secret (prefix secret: or sc:), or an existing Volume (without any prefix) on the specified directory. Example: --mount /mydir=cm:myconfigmap, --mount /mydir=secret:mysecret, or --mount /mydir=myvolume. When a configmap or a secret is specified, a corresponding volume is automatically generated. You can use this flag multiple times. For unmounting a directory, append "-", e.g. --mount /mydir-, which also removes any auto-generated volume.
-n, --namespace string Specify the namespace to operate in.
-p, --port string The port where application listens on, in the format 'NAME:PORT', where 'NAME' is optional. Examples: '--port h2c:8080' , '--port 8080'.
--pull-secret string Image pull secret to set. An empty argument ("") clears the pull secret. The referenced secret must exist in the service's namespace.
--request strings The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource request, append "-" to the resource name, e.g. '--request cpu-'.
--service-account string Service account name to set. An empty argument ("") clears the service account. The referenced service account must exist in the service's namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. If a prefix is not provided, it is considered as a Knative service in the current namespace. If referring to a Knative service in another namespace, 'ksvc:name:namespace' combination must be provided explicitly.
--user int The user ID to run the container (e.g., 1001).
--volume stringArray Add a volume from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret: or sc:). Example: --volume myvolume=cm:myconfigmap or --volume myvolume=secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --volume myvolume-.
```
### Options inherited from parent commands

View File

@ -16,6 +16,7 @@ package service
import (
"fmt"
"os"
"strconv"
"strings"
@ -199,7 +200,7 @@ func (p *ConfigurationEditFlags) Apply(
template := &service.Spec.Template
err := p.PodSpecFlags.ResolvePodSpec(&template.Spec.PodSpec, cmd.Flags())
err := p.PodSpecFlags.ResolvePodSpec(&template.Spec.PodSpec, cmd.Flags(), os.Args)
if err != nil {
return err
}

View File

@ -80,7 +80,7 @@ func TestServiceCreateEnvMock(t *testing.T) {
template.Annotations = map[string]string{servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz"}
r.CreateService(service, nil)
output, err := executeServiceCommand(client, "create", "foo", "--image", "gcr.io/foo/bar:baz", "-e", "a=mouse", "--env", "b=cookie", "--env=empty", "--no-wait", "--revision-name=")
output, err := executeServiceCommand(client, "create", "foo", "--image", "gcr.io/foo/bar:baz", "-e", "a=mouse", "--env", "b=cookie", "--env", "empty=", "--no-wait", "--revision-name=")
assert.NilError(t, err)
assert.Assert(t, util.ContainsAll(output, "created", "foo", "default"))

View File

@ -207,9 +207,18 @@ func TestServiceCreateArg(t *testing.T) {
}
func TestServiceCreateEnv(t *testing.T) {
action, created, _, err := fakeServiceCreate([]string{
// we need to temporary reset os.Args, becase it is being used for evaluation
// of order of envs set by --env and --env-value-from
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
args := []string{
"service", "create", "foo", "--image", "gcr.io/foo/bar:baz",
"-e", "A=DOGS", "--env", "B=WOLVES", "--env=EMPTY", "--no-wait"}, false)
"-e", "A=DOGS", "--env", "B=WOLVES", "--env", "EMPTY=", "--no-wait"}
os.Args = args
action, created, _, err := fakeServiceCreate(args, false)
if err != nil {
t.Fatal(err)
@ -708,15 +717,28 @@ func TestServiceCreateImageForce(t *testing.T) {
}
func TestServiceCreateEnvForce(t *testing.T) {
_, _, _, err := fakeServiceCreate([]string{
// we need to temporary reset os.Args, becase it is being used for evaluation
// of order of envs set by --env and --env-value-from
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
args := []string{
"service", "create", "foo", "--image", "gcr.io/foo/bar:v1",
"-e", "A=DOGS", "--env", "B=WOLVES", "--no-wait"}, false)
"-e", "A=DOGS", "--env", "B=WOLVES", "--no-wait"}
os.Args = args
_, _, _, err := fakeServiceCreate(args, false)
if err != nil {
t.Fatal(err)
}
action, created, output, err := fakeServiceCreate([]string{
args = []string{
"service", "create", "foo", "--force", "--image", "gcr.io/foo/bar:v2",
"-e", "A=CATS", "--env", "B=LIONS", "--no-wait"}, false)
"-e", "A=CATS", "--env", "B=LIONS", "--no-wait"}
os.Args = args
action, created, output, err := fakeServiceCreate(args, false)
if err != nil {
t.Fatal(err)
@ -961,8 +983,16 @@ func TestServiceCreateFromYAMLWithOverride(t *testing.T) {
expectedEnvVars := map[string]string{
"TARGET": "Go Sample v1",
"FOO": "BAR"}
action, created, _, err := fakeServiceCreate([]string{
"service", "create", "foo", "--filename", tempFile, "--env", "FOO=BAR"}, false)
// we need to temporary reset os.Args, becase it is being used for evaluation
// of order of envs set by --env and --env-value-from
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
args := []string{"service", "create", "foo", "--filename", tempFile, "--env", "FOO=BAR"}
os.Args = args
action, created, _, err := fakeServiceCreate(args, false)
assert.NilError(t, err)
assert.Assert(t, action.Matches("create", "services"))
assert.Equal(t, created.Name, "foo")
@ -975,8 +1005,11 @@ func TestServiceCreateFromYAMLWithOverride(t *testing.T) {
expectedEnvVars = map[string]string{
"TARGET": "FOOBAR",
"FOO": "BAR"}
action, created, _, err = fakeServiceCreate([]string{
"service", "create", "foo", "--filename", tempFile, "--env", "TARGET=FOOBAR", "--env", "FOO=BAR"}, false)
args = []string{"service", "create", "foo", "--filename", tempFile, "--env", "TARGET=FOOBAR", "--env", "FOO=BAR"}
os.Args = args
action, created, _, err = fakeServiceCreate(args, false)
assert.NilError(t, err)
assert.Assert(t, action.Matches("create", "services"))
assert.Equal(t, created.Name, "foo")
@ -988,8 +1021,11 @@ func TestServiceCreateFromYAMLWithOverride(t *testing.T) {
// Remove existing env vars
expectedEnvVars = map[string]string{
"FOO": "BAR"}
action, created, _, err = fakeServiceCreate([]string{
"service", "create", "foo", "--filename", tempFile, "--env", "TARGET-", "--env", "FOO=BAR"}, false)
args = []string{"service", "create", "foo", "--filename", tempFile, "--env", "TARGET-", "--env", "FOO=BAR"}
os.Args = args
action, created, _, err = fakeServiceCreate(args, false)
assert.NilError(t, err)
assert.Assert(t, action.Matches("create", "services"))
assert.Equal(t, created.Name, "foo")
@ -1001,8 +1037,12 @@ func TestServiceCreateFromYAMLWithOverride(t *testing.T) {
// Multiple edit flags
expectedAnnotations := map[string]string{
"foo": "bar"}
action, created, _, err = fakeServiceCreate([]string{"service", "create", "foo", "--filename", tempFile,
"--service-account", "foo", "--cmd", "/foo/bar", "-a", "foo=bar"}, false)
args = []string{"service", "create", "foo", "--filename", tempFile,
"--service-account", "foo", "--cmd", "/foo/bar", "-a", "foo=bar"}
os.Args = args
action, created, _, err = fakeServiceCreate(args, false)
assert.NilError(t, err)
assert.Assert(t, action.Matches("create", "services"))
assert.Equal(t, created.Name, "foo")

View File

@ -16,6 +16,7 @@ package service
import (
"bytes"
"os"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
@ -54,6 +55,12 @@ func executeServiceCommand(client clientservingv1.KnServingClient, args ...strin
knParams := &commands.KnParams{}
knParams.ClientConfig = blankConfig
// we need to temporary reset os.Args, becase it is being used for evaluation
// of order of envs set by --env and --env-value-from
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = args
output := new(bytes.Buffer)
knParams.Output = output
knParams.NewServingClient = func(namespace string) (clientservingv1.KnServingClient, error) {

View File

@ -56,7 +56,7 @@ func TestServiceUpdateEnvMock(t *testing.T) {
r := client.Recorder()
recordServiceUpdateWithSuccess(r, "foo", service, updated)
output, err := executeServiceCommand(client, "create", "foo", "--image", "gcr.io/foo/bar:baz", "-e", "a=mouse", "--env", "b=cookie", "--env=empty", "--no-wait", "--revision-name=")
output, err := executeServiceCommand(client, "create", "foo", "--image", "gcr.io/foo/bar:baz", "-e", "a=mouse", "--env", "b=cookie", "--env", "empty=", "--no-wait", "--revision-name=")
assert.NilError(t, err)
assert.Assert(t, util.ContainsAll(output, "created", "foo", "default"))

View File

@ -17,6 +17,7 @@ package service
import (
"errors"
"fmt"
"os"
"reflect"
"strings"
"testing"
@ -655,8 +656,16 @@ func TestServiceUpdateEnv(t *testing.T) {
flags.UpdateImage(&template.Spec.PodSpec, "gcr.io/foo/bar:baz")
action, updated, _, err := fakeServiceUpdate(orig, []string{
"service", "update", "foo", "-e", "TARGET=Awesome", "--env", "EXISTING-", "--env=OTHEREXISTING-=whatever", "--no-wait"})
// we need to temporary reset os.Args, becase it is being used for evaluation
// of order of envs set by --env and --env-value-from
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
args := []string{
"service", "update", "foo", "-e", "TARGET=Awesome", "--env", "EXISTING-", "--env", "OTHEREXISTING-", "--no-wait"}
os.Args = args
action, updated, _, err := fakeServiceUpdate(orig, args)
if err != nil {
t.Fatal(err)

View File

@ -19,6 +19,7 @@ package container
import (
"errors"
"fmt"
"os"
"github.com/spf13/cobra"
"knative.dev/client/pkg/kn/commands/flags"
@ -67,7 +68,7 @@ func NewContainerCreateCommand(p *commands.KnParams) *cobra.Command {
}
podSpec := &corev1.PodSpec{Containers: []corev1.Container{{}}}
err = podFlags.ResolvePodSpec(podSpec, cmd.Flags())
err = podFlags.ResolvePodSpec(podSpec, cmd.Flags(), os.Args)
if err != nil {
return fmt.Errorf(
"cannot create ContainerSource '%s' in namespace '%s' "+

View File

@ -19,6 +19,7 @@ package container
import (
"errors"
"fmt"
"os"
"github.com/spf13/cobra"
"knative.dev/client/pkg/kn/commands/flags"
@ -69,7 +70,7 @@ func NewContainerUpdateCommand(p *commands.KnParams) *cobra.Command {
b := v1.NewContainerSourceBuilderFromExisting(source)
podSpec := b.Build().Spec.Template.Spec
err = podFlags.ResolvePodSpec(&podSpec, cmd.Flags())
err = podFlags.ResolvePodSpec(&podSpec, cmd.Flags(), os.Args)
if err != nil {
return fmt.Errorf(
"cannot update ContainerSource '%s' in namespace '%s' "+

View File

@ -28,11 +28,12 @@ import (
// PodSpecFlags to hold the container resource requirements values
type PodSpecFlags struct {
// Direct field manipulation
Image uniqueStringArg
Env []string
EnvFrom []string
Mount []string
Volume []string
Image uniqueStringArg
Env []string
EnvFrom []string
EnvValueFrom []string
Mount []string
Volume []string
Command string
Arg []string
@ -82,6 +83,13 @@ 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.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. "+
"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-.")
flagNames = append(flagNames, "env-value-from")
flagset.StringArrayVarP(&p.EnvFrom, "env-from", "", []string{},
"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. "+
@ -150,17 +158,22 @@ func (p *PodSpecFlags) AddFlags(flagset *pflag.FlagSet) []string {
return flagNames
}
// ResolvePodSpec will create corev1.PodSpec based on the flag inputs
func (p *PodSpecFlags) ResolvePodSpec(podSpec *corev1.PodSpec, flags *pflag.FlagSet) error {
// ResolvePodSpec will create corev1.PodSpec based on the flag inputs and all input arguments
func (p *PodSpecFlags) ResolvePodSpec(podSpec *corev1.PodSpec, flags *pflag.FlagSet, allArgs []string) error {
var err error
if flags.Changed("env") {
envMap, err := util.MapFromArrayAllowingSingles(p.Env, "=")
if flags.Changed("env") || flags.Changed("env-value-from") {
envToUpdate, envToRemove, err := util.OrderedMapAndRemovalListFromArray(p.Env, "=")
if err != nil {
return fmt.Errorf("Invalid --env: %w", err)
}
envToRemove := util.ParseMinusSuffix(envMap)
err = UpdateEnvVars(podSpec, envMap, envToRemove)
envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(p.EnvValueFrom, "=")
if err != nil {
return fmt.Errorf("Invalid --env-value-from: %w", err)
}
err = UpdateEnvVars(podSpec, allArgs, envToUpdate, envToRemove, envValueFromToUpdate, envValueFromToRemove)
if err != nil {
return err
}

View File

@ -16,7 +16,6 @@ package flags
import (
"fmt"
"sort"
"strconv"
"strings"
@ -53,20 +52,60 @@ 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, then sort by env key name.
func UpdateEnvVars(spec *corev1.PodSpec, toUpdate map[string]string, toRemove []string) error {
// 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 {
container := containerOfPodSpec(spec)
updated := updateEnvVarsFromMap(container.Env, toUpdate)
updated = removeEnvVars(updated, toRemove)
// Sort by env key name
sort.SliceStable(updated, func(i, j int) bool {
return updated[i].Name < updated[j].Name
})
allEnvsToUpdate := util.NewOrderedMap()
envIterator := envToUpdate.Iterator()
envValueFromIterator := envValueFromToUpdate.Iterator()
envKey, envValue, envExists := envIterator.NextString()
envValueFromKey, envValueFromValue, envValueFromExists := envValueFromIterator.NextString()
for _, arg := range allArgs {
// envs are stored as NAME=value
if envExists && isValidEnvArg(arg, envKey, envValue) {
allEnvsToUpdate.Set(envKey, corev1.EnvVar{
Name: envKey,
Value: envValue,
})
envKey, envValue, envExists = envIterator.NextString()
} else if envValueFromExists && isValidEnvValueFromArg(arg, envValueFromKey, envValueFromValue) {
// envs are stored as NAME=secret:sercretName:key or NAME=config-map:cmName:key
envVarSource, err := createEnvVarSource(envValueFromValue)
if err != nil {
return err
}
allEnvsToUpdate.Set(envValueFromKey, corev1.EnvVar{
Name: envValueFromKey,
ValueFrom: envVarSource,
})
envValueFromKey, envValueFromValue, envValueFromExists = envValueFromIterator.NextString()
}
}
updated := updateEnvVarsFromMap(container.Env, allEnvsToUpdate)
updated = removeEnvVars(updated, append(envToRemove, envValueFromToRemove...))
container.Env = updated
return nil
}
// isValidEnvArg checks that the input arg is a valid argument for specifying env value,
// ie. stored as NAME=value
func isValidEnvArg(arg, envKey, envValue string) bool {
return strings.HasPrefix(arg, envKey+"="+envValue) || strings.HasPrefix(arg, "-e="+envKey+"="+envValue) || strings.HasPrefix(arg, "--env="+envKey+"="+envValue)
}
// isValidEnvValueFromArg 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 isValidEnvValueFromArg(arg, envValueFromKey, envValueFromValue string) bool {
return strings.HasPrefix(arg, envValueFromKey+"="+envValueFromValue) || strings.HasPrefix(arg, "--env-value-from="+envValueFromKey+"="+envValueFromValue)
}
// UpdateEnvFrom updates envFrom
func UpdateEnvFrom(spec *corev1.PodSpec, toUpdate []string, toRemove []string) error {
container := containerOfPodSpec(spec)
@ -217,18 +256,20 @@ func UpdateImagePullSecrets(spec *corev1.PodSpec, pullsecrets string) {
}
// =======================================================================================
func updateEnvVarsFromMap(env []corev1.EnvVar, toUpdate map[string]string) []corev1.EnvVar {
set := sets.NewString()
func updateEnvVarsFromMap(env []corev1.EnvVar, toUpdate *util.OrderedMap) []corev1.EnvVar {
updated := sets.NewString()
for i := range env {
envVar := &env[i]
if val, ok := toUpdate[envVar.Name]; ok {
envVar.Value = val
set.Insert(envVar.Name)
object, present := toUpdate.Get(env[i].Name)
if present {
env[i] = object.(corev1.EnvVar)
updated.Insert(env[i].Name)
}
}
for name, val := range toUpdate {
if !set.Has(name) {
env = append(env, corev1.EnvVar{Name: name, Value: val})
it := toUpdate.Iterator()
for name, envVar, ok := it.Next(); ok; name, envVar, ok = it.Next() {
if !updated.Has(name) {
env = append(env, envVar.(corev1.EnvVar))
}
}
return env
@ -246,6 +287,50 @@ func removeEnvVars(env []corev1.EnvVar, toRemove []string) []corev1.EnvVar {
return env
}
func createEnvVarSource(spec string) (*corev1.EnvVarSource, error) {
slices := strings.SplitN(spec, ":", 3)
if len(slices) != 3 {
return nil, fmt.Errorf("argument requires a value in form \"resourceType:name:key\" where \"resourceType\" can be one of \"config-map\" (\"cm\") or \"secret\" (\"sc\"); got %q", spec)
}
typeString := strings.TrimSpace(slices[0])
sourceName := strings.TrimSpace(slices[1])
sourceKey := strings.TrimSpace(slices[2])
var sourceType string
envVarSource := corev1.EnvVarSource{}
switch typeString {
case "config-map", "cm":
sourceType = "ConfigMap"
envVarSource.ConfigMapKeyRef = &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: sourceName,
},
Key: sourceKey}
case "secret", "sc":
sourceType = "Secret"
envVarSource.SecretKeyRef = &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: sourceName,
},
Key: sourceKey}
default:
return nil, fmt.Errorf("unsupported env source type \"%q\"; supported source types are \"config-map\" (\"cm\") and \"secret\" (\"sc\")", slices[0])
}
if len(sourceName) == 0 {
return nil, fmt.Errorf("the name of %s cannot be an empty string", sourceType)
}
if len(sourceKey) == 0 {
return nil, fmt.Errorf("the key referenced by resource %s \"%s\" cannot be an empty string", sourceType, sourceName)
}
return &envVarSource, nil
}
// =======================================================================================
func updateEnvFrom(envFromSources []corev1.EnvFromSource, toUpdate []string) ([]corev1.EnvFromSource, error) {
existingNameSet := make(map[string]bool)

View File

@ -35,15 +35,169 @@ func getPodSpec() (*corev1.PodSpec, *corev1.Container) {
func TestUpdateEnvVarsNew(t *testing.T) {
spec, _ := getPodSpec()
env := []corev1.EnvVar{
expected := []corev1.EnvVar{
{Name: "a", Value: "foo"},
{Name: "b", Value: "bar"},
}
found, err := util.EnvToMap(env)
argsEnv := []string{
"a=foo",
"b=bar",
}
envToUpdate, envToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnv, "=")
assert.NilError(t, err)
err = UpdateEnvVars(spec, found, []string{})
args := append([]string{"command"}, argsEnv...)
err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, util.NewOrderedMap(), []string{})
assert.NilError(t, err)
assert.DeepEqual(t, env, spec.Containers[0].Env)
assert.DeepEqual(t, expected, spec.Containers[0].Env)
}
func TestUpdateEnvVarsMixedEnvOrder(t *testing.T) {
spec, _ := getPodSpec()
expected := []corev1.EnvVar{
{Name: "z", Value: "foo"},
{Name: "a", Value: "bar"},
{Name: "x", Value: "baz"},
}
argsEnv := []string{
"z=foo",
"a=bar",
"x=baz",
}
envToUpdate, envToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnv, "=")
assert.NilError(t, err)
args := append([]string{"command"}, argsEnv...)
err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, util.NewOrderedMap(), []string{})
assert.NilError(t, err)
assert.DeepEqual(t, expected, spec.Containers[0].Env)
}
func TestUpdateEnvVarsValueFromNew(t *testing.T) {
spec, _ := getPodSpec()
expected := []corev1.EnvVar{
{Name: "a", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "foo",
},
Key: "key",
},
}},
{Name: "b", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "bar",
},
Key: "key2",
},
}},
{Name: "c", ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "baz",
},
Key: "key3",
},
}},
{Name: "d", ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "goo",
},
Key: "key4",
},
}},
}
argsEnvValueFrom := []string{
"a=secret:foo:key",
"b=sc:bar:key2",
"c=config-map:baz:key3",
"d=cm:goo:key4",
}
args := append([]string{"command"}, argsEnvValueFrom...)
envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnvValueFrom, "=")
assert.NilError(t, err)
err = UpdateEnvVars(spec, args, util.NewOrderedMap(), []string{}, envValueFromToUpdate, envValueFromToRemove)
assert.NilError(t, err)
assert.DeepEqual(t, expected, spec.Containers[0].Env)
}
func TestUpdateEnvVarsAllNew(t *testing.T) {
spec, _ := getPodSpec()
expected := []corev1.EnvVar{
{Name: "a", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "foo",
},
Key: "key",
},
}},
{Name: "b", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "bar",
},
Key: "key2",
},
}},
{Name: "c", Value: "baz"},
{Name: "d", Value: "goo"},
}
argsEnvValueFrom := []string{
"a=secret:foo:key",
"b=sc:bar:key2",
}
argsEnv := []string{
"c=baz",
"d=goo",
}
args := append([]string{"command"}, append(argsEnvValueFrom, argsEnv...)...)
envToUpdate, envToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnv, "=")
assert.NilError(t, err)
envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnvValueFrom, "=")
assert.NilError(t, err)
err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, envValueFromToUpdate, envValueFromToRemove)
assert.NilError(t, err)
assert.DeepEqual(t, expected, spec.Containers[0].Env)
}
func TestUpdateEnvVarsValueFromValidate(t *testing.T) {
spec, _ := getPodSpec()
envValueFromWrongInput := [][]string{
{"foo=foo"},
{"foo=bar:"},
{"foo=foo:bar"},
{"foo=foo:bar:"},
{"foo=foo:bar:baz"},
{"foo=secret"},
{"foo=sec"},
{"foo=secret:"},
{"foo=sec:"},
{"foo=secret:name"},
{"foo=sec:name"},
{"foo=secret:name"},
{"foo=sec:name:"},
{"foo=config-map"},
{"foo=cm"},
{"foo=config-map:"},
{"foo=cm:"},
{"foo=config-map:name"},
{"foo=cm:name"},
{"foo=config-map:name:"},
{"foo=cm:name:"},
}
for _, input := range envValueFromWrongInput {
args := append([]string{"command"}, input...)
envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(input, "=")
assert.NilError(t, err)
err = UpdateEnvVars(spec, args, util.NewOrderedMap(), []string{}, envValueFromToUpdate, envValueFromToRemove)
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)
}
}
func TestUpdateEnvFrom(t *testing.T) {
@ -313,19 +467,94 @@ func TestUpdateEnvVarsModify(t *testing.T) {
spec, container := getPodSpec()
container.Env = []corev1.EnvVar{
{Name: "a", Value: "foo"}}
env := map[string]string{
"a": "fancy",
}
err := UpdateEnvVars(spec, env, []string{})
assert.NilError(t, err)
expected := map[string]string{
"a": "fancy",
expected := []corev1.EnvVar{
{Name: "a", Value: "bar"},
}
argsEnv := []string{
"a=bar",
}
args := append([]string{"command"}, argsEnv...)
envToUpdate, envToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnv, "=")
assert.NilError(t, err)
err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, util.NewOrderedMap(), []string{})
assert.NilError(t, err)
assert.DeepEqual(t, expected, container.Env)
}
func TestUpdateEnvVarsValueFromModify(t *testing.T) {
spec, container := getPodSpec()
container.Env = []corev1.EnvVar{
{Name: "a", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "foo",
},
Key: "key",
},
}},
}
found, err := util.EnvToMap(container.Env)
expected := []corev1.EnvVar{
{Name: "a", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "bar",
},
Key: "key2",
},
}},
}
argsEnvValueFrom := []string{
"a=secret:bar:key2",
}
args := append([]string{"command"}, argsEnvValueFrom...)
envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnvValueFrom, "=")
assert.NilError(t, err)
assert.DeepEqual(t, expected, found)
err = UpdateEnvVars(spec, args, util.NewOrderedMap(), []string{}, envValueFromToUpdate, envValueFromToRemove)
assert.NilError(t, err)
assert.DeepEqual(t, expected, container.Env)
}
func TestUpdateEnvVarsAllModify(t *testing.T) {
spec, container := getPodSpec()
container.Env = []corev1.EnvVar{
{Name: "a", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "foo",
},
Key: "key",
},
}},
{Name: "b", Value: "bar"},
}
expected := []corev1.EnvVar{
{Name: "a", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "bar",
},
Key: "key2",
},
}},
{Name: "b", Value: "goo"},
}
argsEnvValueFrom := []string{
"a=secret:bar:key2",
}
argsEnv := []string{
"b=goo",
}
args := append([]string{"command"}, append(argsEnvValueFrom, argsEnv...)...)
envToUpdate, envToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnv, "=")
assert.NilError(t, err)
envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnvValueFrom, "=")
assert.NilError(t, err)
err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, envValueFromToUpdate, envValueFromToRemove)
assert.NilError(t, err)
assert.DeepEqual(t, expected, container.Env)
}
func TestUpdateEnvVarsRemove(t *testing.T) {
@ -334,8 +563,11 @@ func TestUpdateEnvVarsRemove(t *testing.T) {
{Name: "a", Value: "foo"},
{Name: "b", Value: "bar"},
}
remove := []string{"b"}
err := UpdateEnvVars(spec, map[string]string{}, remove)
remove := []string{"b-"}
args := append([]string{"command"}, remove...)
envToUpdate, envToRemove, err := util.OrderedMapAndRemovalListFromArray(remove, "=")
assert.NilError(t, err)
err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, util.NewOrderedMap(), []string{})
assert.NilError(t, err)
expected := []corev1.EnvVar{
@ -344,3 +576,190 @@ func TestUpdateEnvVarsRemove(t *testing.T) {
assert.DeepEqual(t, expected, container.Env)
}
func TestUpdateEnvVarsValueFromRemove(t *testing.T) {
spec, container := getPodSpec()
container.Env = []corev1.EnvVar{
{Name: "a", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "foo",
},
Key: "key",
},
}},
{Name: "b", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "bar",
},
Key: "key2",
},
}},
}
remove := []string{"b-"}
args := append([]string{"command"}, remove...)
envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(remove, "=")
assert.NilError(t, err)
err = UpdateEnvVars(spec, args, util.NewOrderedMap(), []string{}, envValueFromToUpdate, envValueFromToRemove)
assert.NilError(t, err)
expected := []corev1.EnvVar{
{Name: "a", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "foo",
},
Key: "key",
},
}},
}
assert.DeepEqual(t, expected, container.Env)
}
func TestUpdateEnvVarsAllRemove(t *testing.T) {
spec, container := getPodSpec()
container.Env = []corev1.EnvVar{
{Name: "a", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "foo",
},
Key: "key",
},
}},
{Name: "b", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "bar",
},
Key: "key2",
},
}},
{Name: "c", Value: "baz"},
{Name: "d", Value: "goo"},
}
argsEnvValueFrom := []string{
"a=secret:foo:key",
"b-",
}
argsEnv := []string{
"c=baz",
"d-",
}
args := append([]string{"command"}, append(argsEnvValueFrom, argsEnv...)...)
envToUpdate, envToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnv, "=")
assert.NilError(t, err)
envValueFromToUpdate, envValueFromToRemove, err := util.OrderedMapAndRemovalListFromArray(argsEnvValueFrom, "=")
assert.NilError(t, err)
err = UpdateEnvVars(spec, args, envToUpdate, envToRemove, envValueFromToUpdate, envValueFromToRemove)
assert.NilError(t, err)
expected := []corev1.EnvVar{
{Name: "a", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "foo",
},
Key: "key",
},
}},
{Name: "c", Value: "baz"},
}
assert.DeepEqual(t, expected, container.Env)
}
func Test_isValidEnvArg(t *testing.T) {
for _, tc := range []struct {
name string
arg string
envKey string
envValue string
isValid bool
}{{
name: "valid env arg specified",
arg: "FOO=bar",
envKey: "FOO",
envValue: "bar",
isValid: true,
}, {
name: "invalid env arg specified",
arg: "FOObar",
envKey: "FOO",
envValue: "bar",
isValid: false,
}, {
name: "valid env arg specified: -e",
arg: "-e=FOO=bar",
envKey: "FOO",
envValue: "bar",
isValid: true,
}, {
name: "invalid env arg specified: -e",
arg: "-e=FOObar",
envKey: "FOO",
envValue: "bar",
isValid: false,
}, {
name: "valid env arg specified: --env",
arg: "--env=FOO=bar",
envKey: "FOO",
envValue: "bar",
isValid: true,
}, {
name: "invalid env arg specified: --env",
arg: "--env=FOObar",
envKey: "FOO",
envValue: "bar",
isValid: false,
},
} {
t.Run(tc.name, func(t *testing.T) {
result := isValidEnvArg(tc.arg, tc.envKey, tc.envValue)
assert.Equal(t, result, tc.isValid)
})
}
}
func Test_isValidEnvValueFromArg(t *testing.T) {
for _, tc := range []struct {
name string
arg string
envValueFromKey string
envValueFromValue string
isValid bool
}{{
name: "valid env value from arg specified",
arg: "FOO=secret:sercretName:key",
envValueFromKey: "FOO",
envValueFromValue: "secret:sercretName:key",
isValid: true,
}, {
name: "invalid env value from arg specified",
arg: "FOOsecret:sercretName:key",
envValueFromKey: "FOO",
envValueFromValue: "secret:sercretName:key",
isValid: false,
}, {
name: "valid env value from arg specified: --env-value-from",
arg: "--env-value-from=FOO=secret:sercretName:key",
envValueFromKey: "FOO",
envValueFromValue: "secret:sercretName:key",
isValid: true,
}, {
name: "invalid env value from arg specified: --env-value-from",
arg: "--env-value-from=FOOsecret:sercretName:key",
envValueFromKey: "FOO",
envValueFromValue: "secret:sercretName:key",
isValid: false,
},
} {
t.Run(tc.name, func(t *testing.T) {
result := isValidEnvValueFromArg(tc.arg, tc.envValueFromKey, tc.envValueFromValue)
assert.Equal(t, result, tc.isValid)
})
}
}

View File

@ -29,12 +29,13 @@ import (
func TestPodSpecFlags(t *testing.T) {
args := []string{"--image", "repo/user/imageID:tag", "--env", "b=c"}
wantedPod := &PodSpecFlags{
Image: "repo/user/imageID:tag",
Env: []string{"b=c"},
EnvFrom: []string{},
Mount: []string{},
Volume: []string{},
Arg: []string{},
Image: "repo/user/imageID:tag",
Env: []string{"b=c"},
EnvFrom: []string{},
EnvValueFrom: []string{},
Mount: []string{},
Volume: []string{},
Arg: []string{},
}
flags := &PodSpecFlags{}
testCmd := &cobra.Command{
@ -56,7 +57,7 @@ func TestUniqueStringArg(t *testing.T) {
}
func TestPodSpecResolve(t *testing.T) {
args := []string{"--image", "repo/user/imageID:tag", "--env", "b=c",
inputArgs := []string{"--image", "repo/user/imageID:tag", "--env", "b=c",
"--port", "8080", "--limit", "cpu=1000m", "--limit", "memory=1024Mi",
"--cmd", "/app/start", "--arg", "myArg1", "--service-account", "foo-bar-account",
"--mount", "/mount/path=volume-name", "--volume", "volume-name=cm:config-map-name",
@ -114,12 +115,12 @@ func TestPodSpecResolve(t *testing.T) {
Use: "test",
Run: func(cmd *cobra.Command, args []string) {
podSpec := &corev1.PodSpec{Containers: []corev1.Container{{}}}
err := flags.ResolvePodSpec(podSpec, cmd.Flags())
err := flags.ResolvePodSpec(podSpec, cmd.Flags(), inputArgs)
assert.NilError(t, err, "PodSpec cannot be resolved.")
assert.DeepEqual(t, expectedPodSpec, *podSpec)
},
}
testCmd.SetArgs(args)
testCmd.SetArgs(inputArgs)
flags.AddFlags(testCmd.Flags())
testCmd.Execute()
}
@ -127,18 +128,18 @@ func TestPodSpecResolve(t *testing.T) {
func TestPodSpecResolveReturnError(t *testing.T) {
outBuf := bytes.Buffer{}
flags := &PodSpecFlags{}
inputArgs := []string{"--mount", "123456"}
testCmd := &cobra.Command{
Use: "test",
Run: func(cmd *cobra.Command, args []string) {
podSpec := &corev1.PodSpec{Containers: []corev1.Container{{}}}
err := flags.ResolvePodSpec(podSpec, cmd.Flags())
err := flags.ResolvePodSpec(podSpec, cmd.Flags(), inputArgs)
fmt.Fprint(cmd.OutOrStdout(), "Return error: ", err)
},
}
testCmd.SetOut(&outBuf)
args := []string{"--mount", "123456"}
testCmd.SetArgs(args)
testCmd.SetArgs(inputArgs)
flags.AddFlags(testCmd.Flags())
testCmd.Execute()
out := outBuf.String()

View File

@ -28,6 +28,7 @@ import (
"knative.dev/serving/pkg/apis/autoscaling"
"gotest.tools/v3/assert"
corev1 "k8s.io/api/core/v1"
"knative.dev/client/lib/test"
"knative.dev/client/pkg/util"
@ -43,11 +44,12 @@ func TestServiceOptions(t *testing.T) {
assert.NilError(t, it.Teardown())
}()
kubectl := test.NewKubectl(it.Namespace())
r := test.NewKnRunResultCollector(t, it)
t.Log("create and validate service with concurrency options")
defer r.DumpIfFailed()
t.Log("create and validate service with concurrency options")
serviceCreateWithOptions(r, "svc1", "--concurrency-limit", "250", "--concurrency-target", "300", "--concurrency-utilization", "50")
validateServiceConcurrencyTarget(r, "svc1", "300")
validateServiceConcurrencyLimit(r, "svc1", "250")
@ -178,6 +180,55 @@ func TestServiceOptions(t *testing.T) {
serviceCreateWithOptions(r, "svc14", "--annotation-revision", "rev=helloworld-rev")
validateServiceAndRevisionAnnotations(r, "svc14", nil, map[string]string{"rev": "helloworld-rev"})
test.ServiceDelete(r, "svc14")
t.Log("create and validate service env vars")
env := []corev1.EnvVar{
{Name: "EXAMPLE", Value: "foo"},
{Name: "EXAMPLE2", Value: "bar"},
}
serviceCreateWithOptions(r, "svc15", "--env", "EXAMPLE=foo", "--env", "EXAMPLE2=bar")
validateServiceEnvVariables(r, "svc15", env)
test.ServiceDelete(r, "svc15")
t.Log("create and validate service env-value-from vars")
_, err = kubectl.Run("create", "-n", it.Namespace(), "configmap", "test-cm", "--from-literal=key=value")
assert.NilError(t, err)
env2 := []corev1.EnvVar{
{Name: "EXAMPLE", ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "test-cm",
},
Key: "key",
},
}},
}
serviceCreateWithOptions(r, "svc16", "--env-value-from", "EXAMPLE=cm:test-cm:key")
validateServiceEnvVariables(r, "svc16", env2)
test.ServiceDelete(r, "svc16")
_, err = kubectl.Run("delete", "-n", it.Namespace(), "configmap", "test-cm")
assert.NilError(t, err)
t.Log("create and validate service env vars and env-value-from vars")
_, err = kubectl.Run("create", "-n", it.Namespace(), "configmap", "test-cm2", "--from-literal=key=value")
assert.NilError(t, err)
env3 := []corev1.EnvVar{
{Name: "EXAMPLE", Value: "foo"},
{Name: "EXAMPLE2", ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "test-cm2",
},
Key: "key",
},
}},
{Name: "EXAMPLE3", Value: "bar"},
}
serviceCreateWithOptions(r, "svc17", "--env", "EXAMPLE=foo", "--env-value-from", "EXAMPLE2=config-map:test-cm2:key", "--env", "EXAMPLE3=bar")
validateServiceEnvVariables(r, "svc17", env3)
test.ServiceDelete(r, "svc17")
_, err = kubectl.Run("delete", "-n", it.Namespace(), "configmap", "test-cm2")
assert.NilError(t, err)
}
func serviceCreateWithOptions(r *test.KnRunResultCollector, serviceName string, options ...string) {
@ -315,3 +366,19 @@ func validatePort(r *test.KnRunResultCollector, serviceName string, portNumber i
assert.Equal(r.T(), svc.Spec.Template.Spec.Containers[0].Ports[0].ContainerPort, portNumber)
assert.Equal(r.T(), svc.Spec.Template.Spec.Containers[0].Ports[0].Name, portName)
}
func validateServiceEnvVariables(r *test.KnRunResultCollector, serviceName string, envVar []corev1.EnvVar) {
svc := test.GetServiceFromKNServiceDescribe(r, serviceName)
for i, env := range svc.Spec.Template.Spec.Containers[0].Env {
assert.Equal(r.T(), env.Name, envVar[i].Name)
if envVar[i].ValueFrom != nil {
if envVar[i].ValueFrom.ConfigMapKeyRef != nil {
assert.Equal(r.T(), env.ValueFrom.ConfigMapKeyRef.Key, envVar[i].ValueFrom.ConfigMapKeyRef.Key)
} else if envVar[i].ValueFrom.SecretKeyRef != nil {
assert.Equal(r.T(), env.ValueFrom.SecretKeyRef.Key, envVar[i].ValueFrom.SecretKeyRef.Key)
}
} else {
assert.Equal(r.T(), env.Value, envVar[i].Value)
}
}
}