Add command group for ContainerSource (#1016)

* add container source create and delete

* add update and list commands

* add describe command

* add e2e test

* add more tests

* changes to address review comments
This commit is contained in:
Ying Chun Guo 2020-12-02 21:56:51 +08:00 committed by GitHub
parent fb594e76c5
commit 45162fb5d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1935 additions and 1 deletions

View File

@ -29,6 +29,7 @@ kn source SOURCE|COMMAND
* [kn](kn.md) - kn manages Knative Serving and Eventing resources * [kn](kn.md) - kn manages Knative Serving and Eventing resources
* [kn source apiserver](kn_source_apiserver.md) - Manage Kubernetes api-server sources * [kn source apiserver](kn_source_apiserver.md) - Manage Kubernetes api-server sources
* [kn source binding](kn_source_binding.md) - Manage sink bindings * [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](kn_source_list.md) - List event sources
* [kn source list-types](kn_source_list-types.md) - List event source types * [kn source list-types](kn_source_list-types.md) - List event source types
* [kn source ping](kn_source_ping.md) - Manage ping sources * [kn source ping](kn_source_ping.md) - Manage ping sources

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,105 @@
/*
Copyright 2020 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package 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
}

View File

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

View File

@ -20,6 +20,7 @@ import (
"knative.dev/client/pkg/kn/commands" "knative.dev/client/pkg/kn/commands"
"knative.dev/client/pkg/kn/commands/source/apiserver" "knative.dev/client/pkg/kn/commands/source/apiserver"
"knative.dev/client/pkg/kn/commands/source/binding" "knative.dev/client/pkg/kn/commands/source/binding"
"knative.dev/client/pkg/kn/commands/source/container"
"knative.dev/client/pkg/kn/commands/source/ping" "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(apiserver.NewAPIServerCommand(p))
sourceCmd.AddCommand(ping.NewPingCommand(p)) sourceCmd.AddCommand(ping.NewPingCommand(p))
sourceCmd.AddCommand(binding.NewBindingCommand(p)) sourceCmd.AddCommand(binding.NewBindingCommand(p))
sourceCmd.AddCommand(container.NewContainerCommand(p))
return sourceCmd return sourceCmd
} }

View File

@ -153,7 +153,6 @@ func (p *PodSpecFlags) AddFlags(flagset *pflag.FlagSet) []string {
// ResolvePodSpec will create corev1.PodSpec based on the flag inputs // ResolvePodSpec will create corev1.PodSpec based on the flag inputs
func (p *PodSpecFlags) ResolvePodSpec(podSpec *corev1.PodSpec, flags *pflag.FlagSet) error { func (p *PodSpecFlags) ResolvePodSpec(podSpec *corev1.PodSpec, flags *pflag.FlagSet) error {
var err error var err error
if flags.Changed("env") { if flags.Changed("env") {
envMap, err := util.MapFromArrayAllowingSingles(p.Env, "=") envMap, err := util.MapFromArrayAllowingSingles(p.Env, "=")
if err != nil { if err != nil {

View File

@ -31,6 +31,9 @@ type KnSourcesClient interface {
// Get client for ApiServer sources // Get client for ApiServer sources
APIServerSourcesClient() KnAPIServerSourcesClient APIServerSourcesClient() KnAPIServerSourcesClient
// Get client for container sources
ContainerSourcesClient() KnContainerSourcesClient
} }
// sourcesClient is a combination of Sources client interface and namespace // 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) 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 // BuiltInSourcesGVKs returns the GVKs for built in sources
func BuiltInSourcesGVKs() []schema.GroupVersionKind { func BuiltInSourcesGVKs() []schema.GroupVersionKind {
return []schema.GroupVersionKind{ return []schema.GroupVersionKind{

View File

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

View File

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

View File

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

View File

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

View File

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