Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add trigger update command #562

Merged
merged 2 commits into from
Dec 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/cmd/kn_trigger.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ kn trigger [flags]
* [kn trigger delete](kn_trigger_delete.md) - Delete a trigger.
* [kn trigger describe](kn_trigger_describe.md) - Describe a trigger.
* [kn trigger list](kn_trigger_list.md) - List available triggers.
* [kn trigger update](kn_trigger_update.md) - Update a trigger

49 changes: 49 additions & 0 deletions docs/cmd/kn_trigger_update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## kn trigger update

Update a trigger

### Synopsis

Update a trigger

```
kn trigger update NAME --filter KEY=VALUE --sink SINK [flags]
```

### Examples

```

# Update the filter which key is 'type' to value 'knative.dev.bar' in a trigger 'mytrigger'
kn trigger update mytrigger --filter type=knative.dev.bar

# Remove the filter which key is 'type' from a trigger 'mytrigger'
kn trigger update mytrigger --filter type-

# Update the sink of a trigger 'mytrigger' to 'svc:new-service'
kn trigger update mytrigger --sink svc:new-service

```

### Options

```
--broker string Name of the Broker which the trigger associates with. (default "default")
--filter []string Key-value pair for exact CloudEvent attribute matching against incoming events, e.g type=dev.knative.foo
-h, --help help for update
-n, --namespace string Specify the namespace to operate in.
-s, --sink string Addressable sink for events
```

### 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 trigger](kn_trigger.md) - Trigger command group

80 changes: 80 additions & 0 deletions pkg/eventing/v1alpha1/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ package v1alpha1

import (
apis_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kn_errors "knative.dev/client/pkg/errors"
"knative.dev/client/pkg/util"
"knative.dev/eventing/pkg/apis/eventing/v1alpha1"
"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
client_v1alpha1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1alpha1"
duckv1 "knative.dev/pkg/apis/duck/v1"
)

const (
Expand All @@ -41,6 +43,8 @@ type KnEventingClient interface {
GetTrigger(name string) (*v1alpha1.Trigger, error)
// ListTrigger returns list of trigger CRDs
ListTriggers() (*v1alpha1.TriggerList, error)
// UpdateTrigger is used to update an instance of trigger
UpdateTrigger(trigger *v1alpha1.Trigger) error
}

// KnEventingClient is a combination of Sources client interface and namespace
Expand Down Expand Up @@ -109,6 +113,15 @@ func (c *knEventingClient) ListTriggers() (*v1alpha1.TriggerList, error) {
return triggerListNew, nil
}

//CreateTrigger is used to create an instance of trigger
func (c *knEventingClient) UpdateTrigger(trigger *v1alpha1.Trigger) error {
trigger, err := c.client.Triggers(c.namespace).Update(trigger)
if err != nil {
return kn_errors.GetError(err)
}
return nil
}

// Return the client's namespace
func (c *knEventingClient) Namespace() string {
return c.namespace
Expand All @@ -118,3 +131,70 @@ func (c *knEventingClient) Namespace() string {
func updateTriggerGvk(obj runtime.Object) error {
return util.UpdateGroupVersionKindWithScheme(obj, v1alpha1.SchemeGroupVersion, scheme.Scheme)
}

// TriggerBuilder is for building the trigger
type TriggerBuilder struct {
trigger *v1alpha1.Trigger
}

// NewTriggerBuilder for building trigger object
func NewTriggerBuilder(name string) *TriggerBuilder {
return &TriggerBuilder{trigger: &v1alpha1.Trigger{
ObjectMeta: meta_v1.ObjectMeta{
Name: name,
},
}}
}

// NewTriggerBuilderFromExisting for building the object from existing Trigger object
func NewTriggerBuilderFromExisting(tr *v1alpha1.Trigger) *TriggerBuilder {
return &TriggerBuilder{trigger: tr.DeepCopy()}
}

// Broker to set the broker of trigger object
func (b *TriggerBuilder) Broker(broker string) *TriggerBuilder {
if broker != "" {
b.trigger.Spec.Broker = broker
}
return b
}

// Filters to set the filters of trigger object
func (b *TriggerBuilder) Filters(filters map[string]string) *TriggerBuilder {
if filters != nil {
triggerFilterAttributes := v1alpha1.TriggerFilterAttributes(filters)
b.trigger.Spec.Filter = &v1alpha1.TriggerFilter{
Attributes: &triggerFilterAttributes,
}
}
return b
}

// UpdateFilters to update the filters of trigger object
func (b *TriggerBuilder) UpdateFilters(toUpdate map[string]string, toRemove []string) *TriggerBuilder {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't do any removal or change in the builder. Its about creating an immutable object, so would prefer to have just a Filter() method (as in my PR ;-) if you don't mind I would update that later.

Copy link
Member Author

@daisy-ycguo daisy-ycguo Dec 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine. I don't mind. Feel free to update it in a following PR.

if b.trigger.Spec.Filter == nil {
b.Filters(toUpdate)
return b
}

existing := map[string]string(*b.trigger.Spec.Filter.Attributes)
for key, value := range toUpdate {
existing[key] = value
}
for _, key := range toRemove {
delete(existing, key)
}
b.Filters(existing)
return b
}

// Sink to set the subscriber of trigger object
func (b *TriggerBuilder) Sink(sink *duckv1.Destination) *TriggerBuilder {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's call it Subscriber like in the spec.

b.trigger.Spec.Subscriber = sink
return b
}

// Build to return an instance of trigger object
func (b *TriggerBuilder) Build() *v1alpha1.Trigger {
return b.trigger
}
11 changes: 11 additions & 0 deletions pkg/eventing/v1alpha1/client_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ func (c *MockKnEventingClient) ListTriggers() (*v1alpha1.TriggerList, error) {
return call.Result[0].(*v1alpha1.TriggerList), mock.ErrorOrNil(call.Result[1])
}

// UpdateTrigger records a call for ListTriggers with the expected result and error (nil if none)
func (sr *EventingRecorder) UpdateTrigger(trigger interface{}, err error) {
sr.r.Add("UpdateTrigger", []interface{}{trigger}, []interface{}{err})
}

// UpdateTrigger performs a previously recorded action
func (c *MockKnEventingClient) UpdateTrigger(trigger *v1alpha1.Trigger) error {
call := c.recorder.r.VerifyCall("UpdateTrigger")
return mock.ErrorOrNil(call.Result[0])
}

// Validate validates whether every recorded action has been called
func (sr *EventingRecorder) Validate() {
sr.r.CheckThatAllRecordedMethodsHaveBeenCalled()
Expand Down
2 changes: 2 additions & 0 deletions pkg/eventing/v1alpha1/client_mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ func TestMockKnClient(t *testing.T) {
recorder.CreateTrigger(&v1alpha1.Trigger{}, nil)
recorder.DeleteTrigger("hello", nil)
recorder.ListTriggers(nil, nil)
recorder.UpdateTrigger(&v1alpha1.Trigger{}, nil)

// Call all service
client.GetTrigger("hello")
client.CreateTrigger(&v1alpha1.Trigger{})
client.DeleteTrigger("hello")
client.ListTriggers()
client.UpdateTrigger(&v1alpha1.Trigger{})

// Validate
recorder.Validate()
Expand Down
66 changes: 49 additions & 17 deletions pkg/eventing/v1alpha1/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,22 +128,54 @@ func TestListTrigger(t *testing.T) {
})
}

func newTrigger(name string) *v1alpha1.Trigger {
obj := &v1alpha1.Trigger{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: testNamespace,
},
Spec: v1alpha1.TriggerSpec{
Broker: "default",
Filter: &v1alpha1.TriggerFilter{
Attributes: &v1alpha1.TriggerFilterAttributes{
"type": "foo",
},
func TestTriggerBuilder(t *testing.T) {
a := NewTriggerBuilder("testtrigger")
a.Filters(map[string]string{"type": "foo", "source": "bar"})

t.Run("update filter", func(t *testing.T) {
b := NewTriggerBuilderFromExisting(a.Build())
assert.DeepEqual(t, b.Build(), a.Build())
b.UpdateFilters(map[string]string{"type": "new"}, []string{"source"})
expected := &v1alpha1.TriggerFilter{
Attributes: &v1alpha1.TriggerFilterAttributes{
"type": "new",
},
}
assert.DeepEqual(t, expected, b.Build().Spec.Filter)
})

t.Run("update filter with only deletions", func(t *testing.T) {
b := NewTriggerBuilderFromExisting(a.Build())
assert.DeepEqual(t, b.Build(), a.Build())
b.UpdateFilters(nil, []string{"source"})
expected := &v1alpha1.TriggerFilter{
Attributes: &v1alpha1.TriggerFilterAttributes{
"type": "foo",
},
}
assert.DeepEqual(t, expected, b.Build().Spec.Filter)
})

t.Run("update filter with only updates", func(t *testing.T) {
b := NewTriggerBuilderFromExisting(a.Build())
assert.DeepEqual(t, b.Build(), a.Build())
b.UpdateFilters(map[string]string{"type": "new"}, nil)
expected := &v1alpha1.TriggerFilter{
Attributes: &v1alpha1.TriggerFilterAttributes{
"type": "new",
"source": "bar",
},
},
}
obj.Name = name
obj.Namespace = testNamespace
return obj
}
assert.DeepEqual(t, expected, b.Build().Spec.Filter)
})

}

func newTrigger(name string) *v1alpha1.Trigger {
b := NewTriggerBuilder(name)
b.Filters(map[string]string{"type": "foo"})
b.Broker("default")
b.trigger.Name = name
b.trigger.Namespace = testNamespace
return b.Build()
}
28 changes: 19 additions & 9 deletions pkg/kn/commands/trigger/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,19 @@ func NewTriggerCreateCommand(p *commands.KnParams) *cobra.Command {
"because %s", name, namespace, err)
}

trigger := constructTrigger(name, namespace, triggerUpdateFlags)
filters, err := triggerUpdateFlags.GetFilters()
if err != nil {
return fmt.Errorf(
"cannot create trigger '%s' "+
"because %s", name, err)
}
if filters == nil {
return fmt.Errorf(
"cannot create trigger '%s' "+
"because filters are required", name)
}

trigger := constructTrigger(name, namespace, triggerUpdateFlags.Broker, filters)
trigger.Spec.Subscriber = &duckv1.Destination{
Ref: objectRef.Ref,
URI: objectRef.URI,
Expand All @@ -87,28 +99,26 @@ func NewTriggerCreateCommand(p *commands.KnParams) *cobra.Command {
triggerUpdateFlags.Add(cmd)
sinkFlags.Add(cmd)
cmd.MarkFlagRequired("sink")
cmd.MarkFlagRequired("filter")

return cmd
}

// constructTrigger is to create an instance of v1alpha1.Trigger
func constructTrigger(name string, namespace string, flags TriggerUpdateFlags) *v1alpha1.Trigger {
func constructTrigger(name string, namespace string, broker string, filters map[string]string) *v1alpha1.Trigger {
trigger := v1alpha1.Trigger{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: v1alpha1.TriggerSpec{
Broker: flags.Broker,
Broker: broker,
},
}

filters := flags.GetFilters()
if filters != nil {
triggerFilterAttributes := v1alpha1.TriggerFilterAttributes(filters)
trigger.Spec.Filter = &v1alpha1.TriggerFilter{
Attributes: &triggerFilterAttributes,
}
triggerFilterAttributes := v1alpha1.TriggerFilterAttributes(filters)
trigger.Spec.Filter = &v1alpha1.TriggerFilter{
Attributes: &triggerFilterAttributes,
}

return &trigger
Expand Down
7 changes: 7 additions & 0 deletions pkg/kn/commands/trigger/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ func TestNoSinkError(t *testing.T) {
assert.ErrorContains(t, err, "required flag(s)", "sink", "not set")
}

func TestNoFilterError(t *testing.T) {
eventingClient := eventing_client.NewMockKnEventingClient(t)
_, err := executeTriggerCommand(eventingClient, nil, "create", triggerName, "--broker", "mybroker",
"--sink", "svc:mysvc")
assert.ErrorContains(t, err, "required flag(s)", "filter", "not set")
}

func TestTriggerCreateMultipleFilter(t *testing.T) {
eventingClient := eventing_client.NewMockKnEventingClient(t)
servingClient := knserving_client.NewMockKnServiceClient(t)
Expand Down
3 changes: 2 additions & 1 deletion pkg/kn/commands/trigger/trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ func NewTriggerCommand(p *commands.KnParams) *cobra.Command {
Short: "Trigger command group",
}
triggerCmd.AddCommand(NewTriggerCreateCommand(p))
triggerCmd.AddCommand(NewTriggerDeleteCommand(p))
triggerCmd.AddCommand(NewTriggerUpdateCommand(p))
triggerCmd.AddCommand(NewTriggerDescribeCommand(p))
triggerCmd.AddCommand(NewTriggerListCommand(p))
triggerCmd.AddCommand(NewTriggerDeleteCommand(p))
return triggerCmd
}
Loading