From 6b8a9896de067386c95abd666f61c0d2db66dd15 Mon Sep 17 00:00:00 2001 From: Navid Shaikh Date: Thu, 1 Oct 2020 22:14:29 +0530 Subject: [PATCH] feat: Subscription CRUD (#1013) * feat: Add subscription CRUD - Add kn subscription command group and CRUDL sub-commands - create/update uses following flag names for better alignment: - --sink for subscriber - --sink-reply for reply - --sink-dead-letter for dead-letter-sink - Add 'subscriptions' and 'sub' aliases - Introduce shared library `knative.dev/client/lib/printing` to print Sink object in describe output * Set default channel type messaging.knative.dev/v1beta1:Channel i.e. if no prefix is given to `--channel`, consider it of `Channel` type * Update e2e tests * Update channel flag description --- CHANGELOG.adoc | 12 ++ docs/cmd/kn.md | 1 + docs/cmd/kn_source_apiserver_create.md | 2 +- docs/cmd/kn_source_apiserver_update.md | 2 +- docs/cmd/kn_source_binding_create.md | 2 +- docs/cmd/kn_source_binding_update.md | 2 +- docs/cmd/kn_source_ping_create.md | 2 +- docs/cmd/kn_source_ping_update.md | 2 +- docs/cmd/kn_subscription.md | 35 ++++ docs/cmd/kn_subscription_create.md | 46 +++++ docs/cmd/kn_subscription_delete.md | 39 ++++ docs/cmd/kn_subscription_describe.md | 43 ++++ docs/cmd/kn_subscription_list.md | 47 +++++ docs/cmd/kn_subscription_update.md | 45 ++++ docs/cmd/kn_trigger_create.md | 2 +- docs/cmd/kn_trigger_update.md | 2 +- lib/printing/describe.go | 45 ++++ lib/test/subscription.go | 61 ++++++ pkg/dynamic/fake/fake.go | 2 + pkg/kn/commands/flags/sink.go | 31 ++- pkg/kn/commands/flags/sink_test.go | 35 ++++ pkg/kn/commands/subscription/create.go | 116 +++++++++++ pkg/kn/commands/subscription/create_test.go | 82 ++++++++ pkg/kn/commands/subscription/delete.go | 57 ++++++ pkg/kn/commands/subscription/delete_test.go | 54 +++++ pkg/kn/commands/subscription/describe.go | 110 ++++++++++ pkg/kn/commands/subscription/describe_test.go | 59 ++++++ pkg/kn/commands/subscription/flags.go | 139 +++++++++++++ pkg/kn/commands/subscription/list.go | 75 +++++++ pkg/kn/commands/subscription/list_test.go | 71 +++++++ pkg/kn/commands/subscription/subscription.go | 71 +++++++ .../subscription/subscription_test.go | 147 +++++++++++++ pkg/kn/commands/subscription/update.go | 105 ++++++++++ pkg/kn/commands/subscription/update_test.go | 74 +++++++ pkg/kn/flags/channel_types.go | 70 +++++-- pkg/kn/flags/channel_types_test.go | 70 +++++++ pkg/kn/root/root.go | 2 + pkg/messaging/v1beta1/client.go | 8 + pkg/messaging/v1beta1/subscriptions_client.go | 193 ++++++++++++++++++ .../v1beta1/subscriptions_client_mock.go | 121 +++++++++++ test/e2e/subscription_test.go | 67 ++++++ 41 files changed, 2116 insertions(+), 33 deletions(-) create mode 100644 docs/cmd/kn_subscription.md create mode 100644 docs/cmd/kn_subscription_create.md create mode 100644 docs/cmd/kn_subscription_delete.md create mode 100644 docs/cmd/kn_subscription_describe.md create mode 100644 docs/cmd/kn_subscription_list.md create mode 100644 docs/cmd/kn_subscription_update.md create mode 100644 lib/printing/describe.go create mode 100644 lib/test/subscription.go create mode 100644 pkg/kn/commands/subscription/create.go create mode 100644 pkg/kn/commands/subscription/create_test.go create mode 100644 pkg/kn/commands/subscription/delete.go create mode 100644 pkg/kn/commands/subscription/delete_test.go create mode 100644 pkg/kn/commands/subscription/describe.go create mode 100644 pkg/kn/commands/subscription/describe_test.go create mode 100644 pkg/kn/commands/subscription/flags.go create mode 100644 pkg/kn/commands/subscription/list.go create mode 100644 pkg/kn/commands/subscription/list_test.go create mode 100644 pkg/kn/commands/subscription/subscription.go create mode 100644 pkg/kn/commands/subscription/subscription_test.go create mode 100644 pkg/kn/commands/subscription/update.go create mode 100644 pkg/kn/commands/subscription/update_test.go create mode 100644 pkg/messaging/v1beta1/subscriptions_client.go create mode 100644 pkg/messaging/v1beta1/subscriptions_client_mock.go create mode 100644 test/e2e/subscription_test.go diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 0a0f537041..a572e3ec71 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -12,6 +12,18 @@ | https://github.com/knative/client/pull/[#] //// +## Unreleased + +[cols="1,10,3", options="header", width="100%"] +|=== +| | Description | PR + +| 🎁 +| Add subscription CRUD +| https://github.com/knative/client/pull/1013[#1013] + +|=== + ## v0.17.0 (2020-08-26) [cols="1,10,3", options="header", width="100%"] diff --git a/docs/cmd/kn.md b/docs/cmd/kn.md index affc0162ac..f00dd7e35c 100644 --- a/docs/cmd/kn.md +++ b/docs/cmd/kn.md @@ -28,6 +28,7 @@ kn is the command line interface for managing Knative Serving and Eventing resou * [kn route](kn_route.md) - List and describe service routes * [kn service](kn_service.md) - Manage Knative services * [kn source](kn_source.md) - Manage event sources +* [kn subscription](kn_subscription.md) - Manage event subscriptions * [kn trigger](kn_trigger.md) - Manage event triggers * [kn version](kn_version.md) - Show the version of this client diff --git a/docs/cmd/kn_source_apiserver_create.md b/docs/cmd/kn_source_apiserver_create.md index d9b84bcc71..a26f962714 100644 --- a/docs/cmd/kn_source_apiserver_create.md +++ b/docs/cmd/kn_source_apiserver_create.md @@ -30,7 +30,7 @@ kn source apiserver create NAME --resource RESOURCE --sink SINK --resource stringArray Specification for which events to listen, in the format Kind:APIVersion:LabelSelector, e.g. "Event:v1:key=value". "LabelSelector" is a list of comma separated key value pairs. "LabelSelector" can be omitted, e.g. "Event:v1". --service-account string Name of the service account to use to run this source - -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink 'ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If prefix is not provided, it is considered as a Knative service. + -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. ``` ### Options inherited from parent commands diff --git a/docs/cmd/kn_source_apiserver_update.md b/docs/cmd/kn_source_apiserver_update.md index e9672544c8..2982dd40be 100644 --- a/docs/cmd/kn_source_apiserver_update.md +++ b/docs/cmd/kn_source_apiserver_update.md @@ -30,7 +30,7 @@ kn source apiserver update NAME --resource stringArray Specification for which events to listen, in the format Kind:APIVersion:LabelSelector, e.g. "Event:v1:key=value". "LabelSelector" is a list of comma separated key value pairs. "LabelSelector" can be omitted, e.g. "Event:v1". --service-account string Name of the service account to use to run this source - -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink 'ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If prefix is not provided, it is considered as a Knative service. + -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. ``` ### Options inherited from parent commands diff --git a/docs/cmd/kn_source_binding_create.md b/docs/cmd/kn_source_binding_create.md index abb619f016..0a36b9ba88 100644 --- a/docs/cmd/kn_source_binding_create.md +++ b/docs/cmd/kn_source_binding_create.md @@ -24,7 +24,7 @@ kn source binding create NAME --subject SUBJECT --sink SINK --ce-override stringArray Cloud Event overrides to apply before sending event to sink. Example: '--ce-override key=value' You may be provide this flag multiple times. To unset, append "-" to the key (e.g. --ce-override key-). -h, --help help for create -n, --namespace string Specify the namespace to operate in. - -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink 'ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If prefix is not provided, it is considered as a Knative service. + -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. --subject string Subject which emits cloud events. This argument takes format kind:apiVersion:name for named resources or kind:apiVersion:labelKey1=value1,labelKey2=value2 for matching via a label selector ``` diff --git a/docs/cmd/kn_source_binding_update.md b/docs/cmd/kn_source_binding_update.md index 821313786e..b3a76743ec 100644 --- a/docs/cmd/kn_source_binding_update.md +++ b/docs/cmd/kn_source_binding_update.md @@ -24,7 +24,7 @@ kn source binding update NAME --ce-override stringArray Cloud Event overrides to apply before sending event to sink. Example: '--ce-override key=value' You may be provide this flag multiple times. To unset, append "-" to the key (e.g. --ce-override key-). -h, --help help for update -n, --namespace string Specify the namespace to operate in. - -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink 'ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If prefix is not provided, it is considered as a Knative service. + -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. --subject string Subject which emits cloud events. This argument takes format kind:apiVersion:name for named resources or kind:apiVersion:labelKey1=value1,labelKey2=value2 for matching via a label selector ``` diff --git a/docs/cmd/kn_source_ping_create.md b/docs/cmd/kn_source_ping_create.md index 14f10355ba..d291b52115 100644 --- a/docs/cmd/kn_source_ping_create.md +++ b/docs/cmd/kn_source_ping_create.md @@ -26,7 +26,7 @@ kn source ping create NAME --sink SINK -h, --help help for create -n, --namespace string Specify the namespace to operate in. --schedule string Optional schedule specification in crontab format (e.g. '*/2 * * * *' for every two minutes. By default fire every minute. - -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink 'ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If prefix is not provided, it is considered as a Knative service. + -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. ``` ### Options inherited from parent commands diff --git a/docs/cmd/kn_source_ping_update.md b/docs/cmd/kn_source_ping_update.md index fe09b8069d..99c1c6937d 100644 --- a/docs/cmd/kn_source_ping_update.md +++ b/docs/cmd/kn_source_ping_update.md @@ -26,7 +26,7 @@ kn source ping update NAME -h, --help help for update -n, --namespace string Specify the namespace to operate in. --schedule string Optional schedule specification in crontab format (e.g. '*/2 * * * *' for every two minutes. By default fire every minute. - -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink 'ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If prefix is not provided, it is considered as a Knative service. + -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. ``` ### Options inherited from parent commands diff --git a/docs/cmd/kn_subscription.md b/docs/cmd/kn_subscription.md new file mode 100644 index 0000000000..640bb9a386 --- /dev/null +++ b/docs/cmd/kn_subscription.md @@ -0,0 +1,35 @@ +## kn subscription + +Manage event subscriptions + +### Synopsis + +Manage event subscriptions + +``` +kn subscription COMMAND +``` + +### Options + +``` + -h, --help help for subscription +``` + +### Options inherited from parent commands + +``` + --config string kn configuration file (default: ~/.config/kn/config.yaml) + --kubeconfig string kubectl configuration file (default: ~/.kube/config) + --log-http log http traffic +``` + +### SEE ALSO + +* [kn](kn.md) - kn manages Knative Serving and Eventing resources +* [kn subscription create](kn_subscription_create.md) - Create a subscription +* [kn subscription delete](kn_subscription_delete.md) - Delete a subscription +* [kn subscription describe](kn_subscription_describe.md) - Show details of a subscription +* [kn subscription list](kn_subscription_list.md) - List subscriptions +* [kn subscription update](kn_subscription_update.md) - Update an event subscription + diff --git a/docs/cmd/kn_subscription_create.md b/docs/cmd/kn_subscription_create.md new file mode 100644 index 0000000000..a36fcc46f6 --- /dev/null +++ b/docs/cmd/kn_subscription_create.md @@ -0,0 +1,46 @@ +## kn subscription create + +Create a subscription + +### Synopsis + +Create a subscription + +``` +kn subscription create NAME +``` + +### Examples + +``` + + # Create a subscription 'sub0' from InMemoryChannel 'pipe0' to a subscriber ksvc 'receiver' + kn subscription create sub0 --channel imcv1beta1:pipe0 --sink ksvc:receiver + + # Create a subscription 'sub1' from KafkaChannel 'k1' to ksvc 'mirror', reply to a broker 'nest' and DeadLetterSink to a ksvc 'bucket' + kn subscription create sub1 --channel messaging.knative.dev:v1alpha1:KafkaChannel:k1 --sink mirror --sink-reply broker:nest --sink-dead-letter bucket +``` + +### Options + +``` + --channel string Specify the channel to subscribe to. For the default channel, just use the name (e.g. 'mychannel'). A mapped channel type like 'imc' can be used as a prefix (e.g. 'imc:mychannel'). Finally you can specify the full coordinates to the referenced channel with Group:Version:Kind:Name (e.g. 'messaging.knative.dev:v1alpha1:KafkaChannel:mychannel'). + -h, --help help for create + -n, --namespace string Specify the namespace to operate in. + -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. + --sink-dead-letter string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink-dead-letter broker:nest' for a broker 'nest', '--sink-dead-letter https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink-dead-letter ksvc:receiver' or simply '--sink-dead-letter receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. + --sink-reply string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink-reply broker:nest' for a broker 'nest', '--sink-reply https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink-reply ksvc:receiver' or simply '--sink-reply receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. +``` + +### Options inherited from parent commands + +``` + --config string kn configuration file (default: ~/.config/kn/config.yaml) + --kubeconfig string kubectl configuration file (default: ~/.kube/config) + --log-http log http traffic +``` + +### SEE ALSO + +* [kn subscription](kn_subscription.md) - Manage event subscriptions + diff --git a/docs/cmd/kn_subscription_delete.md b/docs/cmd/kn_subscription_delete.md new file mode 100644 index 0000000000..c173f3aaae --- /dev/null +++ b/docs/cmd/kn_subscription_delete.md @@ -0,0 +1,39 @@ +## kn subscription delete + +Delete a subscription + +### Synopsis + +Delete a subscription + +``` +kn subscription delete NAME +``` + +### Examples + +``` + + # Delete a subscription 'sub0' + kn subscription delete sub0 +``` + +### Options + +``` + -h, --help help for delete + -n, --namespace string Specify the namespace to operate in. +``` + +### Options inherited from parent commands + +``` + --config string kn configuration file (default: ~/.config/kn/config.yaml) + --kubeconfig string kubectl configuration file (default: ~/.kube/config) + --log-http log http traffic +``` + +### SEE ALSO + +* [kn subscription](kn_subscription.md) - Manage event subscriptions + diff --git a/docs/cmd/kn_subscription_describe.md b/docs/cmd/kn_subscription_describe.md new file mode 100644 index 0000000000..e04c01de19 --- /dev/null +++ b/docs/cmd/kn_subscription_describe.md @@ -0,0 +1,43 @@ +## kn subscription describe + +Show details of a subscription + +### Synopsis + +Show details of a subscription + +``` +kn subscription describe NAME +``` + +### Examples + +``` + + # Describe a subscription 'pipe' + kn subscription describe pipe +``` + +### Options + +``` + --allow-missing-template-keys If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. (default true) + -h, --help help for describe + -n, --namespace string Specify the namespace to operate in. + -o, --output string Output format. One of: json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file. + --template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. + -v, --verbose More output. +``` + +### Options inherited from parent commands + +``` + --config string kn configuration file (default: ~/.config/kn/config.yaml) + --kubeconfig string kubectl configuration file (default: ~/.kube/config) + --log-http log http traffic +``` + +### SEE ALSO + +* [kn subscription](kn_subscription.md) - Manage event subscriptions + diff --git a/docs/cmd/kn_subscription_list.md b/docs/cmd/kn_subscription_list.md new file mode 100644 index 0000000000..4aead93ab4 --- /dev/null +++ b/docs/cmd/kn_subscription_list.md @@ -0,0 +1,47 @@ +## kn subscription list + +List subscriptions + +### Synopsis + +List subscriptions + +``` +kn subscription list +``` + +### Examples + +``` + + # List all subscriptions + kn subscription list + + # List subscriptions in YAML format + kn subscription list -o yaml +``` + +### Options + +``` + -A, --all-namespaces If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace. + --allow-missing-template-keys If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. (default true) + -h, --help help for list + -n, --namespace string Specify the namespace to operate in. + --no-headers When using the default output format, don't print headers (default: print headers). + -o, --output string Output format. One of: json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file. + --template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. +``` + +### Options inherited from parent commands + +``` + --config string kn configuration file (default: ~/.config/kn/config.yaml) + --kubeconfig string kubectl configuration file (default: ~/.kube/config) + --log-http log http traffic +``` + +### SEE ALSO + +* [kn subscription](kn_subscription.md) - Manage event subscriptions + diff --git a/docs/cmd/kn_subscription_update.md b/docs/cmd/kn_subscription_update.md new file mode 100644 index 0000000000..3ce6e6cd75 --- /dev/null +++ b/docs/cmd/kn_subscription_update.md @@ -0,0 +1,45 @@ +## kn subscription update + +Update an event subscription + +### Synopsis + +Update an event subscription + +``` +kn subscription update NAME +``` + +### Examples + +``` + + # Update a subscription 'sub0' with a subscriber ksvc 'receiver' + kn subscription update sub0 --sink ksvc:receiver + + # Update a subscription 'sub1' with subscriber ksvc 'mirror', reply to a broker 'nest' and DeadLetterSink to a ksvc 'bucket' + kn subscription update sub1 --sink mirror --sink-reply broker:nest --sink-dead-letter bucket +``` + +### Options + +``` + -h, --help help for update + -n, --namespace string Specify the namespace to operate in. + -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. + --sink-dead-letter string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink-dead-letter broker:nest' for a broker 'nest', '--sink-dead-letter https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink-dead-letter ksvc:receiver' or simply '--sink-dead-letter receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. + --sink-reply string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink-reply broker:nest' for a broker 'nest', '--sink-reply https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink-reply ksvc:receiver' or simply '--sink-reply receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. +``` + +### Options inherited from parent commands + +``` + --config string kn configuration file (default: ~/.config/kn/config.yaml) + --kubeconfig string kubectl configuration file (default: ~/.kube/config) + --log-http log http traffic +``` + +### SEE ALSO + +* [kn subscription](kn_subscription.md) - Manage event subscriptions + diff --git a/docs/cmd/kn_trigger_create.md b/docs/cmd/kn_trigger_create.md index e5ea45bf12..f9c57ed0a6 100644 --- a/docs/cmd/kn_trigger_create.md +++ b/docs/cmd/kn_trigger_create.md @@ -29,7 +29,7 @@ kn trigger create NAME --sink SINK -h, --help help for create --inject-broker Create new broker with name default through common annotation -n, --namespace string Specify the namespace to operate in. - -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink 'ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If prefix is not provided, it is considered as a Knative service. + -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. ``` ### Options inherited from parent commands diff --git a/docs/cmd/kn_trigger_update.md b/docs/cmd/kn_trigger_update.md index 761bd5cc01..7a609489c4 100644 --- a/docs/cmd/kn_trigger_update.md +++ b/docs/cmd/kn_trigger_update.md @@ -33,7 +33,7 @@ kn trigger update NAME -h, --help help for update --inject-broker Create new broker with name default through common annotation -n, --namespace string Specify the namespace to operate in. - -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink 'ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If prefix is not provided, it is considered as a Knative service. + -s, --sink string Addressable sink for events. You can specify a broker, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. If a prefix is not provided, it is considered as a Knative service. ``` ### Options inherited from parent commands diff --git a/lib/printing/describe.go b/lib/printing/describe.go new file mode 100644 index 0000000000..a130ad6463 --- /dev/null +++ b/lib/printing/describe.go @@ -0,0 +1,45 @@ +/* +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 printing + +import ( + "fmt" + + "knative.dev/client/pkg/printers" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +// DescribeSink prints the given 'sink' for the given prefix writer 'dw', +// provide 'attribute' to print the section heading for this sink +func DescribeSink(dw printers.PrefixWriter, attribute, namespace string, sink *duckv1.Destination) { + if sink == nil { + return + } + subWriter := dw.WriteAttribute(attribute, "") + ref := sink.Ref + if ref != nil { + subWriter.WriteAttribute("Name", sink.Ref.Name) + if sink.Ref.Namespace != "" && sink.Ref.Namespace != namespace { + subWriter.WriteAttribute("Namespace", sink.Ref.Namespace) + } + subWriter.WriteAttribute("Resource", fmt.Sprintf("%s (%s)", sink.Ref.Kind, sink.Ref.APIVersion)) + } + uri := sink.URI + if uri != nil { + subWriter.WriteAttribute("URI", uri.String()) + } +} diff --git a/lib/test/subscription.go b/lib/test/subscription.go new file mode 100644 index 0000000000..08734f6cae --- /dev/null +++ b/lib/test/subscription.go @@ -0,0 +1,61 @@ +/* +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 ( + "gotest.tools/assert" + + "knative.dev/client/pkg/util" +) + +func SubscriptionCreate(r *KnRunResultCollector, sname string, args ...string) { + cmd := []string{"subscription", "create", sname} + cmd = append(cmd, args...) + out := r.KnTest().Kn().Run(cmd...) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "subscription", sname, "created")) +} + +func SubscriptionList(r *KnRunResultCollector, args ...string) string { + cmd := []string{"subscription", "list"} + cmd = append(cmd, args...) + out := r.KnTest().Kn().Run(cmd...) + r.AssertNoError(out) + return out.Stdout +} + +func SubscriptionDescribe(r *KnRunResultCollector, sname string, args ...string) string { + cmd := []string{"subscription", "describe", sname} + cmd = append(cmd, args...) + out := r.KnTest().Kn().Run(cmd...) + r.AssertNoError(out) + return out.Stdout +} + +func SubscriptionDelete(r *KnRunResultCollector, sname string) { + out := r.KnTest().Kn().Run("subscription", "delete", sname) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "subscription", sname, "deleted")) +} + +func SubscriptionUpdate(r *KnRunResultCollector, sname string, args ...string) { + cmd := []string{"subscription", "update", sname} + cmd = append(cmd, args...) + out := r.KnTest().Kn().Run(cmd...) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "subscription", sname, "updated")) +} diff --git a/pkg/dynamic/fake/fake.go b/pkg/dynamic/fake/fake.go index c4c2956808..a4529fa5a5 100644 --- a/pkg/dynamic/fake/fake.go +++ b/pkg/dynamic/fake/fake.go @@ -20,6 +20,7 @@ import ( dynamicfake "k8s.io/client-go/dynamic/fake" eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" servingv1 "knative.dev/serving/pkg/apis/serving/v1" "knative.dev/client/pkg/dynamic" @@ -30,6 +31,7 @@ func CreateFakeKnDynamicClient(testNamespace string, objects ...runtime.Object) scheme := runtime.NewScheme() scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "serving.knative.dev", Version: "v1", Kind: "Service"}, &servingv1.Service{}) scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "eventing.knative.dev", Version: "v1beta1", Kind: "Broker"}, &eventingv1beta1.Broker{}) + scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "eventing.knative.dev", Version: "v1beta1", Kind: "Subscription"}, &messagingv1beta1.Subscription{}) client := dynamicfake.NewSimpleDynamicClient(scheme, objects...) return dynamic.NewKnDynamicClient(client, testNamespace) } diff --git a/pkg/kn/commands/flags/sink.go b/pkg/kn/commands/flags/sink.go index a07c470752..c0c950e9a4 100644 --- a/pkg/kn/commands/flags/sink.go +++ b/pkg/kn/commands/flags/sink.go @@ -32,17 +32,21 @@ type SinkFlags struct { sink string } -func (i *SinkFlags) Add(cmd *cobra.Command) { - cmd.Flags().StringVarP(&i.sink, - "sink", - "s", - "", - "Addressable sink for events. "+ - "You can specify a broker, Knative service or URI. "+ - "Examples: '--sink broker:nest' for a broker 'nest', "+ - "'--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, "+ - "'--sink 'ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver'. "+ - "If prefix is not provided, it is considered as a Knative service.") +// AddWithFlagName configures sink flag with given flag name and a short flag name +// pass empty short flag name if you dont want to set one +func (i *SinkFlags) AddWithFlagName(cmd *cobra.Command, fname, short string) { + flag := "--" + fname + if short == "" { + cmd.Flags().StringVar(&i.sink, fname, "", "") + } else { + cmd.Flags().StringVarP(&i.sink, fname, short, "", "") + } + cmd.Flag(fname).Usage = "Addressable sink for events. " + + "You can specify a broker, Knative service or URI. " + + "Examples: '" + flag + " broker:nest' for a broker 'nest', " + + "'" + flag + " https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, " + + "'" + flag + " ksvc:receiver' or simply '" + flag + " receiver' for a Knative service 'receiver'. " + + "If a prefix is not provided, it is considered as a Knative service." for _, p := range config.GlobalConfig.SinkMappings() { //user configration might override the default configuration @@ -54,6 +58,11 @@ func (i *SinkFlags) Add(cmd *cobra.Command) { } } +// Add configures sink flag with name 'sink' amd short name 's' +func (i *SinkFlags) Add(cmd *cobra.Command) { + i.AddWithFlagName(cmd, "sink", "s") +} + // sinkPrefixes maps prefixes used for sinks to their GroupVersionResources. var sinkMappings = map[string]schema.GroupVersionResource{ "broker": { diff --git a/pkg/kn/commands/flags/sink_test.go b/pkg/kn/commands/flags/sink_test.go index c3a3e9a9ed..5a0470142f 100644 --- a/pkg/kn/commands/flags/sink_test.go +++ b/pkg/kn/commands/flags/sink_test.go @@ -17,6 +17,7 @@ package flags import ( "testing" + "github.com/spf13/cobra" "gotest.tools/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" @@ -33,6 +34,40 @@ type resolveCase struct { errContents string } +type sinkFlagAddTestCases struct { + flagName string + expectedFlagName string + expectedShortName string +} + +func TestSinkFlagAdd(t *testing.T) { + cases := []*sinkFlagAddTestCases{ + { + "", + "sink", + "s", + }, + { + "subscriber", + "subscriber", + "", + }, + } + for _, tc := range cases { + c := &cobra.Command{Use: "sinktest"} + sinkFlags := new(SinkFlags) + if tc.flagName == "" { + sinkFlags.Add(c) + assert.Equal(t, tc.expectedFlagName, c.Flag("sink").Name) + assert.Equal(t, tc.expectedShortName, c.Flag("sink").Shorthand) + } else { + sinkFlags.AddWithFlagName(c, tc.flagName, "") + assert.Equal(t, tc.expectedFlagName, c.Flag(tc.flagName).Name) + assert.Equal(t, tc.expectedShortName, c.Flag(tc.flagName).Shorthand) + } + } +} + func TestResolve(t *testing.T) { targetExampleCom, err := apis.ParseURL("http://target.example.com") mysvc := &servingv1.Service{ diff --git a/pkg/kn/commands/subscription/create.go b/pkg/kn/commands/subscription/create.go new file mode 100644 index 0000000000..867555c4c8 --- /dev/null +++ b/pkg/kn/commands/subscription/create.go @@ -0,0 +1,116 @@ +/* +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 subscription + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + + knerrors "knative.dev/client/pkg/errors" + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/kn/commands/flags" + knflags "knative.dev/client/pkg/kn/flags" + knmessagingv1beta1 "knative.dev/client/pkg/messaging/v1beta1" +) + +// NewSubscriptionCreateCommand to create event subscriptions +func NewSubscriptionCreateCommand(p *commands.KnParams) *cobra.Command { + var ( + crefFlag knflags.ChannelRef + subscriberFlag, replyFlag, dlsFlag flags.SinkFlags + ) + + cmd := &cobra.Command{ + Use: "create NAME", + Short: "Create a subscription", + Example: ` + # Create a subscription 'sub0' from InMemoryChannel 'pipe0' to a subscriber ksvc 'receiver' + kn subscription create sub0 --channel imcv1beta1:pipe0 --sink ksvc:receiver + + # Create a subscription 'sub1' from KafkaChannel 'k1' to ksvc 'mirror', reply to a broker 'nest' and DeadLetterSink to a ksvc 'bucket' + kn subscription create sub1 --channel messaging.knative.dev:v1alpha1:KafkaChannel:k1 --sink mirror --sink-reply broker:nest --sink-dead-letter bucket`, + + RunE: func(cmd *cobra.Command, args []string) (err error) { + if len(args) != 1 { + return errors.New("'kn subscription create' requires the subscription name given as single argument") + } + name := args[0] + + if crefFlag.Cref == "" { + return errors.New("'kn subscription create' requires the channel reference provided with --channel flag") + } + + namespace, err := p.GetNamespace(cmd) + if err != nil { + return err + } + + dynamicClient, err := p.NewDynamicClient(namespace) + if err != nil { + return err + } + + client, err := newSubscriptionClient(p, cmd) + if err != nil { + return err + } + + sb := knmessagingv1beta1.NewSubscriptionBuilder(name) + + cref, err := crefFlag.Parse() + if err != nil { + return err + } + sb.Channel(cref) + + sub, err := subscriberFlag.ResolveSink(dynamicClient, namespace) + if err != nil { + return err + } + sb.Subscriber(sub) + + rep, err := replyFlag.ResolveSink(dynamicClient, namespace) + if err != nil { + return err + } + sb.Reply(rep) + + ds, err := dlsFlag.ResolveSink(dynamicClient, namespace) + if err != nil { + return err + } + sb.DeadLetterSink(ds) + + err = client.CreateSubscription(sb.Build()) + if err != nil { + return knerrors.GetError(err) + } + + fmt.Fprintf(cmd.OutOrStdout(), "Subscription '%s' created in namespace '%s'.\n", name, namespace) + return nil + }, + } + commands.AddNamespaceFlags(cmd.Flags(), false) + crefFlag.Add(cmd.Flags()) + // add subscriber flag as `--sink` + subscriberFlag.Add(cmd) + replyFlag.AddWithFlagName(cmd, "sink-reply", "") + dlsFlag.AddWithFlagName(cmd, "sink-dead-letter", "") + return cmd +} diff --git a/pkg/kn/commands/subscription/create_test.go b/pkg/kn/commands/subscription/create_test.go new file mode 100644 index 0000000000..3073309dfa --- /dev/null +++ b/pkg/kn/commands/subscription/create_test.go @@ -0,0 +1,82 @@ +/* +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 subscription + +import ( + "testing" + + "gotest.tools/assert" + "knative.dev/client/pkg/messaging/v1beta1" + + dynamicfake "knative.dev/client/pkg/dynamic/fake" + "knative.dev/client/pkg/util" +) + +func TestCreateSubscriptionErrorCase(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t) + dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default") + + cRecorder := cClient.Recorder() + _, err := executeSubscriptionCommand(cClient, dynamicClient, "create") + assert.Error(t, err, "'kn subscription create' requires the subscription name given as single argument") + cRecorder.Validate() +} + +func TestCreateSubscriptionErrorCaseRequiredChannelFlag(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t) + dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default") + + cRecorder := cClient.Recorder() + _, err := executeSubscriptionCommand(cClient, dynamicClient, "create", "sub0") + assert.Error(t, err, "'kn subscription create' requires the channel reference provided with --channel flag") + cRecorder.Validate() +} + +func TestCreateSubscriptionErrorCaseChannelFormat(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t) + dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default") + + cRecorder := cClient.Recorder() + _, err := executeSubscriptionCommand(cClient, dynamicClient, "create", "sub0", "--channel", "foo::bar") + assert.Error(t, err, "Error: incorrect value 'foo::bar' for '--channel', must be in the format 'Group:Version:Kind:Name' or configure an alias in kn config and refer as: '--channel ALIAS:NAME'") + cRecorder.Validate() +} + +func TestCreateSubscription(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t) + dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default", + createService("ksvc0"), + createBroker("b0"), + createBroker("b1")) + + cRecorder := cClient.Recorder() + cRecorder.CreateSubscription(createSubscription("sub0", + "imc0", + "ksvc0", + "b0", + "b1"), + nil) + + out, err := executeSubscriptionCommand(cClient, dynamicClient, "create", "sub0", + "--channel", "imcv1beta1:imc0", + "--sink", "ksvc0", + "--sink-reply", "broker:b0", + "--sink-dead-letter", "broker:b1") + assert.NilError(t, err, "subscription should be created") + assert.Assert(t, util.ContainsAll(out, "created", "sub0", "default")) + cRecorder.Validate() +} diff --git a/pkg/kn/commands/subscription/delete.go b/pkg/kn/commands/subscription/delete.go new file mode 100644 index 0000000000..9fdd0fd703 --- /dev/null +++ b/pkg/kn/commands/subscription/delete.go @@ -0,0 +1,57 @@ +/* +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 subscription + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + "knative.dev/client/pkg/kn/commands" +) + +// NewSubscriptionDeleteCommand is for deleting a Subscription +func NewSubscriptionDeleteCommand(p *commands.KnParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "delete NAME", + Short: "Delete a subscription", + Example: ` + # Delete a subscription 'sub0' + kn subscription delete sub0`, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("'kn subscription delete' requires the subscription name as single argument") + } + name := args[0] + + subscriptionClient, err := newSubscriptionClient(p, cmd) + if err != nil { + return err + } + + err = subscriptionClient.DeleteSubscription(name) + if err != nil { + return err + } + + fmt.Fprintf(cmd.OutOrStdout(), "Subscription '%s' deleted in namespace '%s'.\n", name, subscriptionClient.Namespace()) + return nil + }, + } + commands.AddNamespaceFlags(cmd.Flags(), false) + return cmd +} diff --git a/pkg/kn/commands/subscription/delete_test.go b/pkg/kn/commands/subscription/delete_test.go new file mode 100644 index 0000000000..38d1014cf4 --- /dev/null +++ b/pkg/kn/commands/subscription/delete_test.go @@ -0,0 +1,54 @@ +/* +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 subscription + +import ( + "errors" + "testing" + + "gotest.tools/assert" + + "knative.dev/client/pkg/messaging/v1beta1" + "knative.dev/client/pkg/util" +) + +func TestDeleteSubscriptionErrorCase(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t, "test") + cRecorder := cClient.Recorder() + _, err := executeSubscriptionCommand(cClient, nil, "delete") + assert.Error(t, err, "'kn subscription delete' requires the subscription name as single argument") + cRecorder.Validate() +} + +func TestDeleteWithError(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t, "test") + cRecorder := cClient.Recorder() + cRecorder.DeleteSubscription("sub0", errors.New("not found")) + _, err := executeSubscriptionCommand(cClient, nil, "delete", "sub0") + assert.ErrorContains(t, err, "not found") + cRecorder.Validate() +} + +func TestSubscriptionDelete(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t, "test") + cRecorder := cClient.Recorder() + cRecorder.DeleteSubscription("sub0", nil) + out, err := executeSubscriptionCommand(cClient, nil, "delete", "sub0") + assert.NilError(t, err) + assert.Assert(t, util.ContainsAll(out, "deleted", "sub0", "test")) + cRecorder.Validate() +} diff --git a/pkg/kn/commands/subscription/describe.go b/pkg/kn/commands/subscription/describe.go new file mode 100644 index 0000000000..4e24bba6c8 --- /dev/null +++ b/pkg/kn/commands/subscription/describe.go @@ -0,0 +1,110 @@ +/* +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 subscription + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + + "k8s.io/cli-runtime/pkg/genericclioptions" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + + "knative.dev/client/lib/printing" + knerrors "knative.dev/client/pkg/errors" + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/printers" +) + +// NewSubscriptionDescribeCommand returns a new command for describe a subscription object +func NewSubscriptionDescribeCommand(p *commands.KnParams) *cobra.Command { + + // For machine readable output + machineReadablePrintFlags := genericclioptions.NewPrintFlags("") + + cmd := &cobra.Command{ + Use: "describe NAME", + Short: "Show details of a subscription", + Example: ` + # Describe a subscription 'pipe' + kn subscription describe pipe`, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("'kn subscription describe' requires the subscription name given as single argument") + } + name := args[0] + + client, err := newSubscriptionClient(p, cmd) + if err != nil { + return err + } + + subscription, err := client.GetSubscription(name) + if err != nil { + return knerrors.GetError(err) + } + + out := cmd.OutOrStdout() + + if machineReadablePrintFlags.OutputFlagSpecified() { + printer, err := machineReadablePrintFlags.ToPrinter() + if err != nil { + return err + } + return printer.PrintObj(subscription, out) + } + + dw := printers.NewPrefixWriter(out) + + printDetails, err := cmd.Flags().GetBool("verbose") + if err != nil { + return err + } + + writeSubscription(dw, subscription, printDetails) + dw.WriteLine() + if err := dw.Flush(); err != nil { + return err + } + + // Condition info + commands.WriteConditions(dw, subscription.Status.Conditions, printDetails) + if err := dw.Flush(); err != nil { + return err + } + + return nil + }, + } + flags := cmd.Flags() + commands.AddNamespaceFlags(flags, false) + flags.BoolP("verbose", "v", false, "More output.") + machineReadablePrintFlags.AddFlags(cmd) + return cmd +} + +func writeSubscription(dw printers.PrefixWriter, subscription *messagingv1beta1.Subscription, printDetails bool) { + commands.WriteMetadata(dw, &subscription.ObjectMeta, printDetails) + ctype := fmt.Sprintf("%s:%s (%s)", subscription.Spec.Channel.Kind, subscription.Spec.Channel.Name, subscription.Spec.Channel.APIVersion) + dw.WriteAttribute("Channel", ctype) + printing.DescribeSink(dw, "Subscriber", subscription.Namespace, subscription.Spec.Subscriber) + printing.DescribeSink(dw, "Reply", subscription.Namespace, subscription.Spec.Reply) + if subscription.Spec.DeepCopy().Delivery != nil { + printing.DescribeSink(dw, "DeadLetterSink", subscription.Namespace, subscription.Spec.Delivery.DeadLetterSink) + } +} diff --git a/pkg/kn/commands/subscription/describe_test.go b/pkg/kn/commands/subscription/describe_test.go new file mode 100644 index 0000000000..5f581d6bb7 --- /dev/null +++ b/pkg/kn/commands/subscription/describe_test.go @@ -0,0 +1,59 @@ +/* +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 subscription + +import ( + "errors" + "testing" + + "gotest.tools/assert" + "knative.dev/client/pkg/messaging/v1beta1" + + "knative.dev/client/pkg/util" +) + +func TestDescribeSubscriptionErrorCase(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t) + cRecorder := cClient.Recorder() + _, err := executeSubscriptionCommand(cClient, nil, "describe") + assert.Error(t, err, "'kn subscription describe' requires the subscription name given as single argument") + cRecorder.Validate() +} + +func TestDescribeSubscriptionErrorCaseNotFound(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t) + cRecorder := cClient.Recorder() + cRecorder.GetSubscription("sub0", nil, errors.New("not found")) + _, err := executeSubscriptionCommand(cClient, nil, "describe", "sub0") + assert.Error(t, err, "not found") + cRecorder.Validate() +} + +func TestDescribeSubscription(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t) + cRecorder := cClient.Recorder() + cRecorder.GetSubscription("sub0", createSubscription("sub0", "imc0", "ksvc0", "b0", "b1"), nil) + out, err := executeSubscriptionCommand(cClient, nil, "describe", "sub0") + assert.NilError(t, err, "subscription should be described") + assert.Assert(t, util.ContainsAll(out, + "sub0", + "Channel", "imc0", "messaging.knative.dev", "v1beta1", "InMemoryChannel", + "Subscriber", "ksvc0", "serving.knative.dev", "v1", "Service", + "Reply", "b0", "eventing.knative.dev", "v1beta1", "Broker", + "DeadLetterSink", "b1")) + cRecorder.Validate() +} diff --git a/pkg/kn/commands/subscription/flags.go b/pkg/kn/commands/subscription/flags.go new file mode 100644 index 0000000000..d5495b6b7b --- /dev/null +++ b/pkg/kn/commands/subscription/flags.go @@ -0,0 +1,139 @@ +/* +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 subscription + +import ( + "sort" + + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/kn/commands/flags" + hprinters "knative.dev/client/pkg/printers" + + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" +) + +// ListHandlers handles printing human readable table for `kn subscription list` command's output +func ListHandlers(h hprinters.PrintHandler) { + subscriptionColumnDefinitions := []metav1beta1.TableColumnDefinition{ + {Name: "Namespace", Type: "string", Description: "Namespace of the subscription", Priority: 0}, + {Name: "Name", Type: "string", Description: "Name of the subscription", Priority: 1}, + {Name: "Channel", Type: "string", Description: "Channel of the subcription", Priority: 1}, + {Name: "Subscriber", Type: "string", Description: "Subscriber sink of the subscription", Priority: 1}, + {Name: "Reply", Type: "string", Description: "Reply sink of the subscription", Priority: 1}, + {Name: "Dead Letter Sink", Type: "string", Description: "DeadLetterSink of the subscription", Priority: 1}, + {Name: "Ready", Type: "string", Description: "Ready state of the subscription", Priority: 1}, + {Name: "Reason", Type: "string", Description: "Reason for non ready subscription", Priority: 1}, + } + h.TableHandler(subscriptionColumnDefinitions, printSubscription) + h.TableHandler(subscriptionColumnDefinitions, printSubscriptionList) +} + +// printSubscription populates a single row of Subscription list +func printSubscription(subscription *messagingv1beta1.Subscription, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) { + row := metav1beta1.TableRow{ + Object: runtime.RawExtension{Object: subscription}, + } + + name := subscription.Name + ctype := subscription.Spec.Channel.Kind + channel := subscription.Spec.Channel.Name + + var subscriber, reply, dls string + if subscription.Spec.Subscriber != nil { + subscriber = flags.SinkToString(*subscription.Spec.Subscriber) + } else { + subscriber = "" + } + if subscription.Spec.Reply != nil { + reply = flags.SinkToString(*subscription.Spec.Reply) + } else { + reply = "" + } + if subscription.Spec.Delivery != nil && subscription.Spec.Delivery.DeadLetterSink != nil { + dls = flags.SinkToString(*subscription.Spec.Delivery.DeadLetterSink) + } else { + dls = "" + } + ready := commands.ReadyCondition(subscription.Status.Conditions) + reason := commands.NonReadyConditionReason(subscription.Status.Conditions) + + if options.AllNamespaces { + row.Cells = append(row.Cells, subscription.Namespace) + } + + row.Cells = append(row.Cells, name, ctype+":"+channel, subscriber, reply, dls, ready, reason) + return []metav1beta1.TableRow{row}, nil +} + +// printSubscriptionList populates the Subscription list table rows +func printSubscriptionList(subscriptionList *messagingv1beta1.SubscriptionList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) { + if options.AllNamespaces { + return printSubscriptionListWithNamespace(subscriptionList, options) + } + + rows := make([]metav1beta1.TableRow, 0, len(subscriptionList.Items)) + + sort.SliceStable(subscriptionList.Items, func(i, j int) bool { + return subscriptionList.Items[i].GetName() < subscriptionList.Items[j].GetName() + }) + + for _, item := range subscriptionList.Items { + row, err := printSubscription(&item, options) + if err != nil { + return nil, err + } + + rows = append(rows, row...) + } + return rows, nil +} + +// printSubscriptionListWithNamespace populates the knative service table rows with namespace column +func printSubscriptionListWithNamespace(subscriptionList *messagingv1beta1.SubscriptionList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) { + rows := make([]metav1beta1.TableRow, 0, len(subscriptionList.Items)) + + // temporary slice for sorting services in non-default namespace + var others []metav1beta1.TableRow + + for _, subscription := range subscriptionList.Items { + // Fill in with services in `default` namespace at first + if subscription.Namespace == "default" { + r, err := printSubscription(&subscription, options) + if err != nil { + return nil, err + } + rows = append(rows, r...) + continue + } + // put other services in temporary slice + r, err := printSubscription(&subscription, options) + if err != nil { + return nil, err + } + others = append(others, r...) + } + + // sort other services list alphabetically by namespace + sort.SliceStable(others, func(i, j int) bool { + return others[i].Cells[0].(string) < others[j].Cells[0].(string) + }) + + return append(rows, others...), nil +} diff --git a/pkg/kn/commands/subscription/list.go b/pkg/kn/commands/subscription/list.go new file mode 100644 index 0000000000..8ee749d23d --- /dev/null +++ b/pkg/kn/commands/subscription/list.go @@ -0,0 +1,75 @@ +/* +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 subscription + +import ( + "fmt" + + "github.com/spf13/cobra" + + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/kn/commands/flags" +) + +// NewSubscriptionListCommand is for listing subscription objects +func NewSubscriptionListCommand(p *commands.KnParams) *cobra.Command { + listFlags := flags.NewListPrintFlags(ListHandlers) + + listCommand := &cobra.Command{ + Use: "list", + Short: "List subscriptions", + Example: ` + # List all subscriptions + kn subscription list + + # List subscriptions in YAML format + kn subscription list -o yaml`, + + RunE: func(cmd *cobra.Command, args []string) (err error) { + // TODO: filter list by given subscription name + + client, err := newSubscriptionClient(p, cmd) + if err != nil { + return err + } + + subscriptionList, err := client.ListSubscription() + if err != nil { + return err + } + + if subscriptionList == nil || len(subscriptionList.Items) == 0 { + fmt.Fprintf(cmd.OutOrStdout(), "No subscriptions found.\n") + return nil + } + + if client.Namespace() == "" { + listFlags.EnsureWithNamespace() + } + + err = listFlags.Print(subscriptionList, cmd.OutOrStdout()) + if err != nil { + return err + } + + return nil + }, + } + commands.AddNamespaceFlags(listCommand.Flags(), true) + listFlags.AddFlags(listCommand) + return listCommand +} diff --git a/pkg/kn/commands/subscription/list_test.go b/pkg/kn/commands/subscription/list_test.go new file mode 100644 index 0000000000..2be6581b8d --- /dev/null +++ b/pkg/kn/commands/subscription/list_test.go @@ -0,0 +1,71 @@ +/* +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 subscription + +import ( + "strings" + "testing" + + "gotest.tools/assert" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + + v1beta1 "knative.dev/client/pkg/messaging/v1beta1" + "knative.dev/client/pkg/util" +) + +func TestSubscriptionListNoSubscriptionsFound(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t) + cRecorder := cClient.Recorder() + cRecorder.ListSubscription(nil, nil) + out, err := executeSubscriptionCommand(cClient, nil, "list") + assert.NilError(t, err) + assert.Check(t, util.ContainsAll(out, "No subscriptions found")) + cRecorder.Validate() +} + +func TestSubscriptionList(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t) + cRecorder := cClient.Recorder() + clist := &messagingv1beta1.SubscriptionList{} + clist.Items = []messagingv1beta1.Subscription{ + *createSubscription("s0", "imc0", "ksvc0", "b00", "b01"), + *createSubscription("s1", "imc1", "ksvc1", "b10", "b11"), + *createSubscription("s2", "imc2", "ksvc2", "b20", "b21"), + } + + t.Run("default list output", func(t *testing.T) { + cRecorder.ListSubscription(clist, nil) + out, err := executeSubscriptionCommand(cClient, nil, "list") + assert.NilError(t, err) + ol := strings.Split(out, "\n") + assert.Check(t, util.ContainsAll(ol[0], "NAME", "CHANNEL", "SUBSCRIBER", "REPLY", "DEAD LETTER SINK", "READY", "REASON")) + assert.Check(t, util.ContainsAll(ol[1], "s0", "InMemoryChannel:imc0", "ksvc:ksvc0", "broker:b00", "broker:b01")) + assert.Check(t, util.ContainsAll(ol[2], "s1", "imc1", "ksvc1", "b10", "b11")) + assert.Check(t, util.ContainsAll(ol[3], "s2", "imc2", "ksvc2", "b20", "b21")) + }) + + t.Run("no headers list output", func(t *testing.T) { + cRecorder.ListSubscription(clist, nil) + out, err := executeSubscriptionCommand(cClient, nil, "list", "--no-headers") + assert.NilError(t, err) + ol := strings.Split(out, "\n") + assert.Check(t, util.ContainsNone(ol[0], "NAME", "CHANNEL", "SUBSCRIBER", "REPLY", "DEAD LETTER SINK", "READY", "REASON")) + assert.Check(t, util.ContainsAll(ol[0], "s0", "imc0", "ksvc0", "b00", "b01")) + }) + + cRecorder.Validate() +} diff --git a/pkg/kn/commands/subscription/subscription.go b/pkg/kn/commands/subscription/subscription.go new file mode 100644 index 0000000000..ca6bec0cf9 --- /dev/null +++ b/pkg/kn/commands/subscription/subscription.go @@ -0,0 +1,71 @@ +/* +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 subscription + +import ( + "github.com/spf13/cobra" + + "k8s.io/client-go/tools/clientcmd" + clientv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1beta1" + + "knative.dev/client/pkg/kn/commands" + messagingv1beta1 "knative.dev/client/pkg/messaging/v1beta1" +) + +// NewSubscriptionCommand to manage event subscriptions +func NewSubscriptionCommand(p *commands.KnParams) *cobra.Command { + subscriptionCmd := &cobra.Command{ + Use: "subscription COMMAND", + Short: "Manage event subscriptions", + Aliases: []string{"subscriptions", "sub"}, + } + subscriptionCmd.AddCommand(NewSubscriptionCreateCommand(p)) + subscriptionCmd.AddCommand(NewSubscriptionUpdateCommand(p)) + subscriptionCmd.AddCommand(NewSubscriptionListCommand(p)) + subscriptionCmd.AddCommand(NewSubscriptionDeleteCommand(p)) + subscriptionCmd.AddCommand(NewSubscriptionDescribeCommand(p)) + return subscriptionCmd +} + +var subscriptionClientFactory func(config clientcmd.ClientConfig, namespace string) (messagingv1beta1.KnSubscriptionsClient, error) + +func newSubscriptionClient(p *commands.KnParams, cmd *cobra.Command) (messagingv1beta1.KnSubscriptionsClient, error) { + namespace, err := p.GetNamespace(cmd) + if err != nil { + return nil, err + } + + if subscriptionClientFactory != nil { + config, err := p.GetClientConfig() + if err != nil { + return nil, err + } + return subscriptionClientFactory(config, namespace) + } + + clientConfig, err := p.RestConfig() + if err != nil { + return nil, err + } + + client, err := clientv1beta1.NewForConfig(clientConfig) + if err != nil { + return nil, err + } + + return messagingv1beta1.NewKnMessagingClient(client, namespace).SubscriptionsClient(), nil +} diff --git a/pkg/kn/commands/subscription/subscription_test.go b/pkg/kn/commands/subscription/subscription_test.go new file mode 100644 index 0000000000..889fa31bc4 --- /dev/null +++ b/pkg/kn/commands/subscription/subscription_test.go @@ -0,0 +1,147 @@ +/* +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 subscription + +import ( + "bytes" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/clientcmd" + eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + "knative.dev/eventing/pkg/apis/messaging/v1beta1" + duckv1 "knative.dev/pkg/apis/duck/v1" + servingv1 "knative.dev/serving/pkg/apis/serving/v1" + + kndynamic "knative.dev/client/pkg/dynamic" + "knative.dev/client/pkg/kn/commands" + clientv1beta1 "knative.dev/client/pkg/messaging/v1beta1" +) + +// Helper methods +var blankConfig clientcmd.ClientConfig + +// TODO: Remove that blankConfig hack for tests in favor of overwriting GetConfig() +func init() { + var err error + blankConfig, err = clientcmd.NewClientConfigFromBytes([]byte(`kind: Config +version: v1 +users: +- name: u +clusters: +- name: c + cluster: + server: example.com +contexts: +- name: x + context: + user: u + cluster: c +current-context: x +`)) + if err != nil { + panic(err) + } +} + +func executeSubscriptionCommand(subscriptionClient clientv1beta1.KnSubscriptionsClient, dynamicClient kndynamic.KnDynamicClient, args ...string) (string, error) { + knParams := &commands.KnParams{} + knParams.ClientConfig = blankConfig + + output := new(bytes.Buffer) + knParams.Output = output + knParams.NewDynamicClient = func(namespace string) (kndynamic.KnDynamicClient, error) { + return dynamicClient, nil + } + + cmd := NewSubscriptionCommand(knParams) + cmd.SetArgs(args) + cmd.SetOutput(output) + + subscriptionClientFactory = func(config clientcmd.ClientConfig, namespace string) (clientv1beta1.KnSubscriptionsClient, error) { + return subscriptionClient, nil + } + defer cleanupSubscriptionMockClient() + + err := cmd.Execute() + + return output.String(), err +} + +func cleanupSubscriptionMockClient() { + subscriptionClientFactory = nil +} + +func createSubscription(name, channel, subscriber, reply, dls string) *v1beta1.Subscription { + return clientv1beta1. + NewSubscriptionBuilder(name). + Channel(createIMCObjectReference(channel)). + Subscriber(createServiceSink(subscriber)). + Reply(createBrokerSink(reply)). + DeadLetterSink(createBrokerSink(dls)). + Build() +} + +func createIMCObjectReference(channel string) *corev1.ObjectReference { + return &corev1.ObjectReference{ + APIVersion: "messaging.knative.dev/v1beta1", + Kind: "InMemoryChannel", + Name: channel, + } +} + +func createServiceSink(service string) *duckv1.Destination { + if service == "" { + return nil + } + return &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + APIVersion: "serving.knative.dev/v1", + Name: service, + Namespace: "default", + }, + } +} + +func createBrokerSink(broker string) *duckv1.Destination { + if broker == "" { + return nil + } + return &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Broker", + APIVersion: "eventing.knative.dev/v1beta1", + Name: broker, + Namespace: "default", + }, + } +} + +func createService(name string) *servingv1.Service { + return &servingv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "serving.knative.dev/v1"}, + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "default"}, + } +} + +func createBroker(name string) *eventingv1beta1.Broker { + return &eventingv1beta1.Broker{ + TypeMeta: metav1.TypeMeta{Kind: "Broker", APIVersion: "eventing.knative.dev/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "default"}, + } +} diff --git a/pkg/kn/commands/subscription/update.go b/pkg/kn/commands/subscription/update.go new file mode 100644 index 0000000000..87be5523f3 --- /dev/null +++ b/pkg/kn/commands/subscription/update.go @@ -0,0 +1,105 @@ +/* +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 subscription + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + + knerrors "knative.dev/client/pkg/errors" + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/kn/commands/flags" + knmessagingv1beta1 "knative.dev/client/pkg/messaging/v1beta1" +) + +// NewSubscriptionUpdateCommand to update event subscriptions +func NewSubscriptionUpdateCommand(p *commands.KnParams) *cobra.Command { + var subscriberFlag, replyFlag, dlsFlag flags.SinkFlags + cmd := &cobra.Command{ + Use: "update NAME", + Short: "Update an event subscription", + Example: ` + # Update a subscription 'sub0' with a subscriber ksvc 'receiver' + kn subscription update sub0 --sink ksvc:receiver + + # Update a subscription 'sub1' with subscriber ksvc 'mirror', reply to a broker 'nest' and DeadLetterSink to a ksvc 'bucket' + kn subscription update sub1 --sink mirror --sink-reply broker:nest --sink-dead-letter bucket`, + + RunE: func(cmd *cobra.Command, args []string) (err error) { + if len(args) != 1 { + return errors.New("'kn subscription update' requires the subscription name given as single argument") + } + name := args[0] + + namespace, err := p.GetNamespace(cmd) + if err != nil { + return err + } + + dynamicClient, err := p.NewDynamicClient(namespace) + if err != nil { + return err + } + + client, err := newSubscriptionClient(p, cmd) + if err != nil { + return err + } + + foundSub, err := client.GetSubscription(name) + if err != nil { + return err + } + + sb := knmessagingv1beta1.NewSubscriptionBuilderFromExisting(foundSub) + + sub, err := subscriberFlag.ResolveSink(dynamicClient, namespace) + if err != nil { + return err + } + sb.Subscriber(sub) + + rep, err := replyFlag.ResolveSink(dynamicClient, namespace) + if err != nil { + return err + } + sb.Reply(rep) + + ds, err := dlsFlag.ResolveSink(dynamicClient, namespace) + if err != nil { + return err + } + sb.DeadLetterSink(ds) + + err = client.UpdateSubscription(sb.Build()) + if err != nil { + return knerrors.GetError(err) + } + + fmt.Fprintf(cmd.OutOrStdout(), "Subscription '%s' updated in namespace '%s'.\n", name, namespace) + return nil + }, + } + commands.AddNamespaceFlags(cmd.Flags(), false) + // add subscriber flag as `--sink` + subscriberFlag.Add(cmd) + replyFlag.AddWithFlagName(cmd, "sink-reply", "") + dlsFlag.AddWithFlagName(cmd, "sink-dead-letter", "") + return cmd +} diff --git a/pkg/kn/commands/subscription/update_test.go b/pkg/kn/commands/subscription/update_test.go new file mode 100644 index 0000000000..856de466aa --- /dev/null +++ b/pkg/kn/commands/subscription/update_test.go @@ -0,0 +1,74 @@ +/* +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 subscription + +import ( + "testing" + + "gotest.tools/assert" + "knative.dev/client/pkg/messaging/v1beta1" + + dynamicfake "knative.dev/client/pkg/dynamic/fake" + "knative.dev/client/pkg/util" +) + +func TestUpdateSubscriptionErrorCase(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t) + dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default") + + cRecorder := cClient.Recorder() + _, err := executeSubscriptionCommand(cClient, dynamicClient, "update") + assert.Error(t, err, "'kn subscription update' requires the subscription name given as single argument") + cRecorder.Validate() +} + +func TestUpdateSubscriptionErrorCaseUnknownChannelFlag(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t) + dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default") + + cRecorder := cClient.Recorder() + _, err := executeSubscriptionCommand(cClient, dynamicClient, "update", "sub0", "--channel", "imc:i1") + assert.Error(t, err, "unknown flag: --channel") + cRecorder.Validate() +} + +func TestUpdateSubscription(t *testing.T) { + cClient := v1beta1.NewMockKnSubscriptionsClient(t) + sub0 := createSubscription("sub0", "imc0", "ksvc0", "", "") + dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default", + sub0, + createService("ksvc1"), + createBroker("b0"), + createBroker("b1")) + + cRecorder := cClient.Recorder() + cRecorder.GetSubscription("sub0", sub0, nil) + cRecorder.UpdateSubscription(createSubscription("sub0", + "imc0", + "ksvc1", + "b0", + "b1"), + nil) + + out, err := executeSubscriptionCommand(cClient, dynamicClient, "update", "sub0", + "--sink", "ksvc1", + "--sink-reply", "broker:b0", + "--sink-dead-letter", "broker:b1") + assert.NilError(t, err, "subscription should be updated") + assert.Assert(t, util.ContainsAll(out, "updated", "sub0", "default")) + cRecorder.Validate() +} diff --git a/pkg/kn/flags/channel_types.go b/pkg/kn/flags/channel_types.go index 4a5c726a41..102476cbc1 100644 --- a/pkg/kn/flags/channel_types.go +++ b/pkg/kn/flags/channel_types.go @@ -19,6 +19,7 @@ import ( "strings" "github.com/spf13/pflag" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" "knative.dev/client/pkg/kn/config" @@ -28,6 +29,25 @@ type ChannelTypeFlags struct { ctype string } +type ChannelRef struct { + Cref string +} + +// ctypeMappings maps aliases used for channel types to their GroupVersionKind +var ctypeMappings = map[string]schema.GroupVersionKind{ + "imcv1beta1": { + Group: "messaging.knative.dev", + Version: "v1beta1", + Kind: "InMemoryChannel", + }, + "imc": { + Group: "messaging.knative.dev", + Version: "v1", + Kind: "InMemoryChannel", + }, +} + +// Add sets channel type flag definition to given flagset func (i *ChannelTypeFlags) Add(f *pflag.FlagSet) { f.StringVar(&i.ctype, "type", @@ -48,20 +68,7 @@ func (i *ChannelTypeFlags) Add(f *pflag.FlagSet) { } } -// ctypeMappings maps aliases used for channel types to their GroupVersionKind -var ctypeMappings = map[string]schema.GroupVersionKind{ - "imcv1beta1": { - Group: "messaging.knative.dev", - Version: "v1beta1", - Kind: "InMemoryChannel", - }, - "imc": { - Group: "messaging.knative.dev", - Version: "v1", - Kind: "InMemoryChannel", - }, -} - +// Parse parses the CLI value for channel type flag and populates GVK or returns error func (i *ChannelTypeFlags) Parse() (*schema.GroupVersionKind, error) { parts := strings.Split(i.ctype, ":") switch len(parts) { @@ -79,3 +86,38 @@ func (i *ChannelTypeFlags) Parse() (*schema.GroupVersionKind, error) { return nil, fmt.Errorf("Error: incorrect value '%s' for '--type', must be in the format 'Group:Version:Kind' or configure an alias in kn config", i.ctype) } } + +// Add sets channel reference flag definition to given flagset +func (i *ChannelRef) Add(f *pflag.FlagSet) { + f.StringVar(&i.Cref, + "channel", + "", + "Specify the channel to subscribe to. For the default channel, "+ + "just use the name (e.g. 'mychannel'). A mapped channel type like 'imc' "+ + "can be used as a prefix (e.g. 'imc:mychannel'). "+ + "Finally you can specify the full coordinates to the referenced channel "+ + "with Group:Version:Kind:Name (e.g. 'messaging.knative.dev:v1alpha1:KafkaChannel:mychannel').") +} + +// Parse parses the CLI value for channel ref flag and populates object reference or return error +func (i *ChannelRef) Parse() (*corev1.ObjectReference, error) { + parts := strings.Split(i.Cref, ":") + switch len(parts) { + // if no prefix is given, defer to "messaging.knative.dev/v1beta1:Channel" + case 1: + return &corev1.ObjectReference{Kind: "Channel", APIVersion: "messaging.knative.dev/v1beta1", Name: parts[0]}, nil + case 2: + if typ, ok := ctypeMappings[parts[0]]; ok { + return &corev1.ObjectReference{Kind: typ.Kind, APIVersion: typ.GroupVersion().String(), Name: parts[1]}, nil + } + return nil, fmt.Errorf("Error: unknown alias '%s' for '--channel', please configure the alias in kn config or specify in the format '--channel Group:Version:Kind:Name'", parts[0]) + case 4: + if parts[0] == "" || parts[1] == "" || parts[2] == "" || parts[3] == "" { + return nil, fmt.Errorf("Error: incorrect value '%s' for '--channel', must be in the format 'Group:Version:Kind:Name' or configure an alias in kn config and refer as: '--channel ALIAS:NAME'", i.Cref) + } + return &corev1.ObjectReference{Kind: parts[2], APIVersion: parts[0] + "/" + parts[1], Name: parts[3]}, nil + default: + return nil, fmt.Errorf("Error: incorrect value '%s' for '--channel', must be in the format 'Group:Version:Kind:Name' or configure an alias in kn config and refer as: '--channel ALIAS:NAME'", i.Cref) + } + return nil, nil +} diff --git a/pkg/kn/flags/channel_types_test.go b/pkg/kn/flags/channel_types_test.go index 443901949e..d38732d106 100644 --- a/pkg/kn/flags/channel_types_test.go +++ b/pkg/kn/flags/channel_types_test.go @@ -19,6 +19,7 @@ import ( "github.com/spf13/pflag" "gotest.tools/assert" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -29,6 +30,13 @@ type channelTypeFlagsTestCase struct { expectedErrText string } +type channelRefFlagsTestCase struct { + name string + arg string + expectedObjectRef *corev1.ObjectReference + expectedErrText string +} + func TestChannelTypesFlags(t *testing.T) { cases := []*channelTypeFlagsTestCase{ { @@ -82,3 +90,65 @@ func TestChannelTypesFlags(t *testing.T) { } } } + +func TestChannelRefFlags(t *testing.T) { + cases := []*channelRefFlagsTestCase{ + { + "inbuilt alias imcv1beta1 case", + "imcv1beta1:i1", + &corev1.ObjectReference{APIVersion: "messaging.knative.dev/v1beta1", Kind: "InMemoryChannel", Name: "i1"}, + "", + }, + { + "inbuilt alias 'imc' case", + "imc:i2", + &corev1.ObjectReference{APIVersion: "messaging.knative.dev/v1", Kind: "InMemoryChannel", Name: "i2"}, + "", + }, + { + "explicit GVK case", + "messaging.knative.dev:v1alpha1:KafkaChannel:k1", + &corev1.ObjectReference{APIVersion: "messaging.knative.dev/v1alpha1", Kind: "KafkaChannel", Name: "k1"}, + "", + }, + { + "default channel type prefix case", + "c1", + &corev1.ObjectReference{APIVersion: "messaging.knative.dev/v1beta1", Kind: "Channel", Name: "c1"}, + "", + }, + { + "error case unknown alias", + "natss:n1", + nil, + "Error: unknown alias 'natss' for '--channel', please configure the alias in kn config or specify in the format '--channel Group:Version:Kind:Name'", + }, + { + "error case incorrect gvk format, missing version", + "foo::bar", + nil, + "Error: incorrect value 'foo::bar' for '--channel', must be in the format 'Group:Version:Kind:Name' or configure an alias in kn config and refer as: '--channel ALIAS:NAME'", + }, + { + "error case incorrect gvk format, additional field", + "foo:bar::bat", + nil, + "Error: incorrect value 'foo:bar::bat' for '--channel', must be in the format 'Group:Version:Kind:Name' or configure an alias in kn config and refer as: '--channel ALIAS:NAME'", + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + f := &ChannelRef{} + flagset := &pflag.FlagSet{} + f.Add(flagset) + flagset.Set("channel", c.arg) + obj, err := f.Parse() + if c.expectedErrText != "" { + assert.Equal(t, err.Error(), c.expectedErrText) + } else { + assert.Equal(t, *obj, *c.expectedObjectRef) + } + }) + } +} diff --git a/pkg/kn/root/root.go b/pkg/kn/root/root.go index 59b0b32b53..96eeb7c082 100644 --- a/pkg/kn/root/root.go +++ b/pkg/kn/root/root.go @@ -35,6 +35,7 @@ import ( "knative.dev/client/pkg/kn/commands/route" "knative.dev/client/pkg/kn/commands/service" "knative.dev/client/pkg/kn/commands/source" + "knative.dev/client/pkg/kn/commands/subscription" "knative.dev/client/pkg/kn/commands/trigger" "knative.dev/client/pkg/kn/commands/version" "knative.dev/client/pkg/kn/config" @@ -95,6 +96,7 @@ func NewRootCommand(helpFuncs *template.FuncMap) (*cobra.Command, error) { broker.NewBrokerCommand(p), trigger.NewTriggerCommand(p), channel.NewChannelCommand(p), + subscription.NewSubscriptionCommand(p), }, }, { diff --git a/pkg/messaging/v1beta1/client.go b/pkg/messaging/v1beta1/client.go index 196a5bf624..9a6c4868f1 100644 --- a/pkg/messaging/v1beta1/client.go +++ b/pkg/messaging/v1beta1/client.go @@ -28,6 +28,9 @@ import ( type KnMessagingClient interface { // Get the Channels client ChannelsClient() KnChannelsClient + + // Get the Subscriptions client + SubscriptionsClient() KnSubscriptionsClient } // messagingClient holds Messaging client interface and namespace @@ -49,6 +52,11 @@ func (c *messagingClient) ChannelsClient() KnChannelsClient { return newKnChannelsClient(c.client.Channels(c.namespace), c.namespace) } +// SubscriptionsClient for working with Subscriptions +func (c *messagingClient) SubscriptionsClient() KnSubscriptionsClient { + return newKnSubscriptionsClient(c.client.Subscriptions(c.namespace), c.namespace) +} + // update GVK of object func updateMessagingGVK(obj runtime.Object) error { return util.UpdateGroupVersionKindWithScheme(obj, messagingv1beta1.SchemeGroupVersion, scheme.Scheme) diff --git a/pkg/messaging/v1beta1/subscriptions_client.go b/pkg/messaging/v1beta1/subscriptions_client.go new file mode 100644 index 0000000000..ddc6fbb0b6 --- /dev/null +++ b/pkg/messaging/v1beta1/subscriptions_client.go @@ -0,0 +1,193 @@ +/* +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 v1beta1 + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/eventing/pkg/apis/messaging/v1beta1" + clientv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1beta1" + duckv1 "knative.dev/pkg/apis/duck/v1" + + knerrors "knative.dev/client/pkg/errors" +) + +// KnSubscriptionsClient for interacting with Subscriptions +type KnSubscriptionsClient interface { + + // GetSubscription returns a Subscription by its name + GetSubscription(name string) (*v1beta1.Subscription, error) + + // CreteSubscription creates a Subscription with given spec + CreateSubscription(subscription *v1beta1.Subscription) error + + // UpdateSubscription updates a Subscription with given spec + UpdateSubscription(subscription *v1beta1.Subscription) error + + // DeleteSubscription deletes a Subscription by its name + DeleteSubscription(name string) error + + // ListSubscription lists all Subscriptions + ListSubscription() (*v1beta1.SubscriptionList, error) + + // Namespace returns the namespace for this subscription client + Namespace() string +} + +// subscriptionsClient struct holds the client interface and namespace +type subscriptionsClient struct { + client clientv1beta1.SubscriptionInterface + namespace string +} + +// newKnSubscriptionsClient returns kn subscriptions client +func newKnSubscriptionsClient(client clientv1beta1.SubscriptionInterface, namespace string) KnSubscriptionsClient { + return &subscriptionsClient{ + client: client, + namespace: namespace, + } +} + +// Get the namespace for which this client is created +func (c *subscriptionsClient) Namespace() string { + return c.namespace +} + +// GetSubscription gets Subscription by its name +func (c *subscriptionsClient) GetSubscription(name string) (*v1beta1.Subscription, error) { + subscription, err := c.client.Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return nil, knerrors.GetError(err) + } + err = updateMessagingGVK(subscription) + if err != nil { + return nil, err + } + return subscription, nil +} + +// CreateSubscription creates Subscription with given spec +func (c *subscriptionsClient) CreateSubscription(subscription *v1beta1.Subscription) error { + _, err := c.client.Create(context.TODO(), subscription, metav1.CreateOptions{}) + return knerrors.GetError(err) +} + +// UpdateSubscription creates Subscription with given spec +func (c *subscriptionsClient) UpdateSubscription(subscription *v1beta1.Subscription) error { + _, err := c.client.Update(context.TODO(), subscription, metav1.UpdateOptions{}) + return knerrors.GetError(err) +} + +// DeleteSubscription deletes Subscription by its name +func (c *subscriptionsClient) DeleteSubscription(name string) error { + return knerrors.GetError(c.client.Delete(context.TODO(), name, metav1.DeleteOptions{})) +} + +// ListSubscription lists subscriptions in configured namespace +func (c *subscriptionsClient) ListSubscription() (*v1beta1.SubscriptionList, error) { + subscriptionList, err := c.client.List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, knerrors.GetError(err) + } + + return updateSubscriptionListGVK(subscriptionList) +} + +func updateSubscriptionListGVK(subscriptionList *v1beta1.SubscriptionList) (*v1beta1.SubscriptionList, error) { + subscriptionListNew := subscriptionList.DeepCopy() + err := updateMessagingGVK(subscriptionListNew) + if err != nil { + return nil, err + } + + subscriptionListNew.Items = make([]v1beta1.Subscription, len(subscriptionList.Items)) + for idx, subscription := range subscriptionList.Items { + subscriptionClone := subscription.DeepCopy() + err := updateMessagingGVK(subscriptionClone) + if err != nil { + return nil, err + } + subscriptionListNew.Items[idx] = *subscriptionClone + } + return subscriptionListNew, nil +} + +// SubscriptionBuilder is for building the Subscription object +type SubscriptionBuilder struct { + subscription *v1beta1.Subscription +} + +// NewSubscriptionBuilder for building Subscription object +func NewSubscriptionBuilder(name string) *SubscriptionBuilder { + return &SubscriptionBuilder{subscription: &v1beta1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + }} +} + +// NewSubscriptionBuilderFromExisting for building Subscription object from existing Subscription object +func NewSubscriptionBuilderFromExisting(subs *v1beta1.Subscription) *SubscriptionBuilder { + return &SubscriptionBuilder{subscription: subs.DeepCopy()} +} + +// Channel sets the channel reference for this subscription +func (s *SubscriptionBuilder) Channel(channel *corev1.ObjectReference) *SubscriptionBuilder { + if channel == nil { + return s + } + + s.subscription.Spec.Channel = *channel + return s +} + +func (s *SubscriptionBuilder) Subscriber(subs *duckv1.Destination) *SubscriptionBuilder { + if subs == nil { + return s + } + + s.subscription.Spec.Subscriber = subs + return s +} + +func (s *SubscriptionBuilder) Reply(reply *duckv1.Destination) *SubscriptionBuilder { + if reply == nil { + return s + } + + s.subscription.Spec.Reply = reply + return s +} + +func (s *SubscriptionBuilder) DeadLetterSink(dls *duckv1.Destination) *SubscriptionBuilder { + if dls == nil { + return s + } + + ds := &eventingduckv1beta1.DeliverySpec{} + ds.DeadLetterSink = dls + s.subscription.Spec.Delivery = ds + return s +} + +// Build returns the Subscription object from the builder +func (s *SubscriptionBuilder) Build() *v1beta1.Subscription { + return s.subscription +} diff --git a/pkg/messaging/v1beta1/subscriptions_client_mock.go b/pkg/messaging/v1beta1/subscriptions_client_mock.go new file mode 100644 index 0000000000..7123d1daa1 --- /dev/null +++ b/pkg/messaging/v1beta1/subscriptions_client_mock.go @@ -0,0 +1,121 @@ +/* +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 v1beta1 + +import ( + "testing" + + "knative.dev/eventing/pkg/apis/messaging/v1beta1" + + "knative.dev/client/pkg/util/mock" +) + +type MockKnSubscriptionsClient struct { + t *testing.T + recorder *SubscriptionsRecorder + namespace string +} + +// NewMockKnSubscriptionsClient returns a new mock instance which you need to record for +func NewMockKnSubscriptionsClient(t *testing.T, ns ...string) *MockKnSubscriptionsClient { + namespace := "default" + if len(ns) > 0 { + namespace = ns[0] + } + return &MockKnSubscriptionsClient{ + t: t, + recorder: &SubscriptionsRecorder{mock.NewRecorder(t, namespace)}, + } +} + +// Ensure that the interface is implemented +var _ KnSubscriptionsClient = &MockKnSubscriptionsClient{} + +// recorder for service +type SubscriptionsRecorder struct { + r *mock.Recorder +} + +// Recorder returns the recorder for registering API calls +func (c *MockKnSubscriptionsClient) Recorder() *SubscriptionsRecorder { + return c.recorder +} + +// Namespace of this client +func (c *MockKnSubscriptionsClient) Namespace() string { + return c.recorder.r.Namespace() +} + +// CreateSubscription records a call for CreateSubscription with the expected error +func (sr *SubscriptionsRecorder) CreateSubscription(subscription interface{}, err error) { + sr.r.Add("CreateSubscription", []interface{}{subscription}, []interface{}{err}) +} + +// CreateSubscription performs a previously recorded action, failing if non has been registered +func (c *MockKnSubscriptionsClient) CreateSubscription(subscription *v1beta1.Subscription) error { + call := c.recorder.r.VerifyCall("CreateSubscription", subscription) + return mock.ErrorOrNil(call.Result[0]) +} + +// GetSubscription records a call for GetSubscription with the expected object or error. Either subscriptions or err should be nil +func (sr *SubscriptionsRecorder) GetSubscription(name interface{}, subscription *v1beta1.Subscription, err error) { + sr.r.Add("GetSubscription", []interface{}{name}, []interface{}{subscription, err}) +} + +// GetSubscription performs a previously recorded action, failing if non has been registered +func (c *MockKnSubscriptionsClient) GetSubscription(name string) (*v1beta1.Subscription, error) { + call := c.recorder.r.VerifyCall("GetSubscription", name) + return call.Result[0].(*v1beta1.Subscription), mock.ErrorOrNil(call.Result[1]) +} + +// DeleteSubscription records a call for DeleteSubscription with the expected error (nil if none) +func (sr *SubscriptionsRecorder) DeleteSubscription(name interface{}, err error) { + sr.r.Add("DeleteSubscription", []interface{}{name}, []interface{}{err}) +} + +// DeleteSubscription performs a previously recorded action, failing if non has been registered +func (c *MockKnSubscriptionsClient) DeleteSubscription(name string) error { + call := c.recorder.r.VerifyCall("DeleteSubscription", name) + return mock.ErrorOrNil(call.Result[0]) +} + +// ListSubscription records a call for ListSubscription with the expected error (nil if none) +func (sr *SubscriptionsRecorder) ListSubscription(subscriptionsList *v1beta1.SubscriptionList, err error) { + sr.r.Add("ListSubscription", []interface{}{}, []interface{}{subscriptionsList, err}) +} + +// ListSubscription performs a previously recorded action, failing if non has been registered +func (c *MockKnSubscriptionsClient) ListSubscription() (*v1beta1.SubscriptionList, error) { + call := c.recorder.r.VerifyCall("ListSubscription") + return call.Result[0].(*v1beta1.SubscriptionList), mock.ErrorOrNil(call.Result[1]) +} + +// UpdateSubscription records a call for CreateSubscription with the expected error +func (sr *SubscriptionsRecorder) UpdateSubscription(subscription interface{}, err error) { + sr.r.Add("UpdateSubscription", []interface{}{subscription}, []interface{}{err}) +} + +// UpdateSubscription performs a previously recorded action, failing if non has been registered +func (c *MockKnSubscriptionsClient) UpdateSubscription(subscription *v1beta1.Subscription) error { + call := c.recorder.r.VerifyCall("UpdateSubscription", subscription) + return mock.ErrorOrNil(call.Result[0]) +} + +// Validates validates whether every recorded action has been called +func (sr *SubscriptionsRecorder) Validate() { + sr.r.CheckThatAllRecordedMethodsHaveBeenCalled() +} diff --git a/test/e2e/subscription_test.go b/test/e2e/subscription_test.go new file mode 100644 index 0000000000..bc50865dfe --- /dev/null +++ b/test/e2e/subscription_test.go @@ -0,0 +1,67 @@ +// 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 im +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build e2e +// +build !serving + +package e2e + +import ( + "testing" + + "gotest.tools/assert" + + "knative.dev/client/lib/test" + "knative.dev/client/pkg/util" +) + +func TestSubscriptions(t *testing.T) { + t.Parallel() + it, err := test.NewKnTest() + assert.NilError(t, err) + defer func() { + assert.NilError(t, it.Teardown()) + }() + + r := test.NewKnRunResultCollector(t, it) + defer r.DumpIfFailed() + + t.Log("Create a subscription with all the flags") + test.ChannelCreate(r, "c0") + test.ServiceCreate(r, "svc0") + test.ServiceCreate(r, "svc1") + test.ServiceCreate(r, "svc2") + test.SubscriptionCreate(r, "sub0", "--channel", "c0", "--sink", "ksvc:svc0", "--sink-reply", "ksvc:svc1", "--sink-dead-letter", "ksvc:svc2") + + t.Log("Update a subscription") + test.ServiceCreate(r, "svc3") + test.SubscriptionUpdate(r, "sub0", "--sink", "ksvc:svc3") + + t.Log("List subscriptions") + slist := test.SubscriptionList(r) + assert.Check(t, util.ContainsAll(slist, "NAME", "CHANNEL", "SUBSCRIBER", "REPLY", "DEAD LETTER SINK", "READY", "REASON")) + assert.Check(t, util.ContainsAll(slist, "sub0", "c0", "ksvc:svc3", "ksvc:svc1", "ksvc:svc2", "True")) + + t.Log("Describe subscription") + sdesc := test.SubscriptionDescribe(r, "sub0") + assert.Check(t, util.ContainsAll(sdesc, "sub0", "Age", "Channel", "Channel", "c0", "Subscriber", "svc3", "Resource", "Service", "serving.knative.dev/v1", "Reply", "svc1", "DeadLetterSink", "svc2", "Conditions")) + + t.Log("Delete subscription") + test.SubscriptionDelete(r, "sub0") + test.ServiceDelete(r, "svc0") + test.ServiceDelete(r, "svc1") + test.ServiceDelete(r, "svc2") + test.ServiceDelete(r, "svc3") + test.ChannelDelete(r, "c0") +}