Add ApiServer source update and describe commands (#556)

* Dont return created ApiServer source object but only error

 - After ApiServer source object is created, we don't need it to pass
   around in caller function.

* Align creating ApiServer source client, removes unit tests

* Add ApiServer source update command

* Rename TestMockKnClient to TestMockKnCronJobSourceClient

* Add mock client for ApiServer Source and its tests

* Add mock unit tests for create, delete and update

* Add e2e tests for apiserver source update

 - Add a test for apiserver source sink update
 - Verify the updated sink name after the apiserver source is created
 - Update resource names in existing tests

* Uses builder pattern for ApiServer source create command

* Update ApiServer source create/update flags and required config

* Uses builder pattern for ApiServer source update command

* Align create/update/delete description and error messages

* Add unit tests for get/create/update/delete in apiserver_client.go

* Update e2e tests expected output per change in commands output

* Golint fixes, Api -> API and add exported method docs

* Rename a test method and source update command description

* Add ApiServer source describe command

 - Add command and unit tests
 - TODO for later: Add 'Controller Selector' section for --verbose
This commit is contained in:
Navid Shaikh 2019-12-16 23:12:02 +05:30 committed by Knative Prow Robot
parent 7def9f49eb
commit 6ac25cdc00
26 changed files with 1069 additions and 291 deletions

View File

@ -27,6 +27,8 @@ kn source apiserver [flags]
### SEE ALSO
* [kn source](kn_source.md) - Event source command group
* [kn source apiserver create](kn_source_apiserver_create.md) - Create an ApiServerSource, which watches for Kubernetes events and forwards them to a sink
* [kn source apiserver delete](kn_source_apiserver_delete.md) - Delete an ApiServerSource.
* [kn source apiserver create](kn_source_apiserver_create.md) - Create an ApiServer source.
* [kn source apiserver delete](kn_source_apiserver_delete.md) - Delete an ApiServer source.
* [kn source apiserver describe](kn_source_apiserver_describe.md) - Describe an ApiServer source.
* [kn source apiserver update](kn_source_apiserver_update.md) - Update an ApiServer source.

View File

@ -1,10 +1,10 @@
## kn source apiserver create
Create an ApiServerSource, which watches for Kubernetes events and forwards them to a sink
Create an ApiServer source.
### Synopsis
Create an ApiServerSource, which watches for Kubernetes events and forwards them to a sink
Create an ApiServer source.
```
kn source apiserver create NAME --resource RESOURCE --service-account ACCOUNTNAME --sink SINK --mode MODE [flags]
@ -13,7 +13,7 @@ kn source apiserver create NAME --resource RESOURCE --service-account ACCOUNTNAM
### Examples
```
# Create an ApiServerSource 'k8sevents' which consumes Kubernetes events and sends message to service 'mysvc' as a cloudevent
kn source apiserver create k8sevents --resource Event --service-account myaccountname --sink svc:mysvc
```
@ -28,7 +28,7 @@ kn source apiserver create NAME --resource RESOURCE --service-account ACCOUNTNAM
-n, --namespace string Specify the namespace to operate in.
--resource strings Comma seperate Kind:APIVersion:isController list, e.g. Event:v1:true.
"APIVersion" and "isControler" can be omitted.
"APIVersion" is "v1" by default, "isController" is "false" by default.
"APIVersion" is "v1" by default, "isController" is "false" by default.
--service-account string Name of the service account to use to run this source
-s, --sink string Addressable sink for events
```

View File

@ -1,10 +1,10 @@
## kn source apiserver delete
Delete an ApiServerSource.
Delete an ApiServer source.
### Synopsis
Delete an ApiServerSource.
Delete an ApiServer source.
```
kn source apiserver delete NAME [flags]

View File

@ -0,0 +1,40 @@
## kn source apiserver describe
Describe an ApiServer source.
### Synopsis
Describe an ApiServer source.
```
kn source apiserver describe NAME [flags]
```
### Examples
```
# Describe an ApiServer source with name 'k8sevents'
kn source apiserver 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 config file (default is $HOME/.kn/config.yaml)
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
--log-http log http traffic
```
### SEE ALSO
* [kn source apiserver](kn_source_apiserver.md) - Kubernetes API Server Event Source command group

View File

@ -0,0 +1,47 @@
## kn source apiserver update
Update an ApiServer source.
### Synopsis
Update an ApiServer source.
```
kn source apiserver update NAME --resource RESOURCE --service-account ACCOUNTNAME --sink SINK --mode MODE [flags]
```
### Examples
```
# Update an ApiServerSource 'k8sevents' with different service account and sink service
kn source apiserver update k8sevents --service-account newsa --sink svc:newsvc
```
### Options
```
-h, --help help for update
--mode string The mode the receive adapter controller runs under:,
"Ref" sends only the reference to the resource,
"Resource" send the full resource. (default "Ref")
-n, --namespace string Specify the namespace to operate in.
--resource strings Comma seperate Kind:APIVersion:isController list, e.g. Event:v1:true.
"APIVersion" and "isControler" can be omitted.
"APIVersion" is "v1" by default, "isController" is "false" by default.
--service-account string Name of the service account to use to run this source
-s, --sink string Addressable sink for events
```
### Options inherited from parent commands
```
--config string kn config file (default is $HOME/.kn/config.yaml)
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
--log-http log http traffic
```
### SEE ALSO
* [kn source apiserver](kn_source_apiserver.md) - Kubernetes API Server Event Source command group

View File

@ -15,21 +15,27 @@
package v1alpha1
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kn_errors "knative.dev/client/pkg/errors"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
client_v1alpha1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha1"
kn_errors "knative.dev/client/pkg/errors"
duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
)
// Interface for working with ApiServer sources
type KnApiServerSourcesClient interface {
// KnAPIServerSourcesClient interface for working with ApiServer sources
type KnAPIServerSourcesClient interface {
// Get an ApiServerSource by object
CreateApiServerSource(apisvrsrc *v1alpha1.ApiServerSource) (*v1alpha1.ApiServerSource, error)
// Get an ApiServerSource by name
GetAPIServerSource(name string) (*v1alpha1.ApiServerSource, error)
// Create an ApiServerSource by object
CreateAPIServerSource(apiSource *v1alpha1.ApiServerSource) error
// Update an ApiServerSource by object
UpdateAPIServerSource(apiSource *v1alpha1.ApiServerSource) error
// Delete an ApiServerSource by name
DeleteApiServerSource(name string) error
DeleteAPIServerSource(name string) error
// Get namespace for this client
Namespace() string
@ -43,26 +49,47 @@ type apiServerSourcesClient struct {
namespace string
}
// NewKnSourcesClient is to invoke Eventing Sources Client API to create object
func newKnApiServerSourcesClient(client client_v1alpha1.ApiServerSourceInterface, namespace string) KnApiServerSourcesClient {
// newKnAPIServerSourcesClient is to invoke Eventing Sources Client API to create object
func newKnAPIServerSourcesClient(client client_v1alpha1.ApiServerSourceInterface, namespace string) KnAPIServerSourcesClient {
return &apiServerSourcesClient{
client: client,
namespace: namespace,
}
}
//CreateApiServerSource is used to create an instance of ApiServerSource
func (c *apiServerSourcesClient) CreateApiServerSource(apisvrsrc *v1alpha1.ApiServerSource) (*v1alpha1.ApiServerSource, error) {
ins, err := c.client.Create(apisvrsrc)
//GetAPIServerSource returns apiSource object if present
func (c *apiServerSourcesClient) GetAPIServerSource(name string) (*v1alpha1.ApiServerSource, error) {
apiSource, err := c.client.Get(name, metav1.GetOptions{})
if err != nil {
return nil, kn_errors.GetError(err)
}
return ins, nil
return apiSource, nil
}
//DeleteApiServerSource is used to create an instance of ApiServerSource
func (c *apiServerSourcesClient) DeleteApiServerSource(name string) error {
err := c.client.Delete(name, &v1.DeleteOptions{})
//CreateAPIServerSource is used to create an instance of ApiServerSource
func (c *apiServerSourcesClient) CreateAPIServerSource(apiSource *v1alpha1.ApiServerSource) error {
_, err := c.client.Create(apiSource)
if err != nil {
return kn_errors.GetError(err)
}
return nil
}
//UpdateAPIServerSource is used to update an instance of ApiServerSource
func (c *apiServerSourcesClient) UpdateAPIServerSource(apiSource *v1alpha1.ApiServerSource) error {
_, err := c.client.Update(apiSource)
if err != nil {
return kn_errors.GetError(err)
}
return nil
}
//DeleteAPIServerSource is used to create an instance of ApiServerSource
func (c *apiServerSourcesClient) DeleteAPIServerSource(name string) error {
err := c.client.Delete(name, &metav1.DeleteOptions{})
return err
}
@ -70,3 +97,51 @@ func (c *apiServerSourcesClient) DeleteApiServerSource(name string) error {
func (c *apiServerSourcesClient) Namespace() string {
return c.namespace
}
// APIServerSourceBuilder is for building the source
type APIServerSourceBuilder struct {
apiServerSource *v1alpha1.ApiServerSource
}
// NewAPIServerSourceBuilder for building ApiServer source object
func NewAPIServerSourceBuilder(name string) *APIServerSourceBuilder {
return &APIServerSourceBuilder{apiServerSource: &v1alpha1.ApiServerSource{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}}
}
// NewAPIServerSourceBuilderFromExisting for building the object from existing ApiServerSource object
func NewAPIServerSourceBuilderFromExisting(apiServerSource *v1alpha1.ApiServerSource) *APIServerSourceBuilder {
return &APIServerSourceBuilder{apiServerSource: apiServerSource.DeepCopy()}
}
// Resources which should be streamed
func (b *APIServerSourceBuilder) Resources(resources []v1alpha1.ApiServerResource) *APIServerSourceBuilder {
b.apiServerSource.Spec.Resources = resources
return b
}
// ServiceAccount with which this source should operate
func (b *APIServerSourceBuilder) ServiceAccount(sa string) *APIServerSourceBuilder {
b.apiServerSource.Spec.ServiceAccountName = sa
return b
}
// Mode for whether to send resource 'Ref' or complete 'Resource'
func (b *APIServerSourceBuilder) Mode(mode string) *APIServerSourceBuilder {
b.apiServerSource.Spec.Mode = mode
return b
}
// Sink or destination of the source
func (b *APIServerSourceBuilder) Sink(sink *duckv1beta1.Destination) *APIServerSourceBuilder {
b.apiServerSource.Spec.Sink = sink
return b
}
// Build the ApiServerSource object
func (b *APIServerSourceBuilder) Build() *v1alpha1.ApiServerSource {
return b.apiServerSource
}

View File

@ -0,0 +1,109 @@
// 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 implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v1alpha1
import (
"testing"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
"knative.dev/client/pkg/util/mock"
)
// MockKnAPIServerSourceClient for mocking the client
type MockKnAPIServerSourceClient struct {
t *testing.T
recorder *APIServerSourcesRecorder
namespace string
}
// NewMockKnAPIServerSourceClient returns a new mock instance which you need to record for
func NewMockKnAPIServerSourceClient(t *testing.T, ns ...string) *MockKnAPIServerSourceClient {
namespace := "default"
if len(ns) > 0 {
namespace = ns[0]
}
return &MockKnAPIServerSourceClient{
t: t,
recorder: &APIServerSourcesRecorder{mock.NewRecorder(t, namespace)},
}
}
// Ensure that the interface is implemented
var _ KnAPIServerSourcesClient = &MockKnAPIServerSourceClient{}
// APIServerSourcesRecorder for recording actions on source
type APIServerSourcesRecorder struct {
r *mock.Recorder
}
// Recorder returns the recorder for registering API calls
func (c *MockKnAPIServerSourceClient) Recorder() *APIServerSourcesRecorder {
return c.recorder
}
// Namespace of this client
func (c *MockKnAPIServerSourceClient) Namespace() string {
return c.recorder.r.Namespace()
}
// GetAPIServerSource records a call for GetApiServerSource with the expected object or error. Either apiServerSource or err should be nil
func (sr *APIServerSourcesRecorder) GetAPIServerSource(name interface{}, apiServerSource *v1alpha1.ApiServerSource, err error) {
sr.r.Add("GetApiServerSource", []interface{}{name}, []interface{}{apiServerSource, err})
}
// GetAPIServerSource performs a previously recorded action, failing if non has been registered
func (c *MockKnAPIServerSourceClient) GetAPIServerSource(name string) (*v1alpha1.ApiServerSource, error) {
call := c.recorder.r.VerifyCall("GetApiServerSource", name)
return call.Result[0].(*v1alpha1.ApiServerSource), mock.ErrorOrNil(call.Result[1])
}
// CreateAPIServerSource records a call for CreateApiServerSource with the expected error
func (sr *APIServerSourcesRecorder) CreateAPIServerSource(apiServerSource interface{}, err error) {
sr.r.Add("CreateApiServerSource", []interface{}{apiServerSource}, []interface{}{err})
}
// CreateAPIServerSource performs a previously recorded action, failing if non has been registered
func (c *MockKnAPIServerSourceClient) CreateAPIServerSource(apiServerSource *v1alpha1.ApiServerSource) error {
call := c.recorder.r.VerifyCall("CreateApiServerSource", apiServerSource)
return mock.ErrorOrNil(call.Result[0])
}
// UpdateAPIServerSource records a call for UpdateAPIServerSource with the expected error (nil if none)
func (sr *APIServerSourcesRecorder) UpdateAPIServerSource(apiServerSource interface{}, err error) {
sr.r.Add("UpdateAPIServerSource", []interface{}{apiServerSource}, []interface{}{err})
}
// UpdateAPIServerSource performs a previously recorded action, failing if non has been registered
func (c *MockKnAPIServerSourceClient) UpdateAPIServerSource(apiServerSource *v1alpha1.ApiServerSource) error {
call := c.recorder.r.VerifyCall("UpdateAPIServerSource", apiServerSource)
return mock.ErrorOrNil(call.Result[0])
}
// DeleteAPIServerSource records a call for DeleteAPIServerSource with the expected error (nil if none)
func (sr *APIServerSourcesRecorder) DeleteAPIServerSource(name interface{}, err error) {
sr.r.Add("DeleteAPIServerSource", []interface{}{name}, []interface{}{err})
}
// DeleteAPIServerSource performs a previously recorded action, failing if non has been registered
func (c *MockKnAPIServerSourceClient) DeleteAPIServerSource(name string) error {
call := c.recorder.r.VerifyCall("DeleteAPIServerSource", name)
return mock.ErrorOrNil(call.Result[0])
}
// Validate validates whether every recorded action has been called
func (sr *APIServerSourcesRecorder) Validate() {
sr.r.CheckThatAllRecordedMethodsHaveBeenCalled()
}

View File

@ -0,0 +1,43 @@
// 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 implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v1alpha1
import (
"testing"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
)
func TestMockKnAPIServerSourceClient(t *testing.T) {
client := NewMockKnAPIServerSourceClient(t)
recorder := client.Recorder()
// Record all services
recorder.GetAPIServerSource("hello", nil, nil)
recorder.CreateAPIServerSource(&v1alpha1.ApiServerSource{}, nil)
recorder.UpdateAPIServerSource(&v1alpha1.ApiServerSource{}, nil)
recorder.DeleteAPIServerSource("hello", nil)
// Call all service
client.GetAPIServerSource("hello")
client.CreateAPIServerSource(&v1alpha1.ApiServerSource{})
client.UpdateAPIServerSource(&v1alpha1.ApiServerSource{})
client.DeleteAPIServerSource("hello")
// Validate
recorder.Validate()
}

View File

@ -19,90 +19,117 @@ import (
"testing"
"gotest.tools/assert"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
client_testing "k8s.io/client-go/testing"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
"knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake"
"knative.dev/pkg/apis/duck/v1beta1"
)
var testApiServerSourceNamespace = "test-ns"
var testAPIServerSourceNamespace = "test-ns"
func setupApiServerSourcesClient() (fakeSources fake.FakeSourcesV1alpha1, client KnApiServerSourcesClient) {
func setupAPIServerSourcesClient(t *testing.T) (fakeSources fake.FakeSourcesV1alpha1, client KnAPIServerSourcesClient) {
fakeSources = fake.FakeSourcesV1alpha1{Fake: &client_testing.Fake{}}
client = NewKnSourcesClient(&fakeSources, testApiServerSourceNamespace).ApiServerSourcesClient()
client = NewKnSourcesClient(&fakeSources, testAPIServerSourceNamespace).APIServerSourcesClient()
assert.Equal(t, client.Namespace(), testAPIServerSourceNamespace)
return
}
func TestDeleteApiServerSource(t *testing.T) {
var srcName = "new-src"
sourcesServer, client := setupApiServerSourcesClient()
sourcesServer, client := setupAPIServerSourcesClient(t)
apisourceNew := newApiServerSource(srcName)
sourcesServer.AddReactor("create", "apiserversources",
sourcesServer.AddReactor("delete", "apiserversources",
func(a client_testing.Action) (bool, runtime.Object, error) {
assert.Equal(t, testApiServerSourceNamespace, a.GetNamespace())
name := a.(client_testing.CreateAction).GetObject().(metav1.Object).GetName()
if name == apisourceNew.Name {
apisourceNew.Generation = 2
return true, apisourceNew, nil
name := a.(client_testing.DeleteAction).GetName()
if name == "errorSource" {
return true, nil, fmt.Errorf("error while deleting ApiServer source %s", name)
}
return true, nil, fmt.Errorf("error while creating apiserversource %s", name)
return true, nil, nil
})
t.Run("create apiserversource without error", func(t *testing.T) {
ins, err := client.CreateApiServerSource(apisourceNew)
assert.NilError(t, err)
assert.Equal(t, ins.Name, srcName)
assert.Equal(t, ins.Namespace, testApiServerSourceNamespace)
})
err := client.DeleteAPIServerSource("foo")
assert.NilError(t, err)
t.Run("create apiserversource with an error returns an error object", func(t *testing.T) {
_, err := client.CreateApiServerSource(newApiServerSource("unknown"))
assert.ErrorContains(t, err, "unknown")
})
err = client.DeleteAPIServerSource("errorSource")
assert.ErrorContains(t, err, "errorSource")
}
func TestCreateApiServerSource(t *testing.T) {
var srcName = "new-src"
sourcesServer, client := setupApiServerSourcesClient()
apisourceNew := newApiServerSource(srcName)
sourcesServer, client := setupAPIServerSourcesClient(t)
sourcesServer.AddReactor("create", "apiserversources",
func(a client_testing.Action) (bool, runtime.Object, error) {
assert.Equal(t, testApiServerSourceNamespace, a.GetNamespace())
name := a.(client_testing.CreateAction).GetObject().(metav1.Object).GetName()
if name == apisourceNew.Name {
apisourceNew.Generation = 2
return true, apisourceNew, nil
newSource := a.(client_testing.CreateAction).GetObject()
name := newSource.(metav1.Object).GetName()
if name == "errorSource" {
return true, nil, fmt.Errorf("error while creating ApiServer source %s", name)
}
return true, nil, fmt.Errorf("error while creating apiserversource %s", name)
return true, newSource, nil
})
err := client.CreateAPIServerSource(newAPIServerSource("foo", "Event"))
assert.NilError(t, err)
t.Run("create apiserversource without error", func(t *testing.T) {
ins, err := client.CreateApiServerSource(apisourceNew)
assert.NilError(t, err)
assert.Equal(t, ins.Name, srcName)
assert.Equal(t, ins.Namespace, testApiServerSourceNamespace)
})
err = client.CreateAPIServerSource(newAPIServerSource("errorSource", "Event"))
assert.ErrorContains(t, err, "errorSource")
t.Run("create apiserversource with an error returns an error object", func(t *testing.T) {
_, err := client.CreateApiServerSource(newApiServerSource("unknown"))
assert.ErrorContains(t, err, "unknown")
})
}
func newApiServerSource(name string) *v1alpha1.ApiServerSource {
src := &v1alpha1.ApiServerSource{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: testApiServerSourceNamespace,
},
func TestGetApiServerSource(t *testing.T) {
sourcesServer, client := setupAPIServerSourcesClient(t)
sourcesServer.AddReactor("get", "apiserversources",
func(a client_testing.Action) (bool, runtime.Object, error) {
name := a.(client_testing.GetAction).GetName()
if name == "errorSource" {
return true, nil, fmt.Errorf("error while getting ApiServer source %s", name)
}
return true, newAPIServerSource(name, "Event"), nil
})
testsource, err := client.GetAPIServerSource("foo")
assert.NilError(t, err)
assert.Equal(t, testsource.Name, "foo")
assert.Equal(t, testsource.Spec.Sink.Ref.Name, "foosvc")
_, err = client.GetAPIServerSource("errorSource")
assert.ErrorContains(t, err, "errorSource")
}
func TestUpdateApiServerSource(t *testing.T) {
sourcesServer, client := setupAPIServerSourcesClient(t)
sourcesServer.AddReactor("update", "apiserversources",
func(a client_testing.Action) (bool, runtime.Object, error) {
updatedSource := a.(client_testing.UpdateAction).GetObject()
name := updatedSource.(metav1.Object).GetName()
if name == "errorSource" {
return true, nil, fmt.Errorf("error while updating ApiServer source %s", name)
}
return true, NewAPIServerSourceBuilderFromExisting(updatedSource.(*v1alpha1.ApiServerSource)).Build(), nil
})
err := client.UpdateAPIServerSource(newAPIServerSource("foo", "Event"))
assert.NilError(t, err)
err = client.UpdateAPIServerSource(newAPIServerSource("errorSource", "Event"))
assert.ErrorContains(t, err, "errorSource")
}
func newAPIServerSource(name, resource string) *v1alpha1.ApiServerSource {
b := NewAPIServerSourceBuilder(name).ServiceAccount("testsa").Mode("Ref")
b.Sink(&v1beta1.Destination{
Ref: &v1.ObjectReference{
Kind: "Service",
Name: "foosvc",
}})
if resource != "" {
b.Resources([]v1alpha1.ApiServerResource{{
APIVersion: "v1",
Kind: resource,
Controller: false,
}})
}
src.Name = name
src.Namespace = testApiServerSourceNamespace
return src
return b.Build()
}

View File

@ -22,7 +22,7 @@ import (
// namespace specified during construction
type KnSourcesClient interface {
// Get client for ApiServer sources
ApiServerSourcesClient() KnApiServerSourcesClient
APIServerSourcesClient() KnAPIServerSourcesClient
// Get client for CronJob sources
CronJobSourcesClient() KnCronJobSourcesClient
@ -36,7 +36,7 @@ type sourcesClient struct {
namespace string
}
// Create a new client for managing all eventing built-in sources
// NewKnSourcesClient for managing all eventing built-in sources
func NewKnSourcesClient(client client_v1alpha1.SourcesV1alpha1Interface, namespace string) KnSourcesClient {
return &sourcesClient{
client: client,
@ -44,9 +44,9 @@ func NewKnSourcesClient(client client_v1alpha1.SourcesV1alpha1Interface, namespa
}
}
// Get the client for dealing with apiserver sources
func (c *sourcesClient) ApiServerSourcesClient() KnApiServerSourcesClient {
return newKnApiServerSourcesClient(c.client.ApiServerSources(c.namespace), c.namespace)
// ApiServerSourcesClient for dealing with ApiServer sources
func (c *sourcesClient) APIServerSourcesClient() KnAPIServerSourcesClient {
return newKnAPIServerSourcesClient(c.client.ApiServerSources(c.namespace), c.namespace)
}
// Get the client for dealing with cronjob sources

View File

@ -20,7 +20,7 @@ import (
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
)
func TestMockKnClient(t *testing.T) {
func TestMockKnCronJobSourceClient(t *testing.T) {
client := NewMockKnCronJobSourceClient(t)

View File

@ -16,16 +16,52 @@ package apiserver
import (
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
sources_v1alpha1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha1"
knsources_v1alpha1 "knative.dev/client/pkg/eventing/sources/v1alpha1"
"knative.dev/client/pkg/kn/commands"
)
func NewApiServerCommand(p *commands.KnParams) *cobra.Command {
apiServerImporterCmd := &cobra.Command{
// NewAPIServerCommand for managing ApiServer source
func NewAPIServerCommand(p *commands.KnParams) *cobra.Command {
apiServerSourceCmd := &cobra.Command{
Use: "apiserver",
Short: "Kubernetes API Server Event Source command group",
}
apiServerImporterCmd.AddCommand(NewApiServerCreateCommand(p))
apiServerImporterCmd.AddCommand(NewApiServerDeleteCommand(p))
return apiServerImporterCmd
apiServerSourceCmd.AddCommand(NewAPIServerCreateCommand(p))
apiServerSourceCmd.AddCommand(NewAPIServerUpdateCommand(p))
apiServerSourceCmd.AddCommand(NewAPIServerDescribeCommand(p))
apiServerSourceCmd.AddCommand(NewAPIServerDeleteCommand(p))
return apiServerSourceCmd
}
var apiServerSourceClientFactory func(config clientcmd.ClientConfig, namespace string) (knsources_v1alpha1.KnAPIServerSourcesClient, error)
func newAPIServerSourceClient(p *commands.KnParams, cmd *cobra.Command) (knsources_v1alpha1.KnAPIServerSourcesClient, error) {
namespace, err := p.GetNamespace(cmd)
if err != nil {
return nil, err
}
if apiServerSourceClientFactory != nil {
config, err := p.GetClientConfig()
if err != nil {
return nil, err
}
return apiServerSourceClientFactory(config, namespace)
}
clientConfig, err := p.RestConfig()
if err != nil {
return nil, err
}
client, err := sources_v1alpha1.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
return knsources_v1alpha1.NewKnSourcesClient(client, namespace).APIServerSourcesClient(), nil
}

View File

@ -0,0 +1,105 @@
// 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 implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apiserver
import (
"bytes"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/clientcmd"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
knsource_v1alpha1 "knative.dev/client/pkg/eventing/sources/v1alpha1"
"knative.dev/client/pkg/kn/commands"
knserving_v1alpha1 "knative.dev/client/pkg/serving/v1alpha1"
)
const testNamespace = "default"
// Helper methods
var blankConfig clientcmd.ClientConfig
// TOOD: Remove that blankConfig hack for tests in favor of overwriting GetConfig()
func init() {
var err error
blankConfig, err = clientcmd.NewClientConfigFromBytes([]byte(`kind: Config
version: v1
users:
- name: u
clusters:
- name: c
cluster:
server: example.com
contexts:
- name: x
context:
user: u
cluster: c
current-context: x
`))
if err != nil {
panic(err)
}
}
func executeAPIServerSourceCommand(apiServerSourceClient knsource_v1alpha1.KnAPIServerSourcesClient, servingClient knserving_v1alpha1.KnServingClient, args ...string) (string, error) {
knParams := &commands.KnParams{}
knParams.ClientConfig = blankConfig
output := new(bytes.Buffer)
knParams.Output = output
knParams.NewServingClient = func(namespace string) (knserving_v1alpha1.KnServingClient, error) {
return servingClient, nil
}
cmd := NewAPIServerCommand(knParams)
cmd.SetArgs(args)
cmd.SetOutput(output)
apiServerSourceClientFactory = func(config clientcmd.ClientConfig, namespace string) (knsource_v1alpha1.KnAPIServerSourcesClient, error) {
return apiServerSourceClient, nil
}
defer cleanupAPIServerMockClient()
err := cmd.Execute()
return output.String(), err
}
func cleanupAPIServerMockClient() {
apiServerSourceClientFactory = nil
}
func createAPIServerSource(name, resourceKind, resourceVersion, serviceAccount, mode, service string, isController bool) *v1alpha1.ApiServerSource {
resources := []v1alpha1.ApiServerResource{{
APIVersion: resourceVersion,
Kind: resourceKind,
Controller: isController,
}}
sink := &duckv1beta1.Destination{
Ref: &corev1.ObjectReference{
Kind: "Service",
Name: service,
}}
return knsource_v1alpha1.NewAPIServerSourceBuilder(name).
Resources(resources).
ServiceAccount(serviceAccount).
Mode(mode).
Sink(sink).
Build()
}

View File

@ -20,42 +20,38 @@ import (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"knative.dev/client/pkg/eventing/sources/v1alpha1"
"knative.dev/client/pkg/kn/commands"
"knative.dev/client/pkg/kn/commands/flags"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
)
func NewApiServerCreateCommand(p *commands.KnParams) *cobra.Command {
var apiServerUpdateFlags ApiServerSourceUpdateFlags
// NewAPIServerCreateCommand for creating source
func NewAPIServerCreateCommand(p *commands.KnParams) *cobra.Command {
var updateFlags APIServerSourceUpdateFlags
var sinkFlags flags.SinkFlags
cmd := &cobra.Command{
Use: "create NAME --resource RESOURCE --service-account ACCOUNTNAME --sink SINK --mode MODE",
Short: "Create an ApiServerSource, which watches for Kubernetes events and forwards them to a sink",
Example: `
Short: "Create an ApiServer source.",
Example: `
# Create an ApiServerSource 'k8sevents' which consumes Kubernetes events and sends message to service 'mysvc' as a cloudevent
kn source apiserver create k8sevents --resource Event --service-account myaccountname --sink svc:mysvc`,
RunE: func(cmd *cobra.Command, args []string) (err error) {
if len(args) != 1 {
return errors.New("'source apiserver create' requires the name of the source as single argument")
return errors.New("requires the name of the source to create as single argument")
}
name := args[0]
// get namespace
namespace, err := p.GetNamespace(cmd)
if err != nil {
return err
}
// get client
sourcesClient, err := p.NewSourcesClient(namespace)
apiSourceClient, err := newAPIServerSourceClient(p, cmd)
if err != nil {
return err
}
// resolve sink
namespace := apiSourceClient.Namespace()
// create Serving client for resolving service sink
servingClient, err := p.NewServingClient(namespace)
if err != nil {
return err
@ -68,43 +64,31 @@ func NewApiServerCreateCommand(p *commands.KnParams) *cobra.Command {
"because %s", name, namespace, err)
}
// construct ApiServerSource
apisrvsrc := constructApiServerSource(name, namespace, apiServerUpdateFlags)
apisrvsrc.Spec.Sink = objectRef
// create
_, err = sourcesClient.ApiServerSourcesClient().CreateApiServerSource(apisrvsrc)
err = apiSourceClient.CreateAPIServerSource(
v1alpha1.NewAPIServerSourceBuilder(name).
Resources(updateFlags.GetAPIServerResourceArray()).
ServiceAccount(updateFlags.ServiceAccountName).
Mode(updateFlags.Mode).
Sink(objectRef).
Build())
if err != nil {
return fmt.Errorf(
"cannot create ApiServerSource '%s' in namespace '%s' "+
"because %s", name, namespace, err)
}
fmt.Fprintf(cmd.OutOrStdout(), "ApiServerSource '%s' successfully created in namespace '%s'.\n", args[0], namespace)
return nil
if err == nil {
fmt.Fprintf(cmd.OutOrStdout(), "ApiServer source '%s' created in namespace '%s'.\n", args[0], namespace)
}
return err
},
}
commands.AddNamespaceFlags(cmd.Flags(), false)
apiServerUpdateFlags.Add(cmd)
updateFlags.Add(cmd)
sinkFlags.Add(cmd)
cmd.MarkFlagRequired("schedule")
cmd.MarkFlagRequired("resource")
return cmd
}
// constructApiServerSource is to create an instance of v1alpha1.ApiServerSource
func constructApiServerSource(name string, namespace string, apiServerFlags ApiServerSourceUpdateFlags) *v1alpha1.ApiServerSource {
source := v1alpha1.ApiServerSource{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: v1alpha1.ApiServerSourceSpec{
Resources: apiServerFlags.GetApiServerResourceArray(),
ServiceAccountName: apiServerFlags.ServiceAccountName,
Mode: apiServerFlags.Mode,
},
}
return &source
}

View File

@ -21,14 +21,14 @@ import (
sources_v1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1"
)
func TestGetApiServerResourceArray(t *testing.T) {
func TestGetAPIServerResourceArray(t *testing.T) {
t.Run("get single apiserver resource", func(t *testing.T) {
createFlag := ApiServerSourceUpdateFlags{
createFlag := APIServerSourceUpdateFlags{
ServiceAccountName: "test-sa",
Mode: "Ref",
Resources: []string{"Service:serving.knative.dev/v1alpha1:true"},
}
created := createFlag.GetApiServerResourceArray()
created := createFlag.GetAPIServerResourceArray()
wanted := []sources_v1alpha1.ApiServerResource{{
APIVersion: "serving.knative.dev/v1alpha1",
Kind: "Service",
@ -37,12 +37,12 @@ func TestGetApiServerResourceArray(t *testing.T) {
assert.DeepEqual(t, wanted, created)
// default isController
createFlag = ApiServerSourceUpdateFlags{
createFlag = APIServerSourceUpdateFlags{
ServiceAccountName: "test-sa",
Mode: "Ref",
Resources: []string{"Service:serving.knative.dev/v1alpha1"},
}
created = createFlag.GetApiServerResourceArray()
created = createFlag.GetAPIServerResourceArray()
wanted = []sources_v1alpha1.ApiServerResource{{
APIVersion: "serving.knative.dev/v1alpha1",
Kind: "Service",
@ -51,12 +51,12 @@ func TestGetApiServerResourceArray(t *testing.T) {
assert.DeepEqual(t, wanted, created)
// default api version and isController
createFlag = ApiServerSourceUpdateFlags{
createFlag = APIServerSourceUpdateFlags{
ServiceAccountName: "test-sa",
Mode: "Ref",
Resources: []string{"Service"},
}
created = createFlag.GetApiServerResourceArray()
created = createFlag.GetAPIServerResourceArray()
wanted = []sources_v1alpha1.ApiServerResource{{
APIVersion: "v1",
Kind: "Service",
@ -66,12 +66,12 @@ func TestGetApiServerResourceArray(t *testing.T) {
})
t.Run("get multiple apiserver resources", func(t *testing.T) {
createFlag := ApiServerSourceUpdateFlags{
createFlag := APIServerSourceUpdateFlags{
ServiceAccountName: "test-sa",
Mode: "Resource",
Resources: []string{"Event:v1:true", "Pod:v2:false"},
}
created := createFlag.GetApiServerResourceArray()
created := createFlag.GetAPIServerResourceArray()
wanted := []sources_v1alpha1.ApiServerResource{{
APIVersion: "v1",
Kind: "Event",
@ -84,12 +84,12 @@ func TestGetApiServerResourceArray(t *testing.T) {
assert.DeepEqual(t, wanted, created)
// default api version and isController
createFlag = ApiServerSourceUpdateFlags{
createFlag = APIServerSourceUpdateFlags{
ServiceAccountName: "test-sa",
Mode: "Resource",
Resources: []string{"Event", "Pod"},
}
created = createFlag.GetApiServerResourceArray()
created = createFlag.GetAPIServerResourceArray()
wanted = []sources_v1alpha1.ApiServerResource{{
APIVersion: "v1",

View File

@ -16,87 +16,48 @@ package apiserver
import (
"errors"
"fmt"
"testing"
"gotest.tools/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
client_testing "k8s.io/client-go/testing"
"knative.dev/client/pkg/kn/commands"
knsources_v1alpha1 "knative.dev/client/pkg/eventing/sources/v1alpha1"
knserving_client "knative.dev/client/pkg/serving/v1alpha1"
"knative.dev/client/pkg/util"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
serving_v1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1"
)
var (
testApiServerSrcName = "foo"
)
func TestCreateApiServerSource(t *testing.T) {
func fakeApiServerSourceCreate(args []string, withExistingService bool, sync bool) (
action client_testing.Action,
src *v1alpha1.ApiServerSource,
output string,
err error) {
knParams := &commands.KnParams{}
cmd, fakeSource, buf := commands.CreateSourcesTestKnCommand(NewApiServerCommand(knParams), knParams)
fakeSource.AddReactor("create", "apiserversources",
func(a client_testing.Action) (bool, runtime.Object, error) {
createAction, ok := a.(client_testing.CreateAction)
action = createAction
if !ok {
return true, nil, fmt.Errorf("wrong kind of action %v", a)
}
src, ok = createAction.GetObject().(*v1alpha1.ApiServerSource)
if !ok {
return true, nil, errors.New("was passed the wrong object")
}
return true, src, nil
})
cmd.SetArgs(args)
err = cmd.Execute()
if err != nil {
output = err.Error()
return
}
output = buf.String()
return
apiServerClient := knsources_v1alpha1.NewMockKnAPIServerSourceClient(t)
servingClient := knserving_client.NewMockKnServiceClient(t)
servingRecorder := servingClient.Recorder()
servingRecorder.GetService("testsvc", &serving_v1alpha1.Service{
TypeMeta: metav1.TypeMeta{Kind: "Service"},
ObjectMeta: metav1.ObjectMeta{Name: "testsvc"},
}, nil)
apiServerRecorder := apiServerClient.Recorder()
apiServerRecorder.CreateAPIServerSource(createAPIServerSource("testsource", "Event", "v1", "testsa", "Ref", "testsvc", false), nil)
out, err := executeAPIServerSourceCommand(apiServerClient, servingClient, "create", "testsource", "--resource", "Event:v1:false", "--service-account", "testsa", "--sink", "svc:testsvc", "--mode", "Ref")
assert.NilError(t, err, "ApiServer source should be created")
util.ContainsAll(out, "created", "default", "testsource")
apiServerRecorder.Validate()
servingRecorder.Validate()
}
func TestApiServerSourceCreate(t *testing.T) {
action, created, output, err := fakeApiServerSourceCreate([]string{
"apiserver", "create", testApiServerSrcName, "--resource", "Event:v1:true", "--service-account", "myaccountname", "--sink", "svc:mysvc"}, true, false)
if err != nil {
t.Fatal(err)
} else if !action.Matches("create", "apiserversources") {
t.Fatalf("Bad action %v", action)
}
func TestNoSinkError(t *testing.T) {
servingClient := knserving_client.NewMockKnServiceClient(t)
apiServerClient := knsources_v1alpha1.NewMockKnAPIServerSourceClient(t)
//construct a wanted instance
wanted := &v1alpha1.ApiServerSource{
ObjectMeta: metav1.ObjectMeta{
Name: testApiServerSrcName,
Namespace: commands.FakeNamespace,
},
Spec: v1alpha1.ApiServerSourceSpec{
Resources: []v1alpha1.ApiServerResource{{
APIVersion: "v1",
Kind: "Event",
Controller: true,
}},
ServiceAccountName: "myaccountname",
Mode: "Ref",
Sink: &duckv1beta1.Destination{
Ref: &v1.ObjectReference{
Kind: "Service",
APIVersion: "serving.knative.dev/v1alpha1",
},
},
},
}
errorMsg := "cannot create ApiServerSource 'testsource' in namespace 'default' because no Service svc found"
servingRecorder := servingClient.Recorder()
servingRecorder.GetService("testsvc", nil, errors.New("no Service svc found"))
//assert equal
assert.DeepEqual(t, wanted, created)
assert.Check(t, util.ContainsAll(output, "ApiServerSource", testApiServerSrcName, "created", "namespace", commands.FakeNamespace))
out, err := executeAPIServerSourceCommand(apiServerClient, servingClient, "create", "testsource", "--resource", "Event:v1:false", "--service-account", "testsa", "--sink", "svc:testsvc", "--mode", "Ref")
assert.Error(t, err, errorMsg)
assert.Assert(t, util.ContainsAll(out, errorMsg, "Usage"))
servingRecorder.Validate()
}

View File

@ -22,17 +22,17 @@ import (
"knative.dev/client/pkg/kn/commands"
)
// NewRevisionDeleteCommand represent 'revision delete' command
func NewApiServerDeleteCommand(p *commands.KnParams) *cobra.Command {
ApiServerDeleteCommand := &cobra.Command{
// NewAPIServerDeleteCommand for deleting source
func NewAPIServerDeleteCommand(p *commands.KnParams) *cobra.Command {
deleteCommand := &cobra.Command{
Use: "delete NAME",
Short: "Delete an ApiServerSource.",
Short: "Delete an ApiServer source.",
Example: `
# Delete an ApiServerSource 'k8sevents' in default namespace
kn source apiserver delete k8sevents`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("'source apiserver delete' requires the name of the source as single argument")
return errors.New("requires the name of the source as single argument")
}
name := args[0]
@ -41,19 +41,19 @@ func NewApiServerDeleteCommand(p *commands.KnParams) *cobra.Command {
return err
}
sourcesClient, err := p.NewSourcesClient(namespace)
apiSourceClient, err := newAPIServerSourceClient(p, cmd)
if err != nil {
return err
}
err = sourcesClient.ApiServerSourcesClient().DeleteApiServerSource(name)
err = apiSourceClient.DeleteAPIServerSource(name)
if err != nil {
return err
}
fmt.Fprintf(cmd.OutOrStdout(), "ApiServerSource '%s' deleted in namespace '%s'.\n", args[0], namespace)
fmt.Fprintf(cmd.OutOrStdout(), "ApiServer source '%s' deleted in namespace '%s'.\n", args[0], namespace)
return nil
},
}
commands.AddNamespaceFlags(ApiServerDeleteCommand.Flags(), false)
return ApiServerDeleteCommand
commands.AddNamespaceFlags(deleteCommand.Flags(), false)
return deleteCommand
}

View File

@ -15,47 +15,39 @@
package apiserver
import (
"errors"
"testing"
"gotest.tools/assert"
"k8s.io/apimachinery/pkg/runtime"
client_testing "k8s.io/client-go/testing"
"knative.dev/client/pkg/kn/commands"
knsources_v1alpha1 "knative.dev/client/pkg/eventing/sources/v1alpha1"
"knative.dev/client/pkg/util"
)
func fakeServiceDelete(args []string) (action client_testing.Action, name string, output string, err error) {
knParams := &commands.KnParams{}
cmd, fakeSource, buf := commands.CreateSourcesTestKnCommand(NewApiServerCommand(knParams), knParams)
fakeSource.AddReactor("delete", "apiserversources",
func(a client_testing.Action) (bool, runtime.Object, error) {
deleteAction, _ := a.(client_testing.DeleteAction)
action = deleteAction
name = deleteAction.GetName()
return true, nil, nil
})
cmd.SetArgs(args)
err = cmd.Execute()
if err != nil {
return
}
output = buf.String()
return
func TestApiServerSourceDelete(t *testing.T) {
apiServerClient := knsources_v1alpha1.NewMockKnAPIServerSourceClient(t, "testns")
apiServerRecorder := apiServerClient.Recorder()
apiServerRecorder.DeleteAPIServerSource("testsource", nil)
out, err := executeAPIServerSourceCommand(apiServerClient, nil, "delete", "testsource")
assert.NilError(t, err)
util.ContainsAll(out, "deleted", "testns", "testsource")
apiServerRecorder.Validate()
}
func TestServiceDelete(t *testing.T) {
srcName := "src-12345"
action, name, output, err := fakeServiceDelete([]string{"apiserver", "delete", srcName})
if err != nil {
t.Error(err)
return
}
if action == nil {
t.Errorf("No action")
} else if !action.Matches("delete", "apiserversources") {
t.Errorf("Bad action %v", action)
} else if name != srcName {
t.Errorf("Bad service name returned after delete.")
}
assert.Check(t, util.ContainsAll(output, "ApiServerSource", srcName, "deleted", "namespace", commands.FakeNamespace))
func TestDeleteWithError(t *testing.T) {
apiServerClient := knsources_v1alpha1.NewMockKnAPIServerSourceClient(t, "mynamespace")
apiServerRecorder := apiServerClient.Recorder()
apiServerRecorder.DeleteAPIServerSource("testsource", errors.New("apiserver source testsource not found"))
out, err := executeAPIServerSourceCommand(apiServerClient, nil, "delete", "testsource")
assert.ErrorContains(t, err, "testsource")
util.ContainsAll(out, "apiserver", "source", "testsource", "not found")
apiServerRecorder.Validate()
}

View File

@ -0,0 +1,124 @@
// 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 implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apiserver
import (
"errors"
"fmt"
"strconv"
"github.com/spf13/cobra"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
"knative.dev/client/pkg/kn/commands"
"knative.dev/client/pkg/printers"
)
// NewAPIServerDescribeCommand to describe an ApiServer source object
func NewAPIServerDescribeCommand(p *commands.KnParams) *cobra.Command {
apiServerDescribe := &cobra.Command{
Use: "describe NAME",
Short: "Describe an ApiServer source.",
Example: `
# Describe an ApiServer source with name 'k8sevents'
kn source apiserver describe k8sevents`,
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]
apiSourceClient, err := newAPIServerSourceClient(p, cmd)
if err != nil {
return err
}
apiSource, err := apiSourceClient.GetAPIServerSource(name)
if err != nil {
return err
}
out := cmd.OutOrStdout()
dw := printers.NewPrefixWriter(out)
printDetails, err := cmd.Flags().GetBool("verbose")
if err != nil {
return err
}
writeAPIServerSource(dw, apiSource, printDetails)
dw.WriteLine()
if err := dw.Flush(); err != nil {
return err
}
writeSink(dw, apiSource.Spec.Sink)
dw.WriteLine()
if err := dw.Flush(); err != nil {
return err
}
writeResources(dw, apiSource.Spec.Resources)
dw.WriteLine()
if err := dw.Flush(); err != nil {
return err
}
// Condition info
commands.WriteConditions(dw, apiSource.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 writeResources(dw printers.PrefixWriter, resources []v1alpha1.ApiServerResource) {
subWriter := dw.WriteAttribute("Resources", "")
for _, resource := range resources {
subWriter.WriteAttribute("Kind", fmt.Sprintf("%s (%s)", resource.Kind, resource.APIVersion))
subWriter.WriteAttribute("Controller", strconv.FormatBool(resource.Controller))
// TODO : Add Controller Selector section here for --verbose
}
}
func writeSink(dw printers.PrefixWriter, sink *duckv1beta1.Destination) {
subWriter := dw.WriteAttribute("Sink", "")
subWriter.WriteAttribute("Name", sink.Ref.Name)
subWriter.WriteAttribute("Namespace", sink.Ref.Namespace)
ref := sink.Ref
if ref != nil {
subWriter.WriteAttribute("Kind", fmt.Sprintf("%s (%s)", sink.Ref.Kind, sink.Ref.APIVersion))
}
uri := sink.URI
if uri != nil {
subWriter.WriteAttribute("URI", uri.String())
}
}
func writeAPIServerSource(dw printers.PrefixWriter, source *v1alpha1.ApiServerSource, printDetails bool) {
commands.WriteMetadata(dw, &source.ObjectMeta, printDetails)
dw.WriteAttribute("ServiceAccountName", source.Spec.ServiceAccountName)
dw.WriteAttribute("Mode", source.Spec.Mode)
}

View File

@ -0,0 +1,52 @@
// 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 implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apiserver
import (
"errors"
"testing"
"gotest.tools/assert"
knsource_v1alpha1 "knative.dev/client/pkg/eventing/sources/v1alpha1"
"knative.dev/client/pkg/util"
)
func TestSimpleDescribe(t *testing.T) {
apiServerClient := knsource_v1alpha1.NewMockKnAPIServerSourceClient(t, "mynamespace")
apiServerRecorder := apiServerClient.Recorder()
sampleSource := createAPIServerSource("testsource", "Event", "v1", "testsa", "Ref", "testsvc", false)
apiServerRecorder.GetAPIServerSource("testsource", sampleSource, nil)
out, err := executeAPIServerSourceCommand(apiServerClient, nil, "describe", "testsource")
assert.NilError(t, err)
util.ContainsAll(out, "testsource", "testsa", "Ref", "testsvc", "Service", "Resources", "Event", "v1", "false", "Conditions")
apiServerRecorder.Validate()
}
func TestDescribeError(t *testing.T) {
apiServerClient := knsource_v1alpha1.NewMockKnAPIServerSourceClient(t, "mynamespace")
apiServerRecorder := apiServerClient.Recorder()
apiServerRecorder.GetAPIServerSource("testsource", nil, errors.New("no apiserver source testsource"))
out, err := executeAPIServerSourceCommand(apiServerClient, nil, "describe", "testsource")
assert.ErrorContains(t, err, "testsource")
util.ContainsAll(out, "Usage", "testsource")
apiServerRecorder.Validate()
}

View File

@ -19,27 +19,27 @@ import (
"strings"
"github.com/spf13/cobra"
sources_v1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
)
const (
ApiVersionSplitChar = ":"
DefaultApiVersion = "v1"
apiVersionSplitChar = ":"
defaultAPIVersion = "v1"
)
// ApiServerSourceUpdateFlags are flags for create and update a ApiServerSource
type ApiServerSourceUpdateFlags struct {
// APIServerSourceUpdateFlags are flags for create and update a ApiServerSource
type APIServerSourceUpdateFlags struct {
ServiceAccountName string
Mode string
Resources []string
}
// GetApiServerResourceArray is to return an array of ApiServerResource from a string. A sample is Event:v1:true,Pod:v2:false
func (f *ApiServerSourceUpdateFlags) GetApiServerResourceArray() []sources_v1alpha1.ApiServerResource {
var resourceList []sources_v1alpha1.ApiServerResource
// GetAPIServerResourceArray is to return an array of ApiServerResource from a string. A sample is Event:v1:true,Pod:v2:false
func (f *APIServerSourceUpdateFlags) GetAPIServerResourceArray() []v1alpha1.ApiServerResource {
var resourceList []v1alpha1.ApiServerResource
for _, r := range f.Resources {
version, kind, controller := getValidResource(r)
resourceRef := sources_v1alpha1.ApiServerResource{
resourceRef := v1alpha1.ApiServerResource{
APIVersion: version,
Kind: kind,
Controller: controller,
@ -50,11 +50,11 @@ func (f *ApiServerSourceUpdateFlags) GetApiServerResourceArray() []sources_v1alp
}
func getValidResource(resource string) (string, string, bool) {
var version = DefaultApiVersion // v1 as default
var version = defaultAPIVersion // v1 as default
var isController = false //false as default
var err error
parts := strings.Split(resource, ApiVersionSplitChar)
parts := strings.Split(resource, apiVersionSplitChar)
kind := parts[0]
if len(parts) >= 2 {
version = parts[1]
@ -69,7 +69,7 @@ func getValidResource(resource string) (string, string, bool) {
}
//Add is to set parameters
func (f *ApiServerSourceUpdateFlags) Add(cmd *cobra.Command) {
func (f *APIServerSourceUpdateFlags) Add(cmd *cobra.Command) {
cmd.Flags().StringVar(&f.ServiceAccountName,
"service-account",
"",
@ -85,5 +85,5 @@ func (f *ApiServerSourceUpdateFlags) Add(cmd *cobra.Command) {
nil,
`Comma seperate Kind:APIVersion:isController list, e.g. Event:v1:true.
"APIVersion" and "isControler" can be omitted.
"APIVersion" is "v1" by default, "isController" is "false" by default. `)
"APIVersion" is "v1" by default, "isController" is "false" by default.`)
}

View File

@ -0,0 +1,100 @@
// 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 implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apiserver
import (
"errors"
"fmt"
"github.com/spf13/cobra"
"knative.dev/client/pkg/eventing/sources/v1alpha1"
"knative.dev/client/pkg/kn/commands"
"knative.dev/client/pkg/kn/commands/flags"
)
// NewAPIServerUpdateCommand for managing source update
func NewAPIServerUpdateCommand(p *commands.KnParams) *cobra.Command {
var apiServerUpdateFlags APIServerSourceUpdateFlags
var sinkFlags flags.SinkFlags
cmd := &cobra.Command{
Use: "update NAME --resource RESOURCE --service-account ACCOUNTNAME --sink SINK --mode MODE",
Short: "Update an ApiServer source.",
Example: `
# Update an ApiServerSource 'k8sevents' with different service account and sink service
kn source apiserver update k8sevents --service-account newsa --sink svc:newsvc`,
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 namespace
namespace, err := p.GetNamespace(cmd)
if err != nil {
return err
}
servingClient, err := p.NewServingClient(namespace)
if err != nil {
return err
}
sourcesClient, err := newAPIServerSourceClient(p, cmd)
if err != nil {
return err
}
source, err := sourcesClient.GetAPIServerSource(name)
if err != nil {
return err
}
b := v1alpha1.NewAPIServerSourceBuilderFromExisting(source)
if cmd.Flags().Changed("service-account") {
b.ServiceAccount(apiServerUpdateFlags.ServiceAccountName)
}
if cmd.Flags().Changed("mode") {
b.Mode(apiServerUpdateFlags.Mode)
}
if cmd.Flags().Changed("resource") {
b.Resources(apiServerUpdateFlags.GetAPIServerResourceArray())
}
if cmd.Flags().Changed("sink") {
objectRef, err := sinkFlags.ResolveSink(servingClient)
if err != nil {
return err
}
b.Sink(objectRef)
}
err = sourcesClient.UpdateAPIServerSource(b.Build())
if err == nil {
fmt.Fprintf(cmd.OutOrStdout(), "ApiServer source '%s' updated in namespace '%s'.\n", args[0], namespace)
}
return err
},
}
commands.AddNamespaceFlags(cmd.Flags(), false)
apiServerUpdateFlags.Add(cmd)
sinkFlags.Add(cmd)
return cmd
}

View File

@ -0,0 +1,54 @@
// 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 implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apiserver
import (
"testing"
"gotest.tools/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
serving_v1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1"
knsources_v1alpha1 "knative.dev/client/pkg/eventing/sources/v1alpha1"
knserving_client "knative.dev/client/pkg/serving/v1alpha1"
"knative.dev/client/pkg/util"
)
func TestApiServerSourceUpdate(t *testing.T) {
apiServerClient := knsources_v1alpha1.NewMockKnAPIServerSourceClient(t)
servingClient := knserving_client.NewMockKnServiceClient(t)
apiServerRecorder := apiServerClient.Recorder()
servingRecorder := servingClient.Recorder()
present := createAPIServerSource("testsource", "Event", "v1", "testsa1", "Ref", "svc1", false)
apiServerRecorder.GetAPIServerSource("testsource", present, nil)
servingRecorder.GetService("svc2", &serving_v1alpha1.Service{
TypeMeta: metav1.TypeMeta{Kind: "Service"},
ObjectMeta: metav1.ObjectMeta{Name: "svc2"},
}, nil)
updated := createAPIServerSource("testsource", "Event", "v1", "testsa2", "Ref", "svc2", false)
apiServerRecorder.UpdateAPIServerSource(updated, nil)
output, err := executeAPIServerSourceCommand(apiServerClient, servingClient, "update", "testsource", "--service-account", "testsa2", "--sink", "svc:svc2")
assert.NilError(t, err)
assert.Assert(t, util.ContainsAll(output, "testsource", "updated", "default"))
apiServerRecorder.Validate()
servingRecorder.Validate()
}

View File

@ -27,7 +27,7 @@ func NewSourceCommand(p *commands.KnParams) *cobra.Command {
Use: "source",
Short: "Event source command group",
}
sourceCmd.AddCommand(apiserver.NewApiServerCommand(p))
sourceCmd.AddCommand(apiserver.NewAPIServerCommand(p))
sourceCmd.AddCommand(NewListTypesCommand(p))
sourceCmd.AddCommand(cronjob.NewCronJobCommand(p))
return sourceCmd

View File

@ -187,7 +187,7 @@ func TestNewSourcesClient(t *testing.T) {
}
if sourcesClient != nil {
assert.Assert(t, sourcesClient.ApiServerSourcesClient().Namespace() == namespace)
assert.Assert(t, sourcesClient.APIServerSourcesClient().Namespace() == namespace)
assert.Assert(t, sourcesClient.CronJobSourcesClient().Namespace() == namespace)
}
}

View File

@ -31,41 +31,52 @@ func TestSourceApiServer(t *testing.T) {
test.Setup(t)
defer test.Teardown(t)
test.setupServiceAccountForApiserver(t, "mysa")
test.serviceCreate(t, "myservice")
test.setupServiceAccountForApiserver(t, "testsa")
test.serviceCreate(t, "testsvc0")
t.Run("create apiserver source with a sink to a service", func(t *testing.T) {
test.apiServerSourceCreate(t, "firstsrc", "Eventing:v1:true", "mysa", "svc:myservice")
test.apiServerSourceCreate(t, "secondsrc", "Eventing,Namespace", "mysa", "svc:myservice")
t.Run("create apiserver sources with a sink to a service", func(t *testing.T) {
test.apiServerSourceCreate(t, "testapisource0", "Event:v1:true", "testsa", "svc:testsvc0")
test.apiServerSourceCreate(t, "testapisource1", "Event", "testsa", "svc:testsvc0")
})
t.Run("create apiserver source and delete it", func(t *testing.T) {
test.apiServerSourceDelete(t, "firstsrc")
test.apiServerSourceDelete(t, "secondsrc")
t.Run("delete apiserver sources", func(t *testing.T) {
test.apiServerSourceDelete(t, "testapisource0")
test.apiServerSourceDelete(t, "testapisource1")
})
t.Run("create apiserver source with a missing sink service", func(t *testing.T) {
test.apiServerSourceCreateMissingSink(t, "wrongsrc", "Eventing:v1:true", "mysa", "svc:unknown")
test.apiServerSourceCreateMissingSink(t, "testapisource2", "Event:v1:true", "testsa", "svc:unknown")
})
t.Run("update apiserver source sink service", func(t *testing.T) {
test.apiServerSourceCreate(t, "testapisource3", "Event:1:true", "testsa", "svc:testsvc0")
test.serviceCreate(t, "testsvc1")
test.apiServerSourceUpdateSink(t, "testapisource3", "svc:testsvc1")
jpSinkRefNameInSpec := "jsonpath={.spec.sink.ref.name}"
out, err := test.getResourceFieldsWithJSONPath(t, "apiserversource", "testapisource3", jpSinkRefNameInSpec)
assert.NilError(t, err)
assert.Equal(t, out, "testsvc1")
// TODO(navidshaikh): Verify the source's status with synchronous create/update
})
}
func (test *e2eTest) apiServerSourceCreate(t *testing.T, srcName string, resources string, sa string, sink string) {
out, err := test.kn.RunWithOpts([]string{"source", "apiserver", "create", srcName,
func (test *e2eTest) apiServerSourceCreate(t *testing.T, sourceName string, resources string, sa string, sink string) {
out, err := test.kn.RunWithOpts([]string{"source", "apiserver", "create", sourceName,
"--resource", resources, "--service-account", sa, "--sink", sink}, runOpts{NoNamespace: false})
assert.NilError(t, err)
assert.Check(t, util.ContainsAllIgnoreCase(out, "ApiServerSource", srcName, "created", "namespace", test.kn.namespace))
assert.Check(t, util.ContainsAllIgnoreCase(out, "apiserver", "source", sourceName, "created", "namespace", test.kn.namespace))
}
func (test *e2eTest) apiServerSourceCreateMissingSink(t *testing.T, srcName string, resources string, sa string, sink string) {
_, err := test.kn.RunWithOpts([]string{"source", "apiserver", "create", srcName,
func (test *e2eTest) apiServerSourceCreateMissingSink(t *testing.T, sourceName string, resources string, sa string, sink string) {
_, err := test.kn.RunWithOpts([]string{"source", "apiserver", "create", sourceName,
"--resource", resources, "--service-account", sa, "--sink", sink}, runOpts{NoNamespace: false, AllowError: true})
assert.ErrorContains(t, err, "services.serving.knative.dev", "not found")
}
func (test *e2eTest) apiServerSourceDelete(t *testing.T, srcName string) {
out, err := test.kn.RunWithOpts([]string{"source", "apiserver", "delete", srcName}, runOpts{NoNamespace: false})
func (test *e2eTest) apiServerSourceDelete(t *testing.T, sourceName string) {
out, err := test.kn.RunWithOpts([]string{"source", "apiserver", "delete", sourceName}, runOpts{NoNamespace: false})
assert.NilError(t, err)
assert.Check(t, util.ContainsAllIgnoreCase(out, "ApiServerSource", srcName, "deleted", "namespace", test.kn.namespace))
assert.Check(t, util.ContainsAllIgnoreCase(out, "apiserver", "source", sourceName, "deleted", "namespace", test.kn.namespace))
}
func (test *e2eTest) setupServiceAccountForApiserver(t *testing.T, name string) {
@ -84,3 +95,19 @@ func (test *e2eTest) setupServiceAccountForApiserver(t *testing.T, name string)
t.Fatalf(fmt.Sprintf("Error executing 'kubectl clusterrolebinding testsa-binding'. Error: %s", err.Error()))
}
}
func (test *e2eTest) apiServerSourceUpdateSink(t *testing.T, sourceName string, sink string) {
out, err := test.kn.RunWithOpts([]string{"source", "apiserver", "update", sourceName, "--sink", sink}, runOpts{})
assert.NilError(t, err)
assert.Check(t, util.ContainsAll(out, sourceName, "updated", "namespace", test.kn.namespace))
}
func (test *e2eTest) getResourceFieldsWithJSONPath(t *testing.T, resource, name, jsonpath string) (string, error) {
kubectl := kubectl{t, Logger{}}
out, err := kubectl.RunWithOpts([]string{"get", resource, name, "-o", jsonpath, "-n", test.kn.namespace}, runOpts{})
if err != nil {
return "", err
}
return out, nil
}