func sendGetSLIFinishedEvent(inputEvent cloudevents.Event, eventData *keptnv2.GetSLITriggeredEventData, indicatorValues []*keptnv2.SLIResult, err error) error {
	source, _ := url.Parse("dynatrace-sli-service")
	if err != nil {
		errMessage := err.Error()
		if (indicatorValues == nil) || (len(indicatorValues) == 0) {
			if eventData.GetSLI.Indicators == nil || len(eventData.GetSLI.Indicators) == 0 {
				eventData.GetSLI.Indicators = []string{"no metric"}
			}
			for _, indicatorName := range eventData.GetSLI.Indicators {
				indicatorValues = []*keptnv2.SLIResult{
					{
						Metric: indicatorName,
						Value:  0.0,
					},
				}
			}
		}
		for _, indicator := range indicatorValues {
			indicator.Success = false
			indicator.Message = errMessage
		}
	}
	getSLIEvent := keptnv2.GetSLIFinishedEventData{
		EventData: keptnv2.EventData{
			Project: eventData.Project,
			Stage:   eventData.Stage,
			Service: eventData.Service,
			Labels:  eventData.Labels,
			Status:  keptnv2.StatusSucceeded,
			Result:  keptnv2.ResultPass,
		},
		GetSLI: keptnv2.GetSLIFinished{
			IndicatorValues: indicatorValues,
			Start:           eventData.GetSLI.Start,
			End:             eventData.GetSLI.End,
		},
	}
	keptnContext, err := inputEvent.Context.GetExtension("shkeptncontext")
	if err != nil {
		return fmt.Errorf("could not determine keptnContext of input event: %s", err.Error())
	}
	event := cloudevents.NewEvent()
	event.SetType(keptnv2.GetFinishedEventType(keptnv2.GetSLITaskName))
	event.SetSource(source.String())
	event.SetDataContentType(cloudevents.ApplicationJSON)
	event.SetExtension("shkeptncontext", keptnContext)
	event.SetExtension("triggeredid", inputEvent.ID())
	event.SetData(cloudevents.ApplicationJSON, getSLIEvent)
	return sendEvent(event)
}
Sends the SLI Done Event. If err != nil it will send an error message
package main
import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"os"
	"strings"
	"time"
	keptncommon "github.com/keptn/go-utils/pkg/lib"
	log "github.com/sirupsen/logrus"
	"github.com/keptn-contrib/dynatrace-sli-service/pkg/common"
	"github.com/keptn-contrib/dynatrace-sli-service/pkg/lib/dynatrace"
	cloudevents "github.com/cloudevents/sdk-go/v2"
	"github.com/kelseyhightower/envconfig"
	"gopkg.in/yaml.v2"
	"github.com/keptn/go-utils/pkg/lib/keptn"
	keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0"
)
const ProblemOpenSLI = "problem_open"
type envConfig struct {
	Port int    `envconfig:"RCV_PORT" default:"8080"`
	Path string `envconfig:"RCV_PATH" default:"/"`
}
func main() {
	var env envConfig
	if err := envconfig.Process("", &env); err != nil {
		log.WithError(err).Fatal("Failed to process env var")
	}
	if common.RunLocal || common.RunLocalTest {
		log.Println("env=runlocal: Running with local filesystem to fetch resources")
	}
	os.Exit(_main(os.Args[1:], env))
}
func _main(args []string, env envConfig) int {
	ctx := context.Background()
	ctx = cloudevents.WithEncodingStructured(ctx)
	p, err := cloudevents.NewHTTP(cloudevents.WithPath(env.Path), cloudevents.WithPort(env.Port))
	if err != nil {
		log.WithError(err).Fatal("Failed to create cloudevents HTTP")
	}
	c, err := cloudevents.NewClient(p)
	if err != nil {
		log.WithError(err).Fatal("Failed to create cloudevents client")
	}
	err = c.StartReceiver(ctx, gotEvent)
	log.WithError(err).Fatal("Cloudevents StartReceiver failed")
	return 0
}
func gotEvent(ctx context.Context, event cloudevents.Event) error {
	switch event.Type() {
	case keptnv2.GetTriggeredEventType(keptnv2.GetSLITaskName):
		eventData := &keptnv2.GetSLITriggeredEventData{}
		err := event.DataAs(eventData)
		if err != nil {
			return err
		}
		if eventData.GetSLI.SLIProvider != "dynatrace" {
			return nil
		}
		go retrieveMetrics(event, eventData)
		return nil
	default:
		return errors.New("received unknown event type")
	}
}
func ensureRightTimestamps(start string, end string) (time.Time, time.Time, error) {
	startUnix, err := common.ParseUnixTimestamp(start)
	if err != nil {
		return time.Now(), time.Now(), errors.New("Error parsing start date: " + err.Error())
	}
	endUnix, err := common.ParseUnixTimestamp(end)
	if err != nil {
		return startUnix, time.Now(), errors.New("Error parsing end date: " + err.Error())
	}
	now := time.Now()
	timeDiffInSeconds := now.Sub(endUnix).Seconds()
	if timeDiffInSeconds < -120 {  
		return startUnix, endUnix, fmt.Errorf("error validating time range: Supplied end-time %v is too far (>120seconds) in the future (now: %v - diff in sec: %v)\n", endUnix, now, timeDiffInSeconds)
	}
	timeframeInSeconds := endUnix.Sub(startUnix).Seconds()
	if timeframeInSeconds < 0 {
		return startUnix, endUnix, errors.New("error validating time range: start time needs to be before end time")
	}
	waitForSeconds := 120.0         
	if timeframeInSeconds >= 300 {  
		waitForSeconds = 0.0
	} else if timeframeInSeconds >= 120 {  
		waitForSeconds = 60.0
	}
	if time.Now().Sub(endUnix).Seconds() < waitForSeconds {
		log.Debug("As the end date is too close to Now() we are going to wait to make sure we have all the data for the requested timeframe(start-end)")
	}
	for time.Now().Sub(endUnix).Seconds() < waitForSeconds {
		log.WithField("sleepSeconds", int(waitForSeconds-time.Now().Sub(endUnix).Seconds())).Debug("Sleeping while waiting for Dynatrace Metrics API")
		time.Sleep(10 * time.Second)
	}
	return startUnix, endUnix, nil
}
func addSLO(keptnEvent *common.BaseKeptnEvent, newSLO *keptncommon.SLO) error {
	dashboardSLO := &keptncommon.ServiceLevelObjectives{
		Objectives: []*keptncommon.SLO{},
		TotalScore: &keptncommon.SLOScore{Pass: "90%", Warning: "75%"},
		Comparison: &keptncommon.SLOComparison{CompareWith: "single_result", IncludeResultWithScore: "pass", NumberOfComparisonResults: 1, AggregateFunction: "avg"},
	}
	sloContent, err := common.GetKeptnResource(keptnEvent, common.KeptnSLOFilename)
	if err == nil && sloContent != "" {
		err := json.Unmarshal([]byte(sloContent), dashboardSLO)
		if err != nil {
			return fmt.Errorf("Couldnt parse existing SLO.yaml: %v", err)
		}
	}
	for _, objective := range dashboardSLO.Objectives {
		if objective.SLI == newSLO.SLI {
			return nil
		}
	}
	dashboardSLO.Objectives = append(dashboardSLO.Objectives, newSLO)
	if dashboardSLO != nil {
		yamlAsByteArray, _ := yaml.Marshal(dashboardSLO)
		err := common.UploadKeptnResource(yamlAsByteArray, common.KeptnSLOFilename, keptnEvent)
		if err != nil {
			return fmt.Errorf("could not store %s : %v", common.KeptnSLOFilename, err)
		}
	}
	return nil
}
func getDataFromDynatraceDashboard(dynatraceHandler *dynatrace.Handler, keptnEvent *common.BaseKeptnEvent, startUnix time.Time, endUnix time.Time, dashboardConfig string) (string, []*keptnv2.SLIResult, error) {
	dashboardLinkAsLabel, dashboardJSON, dashboardSLI, dashboardSLO, sliResults, err := dynatraceHandler.QueryDynatraceDashboardForSLIs(keptnEvent, dashboardConfig, startUnix, endUnix)
	if err != nil {
		return dashboardLinkAsLabel, sliResults, fmt.Errorf("could not query Dynatrace dashboard for SLIs: %v", err)
	}
	if dashboardJSON != nil {
		jsonAsByteArray, _ := json.MarshalIndent(dashboardJSON, "", "  ")
		err := common.UploadKeptnResource(jsonAsByteArray, common.DynatraceDashboardFilename, keptnEvent)
		if err != nil {
			return dashboardLinkAsLabel, sliResults, fmt.Errorf("could not store %s : %v", common.DynatraceDashboardFilename, err)
		}
	}
	if dashboardSLI != nil {
		yamlAsByteArray, _ := yaml.Marshal(dashboardSLI)
		err := common.UploadKeptnResource(yamlAsByteArray, common.DynatraceSLIFilename, keptnEvent)
		if err != nil {
			return dashboardLinkAsLabel, sliResults, fmt.Errorf("could not store %s : %v", common.DynatraceSLIFilename, err)
		}
	}
	if dashboardSLO != nil {
		yamlAsByteArray, _ := yaml.Marshal(dashboardSLO)
		err := common.UploadKeptnResource(yamlAsByteArray, common.KeptnSLOFilename, keptnEvent)
		if err != nil {
			return dashboardLinkAsLabel, sliResults, fmt.Errorf("could not store %s : %v", common.KeptnSLOFilename, err)
		}
	}
	if sliResults != nil {
		if common.RunLocal || common.RunLocalTest {
			log.Info("(RunLocal Output) Write SLIResult to sliresult.json")
			jsonAsByteArray, _ := json.MarshalIndent(sliResults, "", "  ")
			common.UploadKeptnResource(jsonAsByteArray, "sliresult.json", keptnEvent)
		}
	}
	return dashboardLinkAsLabel, sliResults, nil
}
func getDynatraceProblemContext(eventData *keptnv2.GetSLITriggeredEventData) string {
	if eventData.Labels == nil || len(eventData.Labels) == 0 {
		return ""
	}
	for labelName, labelValue := range eventData.Labels {
		if strings.ToLower(labelName) == "problem url" {
			ix := strings.LastIndex(labelValue, ";pid=")
			if ix > 0 {
				return labelValue[ix+5:]
			}
		}
	}
	return ""
}
func retrieveMetrics(event cloudevents.Event, eventData *keptnv2.GetSLITriggeredEventData) error {
	var shkeptncontext string
	event.Context.ExtensionAs("shkeptncontext", &shkeptncontext)
	if err := sendGetSLIStartedEvent(event, eventData); err != nil {
		return sendGetSLIFinishedEvent(event, eventData, nil, err)
	}
	log.WithFields(
		log.Fields{
			"project": eventData.Project,
			"stage":   eventData.Stage,
			"service": eventData.Service,
		}).Info("Processing sh.keptn.internal.event.get-sli")
	keptnEvent := &common.BaseKeptnEvent{}
	keptnEvent.Project = eventData.Project
	keptnEvent.Stage = eventData.Stage
	keptnEvent.Service = eventData.Service
	keptnEvent.Labels = eventData.Labels
	keptnEvent.Deployment = eventData.Deployment
	keptnEvent.Context = shkeptncontext
	dynatraceConfigFile := common.GetDynatraceConfig(keptnEvent)
	if eventData.Labels == nil {
		eventData.Labels = make(map[string]string)
	}
	eventData.Labels["DtCreds"] = dynatraceConfigFile.DtCreds
	dtCredentials, err := getDynatraceCredentials(dynatraceConfigFile.DtCreds, eventData.Project)
	if err != nil {
		log.WithError(err).Error("Failed to fetch Dynatrace credentials")
		return sendGetSLIFinishedEvent(event, eventData, nil, err)
	}
	dynatraceHandler := dynatrace.NewDynatraceHandler(
		dtCredentials.Tenant,
		keptnEvent,
		map[string]string{
			"Authorization": "Api-Token " + dtCredentials.ApiToken,
			"User-Agent":    "keptn-contrib/dynatrace-sli-service:" + os.Getenv("version"),
		},
		eventData.GetSLI.CustomFilters, shkeptncontext, event.ID())
	startUnix, endUnix, err := ensureRightTimestamps(eventData.GetSLI.Start, eventData.GetSLI.End)
	if err != nil {
		log.WithError(err).Error("ensureRightTimestamps failed")
		return sendGetSLIFinishedEvent(event, eventData, nil, err)
	}
	var sliResults []*keptnv2.SLIResult
	dashboardLinkAsLabel, sliResults, err := getDataFromDynatraceDashboard(dynatraceHandler, keptnEvent, startUnix, endUnix, dynatraceConfigFile.Dashboard)
	if err != nil {
		log.WithError(err).Error("getDataFromDynatraceDashboard failed")
	}
	if dashboardLinkAsLabel != "" {
		if eventData.Labels == nil {
			eventData.Labels = make(map[string]string)
		}
		eventData.Labels["Dashboard Link"] = dashboardLinkAsLabel
	}
	if sliResults == nil {
		projectCustomQueries, _ := common.GetCustomQueries(keptnEvent)
		if projectCustomQueries != nil {
			dynatraceHandler.CustomQueries = projectCustomQueries
		}
		for _, indicator := range eventData.GetSLI.Indicators {
			if strings.Compare(indicator, ProblemOpenSLI) == 0 {
				log.WithField("indicator", indicator).Info("Skipping indicator as it is handled later")
			} else {
				log.WithField("indicator", indicator).Info("Fetching indicator")
				sliValue, err := dynatraceHandler.GetSLIValue(indicator, startUnix, endUnix)
				if err != nil {
					log.WithError(err).Error("GetSLIValue failed")
					sliResults = append(sliResults, &keptnv2.SLIResult{
						Metric:  indicator,
						Value:   0,
						Success: false,  
						Message: err.Error(),
					})
				} else {
					sliResults = append(sliResults, &keptnv2.SLIResult{
						Metric:  indicator,
						Value:   sliValue,
						Success: true,  
					})
				}
			}
		}
		if common.RunLocal || common.RunLocalTest {
			log.WithField("sliResults", sliResults).Print("(RunLocal Output) sliResults")
			return nil
		}
	}
	problemID := getDynatraceProblemContext(eventData)
	if problemID != "" {
		problemIndicator := ProblemOpenSLI
		openProblemValue := 0.0
		success := false
		message := ""
		dynatraceProblem, err := dynatraceHandler.ExecuteGetDynatraceProblemById(problemID)
		if err != nil {
			message = err.Error()
		}
		if dynatraceProblem != nil {
			success = true
			if dynatraceProblem.Status == "OPEN" {
				openProblemValue = 1.0
			}
		}
		sliResults = append(sliResults, &keptnv2.SLIResult{
			Metric:  problemIndicator,
			Value:   openProblemValue,
			Success: success,
			Message: message,
		})
		sloString := fmt.Sprintf("sli=%s;pass=<=0;key=true", problemIndicator)
		_, passSLOs, warningSLOs, weight, keySli := common.ParsePassAndWarningFromString(sloString, []string{}, []string{})
		sloDefinition := &keptncommon.SLO{
			SLI:     problemIndicator,
			Weight:  weight,
			KeySLI:  keySli,
			Pass:    passSLOs,
			Warning: warningSLOs,
		}
		addSLO(keptnEvent, sloDefinition)
	}
	err = nil
	if sliResults == nil {
		err = errors.New("Couldn't retrieve any SLI Results")
	}
	log.Info("Finished fetching metrics; Sending SLIDone event now ...")
	return sendGetSLIFinishedEvent(event, eventData, sliResults, err)
}
func getDynatraceCredentials(secretName string, project string) (*common.DTCredentials, error) {
	secretNames := []string{secretName, fmt.Sprintf("dynatrace-credentials-%s", project), "dynatrace-credentials", "dynatrace"}
	for _, secret := range secretNames {
		if secret == "" {
			continue
		}
		dtCredentials, err := common.GetDTCredentials(secret)
		if err == nil && dtCredentials != nil {
			log.WithFields(
				log.Fields{
					"secret": secret,
					"tenant": dtCredentials.Tenant,
				}).Info("Found secret with credentials")
			return dtCredentials, nil
		}
	}
	return nil, errors.New("Could not find any Dynatrace specific secrets with the following names: " + strings.Join(secretNames, ","))
}