From a2a798a62c9ee64de27c80b421082f6a440feee9 Mon Sep 17 00:00:00 2001 From: Arthur Pitman Date: Tue, 12 Apr 2022 13:19:35 +0200 Subject: [PATCH] feat: Use keptn/go-utils to access Keptn APIs (#767) * Use goutils APIs in UniformClient Signed-off-by: Arthur Pitman * Add test for `GetIntegrationIDByName` Signed-off-by: Arthur Pitman * Use goutils APIs in ServiceClient Signed-off-by: Arthur Pitman * Use goutils APIs in ResourceClient and ConfigResourceClient Signed-off-by: Arthur Pitman * Remove unused LocalResourceClient Signed-off-by: Arthur Pitman * Use goutils APIs in EventClient Signed-off-by: Arthur Pitman * Remove unused APIClient Signed-off-by: Arthur Pitman * Linting Signed-off-by: Arthur Pitman * Method for creating integrations in test Signed-off-by: Arthur Pitman * Remove redundant `EventClientBaseInterface` Signed-off-by: Arthur Pitman * Wrap error Signed-off-by: Arthur Pitman * Remove `keptn` from filenames Signed-off-by: Arthur Pitman * Renaming types Signed-off-by: Arthur Pitman * Rename files Signed-off-by: Arthur Pitman * Fix error check Signed-off-by: Arthur Pitman * Remove redundant check Signed-off-by: Arthur Pitman * Shorten error messages Signed-off-by: Arthur Pitman * Improve problem ID parsing Signed-off-by: Arthur Pitman * Rename constant Signed-off-by: Arthur Pitman * Extract function for getting problem ID from labels Signed-off-by: Arthur Pitman * Extract problem ID from URL fragment and add test Signed-off-by: Arthur Pitman --- cmd/main.go | 16 ++- internal/common/common.go | 3 +- internal/config/dynatrace_config_getter.go | 4 +- .../evaluation_finished_event_handler.go | 3 +- internal/event_handler/error_handler.go | 17 ++- internal/event_handler/handler.go | 43 +++--- ...tn_resource_client.go => config_client.go} | 49 +++---- ...{keptn_event_client.go => event_client.go} | 103 +++++---------- internal/keptn/keptn_api_client.go | 89 ------------- internal/keptn/keptn_labels_helper.go | 28 +++- internal/keptn/keptn_labels_helper_test.go | 124 ++++++++++++++++++ internal/keptn/keptn_local_resource_client.go | 67 ---------- internal/keptn/keptn_service_client.go | 74 ----------- internal/keptn/keptn_uniform_client.go | 62 --------- ..._resource_client.go => resource_client.go} | 78 +++++------ internal/keptn/service_client.go | 60 +++++++++ internal/keptn/uniform_client.go | 50 +++++++ internal/keptn/uniform_client_test.go | 112 ++++++++++++++++ internal/monitoring/configuration.go | 4 +- .../configure_monitoring_event_handler.go | 4 +- internal/monitoring/metric_events_creation.go | 4 +- internal/onboard/service_sync.go | 19 ++- .../problem/action_finished_event_handler.go | 3 +- .../problem/action_started_event_handler.go | 3 +- .../problem/action_triggered_event_handler.go | 3 +- internal/problem/problem_events_factory.go | 7 +- .../sli/get_sli_triggered_event_handler.go | 29 +--- ...rieve_metrics_from_dashboard_fails_test.go | 2 +- ...er_retrieve_metrics_from_dashboard_test.go | 4 +- internal/sli/test_helper_test.go | 4 +- 30 files changed, 567 insertions(+), 501 deletions(-) rename internal/keptn/{keptn_resource_client.go => config_client.go} (51%) rename internal/keptn/{keptn_event_client.go => event_client.go} (55%) delete mode 100644 internal/keptn/keptn_api_client.go create mode 100644 internal/keptn/keptn_labels_helper_test.go delete mode 100644 internal/keptn/keptn_local_resource_client.go delete mode 100644 internal/keptn/keptn_service_client.go delete mode 100644 internal/keptn/keptn_uniform_client.go rename internal/keptn/{keptn_config_resource_client.go => resource_client.go} (71%) create mode 100644 internal/keptn/service_client.go create mode 100644 internal/keptn/uniform_client.go create mode 100644 internal/keptn/uniform_client_test.go diff --git a/cmd/main.go b/cmd/main.go index 10589593d..b3abe9960 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -36,7 +36,13 @@ func _main(args []string, envCfg envConfig) int { if env.IsServiceSyncEnabled() { go func() { - onboard.NewDefaultServiceSynchronizer().Run() + serviceSynchronizer, err := onboard.NewDefaultServiceSynchronizer() + if err != nil { + log.WithError(err).Error("Could not create service synchronizer") + return + } + + serviceSynchronizer.Run() }() } @@ -58,7 +64,13 @@ func _main(args []string, envCfg envConfig) int { } func gotEvent(ctx context.Context, event cloudevents.Event) error { - err := event_handler.NewEventHandler(event).HandleEvent() + handler, err := event_handler.NewEventHandler(event) + if err != nil { + log.WithError(err).Error("Could not create event handler") + return err + } + + err = handler.HandleEvent() if err != nil { log.WithError(err).Error("HandleEvent() returned an error") } diff --git a/internal/common/common.go b/internal/common/common.go index a3bacf208..79ef910d2 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -16,7 +16,8 @@ import ( "github.com/keptn/go-utils/pkg/lib/keptn" ) -const KEPTNSBRIDGE_LABEL = "Keptns Bridge" +const BridgeLabel = "Keptns Bridge" +const ProblemURLLabel = "Problem URL" // ShipyardControllerURLEnvironmentVariableName is the name of the environment variable for specifying the shipyard controller URL. const ShipyardControllerURLEnvironmentVariableName = "SHIPYARD_CONTROLLER" diff --git a/internal/config/dynatrace_config_getter.go b/internal/config/dynatrace_config_getter.go index 6ea79e388..44e1112dd 100644 --- a/internal/config/dynatrace_config_getter.go +++ b/internal/config/dynatrace_config_getter.go @@ -16,10 +16,10 @@ type DynatraceConfigProvider interface { } type DynatraceConfigGetter struct { - resourceClient keptn.DynatraceConfigResourceClientInterface + resourceClient keptn.DynatraceConfigReaderInterface } -func NewDynatraceConfigGetter(client keptn.DynatraceConfigResourceClientInterface) *DynatraceConfigGetter { +func NewDynatraceConfigGetter(client keptn.DynatraceConfigReaderInterface) *DynatraceConfigGetter { return &DynatraceConfigGetter{ resourceClient: client, } diff --git a/internal/deployment/evaluation_finished_event_handler.go b/internal/deployment/evaluation_finished_event_handler.go index 4c76ef120..f53bb1402 100644 --- a/internal/deployment/evaluation_finished_event_handler.go +++ b/internal/deployment/evaluation_finished_event_handler.go @@ -2,6 +2,7 @@ package deployment import ( "fmt" + "github.com/keptn-contrib/dynatrace-service/internal/common" "github.com/keptn-contrib/dynatrace-service/internal/dynatrace" "github.com/keptn-contrib/dynatrace-service/internal/keptn" @@ -50,7 +51,7 @@ func (eh *EvaluationFinishedEventHandler) HandleEvent() error { pid, err := eh.eClient.FindProblemID(eh.event) if err == nil && pid != "" { // Comment we push over - comment := fmt.Sprintf("[Keptn remediation evaluation](%s) resulted in %s (%.2f/100)", eh.event.GetLabels()[common.KEPTNSBRIDGE_LABEL], eh.event.GetResult(), eh.event.GetEvaluationScore()) + comment := fmt.Sprintf("[Keptn remediation evaluation](%s) resulted in %s (%.2f/100)", eh.event.GetLabels()[common.BridgeLabel], eh.event.GetResult(), eh.event.GetEvaluationScore()) // this is posting the Event on the problem as a comment dynatrace.NewProblemsClient(eh.dtClient).AddProblemComment(pid, comment) diff --git a/internal/event_handler/error_handler.go b/internal/event_handler/error_handler.go index 39444bdf5..fbe2913ef 100644 --- a/internal/event_handler/error_handler.go +++ b/internal/event_handler/error_handler.go @@ -12,15 +12,19 @@ import ( "github.com/keptn-contrib/dynatrace-service/internal/sli" ) +// ErrorHandler handles errors by trying to send them to Keptn Uniform. type ErrorHandler struct { - err error - evt cloudevents.Event + err error + evt cloudevents.Event + uniformClient keptn.UniformClientInterface } -func NewErrorHandler(err error, event cloudevents.Event) *ErrorHandler { +// NewErrorHandler creates a new ErrorHandler for the specified error, event and UniformClientInterface. +func NewErrorHandler(err error, event cloudevents.Event, uniformClient keptn.UniformClientInterface) *ErrorHandler { return &ErrorHandler{ - err: err, - evt: event, + err: err, + evt: event, + uniformClient: uniformClient, } } @@ -59,8 +63,7 @@ func (eh ErrorHandler) sendErroredGetSLIFinishedEvent(keptnClient *keptn.Client) } func (eh ErrorHandler) sendErrorEvent(keptnClient *keptn.Client) error { - uniformClient := keptn.NewDefaultUniformClient() - integrationID, err := uniformClient.GetIntegrationIDFor(event.GetEventSource()) + integrationID, err := eh.uniformClient.GetIntegrationIDByName(event.GetEventSource()) if err != nil { log.WithError(err).Error("Could not retrieve integration ID from Keptn Uniform") // no need to continue here, message will not show up in Uniform diff --git a/internal/event_handler/handler.go b/internal/event_handler/handler.go index a6310add3..22e9d2957 100644 --- a/internal/event_handler/handler.go +++ b/internal/event_handler/handler.go @@ -5,11 +5,13 @@ import ( "fmt" cloudevents "github.com/cloudevents/sdk-go/v2" + api "github.com/keptn/go-utils/pkg/api/utils" keptnevents "github.com/keptn/go-utils/pkg/lib" keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0" 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/config" "github.com/keptn-contrib/dynatrace-service/internal/credentials" "github.com/keptn-contrib/dynatrace-service/internal/deployment" @@ -20,22 +22,29 @@ import ( "github.com/keptn-contrib/dynatrace-service/internal/sli" ) +// DynatraceEventHandler is the common interface for all event handlers. type DynatraceEventHandler interface { HandleEvent() error } -func NewEventHandler(event cloudevents.Event) DynatraceEventHandler { - eventHandler, err := getEventHandler(event) +// NewEventHandler creates a new DynatraceEventHandler for the specified event or returns an error. +func NewEventHandler(event cloudevents.Event) (DynatraceEventHandler, error) { + keptnAPISet, err := api.New(common.GetShipyardControllerURL()) + if err != nil { + return nil, fmt.Errorf("could not create Keptn API set: %w", err) + } + + eventHandler, err := getEventHandler(event, *keptnAPISet) if err != nil { err = fmt.Errorf("cannot handle event: %w", err) log.Error(err.Error()) - return NewErrorHandler(err, event) + return NewErrorHandler(err, event, keptn.NewUniformClient(keptnAPISet.UniformV1())), nil } - return eventHandler + return eventHandler, nil } -func getEventHandler(event cloudevents.Event) (DynatraceEventHandler, error) { +func getEventHandler(event cloudevents.Event, keptnAPISet api.APISet) (DynatraceEventHandler, error) { log.WithField("eventType", event.Type()).Debug("Received event") keptnEvent, err := getEventAdapter(event) @@ -52,7 +61,7 @@ func getEventHandler(event cloudevents.Event) (DynatraceEventHandler, error) { return nil, errors.New("event has no project") } - dynatraceConfigGetter := config.NewDynatraceConfigGetter(keptn.NewDefaultResourceClient()) + dynatraceConfigGetter := config.NewDynatraceConfigGetter(keptn.NewConfigClient(keptn.NewResourceClient(keptnAPISet.ResourcesV1()))) dynatraceConfig, err := dynatraceConfigGetter.GetDynatraceConfig(keptnEvent) if err != nil { return nil, fmt.Errorf("could not get configuration: %w", err) @@ -77,29 +86,29 @@ func getEventHandler(event cloudevents.Event) (DynatraceEventHandler, error) { switch aType := keptnEvent.(type) { case *monitoring.ConfigureMonitoringAdapter: - return monitoring.NewConfigureMonitoringEventHandler(keptnEvent.(*monitoring.ConfigureMonitoringAdapter), dtClient, kClient, keptn.NewDefaultResourceClient(), keptn.NewDefaultServiceClient()), nil + return monitoring.NewConfigureMonitoringEventHandler(keptnEvent.(*monitoring.ConfigureMonitoringAdapter), dtClient, kClient, keptn.NewConfigClient(keptn.NewResourceClient(keptnAPISet.ResourcesV1())), keptn.NewServiceClient(keptnAPISet.ServicesV1(), keptnAPISet.APIV1())), nil case *problem.ProblemAdapter: return problem.NewProblemEventHandler(keptnEvent.(*problem.ProblemAdapter), kClient), nil case *problem.ActionTriggeredAdapter: - return problem.NewActionTriggeredEventHandler(keptnEvent.(*problem.ActionTriggeredAdapter), dtClient, keptn.NewDefaultEventClient(), dynatraceConfig.AttachRules), nil + return problem.NewActionTriggeredEventHandler(keptnEvent.(*problem.ActionTriggeredAdapter), dtClient, keptn.NewEventClient(keptnAPISet.EventsV1()), dynatraceConfig.AttachRules), nil case *problem.ActionStartedAdapter: - return problem.NewActionStartedEventHandler(keptnEvent.(*problem.ActionStartedAdapter), dtClient, keptn.NewDefaultEventClient()), nil + return problem.NewActionStartedEventHandler(keptnEvent.(*problem.ActionStartedAdapter), dtClient, keptn.NewEventClient(keptnAPISet.EventsV1())), nil case *problem.ActionFinishedAdapter: - return problem.NewActionFinishedEventHandler(keptnEvent.(*problem.ActionFinishedAdapter), dtClient, keptn.NewDefaultEventClient(), dynatraceConfig.AttachRules), nil + return problem.NewActionFinishedEventHandler(keptnEvent.(*problem.ActionFinishedAdapter), dtClient, keptn.NewEventClient(keptnAPISet.EventsV1()), dynatraceConfig.AttachRules), nil case *sli.GetSLITriggeredAdapter: - return sli.NewGetSLITriggeredHandler(keptnEvent.(*sli.GetSLITriggeredAdapter), dtClient, kClient, keptn.NewDefaultResourceClient(), dynatraceConfig.DtCreds, dynatraceConfig.Dashboard), nil + return sli.NewGetSLITriggeredHandler(keptnEvent.(*sli.GetSLITriggeredAdapter), dtClient, kClient, keptn.NewConfigClient(keptn.NewResourceClient(keptnAPISet.ResourcesV1())), dynatraceConfig.DtCreds, dynatraceConfig.Dashboard), nil case *deployment.DeploymentFinishedAdapter: - return deployment.NewDeploymentFinishedEventHandler(keptnEvent.(*deployment.DeploymentFinishedAdapter), dtClient, keptn.NewDefaultEventClient(), dynatraceConfig.AttachRules), nil + return deployment.NewDeploymentFinishedEventHandler(keptnEvent.(*deployment.DeploymentFinishedAdapter), dtClient, keptn.NewEventClient(keptnAPISet.EventsV1()), dynatraceConfig.AttachRules), nil case *deployment.TestTriggeredAdapter: - return deployment.NewTestTriggeredEventHandler(keptnEvent.(*deployment.TestTriggeredAdapter), dtClient, keptn.NewDefaultEventClient(), dynatraceConfig.AttachRules), nil + return deployment.NewTestTriggeredEventHandler(keptnEvent.(*deployment.TestTriggeredAdapter), dtClient, keptn.NewEventClient(keptnAPISet.EventsV1()), dynatraceConfig.AttachRules), nil case *deployment.TestFinishedAdapter: - return deployment.NewTestFinishedEventHandler(keptnEvent.(*deployment.TestFinishedAdapter), dtClient, keptn.NewDefaultEventClient(), dynatraceConfig.AttachRules), nil + return deployment.NewTestFinishedEventHandler(keptnEvent.(*deployment.TestFinishedAdapter), dtClient, keptn.NewEventClient(keptnAPISet.EventsV1()), dynatraceConfig.AttachRules), nil case *deployment.EvaluationFinishedAdapter: - return deployment.NewEvaluationFinishedEventHandler(keptnEvent.(*deployment.EvaluationFinishedAdapter), dtClient, keptn.NewDefaultEventClient(), dynatraceConfig.AttachRules), nil + return deployment.NewEvaluationFinishedEventHandler(keptnEvent.(*deployment.EvaluationFinishedAdapter), dtClient, keptn.NewEventClient(keptnAPISet.EventsV1()), dynatraceConfig.AttachRules), nil case *deployment.ReleaseTriggeredAdapter: - return deployment.NewReleaseTriggeredEventHandler(keptnEvent.(*deployment.ReleaseTriggeredAdapter), dtClient, keptn.NewDefaultEventClient(), dynatraceConfig.AttachRules), nil + return deployment.NewReleaseTriggeredEventHandler(keptnEvent.(*deployment.ReleaseTriggeredAdapter), dtClient, keptn.NewEventClient(keptnAPISet.EventsV1()), dynatraceConfig.AttachRules), nil default: - return NewErrorHandler(fmt.Errorf("this should not have happened, we are missing an implementation for: %T", aType), event), nil + return NewErrorHandler(fmt.Errorf("this should not have happened, we are missing an implementation for: %T", aType), event, keptn.NewUniformClient(keptnAPISet.UniformV1())), nil } } diff --git a/internal/keptn/keptn_resource_client.go b/internal/keptn/config_client.go similarity index 51% rename from internal/keptn/keptn_resource_client.go rename to internal/keptn/config_client.go index ba439bee3..f1497a4fc 100644 --- a/internal/keptn/keptn_resource_client.go +++ b/internal/keptn/config_client.go @@ -10,23 +10,29 @@ import ( "github.com/keptn-contrib/dynatrace-service/internal/dynatrace" ) -type SLOResourceReaderInterface interface { +// SLOReaderInterface provides functionality for getting SLOs. +type SLOReaderInterface interface { // GetSLOs gets the SLOs stored for exactly the specified project, stage and service. GetSLOs(project string, stage string, service string) (*keptn.ServiceLevelObjectives, error) } -type SLIAndSLOResourceWriterInterface interface { + +// SLIAndSLOWriterInterface provides functionality for uploading SLIs and SLOs. +type SLIAndSLOWriterInterface interface { // UploadSLIs uploads the SLIs for the specified project, stage and service. UploadSLIs(project string, stage string, service string, slis *dynatrace.SLI) error // UploadSLOs uploads the SLOs for the specified project, stage and service. UploadSLOs(project string, stage string, service string, slos *keptn.ServiceLevelObjectives) error } -type ResourceClientInterface interface { - SLOResourceReaderInterface - SLIAndSLOResourceWriterInterface + +// SLOAndSLIClientInterface provides functionality for getting SLOs and uploading SLIs and SLOs. +type SLOAndSLIClientInterface interface { + SLOReaderInterface + SLIAndSLOWriterInterface } -type DynatraceConfigResourceClientInterface interface { +// DynatraceConfigReaderInterface provides functionality for getting a Dynatrace config. +type DynatraceConfigReaderInterface interface { // GetDynatraceConfig gets the Dynatrace config for the specified project, stage and service, checking first on the service, then stage and then project level. GetDynatraceConfig(project string, stage string, service string) (string, error) } @@ -35,25 +41,20 @@ const sloFilename = "slo.yaml" const sliFilename = "dynatrace/sli.yaml" const configFilename = "dynatrace/dynatrace.conf.yaml" -// ResourceClient is the default implementation for the *ResourceClientInterfaces using a ConfigResourceClientInterface -type ResourceClient struct { - client ConfigResourceClientInterface -} - -// NewDefaultResourceClient creates a new ResourceClient with a default Keptn resource handler for the configuration service -func NewDefaultResourceClient() *ResourceClient { - return NewResourceClient( - NewDefaultConfigResourceClient()) +// ConfigClient is the default implementation for ResourceClientInterface using a ConfigResourceClientInterface. +type ConfigClient struct { + client ResourceClientInterface } -// NewResourceClient creates a new ResourceClient with a Keptn resource handler for the configuration service -func NewResourceClient(client ConfigResourceClientInterface) *ResourceClient { - return &ResourceClient{ +// NewConfigClient creates a new ConfigClient with a Keptn resource handler for the configuration service. +func NewConfigClient(client ResourceClientInterface) *ConfigClient { + return &ConfigClient{ client: client, } } -func (rc *ResourceClient) GetSLOs(project string, stage string, service string) (*keptn.ServiceLevelObjectives, error) { +// GetSLOs gets the SLOs stored for exactly the specified project, stage and service. +func (rc *ConfigClient) GetSLOs(project string, stage string, service string) (*keptn.ServiceLevelObjectives, error) { resource, err := rc.client.GetServiceResource(project, stage, service, sloFilename) if err != nil { return nil, err @@ -68,8 +69,8 @@ func (rc *ResourceClient) GetSLOs(project string, stage string, service string) return slos, nil } -func (rc *ResourceClient) UploadSLOs(project string, stage string, service string, slos *keptn.ServiceLevelObjectives) error { - // and now we save it back to Keptn +// UploadSLOs uploads the SLOs for the specified project, stage and service. +func (rc *ConfigClient) UploadSLOs(project string, stage string, service string, slos *keptn.ServiceLevelObjectives) error { yamlAsByteArray, err := yaml.Marshal(slos) if err != nil { return fmt.Errorf("could not convert SLOs to YAML: %s", err) @@ -78,7 +79,8 @@ func (rc *ResourceClient) UploadSLOs(project string, stage string, service strin return rc.client.UploadResource(yamlAsByteArray, sloFilename, project, stage, service) } -func (rc *ResourceClient) UploadSLIs(project string, stage string, service string, slis *dynatrace.SLI) error { +// UploadSLIs uploads the SLIs for the specified project, stage and service. +func (rc *ConfigClient) UploadSLIs(project string, stage string, service string, slis *dynatrace.SLI) error { yamlAsByteArray, err := yaml.Marshal(slis) if err != nil { return fmt.Errorf("could not convert SLIs to YAML: %s", err) @@ -87,6 +89,7 @@ func (rc *ResourceClient) UploadSLIs(project string, stage string, service strin return rc.client.UploadResource(yamlAsByteArray, sliFilename, project, stage, service) } -func (rc *ResourceClient) GetDynatraceConfig(project string, stage string, service string) (string, error) { +// GetDynatraceConfig gets the Dynatrace config for the specified project, stage and service, checking first on the service, then stage and then project level. +func (rc *ConfigClient) GetDynatraceConfig(project string, stage string, service string) (string, error) { return rc.client.GetResource(project, stage, service, configFilename) } diff --git a/internal/keptn/keptn_event_client.go b/internal/keptn/event_client.go similarity index 55% rename from internal/keptn/keptn_event_client.go rename to internal/keptn/event_client.go index 6a03a7705..b71c39620 100644 --- a/internal/keptn/keptn_event_client.go +++ b/internal/keptn/event_client.go @@ -3,67 +3,44 @@ package keptn import ( "errors" "fmt" + "strings" + "github.com/keptn-contrib/dynatrace-service/internal/adapter" "github.com/keptn-contrib/dynatrace-service/internal/common" - "github.com/keptn/go-utils/pkg/api/models" - keptnapi "github.com/keptn/go-utils/pkg/api/utils" + api "github.com/keptn/go-utils/pkg/api/utils" keptncommon "github.com/keptn/go-utils/pkg/lib" keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0" log "github.com/sirupsen/logrus" - "os" - "strings" ) -type EventClientBaseInterface interface { - GetEvents(filter *keptnapi.EventFilter) ([]*models.KeptnContextExtendedCE, error) -} - -type EventClientBase struct { - client *keptnapi.EventHandler -} - -func NewEventClientBase() *EventClientBase { - return &EventClientBase{ - client: keptnapi.NewEventHandler(os.Getenv("DATASTORE")), - } -} - -func (c *EventClientBase) GetEvents(filter *keptnapi.EventFilter) ([]*models.KeptnContextExtendedCE, error) { - events, err := c.client.GetEvents(filter) - if err != nil { - return nil, fmt.Errorf("could not get events: %s", err.GetMessage()) - } - - return events, nil -} - -const problemURLLabel = "Problem URL" - +// EventClientInterface encapsulates functionality built on top of Keptn events. type EventClientInterface interface { + // IsPartOfRemediation checks whether the sequence includes a remediation triggered event or returns an error. IsPartOfRemediation(event adapter.EventContentAdapter) (bool, error) + + // FindProblemID finds the Problem ID that is associated with the specified Keptn event or returns an error. FindProblemID(keptnEvent adapter.EventContentAdapter) (string, error) + + // GetImageAndTag extracts the image and tag associated with a deployment triggered as part of the sequence. GetImageAndTag(keptnEvent adapter.EventContentAdapter) common.ImageAndTag } +// EventClient implements offers EventClientInterface using api.EventsV1Interface. type EventClient struct { - client EventClientBaseInterface + client api.EventsV1Interface } -func NewEventClient(client EventClientBaseInterface) *EventClient { +// NewEventClient creates a new EventClient using the specified api.EventsV1Interface. +func NewEventClient(client api.EventsV1Interface) *EventClient { return &EventClient{ client: client, } } -func NewDefaultEventClient() *EventClient { - return NewEventClient( - NewEventClientBase()) -} - -// IsPartOfRemediation checks whether the evaluation.finished event is part of a remediation task sequence +// IsPartOfRemediation checks whether the sequence includes a remediation triggered event or returns an error. func (c *EventClient) IsPartOfRemediation(event adapter.EventContentAdapter) (bool, error) { events, err := c.client.GetEvents( - &keptnapi.EventFilter{ + &api.EventFilter{ Project: event.GetProject(), Stage: event.GetStage(), Service: event.GetService(), @@ -72,67 +49,59 @@ func (c *EventClient) IsPartOfRemediation(event adapter.EventContentAdapter) (bo }) if err != nil { - return false, err + return false, errors.New(err.GetMessage()) } - if events == nil || len(events) == 0 { + if len(events) == 0 { return false, nil } return true, nil } -// FindProblemID finds the Problem ID that is associated with this Keptn Workflow -// It first parses it from Problem URL label - if it cant be found there it will look for the Initial Problem Open Event and gets the ID from there! +// FindProblemID finds the Problem ID that is associated with the specified Keptn event or returns an error. +// It first parses it from Problem URL label and if it cant be found there it will look for the Initial Problem Open Event and gets the ID from there. func (c *EventClient) FindProblemID(keptnEvent adapter.EventContentAdapter) (string, error) { - // Step 1 - see if we have a Problem Url in the labels - // iterate through the labels and find Problem URL - for labelName, labelValue := range keptnEvent.GetLabels() { - if labelName == problemURLLabel { - // the value should be of form https://dynatracetenant/#problems/problemdetails;pid=8485558334848276629_1604413609638V2 - // so - lets get the last part after pid= - - ix := strings.LastIndex(labelValue, ";pid=") - if ix > 0 { - return labelValue[ix+5:], nil - } - } + problemID := TryGetProblemIDFromLabels(keptnEvent) + if problemID != "" { + return problemID, nil } // Step 2 - lets see if we have a ProblemOpenEvent for this KeptnContext - if so - we try to extract the Problem ID - events, err := c.client.GetEvents( - &keptnapi.EventFilter{ + events, mErr := c.client.GetEvents( + &api.EventFilter{ Project: keptnEvent.GetProject(), EventType: keptncommon.ProblemOpenEventType, KeptnContext: keptnEvent.GetShKeptnContext(), }) - if err != nil { - return "", fmt.Errorf("cannot send DT problem comment: Could not retrieve problem.open event for incoming event: %s", err.Error()) + if mErr != nil { + return "", fmt.Errorf("could not retrieve problem.open event for incoming event: %s", mErr.GetMessage()) } if len(events) == 0 { - return "", errors.New("cannot send DT problem comment: Could not retrieve problem.open event for incoming event: no events returned") + return "", errors.New("could not retrieve problem.open event for incoming event: no events returned") } problemOpenEvent := &keptncommon.ProblemEventData{} - err = keptnv2.Decode(events[0].Data, problemOpenEvent) + err := keptnv2.Decode(events[0].Data, problemOpenEvent) if err != nil { - return "", fmt.Errorf("could not decode problem.open event: %s", err.Error()) + return "", fmt.Errorf("could not decode problem.open event: %w", err) } if problemOpenEvent.PID == "" { - return "", errors.New("cannot send DT problem comment: No problem ID is included in the event") + return "", errors.New("no problem ID is included in problem.open event") } return problemOpenEvent.PID, nil } +// GetImageAndTag extracts the image and tag associated with a deployment triggered as part of the sequence. func (c *EventClient) GetImageAndTag(event adapter.EventContentAdapter) common.ImageAndTag { - events, err := c.client.GetEvents( - &keptnapi.EventFilter{ + events, mErr := c.client.GetEvents( + &api.EventFilter{ Project: event.GetProject(), Stage: event.GetStage(), Service: event.GetService(), @@ -140,8 +109,8 @@ func (c *EventClient) GetImageAndTag(event adapter.EventContentAdapter) common.I KeptnContext: event.GetShKeptnContext(), }) - if err != nil { - log.WithError(err).Error("Could not retrieve image and tag for event") + if mErr != nil { + log.WithError(errors.New(mErr.GetMessage())).Error("Could not retrieve image and tag for event") return common.NewNotAvailableImageAndTag() } @@ -150,7 +119,7 @@ func (c *EventClient) GetImageAndTag(event adapter.EventContentAdapter) common.I } triggeredData := &keptnv2.DeploymentTriggeredEventData{} - err = keptnv2.Decode(events[0].Data, triggeredData) + err := keptnv2.Decode(events[0].Data, triggeredData) if err != nil { log.WithError(err).Error("Could not decode event data") return common.NewNotAvailableImageAndTag() diff --git a/internal/keptn/keptn_api_client.go b/internal/keptn/keptn_api_client.go deleted file mode 100644 index ac34c19b8..000000000 --- a/internal/keptn/keptn_api_client.go +++ /dev/null @@ -1,89 +0,0 @@ -package keptn - -import ( - "encoding/json" - "fmt" - - "github.com/keptn-contrib/dynatrace-service/internal/rest" -) - -type APIClientInterface interface { - Get(apiPath string) ([]byte, error) - Post(apiPath string, body []byte) ([]byte, error) -} - -type APIClient struct { - restClient rest.ClientInterface -} - -func NewAPIClient(client rest.ClientInterface) *APIClient { - return &APIClient{ - restClient: client, - } -} - -func (c *APIClient) Get(apiPath string) ([]byte, error) { - body, status, url, err := c.restClient.Get(apiPath) - if err != nil { - return nil, err - } - - return validateResponse(body, status, url) -} - -func (c *APIClient) Post(apiPath string, body []byte) ([]byte, error) { - body, status, url, err := c.restClient.Post(apiPath, body) - if err != nil { - return nil, err - } - - return validateResponse(body, status, url) -} - -// genericAPIErrorDTO will support multiple Keptn API errors -type genericAPIErrorDTO struct { - Code int `json:"code"` - ErrorCode int `json:"errorCode"` - Message string `json:"message"` -} - -func (e *genericAPIErrorDTO) status() int { - if e.Code != 0 { - return e.Code - } - - return e.ErrorCode -} - -type APIError struct { - status int - message string - url string -} - -func (e *APIError) Error() string { - return fmt.Sprintf("Keptn API error (%d): %s - URL: %s", e.status, e.message, e.url) -} - -func validateResponse(body []byte, status int, url string) ([]byte, error) { - if status < 200 || status >= 300 { - // try to get the error information - keptnAPIError := &genericAPIErrorDTO{} - err := json.Unmarshal(body, keptnAPIError) - if err != nil { - return body, &APIError{ - status: status, - message: string(body), - url: url, - } - } - - return nil, &APIError{ - status: keptnAPIError.status(), - message: keptnAPIError.Message, - url: url, - } - } - - return body, nil -} diff --git a/internal/keptn/keptn_labels_helper.go b/internal/keptn/keptn_labels_helper.go index d64b6e6aa..ec611b084 100644 --- a/internal/keptn/keptn_labels_helper.go +++ b/internal/keptn/keptn_labels_helper.go @@ -1,6 +1,10 @@ package keptn import ( + "net/url" + "strings" + + "github.com/keptn-contrib/dynatrace-service/internal/adapter" "github.com/keptn-contrib/dynatrace-service/internal/common" "github.com/keptn-contrib/dynatrace-service/internal/credentials" ) @@ -21,6 +25,28 @@ func AddOptionalKeptnBridgeUrlToLabels(labels map[string]string, shKeptnContext return labels } - labels[common.KEPTNSBRIDGE_LABEL] = keptnBridgeURL + "/trace/" + shKeptnContext + labels[common.BridgeLabel] = keptnBridgeURL + "/trace/" + shKeptnContext return labels } + +// TryGetProblemIDFromLabels tries to extract the problem ID from a "Problem URL" label or returns "" if it cannot be done. +// The value should be of form https://dynatracetenant/#problems/problemdetails;pid=8485558334848276629_1604413609638V2 +func TryGetProblemIDFromLabels(keptnEvent adapter.EventContentAdapter) string { + for labelName, labelValue := range keptnEvent.GetLabels() { + if strings.EqualFold(labelName, common.ProblemURLLabel) { + u, err := url.Parse(labelValue) + if err != nil { + return "" + } + + ix := strings.LastIndex(u.Fragment, ";pid=") + if ix == -1 { + return "" + } + + return u.Fragment[ix+5:] + } + } + + return "" +} diff --git a/internal/keptn/keptn_labels_helper_test.go b/internal/keptn/keptn_labels_helper_test.go new file mode 100644 index 000000000..a3378f182 --- /dev/null +++ b/internal/keptn/keptn_labels_helper_test.go @@ -0,0 +1,124 @@ +package keptn + +import ( + "testing" + + "github.com/keptn-contrib/dynatrace-service/internal/adapter" + "github.com/stretchr/testify/assert" +) + +const testProblemURLLabelName = "Problem URL" + +type testEventWithLabels struct { + labels map[string]string +} + +func TestTryGetProblemIDFromLabels(t *testing.T) { + + tests := []struct { + name string + keptnEvent adapter.EventContentAdapter + expectedProblemID string + }{ + { + name: "Label with single valid problem ID - works", + keptnEvent: testEventWithLabels{ + labels: map[string]string{ + testProblemURLLabelName: "https://dynatracetenant/#problems/problemdetails;gf=all;pid=2132340906857553706_1649682528607V2", + }, + }, + expectedProblemID: "2132340906857553706_1649682528607V2", + }, + { + name: "Label with two problem IDs - works, returns second", + keptnEvent: testEventWithLabels{ + labels: map[string]string{ + testProblemURLLabelName: "https://dynatracetenant/#problems/problemdetails;gf=all;pid=2132340906857553706_1649682528607V2;pid=8485558334848276629_1604413609638V2", + }, + }, + expectedProblemID: "8485558334848276629_1604413609638V2", + }, + { + + name: "no labels - returns empty", + keptnEvent: testEventWithLabels{}, + expectedProblemID: "", + }, + { + + name: "Label with with invalid URL - returns empty", + keptnEvent: testEventWithLabels{ + labels: map[string]string{ + testProblemURLLabelName: "problems/problemdetails?gf=all;pid=2132340906857553706_1649682528607V2", + }, + }, + expectedProblemID: "", + }, + { + + name: "Label with with no fragment - returns empty", + keptnEvent: testEventWithLabels{ + labels: map[string]string{ + testProblemURLLabelName: "https://dynatracetenant/problems/problemdetails?gf=all;pid=2132340906857553706_1649682528607V2", + }, + }, + expectedProblemID: "", + }, + { + + name: "some other label - returns empty", + keptnEvent: testEventWithLabels{ + labels: map[string]string{ + "other_url": "https://dynatracetenant/#problems/problemdetails;gf=all;pid=2132340906857553706_1649682528607V2", + }, + }, + expectedProblemID: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + problemID := TryGetProblemIDFromLabels(tt.keptnEvent) + assert.EqualValues(t, tt.expectedProblemID, problemID) + }) + } +} + +func (t testEventWithLabels) GetShKeptnContext() string { + panic("GetShKeptnContext should not be called on mock") +} + +func (t testEventWithLabels) GetEvent() string { + panic("GetEvent should not be called on mock") +} + +func (t testEventWithLabels) GetSource() string { + panic("GetSource should not be called on mock") +} + +func (t testEventWithLabels) GetProject() string { + panic("GetProject should not be called on mock") +} + +func (t testEventWithLabels) GetStage() string { + panic("GetStage should not be called on mock") +} + +func (t testEventWithLabels) GetService() string { + panic("GetService should not be called on mock") +} + +func (t testEventWithLabels) GetDeployment() string { + panic("GetDeployment should not be called on mock") +} + +func (t testEventWithLabels) GetTestStrategy() string { + panic("GetTestStrategy should not be called on mock") +} + +func (t testEventWithLabels) GetDeploymentStrategy() string { + panic("GetDeploymentStrategy should not be called on mock") +} + +func (t testEventWithLabels) GetLabels() map[string]string { + return t.labels +} diff --git a/internal/keptn/keptn_local_resource_client.go b/internal/keptn/keptn_local_resource_client.go deleted file mode 100644 index ba8995ac1..000000000 --- a/internal/keptn/keptn_local_resource_client.go +++ /dev/null @@ -1,67 +0,0 @@ -package keptn - -import ( - "fmt" - log "github.com/sirupsen/logrus" - "io/ioutil" - "strings" -) - -// is the local test resource path for the dynatrace.conf.yaml -const localConfigFilename = "dynatrace/_dynatrace.conf.yaml" - -type LocalResourceClient struct { -} - -func NewLocalResourceClient() *LocalResourceClient { - return &LocalResourceClient{} -} - -func (c *LocalResourceClient) GetDynatraceConfig(project string, stage string, service string) (string, error) { - return c.GetResource(project, stage, service, configFilename) -} - -func (c *LocalResourceClient) GetResource(project string, stage string, service string, resourceURI string) (string, error) { - // hack to retrieve the local config file - if resourceURI == configFilename { - resourceURI = localConfigFilename - } - - localFileContent, err := ioutil.ReadFile(resourceURI) - if err != nil { - log.WithFields( - log.Fields{ - "resourceURI": resourceURI, - "service": service, - "stage": stage, - "project": project, - }).Info("File not found locally") - return "", nil - } - - log.WithField("resourceURI", resourceURI).Info("Loaded LOCAL file") - return string(localFileContent), nil -} - -func (c *LocalResourceClient) GetProjectResource(project string, resourceURI string) (string, error) { - return c.GetResource(project, "", "", strings.ToLower(strings.ReplaceAll(resourceURI, "dynatrace/", "../../../dynatrace/project_"))) -} - -func (c *LocalResourceClient) getStageResource(project string, stage string, resourceURI string) (string, error) { - return c.GetResource(project, stage, "", strings.ToLower(strings.ReplaceAll(resourceURI, "dynatrace/", "../../../dynatrace/stage_"))) -} - -func (c *LocalResourceClient) GetServiceResource(project string, stage string, service string, resourceURI string) (string, error) { - return c.GetResource(project, stage, service, strings.ToLower(strings.ReplaceAll(resourceURI, "dynatrace/", "../../../dynatrace/service_"))) -} - -func (c *LocalResourceClient) UploadKeptnResource(contentToUpload []byte, remoteResourceURI string) error { - // if we run in a runlocal mode we are just getting the file from the local disk - err := ioutil.WriteFile(remoteResourceURI, contentToUpload, 0644) - if err != nil { - return fmt.Errorf("couldnt write local file %s: %v", remoteResourceURI, err) - } - - log.WithField("remoteResourceURI", remoteResourceURI).Info("Local file written") - return nil -} diff --git a/internal/keptn/keptn_service_client.go b/internal/keptn/keptn_service_client.go deleted file mode 100644 index 47d328e5f..000000000 --- a/internal/keptn/keptn_service_client.go +++ /dev/null @@ -1,74 +0,0 @@ -package keptn - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/keptn-contrib/dynatrace-service/internal/common" - "github.com/keptn-contrib/dynatrace-service/internal/rest" - - apimodels "github.com/keptn/go-utils/pkg/api/models" - keptnapi "github.com/keptn/go-utils/pkg/api/utils" -) - -type ServiceClientInterface interface { - GetServiceNames(project string, stage string) ([]string, error) - CreateServiceInProject(project string, service string) error -} - -type ServiceClient struct { - client *keptnapi.ServiceHandler - apiClient APIClientInterface -} - -func NewDefaultServiceClient() *ServiceClient { - return NewServiceClient( - keptnapi.NewServiceHandler(common.GetShipyardControllerURL()), - &http.Client{}) -} - -func NewServiceClient(client *keptnapi.ServiceHandler, httpClient *http.Client) *ServiceClient { - return &ServiceClient{ - client: client, - apiClient: NewAPIClient( - rest.NewDefaultClient( - httpClient, - common.GetShipyardControllerURL())), - } -} - -func (c *ServiceClient) GetServiceNames(project string, stage string) ([]string, error) { - services, err := c.client.GetAllServices(project, stage) - if err != nil { - return nil, fmt.Errorf("could not fetch services of Keptn project %s at stage %s: %s", project, stage, err.Error()) - } - - if services == nil { - return nil, nil - } - - serviceNames := make([]string, len(services)) - for i, service := range services { - serviceNames[i] = service.ServiceName - } - - return serviceNames, nil -} - -func (c *ServiceClient) CreateServiceInProject(project string, service string) error { - serviceModel := &apimodels.CreateService{ - ServiceName: &service, - } - reqBody, err := json.Marshal(serviceModel) - if err != nil { - return fmt.Errorf("could not marshal service payload: %s", err.Error()) - } - - _, err = c.apiClient.Post(getServicePathFor(project), reqBody) - return err -} - -func getServicePathFor(project string) string { - return "/v1/project/" + project + "/service" -} diff --git a/internal/keptn/keptn_uniform_client.go b/internal/keptn/keptn_uniform_client.go deleted file mode 100644 index 4d7b15b01..000000000 --- a/internal/keptn/keptn_uniform_client.go +++ /dev/null @@ -1,62 +0,0 @@ -package keptn - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/keptn-contrib/dynatrace-service/internal/common" - "github.com/keptn-contrib/dynatrace-service/internal/rest" -) - -type registrationResponse struct { - ID string `json:"id"` -} - -const UniformPath = "/v1/uniform/registration" - -type UniformClientInterface interface { - GetServiceNames(project string, stage string) ([]string, error) - CreateServiceInProject(project string, service string) error -} - -type UniformClient struct { - client APIClientInterface -} - -func NewDefaultUniformClient() *UniformClient { - return NewUniformClient( - &http.Client{}) -} - -func NewUniformClient(httpClient *http.Client) *UniformClient { - return &UniformClient{ - client: NewAPIClient( - rest.NewDefaultClient( - httpClient, - common.GetShipyardControllerURL())), - } -} - -func (c *UniformClient) GetIntegrationIDFor(integrationName string) (string, error) { - body, err := c.client.Get(UniformPath + "?name=" + integrationName) - if err != nil { - return "", err - } - - var responses []registrationResponse - err = json.Unmarshal(body, &responses) - if err != nil { - return "", fmt.Errorf("could not parse Keptn Uniform API response: %w", err) - } - - if len(responses) == 0 { - return "", fmt.Errorf("could not retrieve integration ID for %s", integrationName) - } - - if len(responses) > 1 { - return "", fmt.Errorf("there are more than one integrations with name %s - this is not supported", integrationName) - } - - return responses[0].ID, nil -} diff --git a/internal/keptn/keptn_config_resource_client.go b/internal/keptn/resource_client.go similarity index 71% rename from internal/keptn/keptn_config_resource_client.go rename to internal/keptn/resource_client.go index a5d3eea8e..861f62cef 100644 --- a/internal/keptn/keptn_config_resource_client.go +++ b/internal/keptn/resource_client.go @@ -5,22 +5,30 @@ import ( "fmt" "strings" - "github.com/keptn-contrib/dynatrace-service/internal/common" keptnmodels "github.com/keptn/go-utils/pkg/api/models" api "github.com/keptn/go-utils/pkg/api/utils" log "github.com/sirupsen/logrus" ) -// ConfigResourceClientInterface defines the methods for interacting with resources of Keptn's configuration service -type ConfigResourceClientInterface interface { +// ResourceClientInterface defines the methods for interacting with resources of Keptn's configuration service. +type ResourceClientInterface interface { + // GetResource tries to find the first instance of a given resource on service, stage or project level. GetResource(project string, stage string, service string, resourceURI string) (string, error) + + // GetProjectResource tries to retrieve a resource on project level. GetProjectResource(project string, resourceURI string) (string, error) + + // GetStageResource tries to retrieve a resource on stage level. GetStageResource(project string, stage string, resourceURI string) (string, error) + + // GetServiceResource tries to retrieve a resource on service level. GetServiceResource(project string, stage string, service string, resourceURI string) (string, error) + + // UploadResource tries to upload a resource. UploadResource(contentToUpload []byte, remoteResourceURI string, project string, stage string, service string) error } -// ResourceError represents an error for a resource that was not found +// ResourceError represents an error for a resource that was not found. type ResourceError struct { uri string project string @@ -28,7 +36,7 @@ type ResourceError struct { service string } -// ResourceNotFoundError represents an error for a resource that was not found +// ResourceNotFoundError represents an error for a resource that was not found. type ResourceNotFoundError ResourceError // Error returns a string representation of this error @@ -36,7 +44,7 @@ func (e *ResourceNotFoundError) Error() string { return fmt.Sprintf("could not find resource: '%s' %s", e.uri, getLocation(e.service, e.stage, e.project)) } -// ResourceEmptyError represents an error for a resource that was found, but is empty +// ResourceEmptyError represents an error for a resource that was found, but is empty. type ResourceEmptyError ResourceError // Error returns a string representation of this error @@ -44,24 +52,24 @@ func (e *ResourceEmptyError) Error() string { return fmt.Sprintf("found resource: '%s' %s, but it is empty", e.uri, getLocation(e.service, e.stage, e.project)) } -// ResourceUploadFailedError represents an error for a resource that could not be uploaded +// ResourceUploadFailedError represents an error for a resource that could not be uploaded. type ResourceUploadFailedError struct { ResourceError message string } -// Error returns a string representation of this error +// Error returns a string representation of this error. func (e *ResourceUploadFailedError) Error() string { return fmt.Sprintf("could not upload resource: '%s' %s: %s", e.uri, getLocation(e.service, e.stage, e.project), e.message) } -// ResourceRetrievalFailedError represents an error for a resource that could not be retrieved because of an error +// ResourceRetrievalFailedError represents an error for a resource that could not be retrieved because of an error. type ResourceRetrievalFailedError struct { ResourceError message string } -// Error returns a string representation of this error +// Error returns a string representation of this error. func (e *ResourceRetrievalFailedError) Error() string { return fmt.Sprintf("could not retrieve resource: '%s' %s: %s", e.uri, getLocation(e.service, e.stage, e.project), e.message) } @@ -82,26 +90,20 @@ func getLocation(service string, stage string, project string) string { return strings.TrimLeft(location, " ") } -// ConfigResourceClient is the default implementation for the ConfigResourceClientInterface using a Keptn api.ResourceHandler -type ConfigResourceClient struct { - handler *api.ResourceHandler -} - -// NewDefaultConfigResourceClient creates a new ResourceClient with a default Keptn resource handler for the configuration service -func NewDefaultConfigResourceClient() *ConfigResourceClient { - return NewConfigResourceClient( - api.NewResourceHandler(common.GetConfigurationServiceURL())) +// ResourceClient is the default implementation for the ResourceClientInterface using a Keptn api.ResourcesV1Interface. +type ResourceClient struct { + client api.ResourcesV1Interface } -// NewConfigResourceClient creates a new ResourceClient with a Keptn resource handler for the configuration service -func NewConfigResourceClient(handler *api.ResourceHandler) *ConfigResourceClient { - return &ConfigResourceClient{ - handler: handler, +// NewResourceClient creates a new ResourceClient using a api.ResourcesV1Interface. +func NewResourceClient(client api.ResourcesV1Interface) *ResourceClient { + return &ResourceClient{ + client: client, } } -// GetResource tries to find the first instance of a given resource on service, stage or project level -func (rc *ConfigResourceClient) GetResource(project string, stage string, service string, resourceURI string) (string, error) { +// GetResource tries to find the first instance of a given resource on service, stage or project level. +func (rc *ResourceClient) GetResource(project string, stage string, service string, resourceURI string) (string, error) { var rnfErrorType *ResourceNotFoundError if project != "" && stage != "" && service != "" { keptnResourceContent, err := rc.GetServiceResource(project, stage, service, resourceURI) @@ -147,7 +149,7 @@ func (rc *ConfigResourceClient) GetResource(project string, stage string, servic if project != "" { keptnResourceContent, err := rc.GetProjectResource(project, resourceURI) - if err == api.ResourceNotFoundError { + if errors.As(err, &rnfErrorType) { log.WithField("project", project).Debugf("%s not available for project", resourceURI) } else if err != nil { return "", err @@ -161,11 +163,11 @@ func (rc *ConfigResourceClient) GetResource(project string, stage string, servic return "", &ResourceNotFoundError{uri: resourceURI, project: project, stage: stage, service: service} } -// GetServiceResource tries to retrieve a resourceURI on service level -func (rc *ConfigResourceClient) GetServiceResource(project string, stage string, service string, resourceURI string) (string, error) { +// GetServiceResource tries to retrieve a resource on service level. +func (rc *ResourceClient) GetServiceResource(project string, stage string, service string, resourceURI string) (string, error) { return getResourceByFunc( func() (*keptnmodels.Resource, error) { - return rc.handler.GetServiceResource(project, stage, service, resourceURI) + return rc.client.GetServiceResource(project, stage, service, resourceURI) }, func() *ResourceNotFoundError { return &ResourceNotFoundError{uri: resourceURI, project: project, stage: stage, service: service} @@ -178,10 +180,10 @@ func (rc *ConfigResourceClient) GetServiceResource(project string, stage string, }) } -// GetStageResource tries to retrieve a resourceURI on stage level -func (rc *ConfigResourceClient) GetStageResource(project string, stage string, resourceURI string) (string, error) { +// GetStageResource tries to retrieve a resource on stage level. +func (rc *ResourceClient) GetStageResource(project string, stage string, resourceURI string) (string, error) { return getResourceByFunc( - func() (*keptnmodels.Resource, error) { return rc.handler.GetStageResource(project, stage, resourceURI) }, + func() (*keptnmodels.Resource, error) { return rc.client.GetStageResource(project, stage, resourceURI) }, func() *ResourceNotFoundError { return &ResourceNotFoundError{uri: resourceURI, project: project, stage: stage} }, @@ -193,10 +195,10 @@ func (rc *ConfigResourceClient) GetStageResource(project string, stage string, r }) } -// GetProjectResource tries to retrieve a resourceURI on project level -func (rc *ConfigResourceClient) GetProjectResource(project string, resourceURI string) (string, error) { +// GetProjectResource tries to retrieve a resource on project level. +func (rc *ResourceClient) GetProjectResource(project string, resourceURI string) (string, error) { return getResourceByFunc( - func() (*keptnmodels.Resource, error) { return rc.handler.GetProjectResource(project, resourceURI) }, + func() (*keptnmodels.Resource, error) { return rc.client.GetProjectResource(project, resourceURI) }, func() *ResourceNotFoundError { return &ResourceNotFoundError{uri: resourceURI, project: project} }, func(msg string) *ResourceRetrievalFailedError { return &ResourceRetrievalFailedError{ResourceError{uri: resourceURI, project: project}, msg} @@ -224,10 +226,10 @@ func getResourceByFunc( return resource.ResourceContent, nil } -// UploadResource tries to upload a resourceURI on service level -func (rc *ConfigResourceClient) UploadResource(contentToUpload []byte, remoteResourceURI string, project string, stage string, service string) error { +// UploadResource tries to upload a resource. +func (rc *ResourceClient) UploadResource(contentToUpload []byte, remoteResourceURI string, project string, stage string, service string) error { resources := []*keptnmodels.Resource{{ResourceContent: string(contentToUpload), ResourceURI: &remoteResourceURI}} - _, err := rc.handler.CreateResources(project, stage, service, resources) + _, err := rc.client.CreateResources(project, stage, service, resources) if err != nil { return &ResourceUploadFailedError{ ResourceError{ diff --git a/internal/keptn/service_client.go b/internal/keptn/service_client.go new file mode 100644 index 000000000..0cf481dfa --- /dev/null +++ b/internal/keptn/service_client.go @@ -0,0 +1,60 @@ +package keptn + +import ( + "errors" + "fmt" + + "github.com/keptn/go-utils/pkg/api/models" + api "github.com/keptn/go-utils/pkg/api/utils" +) + +// ServiceClientInterface provides access to Keptn services. +type ServiceClientInterface interface { + // GetServiceNames gets the names of the services in the specified project and stage or returns an error. + GetServiceNames(project string, stage string) ([]string, error) + + // CreateServiceInProject creates a service in all stages of the specified project or returns an error. + CreateServiceInProject(project string, service string) error +} + +// ServiceClient is an implementation of ServiceClientInterface using api.ServicesV1Interface and api.APIV1Interface. +type ServiceClient struct { + servicesClient api.ServicesV1Interface + apiClient api.APIV1Interface +} + +// NewServiceClient creates a new ServiceClient using the specified clients. +func NewServiceClient(servicesClient api.ServicesV1Interface, apiClient api.APIV1Interface) *ServiceClient { + return &ServiceClient{ + servicesClient: servicesClient, + apiClient: apiClient, + } +} + +// GetServiceNames gets the names of the services in the specified project and stage or returns an error. +func (c *ServiceClient) GetServiceNames(project string, stage string) ([]string, error) { + services, err := c.servicesClient.GetAllServices(project, stage) + if err != nil { + return nil, fmt.Errorf("could not fetch services of Keptn project %s at stage %s: %s", project, stage, err.Error()) + } + + if services == nil { + return nil, nil + } + + serviceNames := make([]string, len(services)) + for i, service := range services { + serviceNames[i] = service.ServiceName + } + + return serviceNames, nil +} + +// CreateServiceInProject creates a service in all stages of the specified project or returns an error. +func (c *ServiceClient) CreateServiceInProject(project string, service string) error { + _, keptnAPIErr := c.apiClient.CreateService(project, models.CreateService{ + ServiceName: &service, + }) + + return errors.New(keptnAPIErr.GetMessage()) +} diff --git a/internal/keptn/uniform_client.go b/internal/keptn/uniform_client.go new file mode 100644 index 000000000..ae77ebb64 --- /dev/null +++ b/internal/keptn/uniform_client.go @@ -0,0 +1,50 @@ +package keptn + +import ( + "fmt" + + api "github.com/keptn/go-utils/pkg/api/utils" +) + +// UniformClientInterface provides access to Keptn Uniform. +type UniformClientInterface interface { + // GetIntegrationIDByName gets the ID of the integration with specified name or returns an error if none or more than one exist with that name. + GetIntegrationIDByName(integrationName string) (string, error) +} + +// UniformClient is an implementation of UniformClientInterface using api.UniformV1Interface. +type UniformClient struct { + client api.UniformV1Interface +} + +// NewUniformClient creates a new UniformClient using the specified api.UniformV1Interface. +func NewUniformClient(client api.UniformV1Interface) *UniformClient { + return &UniformClient{ + client: client, + } +} + +// GetIntegrationIDByName gets the ID of the integration with specified name or returns an error if none or more than one exist with that name. +func (c *UniformClient) GetIntegrationIDByName(integrationName string) (string, error) { + integrations, err := c.client.GetRegistrations() + if err != nil { + return "", fmt.Errorf("could not get Keptn Uniform registrations: %w", err) + } + + var integrationIDs []string + for _, integration := range integrations { + if integration.Name == integrationName { + integrationIDs = append(integrationIDs, integration.ID) + } + } + + if len(integrationIDs) == 0 { + return "", fmt.Errorf("could not retrieve integration ID for %s", integrationName) + } + + if len(integrationIDs) > 1 { + return "", fmt.Errorf("there are more than one integrations with name %s - this is not supported", integrationName) + } + + return integrationIDs[0], nil +} diff --git a/internal/keptn/uniform_client_test.go b/internal/keptn/uniform_client_test.go new file mode 100644 index 000000000..d4190b410 --- /dev/null +++ b/internal/keptn/uniform_client_test.go @@ -0,0 +1,112 @@ +package keptn + +import ( + "testing" + + "github.com/keptn/go-utils/pkg/api/models" + api "github.com/keptn/go-utils/pkg/api/utils" + "github.com/stretchr/testify/assert" +) + +type mockUniformClient struct { + registrations []*models.Integration +} + +func (c mockUniformClient) Ping(integrationID string) (*models.Integration, error) { + panic("Ping should not be called on mockUniformClient") +} + +func (c mockUniformClient) RegisterIntegration(integration models.Integration) (string, error) { + panic("RegisterIntegration should not be called on mockUniformClient") +} + +func (c mockUniformClient) CreateSubscription(integrationID string, subscription models.EventSubscription) (string, error) { + panic("CreateSubscription should not be called on mockUniformClient") +} + +func (c mockUniformClient) UnregisterIntegration(integrationID string) error { + panic("UnregisterIntegration should not be called on mockUniformClient") +} + +func (c mockUniformClient) GetRegistrations() ([]*models.Integration, error) { + return c.registrations, nil +} + +func TestUniformClient_GetIntegrationIDByName(t *testing.T) { + + tests := []struct { + name string + uniformClient api.UniformV1Interface + integrationName string + expectedIntegrationID string + expectError bool + expectedErrorSubstring string + }{ + { + name: "one integration with name - should work", + uniformClient: &mockUniformClient{ + registrations: []*models.Integration{ + createIntegration("5d64eb87c4ce3e23935758c418df9d980c16a3b1", "webhook-service"), + createIntegration("e8039ff0b65c7e4d326a0473a18f04cabfefe747", "dynatrace-service"), + }, + }, + integrationName: "dynatrace-service", + expectedIntegrationID: "e8039ff0b65c7e4d326a0473a18f04cabfefe747", + }, + { + name: "two integrations with name - should fail", + uniformClient: &mockUniformClient{ + registrations: []*models.Integration{ + createIntegration("5d64eb87c4ce3e23935758c418df9d980c16a3b1", "webhook-service"), + createIntegration("e8039ff0b65c7e4d326a0473a18f04cabfefe747", "dynatrace-service"), + createIntegration("d73082b9c42aa147935fe2592a91eb5d2b224038", "dynatrace-service"), + }, + }, + integrationName: "dynatrace-service", + expectError: true, + expectedErrorSubstring: "more than one integrations with name", + }, + { + name: "no integration with name - should fail", + uniformClient: &mockUniformClient{ + registrations: []*models.Integration{ + createIntegration("5d64eb87c4ce3e23935758c418df9d980c16a3b1", "webhook-service"), + }, + }, + integrationName: "dynatrace-service", + expectError: true, + expectedErrorSubstring: "could not retrieve integration ID for", + }, + { + name: "no integrations at all - should fail", + uniformClient: &mockUniformClient{ + registrations: []*models.Integration{}, + }, + integrationName: "dynatrace-service", + expectError: true, + expectedErrorSubstring: "could not retrieve integration ID for", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := UniformClient{ + client: tt.uniformClient, + } + integrationID, err := c.GetIntegrationIDByName(tt.integrationName) + if tt.expectError { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErrorSubstring) + } else { + assert.EqualValues(t, tt.expectedIntegrationID, integrationID) + assert.NoError(t, err) + } + }) + } +} + +func createIntegration(id string, name string) *models.Integration { + return &models.Integration{ + ID: id, + Name: name, + } +} diff --git a/internal/monitoring/configuration.go b/internal/monitoring/configuration.go index ffde34e15..c78f6a53c 100644 --- a/internal/monitoring/configuration.go +++ b/internal/monitoring/configuration.go @@ -26,11 +26,11 @@ type ConfigResult struct { type Configuration struct { dtClient dynatrace.ClientInterface kClient keptn.ClientInterface - sloReader keptn.SLOResourceReaderInterface + sloReader keptn.SLOReaderInterface serviceClient keptn.ServiceClientInterface } -func NewConfiguration(dynatraceClient dynatrace.ClientInterface, keptnClient keptn.ClientInterface, sloReader keptn.SLOResourceReaderInterface, serviceClient keptn.ServiceClientInterface) *Configuration { +func NewConfiguration(dynatraceClient dynatrace.ClientInterface, keptnClient keptn.ClientInterface, sloReader keptn.SLOReaderInterface, serviceClient keptn.ServiceClientInterface) *Configuration { return &Configuration{ dtClient: dynatraceClient, kClient: keptnClient, diff --git a/internal/monitoring/configure_monitoring_event_handler.go b/internal/monitoring/configure_monitoring_event_handler.go index ec9c9cb1c..3329fd285 100644 --- a/internal/monitoring/configure_monitoring_event_handler.go +++ b/internal/monitoring/configure_monitoring_event_handler.go @@ -22,12 +22,12 @@ type ConfigureMonitoringEventHandler struct { event ConfigureMonitoringAdapterInterface dtClient dynatrace.ClientInterface kClient keptn.ClientInterface - sloReader keptn.SLOResourceReaderInterface + sloReader keptn.SLOReaderInterface serviceClient keptn.ServiceClientInterface } // NewConfigureMonitoringEventHandler returns a new ConfigureMonitoringEventHandler -func NewConfigureMonitoringEventHandler(event ConfigureMonitoringAdapterInterface, dtClient dynatrace.ClientInterface, kClient keptn.ClientInterface, sloReader keptn.SLOResourceReaderInterface, serviceClient keptn.ServiceClientInterface) ConfigureMonitoringEventHandler { +func NewConfigureMonitoringEventHandler(event ConfigureMonitoringAdapterInterface, dtClient dynatrace.ClientInterface, kClient keptn.ClientInterface, sloReader keptn.SLOReaderInterface, serviceClient keptn.ServiceClientInterface) ConfigureMonitoringEventHandler { return ConfigureMonitoringEventHandler{ event: event, dtClient: dtClient, diff --git a/internal/monitoring/metric_events_creation.go b/internal/monitoring/metric_events_creation.go index a68b939bd..de0c06bb7 100644 --- a/internal/monitoring/metric_events_creation.go +++ b/internal/monitoring/metric_events_creation.go @@ -28,10 +28,10 @@ type CriteriaObject struct { type MetricEventCreation struct { dtClient dynatrace.ClientInterface kClient keptn.ClientInterface - sloReader keptn.SLOResourceReaderInterface + sloReader keptn.SLOReaderInterface } -func NewMetricEventCreation(dynatraceClient dynatrace.ClientInterface, keptnClient keptn.ClientInterface, sloReader keptn.SLOResourceReaderInterface) MetricEventCreation { +func NewMetricEventCreation(dynatraceClient dynatrace.ClientInterface, keptnClient keptn.ClientInterface, sloReader keptn.SLOReaderInterface) MetricEventCreation { return MetricEventCreation{ dtClient: dynatraceClient, kClient: keptnClient, diff --git a/internal/onboard/service_sync.go b/internal/onboard/service_sync.go index c32b76e02..ef487c0d6 100644 --- a/internal/onboard/service_sync.go +++ b/internal/onboard/service_sync.go @@ -4,8 +4,10 @@ import ( "fmt" "time" + "github.com/keptn-contrib/dynatrace-service/internal/common" "github.com/keptn-contrib/dynatrace-service/internal/config" "github.com/keptn-contrib/dynatrace-service/internal/keptn" + api "github.com/keptn/go-utils/pkg/api/utils" keptnlib "github.com/keptn/go-utils/pkg/lib" "github.com/keptn-contrib/dynatrace-service/internal/dynatrace" @@ -70,7 +72,7 @@ type defaultEntitiesClientFactory struct { configProvider config.DynatraceConfigProvider } -func newDefaultEntitiesClientFactory(resourceClient keptn.DynatraceConfigResourceClientInterface) *defaultEntitiesClientFactory { +func newDefaultEntitiesClientFactory(resourceClient keptn.DynatraceConfigReaderInterface) *defaultEntitiesClientFactory { return &defaultEntitiesClientFactory{ configProvider: config.NewDynatraceConfigGetter(resourceClient), } @@ -99,21 +101,26 @@ func (f defaultEntitiesClientFactory) CreateEntitiesClient() (*dynatrace.Entitie // ServiceSynchronizer encapsulates the service onboarder component. type ServiceSynchronizer struct { servicesClient keptn.ServiceClientInterface - resourcesClient keptn.SLIAndSLOResourceWriterInterface + resourcesClient keptn.SLIAndSLOWriterInterface entitiesClientFactory EntitiesClientFactory } // NewDefaultServiceSynchronizer creates are new default ServiceSynchronizer. -func NewDefaultServiceSynchronizer() *ServiceSynchronizer { - resourceClient := keptn.NewDefaultResourceClient() +func NewDefaultServiceSynchronizer() (*ServiceSynchronizer, error) { + keptnAPISet, err := api.New(common.GetShipyardControllerURL()) + if err != nil { + return nil, fmt.Errorf("could not create Keptn API set: %w", err) + } + + resourceClient := keptn.NewConfigClient(keptn.NewResourceClient(keptnAPISet.ResourcesV1())) serviceSynchronizer := ServiceSynchronizer{ - servicesClient: keptn.NewDefaultServiceClient(), + servicesClient: keptn.NewServiceClient(keptnAPISet.ServicesV1(), keptnAPISet.APIV1()), resourcesClient: resourceClient, entitiesClientFactory: newDefaultEntitiesClientFactory(resourceClient), } - return &serviceSynchronizer + return &serviceSynchronizer, nil } // Run runs the service synchronizer and does not return. diff --git a/internal/problem/action_finished_event_handler.go b/internal/problem/action_finished_event_handler.go index 6df6320c8..4d1c00c26 100644 --- a/internal/problem/action_finished_event_handler.go +++ b/internal/problem/action_finished_event_handler.go @@ -2,6 +2,7 @@ package problem import ( "fmt" + "github.com/keptn-contrib/dynatrace-service/internal/common" "github.com/keptn-contrib/dynatrace-service/internal/dynatrace" "github.com/keptn-contrib/dynatrace-service/internal/keptn" @@ -37,7 +38,7 @@ func (eh *ActionFinishedEventHandler) HandleEvent() error { // Comment text we want to push over comment := fmt.Sprintf("[Keptn finished execution](%s) of action by: %s\nResult: %s\nStatus: %s", - eh.event.GetLabels()[common.KEPTNSBRIDGE_LABEL], + eh.event.GetLabels()[common.BridgeLabel], eh.event.GetSource(), eh.event.GetResult(), eh.event.GetStatus()) diff --git a/internal/problem/action_started_event_handler.go b/internal/problem/action_started_event_handler.go index 312df6a9c..14d5ecfb1 100644 --- a/internal/problem/action_started_event_handler.go +++ b/internal/problem/action_started_event_handler.go @@ -2,6 +2,7 @@ package problem import ( "fmt" + "github.com/keptn-contrib/dynatrace-service/internal/common" "github.com/keptn-contrib/dynatrace-service/internal/dynatrace" "github.com/keptn-contrib/dynatrace-service/internal/keptn" @@ -32,7 +33,7 @@ func (eh *ActionStartedEventHandler) HandleEvent() error { } // Comment we push over - comment := fmt.Sprintf("[Keptn remediation action](%s) started execution by: %s", eh.event.GetLabels()[common.KEPTNSBRIDGE_LABEL], eh.event.GetSource()) + comment := fmt.Sprintf("[Keptn remediation action](%s) started execution by: %s", eh.event.GetLabels()[common.BridgeLabel], eh.event.GetSource()) dynatrace.NewProblemsClient(eh.dtClient).AddProblemComment(pid, comment) diff --git a/internal/problem/action_triggered_event_handler.go b/internal/problem/action_triggered_event_handler.go index 55e99dec7..be5ec09b3 100644 --- a/internal/problem/action_triggered_event_handler.go +++ b/internal/problem/action_triggered_event_handler.go @@ -3,6 +3,7 @@ package problem import ( "errors" "fmt" + "github.com/keptn-contrib/dynatrace-service/internal/common" "github.com/keptn-contrib/dynatrace-service/internal/dynatrace" "github.com/keptn-contrib/dynatrace-service/internal/keptn" @@ -55,7 +56,7 @@ func (eh *ActionTriggeredEventHandler) HandleEvent() error { dynatrace.NewEventsClient(eh.dtClient).AddInfoEvent(dtInfoEvent) // this is posting the Event on the problem as a comment - comment = fmt.Sprintf("[Keptn triggered action](%s) %s", eh.event.GetLabels()[common.KEPTNSBRIDGE_LABEL], eh.event.GetAction()) + comment = fmt.Sprintf("[Keptn triggered action](%s) %s", eh.event.GetLabels()[common.BridgeLabel], eh.event.GetAction()) if eh.event.GetActionDescription() != "" { comment = comment + ": " + eh.event.GetActionDescription() } diff --git a/internal/problem/problem_events_factory.go b/internal/problem/problem_events_factory.go index 8448bd1ed..60d7c7b6d 100644 --- a/internal/problem/problem_events_factory.go +++ b/internal/problem/problem_events_factory.go @@ -6,10 +6,9 @@ import ( keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0" "github.com/keptn-contrib/dynatrace-service/internal/adapter" + "github.com/keptn-contrib/dynatrace-service/internal/common" ) -const problemURLLabel = "Problem URL" - type ProblemClosedEventFactory struct { event ProblemAdapterInterface } @@ -37,7 +36,7 @@ func (f *ProblemClosedEventFactory) CreateCloudEvent() (*cloudevents.Event, erro // https://github.com/keptn-contrib/dynatrace-service/issues/176 // add problem URL as label so it becomes clickable - labels[problemURLLabel] = f.event.GetProblemURL() + labels[common.ProblemURLLabel] = f.event.GetProblemURL() return adapter.NewCloudEventFactoryBase(f.event, keptn.ProblemEventType, rawProblem).CreateCloudEvent() } @@ -73,7 +72,7 @@ func (f *RemediationTriggeredEventFactory) CreateCloudEvent() (*cloudevents.Even // https://github.com/keptn-contrib/dynatrace-service/issues/176 // add problem URL as label so it becomes clickable remediationEventData.Labels = make(map[string]string) - remediationEventData.Labels[problemURLLabel] = f.event.GetProblemURL() + remediationEventData.Labels[common.ProblemURLLabel] = f.event.GetProblemURL() eventType := keptnv2.GetTriggeredEventType(f.event.GetStage() + "." + remediationTaskName) diff --git a/internal/sli/get_sli_triggered_event_handler.go b/internal/sli/get_sli_triggered_event_handler.go index 562b74e5a..3b8830787 100644 --- a/internal/sli/get_sli_triggered_event_handler.go +++ b/internal/sli/get_sli_triggered_event_handler.go @@ -25,13 +25,13 @@ type GetSLIEventHandler struct { event GetSLITriggeredAdapterInterface dtClient dynatrace.ClientInterface kClient keptn.ClientInterface - resourceClient keptn.ResourceClientInterface + resourceClient keptn.SLOAndSLIClientInterface secretName string dashboard string } -func NewGetSLITriggeredHandler(event GetSLITriggeredAdapterInterface, dtClient dynatrace.ClientInterface, kClient keptn.ClientInterface, resourceClient keptn.ResourceClientInterface, secretName string, dashboard string) GetSLIEventHandler { +func NewGetSLITriggeredHandler(event GetSLITriggeredAdapterInterface, dtClient dynatrace.ClientInterface, kClient keptn.ClientInterface, resourceClient keptn.SLOAndSLIClientInterface, secretName string, dashboard string) GetSLIEventHandler { return GetSLIEventHandler{ event: event, dtClient: dtClient, @@ -86,7 +86,7 @@ func (eh *GetSLIEventHandler) retrieveSLIResults() ([]result.SLIResult, error) { // ARE WE CALLED IN CONTEXT OF A PROBLEM REMEDIATION?? // If so - we should try to query the status of the Dynatrace Problem that triggered this evaluation - problemID := getDynatraceProblemContext(eh.event) + problemID := keptn.TryGetProblemIDFromLabels(eh.event) if problemID != "" { sliResults = append(sliResults, eh.getSLIResultsFromProblemContext(problemID)) } @@ -172,29 +172,6 @@ func (eh *GetSLIEventHandler) getSLIResultsFromCustomQueries(timeframe common.Ti return sliResults, nil } -//getDynatraceProblemContext will evaluate the event and - returns dynatrace problem ID if found, 0 otherwise -func getDynatraceProblemContext(eventData GetSLITriggeredAdapterInterface) string { - - // iterate through the labels and find Problem URL - if eventData.GetLabels() == nil || len(eventData.GetLabels()) == 0 { - return "" - } - - for labelName, labelValue := range eventData.GetLabels() { - if strings.ToLower(labelName) == "problem url" { - // the value should be of form https://dynatracetenant/#problems/problemdetails;pid=8485558334848276629_1604413609638V2 - // so - lets get the last part after pid= - - ix := strings.LastIndex(labelValue, ";pid=") - if ix > 0 { - return labelValue[ix+5:] - } - } - } - - return "" -} - func (eh *GetSLIEventHandler) getSLIResultsFromProblemContext(problemID string) result.SLIResult { // let's add this to the SLO in case this indicator is not yet in SLO.yaml. // Because if it does not get added the lighthouse will not evaluate the SLI values diff --git a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_fails_test.go b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_fails_test.go index d32a7ec19..42c4c0991 100644 --- a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_fails_test.go +++ b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_fails_test.go @@ -111,7 +111,7 @@ func TestThatInvalidDashboardIDProducesErrorMessageInNoMetricIndicatorEvenIfTher } } -func runAndAssertDashboardTest(t *testing.T, getSLIEventData *getSLIEventData, handler http.Handler, rClient keptn.ResourceClientInterface, dashboardID string, getSLIFinishedEventAssertionsFunc func(t *testing.T, actual *keptnv2.GetSLIFinishedEventData), sliResultAssertionsFuncs ...func(t *testing.T, actual *keptnv2.SLIResult)) { +func runAndAssertDashboardTest(t *testing.T, getSLIEventData *getSLIEventData, handler http.Handler, rClient keptn.SLOAndSLIClientInterface, dashboardID string, getSLIFinishedEventAssertionsFunc func(t *testing.T, actual *keptnv2.GetSLIFinishedEventData), sliResultAssertionsFuncs ...func(t *testing.T, actual *keptnv2.SLIResult)) { // we do not need custom queries, as we are using the dashboard kClient := &keptnClientMock{} diff --git a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_test.go b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_test.go index c40232ce6..3b89a75ab 100644 --- a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_test.go +++ b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_test.go @@ -62,7 +62,7 @@ func TestErrorIsReturnedWhenSLISLOOrDashboardFileWritingFails(t *testing.T) { testConfigs := []struct { name string - resourceClientMock keptn.ResourceClientInterface + resourceClientMock keptn.SLOAndSLIClientInterface sliResultAssertionsFunc func(t *testing.T, actual *keptnv2.SLIResult) shouldFail bool }{ @@ -213,7 +213,7 @@ func TestThatFallbackToSLIsFromDashboardIfDashboardDidNotChangeWorks(t *testing. runAndAssertThatDashboardTestIsCorrect(t, testGetSLIEventDataWithDefaultStartAndEnd, handler, rClient, getSLIFinishedEventAssertionsFunc, createFailedSLIResultAssertionsFunc(indicator)) } -func runAndAssertThatDashboardTestIsCorrect(t *testing.T, getSLIEventData *getSLIEventData, handler http.Handler, rClient keptn.ResourceClientInterface, getSLIFinishedEventAssertionsFunc func(t *testing.T, actual *keptnv2.GetSLIFinishedEventData), sliResultAssertionsFuncs ...func(t *testing.T, actual *keptnv2.SLIResult)) { +func runAndAssertThatDashboardTestIsCorrect(t *testing.T, getSLIEventData *getSLIEventData, handler http.Handler, rClient keptn.SLOAndSLIClientInterface, getSLIFinishedEventAssertionsFunc func(t *testing.T, actual *keptnv2.GetSLIFinishedEventData), sliResultAssertionsFuncs ...func(t *testing.T, actual *keptnv2.SLIResult)) { // we do not need custom queries, as we are using the dashboard kClient := &keptnClientMock{} diff --git a/internal/sli/test_helper_test.go b/internal/sli/test_helper_test.go index 8b429b378..d66a62771 100644 --- a/internal/sli/test_helper_test.go +++ b/internal/sli/test_helper_test.go @@ -50,7 +50,7 @@ func createTestGetSLIEventDataWithStartAndEnd(sliStart string, sliEnd string) *g } } -func runTestAndAssertNoError(t *testing.T, ev *getSLIEventData, handler http.Handler, kClient *keptnClientMock, rClient keptn.ResourceClientInterface, dashboard string) { +func runTestAndAssertNoError(t *testing.T, ev *getSLIEventData, handler http.Handler, kClient *keptnClientMock, rClient keptn.SLOAndSLIClientInterface, dashboard string) { eh, _, teardown := createGetSLIEventHandler(t, ev, handler, kClient, rClient, dashboard) defer teardown() @@ -108,7 +108,7 @@ func createFailedSLIResultAssertionsFunc(expectedMetric string) func(*testing.T, } } -func createGetSLIEventHandler(t *testing.T, keptnEvent GetSLITriggeredAdapterInterface, handler http.Handler, kClient keptn.ClientInterface, rClient keptn.ResourceClientInterface, dashboard string) (*GetSLIEventHandler, string, func()) { +func createGetSLIEventHandler(t *testing.T, keptnEvent GetSLITriggeredAdapterInterface, handler http.Handler, kClient keptn.ClientInterface, rClient keptn.SLOAndSLIClientInterface, dashboard string) (*GetSLIEventHandler, string, func()) { httpClient, url, teardown := test.CreateHTTPSClient(handler) dtCredentials, err := credentials.NewDynatraceCredentials(url, testDynatraceAPIToken)