From 278ad8bdb21774ecce3e5de80ec6bbd8e0e0d9c0 Mon Sep 17 00:00:00 2001 From: Bernd Warmuth <72415058+warber@users.noreply.github.com> Date: Wed, 7 Sep 2022 08:44:06 +0200 Subject: [PATCH] feat: Introduce skipping of automatic event responses per task handler (#537) * feat: Introduce skipping of automatic event responses per task handler Signed-off-by: warber * cleanup Signed-off-by: warber Signed-off-by: warber --- pkg/sdk/keptn.go | 35 +++++++++++++++++++----- pkg/sdk/keptn_fake.go | 6 +++++ pkg/sdk/keptn_test.go | 59 +++++++++++++++++++++++++++++++++++++++++ pkg/sdk/taskregistry.go | 2 ++ 4 files changed, 96 insertions(+), 6 deletions(-) diff --git a/pkg/sdk/keptn.go b/pkg/sdk/keptn.go index 1e0b5d88..7ce5cbe8 100644 --- a/pkg/sdk/keptn.go +++ b/pkg/sdk/keptn.go @@ -116,21 +116,41 @@ func (w *nopWG) Wait() { // -- } -// WithTaskHandler registers a handler which is responsible for processing a .triggered event +// WithTaskHandler registers a handler which is responsible for processing a .triggered event. +// Note, that if you want to have more control on configuring the behavior of the task handler, +// you can use WithTaskEventHandler instead func WithTaskHandler(eventType string, handler TaskHandler, filters ...func(keptnHandle IKeptn, event KeptnEvent) bool) KeptnOption { + return WithTaskEventHandler(eventType, handler, TaskHandlerOptions{ + Filters: filters, + SkipAutomaticResponse: false, + }) +} + +// WithTaskEventHandler registers a handler which is responsible for processing a received .triggered event +func WithTaskEventHandler(eventType string, handler TaskHandler, options TaskHandlerOptions) KeptnOption { return func(k *Keptn) { - k.taskRegistry.Add(eventType, taskEntry{taskHandler: handler, eventFilters: filters}) + k.taskRegistry.Add(eventType, taskEntry{taskHandler: handler, eventFilters: options.Filters, taskHandlerOpts: options}) } } // WithAutomaticResponse sets the option to instruct the sdk to automatically send a .started and .finished event. -// Per default this behavior is turned on and can be disabled with this function +// Per default this behavior is turned on and can be disabled with this function. Note, that this affects ALL +// task handlers. If you want to disable automatic event responses for a specific task handler, this can be done +// with the respective TaskHandlerOptions passed to WithTaskEventHandler func WithAutomaticResponse(autoResponse bool) KeptnOption { return func(k *Keptn) { k.automaticEventResponse = autoResponse } } +// TaskHandlerOptions are specific options for a task handler +type TaskHandlerOptions struct { + // Filters specifies functions that determine whether the event shall be handled or ignored + Filters []func(IKeptn, KeptnEvent) bool + // SkipAutomaticResponse determines whether automatic sending of .started/.finished events should be skipped + SkipAutomaticResponse bool +} + // WithGracefulShutdown sets the option to ensure running tasks/handlers will finish in case of interrupt or forced termination // Per default this behavior is turned on and can be disabled with this function func WithGracefulShutdown(gracefulShutdown bool) KeptnOption { @@ -251,8 +271,11 @@ func (k *Keptn) OnEvent(ctx context.Context, event models.KeptnContextExtendedCE } } + // automatic response of events is enabled if it is turned on globally, and not disabled for the specific handler + autoResponse := k.automaticEventResponse && !k.taskRegistry.Get(*event.Type).taskHandlerOpts.SkipAutomaticResponse + // only respond with .started event if the incoming event is a task.triggered event - if keptnv2.IsTaskEventType(*event.Type) && keptnv2.IsTriggeredEventType(*event.Type) && k.automaticEventResponse { + if keptnv2.IsTaskEventType(*event.Type) && keptnv2.IsTriggeredEventType(*event.Type) && autoResponse { startedEvent, err := createStartedEvent(k.source, event) if err != nil { k.logger.Errorf("Unable to create '.started' event from '.triggered' event: %v", err) @@ -267,7 +290,7 @@ func (k *Keptn) OnEvent(ctx context.Context, event models.KeptnContextExtendedCE result, err := handler.taskHandler.Execute(k, *keptnEvent) if err != nil { k.logger.Errorf("Error during task execution %v", err.Err) - if k.automaticEventResponse { + if autoResponse { errorEvent, err := createErrorEvent(k.source, event, result, err) if err != nil { k.logger.Errorf("Unable to create '.error' event: %v", err) @@ -282,7 +305,7 @@ func (k *Keptn) OnEvent(ctx context.Context, event models.KeptnContextExtendedCE } if result == nil { k.logger.Infof("no finished data set by task executor for event %s. Skipping sending finished event", *event.Type) - } else if keptnv2.IsTaskEventType(*event.Type) && keptnv2.IsTriggeredEventType(*event.Type) && k.automaticEventResponse { + } else if keptnv2.IsTaskEventType(*event.Type) && keptnv2.IsTriggeredEventType(*event.Type) && autoResponse { finishedEvent, err := createFinishedEvent(k.source, event, result) if err != nil { k.logger.Errorf("Unable to create '.finished' event: %v", err) diff --git a/pkg/sdk/keptn_fake.go b/pkg/sdk/keptn_fake.go index e1d0ca9c..db3e8c08 100644 --- a/pkg/sdk/keptn_fake.go +++ b/pkg/sdk/keptn_fake.go @@ -82,6 +82,12 @@ func (f *FakeKeptn) SetAPI(api api.KeptnInterface) { f.Keptn.api = api } +func (f *FakeKeptn) AddTaskEventHandler(eventType string, handler TaskHandler, options TaskHandlerOptions) { + f.Keptn.taskRegistry.Add(eventType, taskEntry{taskHandler: handler, eventFilters: options.Filters, taskHandlerOpts: options}) +} + +// AddTaskEventHandler registers a TaskHandler +// Deprecated: use AddTaskEventHandler func (f *FakeKeptn) AddTaskHandler(eventType string, handler TaskHandler, filters ...func(keptnHandle IKeptn, event KeptnEvent) bool) { f.AddTaskHandlerWithSubscriptionID(eventType, handler, "", filters...) } diff --git a/pkg/sdk/keptn_test.go b/pkg/sdk/keptn_test.go index 3ef01339..768ecae9 100644 --- a/pkg/sdk/keptn_test.go +++ b/pkg/sdk/keptn_test.go @@ -149,6 +149,65 @@ func Test_WhenReceivingAnEvent_StartedEventAndFinishedEventsAreSent(t *testing.T fakeKeptn.AssertSentEventType(t, 1, "sh.keptn.event.faketask.finished") } +func Test_WhenReceivingAnEvent_AndAutomaticEventResponseIsGloballyDiabled_StartedEventAndFinishedEventsAreNotSent(t *testing.T) { + taskHandler := &TaskHandlerMock{} + taskHandler.ExecuteFunc = func(keptnHandle IKeptn, event KeptnEvent) (interface{}, *Error) { return FakeTaskData{}, nil } + fakeKeptn := NewFakeKeptn("fake") + fakeKeptn.Keptn.automaticEventResponse = false + fakeKeptn.AddTaskEventHandler("sh.keptn.event.faketask.triggered", taskHandler, TaskHandlerOptions{}) + fakeKeptn.AddTaskEventHandler("sh.keptn.event.faketask2.triggered", taskHandler, TaskHandlerOptions{}) + fakeKeptn.NewEvent(models.KeptnContextExtendedCE{ + Data: v0_2_0.EventData{Project: "prj", Stage: "stg", Service: "svc"}, + ID: "id", + Shkeptncontext: "context", + Source: strutils.Stringp("source"), + Type: strutils.Stringp("sh.keptn.event.faketask.triggered"), + }) + + fakeKeptn.NewEvent(models.KeptnContextExtendedCE{ + Data: v0_2_0.EventData{Project: "prj", Stage: "stg", Service: "svc"}, + ID: "id", + Shkeptncontext: "context", + Source: strutils.Stringp("source"), + Type: strutils.Stringp("sh.keptn.event.faketask2.triggered"), + }) + + fakeKeptn.AssertNumberOfEventSent(t, 0) +} + +func Test_WhenReceivingAnEvent_AndAutomaticEventResponseIsDisabledOnTaskHandler_StartedEventAndFinishedEventsAreNotSent(t *testing.T) { + taskHandler := &TaskHandlerMock{} + taskHandler.ExecuteFunc = func(keptnHandle IKeptn, event KeptnEvent) (interface{}, *Error) { return FakeTaskData{}, nil } + fakeKeptn := NewFakeKeptn("fake") + fakeKeptn.AddTaskEventHandler("sh.keptn.event.faketask.triggered", taskHandler, TaskHandlerOptions{ + Filters: nil, + SkipAutomaticResponse: true, + }) + fakeKeptn.AddTaskEventHandler("sh.keptn.event.faketask2.triggered", taskHandler, TaskHandlerOptions{ + Filters: nil, + SkipAutomaticResponse: false, + }) + fakeKeptn.NewEvent(models.KeptnContextExtendedCE{ + Data: v0_2_0.EventData{Project: "prj", Stage: "stg", Service: "svc"}, + ID: "id", + Shkeptncontext: "context", + Source: strutils.Stringp("source"), + Type: strutils.Stringp("sh.keptn.event.faketask.triggered"), + }) + + fakeKeptn.NewEvent(models.KeptnContextExtendedCE{ + Data: v0_2_0.EventData{Project: "prj", Stage: "stg", Service: "svc"}, + ID: "id", + Shkeptncontext: "context", + Source: strutils.Stringp("source"), + Type: strutils.Stringp("sh.keptn.event.faketask2.triggered"), + }) + + fakeKeptn.AssertNumberOfEventSent(t, 2) + fakeKeptn.AssertSentEventType(t, 0, "sh.keptn.event.faketask2.started") + fakeKeptn.AssertSentEventType(t, 1, "sh.keptn.event.faketask2.finished") +} + func Test_WhenReceivingAnEvent_TaskHandlerFails(t *testing.T) { taskHandler := &TaskHandlerMock{} taskHandler.ExecuteFunc = func(keptnHandle IKeptn, event KeptnEvent) (interface{}, *Error) { diff --git a/pkg/sdk/taskregistry.go b/pkg/sdk/taskregistry.go index 7e253fb3..f2c030da 100644 --- a/pkg/sdk/taskregistry.go +++ b/pkg/sdk/taskregistry.go @@ -13,6 +13,8 @@ type taskEntry struct { taskHandler TaskHandler // eventFilters is a list of functions that are executed before a task is handled by the taskHandler. Only if all functions return 'true', the task will be handled eventFilters []func(keptnHandle IKeptn, event KeptnEvent) bool + // taskHandlerOpts are the options for the handler + taskHandlerOpts TaskHandlerOptions } func newTaskMap() *taskRegistry {