From dbb357614041e16999fd0026a56b3dd7c5bbf8b0 Mon Sep 17 00:00:00 2001 From: TannerGabriel Date: Mon, 29 Aug 2022 14:29:59 +0200 Subject: [PATCH] feat: Reimplement service with go-sdk (#358) * feat: Implement prometheus-service using go-sdk Signed-off-by: TannerGabriel * Remove old unit tests Signed-off-by: TannerGabriel * Change error logging Signed-off-by: TannerGabriel * Remove distributor Signed-off-by: TannerGabriel * Change health check port Signed-off-by: TannerGabriel * Uncomment unit tests Signed-off-by: TannerGabriel * Rename Kubernetes API variable Signed-off-by: TannerGabriel * Send started and finished event manually Signed-off-by: TannerGabriel * Fix PR comments Signed-off-by: TannerGabriel * Send project context in finished event Signed-off-by: TannerGabriel * Upgrade integration tests to 0.18 Signed-off-by: TannerGabriel * Send alertmanager events over NATS Signed-off-by: TannerGabriel * Move dependencies in go.mod Signed-off-by: TannerGabriel Signed-off-by: TannerGabriel --- chart/templates/deployment.yaml | 91 ++------- chart/values.yaml | 15 +- eventhandling/alertEvent.go | 45 ++++- ...ureEvent.go => configure_event_handler.go} | 178 +++++++++++------- eventhandling/getSliEvent_test.go | 8 +- ...etSliEvent.go => get_sli_event_handler.go} | 175 +++++++++++------ eventhandling/handler.go | 55 ------ go.mod | 11 +- go.sum | 38 +++- main.go | 122 ++++++------ 10 files changed, 396 insertions(+), 342 deletions(-) rename eventhandling/{configureEvent.go => configure_event_handler.go} (66%) rename eventhandling/{getSliEvent.go => get_sli_event_handler.go} (61%) delete mode 100644 eventhandling/handler.go diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index cf4e6ec..98ddad3 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -40,24 +40,24 @@ spec: livenessProbe: httpGet: path: /health - port: http + port: 8081 readinessProbe: httpGet: path: /health - port: http + port: 8081 resources: {{- toYaml .Values.resources | nindent 12 }} env: - name: METRICS_SCRAPE_PATH value: '/metrics' - - name: CONFIGURATION_SERVICE - value: 'http://resource-service:8080' - name: PROMETHEUS_NS value: '{{- include "prometheus-service.namespace" . }}' - name: PROMETHEUS_CM value: 'prometheus-server' - name: PROMETHEUS_LABELS value: 'component=server' + - name: HEALTH_ENDPOINT_PORT + value: '8081' - name: PROMETHEUS_ENDPOINT value: "{{ include "prometheus-service.endpoint" . }}" - name: PROMETHEUS_CONFIG_FILENAME @@ -82,90 +82,39 @@ spec: value: '{{ ((.Values.prometheus).createTargets) | default "true" }}' - name: CREATE_ALERTS value: '{{ ((.Values.prometheus).createAlerts) | default "true" }}' - - name: K8S_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: distributor - image: "{{ .Values.distributor.image.repository }}:{{ .Values.distributor.image.tag | default .Chart.AppVersion }}" - livenessProbe: - httpGet: - path: /health - port: 8080 - initialDelaySeconds: 0 - periodSeconds: 5 - readinessProbe: - httpGet: - path: /health - port: 8080 - initialDelaySeconds: 5 - periodSeconds: 5 - imagePullPolicy: {{ .Values.distributor.image.pullPolicy }} - ports: - - containerPort: 8080 - resources: - requests: - memory: "16Mi" - cpu: "25m" - limits: - memory: "32Mi" - cpu: "100m" - env: - - name: PUBSUB_URL - value: 'nats://keptn-nats' - name: PUBSUB_TOPIC - value: 'sh.keptn.event.monitoring.configure,sh.keptn.event.configure-monitoring.triggered,sh.keptn.event.get-sli.triggered' - - name: PUBSUB_RECIPIENT - value: '127.0.0.1' - - name: PUBSUB_RECIPIENT_PATH - value: '/events' - - name: STAGE_FILTER - value: "{{ .Values.distributor.stageFilter }}" - - name: PROJECT_FILTER - value: "{{ .Values.distributor.projectFilter }}" - - name: SERVICE_FILTER - value: "{{ .Values.distributor.serviceFilter }}" - - name: DISTRIBUTOR_VERSION - value: {{ .Values.distributor.image.tag | default .Chart.AppVersion }} - - name: VERSION + value: {{ ((.Values).subscription).pubsubTopic | default "sh.keptn.>" }} + - name: K8S_DEPLOYMENT_NAME valueFrom: fieldRef: - fieldPath: metadata.labels['app.kubernetes.io/version'] - - name: LOCATION + apiVersion: v1 + fieldPath: 'metadata.labels[''app.kubernetes.io/name'']' + - name: K8S_DEPLOYMENT_VERSION valueFrom: fieldRef: - fieldPath: metadata.labels['app.kubernetes.io/component'] - - name: K8S_DEPLOYMENT_NAME + apiVersion: v1 + fieldPath: 'metadata.labels[''app.kubernetes.io/version'']' + - name: K8S_DEPLOYMENT_COMPONENT valueFrom: fieldRef: - fieldPath: metadata.labels['app.kubernetes.io/name'] - - name: K8S_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name + apiVersion: v1 + fieldPath: 'metadata.labels[''app.kubernetes.io/component'']' - name: K8S_NAMESPACE valueFrom: fieldRef: + apiVersion: v1 fieldPath: metadata.namespace - name: K8S_NODE_NAME valueFrom: fieldRef: + apiVersion: v1 fieldPath: spec.nodeName - {{- if .Values.remoteControlPlane.enabled }} - - name: KEPTN_API_ENDPOINT - value: "{{ .Values.remoteControlPlane.api.protocol }}://{{ .Values.remoteControlPlane.api.hostname }}/api" - - name: KEPTN_API_TOKEN - value: "{{ .Values.remoteControlPlane.api.token }}" - - name: HTTP_SSL_VERIFY - {{- $apiValidateTls := .Values.remoteControlPlane.api.apiValidateTls | ternary "true" "false" }} - value: "{{ $apiValidateTls }}" - {{- end }} - {{- if (((.Values.distributor).config).queueGroup).enabled | default true }} - - name: PUBSUB_GROUP + - name: K8S_POD_NAME valueFrom: fieldRef: - fieldPath: metadata.labels['app.kubernetes.io/name'] - {{- end }} + apiVersion: v1 + fieldPath: metadata.name + {{- with .Values.nodeSelector }} nodeSelector: diff --git a/chart/values.yaml b/chart/values.yaml index 30531eb..bcc2c4d 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -10,6 +10,9 @@ image: # Overrides the image tag whose default is the chart appVersion. tag: "" +subscription: + pubsubTopic: "sh.keptn.event.monitoring.configure,sh.keptn.event.get-sli.triggered" # Sets the events the service subscribes to + # Prometheus specific configuration prometheus: namespace: "" # K8s namespace where prometheus is installed @@ -21,18 +24,6 @@ prometheus: autodetect: true # Enable of the auto-detection of the Prometheus installation autodetect_am: true # Enable of the auto-detection of the Prometheus Alertmanager installation -distributor: - stageFilter: "" # Sets the stage this helm service belongs to - serviceFilter: "" # Sets the service this helm service belongs to - projectFilter: "" # Sets the project this helm service belongs to - image: - repository: docker.io/keptn/distributor # Container Image Name - pullPolicy: IfNotPresent # Kubernetes Image Pull Policy - tag: "0.18.0" # Container Tag - config: - queueGroup: - enabled: true # Enable connection via Nats queue group to support exactly-once message processing - # Note: Remote Control Plane is currently not supported by prometheus-service - please keep this setting disabled remoteControlPlane: enabled: false # Enables remote execution plane mode diff --git a/eventhandling/alertEvent.go b/eventhandling/alertEvent.go index 8a5f333..9dda5de 100644 --- a/eventhandling/alertEvent.go +++ b/eventhandling/alertEvent.go @@ -1,10 +1,14 @@ package eventhandling import ( + "context" "crypto/sha256" "encoding/json" + "errors" "fmt" keptncommons "github.com/keptn/go-utils/pkg/lib" + "github.com/nats-io/nats.go" + "log" "net/http" "net/url" "strings" @@ -13,6 +17,7 @@ import ( "github.com/keptn/go-utils/pkg/lib/keptn" keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0" + cenats "github.com/cloudevents/sdk-go/protocol/nats/v2" cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/google/uuid" ) @@ -145,18 +150,50 @@ func createAndSendCE(problemData remediationTriggeredEventData, shkeptncontext s return fmt.Errorf("unable to set cloud event data: %w", err) } - keptnHandler, err := keptnv2.NewKeptn(&event, keptn.KeptnOpts{}) + err = forwardEventToNATSServer(event) if err != nil { - return fmt.Errorf("could not initialize Keptn Handler: %s", err.Error()) + return err } - if err := keptnHandler.SendCloudEvent(event); err != nil { - return fmt.Errorf("could not send event: %s", err.Error()) + return nil +} + +func forwardEventToNATSServer(event cloudevents.Event) error { + pubSubConnection, err := createPubSubConnection(event.Context.GetType()) + if err != nil { + return err } + c, err := cloudevents.NewClient(pubSubConnection) + if err != nil { + log.Printf("Failed to create cloudevents client: %v", err) + return err + } + + cloudevents.WithEncodingStructured(context.Background()) + + if result := c.Send(context.Background(), event); cloudevents.IsUndelivered(result) { + log.Printf("Failed to send cloud event: %v", result.Error()) + } else { + log.Printf("Sent: %s, accepted: %t", event.ID(), cloudevents.IsACK(result)) + } return nil } +func createPubSubConnection(topic string) (*cenats.Sender, error) { + if topic == "" { + return nil, errors.New("no PubSub Topic defined") + } + + p, err := cenats.NewSender("nats://keptn-nats", topic, cenats.NatsOptions(nats.MaxReconnects(-1))) + if err != nil { + log.Printf("Failed to create nats protocol, %v", err) + return nil, err + } + + return p, nil +} + // createOrApplyKeptnContext re-uses the existing Keptn Context or creates a new one based on prometheus fingerprint func createOrApplyKeptnContext(contextID string) string { uuid.SetRand(nil) diff --git a/eventhandling/configureEvent.go b/eventhandling/configure_event_handler.go similarity index 66% rename from eventhandling/configureEvent.go rename to eventhandling/configure_event_handler.go index 61dc8bb..e3ca831 100644 --- a/eventhandling/configureEvent.go +++ b/eventhandling/configure_event_handler.go @@ -4,23 +4,25 @@ import ( "context" "errors" "fmt" + "github.com/kelseyhightower/envconfig" + "github.com/keptn/go-utils/pkg/sdk" "log" + "os" "strings" "time" - cloudevents "github.com/cloudevents/sdk-go/v2" "gopkg.in/yaml.v2" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" configutils "github.com/keptn/go-utils/pkg/api/utils" keptnevents "github.com/keptn/go-utils/pkg/lib" - "github.com/keptn/go-utils/pkg/lib/keptn" keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0" "github.com/gobwas/glob" "github.com/keptn-contrib/prometheus-service/utils" "github.com/keptn-contrib/prometheus-service/utils/prometheus" + api "github.com/keptn/go-utils/pkg/api/utils" prometheus_model "github.com/prometheus/common/model" ) @@ -28,10 +30,11 @@ const metricsScrapePathEnvName = "METRICS_SCRAPE_PATH" // ConfigureMonitoringEventHandler is responsible for processing configure monitoring events type ConfigureMonitoringEventHandler struct { - logger keptn.LoggerInterface - event cloudevents.Event - keptnHandler *keptnv2.Keptn - k8sNamespace string +} + +// NewConfigureMonitoringEventHandler creates a new ConfigureMonitoringEventHandler +func NewConfigureMonitoringEventHandler() *ConfigureMonitoringEventHandler { + return &ConfigureMonitoringEventHandler{} } type alertingRules struct { @@ -65,42 +68,54 @@ type alertingAnnotations struct { Description string `json:"description" yaml:"descriptions"` } -// HandleEvent processes an event -func (eh ConfigureMonitoringEventHandler) HandleEvent() error { +// Execute processes an event +func (eh ConfigureMonitoringEventHandler) Execute(k sdk.IKeptn, event sdk.KeptnEvent) (interface{}, *sdk.Error) { + k.Logger().Infof("Handling configure monitoring event from %s with id: %s and context: %s", *event.Source, event.ID, event.Shkeptncontext) + + if err := envconfig.Process("", &env); err != nil { + k.Logger().Error("Failed to process env var: " + err.Error()) + } + eventData := &keptnevents.ConfigureMonitoringEventData{} - if err := eh.event.DataAs(eventData); err != nil { - return err + if err := keptnv2.Decode(event.Data, eventData); err != nil { + return nil, &sdk.Error{Err: err, StatusType: keptnv2.StatusErrored, ResultType: keptnv2.ResultFailed, Message: "failed to decode get-sli.triggered event: " + err.Error()} } - if eventData.Type != "prometheus" { - return nil + + if err := eh.sendConfigureMonitoringStartedEvent(k, event); err != nil { + k.Logger().Infof("Error while sending configure-monitoring.started event: %s", err.Message) + return nil, err } - err := eh.configurePrometheusAndStoreResources(eventData) + err := eh.configurePrometheusAndStoreResources(k, eventData, os.Getenv("K8S_NAMESPACE")) if err != nil { - eh.logger.Error(err.Error()) - return eh.handleError(err.Error()) + k.Logger().Error(err.Error()) + return nil, &sdk.Error{Err: err, StatusType: keptnv2.StatusErrored, ResultType: keptnv2.ResultFailed, Message: "configure prometheus failed with error: " + err.Error()} } - if err = eh.sendConfigureMonitoringFinishedEvent(keptnv2.StatusSucceeded, keptnv2.ResultPass, "Prometheus successfully configured and rule created"); err != nil { - eh.logger.Error(err.Error()) + finishedEventData := eh.getConfigureMonitoringFinishedEvent(keptnv2.StatusSucceeded, keptnv2.ResultPass, *eventData, "Prometheus successfully configured and rule created") + k.Logger().Infof("Sending configure-monitoring.finished event with context: %s", event.Shkeptncontext) + if err := eh.sendConfigureMonitoringFinishedEvent(k, event, finishedEventData); err != nil { + k.Logger().Infof("Error while sending configure-monitoring.finished event: %s", err.Message) + return nil, err } - return nil + + return finishedEventData, nil } // configurePrometheusAndStoreResources -func (eh ConfigureMonitoringEventHandler) configurePrometheusAndStoreResources(eventData *keptnevents.ConfigureMonitoringEventData) error { +func (eh ConfigureMonitoringEventHandler) configurePrometheusAndStoreResources(k sdk.IKeptn, eventData *keptnevents.ConfigureMonitoringEventData, k8sNamespace string) error { // (1) check if prometheus is installed - if eh.isPrometheusInstalled() { + if eh.isPrometheusInstalled(k) { if utils.EnvVarOrDefault("CREATE_TARGETS", "true") == "true" { - eh.logger.Debug("Configure prometheus monitoring with keptn") - if err := eh.updatePrometheusConfigMap(*eventData); err != nil { + k.Logger().Debug("Configure prometheus monitoring with keptn") + if err := eh.updatePrometheusConfigMap(k, *eventData); err != nil { return err } } if utils.EnvVarOrDefault("CREATE_ALERTS", "true") == "true" { - eh.logger.Debug("Configure prometheus alert manager with keptn") - err := eh.configurePrometheusAlertManager(eh.k8sNamespace) + k.Logger().Debug("Configure prometheus alert manager with keptn") + err := eh.configurePrometheusAlertManager(k, k8sNamespace) if err != nil { return err } @@ -110,16 +125,16 @@ func (eh ConfigureMonitoringEventHandler) configurePrometheusAndStoreResources(e return nil } -func (eh ConfigureMonitoringEventHandler) isPrometheusInstalled() bool { - eh.logger.Debug("Check if prometheus service in " + env.PrometheusNamespace + " namespace is available") +func (eh ConfigureMonitoringEventHandler) isPrometheusInstalled(k sdk.IKeptn) bool { + k.Logger().Debug("Check if prometheus service in " + env.PrometheusNamespace + " namespace is available") svcList, err := getPrometheusServiceFromK8s() if err != nil { - eh.logger.Errorf("Error locating prometheus service in k8s: %v", err) + k.Logger().Errorf("Error locating prometheus service in k8s: %v", err) return false } if len(svcList.Items) > 0 { - eh.logger.Debug("Prometheus service in " + env.PrometheusNamespace + " namespace is available") + k.Logger().Debug("Prometheus service in " + env.PrometheusNamespace + " namespace is available") return true } @@ -142,34 +157,38 @@ func getPrometheusAlertManagerServiceFromK8s() (*v1.ServiceList, error) { return svcList, err } -func (eh ConfigureMonitoringEventHandler) configurePrometheusAlertManager(namespace string) error { - eh.logger.Info("Configuring Prometheus AlertManager...") +func (eh ConfigureMonitoringEventHandler) configurePrometheusAlertManager(k sdk.IKeptn, namespace string) error { + k.Logger().Info("Configuring Prometheus AlertManager...") prometheusHelper, err := prometheus.NewPrometheusHelper(namespace) - eh.logger.Info("Updating Prometheus AlertManager configmap...") + k.Logger().Info("Updating Prometheus AlertManager configmap...") err = prometheusHelper.UpdateAMConfigMap(env.AlertManagerConfigMap, env.AlertManagerConfigFileName, env.AlertManagerNamespace) if err != nil { return err } - eh.logger.Info("Prometheus AlertManager configuration successfully") + k.Logger().Info("Prometheus AlertManager configuration successfully") return nil } // updatePrometheusConfigMap updates the prometheus configmap with scrape configs and alerting rules -func (eh ConfigureMonitoringEventHandler) updatePrometheusConfigMap(eventData keptnevents.ConfigureMonitoringEventData) error { - shipyard, err := eh.keptnHandler.GetShipyard() +func (eh ConfigureMonitoringEventHandler) updatePrometheusConfigMap(k sdk.IKeptn, eventData keptnevents.ConfigureMonitoringEventData) error { + scope := api.NewResourceScope() + scope.Project(eventData.Project) + scope.Resource("shipyard.yaml") + + shipyard, err := GetShipyard(k.GetResourceHandler(), *scope) if err != nil { return err } - api, err := utils.GetKubeClient() + kubeAPI, err := utils.GetKubeClient() if err != nil { return err } - cmPrometheus, err := api.CoreV1().ConfigMaps(env.PrometheusNamespace).Get(context.TODO(), env.PrometheusConfigMap, metav1.GetOptions{}) + cmPrometheus, err := kubeAPI.CoreV1().ConfigMaps(env.PrometheusNamespace).Get(context.TODO(), env.PrometheusConfigMap, metav1.GetOptions{}) if err != nil { // Print better error message when role binding is missing g := glob.MustCompile("configmaps * is forbidden: User * cannot get resource * in API group * in the namespace *") @@ -187,7 +206,7 @@ func (eh ConfigureMonitoringEventHandler) updatePrometheusConfigMap(eventData ke scrapeInterval, err := time.ParseDuration(scrapeIntervalString) if err != nil { - eh.logger.Error("Error while converting SCRAPE_INTERVAL value. Using default value instead!") + k.Logger().Error("Error while converting SCRAPE_INTERVAL value. Using default value instead!") scrapeInterval = 5 * time.Second } @@ -215,7 +234,7 @@ func (eh ConfigureMonitoringEventHandler) updatePrometheusConfigMap(eventData ke // .- createScrapeJobConfig(scrapeConfig, config, eventData.Project, stage.Name, eventData.Service, false, false, scrapeInterval) - alertingRulesConfig, err = eh.createPrometheusAlertsIfSLOsAndRemediationDefined(eventData, stage, + alertingRulesConfig, err = eh.createPrometheusAlertsIfSLOsAndRemediationDefined(k, eventData, stage, alertingRulesConfig) if err != nil { @@ -235,7 +254,7 @@ func (eh ConfigureMonitoringEventHandler) updatePrometheusConfigMap(eventData ke // apply cmPrometheus.Data["alerting_rules.yml"] = string(alertingRulesYAMLString) cmPrometheus.Data[env.PrometheusConfigFileName] = string(updatedConfigYAMLString) - _, err = api.CoreV1().ConfigMaps(env.PrometheusNamespace).Update(context.TODO(), cmPrometheus, metav1.UpdateOptions{}) + _, err = kubeAPI.CoreV1().ConfigMaps(env.PrometheusNamespace).Update(context.TODO(), cmPrometheus, metav1.UpdateOptions{}) if err != nil { return err } @@ -243,12 +262,12 @@ func (eh ConfigureMonitoringEventHandler) updatePrometheusConfigMap(eventData ke } func (eh ConfigureMonitoringEventHandler) createPrometheusAlertsIfSLOsAndRemediationDefined( - eventData keptnevents.ConfigureMonitoringEventData, stage keptnv2.Stage, alertingRulesConfig alertingRules, + k sdk.IKeptn, eventData keptnevents.ConfigureMonitoringEventData, stage keptnv2.Stage, alertingRulesConfig alertingRules, ) (alertingRules, error) { // fetch SLOs for the given service and stage - slos, err := retrieveSLOs(eventData, stage.Name, eh.logger) + slos, err := retrieveSLOs(k.GetResourceHandler(), eventData, stage.Name) if err != nil || slos == nil { - eh.logger.Info("No SLO file found for stage " + stage.Name + ". No alerting rules created for this stage") + k.Logger().Info("No SLO file found for stage " + stage.Name + ". No alerting rules created for this stage") return alertingRulesConfig, nil } @@ -260,10 +279,10 @@ func (eh ConfigureMonitoringEventHandler) createPrometheusAlertsIfSLOsAndRemedia resourceScope.Stage(stage.Name) resourceScope.Resource(remediationFileDefaultName) - _, err = eh.keptnHandler.ResourceHandler.GetResource(*resourceScope) + _, err = k.GetResourceHandler().GetResource(*resourceScope) if errors.Is(err, configutils.ResourceNotFoundError) { - eh.logger.Infof("No remediation defined for project %s stage %s, skipping setup of prometheus alerts", + k.Logger().Infof("No remediation defined for project %s stage %s, skipping setup of prometheus alerts", eventData.Project, stage.Name) return alertingRulesConfig, nil } @@ -300,7 +319,7 @@ func (eh ConfigureMonitoringEventHandler) createPrometheusAlertsIfSLOsAndRemedia ) // get SLI queries - projectCustomQueries, err := getCustomQueries(eh.keptnHandler, eventData.Project, stage.Name, eventData.Service) + projectCustomQueries, err := getCustomQueries(k.GetResourceHandler(), eventData.Project, stage.Name, eventData.Service) if err != nil { log.Println("Failed to get custom queries for project " + eventData.Project) log.Println(err.Error()) @@ -311,20 +330,20 @@ func (eh ConfigureMonitoringEventHandler) createPrometheusAlertsIfSLOsAndRemedia prometheusHandler.CustomQueries = projectCustomQueries } - eh.logger.Info("Going over SLO.objectives") + k.Logger().Info("Going over SLO.objectives") for _, objective := range slos.Objectives { - eh.logger.Info("SLO:" + objective.DisplayName + ", " + objective.SLI) + k.Logger().Info("SLO:" + objective.DisplayName + ", " + objective.SLI) // Get Prometheus Metric Expression end := time.Now() start := end.Add(-180 * time.Second) expr, err := prometheusHandler.GetMetricQuery(objective.SLI, start, end) if err != nil || expr == "" { - eh.logger.Error("No query defined for SLI " + objective.SLI + " in project " + eventData.Project) + k.Logger().Error("No query defined for SLI " + objective.SLI + " in project " + eventData.Project) continue } - eh.logger.Info("expr=" + expr) + k.Logger().Info("expr=" + expr) if objective.Pass != nil { for _, criteriaGroup := range objective.Pass { @@ -439,13 +458,7 @@ func getScrapeConfig(config *prometheus.Config, name string) *prometheus.ScrapeC return nil } -func getConfigurationServiceURL() string { - return env.ConfigurationServiceURL -} - -func retrieveSLOs(eventData keptnevents.ConfigureMonitoringEventData, stage string, logger keptn.LoggerInterface) (*keptnevents.ServiceLevelObjectives, error) { - resourceHandler := configutils.NewResourceHandler(getConfigurationServiceURL()) - +func retrieveSLOs(resourceHandler sdk.ResourceHandler, eventData keptnevents.ConfigureMonitoringEventData, stage string) (*keptnevents.ServiceLevelObjectives, error) { resourceScope := configutils.NewResourceScope() resourceScope.Project(eventData.Project) resourceScope.Service(eventData.Service) @@ -467,25 +480,50 @@ func retrieveSLOs(eventData keptnevents.ConfigureMonitoringEventData, stage stri return &slos, nil } -func (eh ConfigureMonitoringEventHandler) sendConfigureMonitoringFinishedEvent(status keptnv2.StatusType, result keptnv2.ResultType, msg string) error { - _, err := eh.keptnHandler.SendTaskFinishedEvent(&keptnv2.EventData{ - Status: status, - Result: result, - Message: msg, - }, utils.ServiceName) +func (eh ConfigureMonitoringEventHandler) getConfigureMonitoringFinishedEvent(status keptnv2.StatusType, result keptnv2.ResultType, configureMonitoringTriggeredEven keptnevents.ConfigureMonitoringEventData, msg string) keptnv2.ConfigureMonitoringFinishedEventData { - if err != nil { - return fmt.Errorf("could not send %s event: %s", keptnv2.GetFinishedEventType(keptnv2.ConfigureMonitoringTaskName), err.Error()) + return keptnv2.ConfigureMonitoringFinishedEventData{ + EventData: keptnv2.EventData{ + Project: configureMonitoringTriggeredEven.Project, + Service: configureMonitoringTriggeredEven.Service, + Status: status, + Result: result, + Message: msg, + }, } +} +func (eh ConfigureMonitoringEventHandler) sendConfigureMonitoringStartedEvent(k sdk.IKeptn, event sdk.KeptnEvent) *sdk.Error { + eventType := keptnv2.GetTriggeredEventType(keptnv2.ConfigureMonitoringTaskName) + event.Type = &eventType + + if err := k.SendStartedEvent(event); err != nil { + return &sdk.Error{Err: err, StatusType: keptnv2.StatusErrored, ResultType: keptnv2.ResultFailed, Message: "Error sending configure-monitoring.started: " + err.Error()} + } return nil } -func (eh ConfigureMonitoringEventHandler) handleError(msg string) error { - //logger.Error(msg) - if err := eh.sendConfigureMonitoringFinishedEvent(keptnv2.StatusErrored, keptnv2.ResultFailed, msg); err != nil { - // an additional error occurred when trying to send configure monitoring finished back to Keptn - eh.logger.Error(err.Error()) +func (eh ConfigureMonitoringEventHandler) sendConfigureMonitoringFinishedEvent(k sdk.IKeptn, event sdk.KeptnEvent, eventData keptnv2.ConfigureMonitoringFinishedEventData) *sdk.Error { + eventType := keptnv2.GetTriggeredEventType(keptnv2.ConfigureMonitoringTaskName) + event.Type = &eventType + + if err := k.SendFinishedEvent(event, eventData); err != nil { + return &sdk.Error{Err: err, StatusType: keptnv2.StatusErrored, ResultType: keptnv2.ResultFailed, Message: "Error sending configure-monitoring.started: " + err.Error()} + } + return nil +} + +// GetShipyard returns the shipyard definition of a project +func GetShipyard(resourceHandler sdk.ResourceHandler, scope api.ResourceScope) (*keptnv2.Shipyard, error) { + shipyardResource, err := resourceHandler.GetResource(scope) + if err != nil { + return nil, err + } + + shipyard := keptnv2.Shipyard{} + err = yaml.Unmarshal([]byte(shipyardResource.ResourceContent), &shipyard) + if err != nil { + return nil, err } - return errors.New(msg) + return &shipyard, nil } diff --git a/eventhandling/getSliEvent_test.go b/eventhandling/getSliEvent_test.go index aab8129..5deabae 100644 --- a/eventhandling/getSliEvent_test.go +++ b/eventhandling/getSliEvent_test.go @@ -4,17 +4,15 @@ import ( "encoding/json" cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/golang/mock/gomock" + prometheusUtils "github.com/keptn-contrib/prometheus-service/utils/prometheus" + prometheusfake "github.com/keptn-contrib/prometheus-service/utils/prometheus/fake" + keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0" prometheusAPI "github.com/prometheus/client_golang/api/prometheus/v1" prometheusModel "github.com/prometheus/common/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "math/rand" "testing" - - prometheusUtils "github.com/keptn-contrib/prometheus-service/utils/prometheus" - prometheusfake "github.com/keptn-contrib/prometheus-service/utils/prometheus/fake" - - keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0" ) const eventJSON = ` diff --git a/eventhandling/getSliEvent.go b/eventhandling/get_sli_event_handler.go similarity index 61% rename from eventhandling/getSliEvent.go rename to eventhandling/get_sli_event_handler.go index 71eba99..cf29779 100644 --- a/eventhandling/getSliEvent.go +++ b/eventhandling/get_sli_event_handler.go @@ -4,26 +4,35 @@ import ( "context" "errors" "fmt" + "github.com/kelseyhightower/envconfig" "github.com/keptn-contrib/prometheus-service/utils/prometheus" + "github.com/keptn/go-utils/pkg/api/models" + api "github.com/keptn/go-utils/pkg/api/utils" + keptncommon "github.com/keptn/go-utils/pkg/lib/keptn" + "github.com/keptn/go-utils/pkg/sdk" "gopkg.in/yaml.v2" + "k8s.io/client-go/kubernetes" "log" "net/url" "strings" - cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/keptn-contrib/prometheus-service/utils" keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" v1 "k8s.io/client-go/kubernetes/typed/core/v1" ) // GetSliEventHandler is responsible for processing configure monitoring events type GetSliEventHandler struct { - event cloudevents.Event - keptnHandler *keptnv2.Keptn - kubeClient *kubernetes.Clientset + kubeClient kubernetes.Clientset +} + +// NewGetSliEventHandler creates a new TriggeredEventHandler +func NewGetSliEventHandler(kubeClient kubernetes.Clientset) *GetSliEventHandler { + return &GetSliEventHandler{ + kubeClient: kubeClient, + } } type prometheusCredentials struct { @@ -32,46 +41,23 @@ type prometheusCredentials struct { Password string `json:"password" yaml:"password"` } -// HandleEvent processes an event -func (eh GetSliEventHandler) HandleEvent() error { - eventData := &keptnv2.GetSLITriggeredEventData{} - err := eh.event.DataAs(eventData) - if err != nil { - return err - } - - // don't continue if SLIProvider is not prometheus - if eventData.GetSLI.SLIProvider != "prometheus" { - return nil - } +var env utils.EnvConfig - // send started event - _, err = eh.keptnHandler.SendTaskStartedEvent(eventData, utils.ServiceName) - if err != nil { - errMsg := fmt.Errorf("failed to send task started CloudEvent: %w", err) - log.Println(errMsg.Error()) - return err +// Execute processes an event +func (eh GetSliEventHandler) Execute(k sdk.IKeptn, event sdk.KeptnEvent) (interface{}, *sdk.Error) { + if err := envconfig.Process("", &env); err != nil { + k.Logger().Error("Failed to process env var: " + err.Error()) } - // helper function to log an error and send an appropriate finished event - sendFinishedErrorEvent := func(err error) error { - log.Printf("sending errored finished event: %s", err.Error()) - - _, sendError := eh.keptnHandler.SendTaskFinishedEvent(&keptnv2.EventData{ - Status: keptnv2.StatusErrored, - Result: keptnv2.ResultFailed, - Message: err.Error(), - }, utils.ServiceName) - - // TODO: Maybe log error to console - - return sendError + eventData := &keptnv2.GetSLITriggeredEventData{} + if err := keptnv2.Decode(event.Data, eventData); err != nil { + return nil, &sdk.Error{Err: err, StatusType: keptnv2.StatusErrored, ResultType: keptnv2.ResultFailed, Message: "failed to decode get-sli.triggered event: " + err.Error()} } // get prometheus API URL for the provided Project from Kubernetes Config Map prometheusAPIURL, err := getPrometheusAPIURL(eventData.Project, eh.kubeClient.CoreV1()) if err != nil { - return sendFinishedErrorEvent(fmt.Errorf("unable to get prometheus api URL: %w", err)) + return nil, &sdk.Error{Err: err, StatusType: keptnv2.StatusErrored, ResultType: keptnv2.ResultFailed, Message: "failed to get Prometheus API URL: " + err.Error()} } deployment := eventData.Deployment // "canary", "primary" or "" (or "direct" or "user_managed") @@ -93,11 +79,9 @@ func (eh GetSliEventHandler) HandleEvent() error { ) // get SLI queries (from SLI.yaml) - projectCustomQueries, err := getCustomQueries(eh.keptnHandler, eventData.Project, eventData.Stage, eventData.Service) + projectCustomQueries, err := getCustomQueries(k.GetResourceHandler(), eventData.Project, eventData.Stage, eventData.Service) if err != nil { - return sendFinishedErrorEvent( - fmt.Errorf("unable to retrieve custom queries for project %s: %w", eventData.Project, err), - ) + return nil, &sdk.Error{Err: err, StatusType: keptnv2.StatusErrored, ResultType: keptnv2.ResultFailed, Message: fmt.Sprintf("unable to retrieve custom queries for project %s: %e", eventData.Project, err)} } // only apply queries if they contain anything @@ -130,8 +114,12 @@ func (eh GetSliEventHandler) HandleEvent() error { // construct finished event data getSliFinishedEventData := &keptnv2.GetSLIFinishedEventData{ EventData: keptnv2.EventData{ - Status: keptnv2.StatusSucceeded, - Result: finalSLIEventResult, + Status: keptnv2.StatusSucceeded, + Result: finalSLIEventResult, + Project: eventData.Project, + Stage: eventData.Stage, + Service: eventData.Service, + Labels: eventData.Labels, }, GetSLI: keptnv2.GetSLIFinished{ IndicatorValues: sliResults, @@ -144,15 +132,7 @@ func (eh GetSliEventHandler) HandleEvent() error { getSliFinishedEventData.EventData.Message = "unable to retrieve metrics" } - // send get-sli.finished event with SLI DATA - _, err = eh.keptnHandler.SendTaskFinishedEvent(getSliFinishedEventData, utils.ServiceName) - if err != nil { - errMsg := fmt.Sprintf("Failed to send task finished CloudEvent (%s), aborting...", err.Error()) - log.Println(errMsg) - return err - } - - return nil + return getSliFinishedEventData, nil } func retrieveMetrics(prometheusHandler *prometheus.Handler, eventData *keptnv2.GetSLITriggeredEventData) []*keptnv2.SLIResult { @@ -182,10 +162,10 @@ func retrieveMetrics(prometheusHandler *prometheus.Handler, eventData *keptnv2.G return sliResults } -func getCustomQueries(keptnHandler *keptnv2.Keptn, project string, stage string, service string) (map[string]string, error) { +func getCustomQueries(resourceHandler sdk.ResourceHandler, project string, stage string, service string) (map[string]string, error) { log.Println("Checking for custom SLI queries") - customQueries, err := keptnHandler.GetSLIConfiguration(project, stage, service, utils.SliResourceURI) + customQueries, err := GetSLIConfiguration(resourceHandler, project, stage, service, utils.SliResourceURI) if err != nil { return nil, err } @@ -259,3 +239,90 @@ func generatePrometheusURL(pc *prometheusCredentials) string { } return strings.Replace(prometheusURL, " ", "", -1) } + +// GetSLIConfiguration retrieves the SLI configuration for a service considering SLI configuration on stage and project level. +// First, the configuration of project-level is retrieved, which is then overridden by configuration on stage level, +// overridden by configuration on service level. +func GetSLIConfiguration(resourceHandler sdk.ResourceHandler, project string, stage string, service string, resourceURI string) (map[string]string, error) { + var res *models.Resource + var err error + SLIs := make(map[string]string) + + // get sli config from project + if project != "" { + scope := api.NewResourceScope() + scope.Project(project) + scope.Resource(resourceURI) + res, err = resourceHandler.GetResource(*scope) + if err != nil { + // return error except "resource not found" type + if !strings.Contains(strings.ToLower(err.Error()), "resource not found") { + return nil, err + } + } + SLIs, err = addResourceContentToSLIMap(SLIs, res) + if err != nil { + return nil, err + } + } + + // get sli config from stage + if project != "" && stage != "" { + scope := api.NewResourceScope() + scope.Project(project) + scope.Stage(stage) + scope.Resource(resourceURI) + res, err = resourceHandler.GetResource(*scope) + if err != nil { + // return error except "resource not found" type + if !strings.Contains(strings.ToLower(err.Error()), "resource not found") { + return nil, err + } + } + SLIs, err = addResourceContentToSLIMap(SLIs, res) + if err != nil { + return nil, err + } + } + + // get sli config from service + if project != "" && stage != "" && service != "" { + scope := api.NewResourceScope() + scope.Project(project) + scope.Stage(stage) + scope.Service(service) + scope.Resource(resourceURI) + res, err = resourceHandler.GetResource(*scope) + if err != nil { + // return error except "resource not found" type + if !strings.Contains(strings.ToLower(err.Error()), "resource not found") { + return nil, err + } + } + SLIs, err = addResourceContentToSLIMap(SLIs, res) + if err != nil { + return nil, err + } + } + + return SLIs, nil +} + +func addResourceContentToSLIMap(SLIs map[string]string, resource *models.Resource) (map[string]string, error) { + if resource != nil { + sliConfig := keptncommon.SLIConfig{} + err := yaml.Unmarshal([]byte(resource.ResourceContent), &sliConfig) + if err != nil { + return nil, err + } + + for key, value := range sliConfig.Indicators { + SLIs[key] = value + } + + if len(SLIs) == 0 { + return nil, errors.New("missing required field: indicators") + } + } + return SLIs, nil +} diff --git a/eventhandling/handler.go b/eventhandling/handler.go deleted file mode 100644 index 0fe72da..0000000 --- a/eventhandling/handler.go +++ /dev/null @@ -1,55 +0,0 @@ -package eventhandling - -import ( - cloudevents "github.com/cloudevents/sdk-go/v2" - "github.com/kelseyhightower/envconfig" - "github.com/keptn-contrib/prometheus-service/utils" - "github.com/keptn/go-utils/pkg/lib/keptn" - keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0" - "k8s.io/client-go/kubernetes" -) - -// PrometheusEventHandler defines a handler for events -type PrometheusEventHandler interface { - HandleEvent() error -} - -// NoOpEventHandler does nothing -type NoOpEventHandler struct { -} - -// HandleEvent processes an event -func (e NoOpEventHandler) HandleEvent() error { - return nil -} - -var env utils.EnvConfig - -// NewEventHandler creates a new Handler for an incoming event -func NewEventHandler(event cloudevents.Event, logger *keptn.Logger, keptnHandler *keptnv2.Keptn, kubeClient *kubernetes.Clientset, k8sNamespace string) PrometheusEventHandler { - logger.Debug("Received event: " + event.Type()) - - if err := envconfig.Process("", &env); err != nil { - logger.Error("Failed to process env var: " + err.Error()) - } - - if event.Type() == keptnv2.GetTriggeredEventType(keptnv2.ConfigureMonitoringTaskName) { - return &ConfigureMonitoringEventHandler{ - logger: logger, - event: event, - keptnHandler: keptnHandler, - k8sNamespace: k8sNamespace, - } - } else if event.Type() == keptnv2.GetTriggeredEventType(keptnv2.GetSLITaskName) { - return &GetSliEventHandler{ - event: event, - keptnHandler: keptnHandler, - kubeClient: kubeClient, - } - } - - logger.Error("Unknown event type " + event.Type()) - - return &NoOpEventHandler{} - -} diff --git a/go.mod b/go.mod index 81a8375..8ad523d 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.17 require ( github.com/Masterminds/semver/v3 v3.1.1 github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 + github.com/cloudevents/sdk-go/protocol/nats/v2 v2.10.1 github.com/cloudevents/sdk-go/v2 v2.10.1 github.com/gobwas/glob v0.2.3 github.com/golang/mock v1.6.0 @@ -12,9 +13,11 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/keptn/go-utils v0.18.0 github.com/mitchellh/mapstructure v1.5.0 + github.com/nats-io/nats.go v1.16.0 github.com/prometheus/alertmanager v0.24.0 github.com/prometheus/client_golang v1.12.2 github.com/prometheus/common v0.32.1 // pin to v0.32.1; must be the same as alertmanager + github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.8.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.24.2 @@ -25,6 +28,7 @@ require ( require ( github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/aws/aws-sdk-go v1.43.11 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -56,6 +60,8 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect + github.com/nats-io/nkeys v0.3.0 // indirect + github.com/nats-io/nuid v1.0.1 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -70,14 +76,15 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.0 // indirect + golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.60.1 // indirect diff --git a/go.sum b/go.sum index c37514b..1a49ea1 100644 --- a/go.sum +++ b/go.sum @@ -66,6 +66,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= +github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.43.11 h1:NebCNJ2QvsFCnsKT1ei98bfwTPEoO2qwtWT42tJ3N3Q= github.com/aws/aws-sdk-go v1.43.11/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= @@ -88,6 +90,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudevents/sdk-go/observability/opentelemetry/v2 v2.0.0-20211001212819-74757a691209 h1:pR23jlIJMXGMxljxP6QYytEsMQpPU2WT3Wjp1FWYOq0= github.com/cloudevents/sdk-go/observability/opentelemetry/v2 v2.0.0-20211001212819-74757a691209/go.mod h1:DmxtN+a7U9ktD8I0nTlI9CCrin/Tf7OdXxE3KBTjlOw= +github.com/cloudevents/sdk-go/protocol/nats/v2 v2.10.1 h1:vhMEC9zc6nIw3HwxaFZF/lT/uTftXx9h++f0KyXJazM= +github.com/cloudevents/sdk-go/protocol/nats/v2 v2.10.1/go.mod h1:9l2pSSkH9AvMCwK8Rscwqtsni30UIWNj/EmgtmaRMmc= github.com/cloudevents/sdk-go/v2 v2.5.0/go.mod h1:nlXhgFkf0uTopxmRXalyMwS2LG70cRGPrxzmjJgSG0U= github.com/cloudevents/sdk-go/v2 v2.10.1 h1:qNFovJ18fWOd8Q9ydWJPk1oiFudXyv1GxJIP7MwPjuM= github.com/cloudevents/sdk-go/v2 v2.10.1/go.mod h1:GpCBmUj7DIRiDhVvsK5d6WCbgTWs8DxAWTRtAwQmIXs= @@ -331,7 +335,9 @@ github.com/keptn/go-utils v0.18.0 h1:dITGnvRmu7sYsW0H6FN0BXMG/RPdjmNtpAATI7E7JAk github.com/keptn/go-utils v0.18.0/go.mod h1:jPys4TFvxkN6KY3IhM5XWBeCCPQeLzsT6zTwt4iYfes= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.14.4 h1:eijASRJcobkVtSt81Olfh7JX43osYLwy5krOJo6YEu4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -362,6 +368,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -387,6 +395,20 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU= +github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= +github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= +github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a h1:lem6QCvxR0Y28gth9P+wV2K/zYUUAkJ+55U8cpS0p5I= +github.com/nats-io/nats-server/v2 v2.3.4/go.mod h1:3mtbaN5GkCo/Z5T3nNj0I0/W1fPkKzLiDC6jjWJKp98= +github.com/nats-io/nats-server/v2 v2.8.4 h1:0jQzze1T9mECg8YZEl8+WYUXb9JKluJfCBriPUtluB4= +github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nats.go v1.16.0 h1:zvLE7fGBQYW6MWaFaRdsgm9qT39PJDQoju+DS8KsO1g= +github.com/nats-io/nats.go v1.16.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= +github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -456,6 +478,8 @@ github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= @@ -535,11 +559,15 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -656,6 +684,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -718,8 +747,8 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= @@ -737,6 +766,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -903,8 +933,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/main.go b/main.go index 2bcaa7d..981cf24 100644 --- a/main.go +++ b/main.go @@ -1,14 +1,13 @@ package main import ( - "context" "encoding/json" "fmt" - "github.com/cloudevents/sdk-go/v2/types" "github.com/google/uuid" "github.com/keptn-contrib/prometheus-service/eventhandling" "github.com/keptn-contrib/prometheus-service/utils" - keptn "github.com/keptn/go-utils/pkg/lib" + "github.com/keptn/go-utils/pkg/sdk" + "github.com/sirupsen/logrus" "io/ioutil" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -16,8 +15,7 @@ import ( "net/http" "os" - cloudevents "github.com/cloudevents/sdk-go/v2" - "github.com/kelseyhightower/envconfig" + keptnevents "github.com/keptn/go-utils/pkg/lib" keptncommon "github.com/keptn/go-utils/pkg/lib/keptn" keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0" ) @@ -26,84 +24,78 @@ var ( env utils.EnvConfig ) -func main() { - logger := keptncommon.NewLogger("", "", utils.ServiceName) - - env = utils.EnvConfig{} - - if err := envconfig.Process("", &env); err != nil { - logger.Error(fmt.Sprintf("Failed to process env var: %s", err)) - } - - logger.Debug(fmt.Sprintf("Configuration service: %s", env.ConfigurationServiceURL)) - logger.Debug(fmt.Sprintf("Port: %d, Path: %s", env.Port, env.Path)) +const serviceName = "prometheus-service" +const envVarLogLevel = "LOG_LEVEL" +const monitoringTriggeredEvent = keptnevents.ConfigureMonitoringEventType +const getSliTriggeredEvent = "sh.keptn.event.get-sli.triggered" - // start internal CloudEvents handler (on port env.Port) - os.Exit(_main(env)) -} - -func _main(env utils.EnvConfig) int { - ctx := context.Background() - ctx = cloudevents.WithEncodingStructured(ctx) - - p, err := cloudevents.NewHTTP() - if err != nil { - log.Fatalf("failed to create protocol: %s", err.Error()) +func main() { + if os.Getenv(envVarLogLevel) != "" { + logLevel, err := logrus.ParseLevel(os.Getenv(envVarLogLevel)) + if err != nil { + logrus.WithError(err).Error("could not parse log level provided by 'LOG_LEVEL' env var") + logrus.SetLevel(logrus.InfoLevel) + } else { + logrus.SetLevel(logLevel) + } } - ceHandler, err := cloudevents.NewHTTPReceiveHandler(ctx, p, gotEvent) - if err != nil { - log.Fatalf("failed to create handler: %s", err.Error()) - } + log.Printf("Starting %s", serviceName) + // Creating an HTTP listener on port 8080 to receive alerts from Prometheus directly http.HandleFunc("/", HTTPGetHandler) - http.Handle(env.Path, ceHandler) - err = http.ListenAndServe(":8080", nil) - if err != nil { - log.Fatal(err) - } - - return 0 -} + go func() { + log.Println("Starting alert manager endpoint") + err := http.ListenAndServe(":8080", nil) + if err != nil { + log.Fatalf("Error with HTTP server: %e", err) + } + }() -// gotEvent processes an incoming CloudEvent -func gotEvent(event cloudevents.Event) error { - keptnContext, err := event.Context.GetExtension("shkeptncontext") + clusterConfig, err := rest.InClusterConfig() if err != nil { - return fmt.Errorf("cloud event does not contain the field 'shkeptncontext'") + log.Fatalf("unable to create kubernetes cluster config: %e", err) } - shkeptncontext, err := types.ToString(keptnContext) + kubeClient, err := kubernetes.NewForConfig(clusterConfig) if err != nil { - return fmt.Errorf("field 'shkeptncontext' can not be parsed as keptn context") - } - - logger := keptncommon.NewLogger(shkeptncontext, "", utils.ServiceName) - - // convert v0.1.4 spec monitoring.configure CloudEvent into a v0.2.0 spec configure-monitoring.triggered CloudEvent - if event.Type() == keptn.ConfigureMonitoringEventType { - event.SetType(keptnv2.GetTriggeredEventType(keptnv2.ConfigureMonitoringTaskName)) + log.Fatalf("unable to create kubernetes client: %e", err) } - keptnHandler, err := keptnv2.NewKeptn(&event, keptncommon.KeptnOpts{}) + log.Fatal(sdk.NewKeptn( + serviceName, + sdk.WithTaskHandler( + monitoringTriggeredEvent, + eventhandling.NewConfigureMonitoringEventHandler(), + prometheusTypeFilter), + sdk.WithTaskHandler( + getSliTriggeredEvent, + eventhandling.NewGetSliEventHandler(*kubeClient), + prometheusSLIProviderFilter), + sdk.WithLogger(logrus.New()), + ).Start()) +} - if err != nil { - return fmt.Errorf("could not create Keptn handler: %v", err) +// prometheusSLIProviderFilter filters get-sli.triggered events for Prometheus +func prometheusSLIProviderFilter(keptnHandle sdk.IKeptn, event sdk.KeptnEvent) bool { + data := &keptnv2.GetSLITriggeredEventData{} + if err := keptnv2.Decode(event.Data, data); err != nil { + keptnHandle.Logger().Errorf("Could not parse get-sli.triggered event: %s", err.Error()) + return false } - clusterConfig, err := rest.InClusterConfig() - if err != nil { - // TODO: Send Error log event to Keptn - return fmt.Errorf("unable to create kubernetes cluster config: %w", err) - } + return data.GetSLI.SLIProvider == "prometheus" +} - kubeClient, err := kubernetes.NewForConfig(clusterConfig) - if err != nil { - // TODO: Send Error log event to Keptn - return fmt.Errorf("unable to create kubernetes client: %w", err) +// prometheusTypeFilter filters monitoring.configure events for Prometheus +func prometheusTypeFilter(keptnHandle sdk.IKeptn, event sdk.KeptnEvent) bool { + data := &keptnevents.ConfigureMonitoringEventData{} + if err := keptnv2.Decode(event.Data, data); err != nil { + keptnHandle.Logger().Errorf("Could not parse monitoring.configure event: %s", err.Error()) + return false } - return eventhandling.NewEventHandler(event, logger, keptnHandler, kubeClient, env.K8sNamespace).HandleEvent() + return data.Type == "prometheus" } // HTTPGetHandler will handle all requests for '/health' and '/ready'