Skip to content
This repository has been archived by the owner on Jan 12, 2024. It is now read-only.

feat: send evaluation finished events to PGI level #822

Merged
merged 12 commits into from
Jun 13, 2022
33 changes: 18 additions & 15 deletions internal/action/action_finished_event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import (
"context"
"fmt"

"github.com/keptn-contrib/dynatrace-service/internal/dynatrace"
"github.com/keptn-contrib/dynatrace-service/internal/keptn"
keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0"
log "github.com/sirupsen/logrus"

"github.com/keptn-contrib/dynatrace-service/internal/dynatrace"
"github.com/keptn-contrib/dynatrace-service/internal/keptn"
)

type ActionFinishedEventHandler struct {
Expand All @@ -28,7 +29,7 @@ func NewActionFinishedEventHandler(event ActionFinishedAdapterInterface, dtClien
}

// HandleEvent handles an action finished event.
func (eh *ActionFinishedEventHandler) HandleEvent(workCtx context.Context, replyCtx context.Context) error {
func (eh *ActionFinishedEventHandler) HandleEvent(workCtx context.Context, _ context.Context) error {
j-poecher marked this conversation as resolved.
Show resolved Hide resolved
// lets find our dynatrace problem details for this remediation workflow
pid, err := eh.eClient.FindProblemID(eh.event)
if err != nil {
Expand All @@ -45,6 +46,10 @@ func (eh *ActionFinishedEventHandler) HandleEvent(workCtx context.Context, reply
eh.event.GetStatus())
dynatrace.NewProblemsClient(eh.dtClient).AddProblemComment(workCtx, pid, comment)

if eh.attachRules == nil {
eh.attachRules = createDefaultAttachRules(eh.event)
}

// https://github.com/keptn-contrib/dynatrace-service/issues/174
// Additionally to the problem comment, send Info or Configuration Change Event to the entities in Dynatrace to indicate that remediation actions have been executed
customProperties := createCustomProperties(eh.event, eh.eClient.GetImageAndTag(eh.event), bridgeURL)
Expand All @@ -58,19 +63,17 @@ func (eh *ActionFinishedEventHandler) HandleEvent(workCtx context.Context, reply
AttachRules: *eh.attachRules,
}

dynatrace.NewEventsClient(eh.dtClient).AddConfigurationEvent(workCtx, configurationEvent)
} else {
infoEvent := dynatrace.InfoEvent{
EventType: dynatrace.InfoEventType,
Source: eventSource,
Title: "Keptn Remediation Action Finished",
Description: "error during execution",
CustomProperties: customProperties,
AttachRules: *eh.attachRules,
}
return dynatrace.NewEventsClient(eh.dtClient).AddConfigurationEvent(workCtx, configurationEvent)
}

dynatrace.NewEventsClient(eh.dtClient).AddInfoEvent(workCtx, infoEvent)
infoEvent := dynatrace.InfoEvent{
EventType: dynatrace.InfoEventType,
Source: eventSource,
Title: "Keptn Remediation Action Finished",
Description: "error during execution",
CustomProperties: customProperties,
AttachRules: *eh.attachRules,
}

return nil
return dynatrace.NewEventsClient(eh.dtClient).AddInfoEvent(workCtx, infoEvent)
}
13 changes: 8 additions & 5 deletions internal/action/action_triggered_event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import (
"errors"
"fmt"

log "github.com/sirupsen/logrus"

"github.com/keptn-contrib/dynatrace-service/internal/dynatrace"
"github.com/keptn-contrib/dynatrace-service/internal/keptn"
log "github.com/sirupsen/logrus"
)

type ActionTriggeredEventHandler struct {
Expand All @@ -28,7 +29,7 @@ func NewActionTriggeredEventHandler(event ActionTriggeredAdapterInterface, dtCli
}

// HandleEvent handles an action triggered event.
func (eh *ActionTriggeredEventHandler) HandleEvent(workCtx context.Context, replyCtx context.Context) error {
func (eh *ActionTriggeredEventHandler) HandleEvent(workCtx context.Context, _ context.Context) error {
j-poecher marked this conversation as resolved.
Show resolved Hide resolved
pid, err := eh.eClient.FindProblemID(eh.event)
if err != nil {
log.WithError(err).Error("Could not find problem ID for event")
Expand All @@ -49,6 +50,10 @@ func (eh *ActionTriggeredEventHandler) HandleEvent(workCtx context.Context, repl

dynatrace.NewProblemsClient(eh.dtClient).AddProblemComment(workCtx, pid, comment)

if eh.attachRules == nil {
eh.attachRules = createDefaultAttachRules(eh.event)
}

// https://github.com/keptn-contrib/dynatrace-service/issues/174
// In addition to the problem comment, send Info and Configuration Change Event to the entities in Dynatrace to indicate that remediation actions have been executed
infoEvent := dynatrace.InfoEvent{
Expand All @@ -60,7 +65,5 @@ func (eh *ActionTriggeredEventHandler) HandleEvent(workCtx context.Context, repl
AttachRules: *eh.attachRules,
}

dynatrace.NewEventsClient(eh.dtClient).AddInfoEvent(workCtx, infoEvent)

return nil
return dynatrace.NewEventsClient(eh.dtClient).AddInfoEvent(workCtx, infoEvent)
}
108 changes: 108 additions & 0 deletions internal/action/common.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package action

import (
"context"

log "github.com/sirupsen/logrus"

"github.com/keptn-contrib/dynatrace-service/internal/adapter"
"github.com/keptn-contrib/dynatrace-service/internal/common"
"github.com/keptn-contrib/dynatrace-service/internal/dynatrace"
)

const eventSource = "Keptn dynatrace-service"
const bridgeURLKey = "Keptns Bridge"

const contextless = "CONTEXTLESS"

func createCustomProperties(a adapter.EventContentAdapter, imageAndTag common.ImageAndTag, bridgeURL string) map[string]string {
customProperties := map[string]string{
"Project": a.GetProject(),
Expand Down Expand Up @@ -39,3 +46,104 @@ func getValueFromLabels(a adapter.EventContentAdapter, key string, defaultValue
}
return defaultValue
}

// KeptnContext is a minimal subset of data needed for creating default attach rules
type KeptnContext interface {
Copy link
Contributor

Choose a reason for hiding this comment

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

[golint] reported by reviewdog 🐶
exported type KeptnContext should have comment or be unexported

GetProject() string
GetStage() string
GetService() string
}

func createDefaultAttachRules(keptnContext KeptnContext) *dynatrace.AttachRules {
return &dynatrace.AttachRules{
TagRule: []dynatrace.TagRule{
{
MeTypes: []string{"SERVICE"},
Tags: []dynatrace.TagEntry{
{
Context: contextless,
Key: "keptn_project",
Value: keptnContext.GetProject(),
},
{
Context: contextless,
Key: "keptn_stage",
Value: keptnContext.GetStage(),
},
{
Context: contextless,
Key: "keptn_service",
Value: keptnContext.GetService(),
},
},
},
},
}
}

// TimeframeFunc is the signature of a function returning a common.Timeframe or and error
type TimeframeFunc func() (*common.Timeframe, error)
Copy link
Contributor

Choose a reason for hiding this comment

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

[golint] reported by reviewdog 🐶
exported type TimeframeFunc should have comment or be unexported


func createOrUpdateAttachRules(ctx context.Context, client dynatrace.ClientInterface, existingAttachRules *dynatrace.AttachRules, imageAndTag common.ImageAndTag, event adapter.EventContentAdapter, timeframeFunc TimeframeFunc) (dynatrace.AttachRules, error) {
version := determineVersionFromTagOrLabel(imageAndTag, event)
if version == "" {
if existingAttachRules != nil {
log.WithField("customAttachRules", *existingAttachRules).Debug("no version information available - will use customer provided attach rules")
return *existingAttachRules, nil
}

log.Debug("no version information available - will use default attach rules")
return *createDefaultAttachRules(event), nil
}

timeframe, err := timeframeFunc()
if err != nil {
return dynatrace.AttachRules{}, err
}

entityClient := dynatrace.NewEntitiesClient(client)
pgis, err := entityClient.GetAllPGIsForKeptnServices(ctx, dynatrace.PGIQueryConfig{
Project: event.GetProject(),
Stage: event.GetStage(),
Service: event.GetService(),
Version: version,
From: timeframe.Start(),
To: timeframe.End(),
})
if err != nil {
return dynatrace.AttachRules{}, err
}

if existingAttachRules != nil {
if len(pgis) == 0 {
log.WithField("customAttachRules", *existingAttachRules).Debug("no PGIs found - will use customer provided attach rules only")
return *existingAttachRules, nil
}

log.WithFields(log.Fields{
"customAttachRules": *existingAttachRules,
"entityIds": pgis,
}).Debug("PGIs found and custom attach rules - will combine them")
existingAttachRules.EntityIds = append(existingAttachRules.EntityIds, pgis...)
return *existingAttachRules, nil
}

if len(pgis) == 0 {
log.Debug("no PGIs found and no custom attach rules - will use default attach rules")
return *createDefaultAttachRules(event), nil
}

log.WithField("PGIs", pgis).Debug("PGIs found - will use them only")
return dynatrace.AttachRules{
EntityIds: pgis,
}, nil
}

func determineVersionFromTagOrLabel(imageAndTag common.ImageAndTag, event adapter.EventContentAdapter) string {

if imageAndTag.HasTag() {
return imageAndTag.Tag()
}

return event.GetLabels()["releasesVersion"]
}
9 changes: 6 additions & 3 deletions internal/action/deployment_finished_event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ func NewDeploymentFinishedEventHandler(event DeploymentFinishedAdapterInterface,
}

// HandleEvent handles a deployment finished event.
func (eh *DeploymentFinishedEventHandler) HandleEvent(workCtx context.Context, replyCtx context.Context) error {
func (eh *DeploymentFinishedEventHandler) HandleEvent(workCtx context.Context, _ context.Context) error {
j-poecher marked this conversation as resolved.
Show resolved Hide resolved
imageAndTag := eh.eClient.GetImageAndTag(eh.event)

if eh.attachRules == nil {
eh.attachRules = createDefaultAttachRules(eh.event)
}

deploymentEvent := dynatrace.DeploymentEvent{
EventType: dynatrace.DeploymentEventType,
Source: eventSource,
Expand All @@ -41,6 +45,5 @@ func (eh *DeploymentFinishedEventHandler) HandleEvent(workCtx context.Context, r
AttachRules: *eh.attachRules,
}

dynatrace.NewEventsClient(eh.dtClient).AddDeploymentEvent(workCtx, deploymentEvent)
return nil
return dynatrace.NewEventsClient(eh.dtClient).AddDeploymentEvent(workCtx, deploymentEvent)
}
12 changes: 11 additions & 1 deletion internal/action/evaluation_finished_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import (
"fmt"

cloudevents "github.com/cloudevents/sdk-go/v2"
"github.com/keptn-contrib/dynatrace-service/internal/adapter"
keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0"

"github.com/keptn-contrib/dynatrace-service/internal/adapter"
)

type EvaluationFinishedAdapterInterface interface {
adapter.EventContentAdapter

GetEvaluationScore() float64
GetResult() keptnv2.ResultType
GetStartTime() string
GetEndTime() string
}

// EvaluationFinishedAdapter is a content adaptor for events of type sh.keptn.event.evaluation.finished
Expand Down Expand Up @@ -102,3 +105,10 @@ func (a EvaluationFinishedAdapter) GetEvaluationScore() float64 {
func (a EvaluationFinishedAdapter) GetResult() keptnv2.ResultType {
return a.event.Result
}

func (a EvaluationFinishedAdapter) GetStartTime() string {
Copy link
Contributor

Choose a reason for hiding this comment

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

[golint] reported by reviewdog 🐶
exported method EvaluationFinishedAdapter.GetStartTime should have comment or be unexported

return a.event.Evaluation.TimeStart
}
func (a EvaluationFinishedAdapter) GetEndTime() string {
Copy link
Contributor

Choose a reason for hiding this comment

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

[golint] reported by reviewdog 🐶
exported method EvaluationFinishedAdapter.GetEndTime should have comment or be unexported

return a.event.Evaluation.TimeEnd
}
35 changes: 27 additions & 8 deletions internal/action/evaluation_finished_event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"context"
"fmt"

"github.com/keptn-contrib/dynatrace-service/internal/dynatrace"
"github.com/keptn-contrib/dynatrace-service/internal/keptn"
keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0"
log "github.com/sirupsen/logrus"

"github.com/keptn-contrib/dynatrace-service/internal/common"
"github.com/keptn-contrib/dynatrace-service/internal/dynatrace"
"github.com/keptn-contrib/dynatrace-service/internal/keptn"
)

// EvaluationFinishedEventHandler handles an evaluation finished event.
Expand All @@ -29,7 +31,7 @@ func NewEvaluationFinishedEventHandler(event EvaluationFinishedAdapterInterface,
}

// HandleEvent handles an evaluation finished event.
func (eh *EvaluationFinishedEventHandler) HandleEvent(workCtx context.Context, replyCtx context.Context) error {
func (eh *EvaluationFinishedEventHandler) HandleEvent(workCtx context.Context, _ context.Context) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

[golint] reported by reviewdog 🐶
context.Context should be the first parameter of a function


isPartOfRemediation, err := eh.eClient.IsPartOfRemediation(eh.event)
if err != nil {
Expand All @@ -46,18 +48,22 @@ func (eh *EvaluationFinishedEventHandler) HandleEvent(workCtx context.Context, r
}
}

imageAndTag := eh.eClient.GetImageAndTag(eh.event)
attachRules, err := eh.createAttachRules(workCtx, imageAndTag)
if err != nil {
return fmt.Errorf("could not setup correct attach rules: %w", err)
}

infoEvent := dynatrace.InfoEvent{
EventType: dynatrace.InfoEventType,
Source: eventSource,
Title: eh.getTitle(isPartOfRemediation),
Description: fmt.Sprintf("Quality Gate Result in stage %s: %s (%.2f/100)", eh.event.GetStage(), eh.event.GetResult(), eh.event.GetEvaluationScore()),
CustomProperties: createCustomProperties(eh.event, eh.eClient.GetImageAndTag(eh.event), bridgeURL),
AttachRules: *eh.attachRules,
CustomProperties: createCustomProperties(eh.event, imageAndTag, bridgeURL),
AttachRules: attachRules,
}

dynatrace.NewEventsClient(eh.dtClient).AddInfoEvent(workCtx, infoEvent)

return nil
return dynatrace.NewEventsClient(eh.dtClient).AddInfoEvent(workCtx, infoEvent)
}

func (eh *EvaluationFinishedEventHandler) getTitle(isPartOfRemediation bool) string {
Expand All @@ -71,3 +77,16 @@ func (eh *EvaluationFinishedEventHandler) getTitle(isPartOfRemediation bool) str

return "Remediation action not successful"
}

func (eh *EvaluationFinishedEventHandler) createAttachRules(ctx context.Context, imageAndTag common.ImageAndTag) (dynatrace.AttachRules, error) {
timeframeFunc := func() (*common.Timeframe, error) {
timeframe, err := common.NewTimeframeParser(eh.event.GetStartTime(), eh.event.GetEndTime()).Parse()
if err != nil {
return nil, err
}

return timeframe, nil
}

return createOrUpdateAttachRules(ctx, eh.dtClient, eh.attachRules, imageAndTag, eh.event, timeframeFunc)
}
Loading