diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 5588b6734d..7e1c617e4e 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -12,7 +12,43 @@ | https://github.com/knative/client/pull/[#] //// -## v0.13.1 (unreleased) +## v0.13.2 (2020-04-15) + +[cols="1,10,3", options="header", width="100%"] +|=== +| | Description | PR + +| 🐛 +| Make wait, no-wait and async flags per bool var CLI convention +| https://github.com/knative/client/pull/802[#802] + +| 🐛 +| Fix resource listing with -oname flag +| https://github.com/knative/client/pull/775[#775] + +| 🐛 +| Fix showing repetitive revisions in service describe +| https://github.com/knative/client/pull/790[#790] + +| 🎁 +| Add concurrency-utilization option for service `create` and `update` +| https://github.com/knative/client/pull/788[#788] + +| 🐣 +| Refactor `e2e` common code into `lib/test` +| https://github.com/knative/client/pull/765[#765] + +| 🐛 +| Fix plugin lookup with file ext on Windows +| https://github.com/knative/client/pull/774[#774] + +| 🐛 +| Correct error message when updating service +| https://github.com/knative/client/pull/778[#778] + +|=== + +## v0.13.1 (2020-03-25) [cols="1,10,3", options="header", width="100%"] |=== @@ -33,6 +69,7 @@ | 🐛 | Fix default config path on Windows | https://github.com/knative/client/pull/751[#751] +|=== ## v0.13.0 (2020-03-11) diff --git a/docs/cmd/kn_revision_delete.md b/docs/cmd/kn_revision_delete.md index 5d6f4e2c41..f12e129ef4 100644 --- a/docs/cmd/kn_revision_delete.md +++ b/docs/cmd/kn_revision_delete.md @@ -21,10 +21,11 @@ kn revision delete NAME [flags] ### Options ``` - --async DEPRECATED: please use --no-wait instead. Delete revision and don't wait for it to be deleted. + --async DEPRECATED: please use --no-wait instead. Do not wait for 'revision delete' operation to be completed. (default true) -h, --help help for delete -n, --namespace string Specify the namespace to operate in. - --no-wait Delete revision and don't wait for it to be deleted. + --no-wait Do not wait for 'revision delete' operation to be completed. (default true) + --wait Wait for 'revision delete' operation to be completed. --wait-timeout int Seconds to wait before giving up on waiting for revision to be deleted. (default 600) ``` diff --git a/docs/cmd/kn_service.md b/docs/cmd/kn_service.md index ca289a08cd..bd4794f9fd 100644 --- a/docs/cmd/kn_service.md +++ b/docs/cmd/kn_service.md @@ -30,7 +30,7 @@ kn service [flags] * [kn service create](kn_service_create.md) - Create a service. * [kn service delete](kn_service_delete.md) - Delete a service. * [kn service describe](kn_service_describe.md) - Show details of a service -* [kn service export](kn_service_export.md) - export a service +* [kn service export](kn_service_export.md) - Export a service. * [kn service list](kn_service_list.md) - List available services. * [kn service update](kn_service_update.md) - Update a service. diff --git a/docs/cmd/kn_service_create.md b/docs/cmd/kn_service_create.md index 834e94f1a9..3ac1b64840 100644 --- a/docs/cmd/kn_service_create.md +++ b/docs/cmd/kn_service_create.md @@ -45,41 +45,43 @@ kn service create NAME --image IMAGE [flags] ### Options ``` - --annotation stringArray Service annotation to set. name=value; you may provide this flag any number of times to set multiple annotations. To unset, specify the annotation name followed by a "-" (e.g., name-). - --arg stringArray Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times. - --async DEPRECATED: please use --no-wait instead. Create service and don't wait for it to be ready. - --autoscale-window string Duration to look back for making auto-scaling decisions. The service is scaled to zero if no request was received in during that time. (eg: 10s) - --cluster-local Specify that the service be private. (--no-cluster-local will make the service publicly available) - --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 aditional arguments. - --concurrency-limit int Hard Limit of concurrent requests to be processed by a single replica. - --concurrency-target int Recommendation for when to scale up based on the concurrent number of incoming request. Defaults to --concurrency-limit when given. - -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-. - --force Create service forcefully, replaces existing service if any. - -h, --help help for create - --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-). - --label-revision stringArray Revision label to set. 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-). This flag takes precedence over "label" flag. - --label-service stringArray Service label to set. 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-). This flag takes precedence over "label" flag. - --limits-cpu string The limits on the requested CPU (e.g., 1000m). - --limits-memory string The limits on the requested memory (e.g., 1024Mi). - --lock-to-digest Keep the running image for the service constant when not explicitly specifying the image. (--no-lock-to-digest pulls the image tag afresh with each new revision) (default true) - --max-scale int Maximal number of replicas. - --min-scale int Minimal number of replicas. - --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. - --no-cluster-local Do not specify that the service be private. (--no-cluster-local will make the service publicly available) (default true) - --no-lock-to-digest Do not keep the running image for the service constant when not explicitly specifying the image. (--no-lock-to-digest pulls the image tag afresh with each new revision) - --no-wait Create service and don't wait for it to be ready. - -p, --port int32 The port where application listens on. - --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. - --requests-cpu string The requested CPU (e.g., 250m). - --requests-memory string The requested memory (e.g., 64Mi). - --revision-name string The revision name to set. Must start with the service name and a dash as a prefix. Empty revision name will result in the server generating a name for the revision. Accepts golang templates, allowing {{.Service}} for the service name, {{.Generation}} for the generation, and {{.Random [n]}} for n random consonants. (default "{{.Service}}-{{.Random 5}}-{{.Generation}}") - --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. - --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-. - --wait-timeout int Seconds to wait before giving up on waiting for service to be ready. (default 600) + --annotation stringArray Service annotation to set. name=value; you may provide this flag any number of times to set multiple annotations. To unset, specify the annotation name followed by a "-" (e.g., name-). + --arg stringArray Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times. + --async DEPRECATED: please use --no-wait instead. Do not wait for 'service create' operation to be completed. + --autoscale-window string Duration to look back for making auto-scaling decisions. The service is scaled to zero if no request was received in during that time. (eg: 10s) + --cluster-local Specify that the service be private. (--no-cluster-local will make the service publicly available) + --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 aditional arguments. + --concurrency-limit int Hard Limit of concurrent requests to be processed by a single replica. + --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-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-. + --force Create service forcefully, replaces existing service if any. + -h, --help help for create + --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-). + --label-revision stringArray Revision label to set. 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-). This flag takes precedence over "label" flag. + --label-service stringArray Service label to set. 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-). This flag takes precedence over "label" flag. + --limits-cpu string The limits on the requested CPU (e.g., 1000m). + --limits-memory string The limits on the requested memory (e.g., 1024Mi). + --lock-to-digest Keep the running image for the service constant when not explicitly specifying the image. (--no-lock-to-digest pulls the image tag afresh with each new revision) (default true) + --max-scale int Maximal number of replicas. + --min-scale int Minimal number of replicas. + --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. + --no-cluster-local Do not specify that the service be private. (--no-cluster-local will make the service publicly available) (default true) + --no-lock-to-digest Do not keep the running image for the service constant when not explicitly specifying the image. (--no-lock-to-digest pulls the image tag afresh with each new revision) + --no-wait Do not wait for 'service create' operation to be completed. + -p, --port int32 The port where application listens on. + --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. + --requests-cpu string The requested CPU (e.g., 250m). + --requests-memory string The requested memory (e.g., 64Mi). + --revision-name string The revision name to set. Must start with the service name and a dash as a prefix. Empty revision name will result in the server generating a name for the revision. Accepts golang templates, allowing {{.Service}} for the service name, {{.Generation}} for the generation, and {{.Random [n]}} for n random consonants. (default "{{.Service}}-{{.Random 5}}-{{.Generation}}") + --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. + --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-. + --wait Wait for 'service create' operation to be completed. (default true) + --wait-timeout int Seconds to wait before giving up on waiting for service to be ready. (default 600) ``` ### Options inherited from parent commands diff --git a/docs/cmd/kn_service_delete.md b/docs/cmd/kn_service_delete.md index 971fd9314b..394463a53b 100644 --- a/docs/cmd/kn_service_delete.md +++ b/docs/cmd/kn_service_delete.md @@ -24,10 +24,11 @@ kn service delete NAME [flags] ### Options ``` - --async DEPRECATED: please use --no-wait instead. Delete service and don't wait for it to be deleted. + --async DEPRECATED: please use --no-wait instead. Do not wait for 'service delete' operation to be completed. (default true) -h, --help help for delete -n, --namespace string Specify the namespace to operate in. - --no-wait Delete service and don't wait for it to be deleted. + --no-wait Do not wait for 'service delete' operation to be completed. (default true) + --wait Wait for 'service delete' operation to be completed. --wait-timeout int Seconds to wait before giving up on waiting for service to be deleted. (default 600) ``` diff --git a/docs/cmd/kn_service_export.md b/docs/cmd/kn_service_export.md index 5c2b726699..b106af535d 100644 --- a/docs/cmd/kn_service_export.md +++ b/docs/cmd/kn_service_export.md @@ -1,10 +1,10 @@ ## kn service export -export a service +Export a service. ### Synopsis -export a service +Export a service. ``` kn service export NAME [flags] diff --git a/docs/cmd/kn_service_update.md b/docs/cmd/kn_service_update.md index b6afaa9107..425654fc82 100644 --- a/docs/cmd/kn_service_update.md +++ b/docs/cmd/kn_service_update.md @@ -38,43 +38,45 @@ kn service update NAME [flags] ### Options ``` - --annotation stringArray Service annotation to set. name=value; you may provide this flag any number of times to set multiple annotations. To unset, specify the annotation name followed by a "-" (e.g., name-). - --arg stringArray Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times. - --async DEPRECATED: please use --no-wait instead. Update service and don't wait for it to be ready. - --autoscale-window string Duration to look back for making auto-scaling decisions. The service is scaled to zero if no request was received in during that time. (eg: 10s) - --cluster-local Specify that the service be private. (--no-cluster-local will make the service publicly available) - --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 aditional arguments. - --concurrency-limit int Hard Limit of concurrent requests to be processed by a single replica. - --concurrency-target int Recommendation for when to scale up based on the concurrent number of incoming request. Defaults to --concurrency-limit when given. - -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. - -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-). - --label-revision stringArray Revision label to set. 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-). This flag takes precedence over "label" flag. - --label-service stringArray Service label to set. 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-). This flag takes precedence over "label" flag. - --limits-cpu string The limits on the requested CPU (e.g., 1000m). - --limits-memory string The limits on the requested memory (e.g., 1024Mi). - --lock-to-digest Keep the running image for the service constant when not explicitly specifying the image. (--no-lock-to-digest pulls the image tag afresh with each new revision) (default true) - --max-scale int Maximal number of replicas. - --min-scale int Minimal number of replicas. - --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. - --no-cluster-local Do not specify that the service be private. (--no-cluster-local will make the service publicly available) (default true) - --no-lock-to-digest Do not keep the running image for the service constant when not explicitly specifying the image. (--no-lock-to-digest pulls the image tag afresh with each new revision) - --no-wait Update service and don't wait for it to be ready. - -p, --port int32 The port where application listens on. - --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. - --requests-cpu string The requested CPU (e.g., 250m). - --requests-memory string The requested memory (e.g., 64Mi). - --revision-name string The revision name to set. Must start with the service name and a dash as a prefix. Empty revision name will result in the server generating a name for the revision. Accepts golang templates, allowing {{.Service}} for the service name, {{.Generation}} for the generation, and {{.Random [n]}} for n random consonants. (default "{{.Service}}-{{.Random 5}}-{{.Generation}}") - --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. - --tag strings Set tag (format: --tag revisionRef=tagName) where revisionRef can be a revision or '@latest' string representing latest ready revision. This flag can be specified multiple times. - --traffic strings Set traffic distribution (format: --traffic revisionRef=percent) where revisionRef can be a revision or a tag or '@latest' string representing latest ready revision. This flag can be given multiple times with percent summing up to 100%. - --untag strings Untag revision (format: --untag tagName). This flag can be specified multiple times. - --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-. - --wait-timeout int Seconds to wait before giving up on waiting for service to be ready. (default 600) + --annotation stringArray Service annotation to set. name=value; you may provide this flag any number of times to set multiple annotations. To unset, specify the annotation name followed by a "-" (e.g., name-). + --arg stringArray Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times. + --async DEPRECATED: please use --no-wait instead. Do not wait for 'service update' operation to be completed. + --autoscale-window string Duration to look back for making auto-scaling decisions. The service is scaled to zero if no request was received in during that time. (eg: 10s) + --cluster-local Specify that the service be private. (--no-cluster-local will make the service publicly available) + --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 aditional arguments. + --concurrency-limit int Hard Limit of concurrent requests to be processed by a single replica. + --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-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. + -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-). + --label-revision stringArray Revision label to set. 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-). This flag takes precedence over "label" flag. + --label-service stringArray Service label to set. 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-). This flag takes precedence over "label" flag. + --limits-cpu string The limits on the requested CPU (e.g., 1000m). + --limits-memory string The limits on the requested memory (e.g., 1024Mi). + --lock-to-digest Keep the running image for the service constant when not explicitly specifying the image. (--no-lock-to-digest pulls the image tag afresh with each new revision) (default true) + --max-scale int Maximal number of replicas. + --min-scale int Minimal number of replicas. + --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. + --no-cluster-local Do not specify that the service be private. (--no-cluster-local will make the service publicly available) (default true) + --no-lock-to-digest Do not keep the running image for the service constant when not explicitly specifying the image. (--no-lock-to-digest pulls the image tag afresh with each new revision) + --no-wait Do not wait for 'service update' operation to be completed. + -p, --port int32 The port where application listens on. + --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. + --requests-cpu string The requested CPU (e.g., 250m). + --requests-memory string The requested memory (e.g., 64Mi). + --revision-name string The revision name to set. Must start with the service name and a dash as a prefix. Empty revision name will result in the server generating a name for the revision. Accepts golang templates, allowing {{.Service}} for the service name, {{.Generation}} for the generation, and {{.Random [n]}} for n random consonants. (default "{{.Service}}-{{.Random 5}}-{{.Generation}}") + --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. + --tag strings Set tag (format: --tag revisionRef=tagName) where revisionRef can be a revision or '@latest' string representing latest ready revision. This flag can be specified multiple times. + --traffic strings Set traffic distribution (format: --traffic revisionRef=percent) where revisionRef can be a revision or a tag or '@latest' string representing latest ready revision. This flag can be given multiple times with percent summing up to 100%. + --untag strings Untag revision (format: --untag tagName). This flag can be specified multiple times. + --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-. + --wait Wait for 'service update' operation to be completed. (default true) + --wait-timeout int Seconds to wait before giving up on waiting for service to be ready. (default 600) ``` ### Options inherited from parent commands diff --git a/docs/cmd/kn_source_binding_create.md b/docs/cmd/kn_source_binding_create.md index 91fba1d1b5..ed5e029660 100644 --- a/docs/cmd/kn_source_binding_create.md +++ b/docs/cmd/kn_source_binding_create.md @@ -7,7 +7,7 @@ Create a sink binding. Create a sink binding. ``` -kn source binding create NAME --subject SCHEDULE --sink SINK --ce-override KEY=VALUE [flags] +kn source binding create NAME --subject SUBJECT --sink SINK --ce-override KEY=VALUE [flags] ``` ### Examples diff --git a/go.mod b/go.mod index 7941a6655c..c4c1e0b565 100644 --- a/go.mod +++ b/go.mod @@ -14,15 +14,16 @@ require ( github.com/spf13/viper v1.4.0 golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 gomodules.xyz/jsonpatch/v2 v2.1.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gotest.tools v2.2.0+incompatible k8s.io/api v0.17.0 k8s.io/apimachinery v0.17.0 k8s.io/cli-runtime v0.17.0 k8s.io/client-go v0.17.0 - knative.dev/eventing v0.13.1 - knative.dev/pkg v0.0.0-20200304185554-312b1be35ceb - knative.dev/serving v0.13.0 - knative.dev/test-infra v0.0.0-20200229011351-4dac123b9a3d + knative.dev/eventing v0.13.6 + knative.dev/pkg v0.0.0-20200409225505-9147309d5366 + knative.dev/serving v0.13.2 + knative.dev/test-infra v0.0.0-20200409221904-5e04d955cdb9 sigs.k8s.io/yaml v1.1.0 ) diff --git a/go.sum b/go.sum index 8354f028e0..2d847082b8 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= @@ -105,7 +104,6 @@ github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= -github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -145,7 +143,6 @@ github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -162,7 +159,6 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -181,7 +177,6 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.2 h1:DcFegQ7+ECdmkJMfVwWlC+89I4esJ7p8nkGt9ainGDk= github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= @@ -201,7 +196,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.4/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -369,7 +363,6 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.1 h1:8dP3SGL7MPB94crU3bEPplMPe83FI4EouesJUeFHv50= @@ -437,7 +430,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -459,7 +451,6 @@ golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -503,7 +494,6 @@ gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3m gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= -google.golang.org/api v0.4.0 h1:KKgc1aqhV8wDPbDzlDtpvyjZFY3vjz85FP7p4wcQUyI= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -513,14 +503,12 @@ google.golang.org/api v0.10.0 h1:7tmAxx3oKE98VMZ+SBZzvYYWRQ9HODBxmC8mXUsraSQ= google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.2 h1:j8RI1yW0SkI+paT6uGwMlrMI/6zwYA6/CFil8rxOzGI= google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7 h1:ZUjXAXmrAyrmmCPHgCA/vChHcpsX27MZ3yBonD/z1KE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -532,7 +520,6 @@ google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBr google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -541,8 +528,9 @@ google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= @@ -592,14 +580,14 @@ k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKf k8s.io/legacy-cloud-providers v0.17.0/go.mod h1:DdzaepJ3RtRy+e5YhNtrCYwlgyK87j/5+Yfp0L9Syp8= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -knative.dev/eventing v0.13.1 h1:2jnxea7XtqFX8dMFu9cZ7ocMuQV5ZnXX+6tLF6AbYGo= -knative.dev/eventing v0.13.1/go.mod h1:UxweNv8yXhsdHJitcb9R6rmfNaUD2DFi9GWwNRyIs58= -knative.dev/pkg v0.0.0-20200304185554-312b1be35ceb h1:cgpFaVF0jb7z+zJGzq30HMfxMmWwX5WEnkMZ9EHli7I= -knative.dev/pkg v0.0.0-20200304185554-312b1be35ceb/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q= -knative.dev/serving v0.13.0 h1:HkkTVBi6EWoFJcgjXgAVUFdKdxZRulb38dnfnkBYCrQ= -knative.dev/serving v0.13.0/go.mod h1:x2n255JS2XBI39tmjZ8CwTxIf9EKNMCrkVuiOttLRm0= -knative.dev/test-infra v0.0.0-20200229011351-4dac123b9a3d h1:YlscBzPOL3Rfyl8844/wHhJNL5uiHDdyaRn6IEVvv64= -knative.dev/test-infra v0.0.0-20200229011351-4dac123b9a3d/go.mod h1:xcdUkMJrLlBswIZqL5zCuBFOC22WIPMQoVX1L35i0vQ= +knative.dev/eventing v0.13.6 h1:TT2w7qjsMTvYcEfTih7Gzv5Nm0GMLe5Y8dxJG3s1wbM= +knative.dev/eventing v0.13.6/go.mod h1:UxweNv8yXhsdHJitcb9R6rmfNaUD2DFi9GWwNRyIs58= +knative.dev/pkg v0.0.0-20200409225505-9147309d5366 h1:86q+/OWhTkiAXymH8F2NCoWqQLKuX4y8jntTXVq/PDI= +knative.dev/pkg v0.0.0-20200409225505-9147309d5366/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q= +knative.dev/serving v0.13.2 h1:y/XGEVNZeoZ36P7+Xa76ap2r/o9LKXVfxMNiFvxPxTg= +knative.dev/serving v0.13.2/go.mod h1:x2n255JS2XBI39tmjZ8CwTxIf9EKNMCrkVuiOttLRm0= +knative.dev/test-infra v0.0.0-20200409221904-5e04d955cdb9 h1:x6htuzuQ7K4AcVAH/uNRf135X7TPac0eaBFyw6SY2qA= +knative.dev/test-infra v0.0.0-20200409221904-5e04d955cdb9/go.mod h1:xcdUkMJrLlBswIZqL5zCuBFOC22WIPMQoVX1L35i0vQ= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= diff --git a/hack/build.sh b/hack/build.sh index d9dbb311ec..2e230ed75e 100755 --- a/hack/build.sh +++ b/hack/build.sh @@ -16,7 +16,7 @@ set -o pipefail -source_dirs="cmd pkg test" +source_dirs="cmd pkg test lib" # Store for later if [ -z "$1" ]; then diff --git a/test/e2e/cli.go b/lib/test/cli.go similarity index 66% rename from test/e2e/cli.go rename to lib/test/cli.go index 7ad1e838a3..7c5a9adf37 100644 --- a/test/e2e/cli.go +++ b/lib/test/cli.go @@ -12,153 +12,65 @@ // See the License for the specific language governing permissions and // limitations under the License. -package e2e +package test import ( "bytes" "fmt" - "io" "os/exec" "strings" - "testing" "github.com/pkg/errors" ) -type kn struct { - namespace string -} - const ( seperatorHeavy = "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" seperatorLight = "╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍" ) -// Run the 'kn' CLI with args and opts -func (k kn) Run(args ...string) KnRunResult { - return RunKn(k.namespace, args) -} - -// Helper methods for calling out to the test cluster -type kubectl struct { +// Kn type +type Kn struct { namespace string } -// Run the 'kubectl' CLI with args and opts -func (k kubectl) Run(args ...string) (string, error) { - return RunKubectl(k.namespace, args...) -} - -// Collector for results -type KnRunResultCollector struct { - results []KnRunResult - extraDumps []string - t *testing.T -} - -func NewKnRunResultCollector(t *testing.T) *KnRunResultCollector { - return &KnRunResultCollector{ - results: []KnRunResult{}, - t: t, - extraDumps: []string{}, - } -} - -func (c *KnRunResultCollector) AssertNoError(result KnRunResult) { - c.results = append(c.results, result) - if result.Error != nil { - c.t.Logf("ERROR: %v", result.Stderr) - c.t.FailNow() - } +// New Kn object +func NewKn() Kn { + return Kn{} } -func (c *KnRunResultCollector) AssertError(result KnRunResult) { - c.results = append(c.results, result) - if result.Error == nil { - c.t.Log("ERROR: Error expected but no error happened") - c.t.FailNow() - } +// Run the 'kn' CLI with args +func (k Kn) Run(args ...string) KnRunResult { + return RunKn(k.namespace, args) } -// AddDump adds extra dump information to the collector which is printed -// out if an error occurs -func (c *KnRunResultCollector) AddDump(kind string, name string, namespace string) { - dumpInfo := extractDumpInfoWithName(kind, name, namespace) - if dumpInfo != "" { - c.extraDumps = append(c.extraDumps, dumpInfo) - } +// Namespace that this Kn instance uses +func (k Kn) Namespace() string { + return k.namespace } -func (c *KnRunResultCollector) DumpIfFailed() { - if c.t.Failed() { - c.t.Log(c.errorDetails()) - } +// Kubectl type +type Kubectl struct { + namespace string } -func (c *KnRunResultCollector) errorDetails() string { - var out = bytes.Buffer{} - fmt.Fprintln(&out, "=== FAIL: =======================[[ERROR]]========================") - c.printCommands(&out) - var dumpInfos []string - if len(c.results) > 0 { - dumpInfo := c.results[len(c.results)-1].DumpInfo - if dumpInfo != "" { - dumpInfos = append(dumpInfos, dumpInfo) - } - } - dumpInfos = append(dumpInfos, c.extraDumps...) - for _, d := range dumpInfos { - fmt.Fprintln(&out, "--------------------------[[DUMP]]-------------------------------") - fmt.Fprintf(&out, d) +// New Kubectl object +func NewKubectl(namespace string) Kubectl { + return Kubectl{ + namespace: namespace, } - - fmt.Fprintln(&out, "=================================================================") - return out.String() } -func (c *KnRunResultCollector) printCommands(out io.Writer) { - for i, result := range c.results { - c.printCommand(out, result) - if i < len(c.results)-1 { - fmt.Fprintf(out, "┣━%s\n", seperatorHeavy) - } - } +// Run the 'kubectl' CLI with args +func (k Kubectl) Run(args ...string) (string, error) { + return RunKubectl(k.namespace, args...) } -func (c *KnRunResultCollector) printCommand(out io.Writer, result KnRunResult) { - fmt.Fprintf(out, "🦆 %s\n", result.CmdLine) - for _, l := range strings.Split(result.Stdout, "\n") { - fmt.Fprintf(out, "┃ %s\n", l) - } - if result.Stderr != "" { - errorPrefix := "🔥" - if result.ErrorExpected { - errorPrefix = "︙" - } - for _, l := range strings.Split(result.Stderr, "\n") { - fmt.Fprintf(out, "%s %s\n", errorPrefix, l) - } - } +// Namespace that this Kubectl instance uses +func (k Kubectl) Namespace() string { + return k.namespace } -// ======================================================== -// Functions: - -// Result of a "kn" call -type KnRunResult struct { - // Command line called - CmdLine string - // Standard output of command - Stdout string - // Standard error of command - Stderr string - // And extra dump informations in case of an unexpected error - DumpInfo string - // Error occurred during execution - Error error - // Was an error expected ? - ErrorExpected bool -} +// Public functions // RunKn runs "kn" in a given namespace func RunKn(namespace string, args []string) KnRunResult { @@ -195,6 +107,8 @@ func RunKubectl(namespace string, args ...string) (string, error) { return stdout, nil } +// Private + func runCli(cli string, args []string) (string, string, error) { var stderr bytes.Buffer var stdout bytes.Buffer diff --git a/test/e2e/e2e_flags.go b/lib/test/flags.go similarity index 89% rename from test/e2e/e2e_flags.go rename to lib/test/flags.go index e8db5c803f..71fe899895 100644 --- a/test/e2e/e2e_flags.go +++ b/lib/test/flags.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package e2e +package test import ( "flag" @@ -23,14 +23,15 @@ import ( // Flags holds the command line flags or defaults for settings in the user's environment. // See ClientFlags for the list of supported fields. -var Flags = initializeFlags() +var Flags = InitializeFlags() // ClientFlags define the flags that are needed to run the e2e tests. type ClientFlags struct { DockerConfigJSON string } -func initializeFlags() *ClientFlags { +// InitializeFlags initializes the client's flags +func InitializeFlags() *ClientFlags { var f ClientFlags dockerConfigJSON := os.Getenv("DOCKER_CONFIG_JSON") diff --git a/test/e2e/common.go b/lib/test/integration.go similarity index 74% rename from test/e2e/common.go rename to lib/test/integration.go index 2d954fdc09..389b77be61 100644 --- a/test/e2e/common.go +++ b/lib/test/integration.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package e2e +package test import ( "fmt" @@ -38,38 +38,59 @@ var serviceMutex sync.Mutex var serviceCount int var namespaceCount int -type e2eTest struct { +// KnTest type +type KnTest struct { namespace string - kn kn + kn Kn } -func NewE2eTest() (*e2eTest, error) { - ns := nextNamespace() +// NewIntegrationTest creates a new ItegrationTest object +func NewKnTest() (*KnTest, error) { + ns := NextNamespace() - err := createNamespace(ns) + err := CreateNamespace(ns) if err != nil { return nil, err } - err = waitForNamespaceCreated(ns) + err = WaitForNamespaceCreated(ns) if err != nil { return nil, err } - return &e2eTest{ + return &KnTest{ namespace: ns, - kn: kn{ns}, + kn: Kn{ns}, }, nil } -func nextNamespace() string { +// Teardown clean up +func (test *KnTest) Teardown() error { + return DeleteNamespace(test.namespace) +} + +// Teardown clean up +func (test *KnTest) Kn() Kn { + return test.kn +} + +// Namespace used by the test +func (test *KnTest) Namespace() string { + return test.namespace +} + +// Public functions + +// NextNamespace return the next unique namespace +func NextNamespace() string { ns := os.Getenv("KN_E2E_NAMESPACE") if ns == "" { ns = "kne2etests" } - return fmt.Sprintf("%s%d", ns, getNextNamespaceId()) + return fmt.Sprintf("%s%d", ns, GetNextNamespaceId()) } -func getNextNamespaceId() int { +// GetNextNamespaceId return the next unique ID for the next namespace +func GetNextNamespaceId() int { nsMutex.Lock() defer nsMutex.Unlock() current := namespaceCount @@ -77,7 +98,8 @@ func getNextNamespaceId() int { return current } -func getNextServiceName(base string) string { +// GetNextServiceName return the name for the next namespace +func GetNextServiceName(base string) string { serviceMutex.Lock() defer serviceMutex.Unlock() current := serviceCount @@ -85,13 +107,8 @@ func getNextServiceName(base string) string { return base + strconv.Itoa(current) } -// Teardown clean up -func (test *e2eTest) Teardown() error { - return deleteNamespace(test.namespace) -} - -// createNamespace creates and tests a namesspace creation invoking kubectl -func createNamespace(namespace string) error { +// CreateNamespace creates and tests a namesspace creation invoking kubectl +func CreateNamespace(namespace string) error { expectedOutputRegexp := fmt.Sprintf("namespace?.+%s.+created", namespace) out, err := createNamespaceWithRetry(namespace, MaxRetries) if err != nil { @@ -109,9 +126,9 @@ func createNamespace(namespace string) error { return nil } -// createNamespace deletes and tests a namesspace deletion invoking kubectl -func deleteNamespace(namespace string) error { - kubectl := kubectl{namespace} +// DeleteNamespace deletes and tests a namesspace deletion invoking kubectl +func DeleteNamespace(namespace string) error { + kubectl := Kubectl{namespace} out, err := kubectl.Run("delete", "namespace", namespace) if err != nil { return errors.Wrap(err, fmt.Sprintf("Cannot delete namespace %s", namespace)) @@ -129,7 +146,7 @@ func deleteNamespace(namespace string) error { } // WaitForNamespaceDeleted wait until namespace is deleted -func waitForNamespaceDeleted(namespace string) error { +func WaitForNamespaceDeleted(namespace string) error { deleted := checkNamespace(namespace, false, MaxRetries) if !deleted { return fmt.Errorf("error deleting namespace %s, timed out after %d retries", namespace, MaxRetries) @@ -138,7 +155,7 @@ func waitForNamespaceDeleted(namespace string) error { } // WaitForNamespaceCreated wait until namespace is created -func waitForNamespaceCreated(namespace string) error { +func WaitForNamespaceCreated(namespace string) error { created := checkNamespace(namespace, true, MaxRetries) if !created { return fmt.Errorf("error creating namespace %s, timed out after %d retries", namespace, MaxRetries) @@ -146,11 +163,20 @@ func waitForNamespaceCreated(namespace string) error { return nil } +func CurrentDir(t *testing.T) string { + dir, err := os.Getwd() + if err != nil { + t.Fatal("Unable to read current dir:", err) + } + return dir +} + // Private functions + func checkNamespace(namespace string, created bool, maxRetries int) bool { retries := 0 for retries < MaxRetries { - output, _ := kubectl{}.Run("get", "namespace") + output, _ := Kubectl{}.Run("get", "namespace") // check for namespace deleted if !created && !strings.Contains(output, namespace) { @@ -177,7 +203,7 @@ func createNamespaceWithRetry(namespace string, maxRetries int) (string, error) ) for retries < maxRetries { - out, err = kubectl{}.Run("create", "namespace", namespace) + out, err = Kubectl{}.Run("create", "namespace", namespace) if err == nil { return out, nil } @@ -195,11 +221,3 @@ func matchRegexp(matchingRegexp, actual string) (bool, error) { } return matched, nil } - -func currentDir(t *testing.T) string { - dir, err := os.Getwd() - if err != nil { - t.Fatal("Unable to read current dir:", err) - } - return dir -} diff --git a/lib/test/result_collector.go b/lib/test/result_collector.go new file mode 100644 index 0000000000..70f67946c6 --- /dev/null +++ b/lib/test/result_collector.go @@ -0,0 +1,137 @@ +// Copyright 2020 The Knative Authors + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "bytes" + "fmt" + "io" + "strings" + "testing" +) + +// KnRunResult holds command and result artifacts of a "kn" call +type KnRunResult struct { + // Command line called + CmdLine string + // Standard output of command + Stdout string + // Standard error of command + Stderr string + // And extra dump informations in case of an unexpected error + DumpInfo string + // Error occurred during execution + Error error + // Was an error expected ? + ErrorExpected bool +} + +// KnRunResultCollector collects Kn run's results +type KnRunResultCollector struct { + results []KnRunResult + extraDumps []string + t *testing.T +} + +// NewKnRunResultCollector returns a new KnRunResultCollector +func NewKnRunResultCollector(t *testing.T) *KnRunResultCollector { + return &KnRunResultCollector{ + results: []KnRunResult{}, + t: t, + extraDumps: []string{}, + } +} + +// AssertError helper to assert no error on result +func (c *KnRunResultCollector) AssertNoError(result KnRunResult) { + c.results = append(c.results, result) + if result.Error != nil { + c.t.Logf("ERROR: %v", result.Stderr) + c.t.FailNow() + } +} + +// AssertError helper to assert error on result +func (c *KnRunResultCollector) AssertError(result KnRunResult) { + c.results = append(c.results, result) + if result.Error == nil { + c.t.Log("ERROR: Error expected but no error happened") + c.t.FailNow() + } +} + +// AddDump adds extra dump information to the collector which is printed +// out if an error occurs +func (c *KnRunResultCollector) AddDump(kind string, name string, namespace string) { + dumpInfo := extractDumpInfoWithName(kind, name, namespace) + if dumpInfo != "" { + c.extraDumps = append(c.extraDumps, dumpInfo) + } +} + +// DumpIfFailed logs if collector failed +func (c *KnRunResultCollector) DumpIfFailed() { + if c.t.Failed() { + c.t.Log(c.errorDetails()) + } +} + +// Private + +func (c *KnRunResultCollector) errorDetails() string { + var out = bytes.Buffer{} + fmt.Fprintln(&out, "=== FAIL: =======================[[ERROR]]========================") + c.printCommands(&out) + var dumpInfos []string + if len(c.results) > 0 { + dumpInfo := c.results[len(c.results)-1].DumpInfo + if dumpInfo != "" { + dumpInfos = append(dumpInfos, dumpInfo) + } + } + dumpInfos = append(dumpInfos, c.extraDumps...) + for _, d := range dumpInfos { + fmt.Fprintln(&out, "--------------------------[[DUMP]]-------------------------------") + fmt.Fprintf(&out, d) + } + + fmt.Fprintln(&out, "=================================================================") + return out.String() +} + +func (c *KnRunResultCollector) printCommands(out io.Writer) { + for i, result := range c.results { + c.printCommand(out, result) + if i < len(c.results)-1 { + fmt.Fprintf(out, "┣━%s\n", seperatorHeavy) + } + } +} + +func (c *KnRunResultCollector) printCommand(out io.Writer, result KnRunResult) { + fmt.Fprintf(out, "🦆 %s\n", result.CmdLine) + for _, l := range strings.Split(result.Stdout, "\n") { + fmt.Fprintf(out, "┃ %s\n", l) + } + if result.Stderr != "" { + errorPrefix := "🔥" + if result.ErrorExpected { + errorPrefix = "︙" + } + for _, l := range strings.Split(result.Stderr, "\n") { + fmt.Fprintf(out, "%s %s\n", errorPrefix, l) + } + } +} diff --git a/pkg/kn/commands/flags/listprint.go b/pkg/kn/commands/flags/listprint.go index a19cca498c..da73381c01 100644 --- a/pkg/kn/commands/flags/listprint.go +++ b/pkg/kn/commands/flags/listprint.go @@ -15,11 +15,15 @@ package flags import ( + "io" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/cli-runtime/pkg/genericclioptions" "knative.dev/client/pkg/kn/commands" hprinters "knative.dev/client/pkg/printers" + "knative.dev/client/pkg/util" ) // ListFlags composes common printer flag structs @@ -56,6 +60,24 @@ func (f *ListPrintFlags) ToPrinter() (hprinters.ResourcePrinter, error) { return p, nil } +// Print is to print an Object to a Writer +func (f *ListPrintFlags) Print(obj runtime.Object, w io.Writer) error { + printer, err := f.ToPrinter() + if err != nil { + return err + } + + if f.GenericPrintFlags.OutputFlagSpecified() { + unstructuredList, err := util.ToUnstructuredList(obj) + if err != nil { + return err + } + return printer.PrintObj(unstructuredList, w) + } + + return printer.PrintObj(obj, w) +} + // AddFlags receives a *cobra.Command reference and binds // flags related to humanreadable and template printing. func (f *ListPrintFlags) AddFlags(cmd *cobra.Command) { diff --git a/pkg/kn/commands/flags/listprint_test.go b/pkg/kn/commands/flags/listprint_test.go index 7a9afb0c2b..da17e88de8 100644 --- a/pkg/kn/commands/flags/listprint_test.go +++ b/pkg/kn/commands/flags/listprint_test.go @@ -48,3 +48,18 @@ func TestListPrintFlags(t *testing.T) { _, ok := p.(hprinters.ResourcePrinter) assert.Check(t, ok == true) } + +func TestListPrintFlagsPrint(t *testing.T) { + var cmd *cobra.Command + flags := NewListPrintFlags(func(h hprinters.PrintHandler) {}) + + cmd = &cobra.Command{} + flags.AddFlags(cmd) + + pr, err := flags.ToPrinter() + assert.NilError(t, err) + assert.Assert(t, pr != nil) + + err = flags.Print(nil, cmd.OutOrStdout()) + assert.NilError(t, err) +} diff --git a/pkg/kn/commands/plugin/handler.go b/pkg/kn/commands/plugin/handler.go index 7ee7c4eeb5..5f8b86a065 100644 --- a/pkg/kn/commands/plugin/handler.go +++ b/pkg/kn/commands/plugin/handler.go @@ -60,6 +60,7 @@ func NewDefaultPluginHandler(validPrefixes []string, pluginsDir string, lookupPl } // Lookup implements PluginHandler +// TODO: The current error handling is not optimal, and some errors may be lost. We should refactor the code in the future. func (h *DefaultPluginHandler) Lookup(name string) (string, bool) { for _, prefix := range h.ValidPrefixes { pluginPath := fmt.Sprintf("%s-%s", prefix, name) @@ -72,10 +73,21 @@ func (h *DefaultPluginHandler) Lookup(name string) (string, bool) { pluginDirPluginPath := filepath.Join(pluginDir, pluginPath) _, err = os.Stat(pluginDirPluginPath) - if !os.IsNotExist(err) { + if err == nil { return pluginDirPluginPath, true } + // Try to match well-known file extensions on Windows + if runtime.GOOS == "windows" { + for _, ext := range []string{".bat", ".cmd", ".com", ".exe", ".ps1"} { + pathWithExt := pluginDirPluginPath + ext + _, err = os.Stat(pathWithExt) + if err == nil { + return pathWithExt, true + } + } + } + // No plugins found in pluginsDir, try in PATH of that's an option if h.LookupPluginsInPath { pluginPath, err = exec.LookPath(pluginPath) diff --git a/pkg/kn/commands/revision/delete.go b/pkg/kn/commands/revision/delete.go index 109448e8a5..e3c3ad7b1e 100644 --- a/pkg/kn/commands/revision/delete.go +++ b/pkg/kn/commands/revision/delete.go @@ -49,7 +49,7 @@ func NewRevisionDeleteCommand(p *commands.KnParams) *cobra.Command { for _, name := range args { timeout := time.Duration(0) - if !waitFlags.NoWait { + if waitFlags.Wait { timeout = time.Duration(waitFlags.TimeoutInSeconds) * time.Second } err = client.DeleteRevision(name, timeout) @@ -63,6 +63,6 @@ func NewRevisionDeleteCommand(p *commands.KnParams) *cobra.Command { }, } commands.AddNamespaceFlags(RevisionDeleteCommand.Flags(), false) - waitFlags.AddConditionWaitFlags(RevisionDeleteCommand, commands.WaitDefaultTimeout, "Delete", "revision", "deleted") + waitFlags.AddConditionWaitFlags(RevisionDeleteCommand, commands.WaitDefaultTimeout, "delete", "revision", "deleted") return RevisionDeleteCommand } diff --git a/pkg/kn/commands/revision/describe.go b/pkg/kn/commands/revision/describe.go index b6f9119489..afdea65027 100644 --- a/pkg/kn/commands/revision/describe.go +++ b/pkg/kn/commands/revision/describe.go @@ -131,7 +131,8 @@ func WriteConcurrencyOptions(dw printers.PrefixWriter, revision *servingv1.Revis target := clientserving.ConcurrencyTarget(&revision.ObjectMeta) limit := revision.Spec.ContainerConcurrency autoscaleWindow := clientserving.AutoscaleWindow(&revision.ObjectMeta) - if target != nil || limit != nil && *limit != 0 || autoscaleWindow != "" { + concurrencyUtilization := clientserving.ConcurrencyTargetUtilization(&revision.ObjectMeta) + if target != nil || limit != nil && *limit != 0 || autoscaleWindow != "" || concurrencyUtilization != nil { section := dw.WriteAttribute("Concurrency", "") if limit != nil && *limit != 0 { section.WriteAttribute("Limit", strconv.FormatInt(int64(*limit), 10)) @@ -142,6 +143,9 @@ func WriteConcurrencyOptions(dw printers.PrefixWriter, revision *servingv1.Revis if autoscaleWindow != "" { section.WriteAttribute("Window", autoscaleWindow) } + if concurrencyUtilization != nil { + section.WriteAttribute("TargetUtilization", strconv.Itoa(*concurrencyUtilization)) + } } } diff --git a/pkg/kn/commands/revision/list.go b/pkg/kn/commands/revision/list.go index 7685cc2d72..31fa130ff3 100644 --- a/pkg/kn/commands/revision/list.go +++ b/pkg/kn/commands/revision/list.go @@ -103,12 +103,7 @@ func NewRevisionListCommand(p *commands.KnParams) *cobra.Command { sortRevisions(revisionList) // Print out infos via printer framework - printer, err := revisionListFlags.ToPrinter() - if err != nil { - return err - } - - return printer.PrintObj(revisionList, cmd.OutOrStdout()) + return revisionListFlags.Print(revisionList, cmd.OutOrStdout()) }, } commands.AddNamespaceFlags(revisionListCommand.Flags(), true) diff --git a/pkg/kn/commands/route/list.go b/pkg/kn/commands/route/list.go index ecc56d7548..981677689f 100644 --- a/pkg/kn/commands/route/list.go +++ b/pkg/kn/commands/route/list.go @@ -69,11 +69,7 @@ func NewRouteListCommand(p *commands.KnParams) *cobra.Command { fmt.Fprintf(cmd.OutOrStdout(), "No routes found.\n") return nil } - printer, err := routeListFlags.ToPrinter() - if err != nil { - return err - } - err = printer.PrintObj(routeList, cmd.OutOrStdout()) + err = routeListFlags.Print(routeList, cmd.OutOrStdout()) if err != nil { return err } diff --git a/pkg/kn/commands/service/configuration_edit_flags.go b/pkg/kn/commands/service/configuration_edit_flags.go index 26a1516f2d..505fcac3d2 100644 --- a/pkg/kn/commands/service/configuration_edit_flags.go +++ b/pkg/kn/commands/service/configuration_edit_flags.go @@ -48,6 +48,7 @@ type ConfigurationEditFlags struct { MaxScale int ConcurrencyTarget int ConcurrencyLimit int + ConcurrencyUtilization int AutoscaleWindow string Port int32 Labels []string @@ -180,6 +181,10 @@ func (p *ConfigurationEditFlags) addSharedFlags(command *cobra.Command) { "Hard Limit of concurrent requests to be processed by a single replica.") p.markFlagMakesRevision("concurrency-limit") + command.Flags().IntVar(&p.ConcurrencyUtilization, "concurrency-utilization", 70, + "Percentage of concurrent requests utilization before scaling up.") + p.markFlagMakesRevision("concurrency-utilization") + command.Flags().Int32VarP(&p.Port, "port", "p", 0, "The port where application listens on.") p.markFlagMakesRevision("port") @@ -404,6 +409,13 @@ func (p *ConfigurationEditFlags) Apply( } } + if cmd.Flags().Changed("concurrency-utilization") { + err = servinglib.UpdateConcurrencyUtilization(template, p.ConcurrencyUtilization) + if err != nil { + return err + } + } + if cmd.Flags().Changed("cluster-local") || cmd.Flags().Changed("no-cluster-local") { if p.ClusterLocal { labels := servinglib.UpdateLabels(service.ObjectMeta.Labels, map[string]string{serving.VisibilityLabelKey: serving.VisibilityClusterLocal}, []string{}) diff --git a/pkg/kn/commands/service/create.go b/pkg/kn/commands/service/create.go index 33900354df..ea1df02f85 100644 --- a/pkg/kn/commands/service/create.go +++ b/pkg/kn/commands/service/create.go @@ -117,7 +117,7 @@ func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command { } commands.AddNamespaceFlags(serviceCreateCommand.Flags(), false) editFlags.AddCreateFlags(serviceCreateCommand) - waitFlags.AddConditionWaitFlags(serviceCreateCommand, commands.WaitDefaultTimeout, "Create", "service", "ready") + waitFlags.AddConditionWaitFlags(serviceCreateCommand, commands.WaitDefaultTimeout, "create", "service", "ready") return serviceCreateCommand } @@ -145,7 +145,7 @@ func waitIfRequested(client clientservingv1.KnServingClient, service *servingv1. fmt.Fprintf(out, "Service '%s' %s in namespace '%s'.\n", service.Name, verbDone, client.Namespace()) return nil } - if waitFlags.NoWait { + if !waitFlags.Wait { fmt.Fprintf(out, "Service '%s' %s in namespace '%s'.\n", service.Name, verbDone, client.Namespace()) return nil } diff --git a/pkg/kn/commands/service/create_test.go b/pkg/kn/commands/service/create_test.go index 0ade9c327c..568bbcbb62 100644 --- a/pkg/kn/commands/service/create_test.go +++ b/pkg/kn/commands/service/create_test.go @@ -143,6 +143,13 @@ func TestServiceCreateWithMultipleImages(t *testing.T) { assert.Assert(t, util.ContainsAll(err.Error(), "\"--image\"", "\"gcr.io/bar/foo:baz\"", "flag", "once")) } +func TestServiceCreateWithMultipleNames(t *testing.T) { + _, _, _, err := fakeServiceCreate([]string{ + "service", "create", "foo", "foo1", "--image", "gcr.io/foo/bar:baz", "--no-wait"}, false) + + assert.Assert(t, util.ContainsAll(err.Error(), "'service create' requires the service name given as single argument")) +} + func TestServiceCreateImageSync(t *testing.T) { action, created, output, err := fakeServiceCreate([]string{ "service", "create", "foo", "--image", "gcr.io/foo/bar:baz"}, false) @@ -370,6 +377,7 @@ func TestServiceCreateMaxMinScale(t *testing.T) { "service", "create", "foo", "--image", "gcr.io/foo/bar:baz", "--min-scale", "1", "--max-scale", "5", "--concurrency-target", "10", "--concurrency-limit", "100", + "--concurrency-utilization", "50", "--no-wait"}, false) if err != nil { @@ -385,6 +393,7 @@ func TestServiceCreateMaxMinScale(t *testing.T) { "autoscaling.knative.dev/minScale", "1", "autoscaling.knative.dev/maxScale", "5", "autoscaling.knative.dev/target", "10", + "autoscaling.knative.dev/targetUtilizationPercentage", "50", } for i := 0; i < len(expectedAnnos); i += 2 { diff --git a/pkg/kn/commands/service/delete.go b/pkg/kn/commands/service/delete.go index 81dc8f9612..73a6e17bc7 100644 --- a/pkg/kn/commands/service/delete.go +++ b/pkg/kn/commands/service/delete.go @@ -40,7 +40,7 @@ func NewServiceDeleteCommand(p *commands.KnParams) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { - return errors.New("requires the service name") + return errors.New("'service delete' requires the service name(s)") } namespace, err := p.GetNamespace(cmd) @@ -53,7 +53,7 @@ func NewServiceDeleteCommand(p *commands.KnParams) *cobra.Command { } for _, name := range args { timeout := time.Duration(0) - if !waitFlags.NoWait { + if waitFlags.Wait { timeout = time.Duration(waitFlags.TimeoutInSeconds) * time.Second } err = client.DeleteService(name, timeout) @@ -67,6 +67,6 @@ func NewServiceDeleteCommand(p *commands.KnParams) *cobra.Command { }, } commands.AddNamespaceFlags(serviceDeleteCommand.Flags(), false) - waitFlags.AddConditionWaitFlags(serviceDeleteCommand, commands.WaitDefaultTimeout, "Delete", "service", "deleted") + waitFlags.AddConditionWaitFlags(serviceDeleteCommand, commands.WaitDefaultTimeout, "delete", "service", "deleted") return serviceDeleteCommand } diff --git a/pkg/kn/commands/service/describe.go b/pkg/kn/commands/service/describe.go index 866c0827cd..7741d34323 100644 --- a/pkg/kn/commands/service/describe.go +++ b/pkg/kn/commands/service/describe.go @@ -81,7 +81,7 @@ func NewServiceDescribeCommand(p *commands.KnParams) *cobra.Command { Short: "Show details of a service", RunE: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { - return errors.New("'kn service describe' requires name of the service as single argument") + return errors.New("'service describe' requires the service name given as single argument") } serviceName := args[0] @@ -267,7 +267,7 @@ func getRevisionDescriptions(client clientservingv1.KnServingClient, service *se return nil, fmt.Errorf("cannot extract revision from service %s: %v", service.Name, err) } revisionsSeen.Insert(revision.Name) - desc, err := newRevisionDesc(revision, &target, service) + desc, err := newRevisionDesc(*revision, &target, service) if err != nil { return nil, err } @@ -302,7 +302,7 @@ func completeWithLatestRevisions(client clientservingv1.KnServingClient, service if err != nil { return nil, err } - newDesc, err := newRevisionDesc(rev, nil, service) + newDesc, err := newRevisionDesc(*rev, nil, service) if err != nil { return nil, err } @@ -321,7 +321,7 @@ func completeWithUntargetedRevisions(client clientservingv1.KnServingClient, ser continue } revisionsSeen.Insert(revision.Name) - newDesc, err := newRevisionDesc(&revision, nil, service) + newDesc, err := newRevisionDesc(revision, nil, service) if err != nil { return nil, err } @@ -331,13 +331,13 @@ func completeWithUntargetedRevisions(client clientservingv1.KnServingClient, ser return descs, nil } -func newRevisionDesc(revision *servingv1.Revision, target *servingv1.TrafficTarget, service *servingv1.Service) (*revisionDesc, error) { +func newRevisionDesc(revision servingv1.Revision, target *servingv1.TrafficTarget, service *servingv1.Service) (*revisionDesc, error) { generation, err := strconv.ParseInt(revision.Labels[serving.ConfigurationGenerationLabelKey], 0, 0) if err != nil { return nil, fmt.Errorf("cannot extract configuration generation for revision %s: %v", revision.Name, err) } revisionDesc := revisionDesc{ - revision: revision, + revision: &revision, configurationGeneration: int(generation), latestCreated: revision.Name == service.Status.LatestCreatedRevisionName, latestReady: revision.Name == service.Status.LatestReadyRevisionName, diff --git a/pkg/kn/commands/service/describe_test.go b/pkg/kn/commands/service/describe_test.go index 1c45b2e0d7..dc2543e257 100644 --- a/pkg/kn/commands/service/describe_test.go +++ b/pkg/kn/commands/service/describe_test.go @@ -75,6 +75,16 @@ func TestServiceDescribeBasic(t *testing.T) { r.Validate() } +func TestServiceDescribeWithMultipleNames(t *testing.T) { + client := knclient.NewMockKnServiceClient(t) + r := client.Recorder() + createTestService("foo", []string{"rev1"}, goodConditions()) + _, err := executeServiceCommand(client, "describe", "foo", "foo1") + + assert.Assert(t, util.ContainsAll(err.Error(), "'service describe' requires the service name given as single argument")) + r.Validate() +} + func TestServiceDescribeSad(t *testing.T) { client := knclient.NewMockKnServiceClient(t) r := client.Recorder() @@ -243,13 +253,13 @@ func TestServiceDescribeScaling(t *testing.T) { for _, data := range []struct { minScale, maxScale, limit, target string - scaleOut string + scaleOut, utilization string }{ - {"", "", "", "", ""}, - {"", "10", "", "", "0 ... 10"}, - {"10", "", "", "", "10 ... ∞"}, - {"5", "20", "10", "", "5 ... 20"}, - {"", "", "20", "30", ""}, + {"", "", "", "", "", ""}, + {"", "10", "", "", "0 ... 10", ""}, + {"10", "", "", "", "10 ... ∞", ""}, + {"5", "20", "10", "", "5 ... 20", ""}, + {"", "", "20", "30", "", "50"}, } { // New mock client client := knclient.NewMockKnServiceClient(t) @@ -263,7 +273,7 @@ func TestServiceDescribeScaling(t *testing.T) { // Get service & revision r.GetService("foo", &expectedService, nil) rev1 := createTestRevision("rev1", 1) - addScaling(&rev1, data.minScale, data.maxScale, data.target, data.limit) + addScaling(&rev1, data.minScale, data.maxScale, data.target, data.limit, data.utilization) r.GetRevision("rev1", &rev1, nil) revList := servingv1.RevisionList{ @@ -422,11 +432,15 @@ func TestServiceDescribeVerbose(t *testing.T) { r := client.Recorder() // Prepare service - expectedService := createTestService("foo", []string{"rev1", "rev2"}, goodConditions()) + expectedService := createTestService("foo", []string{"rev1", "rev2", "rev3"}, goodConditions()) + expectedService.Status.Traffic = expectedService.Status.Traffic[2:] + expectedService.Status.Traffic[0].LatestRevision = ptr.Bool(true) + expectedService.Status.Traffic[0].Percent = ptr.Int64(int64(100)) r.GetService("foo", &expectedService, nil) rev1 := createTestRevision("rev1", 1) rev2 := createTestRevision("rev2", 2) + rev3 := createTestRevision("rev3", 3) revList := servingv1.RevisionList{ TypeMeta: metav1.TypeMeta{ @@ -434,7 +448,7 @@ func TestServiceDescribeVerbose(t *testing.T) { APIVersion: "serving.knative.dev/v1", }, Items: []servingv1.Revision{ - rev1, rev2, + rev1, rev2, rev3, }, } @@ -442,8 +456,7 @@ func TestServiceDescribeVerbose(t *testing.T) { r.ListRevisions(knclient.HasLabelSelector(api_serving.ServiceLabelKey, "foo"), &revList, nil) // Fetch the revisions - r.GetRevision("rev1", &rev1, nil) - r.GetRevision("rev2", &rev2, nil) + r.GetRevision("rev3", &rev3, nil) // Testing: output, err := executeServiceCommand(client, "describe", "foo", "--verbose") @@ -452,12 +465,16 @@ func TestServiceDescribeVerbose(t *testing.T) { validateServiceOutput(t, "foo", output) assert.Assert(t, cmp.Regexp("Cluster:\\s+https://foo.default.svc.cluster.local", output)) - assert.Assert(t, util.ContainsAll(output, "Image", "Name", "gcr.io/test/image (at 123456)", "50%", "(0s)")) + assert.Assert(t, util.ContainsAll(output, "Image", "Name", "gcr.io/test/image (at 123456)", "100%", "(0s)")) assert.Assert(t, util.ContainsAll(output, "Env:", "env1=eval1\n", "env2=eval2\n")) assert.Assert(t, util.ContainsAll(output, "EnvFrom:", "cm:test1\n", "cm:test2\n")) assert.Assert(t, util.ContainsAll(output, "Annotations:", "anno1=aval1\n", "anno2=aval2\n")) assert.Assert(t, util.ContainsAll(output, "Labels:", "label1=lval1\n", "label2=lval2\n")) assert.Assert(t, util.ContainsAll(output, "[1]", "[2]")) + assert.Assert(t, util.ContainsAll(output, "rev1", "rev2", "rev3")) + assert.Equal(t, strings.Count(output, "rev3"), 1) + assert.Equal(t, strings.Count(output, "rev2"), 1) + assert.Equal(t, strings.Count(output, "rev1"), 1) // Validate that all recorded API methods have been called r.Validate() @@ -570,7 +587,7 @@ func createTestServiceWithServiceAccount(name string, revisionNames []string, se return service } -func addScaling(revision *servingv1.Revision, minScale, maxScale, concurrencyTarget, concurrencyLimit string) { +func addScaling(revision *servingv1.Revision, minScale, maxScale, concurrencyTarget, concurrencyLimit, utilization string) { annos := make(map[string]string) if minScale != "" { annos[autoscaling.MinScaleAnnotationKey] = minScale @@ -581,6 +598,9 @@ func addScaling(revision *servingv1.Revision, minScale, maxScale, concurrencyTar if concurrencyTarget != "" { annos[autoscaling.TargetAnnotationKey] = concurrencyTarget } + if utilization != "" { + annos[autoscaling.TargetUtilizationPercentageKey] = utilization + } revision.Annotations = annos if concurrencyLimit != "" { l, _ := strconv.ParseInt(concurrencyLimit, 10, 64) diff --git a/pkg/kn/commands/service/export.go b/pkg/kn/commands/service/export.go index 1b34abb92f..482b47c4ca 100644 --- a/pkg/kn/commands/service/export.go +++ b/pkg/kn/commands/service/export.go @@ -40,7 +40,7 @@ func NewServiceExportCommand(p *commands.KnParams) *cobra.Command { command := &cobra.Command{ Use: "export NAME", - Short: "export a service", + Short: "Export a service.", Example: ` # Export a service in yaml format kn service export foo -n bar -o yaml @@ -125,7 +125,7 @@ func constructServicefromRevision(latestSvc *servingv1.Service, revision serving TypeMeta: latestSvc.TypeMeta, } - exportedSvc.Spec.Template = servingv1.RevisionTemplateSpec{ + exportedSvc.Spec.ConfigurationSpec.Template = servingv1.RevisionTemplateSpec{ Spec: revision.Spec, ObjectMeta: latestSvc.Spec.ConfigurationSpec.Template.ObjectMeta, } @@ -164,8 +164,10 @@ func exportServiceWithActiveRevisions(latestSvc *servingv1.Service, client clien return nil, fmt.Errorf("no revisions found for service %s", latestSvc.ObjectMeta.Name) } - //set traffic in the latest revision - exportedSvcItems[len(exportedSvcItems)-1] = setTrafficSplit(latestSvc, exportedSvcItems[len(exportedSvcItems)-1]) + //set traffic in the latest revision on if there is traffic split + if len(latestSvc.Spec.RouteSpec.Traffic) > 1 { + exportedSvcItems[len(exportedSvcItems)-1] = setTrafficSplit(latestSvc, exportedSvcItems[len(exportedSvcItems)-1]) + } typeMeta := metav1.TypeMeta{ APIVersion: "v1", diff --git a/pkg/kn/commands/service/export_test.go b/pkg/kn/commands/service/export_test.go index 59662281e5..8bac812dd8 100644 --- a/pkg/kn/commands/service/export_test.go +++ b/pkg/kn/commands/service/export_test.go @@ -17,6 +17,7 @@ package service import ( "encoding/json" "fmt" + "strconv" "testing" "gotest.tools/assert" @@ -32,188 +33,97 @@ import ( "sigs.k8s.io/yaml" ) -func TestServiceExport(t *testing.T) { - var svcs []*servingv1.Service - typeMeta := metav1.TypeMeta{ - Kind: "service", - APIVersion: "serving.knative.dev/v1", - } +type expectedServiceOption func(*servingv1.Service) +type expectedRevisionOption func(*servingv1.Revision) - // case 1 - plain svc - plainService := getService("foo") - svcs = append(svcs, plainService) - - // case 2 - svc with env variables - envSvc := getService("foo") - envVars := []v1.EnvVar{ - {Name: "a", Value: "mouse"}, - {Name: "b", Value: "cookie"}, - {Name: "empty", Value: ""}, - } - template := &envSvc.Spec.Template - template.Spec.Containers[0].Env = envVars - template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz" - template.Annotations = map[string]string{servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz"} - svcs = append(svcs, envSvc) - - //case 3 - svc with labels - labelService := getService("foo") - expected := map[string]string{ - "a": "mouse", - "b": "cookie", - "empty": "", - } - labelService.Labels = expected - labelService.Spec.Template.Annotations = map[string]string{ - servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz", - } - template = &labelService.Spec.Template - template.ObjectMeta.Labels = expected - template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz" - svcs = append(svcs, labelService) - - //case 4 - config map - CMservice := getService("foo") - template = &CMservice.Spec.Template - template.Spec.Containers[0].EnvFrom = []v1.EnvFromSource{ - { - ConfigMapRef: &v1.ConfigMapEnvSource{ - LocalObjectReference: v1.LocalObjectReference{ - Name: "config-map-name", - }, - }, - }, - } - template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz" - template.Annotations = map[string]string{servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz"} - svcs = append(svcs, CMservice) - - //case 5 - volume mount and secrets - Volservice := getService("foo") - template = &Volservice.Spec.Template - template.Spec.Volumes = []v1.Volume{ - { - Name: "volume-name", - VolumeSource: v1.VolumeSource{ - Secret: &v1.SecretVolumeSource{ - SecretName: "secret-name", - }, - }, - }, - } +func TestServiceExport(t *testing.T) { - template.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{ - { - Name: "volume-name", - MountPath: "/mount/path", - ReadOnly: true, - }, + svcs := []*servingv1.Service{ + getServiceWithOptions(getService("foo"), withContainer()), + getServiceWithOptions(getService("foo"), withContainer(), withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}})), + getServiceWithOptions(getService("foo"), withContainer(), withLabels(map[string]string{"a": "mouse", "b": "cookie", "empty": ""})), + getServiceWithOptions(getService("foo"), withContainer(), withEnvFrom([]string{"cm-name"})), + getServiceWithOptions(getService("foo"), withContainer(), withVolumeandSecrets("volName", "secretName")), } - svcs = append(svcs, Volservice) - - template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz" - template.Annotations = map[string]string{servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz"} for _, svc := range svcs { - svc.TypeMeta = typeMeta callServiceExportTest(t, svc) } - } func callServiceExportTest(t *testing.T, expectedService *servingv1.Service) { // New mock client client := knclient.NewMockKnServiceClient(t) - // Recording: r := client.Recorder() - r.GetService(expectedService.ObjectMeta.Name, expectedService, nil) output, err := executeServiceCommand(client, "export", expectedService.ObjectMeta.Name, "-o", "yaml") - assert.NilError(t, err) - expectedService.ObjectMeta.Namespace = "" - - expSvcYaml, err := yaml.Marshal(expectedService) - + actSvc := servingv1.Service{} + err = yaml.Unmarshal([]byte(output), &actSvc) assert.NilError(t, err) - - assert.Equal(t, string(expSvcYaml), output) - + stripExpectedSvcVariables(expectedService) + assert.DeepEqual(t, expectedService, &actSvc) // Validate that all recorded API methods have been called r.Validate() } func TestServiceExportwithMultipleRevisions(t *testing.T) { - //case 1 = 2 revisions with traffic split - trafficSplitService := createServiceTwoRevsionsWithTraffic("foo", true) - - multiRevs := createTestRevisionList("rev", "foo") + //case 1 - 2 revisions with traffic split + expSvc1 := getServiceWithOptions(getService("foo"), withContainer(), withServiceRevisionName("foo-rev-1")) + stripExpectedSvcVariables(expSvc1) + expSvc2 := getServiceWithOptions(getService("foo"), withContainer(), withTrafficSplit([]string{"foo-rev-1", "foo-rev-2"}, []int{50, 50}, []string{"latest", "current"}), withServiceRevisionName("foo-rev-2")) + stripExpectedSvcVariables(expSvc2) + latestSvc := getServiceWithOptions(getService("foo"), withContainer(), withTrafficSplit([]string{"foo-rev-1", "foo-rev-2"}, []int{50, 50}, []string{"latest", "current"})) + + expSvcList := servingv1.ServiceList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "List", + }, + Items: []servingv1.Service{*expSvc1, *expSvc2}, + } - callServiceExportHistoryTest(t, trafficSplitService, multiRevs) + multiRevs := getRevisionList("rev", "foo") - //case 2 - same revisions no traffic split - noTrafficSplitService := createServiceTwoRevsionsWithTraffic("foo", false) + callServiceExportHistoryTest(t, latestSvc, multiRevs, &expSvcList) - callServiceExportHistoryTest(t, noTrafficSplitService, multiRevs) + // case 2 - same revisions no traffic split + expSvc2 = getServiceWithOptions(getService("foo"), withContainer(), withServiceRevisionName("foo-rev-2")) + stripExpectedSvcVariables(expSvc2) + expSvcList = servingv1.ServiceList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "List", + }, + Items: []servingv1.Service{*expSvc2}, + } + latestSvc = getServiceWithOptions(getService("foo"), withContainer(), withTrafficSplit([]string{"foo-rev-2"}, []int{100}, []string{"latest"})) + callServiceExportHistoryTest(t, latestSvc, multiRevs, &expSvcList) } -func callServiceExportHistoryTest(t *testing.T, expectedService *servingv1.Service, revs *servingv1.RevisionList) { +func callServiceExportHistoryTest(t *testing.T, latestSvc *servingv1.Service, revs *servingv1.RevisionList, expSvcList *servingv1.ServiceList) { // New mock client client := knclient.NewMockKnServiceClient(t) - // Recording: r := client.Recorder() - r.GetService(expectedService.ObjectMeta.Name, expectedService, nil) - + r.GetService(latestSvc.ObjectMeta.Name, latestSvc, nil) r.ListRevisions(mock.Any(), revs, nil) - output, err := executeServiceCommand(client, "export", expectedService.ObjectMeta.Name, "--with-revisions", "-o", "json") - + output, err := executeServiceCommand(client, "export", latestSvc.ObjectMeta.Name, "--with-revisions", "-o", "json") assert.NilError(t, err) actSvcList := servingv1.ServiceList{} - - json.Unmarshal([]byte(output), &actSvcList) - - for i, actSvc := range actSvcList.Items { - var checkTraffic bool - if i == (len(actSvcList.Items) - 1) { - checkTraffic = true - } - validateServiceWithRevisionHistory(t, expectedService, revs, actSvc, checkTraffic) - } - + err = json.Unmarshal([]byte(output), &actSvcList) + assert.NilError(t, err) + assert.DeepEqual(t, expSvcList, &actSvcList) // Validate that all recorded API methods have been called r.Validate() } -func validateServiceWithRevisionHistory(t *testing.T, expectedsvc *servingv1.Service, expectedRevList *servingv1.RevisionList, actualSvc servingv1.Service, checkTraffic bool) { - var expectedRev servingv1.Revision - var routeSpec servingv1.RouteSpec - for _, rev := range expectedRevList.Items { - if actualSvc.Spec.ConfigurationSpec.Template.ObjectMeta.Name == rev.ObjectMeta.Name { - expectedRev = rev - break - } - } - expectedsvc.Spec.ConfigurationSpec.Template.ObjectMeta.Name = expectedRev.ObjectMeta.Name - expectedsvc.Spec.Template.Spec = expectedRev.Spec - - stripExpectedSvcVariables(expectedsvc) - - if !checkTraffic { - routeSpec = expectedsvc.Spec.RouteSpec - expectedsvc.Spec.RouteSpec = servingv1.RouteSpec{} - } - assert.DeepEqual(t, expectedsvc, &actualSvc) - - expectedsvc.Spec.RouteSpec = routeSpec -} - func TestServiceExportError(t *testing.T) { // New mock client client := knclient.NewMockKnServiceClient(t) @@ -225,98 +135,176 @@ func TestServiceExportError(t *testing.T) { assert.Error(t, err, "'kn service export' requires output format") } -func createTestRevisionList(revision string, service string) *servingv1.RevisionList { - labels1 := make(map[string]string) - labels1[apiserving.ConfigurationGenerationLabelKey] = "1" - labels1[apiserving.ServiceLabelKey] = service +func getRevisionList(revision string, service string) *servingv1.RevisionList { + rev1 := getRevisionWithOptions( + service, + withRevisionGeneration("1"), + withRevisionName(fmt.Sprintf("%s-%s-%d", service, revision, 1)), + ) + + rev2 := getRevisionWithOptions( + service, + withRevisionGeneration("2"), + withRevisionName(fmt.Sprintf("%s-%s-%d", service, revision, 2)), + ) + + return &servingv1.RevisionList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "List", + }, + Items: []servingv1.Revision{rev1, rev2}, + } +} - labels2 := make(map[string]string) - labels2[apiserving.ConfigurationGenerationLabelKey] = "2" - labels2[apiserving.ServiceLabelKey] = service +func stripExpectedSvcVariables(expectedsvc *servingv1.Service) { + expectedsvc.ObjectMeta.Namespace = "" + expectedsvc.Spec.Template.Spec.Containers[0].Resources = v1.ResourceRequirements{} + expectedsvc.Status = servingv1.ServiceStatus{} + expectedsvc.ObjectMeta.Annotations = nil + expectedsvc.ObjectMeta.CreationTimestamp = metav1.Time{} +} - rev1 := servingv1.Revision{ +func getRevisionWithOptions(service string, options ...expectedRevisionOption) servingv1.Revision { + rev := servingv1.Revision{ TypeMeta: metav1.TypeMeta{ Kind: "Revision", APIVersion: "serving.knative.dev/v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-%s-%d", service, revision, 1), - Namespace: "default", - Generation: int64(1), - Labels: labels1, + Namespace: "default", + Labels: map[string]string{ + apiserving.ServiceLabelKey: service, + }, }, Spec: servingv1.RevisionSpec{ PodSpec: v1.PodSpec{ Containers: []v1.Container{ { - Image: "gcr.io/test/image:v1", - Env: []v1.EnvVar{ - {Name: "env1", Value: "eval1"}, - {Name: "env2", Value: "eval2"}, - }, - EnvFrom: []v1.EnvFromSource{ - {ConfigMapRef: &v1.ConfigMapEnvSource{LocalObjectReference: v1.LocalObjectReference{Name: "test1"}}}, - {ConfigMapRef: &v1.ConfigMapEnvSource{LocalObjectReference: v1.LocalObjectReference{Name: "test2"}}}, - }, - Ports: []v1.ContainerPort{ - {ContainerPort: 8080}, - }, + Image: "gcr.io/foo/bar:baz", }, }, }, }, } + for _, fn := range options { + fn(&rev) + } + return rev +} + +func getServiceWithOptions(svc *servingv1.Service, options ...expectedServiceOption) *servingv1.Service { + svc.TypeMeta = metav1.TypeMeta{ + Kind: "service", + APIVersion: "serving.knative.dev/v1", + } - rev2 := rev1 + for _, fn := range options { + fn(svc) + } + + return svc +} - rev2.Spec.PodSpec.Containers[0].Image = "gcr.io/test/image:v2" - rev2.ObjectMeta.Labels = labels2 - rev2.ObjectMeta.Generation = int64(2) - rev2.ObjectMeta.Name = fmt.Sprintf("%s-%s-%d", service, revision, 2) +func withLabels(labels map[string]string) expectedServiceOption { + return func(svc *servingv1.Service) { + svc.Spec.ConfigurationSpec.Template.ObjectMeta.Labels = labels + } +} - typeMeta := metav1.TypeMeta{ - APIVersion: "v1", - Kind: "List", +func withEnvFrom(cmNames []string) expectedServiceOption { + return func(svc *servingv1.Service) { + var list []v1.EnvFromSource + for _, cmName := range cmNames { + list = append(list, v1.EnvFromSource{ + ConfigMapRef: &v1.ConfigMapEnvSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: cmName, + }, + }, + }) + } + svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].EnvFrom = list } +} - return &servingv1.RevisionList{ - TypeMeta: typeMeta, - Items: []servingv1.Revision{rev1, rev2}, +func withEnv(env []v1.EnvVar) expectedServiceOption { + return func(svc *servingv1.Service) { + svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].Env = env } } -func createServiceTwoRevsionsWithTraffic(svc string, trafficSplit bool) *servingv1.Service { - expectedService := createTestService(svc, []string{svc + "-rev-1", svc + "-rev-2"}, goodConditions()) - expectedService.Status.Traffic[0].LatestRevision = ptr.Bool(true) - expectedService.Status.Traffic[0].Tag = "latest" - expectedService.Status.Traffic[1].Tag = "current" +func withContainer() expectedServiceOption { + return func(svc *servingv1.Service) { + svc.Spec.ConfigurationSpec.Template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz" + svc.Spec.ConfigurationSpec.Template.Annotations = map[string]string{servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz"} - if trafficSplit { - trafficList := []servingv1.TrafficTarget{ + } +} + +func withVolumeandSecrets(volName string, secretName string) expectedServiceOption { + return func(svc *servingv1.Service) { + template := &svc.Spec.Template + template.Spec.Volumes = []v1.Volume{ { - RevisionName: "foo-rev-1", - Percent: ptr.Int64(int64(50)), - }, { - RevisionName: "foo-rev-2", - Percent: ptr.Int64(int64(50)), - }} - expectedService.Spec.RouteSpec = servingv1.RouteSpec{Traffic: trafficList} - } else { - trafficList := []servingv1.TrafficTarget{ + Name: volName, + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: secretName, + }, + }, + }, + } + + template.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{ { - RevisionName: "foo-rev-2", - Percent: ptr.Int64(int64(50)), - }} - expectedService.Spec.RouteSpec = servingv1.RouteSpec{Traffic: trafficList} + Name: volName, + MountPath: "/mount/path", + ReadOnly: true, + }, + } } +} - return &expectedService +func withRevisionGeneration(gen string) expectedRevisionOption { + return func(rev *servingv1.Revision) { + i, _ := strconv.Atoi(gen) + rev.ObjectMeta.Generation = int64(i) + rev.ObjectMeta.Labels[apiserving.ConfigurationGenerationLabelKey] = gen + } } -func stripExpectedSvcVariables(expectedsvc *servingv1.Service) { - expectedsvc.ObjectMeta.Namespace = "" - expectedsvc.Spec.Template.Spec.Containers[0].Resources = v1.ResourceRequirements{} - expectedsvc.Status = servingv1.ServiceStatus{} - expectedsvc.ObjectMeta.Annotations = nil - expectedsvc.ObjectMeta.CreationTimestamp = metav1.Time{} +func withRevisionName(name string) expectedRevisionOption { + return func(rev *servingv1.Revision) { + rev.ObjectMeta.Name = name + } +} + +func withServiceRevisionName(name string) expectedServiceOption { + return func(svc *servingv1.Service) { + svc.Spec.ConfigurationSpec.Template.ObjectMeta.Name = name + } +} + +func withTrafficSplit(revisions []string, percentages []int, tags []string) expectedServiceOption { + return func(svc *servingv1.Service) { + var trafficTargets []servingv1.TrafficTarget + for i, rev := range revisions { + trafficTargets = append(trafficTargets, servingv1.TrafficTarget{ + Percent: ptr.Int64(int64(percentages[i])), + }) + if tags[i] != "" { + trafficTargets[i].Tag = tags[i] + } + if rev == "latest" { + trafficTargets[i].LatestRevision = ptr.Bool(true) + } else { + trafficTargets[i].RevisionName = rev + trafficTargets[i].LatestRevision = ptr.Bool(false) + } + } + svc.Spec.RouteSpec = servingv1.RouteSpec{ + Traffic: trafficTargets, + } + } } diff --git a/pkg/kn/commands/service/list.go b/pkg/kn/commands/service/list.go index 50cc13e1e1..10a3308cc2 100644 --- a/pkg/kn/commands/service/list.go +++ b/pkg/kn/commands/service/list.go @@ -65,11 +65,6 @@ func NewServiceListCommand(p *commands.KnParams) *cobra.Command { serviceListFlags.EnsureWithNamespace() } - printer, err := serviceListFlags.ToPrinter() - if err != nil { - return err - } - // Sort serviceList by namespace and name (in this order) sort.SliceStable(serviceList.Items, func(i, j int) bool { a := serviceList.Items[i] @@ -81,11 +76,7 @@ func NewServiceListCommand(p *commands.KnParams) *cobra.Command { return a.ObjectMeta.Name < b.ObjectMeta.Name }) - err = printer.PrintObj(serviceList, cmd.OutOrStdout()) - if err != nil { - return err - } - return nil + return serviceListFlags.Print(serviceList, cmd.OutOrStdout()) }, } commands.AddNamespaceFlags(serviceListCommand.Flags(), true) diff --git a/pkg/kn/commands/service/service_test.go b/pkg/kn/commands/service/service_test.go index 8d77089510..22561a6ec8 100644 --- a/pkg/kn/commands/service/service_test.go +++ b/pkg/kn/commands/service/service_test.go @@ -17,9 +17,11 @@ package service import ( "bytes" + "github.com/spf13/cobra" "k8s.io/client-go/tools/clientcmd" "knative.dev/client/pkg/kn/commands" + knflags "knative.dev/client/pkg/kn/flags" clientservingv1 "knative.dev/client/pkg/serving/v1" ) @@ -60,6 +62,10 @@ func executeServiceCommand(client clientservingv1.KnServingClient, args ...strin cmd := NewServiceCommand(knParams) cmd.SetArgs(args) cmd.SetOutput(output) + + cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { + return knflags.ReconcileBoolFlags(cmd.Flags()) + } err := cmd.Execute() return output.String(), err } diff --git a/pkg/kn/commands/service/update.go b/pkg/kn/commands/service/update.go index b0589e0a1d..1d72dc040f 100644 --- a/pkg/kn/commands/service/update.go +++ b/pkg/kn/commands/service/update.go @@ -60,7 +60,7 @@ func NewServiceUpdateCommand(p *commands.KnParams) *cobra.Command { Example: updateExample, RunE: func(cmd *cobra.Command, args []string) (err error) { if len(args) != 1 { - return errors.New("requires the service name") + return errors.New("'service update' requires the service name given as single argument") } namespace, err := p.GetNamespace(cmd) @@ -110,7 +110,7 @@ func NewServiceUpdateCommand(p *commands.KnParams) *cobra.Command { out := cmd.OutOrStdout() //TODO: deprecated condition should be once --async is gone - if !waitFlags.Async && !waitFlags.NoWait { + if !waitFlags.Async && waitFlags.Wait { fmt.Fprintf(out, "Updating Service '%s' in namespace '%s':\n", args[0], namespace) fmt.Fprintln(out, "") err := waitForService(client, name, out, waitFlags.TimeoutInSeconds) @@ -136,7 +136,7 @@ func NewServiceUpdateCommand(p *commands.KnParams) *cobra.Command { commands.AddNamespaceFlags(serviceUpdateCommand.Flags(), false) editFlags.AddUpdateFlags(serviceUpdateCommand) - waitFlags.AddConditionWaitFlags(serviceUpdateCommand, commands.WaitDefaultTimeout, "Update", "service", "ready") + waitFlags.AddConditionWaitFlags(serviceUpdateCommand, commands.WaitDefaultTimeout, "update", "service", "ready") trafficFlags.Add(serviceUpdateCommand) return serviceUpdateCommand } diff --git a/pkg/kn/commands/service/update_test.go b/pkg/kn/commands/service/update_test.go index bb48aad86f..9364ff7914 100644 --- a/pkg/kn/commands/service/update_test.go +++ b/pkg/kn/commands/service/update_test.go @@ -195,11 +195,19 @@ func TestServiceUpdateImage(t *testing.T) { func TestServiceUpdateWithMultipleImages(t *testing.T) { orig := newEmptyService() _, _, _, err := fakeServiceUpdate(orig, []string{ - "service", "create", "foo", "--image", "gcr.io/foo/bar:baz", "--image", "gcr.io/bar/foo:baz", "--no-wait"}) + "service", "update", "foo", "--image", "gcr.io/foo/bar:baz", "--image", "gcr.io/bar/foo:baz", "--no-wait"}) assert.Assert(t, util.ContainsAll(err.Error(), "\"--image\"", "\"gcr.io/bar/foo:baz\"", "flag", "once")) } +func TestServiceUpdateWithMultipleNames(t *testing.T) { + orig := newEmptyService() + _, _, _, err := fakeServiceUpdate(orig, []string{ + "service", "update", "foo", "foo1", "--image", "gcr.io/foo/bar:baz", "--no-wait"}) + + assert.Assert(t, util.ContainsAll(err.Error(), "'service update' requires the service name given as single argument")) +} + func TestServiceUpdateCommand(t *testing.T) { orig := newEmptyService() @@ -311,7 +319,7 @@ func TestServiceUpdateMaxMinScale(t *testing.T) { action, updated, _, err := fakeServiceUpdate(original, []string{ "service", "update", "foo", - "--min-scale", "1", "--max-scale", "5", "--concurrency-target", "10", "--concurrency-limit", "100", "--no-wait"}) + "--min-scale", "1", "--max-scale", "5", "--concurrency-target", "10", "--concurrency-limit", "100", "--concurrency-utilization", "50", "--no-wait"}) if err != nil { t.Fatal(err) @@ -329,6 +337,7 @@ func TestServiceUpdateMaxMinScale(t *testing.T) { "autoscaling.knative.dev/minScale", "1", "autoscaling.knative.dev/maxScale", "5", "autoscaling.knative.dev/target", "10", + "autoscaling.knative.dev/targetUtilizationPercentage", "50", } for i := 0; i < len(expectedAnnos); i += 2 { diff --git a/pkg/kn/commands/source/apiserver/list.go b/pkg/kn/commands/source/apiserver/list.go index 7f19fffe5a..3337fc44a9 100644 --- a/pkg/kn/commands/source/apiserver/list.go +++ b/pkg/kn/commands/source/apiserver/list.go @@ -59,12 +59,7 @@ func NewAPIServerListCommand(p *commands.KnParams) *cobra.Command { listFlags.EnsureWithNamespace() } - printer, err := listFlags.ToPrinter() - if err != nil { - return nil - } - - err = printer.PrintObj(sourceList, cmd.OutOrStdout()) + err = listFlags.Print(sourceList, cmd.OutOrStdout()) if err != nil { return err } diff --git a/pkg/kn/commands/source/binding/create.go b/pkg/kn/commands/source/binding/create.go index 48eb0a1d71..891770563d 100644 --- a/pkg/kn/commands/source/binding/create.go +++ b/pkg/kn/commands/source/binding/create.go @@ -31,7 +31,7 @@ func NewBindingCreateCommand(p *commands.KnParams) *cobra.Command { var sinkFlags flags.SinkFlags cmd := &cobra.Command{ - Use: "create NAME --subject SCHEDULE --sink SINK --ce-override KEY=VALUE", + Use: "create NAME --subject SUBJECT --sink SINK --ce-override KEY=VALUE", Short: "Create a sink binding.", Example: ` # Create a sink binding which connects a deployment 'myapp' with a Knative service 'mysvc' diff --git a/pkg/kn/commands/source/binding/list.go b/pkg/kn/commands/source/binding/list.go index 2efc18c5a8..07f9110f14 100644 --- a/pkg/kn/commands/source/binding/list.go +++ b/pkg/kn/commands/source/binding/list.go @@ -58,12 +58,7 @@ func NewBindingListCommand(p *commands.KnParams) *cobra.Command { listFlags.EnsureWithNamespace() } - printer, err := listFlags.ToPrinter() - if err != nil { - return nil - } - - err = printer.PrintObj(sourceList, cmd.OutOrStdout()) + err = listFlags.Print(sourceList, cmd.OutOrStdout()) if err != nil { return err } diff --git a/pkg/kn/commands/source/ping/list.go b/pkg/kn/commands/source/ping/list.go index 9b112e564f..2fd285dfba 100644 --- a/pkg/kn/commands/source/ping/list.go +++ b/pkg/kn/commands/source/ping/list.go @@ -59,12 +59,7 @@ func NewPingListCommand(p *commands.KnParams) *cobra.Command { listFlags.EnsureWithNamespace() } - printer, err := listFlags.ToPrinter() - if err != nil { - return nil - } - - err = printer.PrintObj(sourceList, cmd.OutOrStdout()) + err = listFlags.Print(sourceList, cmd.OutOrStdout()) if err != nil { return err } diff --git a/pkg/kn/commands/trigger/list.go b/pkg/kn/commands/trigger/list.go index fa2543657e..c4cadbbc12 100644 --- a/pkg/kn/commands/trigger/list.go +++ b/pkg/kn/commands/trigger/list.go @@ -59,12 +59,7 @@ func NewTriggerListCommand(p *commands.KnParams) *cobra.Command { triggerListFlags.EnsureWithNamespace() } - printer, err := triggerListFlags.ToPrinter() - if err != nil { - return err - } - - err = printer.PrintObj(triggerList, cmd.OutOrStdout()) + err = triggerListFlags.Print(triggerList, cmd.OutOrStdout()) if err != nil { return err } diff --git a/pkg/kn/commands/version/version.go b/pkg/kn/commands/version/version.go index 7d243815b2..7d5149d7ad 100644 --- a/pkg/kn/commands/version/version.go +++ b/pkg/kn/commands/version/version.go @@ -31,12 +31,12 @@ var GitRevision string // update this var as we add more deps var apiVersions = map[string][]string{ "serving": { - "serving.knative.dev/v1 (knative-serving v0.13.0)", + "serving.knative.dev/v1 (knative-serving v0.13.2)", }, "eventing": { - "sources.eventing.knative.dev/v1alpha1 (knative-eventing v0.13.1)", - "sources.eventing.knative.dev/v1alpha2 (knative-eventing v0.13.1)", - "eventing.knative.dev/v1alpha1 (knative-eventing v0.13.1)", + "sources.eventing.knative.dev/v1alpha1 (knative-eventing v0.13.6)", + "sources.eventing.knative.dev/v1alpha2 (knative-eventing v0.13.6)", + "eventing.knative.dev/v1alpha1 (knative-eventing v0.13.6)", }, } diff --git a/pkg/kn/commands/wait_flags.go b/pkg/kn/commands/wait_flags.go index ca78368568..32e0e07c00 100644 --- a/pkg/kn/commands/wait_flags.go +++ b/pkg/kn/commands/wait_flags.go @@ -18,6 +18,8 @@ import ( "fmt" "github.com/spf13/cobra" + + knflags "knative.dev/client/pkg/kn/flags" ) // Default time out to use when waiting for reconciliation. It is deliberately very long as it is expected that @@ -29,9 +31,8 @@ const WaitDefaultTimeout = 600 type WaitFlags struct { // Timeout in seconds for how long to wait for a command to return TimeoutInSeconds int - - // If set then just apply resources and don't wait - NoWait bool + // If set then apply resources and wait for completion + Wait bool //TODO: deprecated variable should be removed with --async flag Async bool } @@ -40,11 +41,16 @@ type WaitFlags struct { // resources. Set `waitDefault` argument if the default behaviour is synchronous. // Use `what` for describing what is waited for. func (p *WaitFlags) AddConditionWaitFlags(command *cobra.Command, waitTimeoutDefault int, action, what, until string) { - waitUsage := fmt.Sprintf("%s %s and don't wait for it to be %s.", action, what, until) - //TODO: deprecated flag should be removed in next release - command.Flags().BoolVar(&p.Async, "async", false, "DEPRECATED: please use --no-wait instead. "+waitUsage) - command.Flags().BoolVar(&p.NoWait, "no-wait", false, waitUsage) + waitUsage := fmt.Sprintf("Wait for '%s %s' operation to be completed.", what, action) + waitDefault := true + // Special-case 'delete' command so it comes back to the user immediately + if action == "delete" { + waitDefault = false + } + //TODO: deprecated flag should be removed in next release + command.Flags().BoolVar(&p.Async, "async", !waitDefault, "DEPRECATED: please use --no-wait instead. "+knflags.InvertUsage(waitUsage)) + knflags.AddBothBoolFlagsUnhidden(command.Flags(), &p.Wait, "wait", "", waitDefault, waitUsage) timeoutUsage := fmt.Sprintf("Seconds to wait before giving up on waiting for %s to be %s.", what, until) command.Flags().IntVar(&p.TimeoutInSeconds, "wait-timeout", waitTimeoutDefault, timeoutUsage) } diff --git a/pkg/kn/commands/wait_flags_test.go b/pkg/kn/commands/wait_flags_test.go index 9c711d2c78..923c0d882d 100644 --- a/pkg/kn/commands/wait_flags_test.go +++ b/pkg/kn/commands/wait_flags_test.go @@ -15,34 +15,37 @@ package commands import ( + "fmt" "strings" "testing" + knflags "knative.dev/client/pkg/kn/flags" + "github.com/spf13/cobra" + "gotest.tools/assert" ) type waitTestCase struct { args []string timeoutExpected int - isNoWaitExpected bool + isWaitExpected bool isParseErrorExpected bool } //TODO: deprecated test should be removed with --async flag func TestAddWaitForReadyDeprecatedFlags(t *testing.T) { - for i, tc := range []waitTestCase{ - {[]string{"--async"}, 60, true, false}, - {[]string{}, 60, false, false}, - {[]string{"--wait-timeout=120"}, 120, false, false}, + {[]string{"--async"}, 60, false, false}, + {[]string{}, 60, true, false}, + {[]string{"--wait-timeout=120"}, 120, true, false}, // Can't be easily prevented, the timeout is just ignored in this case: - {[]string{"--async", "--wait-timeout=120"}, 120, true, false}, + {[]string{"--async", "--wait-timeout=120"}, 120, false, false}, {[]string{"--wait-timeout=bla"}, 0, true, true}, } { flags := &WaitFlags{} cmd := cobra.Command{} - flags.AddConditionWaitFlags(&cmd, 60, "Create", "service", "ready") + flags.AddConditionWaitFlags(&cmd, 60, "create", "service", "ready") err := cmd.ParseFlags(tc.args) if err != nil && !tc.isParseErrorExpected { @@ -54,8 +57,13 @@ func TestAddWaitForReadyDeprecatedFlags(t *testing.T) { if tc.isParseErrorExpected { continue } - if flags.Async != tc.isNoWaitExpected { - t.Errorf("%d: wrong async mode detected: %t (expected) != %t (actual)", i, tc.isNoWaitExpected, flags.Async) + + // reconcile to ensure wait, no-wait and async behaves as expected + err = knflags.ReconcileBoolFlags(cmd.Flags()) + assert.NilError(t, err) + + if flags.Async == tc.isWaitExpected { + t.Errorf("%d: wrong async mode detected: %t (expected) != %t (actual)", i, tc.isWaitExpected, flags.Async) } if flags.TimeoutInSeconds != tc.timeoutExpected { t.Errorf("%d: Invalid timeout set. %d (expected) != %d (actual)", i, tc.timeoutExpected, flags.TimeoutInSeconds) @@ -64,19 +72,17 @@ func TestAddWaitForReadyDeprecatedFlags(t *testing.T) { } func TestAddWaitForReadyFlags(t *testing.T) { - for i, tc := range []waitTestCase{ - {[]string{"--no-wait"}, 60, true, false}, - {[]string{}, 60, false, false}, - {[]string{"--wait-timeout=120"}, 120, false, false}, + {[]string{}, 60, true, false}, + {[]string{"--wait-timeout=120"}, 120, true, false}, // Can't be easily prevented, the timeout is just ignored in this case: - {[]string{"--no-wait", "--wait-timeout=120"}, 120, true, false}, + {[]string{"--no-wait", "--wait-timeout=120"}, 120, false, false}, {[]string{"--wait-timeout=bla"}, 0, true, true}, } { flags := &WaitFlags{} cmd := cobra.Command{} - flags.AddConditionWaitFlags(&cmd, 60, "Create", "service", "ready") + flags.AddConditionWaitFlags(&cmd, 60, "create", "service", "ready") err := cmd.ParseFlags(tc.args) if err != nil && !tc.isParseErrorExpected { @@ -88,8 +94,14 @@ func TestAddWaitForReadyFlags(t *testing.T) { if tc.isParseErrorExpected { continue } - if flags.NoWait != tc.isNoWaitExpected { - t.Errorf("%d: wrong wait mode detected: %t (expected) != %t (actual)", i, tc.isNoWaitExpected, flags.NoWait) + + // reconcile to ensure wait, no-wait and async behaves as expected + err = knflags.ReconcileBoolFlags(cmd.Flags()) + assert.NilError(t, err) + fmt.Println("wait value") + fmt.Println(flags.Wait) + if flags.Wait != tc.isWaitExpected { + t.Errorf("%d: wrong wait mode detected: %t (expected) != %t (actual)", i, tc.isWaitExpected, flags.Wait) } if flags.TimeoutInSeconds != tc.timeoutExpected { t.Errorf("%d: Invalid timeout set. %d (expected) != %d (actual)", i, tc.timeoutExpected, flags.TimeoutInSeconds) @@ -105,7 +117,7 @@ func TestAddWaitUsageMessage(t *testing.T) { if !strings.Contains(cmd.UsageString(), "blub") { t.Error("no type returned in usage") } - if !strings.Contains(cmd.UsageString(), "don't wait") { + if !strings.Contains(cmd.UsageString(), "Do not wait") { t.Error("wrong usage message") } if !strings.Contains(cmd.UsageString(), "60") { @@ -115,3 +127,12 @@ func TestAddWaitUsageMessage(t *testing.T) { t.Error("wrong until message") } } + +func TestAddWaitUsageDelete(t *testing.T) { + flags := &WaitFlags{} + cmd := cobra.Command{} + flags.AddConditionWaitFlags(&cmd, 60, "delete", "blub", "deleted") + if !strings.Contains(cmd.UsageString(), "completed. (default true)") { + t.Error("Delete has wrong default value for --no-wait") + } +} diff --git a/pkg/kn/flags/bool.go b/pkg/kn/flags/bool.go index 026638b822..111af22ec8 100644 --- a/pkg/kn/flags/bool.go +++ b/pkg/kn/flags/bool.go @@ -24,7 +24,10 @@ import ( "github.com/spf13/pflag" ) -var negPrefix = "no-" +var ( + negPrefix = "no-" + deprecatedPrefix = "DEPRECATED:" +) // AddBothBoolFlagsUnhidden is just like AddBothBoolFlags but shows both flags. func AddBothBoolFlagsUnhidden(f *pflag.FlagSet, p *bool, name, short string, value bool, usage string) { @@ -32,7 +35,7 @@ func AddBothBoolFlagsUnhidden(f *pflag.FlagSet, p *bool, name, short string, val negativeName := negPrefix + name f.BoolVarP(p, name, short, value, usage) - f.Bool(negativeName, !value, "Do not "+firstCharToLower(usage)) + f.Bool(negativeName, !value, InvertUsage(usage)) } // AddBothBoolFlags adds the given flag in both `--foo` and `--no-foo` variants. @@ -65,6 +68,20 @@ func ReconcileBoolFlags(f *pflag.FlagSet) error { if err != nil { return } + + // handle async flag + if flag.Name == "async" && flag.Changed { + if f.Lookup("wait").Changed || f.Lookup("no-wait").Changed { + err = fmt.Errorf("only one of (DEPRECATED) --async, --wait and --no-wait may be specified") + return + } + err = checkExplicitFalse(flag, "wait") + if err != nil { + return + } + f.Lookup("no-wait").Value.Set("true") + } + // Walk the "no-" versions of the flags. Make sure we didn't set // both, and set the positive value to the opposite of the "no-" // value if it exists. @@ -90,6 +107,7 @@ func ReconcileBoolFlags(f *pflag.FlagSet) error { err = checkExplicitFalse(positive, flag.Name) } } + }) return err } @@ -110,7 +128,13 @@ func checkExplicitFalse(f *pflag.Flag, betterFlag string) error { return nil } -func firstCharToLower(s string) string { +// FirstCharToLower converts first char in given string to lowercase +func FirstCharToLower(s string) string { r, n := utf8.DecodeRuneInString(s) return string(unicode.ToLower(r)) + s[n:] } + +// InvertUsage inverts the usage string with prefix "Do not" +func InvertUsage(usage string) string { + return "Do not " + FirstCharToLower(usage) +} diff --git a/pkg/kn/flags/bool_test.go b/pkg/kn/flags/bool_test.go index 955486be16..870bea2dfd 100644 --- a/pkg/kn/flags/bool_test.go +++ b/pkg/kn/flags/bool_test.go @@ -29,6 +29,13 @@ type boolPairTestCase struct { expectedErrText string } +type boolPairTestCaseDeprecated struct { + waitDefaultVal bool + flags []string + expectedResult bool + expectedErrText string +} + func TestBooleanPair(t *testing.T) { cases := []*boolPairTestCase{ {"foo", true, []string{}, true, ""}, @@ -69,3 +76,27 @@ func TestBooleanPair(t *testing.T) { } } } + +func TestBooleanPairWithDeprecatedSyncFlag(t *testing.T) { + cases := []*boolPairTestCaseDeprecated{ + {true, []string{}, false, ""}, + {true, []string{"--async"}, true, ""}, + {true, []string{"--async", "--no-wait"}, false, "only one of (DEPRECATED) --async, --wait and --no-wait may be specified"}, + // delete operation + {false, []string{""}, true, ""}, + {false, []string{"--async=false"}, false, "use --wait instead of providing \"false\" to --async"}, + } + for _, c := range cases { + var result, wait bool + f := &pflag.FlagSet{} + AddBothBoolFlags(f, &wait, "wait", "", c.waitDefaultVal, "set wait") + f.BoolVar(&result, "async", !c.waitDefaultVal, "DEPRECATED: set async") + f.Parse(c.flags) + err := ReconcileBoolFlags(f) + if c.expectedErrText != "" { + assert.ErrorContains(t, err, c.expectedErrText) + } else { + assert.Equal(t, result, c.expectedResult) + } + } +} diff --git a/pkg/printers/tableprinter.go b/pkg/printers/tableprinter.go index f51bc1390f..0f309b40d9 100644 --- a/pkg/printers/tableprinter.go +++ b/pkg/printers/tableprinter.go @@ -41,6 +41,10 @@ func NewTablePrinter(options PrintOptions) *HumanReadablePrinter { // PrintObj prints the obj in a human-friendly format according to the type of the obj. func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error { + if obj == nil { + return nil + } + w, found := output.(*tabwriter.Writer) if !found { w = NewTabWriter(output) diff --git a/pkg/serving/config_changes.go b/pkg/serving/config_changes.go index 3cec6e7531..ae7bf3f65b 100644 --- a/pkg/serving/config_changes.go +++ b/pkg/serving/config_changes.go @@ -198,6 +198,11 @@ func UpdateConcurrencyTarget(template *servingv1.RevisionTemplateSpec, target in return UpdateRevisionTemplateAnnotation(template, autoscaling.TargetAnnotationKey, strconv.Itoa(target)) } +// UpdateConcurrencyUtilization updates container target utilization percentage annotation +func UpdateConcurrencyUtilization(template *servingv1.RevisionTemplateSpec, target int) error { + return UpdateRevisionTemplateAnnotation(template, autoscaling.TargetUtilizationPercentageKey, strconv.Itoa(target)) +} + // UpdateConcurrencyLimit updates container concurrency limit func UpdateConcurrencyLimit(template *servingv1.RevisionTemplateSpec, limit int64) error { err := serving.ValidateContainerConcurrency(ptr.Int64(limit)).ViaField("spec.containerConcurrency") diff --git a/pkg/serving/config_changes_test.go b/pkg/serving/config_changes_test.go index 75879a58a5..e4f904eb84 100644 --- a/pkg/serving/config_changes_test.go +++ b/pkg/serving/config_changes_test.go @@ -33,7 +33,7 @@ import ( func TestUpdateAutoscalingAnnotations(t *testing.T) { template := &servingv1.RevisionTemplateSpec{} - updateConcurrencyConfiguration(template, 10, 100, 1000, 1000) + updateConcurrencyConfiguration(template, 10, 100, 1000, 1000, 50) annos := template.Annotations if annos[autoscaling.MinScaleAnnotationKey] != "10" { t.Error("minScale failed") @@ -51,9 +51,9 @@ func TestUpdateAutoscalingAnnotations(t *testing.T) { func TestUpdateInvalidAutoscalingAnnotations(t *testing.T) { template := &servingv1.RevisionTemplateSpec{} - updateConcurrencyConfiguration(template, 10, 100, 1000, 1000) + updateConcurrencyConfiguration(template, 10, 100, 1000, 1000, 50) // Update with invalid concurrency options - updateConcurrencyConfiguration(template, -1, -1, 0, -1) + updateConcurrencyConfiguration(template, -1, -1, 0, -1, 200) annos := template.Annotations if annos[autoscaling.MinScaleAnnotationKey] != "10" { t.Error("minScale failed") @@ -64,6 +64,9 @@ func TestUpdateInvalidAutoscalingAnnotations(t *testing.T) { if annos[autoscaling.TargetAnnotationKey] != "1000" { t.Error("target failed") } + if annos[autoscaling.TargetUtilizationPercentageKey] != "50" { + t.Error("concurrency utilization failed") + } if *template.Spec.ContainerConcurrency != 1000 { t.Error("limit failed") } @@ -723,9 +726,10 @@ func checkContainerConcurrency(t *testing.T, template *servingv1.RevisionTemplat } } -func updateConcurrencyConfiguration(template *servingv1.RevisionTemplateSpec, minScale int, maxScale int, target int, limit int) { +func updateConcurrencyConfiguration(template *servingv1.RevisionTemplateSpec, minScale, maxScale, target, limit, utilization int) { UpdateMinScale(template, minScale) UpdateMaxScale(template, maxScale) UpdateConcurrencyTarget(template, target) UpdateConcurrencyLimit(template, int64(limit)) + UpdateConcurrencyUtilization(template, utilization) } diff --git a/pkg/serving/revision_template.go b/pkg/serving/revision_template.go index a42add6e37..553ebc9e95 100644 --- a/pkg/serving/revision_template.go +++ b/pkg/serving/revision_template.go @@ -63,6 +63,11 @@ func ConcurrencyTarget(m *metav1.ObjectMeta) *int { return ret } +func ConcurrencyTargetUtilization(m *metav1.ObjectMeta) *int { + ret, _ := annotationAsInt(m, autoscaling.TargetUtilizationPercentageKey) + return ret +} + func AutoscaleWindow(m *metav1.ObjectMeta) string { return m.Annotations[autoscaling.WindowAnnotationKey] } diff --git a/pkg/util/unstructured.go b/pkg/util/unstructured.go new file mode 100644 index 0000000000..717f347f85 --- /dev/null +++ b/pkg/util/unstructured.go @@ -0,0 +1,64 @@ +// Copyright © 2020 The Knative Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "encoding/json" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +// ToUnstructuredList is to converts an object to unstructured.UnstructuredList. +// If the object is not a list type, it will convert to a single item UnstructuredList. +func ToUnstructuredList(obj runtime.Object) (*unstructured.UnstructuredList, error) { + unstructuredList := &unstructured.UnstructuredList{} + if meta.IsListType(obj) { + unstructuredList.SetGroupVersionKind(obj.GetObjectKind().GroupVersionKind()) + items, err := meta.ExtractList(obj) + if err != nil { + return nil, err + } + for _, obji := range items { + ud, err := toUnstructured(obji) + if err != nil { + return nil, err + } + unstructuredList.Items = append(unstructuredList.Items, *ud) + } + + } else { + ud, err := toUnstructured(obj) + if err != nil { + return nil, err + } + unstructuredList.Items = append(unstructuredList.Items, *ud) + } + return unstructuredList, nil + +} + +func toUnstructured(obj runtime.Object) (*unstructured.Unstructured, error) { + b, err := json.Marshal(obj) + if err != nil { + return nil, err + } + ud := &unstructured.Unstructured{} + if err := json.Unmarshal(b, ud); err != nil { + return nil, err + } + return ud, nil +} diff --git a/pkg/util/unstructured_test.go b/pkg/util/unstructured_test.go new file mode 100644 index 0000000000..8f40db4ea0 --- /dev/null +++ b/pkg/util/unstructured_test.go @@ -0,0 +1,86 @@ +// Copyright © 2019 The Knative Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "testing" + + "gotest.tools/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + servingv1 "knative.dev/serving/pkg/apis/serving/v1" +) + +func TestToUnstructuredList(t *testing.T) { + serviceList := servingv1.ServiceList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "List", + }, + Items: []servingv1.Service{createService("s1"), createService("s2")}, + } + expectedList := &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "apiVersion": string("v1"), + "kind": string("List"), + }, + } + expectedList.Items = []unstructured.Unstructured{createUnstructured("s1"), createUnstructured("s2")} + unstructedList, err := ToUnstructuredList(&serviceList) + assert.NilError(t, err) + assert.DeepEqual(t, unstructedList, expectedList) + + service1 := createService("s3") + expectedList = &unstructured.UnstructuredList{} + expectedList.Items = []unstructured.Unstructured{createUnstructured("s3")} + unstructedList, err = ToUnstructuredList(&service1) + assert.NilError(t, err) + assert.DeepEqual(t, unstructedList, expectedList) +} + +func createService(name string) servingv1.Service { + service := servingv1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "serving.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "default", + }, + } + return service +} + +func createUnstructured(name string) unstructured.Unstructured { + return unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "serving.knative.dev/v1", + "kind": "Service", + "metadata": map[string]interface{}{ + "namespace": "default", + "name": name, + "creationTimestamp": nil, + }, + "spec": map[string]interface{}{ + "template": map[string]interface{}{ + "metadata": map[string]interface{}{"creationTimestamp": nil}, + "spec": map[string]interface{}{"containers": nil}, + }, + }, + "status": map[string]interface{}{}, + }, + } +} diff --git a/test/e2e/basic_workflow_test.go b/test/e2e/basic_workflow_test.go index a1b51f0fa3..6337c735a8 100644 --- a/test/e2e/basic_workflow_test.go +++ b/test/e2e/basic_workflow_test.go @@ -23,60 +23,64 @@ import ( "gotest.tools/assert" + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) func TestBasicWorkflow(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() t.Log("returns no service before running tests") - test.serviceListEmpty(t, r) + serviceListEmpty(t, it, r) t.Log("create hello service and return no error") - test.serviceCreate(t, r, "hello") + serviceCreate(t, it, r, "hello") t.Log("return valid info about hello service") - test.serviceList(t, r, "hello") - test.serviceDescribe(t, r, "hello") + serviceList(t, it, r, "hello") + serviceDescribe(t, it, r, "hello") + + t.Log("return list --output name about hello service") + serviceListOutput(t, it, r, "hello") t.Log("update hello service's configuration and return no error") - test.serviceUpdate(t, r, "hello", "--env", "TARGET=kn", "--port", "8888") + serviceUpdate(t, it, r, "hello", "--env", "TARGET=kn", "--port", "8888") t.Log("create another service and return no error") - test.serviceCreate(t, r, "svc2") + serviceCreate(t, it, r, "svc2") t.Log("return a list of revisions associated with hello and svc2 services") - test.revisionListForService(t, r, "hello") - test.revisionListForService(t, r, "svc2") + revisionListForService(t, it, r, "hello") + revisionListForService(t, it, r, "svc2") t.Log("describe revision from hello service") - test.revisionDescribe(t, r, "hello") + revisionDescribe(t, it, r, "hello") t.Log("delete hello and svc2 services and return no error") - test.serviceDelete(t, r, "hello") - test.serviceDelete(t, r, "svc2") + serviceDelete(t, it, r, "hello") + serviceDelete(t, it, r, "svc2") t.Log("return no service after completing tests") - test.serviceListEmpty(t, r) + serviceListEmpty(t, it, r) } func TestWrongCommand(t *testing.T) { - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - out := kn{}.Run("source", "apiserver", "noverb", "--tag=0.13") + out := test.Kn{}.Run("source", "apiserver", "noverb", "--tag=0.13") assert.Check(t, util.ContainsAll(out.Stderr, "Error", "unknown subcommand", "noverb")) r.AssertError(out) - out = kn{}.Run("rev") + out = test.Kn{}.Run("rev") assert.Check(t, util.ContainsAll(out.Stderr, "Error", "unknown command", "rev")) r.AssertError(out) @@ -84,48 +88,54 @@ func TestWrongCommand(t *testing.T) { // ========================================================================== -func (test *e2eTest) serviceListEmpty(t *testing.T, r *KnRunResultCollector) { - out := test.kn.Run("service", "list") +func serviceListEmpty(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector) { + out := it.Kn().Run("service", "list") r.AssertNoError(out) assert.Check(t, util.ContainsAll(out.Stdout, "No services found.")) } -func (test *e2eTest) serviceCreate(t *testing.T, r *KnRunResultCollector, serviceName string) { - out := test.kn.Run("service", "create", serviceName, "--image", KnDefaultTestImage) +func serviceCreate(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) { + out := it.Kn().Run("service", "create", serviceName, "--image", test.KnDefaultTestImage) r.AssertNoError(out) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "service", serviceName, "creating", "namespace", test.kn.namespace, "ready")) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "service", serviceName, "creating", "namespace", it.Kn().Namespace(), "ready")) } -func (test *e2eTest) serviceList(t *testing.T, r *KnRunResultCollector, serviceName string) { - out := test.kn.Run("service", "list", serviceName) +func serviceList(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) { + out := it.Kn().Run("service", "list", serviceName) r.AssertNoError(out) assert.Check(t, util.ContainsAll(out.Stdout, serviceName)) } -func (test *e2eTest) serviceDescribe(t *testing.T, r *KnRunResultCollector, serviceName string) { - out := test.kn.Run("service", "describe", serviceName) +func serviceDescribe(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) { + out := it.Kn().Run("service", "describe", serviceName) r.AssertNoError(out) - assert.Assert(t, util.ContainsAll(out.Stdout, serviceName, test.kn.namespace, KnDefaultTestImage)) + assert.Assert(t, util.ContainsAll(out.Stdout, serviceName, it.Kn().Namespace(), test.KnDefaultTestImage)) assert.Assert(t, util.ContainsAll(out.Stdout, "Conditions", "ConfigurationsReady", "Ready", "RoutesReady")) assert.Assert(t, util.ContainsAll(out.Stdout, "Name", "Namespace", "URL", "Age", "Revisions")) } -func (test *e2eTest) serviceUpdate(t *testing.T, r *KnRunResultCollector, serviceName string, args ...string) { +func serviceListOutput(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) { + out := it.Kn().Run("service", "list", serviceName, "--output", "name") + r.AssertNoError(out) + assert.Check(t, util.ContainsAll(out.Stdout, serviceName, "service.serving.knative.dev")) +} + +func serviceUpdate(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string, args ...string) { fullArgs := append([]string{}, "service", "update", serviceName) fullArgs = append(fullArgs, args...) - out := test.kn.Run(fullArgs...) + out := it.Kn().Run(fullArgs...) r.AssertNoError(out) assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "updating", "service", serviceName, "ready")) } -func (test *e2eTest) serviceDelete(t *testing.T, r *KnRunResultCollector, serviceName string) { - out := test.kn.Run("service", "delete", serviceName) +func serviceDelete(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) { + out := it.Kn().Run("service", "delete", "--wait", serviceName) r.AssertNoError(out) - assert.Check(t, util.ContainsAll(out.Stdout, "Service", serviceName, "successfully deleted in namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAll(out.Stdout, "Service", serviceName, "successfully deleted in namespace", it.Kn().Namespace())) } -func (test *e2eTest) revisionListForService(t *testing.T, r *KnRunResultCollector, serviceName string) { - out := test.kn.Run("revision", "list", "-s", serviceName) +func revisionListForService(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) { + out := it.Kn().Run("revision", "list", "-s", serviceName) r.AssertNoError(out) outputLines := strings.Split(out.Stdout, "\n") // Ignore the last line because it is an empty string caused by splitting a line break @@ -136,10 +146,10 @@ func (test *e2eTest) revisionListForService(t *testing.T, r *KnRunResultCollecto } } -func (test *e2eTest) revisionDescribe(t *testing.T, r *KnRunResultCollector, serviceName string) { - revName := test.findRevision(t, r, serviceName) +func revisionDescribe(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) { + revName := findRevision(t, it, r, serviceName) - out := test.kn.Run("revision", "describe", revName) + out := it.Kn().Run("revision", "describe", revName) r.AssertNoError(out) - assert.Check(t, util.ContainsAll(out.Stdout, revName, test.kn.namespace, serviceName, "++ Ready", "TARGET=kn")) + assert.Check(t, util.ContainsAll(out.Stdout, revName, it.Kn().Namespace(), serviceName, "++ Ready", "TARGET=kn")) } diff --git a/test/e2e/ping_test.go b/test/e2e/ping_test.go index ea495b3c39..ea7feb288e 100644 --- a/test/e2e/ping_test.go +++ b/test/e2e/ping_test.go @@ -22,92 +22,99 @@ import ( "gotest.tools/assert" + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) func TestSourcePing(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() t.Log("Creating a testservice") - test.serviceCreate(t, r, "testsvc0") + serviceCreate(t, it, r, "testsvc0") t.Log("create Ping sources with a sink to a service") - test.pingSourceCreate(t, r, "testpingsource0", "* * * * */1", "ping", "svc:testsvc0") + pingSourceCreate(t, it, r, "testpingsource0", "* * * * */1", "ping", "svc:testsvc0") + pingSourceListOutputName(t, it, r, "testpingsource0") t.Log("delete Ping sources") - test.pingSourceDelete(t, r, "testpingsource0") + pingSourceDelete(t, it, r, "testpingsource0") t.Log("create Ping source with a missing sink service") - test.pingSourceCreateMissingSink(t, r, "testpingsource1", "* * * * */1", "ping", "svc:unknown") + pingSourceCreateMissingSink(t, it, r, "testpingsource1", "* * * * */1", "ping", "svc:unknown") t.Log("update Ping source sink service") - test.pingSourceCreate(t, r, "testpingsource2", "* * * * */1", "ping", "svc:testsvc0") - test.serviceCreate(t, r, "testsvc1") - test.pingSourceUpdateSink(t, r, "testpingsource2", "svc:testsvc1") + pingSourceCreate(t, it, r, "testpingsource2", "* * * * */1", "ping", "svc:testsvc0") + serviceCreate(t, it, r, "testsvc1") + pingSourceUpdateSink(t, it, r, "testpingsource2", "svc:testsvc1") jpSinkRefNameInSpec := "jsonpath={.spec.sink.ref.name}" - out, err := test.getResourceFieldsWithJSONPath("pingsource", "testpingsource2", jpSinkRefNameInSpec) + out, err := getResourceFieldsWithJSONPath(t, it, "pingsource", "testpingsource2", jpSinkRefNameInSpec) assert.NilError(t, err) assert.Equal(t, out, "testsvc1") t.Log("verify Ping source description") mymsg := "This is a message from Ping." - test.pingSourceCreate(t, r, "testpingsource3", "*/1 * * * *", mymsg, "svc:testsvc1") - test.verifyPingSourceDescribe(t, r, "testpingsource3", "*/1 * * * *", mymsg, "testsvc1") + pingSourceCreate(t, it, r, "testpingsource3", "*/1 * * * *", mymsg, "svc:testsvc1") + verifyPingSourceDescribe(t, it, r, "testpingsource3", "*/1 * * * *", mymsg, "testsvc1") } -func (test *e2eTest) pingSourceCreate(t *testing.T, r *KnRunResultCollector, sourceName string, schedule string, data string, sink string) { - out := test.kn.Run("source", "ping", "create", sourceName, +func pingSourceCreate(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, sourceName string, schedule string, data string, sink string) { + out := it.Kn().Run("source", "ping", "create", sourceName, "--schedule", schedule, "--data", data, "--sink", sink) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "ping", "source", sourceName, "created", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "ping", "source", sourceName, "created", "namespace", it.Kn().Namespace())) r.AssertNoError(out) } -func (test *e2eTest) pingSourceDelete(t *testing.T, r *KnRunResultCollector, sourceName string) { - out := test.kn.Run("source", "ping", "delete", sourceName) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "ping", "source", sourceName, "deleted", "namespace", test.kn.namespace)) +func pingSourceDelete(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, sourceName string) { + out := it.Kn().Run("source", "ping", "delete", sourceName) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "ping", "source", sourceName, "deleted", "namespace", it.Kn().Namespace())) r.AssertNoError(out) +} +func pingSourceListOutputName(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, pingSource string) { + out := it.Kn().Run("source", "ping", "list", "--output", "name") + r.AssertNoError(out) + assert.Check(t, util.ContainsAll(out.Stdout, pingSource)) } -func (test *e2eTest) pingSourceCreateMissingSink(t *testing.T, r *KnRunResultCollector, sourceName string, schedule string, data string, sink string) { - out := test.kn.Run("source", "ping", "create", sourceName, +func pingSourceCreateMissingSink(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, sourceName string, schedule string, data string, sink string) { + out := it.Kn().Run("source", "ping", "create", sourceName, "--schedule", schedule, "--data", data, "--sink", sink) assert.Check(t, util.ContainsAll(out.Stderr, "services.serving.knative.dev", "not found")) r.AssertError(out) } -func (test *e2eTest) pingSourceUpdateSink(t *testing.T, r *KnRunResultCollector, sourceName string, sink string) { - out := test.kn.Run("source", "ping", "update", sourceName, "--sink", sink) - assert.Check(t, util.ContainsAll(out.Stdout, sourceName, "updated", "namespace", test.kn.namespace)) +func pingSourceUpdateSink(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, sourceName string, sink string) { + out := it.Kn().Run("source", "ping", "update", sourceName, "--sink", sink) + assert.Check(t, util.ContainsAll(out.Stdout, sourceName, "updated", "namespace", it.Kn().Namespace())) r.AssertNoError(out) } -func (test *e2eTest) pingSourceCreateWithResources(t *testing.T, r *KnRunResultCollector, sourceName string, schedule string, data string, sink string, sa string, requestcpu string, requestmm string, limitcpu string, limitmm string) { - out := test.kn.Run("source", "ping", "create", sourceName, +func pingSourceCreateWithResources(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, sourceName string, schedule string, data string, sink string, sa string, requestcpu string, requestmm string, limitcpu string, limitmm string) { + out := it.Kn().Run("source", "ping", "create", sourceName, "--schedule", schedule, "--data", data, "--sink", sink, "--service-account", sa, "--requests-cpu", requestcpu, "--requests-memory", requestmm, "--limits-cpu", limitcpu, "--limits-memory", limitmm) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "ping", "source", sourceName, "created", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "ping", "source", sourceName, "created", "namespace", it.Kn().Namespace())) r.AssertNoError(out) } -func (test *e2eTest) pingSourceUpdateResources(t *testing.T, r *KnRunResultCollector, sourceName string, requestcpu string, requestmm string, limitcpu string, limitmm string) { - out := test.kn.Run("source", "ping", "update", sourceName, +func pingSourceUpdateResources(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, sourceName string, requestcpu string, requestmm string, limitcpu string, limitmm string) { + out := it.Kn().Run("source", "ping", "update", sourceName, "--requests-cpu", requestcpu, "--requests-memory", requestmm, "--limits-cpu", limitcpu, "--limits-memory", limitmm) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, sourceName, "updated", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, sourceName, "updated", "namespace", it.Kn().Namespace())) r.AssertNoError(out) } -func (test *e2eTest) verifyPingSourceDescribe(t *testing.T, r *KnRunResultCollector, sourceName string, schedule string, data string, sink string) { - out := test.kn.Run("source", "ping", "describe", sourceName) +func verifyPingSourceDescribe(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, sourceName string, schedule string, data string, sink string) { + out := it.Kn().Run("source", "ping", "describe", sourceName) assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, sourceName, schedule, data, sink)) r.AssertNoError(out) } diff --git a/test/e2e/plugins_test.go b/test/e2e/plugins_test.go index 78f72dcc65..d2631ea5f0 100644 --- a/test/e2e/plugins_test.go +++ b/test/e2e/plugins_test.go @@ -25,6 +25,7 @@ import ( "gotest.tools/assert" + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) @@ -95,7 +96,7 @@ func TestPluginWithoutLookup(t *testing.T) { pc, oldPath := setupPluginTestConfigWithNewPath(t) defer tearDownWithPath(pc, oldPath) - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() knFlags := []string{fmt.Sprintf("--plugins-dir=%s", pc.knPluginsDir), "--lookup-plugins=false"} @@ -114,7 +115,7 @@ func TestPluginWithoutLookup(t *testing.T) { func TestPluginWithLookup(t *testing.T) { - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() pc := pluginTestConfig{} @@ -132,7 +133,7 @@ func TestPluginWithLookup(t *testing.T) { func TestListPluginInPath(t *testing.T) { - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) pc, oldPath := setupPluginTestConfigWithNewPath(t) defer tearDownWithPath(pc, oldPath) @@ -145,7 +146,7 @@ func TestListPluginInPath(t *testing.T) { } func TestExecutePluginInPath(t *testing.T) { - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() pc, oldPath := setupPluginTestConfigWithNewPath(t) @@ -171,21 +172,21 @@ func tearDownWithPath(pc pluginTestConfig, oldPath string) { // Private -func listPlugin(t *testing.T, r *KnRunResultCollector, knFlags []string, expectedPlugins []string, unexpectedPlugins []string) { +func listPlugin(t *testing.T, r *test.KnRunResultCollector, knFlags []string, expectedPlugins []string, unexpectedPlugins []string) { knArgs := append(knFlags, "plugin", "list") - out := kn{}.Run(knArgs...) + out := test.Kn{}.Run(knArgs...) r.AssertNoError(out) assert.Check(t, util.ContainsAll(out.Stdout, expectedPlugins...)) assert.Check(t, util.ContainsNone(out.Stdout, unexpectedPlugins...)) } -func runPlugin(t *testing.T, r *KnRunResultCollector, knFlags []string, pluginName string, args []string, expectedOutput []string) { +func runPlugin(t *testing.T, r *test.KnRunResultCollector, knFlags []string, pluginName string, args []string, expectedOutput []string) { knArgs := append([]string{}, knFlags...) knArgs = append(knArgs, pluginName) knArgs = append(knArgs, args...) - out := kn{}.Run(knArgs...) + out := test.Kn{}.Run(knArgs...) r.AssertNoError(out) for _, output := range expectedOutput { assert.Check(t, util.ContainsAll(out.Stdout, output)) diff --git a/test/e2e/revision_test.go b/test/e2e/revision_test.go index bd0290c78c..f43a8663d0 100644 --- a/test/e2e/revision_test.go +++ b/test/e2e/revision_test.go @@ -24,99 +24,108 @@ import ( "testing" "gotest.tools/assert" + + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) func TestRevision(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() t.Log("create hello service and return no error") - test.serviceCreate(t, r, "hello") + serviceCreate(t, it, r, "hello") t.Log("describe revision from hello service with print flags") - revName := test.findRevision(t, r, "hello") - test.revisionDescribeWithPrintFlags(t, r, revName) + revName := findRevision(t, it, r, "hello") + revisionListOutputName(t, it, r, revName) + revisionDescribeWithPrintFlags(t, it, r, revName) t.Log("update hello service and increase revision count to 2") - test.serviceUpdate(t, r, "hello", "--env", "TARGET=kn", "--port", "8888") + serviceUpdate(t, it, r, "hello", "--env", "TARGET=kn", "--port", "8888") t.Log("show a list of revisions sorted by the count of configuration generation") - test.revisionListWithService(t, r, "hello") + revisionListWithService(t, it, r, "hello") t.Log("update hello service and increase revision count to 3") - test.serviceUpdate(t, r, "hello", "--env", "TARGET=kn", "--port", "8888") + serviceUpdate(t, it, r, "hello", "--env", "TARGET=kn", "--port", "8888") t.Log("delete three revisions with one revision a nonexistent") - existRevision1 := test.findRevisionByGeneration(t, r, "hello", 1) - existRevision2 := test.findRevisionByGeneration(t, r, "hello", 2) + existRevision1 := findRevisionByGeneration(t, it, r, "hello", 1) + existRevision2 := findRevisionByGeneration(t, it, r, "hello", 2) nonexistRevision := "hello-nonexist" - test.revisionMultipleDelete(t, r, existRevision1, existRevision2, nonexistRevision) + revisionMultipleDelete(t, it, r, existRevision1, existRevision2, nonexistRevision) t.Log("delete latest revision from hello service and return no error") - revName = test.findRevision(t, r, "hello") - test.revisionDelete(t, r, revName) + revName = findRevision(t, it, r, "hello") + revisionDelete(t, it, r, revName) t.Log("delete hello service and return no error") - test.serviceDelete(t, r, "hello") + serviceDelete(t, it, r, "hello") +} + +func revisionListOutputName(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, revisionName string) { + out := it.Kn().Run("revision", "list", "--output", "name") + r.AssertNoError(out) + assert.Check(t, util.ContainsAll(out.Stdout, revisionName, "revision.serving.knative.dev")) } -func (test *e2eTest) revisionListWithService(t *testing.T, r *KnRunResultCollector, serviceNames ...string) { +func revisionListWithService(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceNames ...string) { for _, svcName := range serviceNames { - confGen := test.findConfigurationGeneration(t, r, svcName) - out := test.kn.Run("revision", "list", "-s", svcName) + confGen := findConfigurationGeneration(t, it, r, svcName) + out := it.Kn().Run("revision", "list", "-s", svcName) r.AssertNoError(out) outputLines := strings.Split(out.Stdout, "\n") // Ignore the last line because it is an empty string caused by splitting a line break // at the end of the output string for _, line := range outputLines[1 : len(outputLines)-1] { - revName := test.findRevisionByGeneration(t, r, svcName, confGen) + revName := findRevisionByGeneration(t, it, r, svcName, confGen) assert.Check(t, util.ContainsAll(line, revName, svcName, strconv.Itoa(confGen))) confGen-- } if t.Failed() { - r.AddDump("service", svcName, test.namespace) + r.AddDump("service", svcName, it.Kn().Namespace()) } } } -func (test *e2eTest) revisionDelete(t *testing.T, r *KnRunResultCollector, revName string) { - out := test.kn.Run("revision", "delete", revName) - assert.Check(t, util.ContainsAll(out.Stdout, "Revision", revName, "deleted", "namespace", test.kn.namespace)) +func revisionDelete(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, revName string) { + out := it.Kn().Run("revision", "delete", "--wait", revName) + assert.Check(t, util.ContainsAll(out.Stdout, "Revision", revName, "deleted", "namespace", it.Kn().Namespace())) r.AssertNoError(out) } -func (test *e2eTest) revisionMultipleDelete(t *testing.T, r *KnRunResultCollector, existRevision1, existRevision2, nonexistRevision string) { - out := test.kn.Run("revision", "list") +func revisionMultipleDelete(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, existRevision1, existRevision2, nonexistRevision string) { + out := it.Kn().Run("revision", "list") r.AssertNoError(out) assert.Check(t, strings.Contains(out.Stdout, existRevision1), "Required revision1 does not exist") assert.Check(t, strings.Contains(out.Stdout, existRevision2), "Required revision2 does not exist") - out = test.kn.Run("revision", "delete", existRevision1, existRevision2, nonexistRevision) + out = it.Kn().Run("revision", "delete", existRevision1, existRevision2, nonexistRevision) r.AssertNoError(out) - assert.Check(t, util.ContainsAll(out.Stdout, "Revision", existRevision1, "deleted", "namespace", test.kn.namespace), "Failed to get 'deleted' first revision message") - assert.Check(t, util.ContainsAll(out.Stdout, "Revision", existRevision2, "deleted", "namespace", test.kn.namespace), "Failed to get 'deleted' second revision message") + assert.Check(t, util.ContainsAll(out.Stdout, "Revision", existRevision1, "deleted", "namespace", it.Kn().Namespace()), "Failed to get 'deleted' first revision message") + assert.Check(t, util.ContainsAll(out.Stdout, "Revision", existRevision2, "deleted", "namespace", it.Kn().Namespace()), "Failed to get 'deleted' second revision message") assert.Check(t, util.ContainsAll(out.Stdout, "revisions.serving.knative.dev", nonexistRevision, "not found"), "Failed to get 'not found' error") } -func (test *e2eTest) revisionDescribeWithPrintFlags(t *testing.T, r *KnRunResultCollector, revName string) { - out := test.kn.Run("revision", "describe", revName, "-o=name") +func revisionDescribeWithPrintFlags(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, revName string) { + out := it.Kn().Run("revision", "describe", revName, "-o=name") r.AssertNoError(out) expectedName := fmt.Sprintf("revision.serving.knative.dev/%s", revName) assert.Equal(t, strings.TrimSpace(out.Stdout), expectedName) } -func (test *e2eTest) findRevision(t *testing.T, r *KnRunResultCollector, serviceName string) string { - out := test.kn.Run("revision", "list", "-s", serviceName, "-o=jsonpath={.items[0].metadata.name}") +func findRevision(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) string { + out := it.Kn().Run("revision", "list", "-s", serviceName, "-o=jsonpath={.items[0].metadata.name}") r.AssertNoError(out) if strings.Contains(out.Stdout, "No resources") { t.Errorf("Could not find revision name.") @@ -124,9 +133,9 @@ func (test *e2eTest) findRevision(t *testing.T, r *KnRunResultCollector, service return out.Stdout } -func (test *e2eTest) findRevisionByGeneration(t *testing.T, r *KnRunResultCollector, serviceName string, generation int) string { - maxGen := test.findConfigurationGeneration(t, r, serviceName) - out := test.kn.Run("revision", "list", "-s", serviceName, +func findRevisionByGeneration(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string, generation int) string { + maxGen := findConfigurationGeneration(t, it, r, serviceName) + out := it.Kn().Run("revision", "list", "-s", serviceName, fmt.Sprintf("-o=jsonpath={.items[%d].metadata.name}", maxGen-generation)) r.AssertNoError(out) if strings.Contains(out.Stdout, "No resources found.") { @@ -135,8 +144,8 @@ func (test *e2eTest) findRevisionByGeneration(t *testing.T, r *KnRunResultCollec return out.Stdout } -func (test *e2eTest) findConfigurationGeneration(t *testing.T, r *KnRunResultCollector, serviceName string) int { - out := test.kn.Run("revision", "list", "-s", serviceName, "-o=jsonpath={.items[0].metadata.labels.serving\\.knative\\.dev/configurationGeneration}") +func findConfigurationGeneration(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) int { + out := it.Kn().Run("revision", "list", "-s", serviceName, "-o=jsonpath={.items[0].metadata.labels.serving\\.knative\\.dev/configurationGeneration}") r.AssertNoError(out) if out.Stdout == "" { t.Errorf("Could not find configuration generation.") diff --git a/test/e2e/route_test.go b/test/e2e/route_test.go index be989c8fe1..43b253f942 100644 --- a/test/e2e/route_test.go +++ b/test/e2e/route_test.go @@ -23,75 +23,86 @@ import ( "testing" "gotest.tools/assert" + + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) func TestRoute(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() t.Log("create hello service and return no error") - test.serviceCreate(t, r, "hello") + serviceCreate(t, it, r, "hello") t.Log("return a list of routes") - test.routeList(t, r) + routeList(t, it, r) t.Log("return a list of routes associated with hello service") - test.routeListWithArgument(t, r, "hello") + routeListWithArgument(t, it, r, "hello") + + t.Log("return a list of routes associated with hello service with -oname flag") + routeListOutputName(t, it, r, "hello") t.Log("return a list of routes associated with hello service with print flags") - test.routeListWithPrintFlags(t, r, "hello") + routeListWithPrintFlags(t, it, r, "hello") t.Log("describe route from hello service") - test.routeDescribe(t, r, "hello") + routeDescribe(t, it, r, "hello") t.Log("describe route from hello service with print flags") - test.routeDescribeWithPrintFlags(t, r, "hello") + routeDescribeWithPrintFlags(t, it, r, "hello") t.Log("delete hello service and return no error") - test.serviceDelete(t, r, "hello") + serviceDelete(t, it, r, "hello") } -func (test *e2eTest) routeList(t *testing.T, r *KnRunResultCollector) { - out := test.kn.Run("route", "list") +func routeList(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector) { + out := it.Kn().Run("route", "list") expectedHeaders := []string{"NAME", "URL", "READY"} assert.Check(t, util.ContainsAll(out.Stdout, expectedHeaders...)) r.AssertNoError(out) } -func (test *e2eTest) routeListWithArgument(t *testing.T, r *KnRunResultCollector, routeName string) { - out := test.kn.Run("route", "list", routeName) +func routeListOutputName(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, routeName string) { + out := it.Kn().Run("route", "list", "--output", "name") + r.AssertNoError(out) + assert.Check(t, util.ContainsAll(out.Stdout, routeName, "route.serving.knative.dev")) +} + +func routeListWithArgument(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, routeName string) { + out := it.Kn().Run("route", "list", routeName) assert.Check(t, util.ContainsAll(out.Stdout, routeName)) r.AssertNoError(out) } -func (test *e2eTest) routeDescribe(t *testing.T, r *KnRunResultCollector, routeName string) { - out := test.kn.Run("route", "describe", routeName) +func routeDescribe(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, routeName string) { + out := it.Kn().Run("route", "describe", routeName) assert.Check(t, util.ContainsAll(out.Stdout, - routeName, test.kn.namespace, "URL", "Service", "Traffic", "Targets", "Conditions")) + routeName, it.Kn().Namespace(), "URL", "Service", "Traffic", "Targets", "Conditions")) r.AssertNoError(out) } -func (test *e2eTest) routeDescribeWithPrintFlags(t *testing.T, r *KnRunResultCollector, routeName string) { - out := test.kn.Run("route", "describe", routeName, "-o=name") +func routeDescribeWithPrintFlags(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, routeName string) { + out := it.Kn().Run("route", "describe", routeName, "-o=name") expectedName := fmt.Sprintf("route.serving.knative.dev/%s", routeName) assert.Equal(t, strings.TrimSpace(out.Stdout), expectedName) r.AssertNoError(out) } -func (test *e2eTest) routeListWithPrintFlags(t *testing.T, r *KnRunResultCollector, names ...string) { - out := test.kn.Run("route", "list", "-o=jsonpath={.items[*].metadata.name}") +func routeListWithPrintFlags(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, names ...string) { + out := it.Kn().Run("route", "list", "-o=jsonpath={.items[*].metadata.name}") assert.Check(t, util.ContainsAll(out.Stdout, names...)) r.AssertNoError(out) } diff --git a/test/e2e/service_export_import_apply_test.go b/test/e2e/service_export_import_apply_test.go new file mode 100644 index 0000000000..64443f348c --- /dev/null +++ b/test/e2e/service_export_import_apply_test.go @@ -0,0 +1,269 @@ +// Copyright 2020 The Knative Authors + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build e2e +// +build !eventing + +package e2e + +import ( + "encoding/json" + "testing" + + "gotest.tools/assert" + "k8s.io/apimachinery/pkg/util/intstr" + "knative.dev/pkg/ptr" + "sigs.k8s.io/yaml" + + "knative.dev/client/lib/test" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + servingv1 "knative.dev/serving/pkg/apis/serving/v1" +) + +type expectedServiceOption func(*servingv1.Service) + +func TestServiceExportImportApply(t *testing.T) { + t.Parallel() + it, err := test.NewKnTest() + assert.NilError(t, err) + defer func() { + assert.NilError(t, it.Teardown()) + }() + + r := test.NewKnRunResultCollector(t) + defer r.DumpIfFailed() + + t.Log("create service with byo revision") + serviceCreateWithOptions(t, it, r, "hello", "--revision-name", "rev1") + + t.Log("export service and compare") + serviceExport(t, it, r, "hello", getSvc(withName("hello"), withRevisionName("hello-rev1"), withAnnotations()), "-o", "json") + + t.Log("update service - add env variable") + serviceUpdateWithOptions(t, it, r, "hello", "--env", "key1=val1", "--revision-name", "rev2", "--no-lock-to-digest") + serviceExport(t, it, r, "hello", getSvc(withName("hello"), withRevisionName("hello-rev2"), withEnv("key1", "val1")), "-o", "json") + serviceExportWithRevisions(t, it, r, "hello", getSvcListWithOneRevision(), "--with-revisions", "-o", "yaml") + + t.Log("update service with tag and split traffic") + serviceUpdateWithOptions(t, it, r, "hello", "--tag", "hello-rev1=candidate", "--traffic", "candidate=2%,@latest=98%") + serviceExportWithRevisions(t, it, r, "hello", getSvcListWithTags(), "--with-revisions", "-o", "yaml") + + t.Log("update service - untag, add env variable and traffic split") + serviceUpdateWithOptions(t, it, r, "hello", "--untag", "candidate") + serviceUpdateWithOptions(t, it, r, "hello", "--env", "key2=val2", "--revision-name", "rev3", "--traffic", "hello-rev1=30,hello-rev2=30,hello-rev3=40") + serviceExportWithRevisions(t, it, r, "hello", getSvcListWOTags(), "--with-revisions", "-o", "yaml") +} + +// Private methods + +func serviceExport(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string, expService servingv1.Service, options ...string) { + command := []string{"service", "export", serviceName} + command = append(command, options...) + out := it.Kn().Run(command...) + r.AssertNoError(out) + validateExportedService(t, it, out.Stdout, expService) +} + +func serviceExportWithRevisions(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string, expServiceList servingv1.ServiceList, options ...string) { + command := []string{"service", "export", serviceName} + command = append(command, options...) + out := it.Kn().Run(command...) + r.AssertNoError(out) + validateExportedServiceList(t, it, out.Stdout, expServiceList) +} + +// Private functions + +func validateExportedService(t *testing.T, it *test.KnTest, out string, expService servingv1.Service) { + actSvcJSON := servingv1.Service{} + err := json.Unmarshal([]byte(out), &actSvcJSON) + assert.NilError(t, err) + assert.DeepEqual(t, &expService, &actSvcJSON) +} + +func validateExportedServiceList(t *testing.T, it *test.KnTest, out string, expServiceList servingv1.ServiceList) { + actYaml := servingv1.ServiceList{} + err := yaml.Unmarshal([]byte(out), &actYaml) + assert.NilError(t, err) + assert.DeepEqual(t, &expServiceList, &actYaml) +} + +func getSvc(options ...expectedServiceOption) servingv1.Service { + svc := servingv1.Service{ + Spec: servingv1.ServiceSpec{ + ConfigurationSpec: servingv1.ConfigurationSpec{ + Template: servingv1.RevisionTemplateSpec{ + Spec: servingv1.RevisionSpec{ + ContainerConcurrency: ptr.Int64(int64(0)), + TimeoutSeconds: ptr.Int64(int64(300)), + PodSpec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "user-container", + Image: test.KnDefaultTestImage, + Resources: corev1.ResourceRequirements{}, + ReadinessProbe: &corev1.Probe{ + SuccessThreshold: int32(1), + Handler: corev1.Handler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt(0), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "serving.knative.dev/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "", + }, + } + for _, fn := range options { + fn(&svc) + } + return svc +} + +func getSvcListWOTags() servingv1.ServiceList { + return servingv1.ServiceList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "List", + }, + Items: []servingv1.Service{ + getSvc( + withName("hello"), + withRevisionName("hello-rev1"), + ), + getSvc( + withName("hello"), + withRevisionName("hello-rev2"), + withEnv("key1", "val1"), + ), + getSvc( + withName("hello"), + withRevisionName("hello-rev3"), + withEnv("key1", "val1"), withEnv("key2", "val2"), + withTrafficSplit([]string{"hello-rev1", "hello-rev2", "hello-rev3"}, []int{30, 30, 40}, []string{"", "", ""}), + ), + }, + } +} + +func getSvcListWithTags() servingv1.ServiceList { + return servingv1.ServiceList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "List", + }, + Items: []servingv1.Service{ + getSvc( + withName("hello"), + withRevisionName("hello-rev1"), + ), + getSvc( + withName("hello"), + withRevisionName("hello-rev2"), + withEnv("key1", "val1"), + withTrafficSplit([]string{"latest", "hello-rev1"}, []int{98, 2}, []string{"", "candidate"}), + ), + }, + } +} + +func getSvcListWithOneRevision() servingv1.ServiceList { + return servingv1.ServiceList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "List", + }, + Items: []servingv1.Service{ + getSvc( + withName("hello"), + withRevisionName("hello-rev2"), + withEnv("key1", "val1"), + ), + }, + } +} + +func withRevisionName(revName string) expectedServiceOption { + return func(svc *servingv1.Service) { + svc.Spec.ConfigurationSpec.Template.ObjectMeta.Name = revName + } +} + +func withAnnotations() expectedServiceOption { + return func(svc *servingv1.Service) { + svc.Spec.ConfigurationSpec.Template.ObjectMeta.Annotations = map[string]string{ + "client.knative.dev/user-image": "gcr.io/knative-samples/helloworld-go", + } + } +} + +func withName(name string) expectedServiceOption { + return func(svc *servingv1.Service) { + svc.ObjectMeta.Name = name + } +} + +func withEnv(key string, val string) expectedServiceOption { + return func(svc *servingv1.Service) { + env := []corev1.EnvVar{ + { + Name: key, + Value: val, + }, + } + currentEnv := svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].Env + if len(currentEnv) > 0 { + svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].Env = append(currentEnv, env...) + } else { + svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].Env = env + } + + } +} + +func withTrafficSplit(revisions []string, percentages []int, tags []string) expectedServiceOption { + return func(svc *servingv1.Service) { + var trafficTargets []servingv1.TrafficTarget + for i, rev := range revisions { + trafficTargets = append(trafficTargets, servingv1.TrafficTarget{ + Percent: ptr.Int64(int64(percentages[i])), + }) + if tags[i] != "" { + trafficTargets[i].Tag = tags[i] + } + if rev == "latest" { + trafficTargets[i].LatestRevision = ptr.Bool(true) + } else { + trafficTargets[i].RevisionName = rev + trafficTargets[i].LatestRevision = ptr.Bool(false) + } + } + svc.Spec.RouteSpec = servingv1.RouteSpec{ + Traffic: trafficTargets, + } + } +} diff --git a/test/e2e/service_options_test.go b/test/e2e/service_options_test.go index 011ac7d1dc..4f40fc8d09 100644 --- a/test/e2e/service_options_test.go +++ b/test/e2e/service_options_test.go @@ -26,76 +26,81 @@ import ( "testing" "gotest.tools/assert" + + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" + servingv1 "knative.dev/serving/pkg/apis/serving/v1" ) func TestServiceOptions(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) t.Log("create and validate service with concurrency options") defer r.DumpIfFailed() - test.serviceCreateWithOptions(t, r, "svc1", "--concurrency-limit", "250", "--concurrency-target", "300") - test.validateServiceConcurrencyTarget(t, r, "svc1", "300") - test.validateServiceConcurrencyLimit(t, r, "svc1", "250") + serviceCreateWithOptions(t, it, r, "svc1", "--concurrency-limit", "250", "--concurrency-target", "300", "--concurrency-utilization", "50") + validateServiceConcurrencyTarget(t, it, r, "svc1", "300") + validateServiceConcurrencyLimit(t, it, r, "svc1", "250") + validateServiceConcurrencyUtilization(t, it, r, "svc1", "50") t.Log("update and validate service with concurrency limit") - test.serviceUpdate(t, r, "svc1", "--concurrency-limit", "300") - test.validateServiceConcurrencyLimit(t, r, "svc1", "300") + serviceUpdate(t, it, r, "svc1", "--concurrency-limit", "300") + validateServiceConcurrencyLimit(t, it, r, "svc1", "300") t.Log("update concurrency options with invalid values for service") - out := test.kn.Run("service", "update", "svc1", "--concurrency-limit", "-1", "--concurrency-target", "0") + out := it.Kn().Run("service", "update", "svc1", "--concurrency-limit", "-1", "--concurrency-target", "0") r.AssertError(out) assert.Check(t, util.ContainsAll(out.Stderr, "invalid")) t.Log("returns steady concurrency options for service") - test.validateServiceConcurrencyLimit(t, r, "svc1", "300") - test.validateServiceConcurrencyTarget(t, r, "svc1", "300") + validateServiceConcurrencyLimit(t, it, r, "svc1", "300") + validateServiceConcurrencyTarget(t, it, r, "svc1", "300") + validateServiceConcurrencyUtilization(t, it, r, "svc1", "50") t.Log("delete service") - test.serviceDelete(t, r, "svc1") + serviceDelete(t, it, r, "svc1") t.Log("create and validate service with min/max scale options ") - test.serviceCreateWithOptions(t, r, "svc2", "--min-scale", "1", "--max-scale", "3") - test.validateServiceMinScale(t, r, "svc2", "1") - test.validateServiceMaxScale(t, r, "svc2", "3") + serviceCreateWithOptions(t, it, r, "svc2", "--min-scale", "1", "--max-scale", "3") + validateServiceMinScale(t, it, r, "svc2", "1") + validateServiceMaxScale(t, it, r, "svc2", "3") t.Log("update and validate service with max scale option") - test.serviceUpdate(t, r, "svc2", "--max-scale", "2") - test.validateServiceMaxScale(t, r, "svc2", "2") + serviceUpdate(t, it, r, "svc2", "--max-scale", "2") + validateServiceMaxScale(t, it, r, "svc2", "2") t.Log("delete service") - test.serviceDelete(t, r, "svc2") + serviceDelete(t, it, r, "svc2") t.Log("create, update and validate service with annotations") - test.serviceCreateWithOptions(t, r, "svc3", "--annotation", "alpha=wolf", "--annotation", "brave=horse") - test.validateServiceAnnotations(t, r, "svc3", map[string]string{"alpha": "wolf", "brave": "horse"}) - test.serviceUpdate(t, r, "svc3", "--annotation", "alpha=direwolf", "--annotation", "brave-") - test.validateServiceAnnotations(t, r, "svc3", map[string]string{"alpha": "direwolf", "brave": ""}) - test.serviceDelete(t, r, "svc3") + serviceCreateWithOptions(t, it, r, "svc3", "--annotation", "alpha=wolf", "--annotation", "brave=horse") + validateServiceAnnotations(t, it, r, "svc3", map[string]string{"alpha": "wolf", "brave": "horse"}) + serviceUpdate(t, it, r, "svc3", "--annotation", "alpha=direwolf", "--annotation", "brave-") + validateServiceAnnotations(t, it, r, "svc3", map[string]string{"alpha": "direwolf", "brave": ""}) + serviceDelete(t, it, r, "svc3") t.Log("create, update and validate service with autoscale window option") - test.serviceCreateWithOptions(t, r, "svc4", "--autoscale-window", "1m") - test.validateAutoscaleWindow(t, r, "svc4", "1m") - test.serviceUpdate(t, r, "svc4", "--autoscale-window", "15s") - test.validateAutoscaleWindow(t, r, "svc4", "15s") - test.serviceDelete(t, r, "svc4") + serviceCreateWithOptions(t, it, r, "svc4", "--autoscale-window", "1m") + validateAutoscaleWindow(t, it, r, "svc4", "1m") + serviceUpdate(t, it, r, "svc4", "--autoscale-window", "15s") + validateAutoscaleWindow(t, it, r, "svc4", "15s") + serviceDelete(t, it, r, "svc4") t.Log("create, update and validate service with cmd and arg options") - test.serviceCreateWithOptions(t, r, "svc5", "--cmd", "/go/bin/helloworld") - test.validateContainerField(t, r, "svc5", "command", "[/go/bin/helloworld]") - test.serviceUpdate(t, r, "svc5", "--arg", "myArg1", "--arg", "--myArg2") - test.validateContainerField(t, r, "svc5", "args", "[myArg1 --myArg2]") - test.serviceUpdate(t, r, "svc5", "--arg", "myArg1") - test.validateContainerField(t, r, "svc5", "args", "[myArg1]") + serviceCreateWithOptions(t, it, r, "svc5", "--cmd", "/go/bin/helloworld") + validateContainerField(t, it, r, "svc5", "command", "[/go/bin/helloworld]") + serviceUpdate(t, it, r, "svc5", "--arg", "myArg1", "--arg", "--myArg2") + validateContainerField(t, it, r, "svc5", "args", "[myArg1 --myArg2]") + serviceUpdate(t, it, r, "svc5", "--arg", "myArg1") + validateContainerField(t, it, r, "svc5", "args", "[myArg1]") t.Log("create, update and validate service with user defined") var uid int64 = 1000 @@ -103,79 +108,86 @@ func TestServiceOptions(t *testing.T) { uid, err = strconv.ParseInt(uids, 10, 64) assert.NilError(t, err) } - test.serviceCreateWithOptions(t, r, "svc6", "--user", strconv.FormatInt(uid, 10)) - test.validateUserId(t, r, "svc6", uid) - test.serviceUpdate(t, r, "svc6", "--user", strconv.FormatInt(uid+1, 10)) - test.validateUserId(t, r, "svc6", uid+1) + serviceCreateWithOptions(t, it, r, "svc6", "--user", strconv.FormatInt(uid, 10)) + validateUserId(t, it, r, "svc6", uid) + serviceUpdate(t, it, r, "svc6", "--user", strconv.FormatInt(uid+1, 10)) + validateUserId(t, it, r, "svc6", uid+1) } -func (test *e2eTest) serviceCreateWithOptions(t *testing.T, r *KnRunResultCollector, serviceName string, options ...string) { - command := []string{"service", "create", serviceName, "--image", KnDefaultTestImage} +func serviceCreateWithOptions(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string, options ...string) { + command := []string{"service", "create", serviceName, "--image", test.KnDefaultTestImage} command = append(command, options...) - out := test.kn.Run(command...) - assert.Check(t, util.ContainsAll(out.Stdout, "service", serviceName, "Creating", "namespace", test.kn.namespace, "Ready")) + out := it.Kn().Run(command...) + assert.Check(t, util.ContainsAll(out.Stdout, "service", serviceName, "Creating", "namespace", it.Kn().Namespace(), "Ready")) r.AssertNoError(out) } -func (test *e2eTest) validateServiceConcurrencyLimit(t *testing.T, r *KnRunResultCollector, serviceName, concurrencyLimit string) { +func validateServiceConcurrencyLimit(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName, concurrencyLimit string) { jsonpath := "jsonpath={.items[0].spec.template.spec.containerConcurrency}" - out := test.kn.Run("service", "list", serviceName, "-o", jsonpath) + out := it.Kn().Run("service", "list", serviceName, "-o", jsonpath) assert.Equal(t, out.Stdout, concurrencyLimit) r.AssertNoError(out) } -func (test *e2eTest) validateServiceConcurrencyTarget(t *testing.T, r *KnRunResultCollector, serviceName, concurrencyTarget string) { +func validateServiceConcurrencyTarget(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName, concurrencyTarget string) { jsonpath := "jsonpath={.items[0].spec.template.metadata.annotations.autoscaling\\.knative\\.dev/target}" - out := test.kn.Run("service", "list", serviceName, "-o", jsonpath) + out := it.Kn().Run("service", "list", serviceName, "-o", jsonpath) assert.Equal(t, out.Stdout, concurrencyTarget) r.AssertNoError(out) } -func (test *e2eTest) validateAutoscaleWindow(t *testing.T, r *KnRunResultCollector, serviceName, window string) { +func validateServiceConcurrencyUtilization(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName, concurrencyUtilization string) { + jsonpath := "jsonpath={.items[0].spec.template.metadata.annotations.autoscaling\\.knative\\.dev/targetUtilizationPercentage}" + out := it.Kn().Run("service", "list", serviceName, "-o", jsonpath) + assert.Equal(t, out.Stdout, concurrencyUtilization) + r.AssertNoError(out) +} + +func validateAutoscaleWindow(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName, window string) { jsonpath := "jsonpath={.items[0].spec.template.metadata.annotations.autoscaling\\.knative\\.dev/window}" - out := test.kn.Run("service", "list", serviceName, "-o", jsonpath) + out := it.Kn().Run("service", "list", serviceName, "-o", jsonpath) assert.Equal(t, out.Stdout, window) r.AssertNoError(out) } -func (test *e2eTest) validateServiceMinScale(t *testing.T, r *KnRunResultCollector, serviceName, minScale string) { +func validateServiceMinScale(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName, minScale string) { jsonpath := "jsonpath={.items[0].spec.template.metadata.annotations.autoscaling\\.knative\\.dev/minScale}" - out := test.kn.Run("service", "list", serviceName, "-o", jsonpath) + out := it.Kn().Run("service", "list", serviceName, "-o", jsonpath) assert.Equal(t, out.Stdout, minScale) r.AssertNoError(out) } -func (test *e2eTest) validateServiceMaxScale(t *testing.T, r *KnRunResultCollector, serviceName, maxScale string) { +func validateServiceMaxScale(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName, maxScale string) { jsonpath := "jsonpath={.items[0].spec.template.metadata.annotations.autoscaling\\.knative\\.dev/maxScale}" - out := test.kn.Run("service", "list", serviceName, "-o", jsonpath) + out := it.Kn().Run("service", "list", serviceName, "-o", jsonpath) assert.Equal(t, out.Stdout, maxScale) r.AssertNoError(out) } -func (test *e2eTest) validateServiceAnnotations(t *testing.T, r *KnRunResultCollector, serviceName string, annotations map[string]string) { +func validateServiceAnnotations(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string, annotations map[string]string) { metadataAnnotationsJsonpathFormat := "jsonpath={.metadata.annotations.%s}" templateAnnotationsJsonpathFormat := "jsonpath={.spec.template.metadata.annotations.%s}" for k, v := range annotations { - out := test.kn.Run("service", "describe", serviceName, "-o", fmt.Sprintf(metadataAnnotationsJsonpathFormat, k)) + out := it.Kn().Run("service", "describe", serviceName, "-o", fmt.Sprintf(metadataAnnotationsJsonpathFormat, k)) assert.Equal(t, v, out.Stdout) r.AssertNoError(out) - out = test.kn.Run("service", "describe", serviceName, "-o", fmt.Sprintf(templateAnnotationsJsonpathFormat, k)) + out = it.Kn().Run("service", "describe", serviceName, "-o", fmt.Sprintf(templateAnnotationsJsonpathFormat, k)) assert.Equal(t, v, out.Stdout) r.AssertNoError(out) } } -func (test *e2eTest) validateContainerField(t *testing.T, r *KnRunResultCollector, serviceName, field, expected string) { +func validateContainerField(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName, field, expected string) { jsonpath := fmt.Sprintf("jsonpath={.items[0].spec.template.spec.containers[0].%s}", field) - out := test.kn.Run("service", "list", serviceName, "-o", jsonpath) + out := it.Kn().Run("service", "list", serviceName, "-o", jsonpath) assert.Equal(t, out.Stdout, expected) r.AssertNoError(out) } -func (test *e2eTest) validateUserId(t *testing.T, r *KnRunResultCollector, serviceName string, uid int64) { - out := test.kn.Run("service", "describe", serviceName, "-ojson") +func validateUserId(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string, uid int64) { + out := it.Kn().Run("service", "describe", serviceName, "-ojson") data := json.NewDecoder(strings.NewReader(out.Stdout)) data.UseNumber() var service servingv1.Service diff --git a/test/e2e/service_test.go b/test/e2e/service_test.go index 8ae8078763..21ed78c472 100644 --- a/test/e2e/service_test.go +++ b/test/e2e/service_test.go @@ -23,110 +23,112 @@ import ( "testing" "gotest.tools/assert" + + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" "knative.dev/serving/pkg/apis/serving" ) func TestService(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() t.Log("create hello service, delete, and try to create duplicate and get service already exists error") - test.serviceCreate(t, r, "hello") - test.serviceCreatePrivate(t, r, "hello-private") - test.serviceCreateDuplicate(t, r, "hello-private") + serviceCreate(t, it, r, "hello") + serviceCreatePrivate(t, it, r, "hello-private") + serviceCreateDuplicate(t, it, r, "hello-private") t.Log("return valid info about hello service with print flags") - test.serviceDescribeWithPrintFlags(t, r, "hello") + serviceDescribeWithPrintFlags(t, it, r, "hello") t.Log("delete hello service repeatedly and get an error") - test.serviceDelete(t, r, "hello") - test.serviceDeleteNonexistent(t, r, "hello") + serviceDelete(t, it, r, "hello") + serviceDeleteNonexistent(t, it, r, "hello") t.Log("delete two services with a service nonexistent") - test.serviceCreate(t, r, "hello") - test.serviceMultipleDelete(t, r, "hello", "bla123") + serviceCreate(t, it, r, "hello") + serviceMultipleDelete(t, it, r, "hello", "bla123") t.Log("create service private and make public") - test.serviceCreatePrivateUpdatePublic(t, r, "hello-private-public") + serviceCreatePrivateUpdatePublic(t, it, r, "hello-private-public") } -func (test *e2eTest) serviceCreatePrivate(t *testing.T, r *KnRunResultCollector, serviceName string) { - out := test.kn.Run("service", "create", serviceName, - "--image", KnDefaultTestImage, "--cluster-local") +func serviceCreatePrivate(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) { + out := it.Kn().Run("service", "create", serviceName, + "--image", test.KnDefaultTestImage, "--cluster-local") r.AssertNoError(out) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "service", serviceName, "creating", "namespace", test.kn.namespace, "ready")) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "service", serviceName, "creating", "namespace", it.Kn().Namespace(), "ready")) - out = test.kn.Run("service", "describe", serviceName, "--verbose") + out = it.Kn().Run("service", "describe", serviceName, "--verbose") r.AssertNoError(out) assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, serving.VisibilityLabelKey, serving.VisibilityClusterLocal)) } -func (test *e2eTest) serviceCreatePrivateUpdatePublic(t *testing.T, r *KnRunResultCollector, serviceName string) { - out := test.kn.Run("service", "create", serviceName, - "--image", KnDefaultTestImage, "--cluster-local") +func serviceCreatePrivateUpdatePublic(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) { + out := it.Kn().Run("service", "create", serviceName, + "--image", test.KnDefaultTestImage, "--cluster-local") r.AssertNoError(out) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "service", serviceName, "creating", "namespace", test.kn.namespace, "ready")) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "service", serviceName, "creating", "namespace", it.Kn().Namespace(), "ready")) - out = test.kn.Run("service", "describe", serviceName, "--verbose") + out = it.Kn().Run("service", "describe", serviceName, "--verbose") r.AssertNoError(out) assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, serving.VisibilityLabelKey, serving.VisibilityClusterLocal)) - out = test.kn.Run("service", "update", serviceName, - "--image", KnDefaultTestImage, "--no-cluster-local") + out = it.Kn().Run("service", "update", serviceName, + "--image", test.KnDefaultTestImage, "--no-cluster-local") r.AssertNoError(out) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "service", serviceName, "updated", "namespace", test.kn.namespace, "ready")) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "service", serviceName, "updated", "namespace", it.Kn().Namespace(), "ready")) - out = test.kn.Run("service", "describe", serviceName, "--verbose") + out = it.Kn().Run("service", "describe", serviceName, "--verbose") r.AssertNoError(out) assert.Check(t, util.ContainsNone(out.Stdout, serving.VisibilityLabelKey, serving.VisibilityClusterLocal)) } -func (test *e2eTest) serviceCreateDuplicate(t *testing.T, r *KnRunResultCollector, serviceName string) { - out := test.kn.Run("service", "list", serviceName) +func serviceCreateDuplicate(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) { + out := it.Kn().Run("service", "list", serviceName) r.AssertNoError(out) assert.Check(t, strings.Contains(out.Stdout, serviceName), "The service does not exist yet") - out = test.kn.Run("service", "create", serviceName, "--image", KnDefaultTestImage) + out = it.Kn().Run("service", "create", serviceName, "--image", test.KnDefaultTestImage) r.AssertError(out) assert.Check(t, util.ContainsAll(out.Stderr, "the service already exists")) } -func (test *e2eTest) serviceDescribeWithPrintFlags(t *testing.T, r *KnRunResultCollector, serviceName string) { - out := test.kn.Run("service", "describe", serviceName, "-o=name") +func serviceDescribeWithPrintFlags(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) { + out := it.Kn().Run("service", "describe", serviceName, "-o=name") r.AssertNoError(out) expectedName := fmt.Sprintf("service.serving.knative.dev/%s", serviceName) assert.Equal(t, strings.TrimSpace(out.Stdout), expectedName) } -func (test *e2eTest) serviceDeleteNonexistent(t *testing.T, r *KnRunResultCollector, serviceName string) { - out := test.kn.Run("service", "list", serviceName) +func serviceDeleteNonexistent(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string) { + out := it.Kn().Run("service", "list", serviceName) r.AssertNoError(out) assert.Check(t, !strings.Contains(out.Stdout, serviceName), "The service exists") - out = test.kn.Run("service", "delete", serviceName) + out = it.Kn().Run("service", "delete", serviceName) r.AssertNoError(out) assert.Check(t, util.ContainsAll(out.Stdout, "hello", "not found"), "Failed to get 'not found' error") } -func (test *e2eTest) serviceMultipleDelete(t *testing.T, r *KnRunResultCollector, existService, nonexistService string) { - out := test.kn.Run("service", "list") +func serviceMultipleDelete(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, existService, nonexistService string) { + out := it.Kn().Run("service", "list") r.AssertNoError(out) assert.Check(t, strings.Contains(out.Stdout, existService), "The service ", existService, " does not exist (but is expected to exist)") assert.Check(t, !strings.Contains(out.Stdout, nonexistService), "The service", nonexistService, " exists (but is supposed to be not)") - out = test.kn.Run("service", "delete", existService, nonexistService) + out = it.Kn().Run("service", "delete", existService, nonexistService) r.AssertNoError(out) - expectedSuccess := fmt.Sprintf(`Service '%s' successfully deleted in namespace '%s'.`, existService, test.kn.namespace) + expectedSuccess := fmt.Sprintf(`Service '%s' successfully deleted in namespace '%s'.`, existService, it.Kn().Namespace()) expectedErr := fmt.Sprintf(`services.serving.knative.dev "%s" not found`, nonexistService) assert.Check(t, strings.Contains(out.Stdout, expectedSuccess), "Failed to get 'successfully deleted' message") assert.Check(t, strings.Contains(out.Stdout, expectedErr), "Failed to get 'not found' error") diff --git a/test/e2e/sinkprefix_test.go b/test/e2e/sinkprefix_test.go index dddad853b0..78f52a3d11 100644 --- a/test/e2e/sinkprefix_test.go +++ b/test/e2e/sinkprefix_test.go @@ -22,6 +22,8 @@ import ( "testing" "gotest.tools/assert" + + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) @@ -57,13 +59,13 @@ func (tc *sinkprefixTestConfig) teardown() { func TestSinkPrefixConfig(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() tc := sinkprefixTestConfig{} @@ -71,22 +73,22 @@ func TestSinkPrefixConfig(t *testing.T) { defer tc.teardown() t.Log("Creating a testservice") - test.serviceCreate(t, r, "testsvc0") + serviceCreate(t, it, r, "testsvc0") t.Log("create Ping sources with a sink to hello:testsvc0") - test.pingSourceCreateWithConfig(t, r, "testpingsource0", "* * * * */1", "ping", "hello:testsvc0", tc.knConfigPath) + pingSourceCreateWithConfig(t, it, r, "testpingsource0", "* * * * */1", "ping", "hello:testsvc0", tc.knConfigPath) jpSinkRefNameInSpec := "jsonpath={.spec.sink.ref.name}" - out, err := test.getResourceFieldsWithJSONPath("pingsource", "testpingsource0", jpSinkRefNameInSpec) + out, err := getResourceFieldsWithJSONPath(t, it, "pingsource", "testpingsource0", jpSinkRefNameInSpec) assert.NilError(t, err) assert.Equal(t, out, "testsvc0") t.Log("delete Ping sources") - test.pingSourceDelete(t, r, "testpingsource0") + pingSourceDelete(t, it, r, "testpingsource0") } -func (test *e2eTest) pingSourceCreateWithConfig(t *testing.T, r *KnRunResultCollector, sourceName string, schedule string, data string, sink string, config string) { - out := test.kn.Run("source", "ping", "create", sourceName, +func pingSourceCreateWithConfig(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, sourceName string, schedule string, data string, sink string, config string) { + out := it.Kn().Run("source", "ping", "create", sourceName, "--schedule", schedule, "--data", data, "--sink", sink, "--config", config) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "ping", "source", sourceName, "created", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "ping", "source", sourceName, "created", "namespace", it.Kn().Namespace())) r.AssertNoError(out) } diff --git a/test/e2e/source_apiserver_test.go b/test/e2e/source_apiserver_test.go index cf94091f1e..bb1c5f0c14 100644 --- a/test/e2e/source_apiserver_test.go +++ b/test/e2e/source_apiserver_test.go @@ -25,6 +25,7 @@ import ( "github.com/pkg/errors" "gotest.tools/assert" + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) @@ -37,117 +38,123 @@ const ( func TestSourceApiServer(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - err1 := test.tearDownForSourceApiServer() - err2 := test.Teardown() + err1 := tearDownForSourceApiServer(t, it) + err2 := it.Teardown() assert.NilError(t, err1) assert.NilError(t, err2) }() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - test.setupForSourceApiServer(t) - test.serviceCreate(t, r, "testsvc0") + setupForSourceApiServer(t, it) + serviceCreate(t, it, r, "testsvc0") t.Log("create apiserver sources with a sink to a service") - test.apiServerSourceCreate(t, r, "testapisource0", "Event:v1:true", "testsa", "svc:testsvc0") - test.apiServerSourceCreate(t, r, "testapisource1", "Event:v1", "testsa", "svc:testsvc0") + apiServerSourceCreate(t, it, r, "testapisource0", "Event:v1:true", "testsa", "svc:testsvc0") + apiServerSourceCreate(t, it, r, "testapisource1", "Event:v1", "testsa", "svc:testsvc0") + apiServerSourceListOutputName(t, it, r, "testapisource0", "testapisource1") t.Log("list sources") - output := test.sourceList(t, r) + output := sourceList(t, it, r) assert.Check(t, util.ContainsAll(output, "NAME", "TYPE", "RESOURCE", "SINK", "READY")) assert.Check(t, util.ContainsAll(output, "testapisource0", "ApiServerSource", "apiserversources.sources.knative.dev", "svc:testsvc0")) assert.Check(t, util.ContainsAll(output, "testapisource1", "ApiServerSource", "apiserversources.sources.knative.dev", "svc:testsvc0")) t.Log("list sources in YAML format") - output = test.sourceList(t, r, "-oyaml") + output = sourceList(t, it, r, "-oyaml") assert.Check(t, util.ContainsAll(output, "testapisource1", "ApiServerSource", "Service", "testsvc0")) t.Log("delete apiserver sources") - test.apiServerSourceDelete(t, r, "testapisource0") - test.apiServerSourceDelete(t, r, "testapisource1") + apiServerSourceDelete(t, it, r, "testapisource0") + apiServerSourceDelete(t, it, r, "testapisource1") t.Log("create apiserver source with a missing sink service") - test.apiServerSourceCreateMissingSink(t, r, "testapisource2", "Event:v1:true", "testsa", "svc:unknown") + apiServerSourceCreateMissingSink(t, it, r, "testapisource2", "Event:v1:true", "testsa", "svc:unknown") t.Log("update apiserver source sink service") - test.apiServerSourceCreate(t, r, "testapisource3", "Event:v1:true", "testsa", "svc:testsvc0") - test.serviceCreate(t, r, "testsvc1") - test.apiServerSourceUpdateSink(t, r, "testapisource3", "svc:testsvc1") + apiServerSourceCreate(t, it, r, "testapisource3", "Event:v1:true", "testsa", "svc:testsvc0") + serviceCreate(t, it, r, "testsvc1") + apiServerSourceUpdateSink(t, it, r, "testapisource3", "svc:testsvc1") jpSinkRefNameInSpec := "jsonpath={.spec.sink.ref.name}" - out, err := test.getResourceFieldsWithJSONPath("apiserversource.sources.knative.dev", "testapisource3", jpSinkRefNameInSpec) + out, err := getResourceFieldsWithJSONPath(t, it, "apiserversource.sources.knative.dev", "testapisource3", jpSinkRefNameInSpec) assert.NilError(t, err) assert.Equal(t, out, "testsvc1") // TODO(navidshaikh): Verify the source's status with synchronous create/update } -func (test *e2eTest) apiServerSourceCreate(t *testing.T, r *KnRunResultCollector, sourceName string, resources string, sa string, sink string) { - out := test.kn.Run("source", "apiserver", "create", sourceName, "--resource", resources, "--service-account", sa, "--sink", sink) +func apiServerSourceCreate(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, sourceName string, resources string, sa string, sink string) { + out := it.Kn().Run("source", "apiserver", "create", sourceName, "--resource", resources, "--service-account", sa, "--sink", sink) r.AssertNoError(out) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "apiserver", "source", sourceName, "created", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "apiserver", "source", sourceName, "created", "namespace", it.Kn().Namespace())) } -func (test *e2eTest) apiServerSourceCreateMissingSink(t *testing.T, r *KnRunResultCollector, sourceName string, resources string, sa string, sink string) { - out := test.kn.Run("source", "apiserver", "create", sourceName, "--resource", resources, "--service-account", sa, "--sink", sink) +func apiServerSourceListOutputName(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, apiserverSources ...string) { + out := it.Kn().Run("source", "apiserver", "list", "--output", "name") + r.AssertNoError(out) + assert.Check(t, util.ContainsAll(out.Stdout, apiserverSources...)) +} + +func apiServerSourceCreateMissingSink(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, sourceName string, resources string, sa string, sink string) { + out := it.Kn().Run("source", "apiserver", "create", sourceName, "--resource", resources, "--service-account", sa, "--sink", sink) r.AssertError(out) assert.Check(t, util.ContainsAll(out.Stderr, "services.serving.knative.dev", "not found")) } -func (test *e2eTest) apiServerSourceDelete(t *testing.T, r *KnRunResultCollector, sourceName string) { - out := test.kn.Run("source", "apiserver", "delete", sourceName) +func apiServerSourceDelete(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, sourceName string) { + out := it.Kn().Run("source", "apiserver", "delete", sourceName) r.AssertNoError(out) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "apiserver", "source", sourceName, "deleted", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "apiserver", "source", sourceName, "deleted", "namespace", it.Kn().Namespace())) } -func (test *e2eTest) setupForSourceApiServer(t *testing.T) { - _, err := kubectl{test.kn.namespace}.Run("create", "serviceaccount", testServiceAccount) +func setupForSourceApiServer(t *testing.T, it *test.KnTest) { + _, err := test.NewKubectl(it.Kn().Namespace()).Run("create", "serviceaccount", testServiceAccount) assert.NilError(t, err) - _, err = kubectl{}.Run("create", "clusterrole", clusterRolePrefix+test.kn.namespace, "--verb=get,list,watch", "--resource=events,namespaces") + _, err = test.Kubectl{}.Run("create", "clusterrole", clusterRolePrefix+it.Kn().Namespace(), "--verb=get,list,watch", "--resource=events,namespaces") assert.NilError(t, err) - _, err = kubectl{}.Run( + _, err = test.Kubectl{}.Run( "create", "clusterrolebinding", - clusterRoleBindingPrefix+test.kn.namespace, - "--clusterrole="+clusterRolePrefix+test.kn.namespace, - "--serviceaccount="+test.kn.namespace+":"+testServiceAccount) + clusterRoleBindingPrefix+it.Kn().Namespace(), + "--clusterrole="+clusterRolePrefix+it.Kn().Namespace(), + "--serviceaccount="+it.Kn().Namespace()+":"+testServiceAccount) assert.NilError(t, err) } -func (test *e2eTest) tearDownForSourceApiServer() error { - +func tearDownForSourceApiServer(t *testing.T, it *test.KnTest) error { saCmd := []string{"delete", "serviceaccount", testServiceAccount} - _, err := kubectl{test.kn.namespace}.Run(saCmd...) + _, err := test.NewKubectl(it.Kn().Namespace()).Run(saCmd...) if err != nil { return errors.Wrap(err, fmt.Sprintf("Error executing '%s'", strings.Join(saCmd, " "))) } - crCmd := []string{"delete", "clusterrole", clusterRolePrefix + test.kn.namespace} - _, err = kubectl{}.Run(crCmd...) + crCmd := []string{"delete", "clusterrole", clusterRolePrefix + it.Kn().Namespace()} + _, err = test.Kubectl{}.Run(crCmd...) if err != nil { return errors.Wrap(err, fmt.Sprintf("Error executing '%s'", strings.Join(saCmd, " "))) } - crbCmd := []string{"delete", "clusterrolebinding", clusterRoleBindingPrefix + test.kn.namespace} - _, err = kubectl{}.Run(crbCmd...) + crbCmd := []string{"delete", "clusterrolebinding", clusterRoleBindingPrefix + it.Kn().Namespace()} + _, err = test.Kubectl{}.Run(crbCmd...) if err != nil { return errors.Wrap(err, fmt.Sprintf("Error executing '%s'", strings.Join(saCmd, " "))) } return nil } -func (test *e2eTest) apiServerSourceUpdateSink(t *testing.T, r *KnRunResultCollector, sourceName string, sink string) { - out := test.kn.Run("source", "apiserver", "update", sourceName, "--sink", sink) +func apiServerSourceUpdateSink(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, sourceName string, sink string) { + out := it.Kn().Run("source", "apiserver", "update", sourceName, "--sink", sink) r.AssertNoError(out) - assert.Check(t, util.ContainsAll(out.Stdout, sourceName, "updated", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAll(out.Stdout, sourceName, "updated", "namespace", it.Kn().Namespace())) } -func (test *e2eTest) getResourceFieldsWithJSONPath(resource, name, jsonpath string) (string, error) { - out, err := kubectl{test.kn.namespace}.Run("get", resource, name, "-o", jsonpath, "-n", test.kn.namespace) +func getResourceFieldsWithJSONPath(t *testing.T, it *test.KnTest, resource, name, jsonpath string) (string, error) { + out, err := test.NewKubectl(it.Kn().Namespace()).Run("get", resource, name, "-o", jsonpath, "-n", it.Kn().Namespace()) if err != nil { return "", err } diff --git a/test/e2e/source_binding_test.go b/test/e2e/source_binding_test.go index a44240ccf7..f89639ae45 100644 --- a/test/e2e/source_binding_test.go +++ b/test/e2e/source_binding_test.go @@ -21,52 +21,61 @@ import ( "testing" "gotest.tools/assert" + + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) func TestSourceBinding(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - test.serviceCreate(t, r, "testsvc0") + serviceCreate(t, it, r, "testsvc0") t.Log("create source binding") - test.sourceBindingCreate(t, r, "my-binding0", "Deployment:apps/v1:myapp", "svc:testsvc0") + sourceBindingCreate(t, it, r, "my-binding0", "Deployment:apps/v1:myapp", "svc:testsvc0") + sourceBindingListOutputName(t, it, r, "my-binding0") t.Log("delete source binding") - test.sourceBindingDelete(t, r, "my-binding0") + sourceBindingDelete(t, it, r, "my-binding0") t.Log("update source binding") - test.sourceBindingCreate(t, r, "my-binding1", "Deployment:apps/v1:myapp", "svc:testsvc0") - test.serviceCreate(t, r, "testsvc1") - test.sourceBindingUpdate(t, r, "my-binding1", "Deployment:apps/v1:myapp", "svc:testsvc1") + sourceBindingCreate(t, it, r, "my-binding1", "Deployment:apps/v1:myapp", "svc:testsvc0") + serviceCreate(t, it, r, "testsvc1") + sourceBindingUpdate(t, it, r, "my-binding1", "Deployment:apps/v1:myapp", "svc:testsvc1") jpSinkRefNameInSpec := "jsonpath={.spec.sink.ref.name}" - out, err := test.getResourceFieldsWithJSONPath("sinkbindings.sources.knative.dev", "my-binding1", jpSinkRefNameInSpec) + out, err := getResourceFieldsWithJSONPath(t, it, "sinkbindings.sources.knative.dev", "my-binding1", jpSinkRefNameInSpec) assert.NilError(t, err) assert.Equal(t, out, "testsvc1") } -func (test *e2eTest) sourceBindingCreate(t *testing.T, r *KnRunResultCollector, bindingName string, subject string, sink string) { - out := test.kn.Run("source", "binding", "create", bindingName, "--subject", subject, "--sink", sink) +func sourceBindingCreate(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, bindingName string, subject string, sink string) { + out := it.Kn().Run("source", "binding", "create", bindingName, "--subject", subject, "--sink", sink) + r.AssertNoError(out) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Sink", "binding", bindingName, "created", "namespace", it.Kn().Namespace())) +} + +func sourceBindingDelete(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, bindingName string) { + out := it.Kn().Run("source", "binding", "delete", bindingName) r.AssertNoError(out) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Sink", "binding", bindingName, "created", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Sink", "binding", bindingName, "deleted", "namespace", it.Kn().Namespace())) } -func (test *e2eTest) sourceBindingDelete(t *testing.T, r *KnRunResultCollector, bindingName string) { - out := test.kn.Run("source", "binding", "delete", bindingName) +func sourceBindingUpdate(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, bindingName string, subject string, sink string) { + out := it.Kn().Run("source", "binding", "update", bindingName, "--subject", subject, "--sink", sink) r.AssertNoError(out) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Sink", "binding", bindingName, "deleted", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAll(out.Stdout, bindingName, "updated", "namespace", it.Kn().Namespace())) } -func (test *e2eTest) sourceBindingUpdate(t *testing.T, r *KnRunResultCollector, bindingName string, subject string, sink string) { - out := test.kn.Run("source", "binding", "update", bindingName, "--subject", subject, "--sink", sink) +func sourceBindingListOutputName(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, bindingName string) { + out := it.Kn().Run("source", "binding", "list", "--output", "name") r.AssertNoError(out) - assert.Check(t, util.ContainsAll(out.Stdout, bindingName, "updated", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAll(out.Stdout, bindingName)) } diff --git a/test/e2e/source_list_test.go b/test/e2e/source_list_test.go index faceaa1b15..75f6696194 100644 --- a/test/e2e/source_list_test.go +++ b/test/e2e/source_list_test.go @@ -22,59 +22,60 @@ import ( "gotest.tools/assert" + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) func TestSourceListTypes(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() t.Log("List available source types") - output := test.sourceListTypes(t, r) + output := sourceListTypes(t, it, r) assert.Check(t, util.ContainsAll(output, "TYPE", "NAME", "DESCRIPTION", "Ping", "ApiServer")) t.Log("List available source types in YAML format") - output = test.sourceListTypes(t, r, "-oyaml") + output = sourceListTypes(t, it, r, "-oyaml") assert.Check(t, util.ContainsAll(output, "apiextensions.k8s.io/v1beta1", "CustomResourceDefinition", "Ping", "ApiServer")) } func TestSourceList(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() t.Log("List sources empty case") - output := test.sourceList(t, r) + output := sourceList(t, it, r) assert.Check(t, util.ContainsAll(output, "No", "sources", "found", "namespace")) assert.Check(t, util.ContainsNone(output, "NAME", "TYPE", "RESOURCE", "SINK", "READY")) - // non empty list case is tested in test/e2e/source_apiserver_test.go where source setup is present + // non empty list case is tested in test/e2e/source_apiserver_it.go where source setup is present } -func (test *e2eTest) sourceListTypes(t *testing.T, r *KnRunResultCollector, args ...string) string { +func sourceListTypes(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, args ...string) string { cmd := append([]string{"source", "list-types"}, args...) - out := test.kn.Run(cmd...) + out := it.Kn().Run(cmd...) r.AssertNoError(out) return out.Stdout } -func (test *e2eTest) sourceList(t *testing.T, r *KnRunResultCollector, args ...string) string { +func sourceList(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, args ...string) string { cmd := append([]string{"source", "list"}, args...) - out := test.kn.Run(cmd...) + out := it.Kn().Run(cmd...) r.AssertNoError(out) return out.Stdout } diff --git a/test/e2e/tekton_test.go b/test/e2e/tekton_test.go index ee5cace8d5..b6cfc3a483 100644 --- a/test/e2e/tekton_test.go +++ b/test/e2e/tekton_test.go @@ -23,6 +23,8 @@ import ( "gotest.tools/assert" "k8s.io/apimachinery/pkg/util/wait" + + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) @@ -34,26 +36,26 @@ const ( ) func TestTektonPipeline(t *testing.T) { - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() - kubectl := kubectl{test.namespace} - basedir := currentDir(t) + "/../resources/tekton" + kubectl := test.NewKubectl(it.Namespace()) + basedir := test.CurrentDir(t) + "/../resources/tekton" // create secret for the kn-deployer-account service account - _, err = kubectl.Run("create", "-n", test.namespace, "secret", + _, err = kubectl.Run("create", "-n", it.Namespace(), "secret", "generic", "container-registry", - "--from-file=.dockerconfigjson="+Flags.DockerConfigJSON, + "--from-file=.dockerconfigjson="+test.Flags.DockerConfigJSON, "--type=kubernetes.io/dockerconfigjson") assert.NilError(t, err) _, err = kubectl.Run("apply", "-f", basedir+"/kn-deployer-rbac.yaml") assert.NilError(t, err) - _, err = kubectl.Run("apply", "-f", basedir+"/buildah.yaml") + _, err = kubectl.Run("apply", "-f", "https://raw.githubusercontent.com/tektoncd/catalog/master/buildah/buildah.yaml") assert.NilError(t, err) _, err = kubectl.Run("apply", "-f", "https://raw.githubusercontent.com/tektoncd/catalog/master/kn/kn.yaml") @@ -71,16 +73,16 @@ func TestTektonPipeline(t *testing.T) { err = waitForPipelineSuccess(kubectl) assert.NilError(t, err) - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) const serviceName = "hello" - out := test.kn.Run("service", "describe", serviceName) + out := it.Kn().Run("service", "describe", serviceName) r.AssertNoError(out) - assert.Assert(t, util.ContainsAll(out.Stdout, serviceName, test.kn.namespace)) + assert.Assert(t, util.ContainsAll(out.Stdout, serviceName, it.Kn().Namespace())) assert.Assert(t, util.ContainsAll(out.Stdout, "Conditions", "ConfigurationsReady", "Ready", "RoutesReady")) } -func waitForPipelineSuccess(k kubectl) error { +func waitForPipelineSuccess(k test.Kubectl) error { return wait.PollImmediate(Interval, Timeout, func() (bool, error) { out, err := k.Run("get", "pipelinerun", "-o=jsonpath='{.items[0].status.conditions[?(@.type==\"Succeeded\")].status}'") return strings.Contains(out, "True"), err diff --git a/test/e2e/traffic_split_test.go b/test/e2e/traffic_split_test.go index 90f15c14dd..06794eb70b 100644 --- a/test/e2e/traffic_split_test.go +++ b/test/e2e/traffic_split_test.go @@ -24,6 +24,8 @@ import ( "testing" "gotest.tools/assert" + + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) @@ -59,7 +61,7 @@ func splitTargets(s, separator string, partsCount int) ([]string, error) { // formatActualTargets takes the traffic targets string received after jsonpath operation and converts // them into []TargetFields for comparison -func formatActualTargets(t *testing.T, actualTargets []string) (formattedTargets []TargetFields) { +func formatActualTargets(t *testing.T, it *test.KnTest, actualTargets []string) (formattedTargets []TargetFields) { for _, each := range actualTargets { each := strings.TrimSuffix(each, targetFieldsSeparator) fields, err := splitTargets(each, targetFieldsSeparator, targetFieldsLength) @@ -76,324 +78,324 @@ func formatActualTargets(t *testing.T, actualTargets []string) (formattedTargets // TestTrafficSplitSuite runs different e2e tests for service traffic splitting and verifies the traffic targets from service status func TestTrafficSplit(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() serviceBase := "echo" t.Run("50:50", func(t *testing.T) { t.Log("tag two revisions as v1 and v2 and give 50-50% share") - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - serviceName := getNextServiceName(serviceBase) - test.serviceCreate(t, r, serviceName) + serviceName := test.GetNextServiceName(serviceBase) + serviceCreate(t, it, r, serviceName) rev1 := fmt.Sprintf("%s-rev-1", serviceName) - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v1", "--revision-name", rev1) + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v1", "--revision-name", rev1) rev2 := fmt.Sprintf("%s-rev-2", serviceName) - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) tflags := []string{"--tag", fmt.Sprintf("%s=v1,%s=v2", rev1, rev2), "--traffic", "v1=50,v2=50"} - test.serviceUpdateWithOptions(t, r, serviceName, tflags...) + serviceUpdateWithOptions(t, it, r, serviceName, tflags...) // make ordered fields per tflags (tag, revision, percent, latest) expectedTargets := []TargetFields{newTargetFields("v1", rev1, 50, false), newTargetFields("v2", rev2, 50, false)} - test.verifyTargets(t, r, serviceName, expectedTargets) - test.serviceDelete(t, r, serviceName) + verifyTargets(t, it, r, serviceName, expectedTargets) + serviceDelete(t, it, r, serviceName) }, ) t.Run("20:80", func(t *testing.T) { t.Log("ramp/up down a revision to 20% adjusting other traffic to accommodate") - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - serviceName := getNextServiceName(serviceBase) - test.serviceCreate(t, r, serviceName) + serviceName := test.GetNextServiceName(serviceBase) + serviceCreate(t, it, r, serviceName) rev1 := fmt.Sprintf("%s-rev-1", serviceName) - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v1", "--revision-name", rev1) + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v1", "--revision-name", rev1) rev2 := fmt.Sprintf("%s-rev-2", serviceName) - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) - test.serviceUpdateWithOptions(t, r, serviceName, "--traffic", fmt.Sprintf("%s=20,%s=80", rev1, rev2)) + serviceUpdateWithOptions(t, it, r, serviceName, "--traffic", fmt.Sprintf("%s=20,%s=80", rev1, rev2)) expectedTargets := []TargetFields{newTargetFields("", rev1, 20, false), newTargetFields("", rev2, 80, false)} - test.verifyTargets(t, r, serviceName, expectedTargets) - test.serviceDelete(t, r, serviceName) + verifyTargets(t, it, r, serviceName, expectedTargets) + serviceDelete(t, it, r, serviceName) }, ) t.Run("TagCandidate", func(t *testing.T) { t.Log("tag a revision as candidate, without otherwise changing any traffic split") - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - serviceName := getNextServiceName(serviceBase) + serviceName := test.GetNextServiceName(serviceBase) rev1 := fmt.Sprintf("%s-rev-1", serviceName) - test.serviceCreateWithOptions(t, r, serviceName, "--revision-name", rev1) + serviceCreateWithOptions(t, it, r, serviceName, "--revision-name", rev1) rev2 := fmt.Sprintf("%s-rev-2", serviceName) - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v1", "--revision-name", rev2) + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v1", "--revision-name", rev2) // no traffic, append new target with tag in traffic block - test.serviceUpdateWithOptions(t, r, serviceName, "--tag", fmt.Sprintf("%s=%s", rev1, "candidate")) + serviceUpdateWithOptions(t, it, r, serviceName, "--tag", fmt.Sprintf("%s=%s", rev1, "candidate")) expectedTargets := []TargetFields{newTargetFields("", rev2, 100, true), newTargetFields("candidate", rev1, 0, false)} - test.verifyTargets(t, r, serviceName, expectedTargets) - test.serviceDelete(t, r, serviceName) + verifyTargets(t, it, r, serviceName, expectedTargets) + serviceDelete(t, it, r, serviceName) }, ) t.Run("TagCandidate:2:98", func(t *testing.T) { t.Log("tag a revision as candidate, set 2% traffic adjusting other traffic to accommodate") - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - serviceName := getNextServiceName(serviceBase) + serviceName := test.GetNextServiceName(serviceBase) rev1 := fmt.Sprintf("%s-rev-1", serviceName) - test.serviceCreateWithOptions(t, r, serviceName, "--revision-name", rev1) + serviceCreateWithOptions(t, it, r, serviceName, "--revision-name", rev1) rev2 := fmt.Sprintf("%s-rev-2", serviceName) - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v1", "--revision-name", rev2) + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v1", "--revision-name", rev2) // traffic by tag name and use % at the end - test.serviceUpdateWithOptions(t, r, serviceName, + serviceUpdateWithOptions(t, it, r, serviceName, "--tag", fmt.Sprintf("%s=%s", rev1, "candidate"), "--traffic", "candidate=2%,@latest=98%") expectedTargets := []TargetFields{newTargetFields("", rev2, 98, true), newTargetFields("candidate", rev1, 2, false)} - test.verifyTargets(t, r, serviceName, expectedTargets) - test.serviceDelete(t, r, serviceName) + verifyTargets(t, it, r, serviceName, expectedTargets) + serviceDelete(t, it, r, serviceName) }, ) t.Run("TagCurrent", func(t *testing.T) { t.Log("update tag for a revision from candidate to current, tag current is present on another revision") - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - serviceName := getNextServiceName(serviceBase) + serviceName := test.GetNextServiceName(serviceBase) // make available 3 revisions for service first rev1 := fmt.Sprintf("%s-rev-1", serviceName) - test.serviceCreateWithOptions(t, r, serviceName, "--revision-name", rev1) + serviceCreateWithOptions(t, it, r, serviceName, "--revision-name", rev1) rev2 := fmt.Sprintf("%s-rev-2", serviceName) - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) rev3 := fmt.Sprintf("%s-rev-3", serviceName) - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v3", "--revision-name", rev3) //note that this gives 100% traffic to latest revision (rev3) + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v3", "--revision-name", rev3) //note that this gives 100% traffic to latest revision (rev3) // make existing state: tag current and candidate exist in traffic block - test.serviceUpdateWithOptions(t, r, serviceName, "--tag", fmt.Sprintf("%s=current,%s=candidate", rev1, rev2)) + serviceUpdateWithOptions(t, it, r, serviceName, "--tag", fmt.Sprintf("%s=current,%s=candidate", rev1, rev2)) // desired state of tags: update tag of revision (rev2) from candidate to current (which is present on rev1) //untag first to update - test.serviceUpdateWithOptions(t, r, serviceName, + serviceUpdateWithOptions(t, it, r, serviceName, "--untag", "current,candidate", "--tag", fmt.Sprintf("%s=current", rev2)) // there will be 2 targets in existing block 1. @latest, 2.for revision $rev2 // target for rev1 is removed as it had no traffic and we untagged it's tag current expectedTargets := []TargetFields{newTargetFields("", rev3, 100, true), newTargetFields("current", rev2, 0, false)} - test.verifyTargets(t, r, serviceName, expectedTargets) - test.serviceDelete(t, r, serviceName) + verifyTargets(t, it, r, serviceName, expectedTargets) + serviceDelete(t, it, r, serviceName) }, ) t.Run("TagStagingLatest", func(t *testing.T) { t.Log("update tag from testing to staging for @latest revision") - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - serviceName := getNextServiceName(serviceBase) + serviceName := test.GetNextServiceName(serviceBase) rev1 := fmt.Sprintf("%s-rev-1", serviceName) - test.serviceCreateWithOptions(t, r, serviceName, "--revision-name", rev1) + serviceCreateWithOptions(t, it, r, serviceName, "--revision-name", rev1) // make existing state: tag @latest as testing - test.serviceUpdateWithOptions(t, r, serviceName, "--tag", "@latest=testing") + serviceUpdateWithOptions(t, it, r, serviceName, "--tag", "@latest=testing") // desired state: change tag from testing to staging - test.serviceUpdateWithOptions(t, r, serviceName, "--untag", "testing", "--tag", "@latest=staging") + serviceUpdateWithOptions(t, it, r, serviceName, "--untag", "testing", "--tag", "@latest=staging") expectedTargets := []TargetFields{newTargetFields("staging", rev1, 100, true)} - test.verifyTargets(t, r, serviceName, expectedTargets) - test.serviceDelete(t, r, serviceName) + verifyTargets(t, it, r, serviceName, expectedTargets) + serviceDelete(t, it, r, serviceName) }, ) t.Run("TagStagingNonLatest", func(t *testing.T) { t.Log("update tag from testing to staging for a revision (non @latest)") - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - serviceName := getNextServiceName(serviceBase) + serviceName := test.GetNextServiceName(serviceBase) rev1 := fmt.Sprintf("%s-rev-1", serviceName) - test.serviceCreateWithOptions(t, r, serviceName, "--revision-name", rev1) + serviceCreateWithOptions(t, it, r, serviceName, "--revision-name", rev1) rev2 := fmt.Sprintf("%s-rev-2", serviceName) - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) // make existing state: tag a revision as testing - test.serviceUpdateWithOptions(t, r, serviceName, "--tag", fmt.Sprintf("%s=testing", rev1)) + serviceUpdateWithOptions(t, it, r, serviceName, "--tag", fmt.Sprintf("%s=testing", rev1)) // desired state: change tag from testing to staging - test.serviceUpdateWithOptions(t, r, serviceName, "--untag", "testing", "--tag", fmt.Sprintf("%s=staging", rev1)) + serviceUpdateWithOptions(t, it, r, serviceName, "--untag", "testing", "--tag", fmt.Sprintf("%s=staging", rev1)) expectedTargets := []TargetFields{newTargetFields("", rev2, 100, true), newTargetFields("staging", rev1, 0, false)} - test.verifyTargets(t, r, serviceName, expectedTargets) - test.serviceDelete(t, r, serviceName) + verifyTargets(t, it, r, serviceName, expectedTargets) + serviceDelete(t, it, r, serviceName) }, ) // test reducing number of targets from traffic blockdd t.Run("RemoveTag", func(t *testing.T) { t.Log("remove a revision with tag old from traffic block entirely") - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - serviceName := getNextServiceName(serviceBase) + serviceName := test.GetNextServiceName(serviceBase) rev1 := fmt.Sprintf("%s-rev-1", serviceName) - test.serviceCreateWithOptions(t, r, serviceName, "--revision-name", rev1) + serviceCreateWithOptions(t, it, r, serviceName, "--revision-name", rev1) rev2 := fmt.Sprintf("%s-rev-2", serviceName) - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) // existing state: traffic block having a revision with tag old and some traffic - test.serviceUpdateWithOptions(t, r, serviceName, + serviceUpdateWithOptions(t, it, r, serviceName, "--tag", fmt.Sprintf("%s=old", rev1), "--traffic", "old=2,@latest=98") // desired state: remove revision with tag old - test.serviceUpdateWithOptions(t, r, serviceName, "--untag", "old", "--traffic", "@latest=100") + serviceUpdateWithOptions(t, it, r, serviceName, "--untag", "old", "--traffic", "@latest=100") expectedTargets := []TargetFields{newTargetFields("", rev2, 100, true)} - test.verifyTargets(t, r, serviceName, expectedTargets) - test.serviceDelete(t, r, serviceName) + verifyTargets(t, it, r, serviceName, expectedTargets) + serviceDelete(t, it, r, serviceName) }, ) t.Run("TagStable:50:50", func(t *testing.T) { t.Log("tag a revision as stable and current with 50-50% traffic") - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - serviceName := getNextServiceName(serviceBase) + serviceName := test.GetNextServiceName(serviceBase) rev1 := fmt.Sprintf("%s-rev-1", serviceName) - test.serviceCreateWithOptions(t, r, serviceName, "--revision-name", rev1) + serviceCreateWithOptions(t, it, r, serviceName, "--revision-name", rev1) // existing state: traffic block having two targets - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v2") + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v2") // desired state: tag non-@latest revision with two tags and 50-50% traffic each - test.serviceUpdateWithOptions(t, r, serviceName, + serviceUpdateWithOptions(t, it, r, serviceName, "--tag", fmt.Sprintf("%s=stable,%s=current", rev1, rev1), "--traffic", "stable=50%,current=50%") expectedTargets := []TargetFields{newTargetFields("stable", rev1, 50, false), newTargetFields("current", rev1, 50, false)} - test.verifyTargets(t, r, serviceName, expectedTargets) - test.serviceDelete(t, r, serviceName) + verifyTargets(t, it, r, serviceName, expectedTargets) + serviceDelete(t, it, r, serviceName) }, ) t.Run("RevertToLatest", func(t *testing.T) { t.Log("revert all traffic to latest ready revision of service") - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - serviceName := getNextServiceName(serviceBase) + serviceName := test.GetNextServiceName(serviceBase) rev1 := fmt.Sprintf("%s-rev-1", serviceName) - test.serviceCreateWithOptions(t, r, serviceName, "--revision-name", rev1) + serviceCreateWithOptions(t, it, r, serviceName, "--revision-name", rev1) rev2 := fmt.Sprintf("%s-rev-2", serviceName) - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) // existing state: latest ready revision not getting any traffic - test.serviceUpdateWithOptions(t, r, serviceName, "--traffic", fmt.Sprintf("%s=100", rev1)) + serviceUpdateWithOptions(t, it, r, serviceName, "--traffic", fmt.Sprintf("%s=100", rev1)) // desired state: revert traffic to latest ready revision - test.serviceUpdateWithOptions(t, r, serviceName, "--traffic", "@latest=100") + serviceUpdateWithOptions(t, it, r, serviceName, "--traffic", "@latest=100") expectedTargets := []TargetFields{newTargetFields("", rev2, 100, true)} - test.verifyTargets(t, r, serviceName, expectedTargets) - test.serviceDelete(t, r, serviceName) + verifyTargets(t, it, r, serviceName, expectedTargets) + serviceDelete(t, it, r, serviceName) }, ) t.Run("TagLatestAsCurrent", func(t *testing.T) { t.Log("tag latest ready revision of service as current") - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - serviceName := getNextServiceName(serviceBase) + serviceName := test.GetNextServiceName(serviceBase) // existing state: latest revision has no tag rev1 := fmt.Sprintf("%s-rev-1", serviceName) - test.serviceCreateWithOptions(t, r, serviceName, "--revision-name", rev1) + serviceCreateWithOptions(t, it, r, serviceName, "--revision-name", rev1) // desired state: tag latest ready revision as 'current' - test.serviceUpdateWithOptions(t, r, serviceName, "--tag", "@latest=current") + serviceUpdateWithOptions(t, it, r, serviceName, "--tag", "@latest=current") expectedTargets := []TargetFields{newTargetFields("current", rev1, 100, true)} - test.verifyTargets(t, r, serviceName, expectedTargets) - test.serviceDelete(t, r, serviceName) + verifyTargets(t, it, r, serviceName, expectedTargets) + serviceDelete(t, it, r, serviceName) }, ) t.Run("UpdateTag:100:0", func(t *testing.T) { t.Log("update tag for a revision as testing and assign all the traffic to it") - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - serviceName := getNextServiceName(serviceBase) + serviceName := test.GetNextServiceName(serviceBase) rev1 := fmt.Sprintf("%s-rev-1", serviceName) - test.serviceCreateWithOptions(t, r, serviceName, "--revision-name", rev1) + serviceCreateWithOptions(t, it, r, serviceName, "--revision-name", rev1) rev2 := fmt.Sprintf("%s-rev-2", serviceName) - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) // existing state: two revision exists with traffic share and // each revision has tag and traffic portions - test.serviceUpdateWithOptions(t, r, serviceName, + serviceUpdateWithOptions(t, it, r, serviceName, "--tag", fmt.Sprintf("@latest=current,%s=candidate", rev1), "--traffic", "current=90,candidate=10") // desired state: update tag for rev1 as testing (from candidate) with 100% traffic - test.serviceUpdateWithOptions(t, r, serviceName, + serviceUpdateWithOptions(t, it, r, serviceName, "--untag", "candidate", "--tag", fmt.Sprintf("%s=testing", rev1), "--traffic", "testing=100") expectedTargets := []TargetFields{newTargetFields("current", rev2, 0, true), newTargetFields("testing", rev1, 100, false)} - test.verifyTargets(t, r, serviceName, expectedTargets) - test.serviceDelete(t, r, serviceName) + verifyTargets(t, it, r, serviceName, expectedTargets) + serviceDelete(t, it, r, serviceName) }, ) t.Run("TagReplace", func(t *testing.T) { t.Log("replace latest tag of a revision with old and give latest to another revision") - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - serviceName := getNextServiceName(serviceBase) + serviceName := test.GetNextServiceName(serviceBase) rev1 := fmt.Sprintf("%s-rev-1", serviceName) - test.serviceCreateWithOptions(t, r, serviceName, "--revision-name", rev1) + serviceCreateWithOptions(t, it, r, serviceName, "--revision-name", rev1) rev2 := fmt.Sprintf("%s-rev-2", serviceName) - test.serviceUpdateWithOptions(t, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) + serviceUpdateWithOptions(t, it, r, serviceName, "--env", "TARGET=v2", "--revision-name", rev2) // existing state: a revision exist with latest tag - test.serviceUpdateWithOptions(t, r, serviceName, "--tag", fmt.Sprintf("%s=latest", rev1)) + serviceUpdateWithOptions(t, it, r, serviceName, "--tag", fmt.Sprintf("%s=latest", rev1)) // desired state of revision tags: rev1=old rev2=latest - test.serviceUpdateWithOptions(t, r, serviceName, + serviceUpdateWithOptions(t, it, r, serviceName, "--untag", "latest", "--tag", fmt.Sprintf("%s=old,%s=latest", rev1, rev2)) @@ -404,34 +406,34 @@ func TestTrafficSplit(t *testing.T) { // In spec of traffic block (not status) either latestReadyRevision:true or revisionName can be given per target newTargetFields("latest", rev2, 0, false)} - test.verifyTargets(t, r, serviceName, expectedTargets) - test.serviceDelete(t, r, serviceName) + verifyTargets(t, it, r, serviceName, expectedTargets) + serviceDelete(t, it, r, serviceName) }, ) } -func (test *e2eTest) verifyTargets(t *testing.T, r *KnRunResultCollector, serviceName string, expectedTargets []TargetFields) { - out := test.serviceDescribeWithJsonPath(r, serviceName, targetsJsonPath) +func verifyTargets(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string, expectedTargets []TargetFields) { + out := serviceDescribeWithJsonPath(t, it, r, serviceName, targetsJsonPath) assert.Check(t, out != "") actualTargets, err := splitTargets(out, targetsSeparator, len(expectedTargets)) assert.NilError(t, err) - formattedActualTargets := formatActualTargets(t, actualTargets) + formattedActualTargets := formatActualTargets(t, it, actualTargets) assert.DeepEqual(t, expectedTargets, formattedActualTargets) if t.Failed() { - r.AddDump("service", serviceName, test.namespace) + r.AddDump("service", serviceName, it.Kn().Namespace()) } } -func (test *e2eTest) serviceDescribeWithJsonPath(r *KnRunResultCollector, serviceName, jsonpath string) string { - out := test.kn.Run("service", "describe", serviceName, "-o", jsonpath) +func serviceDescribeWithJsonPath(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName, jsonpath string) string { + out := it.Kn().Run("service", "describe", serviceName, "-o", jsonpath) r.AssertNoError(out) return out.Stdout } -func (test *e2eTest) serviceUpdateWithOptions(t *testing.T, r *KnRunResultCollector, serviceName string, options ...string) { +func serviceUpdateWithOptions(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName string, options ...string) { command := []string{"service", "update", serviceName} command = append(command, options...) - out := test.kn.Run(command...) + out := it.Kn().Run(command...) r.AssertNoError(out) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Service", serviceName, "updating", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Service", serviceName, "updating", "namespace", it.Kn().Namespace())) } diff --git a/test/e2e/trigger_inject_broker_test.go b/test/e2e/trigger_inject_broker_test.go index 234d96618b..46c319eecf 100644 --- a/test/e2e/trigger_inject_broker_test.go +++ b/test/e2e/trigger_inject_broker_test.go @@ -21,45 +21,47 @@ import ( "testing" "gotest.tools/assert" + + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) func TestInjectBrokerTrigger(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() assert.NilError(t, err) - test.serviceCreate(t, r, "sinksvc0") - test.serviceCreate(t, r, "sinksvc1") + serviceCreate(t, it, r, "sinksvc0") + serviceCreate(t, it, r, "sinksvc1") t.Log("create triggers and list them") - test.triggerCreateWithInject(t, r, "trigger1", "sinksvc0", []string{"a=b"}) - test.triggerCreateWithInject(t, r, "trigger2", "sinksvc1", []string{"type=knative.dev.bar", "source=ping"}) - test.verifyTriggerList(t, r, "trigger1", "trigger2") - test.triggerDelete(t, r, "trigger1") - test.triggerDelete(t, r, "trigger2") + triggerCreateWithInject(t, it, r, "trigger1", "sinksvc0", []string{"a=b"}) + triggerCreateWithInject(t, it, r, "trigger2", "sinksvc1", []string{"type=knative.dev.bar", "source=ping"}) + verifyTriggerList(t, it, r, "trigger1", "trigger2") + triggerDelete(t, it, r, "trigger1") + triggerDelete(t, it, r, "trigger2") t.Log("create trigger with error") - out := test.kn.Run("trigger", "create", "errorTrigger", "--broker", "mybroker", "--inject-broker", + out := it.Kn().Run("trigger", "create", "errorTrigger", "--broker", "mybroker", "--inject-broker", "--sink", "svc:sinksvc0", "--filter", "a=b") r.AssertError(out) assert.Check(t, util.ContainsAllIgnoreCase(out.Stderr, "broker", "name", "'default'", "--inject-broker", "flag")) } -func (test *e2eTest) triggerCreateWithInject(t *testing.T, r *KnRunResultCollector, name string, sinksvc string, filters []string) { +func triggerCreateWithInject(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, name string, sinksvc string, filters []string) { args := []string{"trigger", "create", name, "--broker", "default", "--inject-broker", "--sink", "svc:" + sinksvc} for _, v := range filters { args = append(args, "--filter", v) } - out := test.kn.Run(args...) + out := it.Kn().Run(args...) r.AssertNoError(out) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Trigger", name, "created", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Trigger", name, "created", "namespace", it.Kn().Namespace())) } diff --git a/test/e2e/trigger_test.go b/test/e2e/trigger_test.go index ee4b36bae2..faac920d1f 100644 --- a/test/e2e/trigger_test.go +++ b/test/e2e/trigger_test.go @@ -24,74 +24,80 @@ import ( "gotest.tools/assert" "k8s.io/apimachinery/pkg/util/wait" + + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) func TestBrokerTrigger(t *testing.T) { t.Parallel() - test, err := NewE2eTest() + it, err := test.NewKnTest() assert.NilError(t, err) defer func() { - assert.NilError(t, test.Teardown()) + assert.NilError(t, it.Teardown()) }() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - err = test.lableNamespaceForDefaultBroker(t) + err = lableNamespaceForDefaultBroker(t, it) assert.NilError(t, err) - defer test.unlableNamespaceForDefaultBroker(t) + defer unlableNamespaceForDefaultBroker(t, it) - test.serviceCreate(t, r, "sinksvc0") - test.serviceCreate(t, r, "sinksvc1") + serviceCreate(t, it, r, "sinksvc0") + serviceCreate(t, it, r, "sinksvc1") t.Log("create triggers and list them") - test.triggerCreate(t, r, "trigger1", "sinksvc0", nil) - test.triggerCreate(t, r, "trigger2", "sinksvc1", []string{"type=knative.dev.bar", "source=ping"}) - test.verifyTriggerList(t, r, "trigger1", "trigger2") - test.triggerDelete(t, r, "trigger1") - test.triggerDelete(t, r, "trigger2") + triggerCreate(t, it, r, "trigger1", "sinksvc0", []string{"a=b"}) + triggerCreate(t, it, r, "trigger2", "sinksvc1", []string{"type=knative.dev.bar", "source=ping"}) + verifyTriggerList(t, it, r, "trigger1", "trigger2") + verifyTriggerListOutputName(t, it, r, "trigger1", "trigger2") + triggerDelete(t, it, r, "trigger1") + triggerDelete(t, it, r, "trigger2") t.Log("create a trigger and delete it") - test.triggerCreate(t, r, "deltrigger", "sinksvc0", []string{"a=b"}) - test.triggerDelete(t, r, "deltrigger") - test.verifyTriggerNotfound(t, r, "deltrigger") + triggerCreate(t, it, r, "deltrigger", "sinksvc0", []string{"a=b"}) + triggerDelete(t, it, r, "deltrigger") + verifyTriggerNotfound(t, it, r, "deltrigger") t.Log("create a trigger with filters and remove them one by one") - test.triggerCreate(t, r, "filtertrigger", "sinksvc0", []string{"foo=bar", "source=ping"}) - test.verifyTriggerDescribe(t, r, "filtertrigger", "default", "sinksvc0", []string{"foo", "bar", "source", "ping"}) - test.triggerUpdate(t, r, "filtertrigger", "foo-", "sinksvc0") - test.verifyTriggerDescribe(t, r, "filtertrigger", "default", "sinksvc0", []string{"source", "ping"}) - test.triggerUpdate(t, r, "filtertrigger", "source-", "sinksvc0") - test.verifyTriggerDescribe(t, r, "filtertrigger", "default", "sinksvc0", nil) - test.triggerDelete(t, r, "filtertrigger") + triggerCreate(t, it, r, "filtertrigger", "sinksvc0", []string{"foo=bar", "source=ping"}) + verifyTriggerDescribe(t, it, r, "filtertrigger", "default", "sinksvc0", []string{"foo", "bar", "source", "ping"}) + triggerUpdate(t, it, r, "filtertrigger", "foo-", "sinksvc0") + verifyTriggerDescribe(t, it, r, "filtertrigger", "default", "sinksvc0", []string{"source", "ping"}) + triggerUpdate(t, it, r, "filtertrigger", "source-", "sinksvc0") + verifyTriggerDescribe(t, it, r, "filtertrigger", "default", "sinksvc0", nil) + triggerDelete(t, it, r, "filtertrigger") t.Log("create a trigger, describe and update it") - test.triggerCreate(t, r, "updtrigger", "sinksvc0", []string{"a=b"}) - test.verifyTriggerDescribe(t, r, "updtrigger", "default", "sinksvc0", []string{"a", "b"}) - test.triggerUpdate(t, r, "updtrigger", "type=knative.dev.bar", "sinksvc1") - test.verifyTriggerDescribe(t, r, "updtrigger", "default", "sinksvc1", []string{"a", "b", "type", "knative.dev.bar"}) - test.triggerDelete(t, r, "updtrigger") + triggerCreate(t, it, r, "updtrigger", "sinksvc0", []string{"a=b"}) + verifyTriggerDescribe(t, it, r, "updtrigger", "default", "sinksvc0", []string{"a", "b"}) + triggerUpdate(t, it, r, "updtrigger", "type=knative.dev.bar", "sinksvc1") + verifyTriggerDescribe(t, it, r, "updtrigger", "default", "sinksvc1", []string{"a", "b", "type", "knative.dev.bar"}) + triggerDelete(t, it, r, "updtrigger") t.Log("create trigger with error return") - test.triggerCreateMissingSink(t, r, "errtrigger", "notfound") + triggerCreateMissingSink(t, it, r, "errtrigger", "notfound") } -func (test *e2eTest) unlableNamespaceForDefaultBroker(t *testing.T) { - _, err := kubectl{}.Run("label", "namespace", test.kn.namespace, "knative-eventing-injection-") +// Private functions + +func unlableNamespaceForDefaultBroker(t *testing.T, it *test.KnTest) { + _, err := test.Kubectl{}.Run("label", "namespace", it.Kn().Namespace(), "knative-eventing-injection-") if err != nil { - t.Fatalf("Error executing 'kubectl label namespace %s knative-eventing-injection-'. Error: %s", test.kn.namespace, err.Error()) + t.Fatalf("Error executing 'kubectl label namespace %s knative-eventing-injection-'. Error: %s", it.Kn().Namespace(), err.Error()) } } -func (test *e2eTest) lableNamespaceForDefaultBroker(t *testing.T) error { - _, err := kubectl{}.Run("label", "namespace", test.kn.namespace, "knative-eventing-injection=enabled") +func lableNamespaceForDefaultBroker(t *testing.T, it *test.KnTest) error { + _, err := test.Kubectl{}.Run("label", "namespace", it.Kn().Namespace(), "knative-eventing-injection=enabled") + if err != nil { - t.Fatalf("Error executing 'kubectl label namespace %s knative-eventing-injection=enabled'. Error: %s", test.kn.namespace, err.Error()) + t.Fatalf("Error executing 'kubectl label namespace %s knative-eventing-injection=enabled'. Error: %s", it.Kn().Namespace(), err.Error()) } return wait.PollImmediate(10*time.Second, 5*time.Minute, func() (bool, error) { - out, err := kubectl{test.kn.namespace}.Run("get", "broker", "-o=jsonpath='{.items[0].status.conditions[?(@.type==\"Ready\")].status}'") + out, err := test.NewKubectl(it.Kn().Namespace()).Run("get", "broker", "-o=jsonpath='{.items[0].status.conditions[?(@.type==\"Ready\")].status}'") if err != nil { return false, nil } else { @@ -100,44 +106,50 @@ func (test *e2eTest) lableNamespaceForDefaultBroker(t *testing.T) error { }) } -func (test *e2eTest) triggerCreate(t *testing.T, r *KnRunResultCollector, name string, sinksvc string, filters []string) { +func triggerCreate(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, name string, sinksvc string, filters []string) { args := []string{"trigger", "create", name, "--broker", "default", "--sink", "svc:" + sinksvc} if len(filters) > 0 { for _, v := range filters { args = append(args, "--filter", v) } } - out := test.kn.Run(args...) + out := it.Kn().Run(args...) r.AssertNoError(out) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Trigger", name, "created", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Trigger", name, "created", "namespace", it.Kn().Namespace())) } -func (test *e2eTest) triggerCreateMissingSink(t *testing.T, r *KnRunResultCollector, name string, sinksvc string) { - out := test.kn.Run("trigger", "create", name, "--broker", "default", "--sink", "svc:"+sinksvc) +func triggerCreateMissingSink(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, name string, sinksvc string) { + out := it.Kn().Run("trigger", "create", name, "--broker", "default", "--sink", "svc:"+sinksvc) r.AssertError(out) assert.Check(t, util.ContainsAll(out.Stderr, "services.serving.knative.dev", "not found")) } -func (test *e2eTest) triggerDelete(t *testing.T, r *KnRunResultCollector, name string) { - out := test.kn.Run("trigger", "delete", name) +func triggerDelete(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, name string) { + out := it.Kn().Run("trigger", "delete", name) r.AssertNoError(out) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Trigger", name, "deleted", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Trigger", name, "deleted", "namespace", it.Kn().Namespace())) } -func (test *e2eTest) triggerUpdate(t *testing.T, r *KnRunResultCollector, name string, filter string, sinksvc string) { - out := test.kn.Run("trigger", "update", name, "--filter", filter, "--sink", "svc:"+sinksvc) +func triggerUpdate(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, name string, filter string, sinksvc string) { + out := it.Kn().Run("trigger", "update", name, "--filter", filter, "--sink", "svc:"+sinksvc) r.AssertNoError(out) - assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Trigger", name, "updated", "namespace", test.kn.namespace)) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "Trigger", name, "updated", "namespace", it.Kn().Namespace())) +} + +func verifyTriggerList(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, triggers ...string) { + out := it.Kn().Run("trigger", "list") + r.AssertNoError(out) + assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, triggers...)) } -func (test *e2eTest) verifyTriggerList(t *testing.T, r *KnRunResultCollector, triggers ...string) { - out := test.kn.Run("trigger", "list") +func verifyTriggerListOutputName(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, triggers ...string) { + out := it.Kn().Run("trigger", "list", "--output", "name") r.AssertNoError(out) assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, triggers...)) } -func (test *e2eTest) verifyTriggerDescribe(t *testing.T, r *KnRunResultCollector, name string, broker string, sink string, filters []string) { - out := test.kn.Run("trigger", "describe", name) +func verifyTriggerDescribe(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, name string, broker string, sink string, filters []string) { + out := it.Kn().Run("trigger", "describe", name) r.AssertNoError(out) if len(filters) > 0 { assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, filters...)) @@ -147,8 +159,8 @@ func (test *e2eTest) verifyTriggerDescribe(t *testing.T, r *KnRunResultCollector assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, name, broker, sink)) } -func (test *e2eTest) verifyTriggerNotfound(t *testing.T, r *KnRunResultCollector, name string) { - out := test.kn.Run("trigger", "describe", name) +func verifyTriggerNotfound(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, name string) { + out := it.Kn().Run("trigger", "describe", name) r.AssertError(out) assert.Check(t, util.ContainsAll(out.Stderr, name, "not found")) } diff --git a/test/e2e/version_test.go b/test/e2e/version_test.go index 7921724e8d..adb8e2fdd2 100644 --- a/test/e2e/version_test.go +++ b/test/e2e/version_test.go @@ -20,16 +20,18 @@ import ( "testing" "gotest.tools/assert" + + "knative.dev/client/lib/test" "knative.dev/client/pkg/util" ) func TestVersion(t *testing.T) { t.Parallel() - r := NewKnRunResultCollector(t) + r := test.NewKnRunResultCollector(t) defer r.DumpIfFailed() - out := kn{}.Run("version") + out := test.Kn{}.Run("version") r.AssertNoError(out) assert.Check(t, util.ContainsAll(out.Stdout, "Version")) } diff --git a/test/local-e2e-tests.sh b/test/local-e2e-tests.sh index a52f094a38..8f1f7fd255 100755 --- a/test/local-e2e-tests.sh +++ b/test/local-e2e-tests.sh @@ -15,6 +15,7 @@ # limitations under the License. export PATH=$PWD:$PATH +export GO111MODULE=on dir=$(dirname "${BASH_SOURCE[0]}") base=$(cd "$dir/.." && pwd) @@ -23,4 +24,4 @@ base=$(cd "$dir/.." && pwd) export KN_E2E_NAMESPACE=kne2etests echo "🧪 Testing" -go test ${base}/test/e2e/ -test.v -tags "e2e ${E2E_TAGS}" "$@" +go test -mod=vendor ${base}/test/e2e/ -test.v -tags "e2e ${E2E_TAGS}" "$@" diff --git a/test/resources/tekton/buildah.yaml b/test/resources/tekton/buildah.yaml deleted file mode 100644 index 1e9635b277..0000000000 --- a/test/resources/tekton/buildah.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright 2020 The Knative Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# Define a ServiceAccount named kn-deployer-account that has permission to -# manage Knative services. ---- -apiVersion: tekton.dev/v1alpha1 -kind: Task -metadata: - name: buildah -spec: - inputs: - params: - - name: BUILDER_IMAGE - description: The location of the buildah builder image. - default: quay.io/buildah/stable:v1.11.0 - - name: DOCKERFILE - description: Path to the Dockerfile to build. - default: ./Dockerfile - - name: TLSVERIFY - description: Verify the TLS on the registry endpoint (for push/pull to a non-TLS registry) - default: "true" - - resources: - - name: source - type: git - - outputs: - resources: - - name: image - type: image - - steps: - - name: build - image: $(inputs.params.BUILDER_IMAGE) - workingDir: /workspace/source - command: ['buildah', 'bud', '--format=docker', '--tls-verify=$(inputs.params.TLSVERIFY)', '--layers', '-f', '$(inputs.params.DOCKERFILE)', '-t', '$(outputs.resources.image.url)', '.'] - volumeMounts: - - name: varlibcontainers - mountPath: /var/lib/containers - securityContext: - privileged: true - - - name: push - image: $(inputs.params.BUILDER_IMAGE) - workingDir: /workspace/source - command: ['buildah', 'push', '--tls-verify=$(inputs.params.TLSVERIFY)', '$(outputs.resources.image.url)', 'docker://$(outputs.resources.image.url)'] - volumeMounts: - - name: varlibcontainers - mountPath: /var/lib/containers - securityContext: - privileged: true - - volumes: - - name: varlibcontainers - emptyDir: {} diff --git a/test/resources/tekton/kn-pipeline-run.yaml b/test/resources/tekton/kn-pipeline-run.yaml index 9a5b671c48..f8f2757fb9 100644 --- a/test/resources/tekton/kn-pipeline-run.yaml +++ b/test/resources/tekton/kn-pipeline-run.yaml @@ -27,6 +27,8 @@ spec: resourceRef: name: buildah-build-kn-create-image params: + - name: IMAGE_FORMAT + value: "docker" - name: ARGS value: - "service" diff --git a/test/resources/tekton/kn-pipeline.yaml b/test/resources/tekton/kn-pipeline.yaml index 6e1d8bbda4..0f4bbb51b3 100644 --- a/test/resources/tekton/kn-pipeline.yaml +++ b/test/resources/tekton/kn-pipeline.yaml @@ -27,6 +27,9 @@ spec: description: Arguments to pass to kn CLI default: - "help" + - name: IMAGE_FORMAT + type: string + description: Image format to build and push to registry tasks: - name: buildah-build taskRef: @@ -43,6 +46,8 @@ spec: value: ./test/test_images/helloworld/Dockerfile - name: BUILDER_IMAGE value: "quay.io/buildah/stable:latest" + - name: FORMAT + value: "$(params.IMAGE_FORMAT)" - name: kn-service-create taskRef: name: kn diff --git a/test/tekton-tests.sh b/test/tekton-tests.sh index 27594a46bb..4b0a3c3315 100755 --- a/test/tekton-tests.sh +++ b/test/tekton-tests.sh @@ -28,16 +28,18 @@ export PATH=$PATH:${REPO_ROOT_DIR} # Script entry point. initialize $@ -export TEKTON_VERSION=${TEKTON_VERSION:-v0.9.2} +export TEKTON_VERSION=${TEKTON_VERSION:-v0.11.1} export KN_E2E_NAMESPACE=tkn-kn header "Running integration tests for Tekton" +subheader "Installing Tekton Pipelines ${TEKTON_VERSION}" # Install Tekton if not already installed if [[ $(kubectl api-resources | grep -c tekton.dev) -eq 0 ]]; then kubectl apply -f https://github.com/tektoncd/pipeline/releases/download/${TEKTON_VERSION}/release.yaml fi +subheader "Configuring docker and registry" if (( IS_PROW )); then # Configure Docker so that we can create a secret for GCR gcloud auth configure-docker @@ -53,6 +55,7 @@ for file in kn-deployer-rbac kn-pipeline-resource; do -e "s#\${CONTAINER_REGISTRY}#${CONTAINER_REGISTRY}#" ${resource_dir}/${file}-template.yaml > ${resource_dir}/${file}.yaml done +subheader "Running test pipeline" go_test_e2e -timeout=30m -tags=tekton ./test/e2e || fail_test success diff --git a/vendor/knative.dev/pkg/apis/condition_set.go b/vendor/knative.dev/pkg/apis/condition_set.go index 2691dcf0fa..3c0746df61 100644 --- a/vendor/knative.dev/pkg/apis/condition_set.go +++ b/vendor/knative.dev/pkg/apis/condition_set.go @@ -75,6 +75,10 @@ type ConditionManager interface { // true if all dependents are true. MarkTrue(t ConditionType) + // MarkTrueWithReason sets the status of t to true with the reason, and then marks the happy + // condition to true if all dependents are true. + MarkTrueWithReason(t ConditionType, reason, messageFormat string, messageA ...interface{}) + // MarkUnknown sets the status of t to Unknown and also sets the happy condition // to Unknown if no other dependent condition is in an error state. MarkUnknown(t ConditionType, reason, messageFormat string, messageA ...interface{}) @@ -253,7 +257,25 @@ func (r conditionsImpl) MarkTrue(t ConditionType) { Status: corev1.ConditionTrue, Severity: r.severity(t), }) + r.recomputeHappiness(t) +} + +// MarkTrueWithReason sets the status of t to true with the reason, and then marks the happy condition to +// true if all other dependents are also true. +func (r conditionsImpl) MarkTrueWithReason(t ConditionType, reason, messageFormat string, messageA ...interface{}) { + // set the specified condition + r.SetCondition(Condition{ + Type: t, + Status: corev1.ConditionTrue, + Reason: reason, + Message: fmt.Sprintf(messageFormat, messageA...), + Severity: r.severity(t), + }) + r.recomputeHappiness(t) +} +// recomputeHappiness marks the happy condition to true if all other dependents are also true. +func (r conditionsImpl) recomputeHappiness(t ConditionType) { if c := r.findUnhappyDependent(); c != nil { // Propagate unhappy dependent to happy condition. r.SetCondition(Condition{ @@ -312,7 +334,7 @@ func (r conditionsImpl) findUnhappyDependent() *Condition { } // If something was not initialized. - if len(r.dependents) != len(conditions) { + if len(r.dependents) > len(conditions) { return &Condition{ Status: corev1.ConditionUnknown, } diff --git a/vendor/knative.dev/pkg/apis/contexts.go b/vendor/knative.dev/pkg/apis/contexts.go index abb5706ac0..735c6191ed 100644 --- a/vendor/knative.dev/pkg/apis/contexts.go +++ b/vendor/knative.dev/pkg/apis/contexts.go @@ -198,3 +198,16 @@ func AllowDifferentNamespace(ctx context.Context) context.Context { func IsDifferentNamespaceAllowed(ctx context.Context) bool { return ctx.Value(allowDifferentNamespace{}) != nil } + +// This is attached to contexts passed to webhook interfaces when the user has request DryRun mode. +type isDryRun struct{} + +// WithDryRun is used to indicate that this call is in DryRun mode. +func WithDryRun(ctx context.Context) context.Context { + return context.WithValue(ctx, isDryRun{}, struct{}{}) +} + +// IsDryRun indicates that this request is in DryRun mode. +func IsDryRun(ctx context.Context) bool { + return ctx.Value(isDryRun{}) != nil +} diff --git a/vendor/knative.dev/pkg/apis/duck/ABOUT.md b/vendor/knative.dev/pkg/apis/duck/ABOUT.md new file mode 100644 index 0000000000..0c6edabdea --- /dev/null +++ b/vendor/knative.dev/pkg/apis/duck/ABOUT.md @@ -0,0 +1,325 @@ +# Knative Duck Typing + +![A Trojan Duck](images/Knative-Duck0.png) + +**Figure 1:** How to integrate with Knative. + +## Problem statement + +In Knative, we want to support +[loose coupling](https://docs.google.com/presentation/d/1KxKAcIZyblkXbpdGVCgmzfDDIhqgUcwsa0zlABfvaXI/edit#slide=id.p) +of the building blocks we are releasing. We want users to be able to use these +building blocks together, but also support composing them with non-Knative +components as well. + +Unlike Knative’s +[pluggability story](https://docs.google.com/presentation/d/10KWynvAJYuOEWy69VBa6bHJVCqIsz1TNdEKosNvcpPY/edit#slide=id.p) +(for replacing subsystems within a building block), we do not want to require +that the systems with which we compose have **identical** APIs (distinct +implementations). However, we do need a way of accessing (reading / writing) +certain **_pieces_** of information in a structured way. + +**Enter [duck typing](https://en.wikipedia.org/wiki/Duck_typing)**. We will +define a partial schema, to which resource authors will adhere if they want to +participate within certain contexts of Knative. + +For instance, consider the partial schema: + +```yaml +foo: + bar: +``` + +Both of these resources implement the above duck type: + +```yaml +baz: 1234 +foo: + bar: asdf +blah: + blurp: true +``` + +```yaml +field: running out of ideas +foo: + bar: a different string +another: you get the point +``` + +### Reading duck-typed data + +At a high-level, reading duck-typed data is very straightforward: using the +partial object schema deserialize the resource ignoring unknown fields. The +fields we care about can then be accessed through the structured object that +represents the duck type. + +### Writing duck-typed data + +How to write duck-typed data is less straightforward because we do not want to +clobber every field we do not know about. To accomplish this, we will lean on +Kubernetes’ well established patching model. + +First, we read the resource we intend to modify as our duck type. Keeping a copy +of the original, we then modify the fields of this duck typed resource to +reflect the change we want. Lastly, we synthesize a JSON Patch of the changes +between the original and the final version and issue a Patch to the Kubernetes +API with the delta. + +Since the duck type inherently contains a subset of the fields in the resource, +the resulting JSON Patch can only contain fields relevant to the resource. + +## Example: Reading Knative-style Conditions + +In Knative, we follow the Kubernetes API principles of using `conditions` as a +key part of our resources’ status, but we go a step further in +[defining particular conventions](https://github.com/knative/serving/blob/master/docs/spec/errors.md#error-conditions-and-reporting) +on how these are used. + +To support this, we define: + +```golang +type KResource struct { +        metav1.TypeMeta   `json:",inline"` +        metav1.ObjectMeta `json:"metadata,omitempty"` + +        Status KResourceStatus `json:"status"` +} + +type KResourceStatus struct { +        Conditions Conditions `json:"conditions,omitempty"` +} + +type Conditions []Condition + +type Condition struct { +  // structure adhering to K8s API principles +  ... +} +``` + +We can now deserialize and reason about the status of any Knative-compatible +resource using this partial schema. + +## Example: Mutating Knative CRD Generations + +In Knative, all of our resources define a `.spec.generation` field, which we use +in place of `.metadata.generation` because the latter was not properly managed +by Kubernetes (prior to 1.11 with `/status` subresource). We manage bumping this +generation field in our webhook if and only if the `.spec` changed. + +To support this, we define: + +```golang +type Generational struct { +        metav1.TypeMeta   `json:",inline"` +        metav1.ObjectMeta `json:"metadata,omitempty"` + +        Spec GenerationalSpec `json:"spec"` +} + +type GenerationalSpec struct { +        Generation Generation `json:"generation,omitempty"` +} + +type Generation int64 +``` + +Using this our webhook can read the current resource’s generation, increment it, +and generate a patch to apply it. + +## Example: Mutating Core Kubernetes Resources + +Kubernetes already uses duck typing, in a way. Consider that `Deployment`, +`ReplicaSet`, `DaemonSet`, `StatefulSet`, and `Job` all embed a +`corev1.PodTemplateSpec` at the exact path: `.spec.template`. + +Consider the example duck type: + +```yaml +type PodSpecable corev1.PodTemplateSpec + +type WithPod struct { metav1.TypeMeta   `json:",inline"` metav1.ObjectMeta +`json:"metadata,omitempty"` + +Spec WithPodSpec `json:"spec,omitempty"` } + +type WithPodSpec struct { Template PodSpecable `json:"template,omitempty"` } +``` + +Using this, we can access the PodSpec of arbitrary higher-level Kubernetes +resources in a very structured way and generate patches to mutate them. +[See examples](https://github.com/knative/pkg/blob/07104dad53e803457a95306e5b1322024bd69af3/apis/duck/podspec_test.go#L49-L53). + +_You can also see a sample controller that reconciles duck-typed resources +[here](https://github.com/mattmoor/cachier)._ + +## Conventions + +Each of our duck types will consist of a single structured field that must be +enclosed within the containing resource in a particular way. + +1. This structured field will be named Fooable, +2. Fooable will be directly included via a field named + fooable, +3. Additional skeletal layers around Fooable will be defined to + fully define Fooable’s position within complete resources. + +_You can see parts of these in the examples above, however, those special cases +have been exempted from the first condition for legacy compatibility reasons._ + +For example: + +1. `type Conditions []Condition` +2. Conditions Conditions + `json:"conditions,omitempty"` +3. KResource -> KResourceStatus -> Conditions + +## Supporting Mechanics + +We will provide a number of tools to enable working with duck types without +blowing off feet. + +### Verification + +To verify that a particular resource implements a particular duck type, resource +authors are strongly encouraged to add the following as test code adjacent to +resource definitions. + +`myresource_types.go`: + +```golang +package v1alpha1 + +type MyResource struct { + ... +} +``` + +`myresource_types_test.go`: + +```golang +package v1alpha1 + +import ( + "testing" + + // This is where supporting tools for duck-typing will live. + "github.com/knative/pkg/apis/duck" + + // This is where Knative-provided duck types will live. + duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" +) + +// This verifies that MyResource contains all the necessary fields for the +// given implementable duck type. +func TestType(t *testing.T) { + err := duck.VerifyType(&MyResource{}, &duckv1alpha1.Conditions{}) + if err != nil { + t.Errorf("VerifyType() = %v", err) + } +} +``` + +\_This call will create a fully populated instance of the skeletal resource +containing the Conditions and ensure that the fields can 100% roundtrip through +MyResource. + +### Patching + +To produce a patch of a particular resource modification suitable for use with +k8s.io/client-[go/dynamic](https://goto.google.com/dynamic), +developers can write: + +```golang +before := … +after := before.DeepCopy() +// modify "after" + +patch, err := duck.CreatePatch(before, after) +// check err + +bytes, err := patch.MarshalJSON() +// check err + +dynamicClient.Patch(bytes) +``` + +### Informers / Listers + +To be able to efficiently access / monitor arbitrary duck-typed resources, we +want to be able to produce an Informer / Lister for interpreting particular +resource groups as a particular duck type. + +To facilitate this, we provide several composable implementations of +`duck.InformerFactory`. + +```golang +type InformerFactory interface { +        // Get an informer/lister pair for the given resource group. +        Get(GroupVersionResource) (SharedIndexInformer, GenericLister, error) +} + + +// This produces informer/lister pairs that interpret objects in the resource group +// as the provided duck "Type" +dif := &duck.TypedInformerFactory{ +        Client:       dynaClient, +        Type:         &duckv1alpha1.Foo{}, +        ResyncPeriod: 30 * time.Second, +        StopChannel:  stopCh, +} + +// This registers the provided EventHandler with the informer each time an +// informer/lister pair is produced. +eif := &duck.EnqueueInformerFactory{ + Delegate: dif, + EventHandler: cache.ResourceEventHandlerFuncs{ + AddFunc: impl.EnqueueControllerOf, + UpdateFunc: controller.PassNew(impl.EnqueueControllerOf), + }, +} + +// This caches informer/lister pairs so that we only produce one for each GVR. +cif := &duck.CachedInformerFactory{ + Delegate: eif, +} +``` + +### Trackers + +Informers are great when you have something like an `OwnerReference` to key off +of for the association (e.g. `impl.EnqueueControllerOf`), however, when the +association is looser e.g. `corev1.ObjectReference`, then we need a way of +configuring a reconciliation trigger for the cross-reference. + +For this (generally) we have the `knative/pkg/tracker` package. Here is how it +is used with duck types: + +```golang +        c := &Reconciler{ +                Base:             reconciler.NewBase(opt, controllerAgentName), +                ... +        } +        impl := controller.NewImpl(c, c.Logger, "Revisions") + + // Calls to Track create a 30 minute lease before they must be renewed. + // Coordinate this value with controller resync periods. +        t := tracker.New(impl.EnqueueKey, 30*time.Minute) +        cif := &duck.CachedInformerFactory{ +                Delegate: &duck.EnqueueInformerFactory{ +                        Delegate: buildInformerFactory, +                        EventHandler: cache.ResourceEventHandlerFuncs{ +                                AddFunc:    t.OnChanged, +                                UpdateFunc: controller.PassNew(t.OnChanged), +                        }, +                }, +        } + +        // Now use: c.buildInformerFactory.Get() to access ObjectReferences. +        c.buildInformerFactory = buildInformerFactory + +        // Now use: c.tracker.Track(rev.Spec.BuildRef, rev) to queue rev +        // each time rev.Spec.BuildRef changes. +        c.tracker = t +``` diff --git a/vendor/knative.dev/pkg/apis/duck/v1/source_types.go b/vendor/knative.dev/pkg/apis/duck/v1/source_types.go index 782556471e..3b378ca3d4 100644 --- a/vendor/knative.dev/pkg/apis/duck/v1/source_types.go +++ b/vendor/knative.dev/pkg/apis/duck/v1/source_types.go @@ -81,6 +81,22 @@ type SourceStatus struct { // Source. // +optional SinkURI *apis.URL `json:"sinkUri,omitempty"` + + // CloudEventAttributes are the specific attributes that the Source uses + // as part of its CloudEvents. + // +optional + CloudEventAttributes []CloudEventAttributes `json:"ceAttributes,omitempty"` +} + +// CloudEventAttributes specifies the attributes that a Source +// uses as part of its CloudEvents. +type CloudEventAttributes struct { + + // Type refers to the CloudEvent type attribute. + Type string `json:"type,omitempty"` + + // Source is the CloudEvents source attribute. + Source string `json:"source,omitempty"` } // IsReady returns true if the resource is ready overall. @@ -137,6 +153,10 @@ func (s *Source) Populate() { Host: "tableflip.dev", RawQuery: "flip=mattmoor", } + s.Status.CloudEventAttributes = []CloudEventAttributes{{ + Type: "dev.knative.foo", + Source: "http://knative.dev/knative/eventing", + }} } // GetListType implements apis.Listable diff --git a/vendor/knative.dev/pkg/apis/duck/v1/zz_generated.deepcopy.go b/vendor/knative.dev/pkg/apis/duck/v1/zz_generated.deepcopy.go index 2fcfbed776..3397713be0 100644 --- a/vendor/knative.dev/pkg/apis/duck/v1/zz_generated.deepcopy.go +++ b/vendor/knative.dev/pkg/apis/duck/v1/zz_generated.deepcopy.go @@ -127,6 +127,22 @@ func (in *AddressableTypeList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CloudEventAttributes) DeepCopyInto(out *CloudEventAttributes) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CloudEventAttributes. +func (in *CloudEventAttributes) DeepCopy() *CloudEventAttributes { + if in == nil { + return nil + } + out := new(CloudEventAttributes) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CloudEventOverrides) DeepCopyInto(out *CloudEventOverrides) { *out = *in @@ -384,6 +400,11 @@ func (in *SourceStatus) DeepCopyInto(out *SourceStatus) { *out = new(apis.URL) (*in).DeepCopyInto(*out) } + if in.CloudEventAttributes != nil { + in, out := &in.CloudEventAttributes, &out.CloudEventAttributes + *out = make([]CloudEventAttributes, len(*in)) + copy(*out, *in) + } return } diff --git a/vendor/knative.dev/pkg/logging/config b/vendor/knative.dev/pkg/logging/config new file mode 100644 index 0000000000..2480fc6d43 --- /dev/null +++ b/vendor/knative.dev/pkg/logging/config @@ -0,0 +1,4 @@ +# The OWNERS file is used by prow to automatically merge approved PRs. + +approvers: +- configmap-approvers diff --git a/vendor/knative.dev/pkg/tracker/enqueue.go b/vendor/knative.dev/pkg/tracker/enqueue.go index a8f02ea6ab..fc036466da 100644 --- a/vendor/knative.dev/pkg/tracker/enqueue.go +++ b/vendor/knative.dev/pkg/tracker/enqueue.go @@ -159,7 +159,7 @@ func (i *impl) TrackReference(ref Reference, obj interface{}) error { // doesn't create problems: // foo, err := lister.Get(key) // // Later... - // err := tracker.Track(fooRef, parent) + // err := tracker.TrackReference(fooRef, parent) // In this example, "Later" represents a window where "foo" may // have changed or been created while the Track is not active. // The simplest way of eliminating such a window is to call the @@ -192,7 +192,7 @@ func (i *impl) TrackReference(ref Reference, obj interface{}) error { // doesn't create problems: // foo, err := lister.Get(key) // // Later... - // err := tracker.Track(fooRef, parent) + // err := tracker.TrackReference(fooRef, parent) // In this example, "Later" represents a window where "foo" may // have changed or been created while the Track is not active. // The simplest way of eliminating such a window is to call the diff --git a/vendor/knative.dev/test-infra/scripts/README.md b/vendor/knative.dev/test-infra/scripts/README.md index ca673fb135..32e17ac1ff 100644 --- a/vendor/knative.dev/test-infra/scripts/README.md +++ b/vendor/knative.dev/test-infra/scripts/README.md @@ -70,10 +70,11 @@ integration tests). Use the flags `--build-tests`, `--unit-tests` and `--integration-tests` to run a specific set of tests. -To run a specific program as a test, use the `--run-test` flag, and provide the +To run specific programs as a test, use the `--run-test` flag, and provide the program as the argument. If arguments are required for the program, pass everything as a single quotes argument. For example, -`./presubmit-tests.sh --run-test "test/my/test data"`. +`./presubmit-tests.sh --run-test "test/my/test data"`. This flag can be used +repeatedly, and each one will be ran in sequential order. The script will automatically skip all presubmit tests for PRs where all changed files are exempt of tests (e.g., a PR changing only the `OWNERS` file). diff --git a/vendor/knative.dev/test-infra/scripts/e2e-tests.sh b/vendor/knative.dev/test-infra/scripts/e2e-tests.sh index a78920dccb..917f5ebf90 100644 --- a/vendor/knative.dev/test-infra/scripts/e2e-tests.sh +++ b/vendor/knative.dev/test-infra/scripts/e2e-tests.sh @@ -93,7 +93,7 @@ function dump_cluster_state() { echo "*** Start of information dump ***" echo "***************************************" - local output="${ARTIFACTS}/k8s.dump.txt" + local output="${ARTIFACTS}/k8s.dump-$(basename ${E2E_SCRIPT}).txt" echo ">>> The dump is located at ${output}" for crd in $(kubectl api-resources --verbs=list -o name | sort); do diff --git a/vendor/knative.dev/test-infra/scripts/library.sh b/vendor/knative.dev/test-infra/scripts/library.sh index d2715650cd..434deda253 100644 --- a/vendor/knative.dev/test-infra/scripts/library.sh +++ b/vendor/knative.dev/test-infra/scripts/library.sh @@ -327,6 +327,13 @@ function capture_output() { return ${failed} } +# Print failed step, which could be highlighted by spyglass. +# Parameters: $1...n - description of step that failed +function step_failed() { + local spyglass_token="Step failed:" + echo "${spyglass_token} $@" +} + # Create a temporary file with the given extension in a way that works on both Linux and macOS. # Parameters: $1 - file name without extension (e.g. 'myfile_XXXX') # $2 - file extension (e.g. 'xml') @@ -475,7 +482,19 @@ function run_go_tool() { if [[ -z "$(which ${tool})" ]]; then local action=get [[ $1 =~ ^[\./].* ]] && action=install - go ${action} $1 + # Avoid running `go get` from root dir of the repository, as it can change go.sum and go.mod files. + # See discussions in https://github.com/golang/go/issues/27643. + if [[ ${action} == "get" && $(pwd) == "${REPO_ROOT_DIR}" ]]; then + local temp_dir="$(mktemp -d)" + local install_failed=0 + # Swallow the output as we are returning the stdout in the end. + pushd "${temp_dir}" > /dev/null 2>&1 + go ${action} $1 || install_failed=1 + popd > /dev/null 2>&1 + (( install_failed )) && return ${install_failed} + else + go ${action} $1 + fi fi shift 2 ${tool} "$@" diff --git a/vendor/knative.dev/test-infra/scripts/markdown-link-check-config.rc b/vendor/knative.dev/test-infra/scripts/markdown-link-check-config.rc index 9d802a0d48..49b042e827 100644 --- a/vendor/knative.dev/test-infra/scripts/markdown-link-check-config.rc +++ b/vendor/knative.dev/test-infra/scripts/markdown-link-check-config.rc @@ -1,5 +1,5 @@ # For help, see # https://github.com/raviqqe/liche/blob/master/README.md -# Don't check localhost links --x "^https?://localhost($|[:/].*)" +# Don't check localhost links and don't check templated links +-x "(^https?://localhost($|[:/].*))|(^https://.*{{.*$)" diff --git a/vendor/knative.dev/test-infra/scripts/presubmit-tests.sh b/vendor/knative.dev/test-infra/scripts/presubmit-tests.sh index c14f251712..bdd74754a2 100644 --- a/vendor/knative.dev/test-infra/scripts/presubmit-tests.sh +++ b/vendor/knative.dev/test-infra/scripts/presubmit-tests.sh @@ -28,7 +28,7 @@ readonly PRESUBMIT_TEST_FAIL_FAST=${PRESUBMIT_TEST_FAIL_FAST:-0} readonly NO_PRESUBMIT_FILES=(\.png \.gitignore \.gitattributes ^OWNERS ^OWNERS_ALIASES ^AUTHORS) # Flag if this is a presubmit run or not. -[[ IS_PROW && -n "${PULL_PULL_SHA}" ]] && IS_PRESUBMIT=1 || IS_PRESUBMIT=0 +(( IS_PROW )) && [[ -n "${PULL_PULL_SHA}" ]] && IS_PRESUBMIT=1 || IS_PRESUBMIT=0 readonly IS_PRESUBMIT # List of changed files on presubmit, LF separated. @@ -93,19 +93,19 @@ function run_build_tests() { local failed=0 # Run pre-build tests, if any if function_exists pre_build_tests; then - pre_build_tests || failed=1 + pre_build_tests || { failed=1; step_failed "pre_build_tests"; } fi # Don't run build tests if pre-build tests failed if (( ! failed )); then if function_exists build_tests; then - build_tests || failed=1 + build_tests || { failed=1; step_failed "build_tests"; } else - default_build_test_runner || failed=1 + default_build_test_runner || { failed=1; step_failed "default_build_test_runner"; } fi fi # Don't run post-build tests if pre/build tests failed if (( ! failed )) && function_exists post_build_tests; then - post_build_tests || failed=1 + post_build_tests || { failed=1; step_failed "post_build_tests"; } fi results_banner "Build" ${failed} return ${failed} @@ -129,7 +129,7 @@ function markdown_build_tests() { (( DISABLE_MD_LINTING && DISABLE_MD_LINK_CHECK )) && return 0 # Get changed markdown files (ignore /vendor, github templates, and deleted files) local mdfiles="" - for file in $(echo "${CHANGED_FILES}" | grep \.md$ | grep -v ^vendor/ | grep -v ^.github/); do + for file in $(echo "${CHANGED_FILES}" | grep \\.md$ | grep -v ^vendor/ | grep -v ^.github/); do [[ -f "${file}" ]] && mdfiles="${mdfiles} ${file}" done [[ -z "${mdfiles}" ]] && return 0 @@ -147,14 +147,20 @@ function markdown_build_tests() { # Default build test runner that: # * check markdown files -# * `go build` on the entire repo # * run `/hack/verify-codegen.sh` (if it exists) +# * `go build` on the entire repo # * check licenses in all go packages function default_build_test_runner() { local failed=0 - # Perform markdown build checks first + # Perform markdown build checks markdown_build_tests || failed=1 - # For documentation PRs, just check the md files + # Run verify-codegen check + if [[ -f ./hack/verify-codegen.sh ]]; then + subheader "Checking autogenerated code is up-to-date" + report_build_test Verify_CodeGen ./hack/verify-codegen.sh || failed=1 + fi + # For documentation PRs, just check the md files and run + # verify-codegen (as md files can be auto-generated in some repos). (( IS_DOCUMENTATION_PR )) && return ${failed} # Don't merge these two lines, or return code will always be 0. local go_pkg_dirs @@ -171,11 +177,12 @@ function default_build_test_runner() { # Consider an error message everything that's not a package name. errors_go1="$(grep -v '^\(github\.com\|knative\.dev\)/' "${report}" | sort | uniq)" fi - # Get all build tags in go code (ignore /vendor) + # Get all build tags in go code (ignore /vendor and /hack) local tags="$(grep -r '// +build' . \ - | grep -v '^./vendor/' | cut -f3 -d' ' | sort | uniq | tr '\n' ' ')" + | grep -v '^./vendor/' | grep -v '^./hack/' | cut -f3 -d' ' | sort | uniq | tr '\n' ' ')" local tagged_pkgs="$(grep -r '// +build' . \ - | grep -v '^./vendor/' | grep ":// +build " | cut -f1 -d: | xargs dirname | sort | uniq | tr '\n' ' ')" + | grep -v '^./vendor/' | grep -v '^./hack/' | grep ":// +build " | cut -f1 -d: | xargs dirname \ + | sort | uniq | tr '\n' ' ')" for pkg in ${tagged_pkgs}; do # `go test -c` lets us compile the tests but do not run them. if ! capture_output "${report}" go test -c -tags="${tags}" ${pkg} ; then @@ -186,13 +193,9 @@ function default_build_test_runner() { # Remove unused generated binary, if any. rm -f e2e.test done - + local errors_go="$(echo -e "${errors_go1}\n${errors_go2}" | uniq)" create_junit_xml _build_tests Build_Go "${errors_go}" - if [[ -f ./hack/verify-codegen.sh ]]; then - subheader "Checking autogenerated code is up-to-date" - report_build_test Verify_CodeGen ./hack/verify-codegen.sh || failed=1 - fi # Check that we don't have any forbidden licenses in our images. subheader "Checking for forbidden licenses" report_build_test Check_Licenses check_licenses ${go_pkg_dirs} || failed=1 @@ -211,19 +214,19 @@ function run_unit_tests() { local failed=0 # Run pre-unit tests, if any if function_exists pre_unit_tests; then - pre_unit_tests || failed=1 + pre_unit_tests || { failed=1; step_failed "pre_unit_tests"; } fi # Don't run unit tests if pre-unit tests failed if (( ! failed )); then if function_exists unit_tests; then - unit_tests || failed=1 + unit_tests || { failed=1; step_failed "unit_tests"; } else - default_unit_test_runner || failed=1 + default_unit_test_runner || { failed=1; step_failed "default_unit_test_runner"; } fi fi # Don't run post-unit tests if pre/unit tests failed if (( ! failed )) && function_exists post_unit_tests; then - post_unit_tests || failed=1 + post_unit_tests || { failed=1; step_failed "post_unit_tests"; } fi results_banner "Unit" ${failed} return ${failed} @@ -247,19 +250,19 @@ function run_integration_tests() { local failed=0 # Run pre-integration tests, if any if function_exists pre_integration_tests; then - pre_integration_tests || failed=1 + pre_integration_tests || { failed=1; step_failed "pre_integration_tests"; } fi # Don't run integration tests if pre-integration tests failed if (( ! failed )); then if function_exists integration_tests; then - integration_tests || failed=1 + integration_tests || { failed=1; step_failed "integration_tests"; } else - default_integration_test_runner || failed=1 + default_integration_test_runner || { failed=1; step_failed "default_integration_test_runner"; } fi fi # Don't run integration tests if pre/integration tests failed if (( ! failed )) && function_exists post_integration_tests; then - post_integration_tests || failed=1 + post_integration_tests || { failed=1; step_failed "post_integration_tests"; } fi results_banner "Integration" ${failed} return ${failed} @@ -273,6 +276,7 @@ function default_integration_test_runner() { echo "Running integration test ${e2e_test}" if ! ${e2e_test} ${options}; then failed=1 + step_failed "${e2e_test} ${options}" fi done return ${failed} @@ -325,7 +329,7 @@ function main() { [[ -z $1 ]] && set -- "--all-tests" - local TEST_TO_RUN="" + local TESTS_TO_RUN=() while [[ $# -ne 0 ]]; do local parameter=$1 @@ -341,7 +345,7 @@ function main() { --run-test) shift [[ $# -ge 1 ]] || abort "missing executable after --run-test" - TEST_TO_RUN="$1" + TESTS_TO_RUN+=("$1") ;; *) abort "error: unknown option ${parameter}" ;; esac @@ -351,7 +355,7 @@ function main() { readonly RUN_BUILD_TESTS readonly RUN_UNIT_TESTS readonly RUN_INTEGRATION_TESTS - readonly TEST_TO_RUN + readonly TESTS_TO_RUN cd ${REPO_ROOT_DIR} @@ -359,7 +363,7 @@ function main() { local failed=0 - if [[ -n "${TEST_TO_RUN}" ]]; then + if [[ ${#TESTS_TO_RUN[@]} -gt 0 ]]; then if (( RUN_BUILD_TESTS || RUN_UNIT_TESTS || RUN_INTEGRATION_TESTS )); then abort "--run-test must be used alone" fi @@ -368,17 +372,19 @@ function main() { header "Documentation only PR, skipping running custom test" exit 0 fi - ${TEST_TO_RUN} || failed=1 + for test_to_run in "${TESTS_TO_RUN[@]}"; do + ${test_to_run} || { failed=1; step_failed "${test_to_run}"; } + done fi - run_build_tests || failed=1 + run_build_tests || { failed=1; step_failed "run_build_tests"; } # If PRESUBMIT_TEST_FAIL_FAST is set to true, don't run unit tests if build tests failed if (( ! PRESUBMIT_TEST_FAIL_FAST )) || (( ! failed )); then - run_unit_tests || failed=1 + run_unit_tests || { failed=1; step_failed "run_unit_tests"; } fi # If PRESUBMIT_TEST_FAIL_FAST is set to true, don't run integration tests if build/unit tests failed if (( ! PRESUBMIT_TEST_FAIL_FAST )) || (( ! failed )); then - run_integration_tests || failed=1 + run_integration_tests || { failed=1; step_failed "run_integration_tests"; } fi exit ${failed} diff --git a/vendor/knative.dev/test-infra/scripts/release.sh b/vendor/knative.dev/test-infra/scripts/release.sh index 915fc97f62..d23c4c33b5 100644 --- a/vendor/knative.dev/test-infra/scripts/release.sh +++ b/vendor/knative.dev/test-infra/scripts/release.sh @@ -498,9 +498,30 @@ function publish_artifacts() { # Entry point for a release script. function main() { + parse_flags "$@" + + # Checkout specific branch, if necessary + local current_branch + current_branch="$(git rev-parse --abbrev-ref HEAD)" + if [[ -n "${RELEASE_BRANCH}" && -z "${FROM_NIGHTLY_RELEASE}" && "${current_branch}" != "${RELEASE_BRANCH}" ]]; then + setup_upstream + setup_branch + # When it runs in Prow, the origin is identical with upstream, and previous + # fetch already fetched release-* branches, so no need to `checkout -b` + if (( IS_PROW )); then + git checkout "${RELEASE_BRANCH}" || abort "cannot checkout branch ${RELEASE_BRANCH}" + else + git checkout -b "${RELEASE_BRANCH}" upstream/"${RELEASE_BRANCH}" || abort "cannot checkout branch ${RELEASE_BRANCH}" + fi + # HACK HACK HACK + # Rerun the release script from the release branch. Fixes https://github.com/knative/test-infra/issues/1262 + ./hack/release.sh "$@" + exit "$?" + fi + function_exists build_release || abort "function 'build_release()' not defined" [[ -x ${VALIDATION_TESTS} ]] || abort "test script '${VALIDATION_TESTS}' doesn't exist" - parse_flags "$@" + # Log what will be done and where. banner "Release configuration" if which gcloud &>/dev/null ; then @@ -533,13 +554,6 @@ function main() { fi [[ -n "${RELEASE_NOTES}" ]] && echo "- Release notes are generated from '${RELEASE_NOTES}'" - # Checkout specific branch, if necessary - if [[ -n "${RELEASE_BRANCH}" && -z "${FROM_NIGHTLY_RELEASE}" ]]; then - setup_upstream - setup_branch - git checkout upstream/${RELEASE_BRANCH} || abort "cannot checkout branch ${RELEASE_BRANCH}" - fi - if [[ -n "${FROM_NIGHTLY_RELEASE}" ]]; then build_from_nightly_release else diff --git a/vendor/modules.txt b/vendor/modules.txt index 79ab33800a..0678f4c67d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -463,7 +463,7 @@ k8s.io/kube-openapi/pkg/util/proto k8s.io/utils/buffer k8s.io/utils/integer k8s.io/utils/trace -# knative.dev/eventing v0.13.1 +# knative.dev/eventing v0.13.6 knative.dev/eventing/pkg/apis/config knative.dev/eventing/pkg/apis/configs knative.dev/eventing/pkg/apis/configs/v1alpha1 @@ -495,7 +495,7 @@ knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake knative.dev/eventing/pkg/legacyclient/clientset/versioned/scheme knative.dev/eventing/pkg/legacyclient/clientset/versioned/typed/legacysources/v1alpha1 knative.dev/eventing/pkg/logging -# knative.dev/pkg v0.0.0-20200304185554-312b1be35ceb +# knative.dev/pkg v0.0.0-20200409225505-9147309d5366 knative.dev/pkg/apis knative.dev/pkg/apis/duck knative.dev/pkg/apis/duck/v1 @@ -511,7 +511,7 @@ knative.dev/pkg/logging/logkey knative.dev/pkg/profiling knative.dev/pkg/ptr knative.dev/pkg/tracker -# knative.dev/serving v0.13.0 +# knative.dev/serving v0.13.2 knative.dev/serving/pkg/apis/autoscaling knative.dev/serving/pkg/apis/autoscaling/v1alpha1 knative.dev/serving/pkg/apis/config @@ -525,7 +525,7 @@ knative.dev/serving/pkg/autoscaler/config knative.dev/serving/pkg/client/clientset/versioned/scheme knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1 knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1/fake -# knative.dev/test-infra v0.0.0-20200229011351-4dac123b9a3d +# knative.dev/test-infra v0.0.0-20200409221904-5e04d955cdb9 knative.dev/test-infra/scripts # sigs.k8s.io/kustomize v2.0.3+incompatible sigs.k8s.io/kustomize/pkg/commands/build