💝 Extract reusable part of sink flag (#1968)

* Extract reuseable part of sink flag

* Return nil on empty sink

* Extract kube params

* Compute with default mappings

* Allow to change the default log level

* Publicate Zap based logger, and allow custom loggers to be used.
This commit is contained in:
Chris Suszynski 2024-09-25 12:46:31 +02:00 committed by GitHub
parent fd0126d099
commit c9f128423b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 1082 additions and 468 deletions

View File

@ -26,7 +26,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:sourcesv1:key=value".
"LabelSelector" is a list of comma separated key value pairs. "LabelSelector" can be omitted, e.g. "Event:sourcesv1".
--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, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace, '--sink svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
```
### Options inherited from parent commands

View File

@ -26,7 +26,7 @@ kn source apiserver update NAME
--resource stringArray Specification for which events to listen, in the format Kind:APIVersion:LabelSelector, e.g. "Event:sourcesv1:key=value".
"LabelSelector" is a list of comma separated key value pairs. "LabelSelector" can be omitted, e.g. "Event:sourcesv1".
--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, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace, '--sink svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
```
### Options inherited from parent commands

View File

@ -20,7 +20,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, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace, '--sink svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
--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

@ -20,7 +20,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, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace, '--sink svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
--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

@ -41,7 +41,7 @@ kn source container create NAME --image IMAGE --sink SINK
--request strings The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource request, append "-" to the resource name, e.g. '--request cpu-'.
--security-context string Predefined security context for the service. Accepted values: 'none' for no security context and 'strict' for dropping all capabilities, running as non-root, and no privilege escalation. (default "none")
--service-account string Service account name to set. An empty argument ("") clears the service account. The referenced service account must exist in the service's namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace, '--sink svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
--toleration strings Add toleration to be set, works if the feature gate is enabled in Knative Serving feature flags configuration. Example: --tolerations Key="key1",Operator="Equal",Value="value1",Effect="NoSchedule"
--user int The user ID to run the container (e.g., 1001).
--volume stringArray Add a volume from a ConfigMap (prefix cm: or config-map:) a Secret (prefix secret: or sc:), an EmptyDir (prefix ed: or emptyDir:) or a PersistentVolumeClaim (prefix pvc: or persistentVolumeClaim). PersistentVolumeClaim only works if the feature gate is enabled in Knative Serving feature flags configuration. Example: --volume myvolume=cm:myconfigmap, --volume myvolume=secret:mysecret or --volume emptyDir:myvol:size=1Gi,type=Memory. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --volume myvolume-.

View File

@ -41,7 +41,7 @@ kn source container update NAME --image IMAGE
--request strings The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource request, append "-" to the resource name, e.g. '--request cpu-'.
--security-context string Predefined security context for the service. Accepted values: 'none' for no security context and 'strict' for dropping all capabilities, running as non-root, and no privilege escalation. (default "none")
--service-account string Service account name to set. An empty argument ("") clears the service account. The referenced service account must exist in the service's namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace, '--sink svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
--toleration strings Add toleration to be set, works if the feature gate is enabled in Knative Serving feature flags configuration. Example: --tolerations Key="key1",Operator="Equal",Value="value1",Effect="NoSchedule"
--user int The user ID to run the container (e.g., 1001).
--volume stringArray Add a volume from a ConfigMap (prefix cm: or config-map:) a Secret (prefix secret: or sc:), an EmptyDir (prefix ed: or emptyDir:) or a PersistentVolumeClaim (prefix pvc: or persistentVolumeClaim). PersistentVolumeClaim only works if the feature gate is enabled in Knative Serving feature flags configuration. Example: --volume myvolume=cm:myconfigmap, --volume myvolume=secret:mysecret or --volume emptyDir:myvol:size=1Gi,type=Memory. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --volume myvolume-.

View File

@ -23,7 +23,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, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace, '--sink svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
```
### Options inherited from parent commands

View File

@ -23,7 +23,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, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace, '--sink svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
```
### Options inherited from parent commands

View File

@ -23,9 +23,9 @@ kn subscription create NAME
--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:v1beta1: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, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
--sink-dead-letter string Addressable sink for events. You can specify a broker, channel, Knative service or URI. Examples: '--sink-dead-letter broker:nest' for a broker 'nest', '--sink-dead-letter channel:pipe' for a channel 'pipe', '--sink-dead-letter ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink-dead-letter https://event.receiver.uri' for an HTTP URI, '--sink-dead-letter ksvc:receiver' or simply '--sink-dead-letter receiver' for a Knative service 'receiver' in the current namespace. '--sink-dead-letter special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
--sink-reply string Addressable sink for events. You can specify a broker, channel, Knative service or URI. Examples: '--sink-reply broker:nest' for a broker 'nest', '--sink-reply channel:pipe' for a channel 'pipe', '--sink-reply ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink-reply https://event.receiver.uri' for an HTTP URI, '--sink-reply ksvc:receiver' or simply '--sink-reply receiver' for a Knative service 'receiver' in the current namespace. '--sink-reply special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace, '--sink svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
--sink-dead-letter string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink-dead-letter broker:nest' for a broker 'nest', '--sink-dead-letter channel:pipe' for a channel 'pipe', '--sink-dead-letter ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink-dead-letter https://event.receiver.uri' for an HTTP URI, '--sink-dead-letter ksvc:receiver' or simply '--sink-dead-letter receiver' for a Knative service 'receiver' in the current namespace, '--sink-dead-letter svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink-dead-letter special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
--sink-reply string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink-reply broker:nest' for a broker 'nest', '--sink-reply channel:pipe' for a channel 'pipe', '--sink-reply ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink-reply https://event.receiver.uri' for an HTTP URI, '--sink-reply ksvc:receiver' or simply '--sink-reply receiver' for a Knative service 'receiver' in the current namespace, '--sink-reply svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink-reply special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
```
### Options inherited from parent commands

View File

@ -22,9 +22,9 @@ kn subscription update NAME
```
-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, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
--sink-dead-letter string Addressable sink for events. You can specify a broker, channel, Knative service or URI. Examples: '--sink-dead-letter broker:nest' for a broker 'nest', '--sink-dead-letter channel:pipe' for a channel 'pipe', '--sink-dead-letter ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink-dead-letter https://event.receiver.uri' for an HTTP URI, '--sink-dead-letter ksvc:receiver' or simply '--sink-dead-letter receiver' for a Knative service 'receiver' in the current namespace. '--sink-dead-letter special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
--sink-reply string Addressable sink for events. You can specify a broker, channel, Knative service or URI. Examples: '--sink-reply broker:nest' for a broker 'nest', '--sink-reply channel:pipe' for a channel 'pipe', '--sink-reply ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink-reply https://event.receiver.uri' for an HTTP URI, '--sink-reply ksvc:receiver' or simply '--sink-reply receiver' for a Knative service 'receiver' in the current namespace. '--sink-reply special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace, '--sink svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
--sink-dead-letter string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink-dead-letter broker:nest' for a broker 'nest', '--sink-dead-letter channel:pipe' for a channel 'pipe', '--sink-dead-letter ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink-dead-letter https://event.receiver.uri' for an HTTP URI, '--sink-dead-letter ksvc:receiver' or simply '--sink-dead-letter receiver' for a Knative service 'receiver' in the current namespace, '--sink-dead-letter svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink-dead-letter special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
--sink-reply string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink-reply broker:nest' for a broker 'nest', '--sink-reply channel:pipe' for a channel 'pipe', '--sink-reply ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink-reply https://event.receiver.uri' for an HTTP URI, '--sink-reply ksvc:receiver' or simply '--sink-reply receiver' for a Knative service 'receiver' in the current namespace, '--sink-reply svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink-reply special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
```
### Options inherited from parent commands

View File

@ -24,7 +24,7 @@ kn trigger create NAME --sink SINK
--filter strings Key-value pair for exact CloudEvent attribute matching against incoming events, e.g type=dev.knative.foo
-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, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace, '--sink svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
```
### Options inherited from parent commands

View File

@ -27,7 +27,7 @@ kn trigger update NAME
--filter strings Key-value pair for exact CloudEvent attribute matching against incoming events, e.g type=dev.knative.foo
-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, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace. '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
-s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service, Kubernetes service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', '--sink https://event.receiver.uri' for an HTTP URI, '--sink ksvc:receiver' or simply '--sink receiver' for a Knative service 'receiver' in the current namespace, '--sink svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, '--sink special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. If a prefix is not provided, it is considered as a Knative service in the current namespace.
```
### Options inherited from parent commands

1
go.mod
View File

@ -21,6 +21,7 @@ replace knative.dev/client/pkg => ./pkg
require (
contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d // indirect
contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect
emperror.dev/errors v0.8.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
github.com/beorn7/perks v1.0.1 // indirect

4
go.sum
View File

@ -40,6 +40,8 @@ contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d/g
contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg=
contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0=
emperror.dev/errors v0.8.1/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@ -421,11 +423,13 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=

View File

@ -16,19 +16,18 @@ package flags
import (
"context"
"fmt"
"strings"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/runtime/schema"
"knative.dev/client/pkg/config"
"knative.dev/pkg/apis"
duckv1 "knative.dev/pkg/apis/duck/v1"
clientdynamic "knative.dev/client/pkg/dynamic"
"knative.dev/client/pkg/flags/sink"
"knative.dev/client/pkg/util/errors"
duckv1 "knative.dev/pkg/apis/duck/v1"
)
// SinkFlags holds information about given sink together with optional mappings
// to allow ease of referencing the common types.
type SinkFlags struct {
Sink string
SinkMappings map[string]schema.GroupVersionResource
@ -42,156 +41,69 @@ func NewSinkFlag(mapping map[string]schema.GroupVersionResource) *SinkFlags {
}
// AddWithFlagName configures Sink flag with given flag name and a short flag name
// pass empty short flag name if you don't want to set one
// pass empty short flag name if you don't want to set one.
func (i *SinkFlags) AddWithFlagName(cmd *cobra.Command, fname, short string) {
flag := "--" + fname
i.AddToFlagSet(cmd.Flags(), fname, short)
}
// AddToFlagSet configures Sink flag with given flag name and a short flag name
// pass empty short flag name if you don't want to set one
func (i *SinkFlags) AddToFlagSet(fs *pflag.FlagSet, fname, short string) {
if short == "" {
cmd.Flags().StringVar(&i.Sink, fname, "", "")
fs.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, channel, Knative service or URI. " +
"Examples: '" + flag + " broker:nest' for a broker 'nest', " +
"'" + flag + " channel:pipe' for a channel 'pipe', " +
"'" + flag + " ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', " +
"'" + flag + " https://event.receiver.uri' for an HTTP URI, " +
"'" + flag + " ksvc:receiver' or simply '" + flag + " receiver' for a Knative service 'receiver' in the current namespace. " +
"'" + flag + " special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. " +
"If a prefix is not provided, it is considered as a Knative service in the current namespace."
// Use default mapping if empty
if i.SinkMappings == nil {
i.SinkMappings = defaultSinkMappings
}
for _, p := range config.GlobalConfig.SinkMappings() {
//user configuration might override the default configuration
i.SinkMappings[p.Prefix] = schema.GroupVersionResource{
Resource: p.Resource,
Group: p.Group,
Version: p.Version,
}
fs.StringVarP(&i.Sink, fname, short, "", "")
}
fs.Lookup(fname).Usage = sink.Usage(fname)
}
// Add configures Sink flag with name 'Sink' amd short name 's'
func (i *SinkFlags) Add(cmd *cobra.Command) {
i.AddWithFlagName(cmd, "sink", "s")
i.AddWithFlagName(cmd, sink.DefaultFlagName, sink.DefaultFlagShorthand)
}
// SinkPrefixes maps prefixes used for sinks to their GroupVersionResources.
var defaultSinkMappings = map[string]schema.GroupVersionResource{
"broker": {
Resource: "brokers",
Group: "eventing.knative.dev",
Version: "v1",
},
// Shorthand alias for service
"ksvc": {
Resource: "services",
Group: "serving.knative.dev",
Version: "v1",
},
"channel": {
Resource: "channels",
Group: "messaging.knative.dev",
Version: "v1",
},
// WithDefaultMappings will return a copy of SinkFlags with provided mappings
// and the default ones.
func (i *SinkFlags) WithDefaultMappings() *SinkFlags {
return &SinkFlags{
Sink: i.Sink,
SinkMappings: sink.ComputeWithDefaultMappings(i.SinkMappings),
}
}
// Parse returns the sink reference, which may refer to URL or to Kubernetes
// resource. The namespace given should be the current namespace within the
// context.
func (i *SinkFlags) Parse(namespace string) (*sink.Reference, error) {
// Use default mapping if empty
sf := i.WithDefaultMappings()
return sink.Parse(sf.Sink, namespace, sf.SinkMappings)
}
// ResolveSink returns the Destination referred to by the flags in the acceptor.
// It validates that any object the user is referring to exists.
func (i *SinkFlags) ResolveSink(ctx context.Context, knclient clientdynamic.KnDynamicClient, namespace string) (*duckv1.Destination, error) {
client := knclient.RawClient()
if i.Sink == "" {
return nil, nil
}
// Use default mapping if empty
if i.SinkMappings == nil {
i.SinkMappings = defaultSinkMappings
}
prefix, name, ns := parseSink(i.Sink)
if prefix == "" {
// URI target
uri, err := apis.ParseURL(name)
if err != nil {
return nil, err
}
return &duckv1.Destination{URI: uri}, nil
}
gvr, ok := i.SinkMappings[prefix]
if !ok {
if prefix == "svc" || prefix == "service" {
return nil, fmt.Errorf("unsupported Sink prefix: '%s', please use prefix 'ksvc' for knative service", prefix)
}
idx := strings.LastIndex(prefix, "/")
var groupVersion string
var kind string
if idx != -1 && idx < len(prefix)-1 {
groupVersion, kind = prefix[:idx], prefix[idx+1:]
} else {
kind = prefix
}
parsedVersion, err := schema.ParseGroupVersion(groupVersion)
if err != nil {
return nil, err
}
// For the RAWclient the resource name must be in lower case plural form.
// This is the best effort to sanitize the inputs, but the safest way is to provide
// the appropriate form in user's input.
if !strings.HasSuffix(kind, "s") {
kind = kind + "s"
}
kind = strings.ToLower(kind)
gvr = parsedVersion.WithResource(kind)
}
if ns != "" {
namespace = ns
}
obj, err := client.Resource(gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
s, err := i.Parse(namespace)
if err != nil {
if errors.Is(err, sink.ErrSinkIsRequired) {
// returns nil, if sink isn't provided to keep the current contract
return nil, nil
}
return nil, err
}
destination := &duckv1.Destination{
Ref: &duckv1.KReference{
Kind: obj.GetKind(),
APIVersion: obj.GetAPIVersion(),
Name: obj.GetName(),
Namespace: namespace,
},
}
return destination, nil
}
// parseSink takes the string given by the user into the prefix, name and namespace of
// the object. If the user put a URI instead, the prefix is empty and the name
// is the whole URI.
func parseSink(sink string) (string, string, string) {
parts := strings.SplitN(sink, ":", 3)
switch {
case len(parts) == 1:
return "ksvc", parts[0], ""
case parts[0] == "http" || parts[0] == "https":
return "", sink, ""
case len(parts) == 3:
return parts[0], parts[1], parts[2]
default:
return parts[0], parts[1], ""
var dest *duckv1.Destination
dest, err = s.Resolve(ctx, knclient)
if err != nil {
// Returning original error that caused sink.ErrSinkIsInvalid as it is
// directly presented to the end-user.
return nil, errors.CauseOf(err, sink.ErrSinkIsInvalid)
}
return dest, nil
}
// SinkToString prepares a Sink for list output
func SinkToString(sink duckv1.Destination) string {
if sink.Ref != nil {
if sink.Ref.Kind == "Service" && strings.HasPrefix(sink.Ref.APIVersion, defaultSinkMappings["ksvc"].Group) {
return fmt.Sprintf("ksvc:%s", sink.Ref.Name)
} else {
return fmt.Sprintf("%s:%s", strings.ToLower(sink.Ref.Kind), sink.Ref.Name)
}
}
if sink.URI != nil {
return sink.URI.String()
}
return ""
// Deprecated: use (*sink.Reference).AsText instead.
func SinkToString(dest duckv1.Destination) string {
ref := sink.GuessFromDestination(dest)
return ref.String()
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package flags
package flags_test
import (
"context"
@ -20,7 +20,9 @@ import (
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"knative.dev/client/pkg/commands/flags"
eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1"
messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1"
"knative.dev/eventing/pkg/apis/sources/v1beta2"
@ -44,37 +46,33 @@ type sinkFlagAddTestCases struct {
}
func TestSinkFlagAdd(t *testing.T) {
cases := []*sinkFlagAddTestCases{
{
"",
"sink",
"s",
},
{
"subscriber",
"subscriber",
"",
},
}
cases := []sinkFlagAddTestCases{{
"",
"sink",
"s",
}, {
"subscriber",
"subscriber",
"",
}}
for _, tc := range cases {
c := &cobra.Command{Use: "sinktest"}
sinkFlags := 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)
}
t.Run(tc.flagName, func(t *testing.T) {
c := &cobra.Command{Use: "sinktest"}
sinkFlags := flags.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")
assert.NilError(t, err)
mysvc := &servingv1.Service{
TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "serving.knative.dev/v1"},
ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "default"},
@ -91,6 +89,10 @@ func TestResolve(t *testing.T) {
TypeMeta: metav1.TypeMeta{Kind: "PingSource", APIVersion: "sources.knative.dev/v1"},
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
}
k8sService := &corev1.Service{
TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
}
cases := []resolveCase{
{"ksvc:mysvc", &duckv1.Destination{
Ref: &duckv1.KReference{Kind: "Service",
@ -143,24 +145,42 @@ func TestResolve(t *testing.T) {
Name: "foo",
}}, ""},
{"http://target.example.com", &duckv1.Destination{
URI: targetExampleCom,
URI: url(t, "http://target.example.com"),
}, ""},
{"k8ssvc:foo", nil, "k8ssvcs \"foo\" not found"},
{"svc:foo", nil, "please use prefix 'ksvc' for knative service"},
{"service:foo", nil, "please use prefix 'ksvc' for knative service"},
{"svc:foo", &duckv1.Destination{Ref: &duckv1.KReference{
APIVersion: "v1",
Kind: "Service",
Namespace: "default",
Name: "foo",
}}, ""},
{"service:foo", &duckv1.Destination{Ref: &duckv1.KReference{
APIVersion: "v1",
Kind: "Service",
Namespace: "default",
Name: "foo",
}}, ""},
{"absent:foo", nil, "absents \"foo\" not found"},
{"", nil, ""},
}
dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default", mysvc, defaultBroker, pipeChannel, pingSource)
dynamicClient := dynamicfake.CreateFakeKnDynamicClient(
"default",
mysvc, defaultBroker, pipeChannel, pingSource, k8sService,
)
for _, c := range cases {
i := &SinkFlags{Sink: c.sink}
result, err := i.ResolveSink(context.Background(), dynamicClient, "default")
if c.destination != nil {
assert.DeepEqual(t, result, c.destination)
assert.NilError(t, err)
} else {
assert.ErrorContains(t, err, c.errContents)
}
t.Run(c.sink, func(t *testing.T) {
sf := &flags.SinkFlags{Sink: c.sink}
result, err := sf.ResolveSink(context.Background(), dynamicClient, "default")
if c.errContents == "" {
assert.DeepEqual(t, result, c.destination)
assert.NilError(t, err)
} else {
assert.Check(t, err != nil && err.Error() != "",
"error ins't empty")
assert.ErrorContains(t, err, c.errContents)
}
})
}
}
@ -197,47 +217,73 @@ func TestResolveWithNamespace(t *testing.T) {
}
dynamicClient := dynamicfake.CreateFakeKnDynamicClient("my-namespace", mysvc, defaultBroker, pipeChannel)
for _, c := range cases {
i := &SinkFlags{Sink: c.sink}
result, err := i.ResolveSink(context.Background(), dynamicClient, "default")
if c.destination != nil {
assert.DeepEqual(t, result, c.destination)
assert.Equal(t, c.destination.Ref.Namespace, "my-namespace")
assert.NilError(t, err)
} else {
assert.ErrorContains(t, err, c.errContents)
}
t.Run(c.sink, func(t *testing.T) {
i := &flags.SinkFlags{Sink: c.sink}
result, err := i.ResolveSink(context.Background(), dynamicClient, "default")
if c.destination != nil {
assert.DeepEqual(t, result, c.destination)
assert.Equal(t, c.destination.Ref.Namespace, "my-namespace")
assert.NilError(t, err)
} else {
assert.ErrorContains(t, err, c.errContents)
}
})
}
}
func TestSinkToString(t *testing.T) {
sink := duckv1.Destination{
Ref: &duckv1.KReference{Kind: "Service",
tcs := []resolveCase{{
sink: "ksvc:mysvc",
destination: &duckv1.Destination{Ref: &duckv1.KReference{
Kind: "Service",
APIVersion: "serving.knative.dev/v1",
Namespace: "my-namespace",
Name: "mysvc"}}
expected := "ksvc:mysvc"
assert.Equal(t, expected, SinkToString(sink))
sink = duckv1.Destination{
Ref: &duckv1.KReference{Kind: "Broker",
Name: "mysvc",
}},
}, {
sink: "broker:default",
destination: &duckv1.Destination{Ref: &duckv1.KReference{
Kind: "Broker",
APIVersion: "eventing.knative.dev/v1",
Namespace: "my-namespace",
Name: "default"}}
expected = "broker:default"
assert.Equal(t, expected, SinkToString(sink))
sink = duckv1.Destination{
Ref: &duckv1.KReference{Kind: "Service",
Name: "default",
}},
}, {
sink: "svc:mysvc",
destination: &duckv1.Destination{Ref: &duckv1.KReference{
Kind: "Service",
APIVersion: "v1",
Namespace: "my-namespace",
Name: "mysvc"}}
expected = "service:mysvc"
assert.Equal(t, expected, SinkToString(sink))
uri := "http://target.example.com"
targetExampleCom, err := apis.ParseURL(uri)
assert.NilError(t, err)
sink = duckv1.Destination{
URI: targetExampleCom,
Name: "mysvc",
}},
}, {
sink: "things.acme.dev/v1alpha1:abc",
destination: &duckv1.Destination{Ref: &duckv1.KReference{
Kind: "Thing",
APIVersion: "acme.dev/v1alpha1",
Namespace: "my-namespace",
Name: "abc",
}},
}, {
sink: "http://target.example.com",
destination: &duckv1.Destination{
URI: url(t, "http://target.example.com"),
},
}, {
sink: "",
destination: &duckv1.Destination{},
}}
for _, tc := range tcs {
t.Run(tc.sink, func(t *testing.T) {
got := flags.SinkToString(*tc.destination)
assert.Equal(t, got, tc.sink)
})
}
assert.Equal(t, uri, SinkToString(sink))
assert.Equal(t, "", SinkToString(duckv1.Destination{}))
}
func url(t testing.TB, uri string) *apis.URL {
t.Helper()
u, err := apis.ParseURL(uri)
assert.NilError(t, err)
return u
}

View File

@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
"k8s.io/client-go/tools/clientcmd"
"knative.dev/client/pkg/k8s"
"knative.dev/client/pkg/util/test"
)
@ -41,7 +42,7 @@ func TestGetNamespaceSample(t *testing.T) {
testCmd := testCommandGenerator(true)
expectedNamespace := "test1"
testCmd.SetArgs([]string{"--namespace", expectedNamespace})
testCmd.Execute()
assert.NilError(t, testCmd.Execute())
kp := &KnParams{fixedCurrentNamespace: FakeNamespace}
actualNamespace, err := kp.GetNamespace(testCmd)
if err != nil {
@ -63,7 +64,7 @@ func TestGetNamespaceSample(t *testing.T) {
func TestGetNamespaceDefault(t *testing.T) {
testCmd := testCommandGenerator(true)
expectedNamespace := "current"
testCmd.Execute()
assert.NilError(t, testCmd.Execute())
kp := &KnParams{fixedCurrentNamespace: FakeNamespace}
actualNamespace, err := kp.GetNamespace(testCmd)
if err != nil {
@ -84,7 +85,7 @@ func TestGetNamespaceAllNamespacesSet(t *testing.T) {
// Test both variants of the "all namespaces" flag
for _, arg := range []string{"--all-namespaces", "-A"} {
testCmd.SetArgs([]string{"--namespace", sampleNamespace, arg})
testCmd.Execute()
assert.NilError(t, testCmd.Execute())
kp := &KnParams{fixedCurrentNamespace: FakeNamespace}
actualNamespace, err := kp.GetNamespace(testCmd)
if err != nil {
@ -106,7 +107,7 @@ func TestGetNamespaceDefaultAllNamespacesUnset(t *testing.T) {
// Test both variants of the "all namespaces" flag
for _, arg := range []string{"--all-namespaces", "-A"} {
testCmd.SetArgs([]string{arg})
testCmd.Execute()
assert.NilError(t, testCmd.Execute())
kp := &KnParams{fixedCurrentNamespace: FakeNamespace}
actualNamespace, err := kp.GetNamespace(testCmd)
if err != nil {
@ -124,7 +125,7 @@ func TestGetNamespaceAllNamespacesNotDefined(t *testing.T) {
testCmd := testCommandGenerator(false)
expectedNamespace := "test1"
testCmd.SetArgs([]string{"--namespace", expectedNamespace})
testCmd.Execute()
assert.NilError(t, testCmd.Execute())
kp := &KnParams{fixedCurrentNamespace: FakeNamespace}
actualNamespace, err := kp.GetNamespace(testCmd)
if err != nil {
@ -143,9 +144,9 @@ func TestGetNamespaceFallback(t *testing.T) {
err := os.WriteFile(tempFile, []byte(BASIC_KUBECONFIG), test.FileModeReadWrite)
assert.NilError(t, err)
kp := &KnParams{KubeCfgPath: tempFile}
kp := &KnParams{Params: k8s.Params{KubeCfgPath: tempFile}}
testCmd := testCommandGenerator(true)
testCmd.Execute()
assert.NilError(t, testCmd.Execute())
actual, err := kp.GetNamespace(testCmd)
assert.NilError(t, err)
if isInCluster() {
@ -161,9 +162,9 @@ func TestGetNamespaceFallback(t *testing.T) {
err := os.WriteFile(tempFile, []byte(""), test.FileModeReadWrite)
assert.NilError(t, err)
kp := &KnParams{KubeCfgPath: tempFile}
kp := &KnParams{Params: k8s.Params{KubeCfgPath: tempFile}}
testCmd := testCommandGenerator(true)
testCmd.Execute()
assert.NilError(t, testCmd.Execute())
actual, err := kp.GetNamespace(testCmd)
assert.NilError(t, err)
if isInCluster() {
@ -175,11 +176,11 @@ func TestGetNamespaceFallback(t *testing.T) {
})
t.Run("MissingConfig", func(t *testing.T) {
kp := &KnParams{KubeCfgPath: filepath.Join(tempDir, "missing")}
kp := &KnParams{Params: k8s.Params{KubeCfgPath: filepath.Join(tempDir, "missing")}}
testCmd := testCommandGenerator(true)
testCmd.Execute()
assert.NilError(t, testCmd.Execute())
actual, err := kp.GetNamespace(testCmd)
assert.ErrorContains(t, err, "can not be found")
assert.ErrorIs(t, err, k8s.ErrCantFindConfigFile)
assert.Equal(t, actual, "")
})
}
@ -193,7 +194,7 @@ func TestCurrentNamespace(t *testing.T) {
err := os.WriteFile(tempFile, []byte(""), test.FileModeReadWrite)
assert.NilError(t, err)
kp := &KnParams{KubeCfgPath: tempFile}
kp := &KnParams{Params: k8s.Params{KubeCfgPath: tempFile}}
actual, err := kp.CurrentNamespace()
if isInCluster() {
// In-cluster config overrides the mocked one in OpenShift CI
@ -209,10 +210,10 @@ func TestCurrentNamespace(t *testing.T) {
t.Run("MissingConfig", func(t *testing.T) {
// Missing kubeconfig
kp := &KnParams{KubeCfgPath: filepath.Join(tempDir, "missing")}
kp := &KnParams{Params: k8s.Params{KubeCfgPath: filepath.Join(tempDir, "missing")}}
actual, err := kp.CurrentNamespace()
assert.Assert(t, err != nil)
assert.ErrorContains(t, err, "can not be found")
assert.ErrorIs(t, err, k8s.ErrCantFindConfigFile)
assert.Assert(t, actual == "")
})
@ -229,7 +230,7 @@ func TestCurrentNamespace(t *testing.T) {
tempFile := filepath.Join(tempDir, "mock")
err := os.WriteFile(tempFile, []byte(BASIC_KUBECONFIG), test.FileModeReadWrite)
assert.NilError(t, err)
kp := &KnParams{KubeCfgPath: tempFile}
kp := &KnParams{Params: k8s.Params{KubeCfgPath: tempFile}}
actual, err := kp.CurrentNamespace()
assert.NilError(t, err)
if isInCluster() {

View File

@ -69,7 +69,7 @@ func TestSubscriptionList(t *testing.T) {
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[1], "s0", "InMemoryChannel:imc0", "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"))
})

View File

@ -15,12 +15,11 @@
package commands
import (
"fmt"
"io"
"os"
"path/filepath"
"k8s.io/client-go/kubernetes"
"knative.dev/client/pkg/k8s"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
@ -48,14 +47,8 @@ import (
// KnParams for creating commands. Useful for inserting mocks for testing.
type KnParams struct {
k8s.Params
Output io.Writer
KubeCfgPath string
KubeContext string
KubeCluster string
KubeAsUser string
KubeAsUID string
KubeAsGroup []string
ClientConfig clientcmd.ClientConfig
NewKubeClient func() (kubernetes.Interface, error)
NewServingClient func(namespace string) (clientservingv1.KnServingClient, error)
NewServingV1beta1Client func(namespace string) (clientservingv1beta1.KnServingClient, error)
@ -72,8 +65,12 @@ type KnParams struct {
// Set this if you want to nail down the namespace
fixedCurrentNamespace string
// Memorizes the loaded config
clientcmd.ClientConfig
}
// Initialize will initialize the default factories for the clients.
func (params *KnParams) Initialize() {
if params.NewKubeClient == nil {
params.NewKubeClient = params.newKubeClient
@ -246,44 +243,3 @@ func (params *KnParams) RestConfig() (*rest.Config, error) {
return config, nil
}
// GetClientConfig gets ClientConfig from KubeCfgPath
func (params *KnParams) GetClientConfig() (clientcmd.ClientConfig, error) {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
configOverrides := &clientcmd.ConfigOverrides{}
if params.KubeContext != "" {
configOverrides.CurrentContext = params.KubeContext
}
if params.KubeCluster != "" {
configOverrides.Context.Cluster = params.KubeCluster
}
if params.KubeAsUser != "" {
configOverrides.AuthInfo.Impersonate = params.KubeAsUser
}
if params.KubeAsUID != "" {
configOverrides.AuthInfo.ImpersonateUID = params.KubeAsUID
}
if len(params.KubeAsGroup) > 0 {
configOverrides.AuthInfo.ImpersonateGroups = params.KubeAsGroup
}
if len(params.KubeCfgPath) == 0 {
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides), nil
}
_, err := os.Stat(params.KubeCfgPath)
if err == nil {
loadingRules.ExplicitPath = params.KubeCfgPath
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides), nil
}
if !os.IsNotExist(err) {
return nil, err
}
paths := filepath.SplitList(params.KubeCfgPath)
if len(paths) > 1 {
return nil, fmt.Errorf("can not find config file. '%s' looks like a path. "+
"Please use the env var KUBECONFIG if you want to check for multiple configuration files", params.KubeCfgPath)
}
return nil, fmt.Errorf("config file '%s' can not be found", params.KubeCfgPath)
}

View File

@ -15,18 +15,14 @@
package commands
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"knative.dev/client/pkg/util/test"
"gotest.tools/v3/assert"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"knative.dev/client/pkg/util"
"knative.dev/client/pkg/k8s"
)
type configTestCase struct {
@ -106,149 +102,7 @@ func TestPrepareConfig(t *testing.T) {
kpEmptyConfig = &KnParams{}
kpEmptyConfig.KubeCfgPath = filepath.Join("non", "existing", "file")
_, err = kpEmptyConfig.RestConfig()
assert.ErrorContains(t, err, "can not be found")
}
type typeTestCase struct {
kubeCfgPath string
kubeContext string
kubeAsUser string
kubeAsUID string
kubeAsGroup []string
kubeCluster string
explicitPath string
expectedError string
}
func TestGetClientConfig(t *testing.T) {
multiConfigs := fmt.Sprintf("%s%s%s", "/testing/assets/kube-config-01.yml", string(os.PathListSeparator), "/testing/assets/kube-config-02.yml")
tempDir := t.TempDir()
tempFile := filepath.Join(tempDir, "mock")
err := os.WriteFile(tempFile, []byte(BASIC_KUBECONFIG), test.FileModeReadWrite)
assert.NilError(t, err)
for _, tc := range []typeTestCase{
{
"",
"",
"",
"",
[]string{},
"",
clientcmd.NewDefaultClientConfigLoadingRules().ExplicitPath,
"",
},
{
tempFile,
"",
"",
"",
[]string{},
"",
tempFile,
"",
},
{
"/testing/assets/kube-config-01.yml",
"foo",
"",
"",
[]string{},
"bar",
"",
fmt.Sprintf("config file '%s' can not be found", "/testing/assets/kube-config-01.yml"),
},
{
multiConfigs,
"",
"",
"",
[]string{},
"",
"",
fmt.Sprintf("can not find config file. '%s' looks like a path. Please use the env var KUBECONFIG if you want to check for multiple configuration files", multiConfigs),
},
{
tempFile,
"",
"admin",
"",
[]string{},
"",
tempFile,
"",
},
{
tempFile,
"",
"admin",
"",
[]string{"system:authenticated", "system:masters"},
"",
tempFile,
"",
},
{
tempFile,
"",
"admin",
"abc123",
[]string{},
"",
tempFile,
"",
},
} {
p := &KnParams{
KubeCfgPath: tc.kubeCfgPath,
KubeContext: tc.kubeContext,
KubeAsUser: tc.kubeAsUser,
KubeAsUID: tc.kubeAsUID,
KubeAsGroup: tc.kubeAsGroup,
KubeCluster: tc.kubeCluster,
}
clientConfig, err := p.GetClientConfig()
if tc.expectedError != "" {
assert.Assert(t, util.ContainsAll(err.Error(), tc.expectedError))
} else {
assert.Assert(t, err == nil, err)
}
if clientConfig != nil {
configAccess := clientConfig.ConfigAccess()
assert.Assert(t, configAccess.GetExplicitFile() == tc.explicitPath)
if tc.kubeContext != "" {
config, err := clientConfig.RawConfig()
assert.NilError(t, err)
assert.Assert(t, config.CurrentContext == tc.kubeContext)
assert.Assert(t, config.Contexts[tc.kubeContext].Cluster == tc.kubeCluster)
}
if tc.kubeAsUser != "" {
config, err := clientConfig.ClientConfig()
assert.NilError(t, err)
assert.Assert(t, config.Impersonate.UserName == tc.kubeAsUser)
}
if tc.kubeAsUID != "" {
config, err := clientConfig.ClientConfig()
assert.NilError(t, err)
assert.Assert(t, config.Impersonate.UID == tc.kubeAsUID)
}
if len(tc.kubeAsGroup) > 0 {
config, err := clientConfig.ClientConfig()
assert.NilError(t, err)
assert.Assert(t, len(config.Impersonate.Groups) == len(tc.kubeAsGroup))
for i := range tc.kubeAsGroup {
assert.Assert(t, config.Impersonate.Groups[i] == tc.kubeAsGroup[i])
}
}
}
}
assert.ErrorIs(t, err, k8s.ErrCantFindConfigFile)
}
func TestNewSourcesClient(t *testing.T) {

View File

@ -16,7 +16,9 @@ package fake
import (
"context"
"testing"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -32,13 +34,17 @@ import (
// CreateFakeKnDynamicClient gives you a dynamic client for testing containing the given objects.
func CreateFakeKnDynamicClient(testNamespace string, objects ...runtime.Object) dynamic.KnDynamicClient {
if !testing.Testing() {
panic("For test usage only!")
}
scheme := runtime.NewScheme()
servingv1.AddToScheme(scheme)
eventingv1.AddToScheme(scheme)
messagingv1.AddToScheme(scheme)
sourcesv1.AddToScheme(scheme)
sourcesv1beta2.AddToScheme(scheme)
apiextensionsv1.AddToScheme(scheme)
_ = corev1.AddToScheme(scheme)
_ = servingv1.AddToScheme(scheme)
_ = eventingv1.AddToScheme(scheme)
_ = messagingv1.AddToScheme(scheme)
_ = sourcesv1.AddToScheme(scheme)
_ = sourcesv1beta2.AddToScheme(scheme)
_ = apiextensionsv1.AddToScheme(scheme)
_, dynamicClient := dynamicclientfake.With(context.TODO(), scheme, objects...)
return dynamic.NewKnDynamicClient(dynamicClient, testNamespace)
}

View File

@ -0,0 +1,60 @@
/*
Copyright 2024 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 sink
import (
"strings"
)
var (
// DefaultFlagName is a default command-line flag name.
DefaultFlagName = "sink"
// DefaultFlagShorthand is a default command-line flag shorthand.
DefaultFlagShorthand = "s"
)
// Usage returns a usage text which can be used to define sink-like flag.
func Usage(fname string) string {
flag := "--" + fname
return "Addressable sink for events. " +
"You can specify a broker, channel, Knative service, Kubernetes service or URI. " +
"Examples: '" + flag + " broker:nest' for a broker 'nest', " +
"'" + flag + " channel:pipe' for a channel 'pipe', " +
"'" + flag + " ksvc:mysvc:mynamespace' for a Knative service 'mysvc' in another namespace 'mynamespace', " +
"'" + flag + " https://event.receiver.uri' for an HTTP URI, " +
"'" + flag + " ksvc:receiver' or simply '" + flag + " receiver' for a Knative service 'receiver' in the current namespace, " +
"'" + flag + " svc:receiver:mynamespace' for a Kubernetes service 'receiver' in the 'mynamespace' namespace, " +
"'" + flag + " special.eventing.dev/v1alpha1/channels:pipe' for GroupVersionResource of v1alpha1 'pipe'. " +
"If a prefix is not provided, it is considered as a Knative service in the current namespace."
}
// parseSink takes the string given by the user into the prefix, name and namespace of
// the object. If the user put a URI instead, the prefix is empty and the name
// is the whole URI.
func parseSink(sink string) (string, string, string) {
parts := strings.SplitN(sink, ":", 3)
switch {
case len(parts) == 1:
return knativeServiceShorthand, parts[0], ""
case parts[0] == "http" || parts[0] == "https":
return "", sink, ""
case len(parts) == 3:
return parts[0], parts[1], parts[2]
default:
return parts[0], parts[1], ""
}
}

299
pkg/flags/sink/sink.go Normal file
View File

@ -0,0 +1,299 @@
/*
Copyright 2024 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 sink
import (
"context"
"errors"
"fmt"
"strings"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"knative.dev/client/pkg/config"
clientdynamic "knative.dev/client/pkg/dynamic"
"knative.dev/pkg/apis"
duckv1 "knative.dev/pkg/apis/duck/v1"
)
// ErrSinkIsRequired is returned when no sink is given.
var ErrSinkIsRequired = errors.New("sink is required")
// ErrSinkIsInvalid is returned when the sink has invalid format.
var ErrSinkIsInvalid = errors.New("sink has invalid format")
// Type is a type of Reference.
type Type int
const (
// TypeURL is a URL version of the sink.
TypeURL Type = iota
// TypeReference is a Kuberentes version of the sink.
TypeReference
)
// Reference represents either a URL or Kubernetes resource.
type Reference struct {
*KubeReference
*apis.URL
}
// KubeReference represents a Kubernetes resource as given by command-line args.
type KubeReference struct {
GVR schema.GroupVersionResource
Name string
Namespace string
}
// DefaultMappings are used to easily map prefixes for sinks to their
// GroupVersionResources.
var DefaultMappings = withAliasses(map[string]schema.GroupVersionResource{
"kservice": {
Resource: "services",
Group: "serving.knative.dev",
Version: "v1",
},
"broker": {
Resource: "brokers",
Group: "eventing.knative.dev",
Version: "v1",
},
"channel": {
Resource: "channels",
Group: "messaging.knative.dev",
Version: "v1",
},
"service": { // K8s' service
Resource: "services",
Group: "",
Version: "v1",
},
}, defaultMappingAliasses)
var defaultMappingAliasses = map[string]string{
knativeServiceShorthand: "kservice",
"svc": "service",
}
const knativeServiceShorthand = "ksvc"
// Type returns the type of the reference.
func (r *Reference) Type() Type {
if r.KubeReference != nil {
return TypeReference
}
if r.URL != nil {
return TypeURL
}
return Type(-1) // unknown type, unexpected
}
// Resolve returns the Destination referred to by the sink. It validates that
// any object the user is referring to exists.
func (r *Reference) Resolve(ctx context.Context, knclient clientdynamic.KnDynamicClient) (*duckv1.Destination, error) {
if r.Type() == TypeURL {
return &duckv1.Destination{URI: r.URL}, nil
}
if r.Type() != TypeReference {
return nil, fmt.Errorf("%w: unexpected type %q",
ErrSinkIsInvalid, r.Type())
}
client := knclient.RawClient()
obj, err := client.Resource(r.GVR).
Namespace(r.Namespace).
Get(ctx, r.Name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrSinkIsInvalid, err)
}
destination := &duckv1.Destination{
Ref: &duckv1.KReference{
Kind: obj.GetKind(),
APIVersion: obj.GetAPIVersion(),
Name: obj.GetName(),
Namespace: r.Namespace,
},
}
return destination, nil
}
// String creates a text representation of the reference
// Deprecated: use AsText instead
func (r *Reference) String() string {
if r == nil {
return ""
}
// unexpected random-like value
ns := "vaizaeso3sheem5ebie5eeh9Aew5eekei3thie4ezooy9geef6iesh9auPhai7na"
if r.KubeReference != nil {
ns = r.Namespace
}
return r.AsText(ns)
}
// AsText creates a text representation of the resource, and should
// be used by giving a current namespace.
func (r *Reference) AsText(currentNamespace string) string {
if r.Type() == TypeURL {
return r.URL.String()
}
if r.Type() == TypeReference {
repr := r.GvrAsText() + ":" + r.Name
if currentNamespace != r.Namespace {
repr = fmt.Sprintf("%s:%s", repr, r.Namespace)
}
return repr
}
return fmt.Errorf("%w: unexpected type %q",
ErrSinkIsInvalid, r.Type()).Error()
}
// GvrAsText returns the
func (r *Reference) GvrAsText() string {
if r == nil || r.KubeReference == nil {
return fmt.Errorf("%w: unexpected type %#v",
ErrSinkIsInvalid, r).Error()
}
for alias, as := range defaultMappingAliasses {
if gvr, ok := DefaultMappings[as]; ok && gvr == r.GVR {
return alias
}
}
for alias, gvr := range DefaultMappings {
if r.GVR == gvr {
return alias
}
}
return fmt.Sprintf("%s.%s/%s",
r.GVR.Resource, r.GVR.Group, r.GVR.Version)
}
// Parse returns the sink reference of given sink representation, which may
// refer to URL or to the Kubernetes resource. The namespace given should be
// the current namespace within the context.
func Parse(sinkRepr, namespace string, mappings map[string]schema.GroupVersionResource) (*Reference, error) {
if sinkRepr == "" {
return nil, ErrSinkIsRequired
}
prefix, name, ns := parseSink(sinkRepr)
if prefix == "" {
// URI target
uri, err := apis.ParseURL(name)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrSinkIsInvalid, err)
}
return &Reference{URL: uri}, nil
}
gvr, ok := mappings[prefix]
if !ok {
idx := strings.LastIndex(prefix, "/")
var groupVersion string
var kind string
if idx != -1 && idx < len(prefix)-1 {
groupVersion, kind = prefix[:idx], prefix[idx+1:]
} else {
kind = prefix
}
parsedVersion, err := schema.ParseGroupVersion(groupVersion)
if err != nil {
return nil, err
}
// For the RAWclient the resource name must be in lower case plural form.
// This is the best effort to sanitize the inputs, but the safest way is to provide
// the appropriate form in user's input.
if !strings.HasSuffix(kind, "s") {
kind = kind + "s"
}
kind = strings.ToLower(kind)
gvr = parsedVersion.WithResource(kind)
}
if ns != "" {
namespace = ns
}
return &Reference{KubeReference: &KubeReference{
GVR: gvr,
Name: name,
Namespace: namespace,
}}, nil
}
// ComputeWithDefaultMappings will compute mapping by including default mappings
// and mappings provided by the end-user.
func ComputeWithDefaultMappings(mappings map[string]schema.GroupVersionResource) map[string]schema.GroupVersionResource {
sm := make(map[string]schema.GroupVersionResource,
len(mappings)+len(DefaultMappings))
for k, v := range DefaultMappings {
sm[k] = v
}
for k, v := range mappings {
sm[k] = v
}
for _, p := range config.GlobalConfig.SinkMappings() {
// user configuration might override the default configuration
sm[p.Prefix] = schema.GroupVersionResource{
Resource: p.Resource,
Group: p.Group,
Version: p.Version,
}
}
return sm
}
// GuessFromDestination converts the duckv1.Destination to the Reference by guessing
// the type by convention.
// Will return nil, if given empty destination.
func GuessFromDestination(dest duckv1.Destination) *Reference {
if dest.URI != nil {
return &Reference{URL: dest.URI}
}
if dest.Ref == nil {
return nil
}
ref := &corev1.ObjectReference{
Kind: dest.Ref.Kind,
Namespace: dest.Ref.Namespace,
Name: dest.Ref.Name,
APIVersion: dest.Ref.APIVersion,
}
gvk := ref.GroupVersionKind()
gvr, _ := meta.UnsafeGuessKindToResource(gvk)
return &Reference{KubeReference: &KubeReference{
GVR: gvr,
Name: ref.Name,
Namespace: ref.Namespace,
}}
}
func withAliasses(
mappings map[string]schema.GroupVersionResource,
aliases map[string]string,
) map[string]schema.GroupVersionResource {
result := make(map[string]schema.GroupVersionResource, len(aliases)+len(mappings))
for k, v := range mappings {
result[k] = v
}
for as, alias := range aliases {
if val, ok := result[alias]; ok {
result[as] = val.GroupResource().WithVersion(val.Version)
}
}
return result
}

99
pkg/k8s/params.go Normal file
View File

@ -0,0 +1,99 @@
/*
Copyright 2024 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 k8s
import (
"fmt"
"os"
"path/filepath"
"emperror.dev/errors"
"github.com/spf13/pflag"
"k8s.io/client-go/tools/clientcmd"
)
// ErrCantFindConfigFile is returned when given config file can't be found.
var ErrCantFindConfigFile = errors.New("can not find config file")
// Params contain Kubernetes specific params, that CLI should comply to.
type Params struct {
KubeCfgPath string
KubeContext string
KubeCluster string
KubeAsUser string
KubeAsUID string
KubeAsGroup []string
}
// SetFlags is used set flags to the given flagset.
func (kp *Params) SetFlags(flags *pflag.FlagSet) {
flags.StringVar(&kp.KubeCfgPath, "kubeconfig", "",
"kubectl configuration file (default: ~/.kube/config)")
flags.StringVar(&kp.KubeContext, "context", "",
"name of the kubeconfig context to use")
flags.StringVar(&kp.KubeCluster, "cluster", "",
"name of the kubeconfig cluster to use")
flags.StringVar(&kp.KubeAsUser, "as", "",
"username to impersonate for the operation")
flags.StringVar(&kp.KubeAsUID, "as-uid", "",
"uid to impersonate for the operation")
flags.StringArrayVar(&kp.KubeAsGroup, "as-group",
[]string{}, "group to impersonate for the operation, this flag can "+
"be repeated to specify multiple groups")
}
// GetClientConfig gets ClientConfig from Kube' configuration params.
func (kp *Params) GetClientConfig() (clientcmd.ClientConfig, error) {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
configOverrides := &clientcmd.ConfigOverrides{}
if kp.KubeContext != "" {
configOverrides.CurrentContext = kp.KubeContext
}
if kp.KubeCluster != "" {
configOverrides.Context.Cluster = kp.KubeCluster
}
if kp.KubeAsUser != "" {
configOverrides.AuthInfo.Impersonate = kp.KubeAsUser
}
if kp.KubeAsUID != "" {
configOverrides.AuthInfo.ImpersonateUID = kp.KubeAsUID
}
if len(kp.KubeAsGroup) > 0 {
configOverrides.AuthInfo.ImpersonateGroups = kp.KubeAsGroup
}
if len(kp.KubeCfgPath) == 0 {
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides), nil
}
_, err := os.Stat(kp.KubeCfgPath)
if err == nil {
loadingRules.ExplicitPath = kp.KubeCfgPath
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides), nil
}
if !os.IsNotExist(err) {
return nil, err
}
paths := filepath.SplitList(kp.KubeCfgPath)
if len(paths) > 1 {
return nil, fmt.Errorf("%w. '%s' looks "+
"like a path. Please use the env var KUBECONFIG if you want to "+
"check for multiple configuration files", ErrCantFindConfigFile, kp.KubeCfgPath)
}
return nil, fmt.Errorf("%w: '%s'", ErrCantFindConfigFile, kp.KubeCfgPath)
}

190
pkg/k8s/params_test.go Normal file
View File

@ -0,0 +1,190 @@
/*
Copyright 2024 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 k8s_test
import (
"fmt"
"os"
"path/filepath"
"testing"
"gotest.tools/v3/assert"
"k8s.io/client-go/tools/clientcmd"
"knative.dev/client/pkg/k8s"
"knative.dev/client/pkg/util"
"knative.dev/client/pkg/util/test"
)
func TestGetClientConfig(t *testing.T) {
multiConfigs := fmt.Sprintf("%s%s%s",
"/testing/assets/kube-config-01.yml",
string(os.PathListSeparator),
"/testing/assets/kube-config-02.yml")
tempDir := t.TempDir()
tempFile := filepath.Join(tempDir, "mock")
assert.NilError(t, os.WriteFile(tempFile, []byte(basicKubeconfig), test.FileModeReadWrite))
for _, tc := range []typeTestCase{{
"",
"",
"",
"",
[]string{},
"",
clientcmd.NewDefaultClientConfigLoadingRules().ExplicitPath,
"",
}, {
tempFile,
"",
"",
"",
[]string{},
"",
tempFile,
"",
}, {
"/testing/assets/kube-config-01.yml",
"foo",
"",
"",
[]string{},
"bar",
"",
fmt.Sprintf("can not find config file: '%s'", "/testing/assets/kube-config-01.yml"),
}, {
multiConfigs,
"",
"",
"",
[]string{},
"",
"",
fmt.Sprintf("can not find config file. '%s' looks like a path. Please use the env var KUBECONFIG if you want to check for multiple configuration files", multiConfigs),
}, {
tempFile,
"",
"admin",
"",
[]string{},
"",
tempFile,
"",
}, {
tempFile,
"",
"admin",
"",
[]string{"system:authenticated", "system:masters"},
"",
tempFile,
"",
}, {
tempFile,
"",
"admin",
"abc123",
[]string{},
"",
tempFile,
"",
}} {
p := &k8s.Params{
KubeCfgPath: tc.kubeCfgPath,
KubeContext: tc.kubeContext,
KubeAsUser: tc.kubeAsUser,
KubeAsUID: tc.kubeAsUID,
KubeAsGroup: tc.kubeAsGroup,
KubeCluster: tc.kubeCluster,
}
var clientConfig clientcmd.ClientConfig
{
cc, err := p.GetClientConfig()
if tc.expectedError != "" {
assert.Assert(t, util.ContainsAll(err.Error(), tc.expectedError))
} else {
assert.Assert(t, err == nil, err)
}
clientConfig = cc
}
if clientConfig != nil {
configAccess := clientConfig.ConfigAccess()
assert.Assert(t, configAccess.GetExplicitFile() == tc.explicitPath)
if tc.kubeContext != "" {
config, err := clientConfig.RawConfig()
assert.NilError(t, err)
assert.Assert(t, config.CurrentContext == tc.kubeContext)
assert.Assert(t, config.Contexts[tc.kubeContext].Cluster == tc.kubeCluster)
}
if tc.kubeAsUser != "" {
config, err := clientConfig.ClientConfig()
assert.NilError(t, err)
assert.Assert(t, config.Impersonate.UserName == tc.kubeAsUser)
}
if tc.kubeAsUID != "" {
config, err := clientConfig.ClientConfig()
assert.NilError(t, err)
assert.Assert(t, config.Impersonate.UID == tc.kubeAsUID)
}
if len(tc.kubeAsGroup) > 0 {
config, err := clientConfig.ClientConfig()
assert.NilError(t, err)
assert.Assert(t, len(config.Impersonate.Groups) == len(tc.kubeAsGroup))
for i := range tc.kubeAsGroup {
assert.Assert(t, config.Impersonate.Groups[i] == tc.kubeAsGroup[i])
}
}
}
}
}
var basicKubeconfig = `apiVersion: v1
kind: Config
preferences: {}
users:
- name: a
user:
client-certificate-data: ""
client-key-data: ""
clusters:
- name: a
cluster:
insecure-skip-tls-verify: true
server: https://127.0.0.1:8080
contexts:
- name: a
context:
cluster: a
user: a
current-context: a
`
type typeTestCase struct {
kubeCfgPath string
kubeContext string
kubeAsUser string
kubeAsUID string
kubeAsGroup []string
kubeCluster string
explicitPath string
expectedError string
}

View File

@ -18,7 +18,6 @@ package logging
import (
"context"
"fmt"
"os"
"time"
@ -40,14 +39,14 @@ var ErrCallEnsureLoggerFirst = errors.New("call EnsureLogger() before LoggerFrom
// context will have a logger attached to it. Given fields will be added to the
// logger, either new or existing.
func EnsureLogger(ctx context.Context, fields ...Fields) context.Context {
z, err := loggerFrom(ctx)
z, err := zapLoggerFrom(ctx)
if errors.Is(err, ErrCallEnsureLoggerFirst) {
ctx = EnsureLogFile(ctx)
z = setupLogging(ctx)
}
l := &zapLogger{SugaredLogger: z}
l := &ZapLogger{SugaredLogger: z}
for _, f := range fields {
l = l.WithFields(f).(*zapLogger)
l = l.WithFields(f).(*ZapLogger)
}
return WithLogger(ctx, l)
}
@ -55,24 +54,28 @@ func EnsureLogger(ctx context.Context, fields ...Fields) context.Context {
// LoggerFrom returns the logger from the context. If EnsureLogger() was not
// called before, it will panic.
func LoggerFrom(ctx context.Context) Logger {
z, err := loggerFrom(ctx)
if l, ok := ctx.Value(loggerKey).(Logger); ok {
return l
}
z, err := zapLoggerFrom(ctx)
if err != nil {
fatal(err)
}
return &zapLogger{z}
return &ZapLogger{z}
}
// WithLogger attaches the given logger to the context.
func WithLogger(ctx context.Context, l Logger) context.Context {
if z, ok := l.(*zapLogger); ok {
if z, ok := l.(*ZapLogger); ok {
return logging.WithLogger(ctx, z.SugaredLogger)
}
fatal("unsupported logger type: " + fmt.Sprintf("%#v", l))
return nil
return context.WithValue(ctx, loggerKey, l)
}
func loggerFrom(ctx context.Context) (*zap.SugaredLogger, error) {
var loggerKey = struct{}{}
func zapLoggerFrom(ctx context.Context) (*zap.SugaredLogger, error) {
l := logging.FromContext(ctx)
if l.Desugar().Name() == "fallback" {
return nil, ErrCallEnsureLoggerFirst
@ -126,7 +129,7 @@ func createDefaultLogger(ctx context.Context) *zap.Logger {
ec.EncodeTime = ElapsedMillisTimeEncoder(time.Now())
ec.ConsoleSeparator = " "
lvl := activeLogLevel(zapcore.WarnLevel)
lvl := activeLogLevel(LogLevelFromContext(ctx))
logger := zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(ec),
zapcore.AddSync(errout),
@ -161,6 +164,8 @@ func activeLogLevel(defaultLevel zapcore.Level) zapcore.Level {
return defaultLevel
}
// ElapsedMillisTimeEncoder is a time encoder using elapsed time since the
// logger setup.
func ElapsedMillisTimeEncoder(setupTime time.Time) zapcore.TimeEncoder {
return func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendInt64(t.Sub(setupTime).Milliseconds())

View File

@ -0,0 +1,43 @@
/*
Copyright 2024 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 logging
import (
"context"
"go.uber.org/zap/zapcore"
pkgcontext "knative.dev/client/pkg/context"
)
var logLevelKey = struct{}{}
// LogLevelFromContext returns the default log level for the logger.
func LogLevelFromContext(ctx context.Context) zapcore.Level {
if val, ok := ctx.Value(logLevelKey).(zapcore.Level); ok {
return val
} else {
if pkgcontext.TestingTFromContext(ctx) != nil {
return zapcore.DebugLevel
}
return zapcore.WarnLevel
}
}
// WithLogLevel will set given log level as the default one in given context.
func WithLogLevel(ctx context.Context, level zapcore.Level) context.Context {
return context.WithValue(ctx, logLevelKey, level)
}

View File

@ -0,0 +1,36 @@
/*
Copyright 2024 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 logging_test
import (
"context"
"testing"
"go.uber.org/zap/zapcore"
"gotest.tools/v3/assert"
pkgcontext "knative.dev/client/pkg/context"
"knative.dev/client/pkg/output/logging"
)
func TestLogLevel(t *testing.T) {
ctx := context.TODO()
assert.Equal(t, zapcore.WarnLevel, logging.LogLevelFromContext(ctx))
ctx = pkgcontext.WithTestingT(ctx, t)
assert.Equal(t, zapcore.DebugLevel, logging.LogLevelFromContext(ctx))
ctx = logging.WithLogLevel(ctx, zapcore.InfoLevel)
assert.Equal(t, zapcore.InfoLevel, logging.LogLevelFromContext(ctx))
}

View File

@ -18,23 +18,24 @@ package logging
import "go.uber.org/zap"
type zapLogger struct {
// ZapLogger is a Google' zap logger based logger.
type ZapLogger struct {
*zap.SugaredLogger
}
func (z zapLogger) WithName(name string) Logger {
return &zapLogger{
func (z ZapLogger) WithName(name string) Logger {
return &ZapLogger{
SugaredLogger: z.SugaredLogger.Named(name),
}
}
func (z zapLogger) WithFields(fields Fields) Logger {
func (z ZapLogger) WithFields(fields Fields) Logger {
a := make([]interface{}, 0, len(fields)*2)
for k, v := range fields {
a = append(a, k, v)
}
return &zapLogger{
return &ZapLogger{
SugaredLogger: z.SugaredLogger.With(a...),
}
}

View File

@ -84,13 +84,9 @@ Find more information about Knative at: https://knative.dev`, rootName),
// Bootstrap flags (rebinding to avoid errors when parsing the full commands)
config.AddBootstrapFlags(rootCmd.PersistentFlags())
// Global flags
rootCmd.PersistentFlags().StringVar(&p.KubeCfgPath, "kubeconfig", "", "kubectl configuration file (default: ~/.kube/config)")
rootCmd.PersistentFlags().StringVar(&p.KubeContext, "context", "", "name of the kubeconfig context to use")
rootCmd.PersistentFlags().StringVar(&p.KubeCluster, "cluster", "", "name of the kubeconfig cluster to use")
rootCmd.PersistentFlags().StringVar(&p.KubeAsUser, "as", "", "username to impersonate for the operation")
rootCmd.PersistentFlags().StringVar(&p.KubeAsUID, "as-uid", "", "uid to impersonate for the operation")
rootCmd.PersistentFlags().StringArrayVar(&p.KubeAsGroup, "as-group", []string{}, "group to impersonate for the operation, this flag can be repeated to specify multiple groups")
// Global Kube' flags
p.Params.SetFlags(rootCmd.PersistentFlags())
flags.AddBothBoolFlags(rootCmd.PersistentFlags(), &p.LogHTTP, "log-http", "", false, "log http traffic")
// Grouped commands

37
pkg/util/errors/cause.go Normal file
View File

@ -0,0 +1,37 @@
/*
Copyright 2024 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 errors
import (
"emperror.dev/errors"
)
// CauseOf will return the error that caused the returned error. This can be
// used when multierr package is used or fmt.Errorf("%w: %w", ErrFront, cause)
// from standard library is used, and we know the front error.
func CauseOf(err, rootErr error) error {
if errors.Is(err, rootErr) {
for _, cause := range errors.GetErrors(err) {
if errors.Is(cause, rootErr) {
continue
}
return cause
}
return err
}
return err
}

View File

@ -0,0 +1,36 @@
/*
Copyright 2024 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 errors_test
import (
"fmt"
"syscall"
"testing"
"knative.dev/client/pkg/util/errors"
)
func TestCauseOf(t *testing.T) {
errExample := errors.New("example error")
want := syscall.EINVAL
err := fmt.Errorf("%w: %w", errExample, want)
got := errors.CauseOf(err, errExample)
if !errors.Is(got, want) {
t.Errorf("got error %v, want %v", got, want)
}
}

32
pkg/util/errors/wrap.go Normal file
View File

@ -0,0 +1,32 @@
/*
Copyright 2024 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 errors
import "emperror.dev/errors"
// Is reports whether any error in err's chain matches target.
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
func Is(err, target error) bool {
return errors.Is(err, target)
}
// New returns a new error annotated with stack trace at the point New is called.
func New(msg string) error {
return errors.New(msg)
}