diff --git a/docs/cmd/kn_source.md b/docs/cmd/kn_source.md index 8eece1741..4558d06f7 100644 --- a/docs/cmd/kn_source.md +++ b/docs/cmd/kn_source.md @@ -29,6 +29,7 @@ kn source SOURCE|COMMAND * [kn](kn.md) - kn manages Knative Serving and Eventing resources * [kn source apiserver](kn_source_apiserver.md) - Manage Kubernetes api-server sources * [kn source binding](kn_source_binding.md) - Manage sink bindings +* [kn source container](kn_source_container.md) - Manage container sources * [kn source list](kn_source_list.md) - List event sources * [kn source list-types](kn_source_list-types.md) - List event source types * [kn source ping](kn_source_ping.md) - Manage ping sources diff --git a/docs/cmd/kn_source_container.md b/docs/cmd/kn_source_container.md new file mode 100644 index 000000000..f6362cbc5 --- /dev/null +++ b/docs/cmd/kn_source_container.md @@ -0,0 +1,35 @@ +## kn source container + +Manage container sources + +### Synopsis + +Manage container sources + +``` +kn source container create|delete|update|list|describe +``` + +### Options + +``` + -h, --help help for container +``` + +### 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 source](kn_source.md) - Manage event sources +* [kn source container create](kn_source_container_create.md) - Create a container source +* [kn source container delete](kn_source_container_delete.md) - Delete a container source +* [kn source container describe](kn_source_container_describe.md) - Show details of a container source +* [kn source container list](kn_source_container_list.md) - List container sources +* [kn source container update](kn_source_container_update.md) - Update a container source + diff --git a/docs/cmd/kn_source_container_create.md b/docs/cmd/kn_source_container_create.md new file mode 100644 index 000000000..94f7e9e72 --- /dev/null +++ b/docs/cmd/kn_source_container_create.md @@ -0,0 +1,53 @@ +## kn source container create + +Create a container source + +### Synopsis + +Create a container source + +``` +kn source container create NAME --image IMAGE --sink SINK +``` + +### Examples + +``` + + # Create a ContainerSource 'src' to start a container with image 'docker.io/sample/image' and send messages to service 'mysvc' + kn source container create src --image docker.io/sample/image --sink ksvc:mysvc +``` + +### Options + +``` + --arg stringArray Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times. + --cmd string Specify command to be used as entrypoint instead of default one. Example: --cmd /app/start or --cmd /app/start --arg myArg to pass additional arguments. + -e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-). + --env-from stringArray Add environment variables from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret:). Example: --env-from cm:myconfigmap or --env-from secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --env-from cm:myconfigmap-. + -h, --help help for create + --image string Image to run. + --limit strings The resource requirement limits for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource limit, append "-" to the resource name, e.g. '--limit memory-'. + --mount stringArray Mount a ConfigMap (prefix cm: or config-map:), a Secret (prefix secret: or sc:), or an existing Volume (without any prefix) on the specified directory. Example: --mount /mydir=cm:myconfigmap, --mount /mydir=secret:mysecret, or --mount /mydir=myvolume. When a configmap or a secret is specified, a corresponding volume is automatically generated. You can use this flag multiple times. For unmounting a directory, append "-", e.g. --mount /mydir-, which also removes any auto-generated volume. + -n, --namespace string Specify the namespace to operate in. + -p, --port string The port where application listens on, in the format 'NAME:PORT', where 'NAME' is optional. Examples: '--port h2c:8080' , '--port 8080'. + --pull-secret string Image pull secret to set. An empty argument ("") clears the pull secret. The referenced secret must exist in the service's namespace. + --request strings The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource request, append "-" to the resource name, e.g. '--request cpu-'. + --service-account string Service account name to set. An empty argument ("") clears the service account. The referenced service account must exist in the service's namespace. + -s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink 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. + --user int The user ID to run the container (e.g., 1001). + --volume stringArray Add a volume from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret: or sc:). Example: --volume myvolume=cm:myconfigmap or --volume myvolume=secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --volume myvolume-. +``` + +### Options inherited from parent commands + +``` + --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 source container](kn_source_container.md) - Manage container sources + diff --git a/docs/cmd/kn_source_container_delete.md b/docs/cmd/kn_source_container_delete.md new file mode 100644 index 000000000..1a7de5122 --- /dev/null +++ b/docs/cmd/kn_source_container_delete.md @@ -0,0 +1,39 @@ +## kn source container delete + +Delete a container source + +### Synopsis + +Delete a container source + +``` +kn source container delete NAME +``` + +### Examples + +``` + + # Delete a ContainerSource 'containersrc' in default namespace + kn source container delete containersrc +``` + +### 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 source container](kn_source_container.md) - Manage container sources + diff --git a/docs/cmd/kn_source_container_describe.md b/docs/cmd/kn_source_container_describe.md new file mode 100644 index 000000000..d771620ab --- /dev/null +++ b/docs/cmd/kn_source_container_describe.md @@ -0,0 +1,40 @@ +## kn source container describe + +Show details of a container source + +### Synopsis + +Show details of a container source + +``` +kn source container describe NAME +``` + +### Examples + +``` + + # Describe a container source with name 'k8sevents' + kn source container describe k8sevents +``` + +### Options + +``` + -h, --help help for describe + -n, --namespace string Specify the namespace to operate in. + -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 source container](kn_source_container.md) - Manage container sources + diff --git a/docs/cmd/kn_source_container_list.md b/docs/cmd/kn_source_container_list.md new file mode 100644 index 000000000..8f794dd7b --- /dev/null +++ b/docs/cmd/kn_source_container_list.md @@ -0,0 +1,47 @@ +## kn source container list + +List container sources + +### Synopsis + +List container sources + +``` +kn source container list +``` + +### Examples + +``` + + # List all Container sources + kn source container list + + # List all Container sources in YAML format + kn source apiserver 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 source container](kn_source_container.md) - Manage container sources + diff --git a/docs/cmd/kn_source_container_update.md b/docs/cmd/kn_source_container_update.md new file mode 100644 index 000000000..5d9360c1f --- /dev/null +++ b/docs/cmd/kn_source_container_update.md @@ -0,0 +1,53 @@ +## kn source container update + +Update a container source + +### Synopsis + +Update a container source + +``` +kn source container update NAME --image IMAGE +``` + +### Examples + +``` + + # Update a ContainerSource 'src' with a different image uri 'docker.io/sample/newimage' + kn source container update src --image docker.io/sample/newimage +``` + +### Options + +``` + --arg stringArray Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times. + --cmd string Specify command to be used as entrypoint instead of default one. Example: --cmd /app/start or --cmd /app/start --arg myArg to pass additional arguments. + -e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-). + --env-from stringArray Add environment variables from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret:). Example: --env-from cm:myconfigmap or --env-from secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --env-from cm:myconfigmap-. + -h, --help help for update + --image string Image to run. + --limit strings The resource requirement limits for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource limit, append "-" to the resource name, e.g. '--limit memory-'. + --mount stringArray Mount a ConfigMap (prefix cm: or config-map:), a Secret (prefix secret: or sc:), or an existing Volume (without any prefix) on the specified directory. Example: --mount /mydir=cm:myconfigmap, --mount /mydir=secret:mysecret, or --mount /mydir=myvolume. When a configmap or a secret is specified, a corresponding volume is automatically generated. You can use this flag multiple times. For unmounting a directory, append "-", e.g. --mount /mydir-, which also removes any auto-generated volume. + -n, --namespace string Specify the namespace to operate in. + -p, --port string The port where application listens on, in the format 'NAME:PORT', where 'NAME' is optional. Examples: '--port h2c:8080' , '--port 8080'. + --pull-secret string Image pull secret to set. An empty argument ("") clears the pull secret. The referenced secret must exist in the service's namespace. + --request strings The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource request, append "-" to the resource name, e.g. '--request cpu-'. + --service-account string Service account name to set. An empty argument ("") clears the service account. The referenced service account must exist in the service's namespace. + -s, --sink string Addressable sink for events. You can specify a broker, channel, Knative service or URI. Examples: '--sink broker:nest' for a broker 'nest', '--sink channel:pipe' for a channel 'pipe', '--sink 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. + --user int The user ID to run the container (e.g., 1001). + --volume stringArray Add a volume from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret: or sc:). Example: --volume myvolume=cm:myconfigmap or --volume myvolume=secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --volume myvolume-. +``` + +### Options inherited from parent commands + +``` + --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 source container](kn_source_container.md) - Manage container sources + diff --git a/pkg/kn/commands/source/container/container.go b/pkg/kn/commands/source/container/container.go new file mode 100644 index 000000000..dd078670b --- /dev/null +++ b/pkg/kn/commands/source/container/container.go @@ -0,0 +1,68 @@ +/* +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 container + +import ( + "github.com/spf13/cobra" + "k8s.io/client-go/tools/clientcmd" + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/sources/v1alpha2" + clientv1alpha2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha2" +) + +// NewContainerCommand for managing Container source +func NewContainerCommand(p *commands.KnParams) *cobra.Command { + containerSourceCmd := &cobra.Command{ + Use: "container create|delete|update|list|describe", + Short: "Manage container sources", + } + containerSourceCmd.AddCommand(NewContainerCreateCommand(p)) + containerSourceCmd.AddCommand(NewContainerDeleteCommand(p)) + containerSourceCmd.AddCommand(NewContainerUpdateCommand(p)) + containerSourceCmd.AddCommand(NewContainerListCommand(p)) + containerSourceCmd.AddCommand(NewContainerDescribeCommand(p)) + return containerSourceCmd +} + +var containerSourceClientFactory func(config clientcmd.ClientConfig, namespace string) (v1alpha2.KnContainerSourcesClient, error) + +func newContainerSourceClient(p *commands.KnParams, cmd *cobra.Command) (v1alpha2.KnContainerSourcesClient, error) { + namespace, err := p.GetNamespace(cmd) + if err != nil { + return nil, err + } + + if containerSourceClientFactory != nil { + config, err := p.GetClientConfig() + if err != nil { + return nil, err + } + return containerSourceClientFactory(config, namespace) + } + + clientConfig, err := p.RestConfig() + if err != nil { + return nil, err + } + + client, err := clientv1alpha2.NewForConfig(clientConfig) + if err != nil { + return nil, err + } + + return v1alpha2.NewKnSourcesClient(client, namespace).ContainerSourcesClient(), nil +} diff --git a/pkg/kn/commands/source/container/container_test.go b/pkg/kn/commands/source/container/container_test.go new file mode 100644 index 000000000..ca497162e --- /dev/null +++ b/pkg/kn/commands/source/container/container_test.go @@ -0,0 +1,108 @@ +/* +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 container + +import ( + "bytes" + + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/clientcmd" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + duckv1 "knative.dev/pkg/apis/duck/v1" + + kndynamic "knative.dev/client/pkg/dynamic" + clientv1alpha2 "knative.dev/client/pkg/sources/v1alpha2" + + "knative.dev/client/pkg/kn/commands" +) + +var blankConfig clientcmd.ClientConfig + +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 executeContainerSourceCommand(containerSourceClient clientv1alpha2.KnContainerSourcesClient, 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 := NewContainerCommand(knParams) + cmd.SetArgs(args) + cmd.SetOutput(output) + + containerSourceClientFactory = func(config clientcmd.ClientConfig, namespace string) (clientv1alpha2.KnContainerSourcesClient, error) { + return containerSourceClient, nil + } + defer cleanupContainerServerMockClient() + + err := cmd.Execute() + + return output.String(), err +} + +func cleanupContainerServerMockClient() { + containerSourceClientFactory = nil +} + +func createContainerSource(name, image string, sink duckv1.Destination) *v1alpha2.ContainerSource { + return clientv1alpha2.NewContainerSourceBuilder(name). + PodSpec(corev1.PodSpec{ + Containers: []corev1.Container{{ + Image: image, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{}, + Requests: corev1.ResourceList{}, + }, + }}}). + Sink(sink). + Build() +} + +func createSinkv1(serviceName, namespace string) duckv1.Destination { + return duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Name: serviceName, + APIVersion: "serving.knative.dev/v1", + Namespace: namespace, + }, + } +} diff --git a/pkg/kn/commands/source/container/create.go b/pkg/kn/commands/source/container/create.go new file mode 100644 index 000000000..97c9313e0 --- /dev/null +++ b/pkg/kn/commands/source/container/create.go @@ -0,0 +1,95 @@ +/* +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 container + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + "knative.dev/client/pkg/kn/commands/flags" + knflags "knative.dev/client/pkg/kn/flags" + + corev1 "k8s.io/api/core/v1" + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/sources/v1alpha2" +) + +// NewContainerCreateCommand for creating source +func NewContainerCreateCommand(p *commands.KnParams) *cobra.Command { + var podFlags knflags.PodSpecFlags + var sinkFlags flags.SinkFlags + + cmd := &cobra.Command{ + Use: "create NAME --image IMAGE --sink SINK", + Short: "Create a container source", + Example: ` + # Create a ContainerSource 'src' to start a container with image 'docker.io/sample/image' and send messages to service 'mysvc' + kn source container create src --image docker.io/sample/image --sink ksvc:mysvc`, + + RunE: func(cmd *cobra.Command, args []string) (err error) { + if len(args) != 1 { + return errors.New("requires the name of the source to create as single argument") + } + name := args[0] + + srcClient, err := newContainerSourceClient(p, cmd) + if err != nil { + return err + } + + namespace := srcClient.Namespace() + + dynamicClient, err := p.NewDynamicClient(namespace) + if err != nil { + return err + } + + objectRef, err := sinkFlags.ResolveSink(dynamicClient, namespace) + if err != nil { + return fmt.Errorf( + "cannot create ContainerSource '%s' in namespace '%s' "+ + "because: %s", name, namespace, err) + } + + podSpec := &corev1.PodSpec{Containers: []corev1.Container{{}}} + err = podFlags.ResolvePodSpec(podSpec, cmd.Flags()) + if err != nil { + return fmt.Errorf( + "cannot create ContainerSource '%s' in namespace '%s' "+ + "because: %s", name, namespace, err) + } + + b := v1alpha2.NewContainerSourceBuilder(name).Sink(*objectRef).PodSpec(*podSpec) + err = srcClient.CreateContainerSource(b.Build()) + if err != nil { + return fmt.Errorf( + "cannot create ContainerSource '%s' in namespace '%s' "+ + "because: %s", name, namespace, err) + } + + fmt.Fprintf(cmd.OutOrStdout(), "ContainerSource '%s' created in namespace '%s'.\n", args[0], namespace) + return nil + }, + } + commands.AddNamespaceFlags(cmd.Flags(), false) + podFlags.AddFlags(cmd.Flags()) + sinkFlags.Add(cmd) + cmd.MarkFlagRequired("image") + cmd.MarkFlagRequired("sink") + return cmd +} diff --git a/pkg/kn/commands/source/container/create_test.go b/pkg/kn/commands/source/container/create_test.go new file mode 100644 index 000000000..13cc7b719 --- /dev/null +++ b/pkg/kn/commands/source/container/create_test.go @@ -0,0 +1,62 @@ +/* +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 container + +import ( + "testing" + + "gotest.tools/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + servingv1 "knative.dev/serving/pkg/apis/serving/v1" + + dynamicfake "knative.dev/client/pkg/dynamic/fake" + "knative.dev/client/pkg/sources/v1alpha2" + "knative.dev/client/pkg/util" +) + +func TestCreateContainerSource(t *testing.T) { + testsvc := &servingv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "serving.knative.dev/v1"}, + ObjectMeta: metav1.ObjectMeta{Name: "testsvc", Namespace: "default"}, + } + dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default", testsvc) + containerClient := v1alpha2.NewMockKnContainerSourceClient(t) + + containerRecorder := containerClient.Recorder() + containerRecorder.CreateContainerSource(createContainerSource("testsource", "docker.io/test/testimg", createSinkv1("testsvc", "default")), nil) + + out, err := executeContainerSourceCommand(containerClient, dynamicClient, "create", "testsource", "--image", "docker.io/test/testimg", "--sink", "ksvc:testsvc") + assert.NilError(t, err, "Container source should be created") + assert.Assert(t, util.ContainsAll(out, "created", "default", "testsource")) + + containerRecorder.Validate() +} + +func TestSinkNotFoundError(t *testing.T) { + dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default") + containerClient := v1alpha2.NewMockKnContainerSourceClient(t) + errorMsg := "cannot create ContainerSource 'testsource' in namespace 'default' because: services.serving.knative.dev \"testsvc\" not found" + out, err := executeContainerSourceCommand(containerClient, dynamicClient, "create", "testsource", "--image", "docker.io/test/testimg", "--sink", "ksvc:testsvc") + assert.Error(t, err, errorMsg) + assert.Assert(t, util.ContainsAll(out, errorMsg, "Usage")) +} + +func TestNoSinkError(t *testing.T) { + containerClient := v1alpha2.NewMockKnContainerSourceClient(t) + _, err := executeContainerSourceCommand(containerClient, nil, "create", "testsource", "--image", "docker.io/test/testimg") + assert.ErrorContains(t, err, "required flag(s)", "sink", "not set") +} diff --git a/pkg/kn/commands/source/container/delete.go b/pkg/kn/commands/source/container/delete.go new file mode 100644 index 000000000..a486431a0 --- /dev/null +++ b/pkg/kn/commands/source/container/delete.go @@ -0,0 +1,63 @@ +/* +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 container + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + + "knative.dev/client/pkg/kn/commands" +) + +// NewContainerDeleteCommand for deleting source +func NewContainerDeleteCommand(p *commands.KnParams) *cobra.Command { + deleteCommand := &cobra.Command{ + Use: "delete NAME", + Short: "Delete a container source", + Example: ` + # Delete a ContainerSource 'containersrc' in default namespace + kn source container delete containersrc`, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("requires the name of the source as single argument") + } + name := args[0] + + namespace, err := p.GetNamespace(cmd) + if err != nil { + return err + } + + // get client + srcClient, err := newContainerSourceClient(p, cmd) + if err != nil { + return err + } + + err = srcClient.DeleteContainerSource(name) + if err != nil { + return err + } + fmt.Fprintf(cmd.OutOrStdout(), "ContainerSourcd '%s' deleted in namespace '%s'.\n", args[0], namespace) + return nil + }, + } + commands.AddNamespaceFlags(deleteCommand.Flags(), false) + return deleteCommand +} diff --git a/pkg/kn/commands/source/container/delete_test.go b/pkg/kn/commands/source/container/delete_test.go new file mode 100644 index 000000000..6c8a61ee9 --- /dev/null +++ b/pkg/kn/commands/source/container/delete_test.go @@ -0,0 +1,55 @@ +/* +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 container + +import ( + "errors" + "testing" + + "gotest.tools/assert" + + "knative.dev/client/pkg/sources/v1alpha2" + "knative.dev/client/pkg/util" +) + +func TestContainerSourceDelete(t *testing.T) { + + containerClient := v1alpha2.NewMockKnContainerSourceClient(t, "testns") + containerRecorder := containerClient.Recorder() + + containerRecorder.DeleteContainerSource("testsource", nil) + + out, err := executeContainerSourceCommand(containerClient, nil, "delete", "testsource") + assert.NilError(t, err) + assert.Assert(t, util.ContainsAll(out, "deleted", "default", "testsource")) + + containerRecorder.Validate() +} + +func TestDeleteWithError(t *testing.T) { + + containerClient := v1alpha2.NewMockKnContainerSourceClient(t, "mynamespace") + containerRecorder := containerClient.Recorder() + + containerRecorder.DeleteContainerSource("testsource", errors.New("container source testsource not found")) + + out, err := executeContainerSourceCommand(containerClient, nil, "delete", "testsource") + assert.ErrorContains(t, err, "testsource") + assert.Assert(t, util.ContainsAll(out, "container", "source", "testsource", "not found")) + + containerRecorder.Validate() +} diff --git a/pkg/kn/commands/source/container/describe.go b/pkg/kn/commands/source/container/describe.go new file mode 100644 index 000000000..6d3fa69c9 --- /dev/null +++ b/pkg/kn/commands/source/container/describe.go @@ -0,0 +1,136 @@ +/* +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 container + +import ( + "errors" + "sort" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "knative.dev/client/lib/printing" + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/printers" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" +) + +// NewContainerDescribeCommand to describe an Container source object +func NewContainerDescribeCommand(p *commands.KnParams) *cobra.Command { + apiServerDescribe := &cobra.Command{ + Use: "describe NAME", + Short: "Show details of a container source", + Example: ` + # Describe a container source with name 'k8sevents' + kn source container describe k8sevents`, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("'kn source container describe' requires name of the source as single argument") + } + name := args[0] + + sourceClient, err := newContainerSourceClient(p, cmd) + if err != nil { + return err + } + + source, err := sourceClient.GetContainerSource(name) + if err != nil { + return err + } + + out := cmd.OutOrStdout() + dw := printers.NewPrefixWriter(out) + + printDetails, err := cmd.Flags().GetBool("verbose") + if err != nil { + return err + } + + writeContainerSource(dw, source, printDetails) + dw.WriteLine() + if err := dw.Flush(); err != nil { + return err + } + + printing.DescribeSink(dw, "Sink", source.Namespace, &source.Spec.Sink) + dw.WriteLine() + if err := dw.Flush(); err != nil { + return err + } + + if source.Spec.CloudEventOverrides != nil && source.Spec.CloudEventOverrides.Extensions != nil { + writeCeOverrides(dw, source.Spec.CloudEventOverrides.Extensions) + } + + dw.WriteLine() + if err := dw.Flush(); err != nil { + return err + } + + // Condition info + commands.WriteConditions(dw, source.Status.Conditions, printDetails) + if err := dw.Flush(); err != nil { + return err + } + + return nil + }, + } + flags := apiServerDescribe.Flags() + commands.AddNamespaceFlags(flags, false) + flags.BoolP("verbose", "v", false, "More output.") + + return apiServerDescribe +} + +func writeContainerSource(dw printers.PrefixWriter, source *v1alpha2.ContainerSource, printDetails bool) { + commands.WriteMetadata(dw, &source.ObjectMeta, printDetails) + writeContainer(dw, &source.Spec.Template.Spec.Containers[0]) +} + +func writeCeOverrides(dw printers.PrefixWriter, ceOverrides map[string]string) { + subDw := dw.WriteAttribute("CloudEvent Overrides", "") + keys := make([]string, 0, len(ceOverrides)) + for k := range ceOverrides { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + subDw.WriteAttribute(k, ceOverrides[k]) + } +} + +func writeContainer(dw printers.PrefixWriter, container *corev1.Container) { + subDw := dw.WriteAttribute("Container", "") + subDw.WriteAttribute("Image", container.Image) + if len(container.Env) > 0 { + envDw := subDw.WriteAttribute("Env", "") + for _, env := range container.Env { + value := env.Value + if env.ValueFrom != nil { + value = "[ref]" + } + envDw.WriteAttribute(env.Name, value) + } + } + if len(container.Args) > 0 { + envDw := subDw.WriteAttribute("Args", "") + for _, k := range container.Args { + envDw.WriteAttribute(k, "") + } + } +} diff --git a/pkg/kn/commands/source/container/describe_test.go b/pkg/kn/commands/source/container/describe_test.go new file mode 100644 index 000000000..efe6d52f0 --- /dev/null +++ b/pkg/kn/commands/source/container/describe_test.go @@ -0,0 +1,55 @@ +/* +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 container + +import ( + "errors" + "testing" + + "gotest.tools/assert" + "knative.dev/client/pkg/sources/v1alpha2" + "knative.dev/client/pkg/util" +) + +func TestDescribeError(t *testing.T) { + containerClient := v1alpha2.NewMockKnContainerSourceClient(t, "mynamespace") + + containerRecorder := containerClient.Recorder() + containerRecorder.GetContainerSource("testsource", nil, errors.New("no container source testsource")) + + out, err := executeContainerSourceCommand(containerClient, nil, "describe", "testsource") + assert.ErrorContains(t, err, "testsource") + assert.Assert(t, util.ContainsAll(out, "Usage", "testsource")) + + containerRecorder.Validate() +} + +func TestSimpleDescribe(t *testing.T) { + containerClient := v1alpha2.NewMockKnContainerSourceClient(t, "mynamespace") + + containerRecorder := containerClient.Recorder() + sampleSource := createContainerSource("testsource", "docker.io/test/testimg", createSinkv1("testsvc", "default")) + sampleSource.Namespace = "mynamespace" + containerRecorder.GetContainerSource("testsource", sampleSource, nil) + + out, err := executeContainerSourceCommand(containerClient, nil, "describe", "testsource") + assert.NilError(t, err) + assert.Assert(t, util.ContainsAll(out, "testsource", "docker.io/test/testimg", "testsvc")) + assert.Assert(t, util.ContainsNone(out, "URI")) + + containerRecorder.Validate() +} diff --git a/pkg/kn/commands/source/container/human_readable_flags.go b/pkg/kn/commands/source/container/human_readable_flags.go new file mode 100644 index 000000000..bdd35f974 --- /dev/null +++ b/pkg/kn/commands/source/container/human_readable_flags.go @@ -0,0 +1,130 @@ +/* +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 container + +import ( + "sort" + "strings" + + "k8s.io/apimachinery/pkg/runtime" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/kn/commands/flags" + + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + + hprinters "knative.dev/client/pkg/printers" +) + +// ContainerSourceListHandlers handles printing human readable table for `kn source apiserver list` command's output +func ContainerSourceListHandlers(h hprinters.PrintHandler) { + sourceColumnDefinitions := []metav1beta1.TableColumnDefinition{ + {Name: "Namespace", Type: "string", Description: "Namespace of the Container source", Priority: 0}, + {Name: "Name", Type: "string", Description: "Name of the Container source", Priority: 1}, + {Name: "Image", Type: "string", Description: "Image URI configured for the Container source", Priority: 1}, + {Name: "Sink", Type: "string", Description: "Sink of the Container source", Priority: 1}, + {Name: "Age", Type: "string", Description: "Age of the Container source", Priority: 1}, + {Name: "Conditions", Type: "string", Description: "Ready state conditions", Priority: 1}, + {Name: "Ready", Type: "string", Description: "Ready state of the Container source", Priority: 1}, + {Name: "Reason", Type: "string", Description: "Reason if state is not Ready", Priority: 1}, + } + h.TableHandler(sourceColumnDefinitions, printSource) + h.TableHandler(sourceColumnDefinitions, printSourceList) +} + +// printSource populates a single row of source apiserver list table +func printSource(source *v1alpha2.ContainerSource, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) { + row := metav1beta1.TableRow{ + Object: runtime.RawExtension{Object: source}, + } + + name := source.Name + age := commands.TranslateTimestampSince(source.CreationTimestamp) + conditions := commands.ConditionsValue(source.Status.Conditions) + ready := commands.ReadyCondition(source.Status.Conditions) + reason := strings.TrimSpace(commands.NonReadyConditionReason(source.Status.Conditions)) + image := source.Spec.Template.Spec.Containers[0].Image + + // Not moving to SinkToString() as it references v1beta1.Destination + // This source is going to be moved/removed soon to v1, so no need to move + // it now + sink := flags.SinkToString(source.Spec.Sink) + + if options.AllNamespaces { + row.Cells = append(row.Cells, source.Namespace) + } + + row.Cells = append(row.Cells, name, image, sink, age, conditions, ready, reason) + return []metav1beta1.TableRow{row}, nil +} + +// printSourceList populates the source apiserver list table rows +func printSourceList(sourceList *v1alpha2.ContainerSourceList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) { + if options.AllNamespaces { + return printSourceListWithNamespace(sourceList, options) + } + + rows := make([]metav1beta1.TableRow, 0, len(sourceList.Items)) + + sort.SliceStable(sourceList.Items, func(i, j int) bool { + return sourceList.Items[i].GetName() < sourceList.Items[j].GetName() + }) + + for _, item := range sourceList.Items { + row, err := printSource(&item, options) + if err != nil { + return nil, err + } + + rows = append(rows, row...) + } + return rows, nil +} + +// printSourceListWithNamespace populates the knative service table rows with namespace column +func printSourceListWithNamespace(sourceList *v1alpha2.ContainerSourceList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) { + rows := make([]metav1beta1.TableRow, 0, len(sourceList.Items)) + + // temporary slice for sorting services in non-default namespace + others := []metav1beta1.TableRow{} + + for _, source := range sourceList.Items { + // Fill in with services in `default` namespace at first + if source.Namespace == "default" { + r, err := printSource(&source, options) + if err != nil { + return nil, err + } + rows = append(rows, r...) + continue + } + // put other services in temporary slice + r, err := printSource(&source, options) + if err != nil { + return nil, err + } + others = append(others, r...) + } + + // sort other services list alphabetically by namespace + sort.SliceStable(others, func(i, j int) bool { + return others[i].Cells[0].(string) < others[j].Cells[0].(string) + }) + + return append(rows, others...), nil +} diff --git a/pkg/kn/commands/source/container/list.go b/pkg/kn/commands/source/container/list.go new file mode 100644 index 000000000..5f830ae2a --- /dev/null +++ b/pkg/kn/commands/source/container/list.go @@ -0,0 +1,68 @@ +/* +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 container + +import ( + "fmt" + + "github.com/spf13/cobra" + + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/kn/commands/flags" +) + +// NewContainerListCommand is for listing Container sources +func NewContainerListCommand(p *commands.KnParams) *cobra.Command { + listFlags := flags.NewListPrintFlags(ContainerSourceListHandlers) + + listCommand := &cobra.Command{ + Use: "list", + Short: "List container sources", + Example: ` + # List all Container sources + kn source container list + + # List all Container sources in YAML format + kn source apiserver list -o yaml`, + + RunE: func(cmd *cobra.Command, args []string) (err error) { + containerClient, err := newContainerSourceClient(p, cmd) + if err != nil { + return err + } + + sourceList, err := containerClient.ListContainerSources() + if err != nil { + return err + } + + if len(sourceList.Items) == 0 { + fmt.Fprintf(cmd.OutOrStdout(), "No Container source found.\n") + return nil + } + + if containerClient.Namespace() == "" { + listFlags.EnsureWithNamespace() + } + + return listFlags.Print(sourceList, cmd.OutOrStdout()) + }, + } + commands.AddNamespaceFlags(listCommand.Flags(), true) + listFlags.AddFlags(listCommand) + return listCommand +} diff --git a/pkg/kn/commands/source/container/list_test.go b/pkg/kn/commands/source/container/list_test.go new file mode 100644 index 000000000..c142cb2db --- /dev/null +++ b/pkg/kn/commands/source/container/list_test.go @@ -0,0 +1,60 @@ +/* +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 container + +import ( + "testing" + + "gotest.tools/assert" + v1alpha22 "knative.dev/client/pkg/sources/v1alpha2" + "knative.dev/client/pkg/util" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" +) + +func TestListContainerSource(t *testing.T) { + containerClient := v1alpha22.NewMockKnContainerSourceClient(t) + + containerRecorder := containerClient.Recorder() + sampleSource := createContainerSource("testsource", "docker.io/test/newimg", createSinkv1("svc2", "default")) + sampleSourceList := v1alpha2.ContainerSourceList{} + sampleSourceList.Items = []v1alpha2.ContainerSource{*sampleSource} + + containerRecorder.ListContainerSources(&sampleSourceList, nil) + + out, err := executeContainerSourceCommand(containerClient, nil, "list") + assert.NilError(t, err, "sources should be listed") + assert.Assert(t, util.ContainsAll(out, "NAME", "IMAGE", "SINK", "AGE", "CONDITIONS", "READY", "REASON")) + assert.Assert(t, util.ContainsAll(out, "testsource", "docker.io/test/newimg", "ksvc:svc2")) + + containerRecorder.Validate() +} + +func TestListContainerSourceEmpty(t *testing.T) { + containerClient := v1alpha22.NewMockKnContainerSourceClient(t) + + containerRecorder := containerClient.Recorder() + sampleSourceList := v1alpha2.ContainerSourceList{} + + containerRecorder.ListContainerSources(&sampleSourceList, nil) + + out, err := executeContainerSourceCommand(containerClient, nil, "list") + assert.NilError(t, err, "Sources should be listed") + assert.Assert(t, util.ContainsNone(out, "NAME", "IMAGE", "SINK", "AGE", "CONDITIONS", "READY", "REASON")) + assert.Assert(t, util.ContainsAll(out, "No", "Container", "source", "found")) + + containerRecorder.Validate() +} diff --git a/pkg/kn/commands/source/container/update.go b/pkg/kn/commands/source/container/update.go new file mode 100644 index 000000000..28c7e13e1 --- /dev/null +++ b/pkg/kn/commands/source/container/update.go @@ -0,0 +1,105 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package container + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + "knative.dev/client/pkg/kn/commands/flags" + knflags "knative.dev/client/pkg/kn/flags" + + "knative.dev/client/pkg/kn/commands" + "knative.dev/client/pkg/sources/v1alpha2" +) + +// NewContainerUpdateCommand for managing source update +func NewContainerUpdateCommand(p *commands.KnParams) *cobra.Command { + var podFlags knflags.PodSpecFlags + var sinkFlags flags.SinkFlags + + cmd := &cobra.Command{ + Use: "update NAME --image IMAGE", + Short: "Update a container source", + Example: ` + # Update a ContainerSource 'src' with a different image uri 'docker.io/sample/newimage' + kn source container update src --image docker.io/sample/newimage`, + + RunE: func(cmd *cobra.Command, args []string) (err error) { + if len(args) != 1 { + return errors.New("requires the name of the source as single argument") + } + name := args[0] + + // get client + srcClient, err := newContainerSourceClient(p, cmd) + if err != nil { + return err + } + + namespace := srcClient.Namespace() + + dynamicClient, err := p.NewDynamicClient(namespace) + if err != nil { + return err + } + + source, err := srcClient.GetContainerSource(name) + if err != nil { + return err + } + if source.GetDeletionTimestamp() != nil { + return fmt.Errorf("can't update container source %s because it has been marked for deletion", name) + } + + b := v1alpha2.NewContainerSourceBuilderFromExisting(source) + podSpec := b.Build().Spec.Template.Spec + err = podFlags.ResolvePodSpec(&podSpec, cmd.Flags()) + if err != nil { + return fmt.Errorf( + "cannot update ContainerSource '%s' in namespace '%s' "+ + "because: %s", name, namespace, err) + } + b.PodSpec(podSpec) + + if cmd.Flags().Changed("sink") { + objectRef, err := sinkFlags.ResolveSink(dynamicClient, namespace) + if err != nil { + return fmt.Errorf( + "cannot update ContainerSource '%s' in namespace '%s' "+ + "because: %s", name, namespace, err) + } + b.Sink(*objectRef) + } + + err = srcClient.UpdateContainerSource(b.Build()) + if err != nil { + return fmt.Errorf( + "cannot update ContainerSource '%s' in namespace '%s' "+ + "because: %s", name, namespace, err) + } + + fmt.Fprintf(cmd.OutOrStdout(), "Container source '%s' updated in namespace '%s'.\n", args[0], namespace) + return err + }, + } + commands.AddNamespaceFlags(cmd.Flags(), false) + podFlags.AddFlags(cmd.Flags()) + sinkFlags.Add(cmd) + return cmd +} diff --git a/pkg/kn/commands/source/container/update_test.go b/pkg/kn/commands/source/container/update_test.go new file mode 100644 index 000000000..c516bb77a --- /dev/null +++ b/pkg/kn/commands/source/container/update_test.go @@ -0,0 +1,62 @@ +/* +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 container + +import ( + "testing" + + "gotest.tools/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + dynamicfake "knative.dev/client/pkg/dynamic/fake" + "knative.dev/client/pkg/sources/v1alpha2" + "knative.dev/client/pkg/util" + servingv1 "knative.dev/serving/pkg/apis/serving/v1" +) + +func TestContainerSourceUpdate(t *testing.T) { + containerClient := v1alpha2.NewMockKnContainerSourceClient(t) + dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default", &servingv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "serving.knative.dev/v1"}, + ObjectMeta: metav1.ObjectMeta{Name: "svc2", Namespace: "default"}, + }) + + containerRecorder := containerClient.Recorder() + + present := createContainerSource("testsource", "docker.io/test/testimg", createSinkv1("svc2", "default")) + containerRecorder.GetContainerSource("testsource", present, nil) + + updated := createContainerSource("testsource", "docker.io/test/newimg", createSinkv1("svc2", "default")) + containerRecorder.UpdateContainerSource(updated, nil) + + output, err := executeContainerSourceCommand(containerClient, dynamicClient, "update", "testsource", "--image", "docker.io/test/newimg") + assert.NilError(t, err) + assert.Assert(t, util.ContainsAll(output, "testsource", "updated", "default")) + + containerRecorder.Validate() +} + +func TestContainerSourceUpdateSinkError(t *testing.T) { + containerClient := v1alpha2.NewMockKnContainerSourceClient(t) + dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default") + containerRecorder := containerClient.Recorder() + present := createContainerSource("testsource", "docker.io/test/testimg", createSinkv1("svc2", "default")) + containerRecorder.GetContainerSource("testsource", present, nil) + errorMsg := "cannot update ContainerSource 'testsource' in namespace 'default' because: services.serving.knative.dev \"testsvc\" not found" + out, err := executeContainerSourceCommand(containerClient, dynamicClient, "update", "testsource", "--sink", "ksvc:testsvc") + assert.Error(t, err, errorMsg) + assert.Assert(t, util.ContainsAll(out, errorMsg, "Usage")) +} diff --git a/pkg/kn/commands/source/source.go b/pkg/kn/commands/source/source.go index e7817c7d4..42bb4548d 100644 --- a/pkg/kn/commands/source/source.go +++ b/pkg/kn/commands/source/source.go @@ -20,6 +20,7 @@ import ( "knative.dev/client/pkg/kn/commands" "knative.dev/client/pkg/kn/commands/source/apiserver" "knative.dev/client/pkg/kn/commands/source/binding" + "knative.dev/client/pkg/kn/commands/source/container" "knative.dev/client/pkg/kn/commands/source/ping" ) @@ -34,5 +35,6 @@ func NewSourceCommand(p *commands.KnParams) *cobra.Command { sourceCmd.AddCommand(apiserver.NewAPIServerCommand(p)) sourceCmd.AddCommand(ping.NewPingCommand(p)) sourceCmd.AddCommand(binding.NewBindingCommand(p)) + sourceCmd.AddCommand(container.NewContainerCommand(p)) return sourceCmd } diff --git a/pkg/kn/flags/podspec.go b/pkg/kn/flags/podspec.go index 6a6d22015..1ad26bb2b 100644 --- a/pkg/kn/flags/podspec.go +++ b/pkg/kn/flags/podspec.go @@ -153,7 +153,6 @@ func (p *PodSpecFlags) AddFlags(flagset *pflag.FlagSet) []string { // ResolvePodSpec will create corev1.PodSpec based on the flag inputs func (p *PodSpecFlags) ResolvePodSpec(podSpec *corev1.PodSpec, flags *pflag.FlagSet) error { var err error - if flags.Changed("env") { envMap, err := util.MapFromArrayAllowingSingles(p.Env, "=") if err != nil { diff --git a/pkg/sources/v1alpha2/client.go b/pkg/sources/v1alpha2/client.go index 0ea81c68f..3971c6536 100644 --- a/pkg/sources/v1alpha2/client.go +++ b/pkg/sources/v1alpha2/client.go @@ -31,6 +31,9 @@ type KnSourcesClient interface { // Get client for ApiServer sources APIServerSourcesClient() KnAPIServerSourcesClient + + // Get client for container sources + ContainerSourcesClient() KnContainerSourcesClient } // sourcesClient is a combination of Sources client interface and namespace @@ -64,6 +67,11 @@ func (c *sourcesClient) APIServerSourcesClient() KnAPIServerSourcesClient { return newKnAPIServerSourcesClient(c.client.ApiServerSources(c.namespace), c.namespace) } +// ApiServerSourcesClient for dealing with ApiServer sources +func (c *sourcesClient) ContainerSourcesClient() KnContainerSourcesClient { + return newKnContainerSourcesClient(c.client.ContainerSources(c.namespace), c.namespace) +} + // BuiltInSourcesGVKs returns the GVKs for built in sources func BuiltInSourcesGVKs() []schema.GroupVersionKind { return []schema.GroupVersionKind{ diff --git a/pkg/sources/v1alpha2/container_client.go b/pkg/sources/v1alpha2/container_client.go new file mode 100644 index 000000000..33a2e595e --- /dev/null +++ b/pkg/sources/v1alpha2/container_client.go @@ -0,0 +1,176 @@ +/* +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 v1alpha2 + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + knerrors "knative.dev/client/pkg/errors" + "knative.dev/client/pkg/util" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + "knative.dev/eventing/pkg/client/clientset/versioned/scheme" + clientv1alpha2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha2" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +// KnContainerSourcesClient interface for working with ApiServer sources +type KnContainerSourcesClient interface { + + // Get an ContainerSource by name + GetContainerSource(name string) (*v1alpha2.ContainerSource, error) + + // Create an ContainerSource by object + CreateContainerSource(containerSrc *v1alpha2.ContainerSource) error + + // Update an ContainerSource by object + UpdateContainerSource(containerSrc *v1alpha2.ContainerSource) error + + // Delete an ContainerSource by name + DeleteContainerSource(name string) error + + // List ContainerSource + ListContainerSources() (*v1alpha2.ContainerSourceList, error) + + // Get namespace for this client + Namespace() string +} + +// knSourcesClient is a combination of Sources client interface and namespace +// Temporarily help to add sources dependencies +// May be changed when adding real sources features +type containerSourcesClient struct { + client clientv1alpha2.ContainerSourceInterface + namespace string +} + +// newKnContainerSourcesClient is to invoke Eventing Sources Client API to create object +func newKnContainerSourcesClient(client clientv1alpha2.ContainerSourceInterface, namespace string) KnContainerSourcesClient { + return &containerSourcesClient{ + client: client, + namespace: namespace, + } +} + +//GetContainerSource returns containerSrc object if present +func (c *containerSourcesClient) GetContainerSource(name string) (*v1alpha2.ContainerSource, error) { + containerSrc, err := c.client.Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return nil, knerrors.GetError(err) + } + + return containerSrc, nil +} + +//CreateContainerSource is used to create an instance of ContainerSource +func (c *containerSourcesClient) CreateContainerSource(containerSrc *v1alpha2.ContainerSource) error { + _, err := c.client.Create(context.TODO(), containerSrc, metav1.CreateOptions{}) + if err != nil { + return knerrors.GetError(err) + } + + return nil +} + +//UpdateContainerSource is used to update an instance of ContainerSource +func (c *containerSourcesClient) UpdateContainerSource(containerSrc *v1alpha2.ContainerSource) error { + _, err := c.client.Update(context.TODO(), containerSrc, metav1.UpdateOptions{}) + if err != nil { + return knerrors.GetError(err) + } + + return nil +} + +//DeleteContainerSource is used to create an instance of ContainerSource +func (c *containerSourcesClient) DeleteContainerSource(name string) error { + return c.client.Delete(context.TODO(), name, metav1.DeleteOptions{}) +} + +// Return the client's namespace +func (c *containerSourcesClient) Namespace() string { + return c.namespace +} + +// ListContainerSource returns the available container sources +func (c *containerSourcesClient) ListContainerSources() (*v1alpha2.ContainerSourceList, error) { + sourceList, err := c.client.List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, knerrors.GetError(err) + } + + containerListNew := sourceList.DeepCopy() + err = updateContainerSourceGvk(containerListNew) + if err != nil { + return nil, err + } + + containerListNew.Items = make([]v1alpha2.ContainerSource, len(sourceList.Items)) + for idx, binding := range sourceList.Items { + bindingClone := binding.DeepCopy() + err := updateSinkBindingGvk(bindingClone) + if err != nil { + return nil, err + } + containerListNew.Items[idx] = *bindingClone + } + + return containerListNew, nil +} + +// update with the v1alpha2 group + version +func updateContainerSourceGvk(obj runtime.Object) error { + return util.UpdateGroupVersionKindWithScheme(obj, v1alpha2.SchemeGroupVersion, scheme.Scheme) +} + +// ContainerSourceBuilder is for building the source +type ContainerSourceBuilder struct { + ContainerSource *v1alpha2.ContainerSource +} + +// NewContainerSourceBuilder for building Container source object +func NewContainerSourceBuilder(name string) *ContainerSourceBuilder { + return &ContainerSourceBuilder{ContainerSource: &v1alpha2.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + }} +} + +// NewContainerSourceBuilderFromExisting for building the object from existing ContainerSource object +func NewContainerSourceBuilderFromExisting(ContainerSource *v1alpha2.ContainerSource) *ContainerSourceBuilder { + return &ContainerSourceBuilder{ContainerSource: ContainerSource.DeepCopy()} +} + +// Sink or destination of the source +func (b *ContainerSourceBuilder) Sink(sink duckv1.Destination) *ContainerSourceBuilder { + b.ContainerSource.Spec.Sink = sink + return b +} + +// Build the ContainerSource object +func (b *ContainerSourceBuilder) Build() *v1alpha2.ContainerSource { + return b.ContainerSource +} + +// PodSpec defines the PodSpec +func (b *ContainerSourceBuilder) PodSpec(podSpec corev1.PodSpec) *ContainerSourceBuilder { + b.ContainerSource.Spec.Template.Spec = podSpec + return b +} diff --git a/pkg/sources/v1alpha2/container_client_mock.go b/pkg/sources/v1alpha2/container_client_mock.go new file mode 100644 index 000000000..37eb4f62a --- /dev/null +++ b/pkg/sources/v1alpha2/container_client_mock.go @@ -0,0 +1,122 @@ +/* +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 v1alpha2 + +import ( + "testing" + + "knative.dev/client/pkg/util/mock" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" +) + +// MockKnContainerSourceClient is a combine of test object and recorder +type MockKnContainerSourceClient struct { + t *testing.T + recorder *ConainterSourceRecorder + namespace string +} + +// NewMockKnContainerSourceClient returns a new mock instance which you need to record for +func NewMockKnContainerSourceClient(t *testing.T, ns ...string) *MockKnContainerSourceClient { + namespace := "default" + if len(ns) > 0 { + namespace = ns[0] + } + return &MockKnContainerSourceClient{ + t: t, + recorder: &ConainterSourceRecorder{mock.NewRecorder(t, namespace)}, + namespace: namespace, + } +} + +// Ensure that the interface is implemented +var _ KnContainerSourcesClient = &MockKnContainerSourceClient{} + +// ConainterSourceRecorder is recorder for eventing objects +type ConainterSourceRecorder struct { + r *mock.Recorder +} + +// Recorder returns the recorder for registering API calls +func (c *MockKnContainerSourceClient) Recorder() *ConainterSourceRecorder { + return c.recorder +} + +// Namespace of this client +func (c *MockKnContainerSourceClient) Namespace() string { + return c.recorder.r.Namespace() +} + +// CreateContainerSource records a call for CreateContainerSource with the expected error +func (sr *ConainterSourceRecorder) CreateContainerSource(binding interface{}, err error) { + sr.r.Add("CreateContainerSource", []interface{}{binding}, []interface{}{err}) +} + +// CreateContainerSource performs a previously recorded action +func (c *MockKnContainerSourceClient) CreateContainerSource(binding *v1alpha2.ContainerSource) error { + call := c.recorder.r.VerifyCall("CreateContainerSource", binding) + return mock.ErrorOrNil(call.Result[0]) +} + +// GetContainerSource records a call for GetContainerSource with the expected object or error. Either binding or err should be nil +func (sr *ConainterSourceRecorder) GetContainerSource(name interface{}, binding *v1alpha2.ContainerSource, err error) { + sr.r.Add("GetContainerSource", []interface{}{name}, []interface{}{binding, err}) +} + +// GetContainerSource performs a previously recorded action +func (c *MockKnContainerSourceClient) GetContainerSource(name string) (*v1alpha2.ContainerSource, error) { + call := c.recorder.r.VerifyCall("GetContainerSource", name) + return call.Result[0].(*v1alpha2.ContainerSource), mock.ErrorOrNil(call.Result[1]) +} + +// DeleteContainerSource records a call for DeleteContainerSource with the expected error (nil if none) +func (sr *ConainterSourceRecorder) DeleteContainerSource(name interface{}, err error) { + sr.r.Add("DeleteContainerSource", []interface{}{name}, []interface{}{err}) +} + +// DeleteContainerSource performs a previously recorded action, failing if non has been registered +func (c *MockKnContainerSourceClient) DeleteContainerSource(name string) error { + call := c.recorder.r.VerifyCall("DeleteContainerSource", name) + return mock.ErrorOrNil(call.Result[0]) +} + +// ListContainerSources records a call for ListContainerSources with the expected result and error (nil if none) +func (sr *ConainterSourceRecorder) ListContainerSources(bindingList *v1alpha2.ContainerSourceList, err error) { + sr.r.Add("ListContainerSources", nil, []interface{}{bindingList, err}) +} + +// ListContainerSources performs a previously recorded action +func (c *MockKnContainerSourceClient) ListContainerSources() (*v1alpha2.ContainerSourceList, error) { + call := c.recorder.r.VerifyCall("ListContainerSources") + return call.Result[0].(*v1alpha2.ContainerSourceList), mock.ErrorOrNil(call.Result[1]) +} + +// UpdateContainerSource records a call for ListContainerSources with the expected result and error (nil if none) +func (sr *ConainterSourceRecorder) UpdateContainerSource(binding interface{}, err error) { + sr.r.Add("UpdateContainerSource", []interface{}{binding}, []interface{}{err}) +} + +// UpdateContainerSource performs a previously recorded action +func (c *MockKnContainerSourceClient) UpdateContainerSource(binding *v1alpha2.ContainerSource) error { + call := c.recorder.r.VerifyCall("UpdateContainerSource") + return mock.ErrorOrNil(call.Result[0]) +} + +// Validate validates whether every recorded action has been called +func (sr *ConainterSourceRecorder) Validate() { + sr.r.CheckThatAllRecordedMethodsHaveBeenCalled() +} diff --git a/pkg/sources/v1alpha2/container_client_mock_test.go b/pkg/sources/v1alpha2/container_client_mock_test.go new file mode 100644 index 000000000..2d91b6a48 --- /dev/null +++ b/pkg/sources/v1alpha2/container_client_mock_test.go @@ -0,0 +1,46 @@ +/* +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 v1alpha2 + +import ( + "testing" + + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" +) + +func TestMockKnConatinerSourceClient(t *testing.T) { + client := NewMockKnContainerSourceClient(t) + + recorder := client.Recorder() + + // Record all services + recorder.GetContainerSource("hello", nil, nil) + recorder.CreateContainerSource(&v1alpha2.ContainerSource{}, nil) + recorder.DeleteContainerSource("hello", nil) + recorder.ListContainerSources(nil, nil) + recorder.UpdateContainerSource(&v1alpha2.ContainerSource{}, nil) + + // Call all service + client.GetContainerSource("hello") + client.CreateContainerSource(&v1alpha2.ContainerSource{}) + client.DeleteContainerSource("hello") + client.ListContainerSources() + client.UpdateContainerSource(&v1alpha2.ContainerSource{}) + + // Validate + recorder.Validate() +} diff --git a/pkg/sources/v1alpha2/container_client_test.go b/pkg/sources/v1alpha2/container_client_test.go new file mode 100644 index 000000000..8db1c691a --- /dev/null +++ b/pkg/sources/v1alpha2/container_client_test.go @@ -0,0 +1,143 @@ +/* +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 v1alpha2 + +import ( + "fmt" + "testing" + + "gotest.tools/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + clienttesting "k8s.io/client-go/testing" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + fake "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func setupFakeContainerSourcesClient() (fakeSvr fake.FakeSourcesV1alpha2, client KnContainerSourcesClient) { + fakeE := fake.FakeSourcesV1alpha2{Fake: &clienttesting.Fake{}} + cli := NewKnSourcesClient(&fakeE, "test-ns").ContainerSourcesClient() + return fakeE, cli +} + +func TestDeleteContainerSourceSource(t *testing.T) { + sourcesServer, client := setupFakeContainerSourcesClient() + + sourcesServer.AddReactor("delete", "containersources", + func(a clienttesting.Action) (bool, runtime.Object, error) { + name := a.(clienttesting.DeleteAction).GetName() + fmt.Printf("name=%s \n", name) + if name == "errorSource" { + return true, nil, fmt.Errorf("error while deleting ContainerSource source %s", name) + } + return true, nil, nil + }) + + err := client.DeleteContainerSource("foo") + assert.NilError(t, err) + + err = client.DeleteContainerSource("errorSource") + assert.ErrorContains(t, err, "errorSource") +} + +func TestCreateContainerSourceSource(t *testing.T) { + sourcesServer, client := setupFakeContainerSourcesClient() + + sourcesServer.AddReactor("create", "containersources", + func(a clienttesting.Action) (bool, runtime.Object, error) { + newSource := a.(clienttesting.CreateAction).GetObject() + name := newSource.(metav1.Object).GetName() + if name == "errorSource" { + return true, nil, fmt.Errorf("error while creating ContainerSource source %s", name) + } + return true, newSource, nil + }) + err := client.CreateContainerSource(newContainerSource("foo", "Event")) + assert.NilError(t, err) + + err = client.CreateContainerSource(newContainerSource("errorSource", "Event")) + assert.ErrorContains(t, err, "errorSource") + +} + +func TestGetContainerSource(t *testing.T) { + sourcesServer, client := setupFakeContainerSourcesClient() + + sourcesServer.AddReactor("get", "containersources", + func(a clienttesting.Action) (bool, runtime.Object, error) { + name := a.(clienttesting.GetAction).GetName() + if name == "errorSource" { + return true, nil, fmt.Errorf("error while getting Container source %s", name) + } + return true, newContainerSource(name, "Event"), nil + }) + testsource, err := client.GetContainerSource("foo") + assert.NilError(t, err) + assert.Equal(t, testsource.Name, "foo") + assert.Equal(t, testsource.Spec.Sink.Ref.Name, "foosvc") + + _, err = client.GetContainerSource("errorSource") + assert.ErrorContains(t, err, "errorSource") +} + +func TestUpdateContainerSource(t *testing.T) { + sourcesServer, client := setupFakeContainerSourcesClient() + + sourcesServer.AddReactor("update", "containersources", + func(a clienttesting.Action) (bool, runtime.Object, error) { + updatedSource := a.(clienttesting.UpdateAction).GetObject() + name := updatedSource.(metav1.Object).GetName() + if name == "errorSource" { + return true, nil, fmt.Errorf("error while updating Container source %s", name) + } + return true, NewContainerSourceBuilderFromExisting(updatedSource.(*v1alpha2.ContainerSource)).Build(), nil + }) + err := client.UpdateContainerSource(newContainerSource("foo", "Event")) + assert.NilError(t, err) + + err = client.UpdateContainerSource(newContainerSource("errorSource", "Event")) + assert.ErrorContains(t, err, "errorSource") +} + +func TestListContainerSource(t *testing.T) { + sourcesServer, client := setupFakeContainerSourcesClient() + + sourcesServer.AddReactor("list", "containersources", + func(a clienttesting.Action) (bool, runtime.Object, error) { + cJSource := newContainerSource("testsource", "Event") + return true, &v1alpha2.ContainerSourceList{Items: []v1alpha2.ContainerSource{*cJSource}}, nil + }) + + sourceList, err := client.ListContainerSources() + assert.NilError(t, err) + assert.Equal(t, len(sourceList.Items), 1) +} + +func newContainerSource(name, container string) *v1alpha2.ContainerSource { + b := NewContainerSourceBuilder(name). + PodSpec(corev1.PodSpec{}). + Sink(duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Name: "foosvc", + Namespace: "default", + }}) + + return b.Build() +} diff --git a/test/e2e/source_container_test.go b/test/e2e/source_container_test.go new file mode 100644 index 000000000..a50786595 --- /dev/null +++ b/test/e2e/source_container_test.go @@ -0,0 +1,103 @@ +// Copyright 2019 The Knative Authors + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 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" + pkgtest "knative.dev/pkg/test" +) + +func TestSourceContainer(t *testing.T) { + t.Parallel() + it, err := test.NewKnTest() + assert.NilError(t, err) + defer func() { + err := it.Teardown() + assert.NilError(t, err) + }() + + r := test.NewKnRunResultCollector(t, it) + defer r.DumpIfFailed() + + test.ServiceCreate(r, "testsvc0") + + t.Log("create container source with a sink to a service") + containerSourceCreate(r, "testsource0", "ksvc:testsvc0") + containerSourceListOutputName(r, "testsource0") + + t.Log("list container sources") + containerSourceList(r, "testsource0") + + t.Log("delete container sources") + containerSourceDelete(r, "testsource0") + + t.Log("create container source with a missing sink service") + containerSourceCreateMissingSink(r, "testsource2", "ksvc:unknown") + + t.Log("update container source sink service") + containerSourceCreate(r, "testsource3", "ksvc:testsvc0") + test.ServiceCreate(r, "testsvc1") + containerSourceUpdateSink(r, "testsource3", "ksvc:testsvc1") + jpSinkRefNameInSpec := "jsonpath={.spec.sink.ref.name}" + out, err := test.GetResourceFieldsWithJSONPath(t, it, "containersource.sources.knative.dev", "testsource3", jpSinkRefNameInSpec) + assert.NilError(t, err) + assert.Equal(t, out, "testsvc1") +} + +func containerSourceCreate(r *test.KnRunResultCollector, sourceName string, sink string) { + out := r.KnTest().Kn().Run("source", "container", "create", sourceName, "--image", pkgtest.ImagePath("grpc-ping"), "--port", "h2c:8080", "--sink", sink) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "container", "source", sourceName, "created", "namespace", r.KnTest().Kn().Namespace())) +} + +func containerSourceListOutputName(r *test.KnRunResultCollector, containerSources ...string) { + out := r.KnTest().Kn().Run("source", "container", "list", "--output", "name") + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAll(out.Stdout, containerSources...)) +} + +func containerSourceList(r *test.KnRunResultCollector, containerSources ...string) { + out := r.KnTest().Kn().Run("source", "container", "list") + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAll(out.Stdout, "NAME", "IMAGE", "SINK", "READY")) + assert.Check(r.T(), util.ContainsAll(out.Stdout, containerSources...)) + assert.Check(r.T(), util.ContainsAll(out.Stdout, "grpc-ping", "ksvc:testsvc0")) +} + +func containerSourceCreateMissingSink(r *test.KnRunResultCollector, sourceName string, sink string) { + out := r.KnTest().Kn().Run("source", "container", "create", sourceName, "--image", pkgtest.ImagePath("grpc-ping"), "--port", "h2c:8080", "--sink", sink) + r.AssertError(out) + assert.Check(r.T(), util.ContainsAll(out.Stderr, "services.serving.knative.dev", "not found")) +} + +func containerSourceDelete(r *test.KnRunResultCollector, sourceName string) { + out := r.KnTest().Kn().Run("source", "container", "delete", sourceName) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "container", "source", sourceName, "deleted", "namespace", r.KnTest().Kn().Namespace())) +} + +func containerSourceUpdateSink(r *test.KnRunResultCollector, sourceName string, sink string) { + out := r.KnTest().Kn().Run("source", "container", "update", sourceName, "--sink", sink) + r.AssertNoError(out) + assert.Check(r.T(), util.ContainsAll(out.Stdout, sourceName, "updated", "namespace", r.KnTest().Kn().Namespace())) +}