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
This commit is contained in:
Navid Shaikh 2020-10-01 22:14:29 +05:30 committed by GitHub
parent 2c67eb9d7c
commit ce519b520b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 2109 additions and 34 deletions

View File

@ -18,12 +18,16 @@
|=== |===
| | Description | PR | | Description | PR
| 🎁
| Add subscription CRUD
| https://github.com/knative/client/pull/1013[#1013]
| 🐛 | 🐛
| Fix service export example documentation | Fix service export example documentation
| https://github.com/knative/client/pull/1006[#1006] | https://github.com/knative/client/pull/1006[#1006]
| 🎁 | 🎁
| Add support for service initialScale via `--scale-init` flag | Add support for service initialscale via `--scale-init` flag
| https://github.com/knative/client/pull/990[#990] | https://github.com/knative/client/pull/990[#990]
| 🎁 | 🎁

View File

@ -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 route](kn_route.md) - List and describe service routes
* [kn service](kn_service.md) - Manage Knative services * [kn service](kn_service.md) - Manage Knative services
* [kn source](kn_source.md) - Manage event sources * [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 trigger](kn_trigger.md) - Manage event triggers
* [kn version](kn_version.md) - Show the version of this client * [kn version](kn_version.md) - Show the version of this client

View File

@ -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". --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". "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 --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 ### Options inherited from parent commands

View File

@ -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". --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". "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 --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 ### Options inherited from parent commands

View File

@ -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-). --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 -h, --help help for create
-n, --namespace string Specify the namespace to operate in. -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 --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
``` ```

View File

@ -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-). --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 -h, --help help for update
-n, --namespace string Specify the namespace to operate in. -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 --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
``` ```

View File

@ -26,7 +26,7 @@ kn source ping create NAME --sink SINK
-h, --help help for create -h, --help help for create
-n, --namespace string Specify the namespace to operate in. -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. --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 ### Options inherited from parent commands

View File

@ -26,7 +26,7 @@ kn source ping update NAME
-h, --help help for update -h, --help help for update
-n, --namespace string Specify the namespace to operate in. -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. --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 ### Options inherited from parent commands

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -29,7 +29,7 @@ kn trigger create NAME --sink SINK
-h, --help help for create -h, --help help for create
--inject-broker Create new broker with name default through common annotation --inject-broker Create new broker with name default through common annotation
-n, --namespace string Specify the namespace to operate in. -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 ### Options inherited from parent commands

View File

@ -33,7 +33,7 @@ kn trigger update NAME
-h, --help help for update -h, --help help for update
--inject-broker Create new broker with name default through common annotation --inject-broker Create new broker with name default through common annotation
-n, --namespace string Specify the namespace to operate in. -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 ### Options inherited from parent commands

45
lib/printing/describe.go Normal file
View File

@ -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())
}
}

61
lib/test/subscription.go Normal file
View File

@ -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"))
}

View File

@ -20,6 +20,7 @@ import (
dynamicfake "k8s.io/client-go/dynamic/fake" dynamicfake "k8s.io/client-go/dynamic/fake"
eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1"
messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1"
servingv1 "knative.dev/serving/pkg/apis/serving/v1" servingv1 "knative.dev/serving/pkg/apis/serving/v1"
"knative.dev/client/pkg/dynamic" "knative.dev/client/pkg/dynamic"
@ -30,6 +31,7 @@ func CreateFakeKnDynamicClient(testNamespace string, objects ...runtime.Object)
scheme := runtime.NewScheme() scheme := runtime.NewScheme()
scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "serving.knative.dev", Version: "v1", Kind: "Service"}, &servingv1.Service{}) 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: "Broker"}, &eventingv1beta1.Broker{})
scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "eventing.knative.dev", Version: "v1beta1", Kind: "Subscription"}, &messagingv1beta1.Subscription{})
client := dynamicfake.NewSimpleDynamicClient(scheme, objects...) client := dynamicfake.NewSimpleDynamicClient(scheme, objects...)
return dynamic.NewKnDynamicClient(client, testNamespace) return dynamic.NewKnDynamicClient(client, testNamespace)
} }

View File

@ -33,17 +33,21 @@ type SinkFlags struct {
sink string sink string
} }
func (i *SinkFlags) Add(cmd *cobra.Command) { // AddWithFlagName configures sink flag with given flag name and a short flag name
cmd.Flags().StringVarP(&i.sink, // pass empty short flag name if you dont want to set one
"sink", func (i *SinkFlags) AddWithFlagName(cmd *cobra.Command, fname, short string) {
"s", flag := "--" + fname
"", if short == "" {
"Addressable sink for events. "+ 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. " + "You can specify a broker, Knative service or URI. " +
"Examples: '--sink broker:nest' for a broker 'nest', "+ "Examples: '" + flag + " broker:nest' for a broker 'nest', " +
"'--sink https://event.receiver.uri' for an URI with an 'http://' or 'https://' schema, "+ "'" + flag + " 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'. "+ "'" + flag + " ksvc:receiver' or simply '" + flag + " receiver' for a Knative service 'receiver'. " +
"If prefix is not provided, it is considered as a Knative service.") "If a prefix is not provided, it is considered as a Knative service."
for _, p := range config.GlobalConfig.SinkMappings() { for _, p := range config.GlobalConfig.SinkMappings() {
//user configration might override the default configuration //user configration might override the default configuration
@ -55,6 +59,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. // sinkPrefixes maps prefixes used for sinks to their GroupVersionResources.
var sinkMappings = map[string]schema.GroupVersionResource{ var sinkMappings = map[string]schema.GroupVersionResource{
"broker": { "broker": {

View File

@ -17,6 +17,7 @@ package flags
import ( import (
"testing" "testing"
"github.com/spf13/cobra"
"gotest.tools/assert" "gotest.tools/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1"
@ -33,6 +34,40 @@ type resolveCase struct {
errContents string 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) { func TestResolve(t *testing.T) {
targetExampleCom, err := apis.ParseURL("http://target.example.com") targetExampleCom, err := apis.ParseURL("http://target.example.com")
mysvc := &servingv1.Service{ mysvc := &servingv1.Service{

View File

@ -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
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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"},
}
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -19,6 +19,7 @@ import (
"strings" "strings"
"github.com/spf13/pflag" "github.com/spf13/pflag"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"knative.dev/client/pkg/kn/config" "knative.dev/client/pkg/kn/config"
@ -28,6 +29,25 @@ type ChannelTypeFlags struct {
ctype string 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) { func (i *ChannelTypeFlags) Add(f *pflag.FlagSet) {
f.StringVar(&i.ctype, f.StringVar(&i.ctype,
"type", "type",
@ -48,20 +68,7 @@ func (i *ChannelTypeFlags) Add(f *pflag.FlagSet) {
} }
} }
// ctypeMappings maps aliases used for channel types to their GroupVersionKind // Parse parses the CLI value for channel type flag and populates GVK or returns error
var ctypeMappings = map[string]schema.GroupVersionKind{
"imcv1beta1": {
Group: "messaging.knative.dev",
Version: "v1beta1",
Kind: "InMemoryChannel",
},
"imc": {
Group: "messaging.knative.dev",
Version: "v1",
Kind: "InMemoryChannel",
},
}
func (i *ChannelTypeFlags) Parse() (*schema.GroupVersionKind, error) { func (i *ChannelTypeFlags) Parse() (*schema.GroupVersionKind, error) {
parts := strings.Split(i.ctype, ":") parts := strings.Split(i.ctype, ":")
switch len(parts) { 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) 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
}

View File

@ -19,6 +19,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
"gotest.tools/assert" "gotest.tools/assert"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
@ -29,6 +30,13 @@ type channelTypeFlagsTestCase struct {
expectedErrText string expectedErrText string
} }
type channelRefFlagsTestCase struct {
name string
arg string
expectedObjectRef *corev1.ObjectReference
expectedErrText string
}
func TestChannelTypesFlags(t *testing.T) { func TestChannelTypesFlags(t *testing.T) {
cases := []*channelTypeFlagsTestCase{ cases := []*channelTypeFlagsTestCase{
{ {
@ -84,3 +92,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)
}
})
}
}

View File

@ -35,6 +35,7 @@ import (
"knative.dev/client/pkg/kn/commands/route" "knative.dev/client/pkg/kn/commands/route"
"knative.dev/client/pkg/kn/commands/service" "knative.dev/client/pkg/kn/commands/service"
"knative.dev/client/pkg/kn/commands/source" "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/trigger"
"knative.dev/client/pkg/kn/commands/version" "knative.dev/client/pkg/kn/commands/version"
"knative.dev/client/pkg/kn/config" "knative.dev/client/pkg/kn/config"
@ -95,6 +96,7 @@ func NewRootCommand(helpFuncs *template.FuncMap) (*cobra.Command, error) {
broker.NewBrokerCommand(p), broker.NewBrokerCommand(p),
trigger.NewTriggerCommand(p), trigger.NewTriggerCommand(p),
channel.NewChannelCommand(p), channel.NewChannelCommand(p),
subscription.NewSubscriptionCommand(p),
}, },
}, },
{ {

View File

@ -28,6 +28,9 @@ import (
type KnMessagingClient interface { type KnMessagingClient interface {
// Get the Channels client // Get the Channels client
ChannelsClient() KnChannelsClient ChannelsClient() KnChannelsClient
// Get the Subscriptions client
SubscriptionsClient() KnSubscriptionsClient
} }
// messagingClient holds Messaging client interface and namespace // 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) 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 // update GVK of object
func updateMessagingGVK(obj runtime.Object) error { func updateMessagingGVK(obj runtime.Object) error {
return util.UpdateGroupVersionKindWithScheme(obj, messagingv1beta1.SchemeGroupVersion, scheme.Scheme) return util.UpdateGroupVersionKindWithScheme(obj, messagingv1beta1.SchemeGroupVersion, scheme.Scheme)

View File

@ -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
}

View File

@ -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()
}

View File

@ -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")
}