From 23ba3d39968595a92aab2ad6c307745ef3ffd6ce Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 29 Mar 2023 19:56:04 -0400 Subject: [PATCH 01/51] slo: Support SLO Datasource Read Functionality --- GNUmakefile | 62 ---- Makefile | 37 +++ internal/provider/provider.go | 6 + internal/resources/slo/data_source_slo.go | 371 ++++++++++++++++++++++ internal/resources/slo/types.go | 83 +++++ slo_testing/.terraform.lock.hcl | 10 + slo_testing/SLO_README.md | 71 +++++ slo_testing/slo-ds-read.tf | 19 ++ 8 files changed, 597 insertions(+), 62 deletions(-) delete mode 100644 GNUmakefile create mode 100644 Makefile create mode 100644 internal/resources/slo/data_source_slo.go create mode 100644 internal/resources/slo/types.go create mode 100644 slo_testing/.terraform.lock.hcl create mode 100644 slo_testing/SLO_README.md create mode 100644 slo_testing/slo-ds-read.tf diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100644 index c45dc0103..000000000 --- a/GNUmakefile +++ /dev/null @@ -1,62 +0,0 @@ -GRAFANA_VERSION ?= 9.4.3 - -testacc: - TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m - -# Test OSS features -testacc-oss: - TF_ACC_OSS=true make testacc - -# Test Enterprise features -testacc-enterprise: - TF_ACC_ENTERPRISE=true make testacc - -# Test Cloud API features -testacc-cloud-api: - TF_ACC_CLOUD_API=true make testacc - -# Test Cloud instance features (ex: Machine Learning and Synthetic Monitoring) -testacc-cloud-instance: - TF_ACC_CLOUD_INSTANCE=true make testacc - -testacc-docker: - make -C testdata generate - docker-compose -f ./docker-compose.yml stop - GRAFANA_VERSION=$(GRAFANA_VERSION) \ - docker-compose \ - -f ./docker-compose.yml \ - run --rm -e TESTARGS="$(TESTARGS)" \ - grafana-provider \ - make testacc-oss - -testacc-docker-tls: - make -C testdata generate - docker-compose -f ./docker-compose.yml -f ./docker-compose.tls.yml stop - GRAFANA_VERSION=$(GRAFANA_VERSION) \ - docker-compose \ - -f ./docker-compose.yml \ - -f ./docker-compose.tls.yml \ - run --rm -e TESTARGS="$(TESTARGS)" \ - grafana-provider \ - make testacc-oss - -release: - @test $${RELEASE_VERSION?Please set environment variable RELEASE_VERSION} - @git tag $$RELEASE_VERSION - @git push origin $$RELEASE_VERSION - -DRONE_DOCKER := docker run --rm -e DRONE_SERVER -e DRONE_TOKEN -v ${PWD}:${PWD} -w "${PWD}" drone/cli:1.6.1 -drone: - $(DRONE_DOCKER) jsonnet --stream --source .drone/drone.jsonnet --target .drone/drone.yml --format - $(DRONE_DOCKER) lint .drone/drone.yml - $(DRONE_DOCKER) sign --save grafana/terraform-provider-grafana .drone/drone.yml - -golangci-lint: - docker run \ - --rm \ - --volume "$(shell pwd):/src" \ - --workdir "/src" \ - golangci/golangci-lint:v1.49 golangci-lint run ./... - -linkcheck: - docker run -it --entrypoint sh -v "$$PWD:$$PWD" -w "$$PWD" python:3.9-alpine -c "pip3 install linkchecker && linkchecker --config .linkcheckerrc docs" diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..5cb8be006 --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +TEST?=$$(go list ./... | grep -v 'vendor') +HOSTNAME=registry.terraform.io +NAMESPACE=grafana +NAME=grafana +BINARY=terraform-provider-${NAME} +VERSION=0.2 +OS_ARCH=darwin_arm64 + +default: install + +build: + go build -o ${BINARY} + +release: + GOOS=darwin GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_darwin_amd64 + GOOS=freebsd GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_freebsd_386 + GOOS=freebsd GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_freebsd_amd64 + GOOS=freebsd GOARCH=arm go build -o ./bin/${BINARY}_${VERSION}_freebsd_arm + GOOS=linux GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_linux_386 + GOOS=linux GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_linux_amd64 + GOOS=linux GOARCH=arm go build -o ./bin/${BINARY}_${VERSION}_linux_arm + GOOS=openbsd GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_openbsd_386 + GOOS=openbsd GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_openbsd_amd64 + GOOS=solaris GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_solaris_amd64 + GOOS=windows GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_windows_386 + GOOS=windows GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_windows_amd64 + +install: build + mkdir -p ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} + mv ${BINARY} ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} + +test: + go test -i $(TEST) || exit 1 + echo $(TEST) | xargs -t -n4 go test $(TESTARGS) -timeout=30s -parallel=4 + +testacc: + TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 120m \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index dd82bc50e..88cb6ac53 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -25,6 +25,7 @@ import ( "github.com/grafana/terraform-provider-grafana/internal/resources/grafana" "github.com/grafana/terraform-provider-grafana/internal/resources/machinelearning" "github.com/grafana/terraform-provider-grafana/internal/resources/oncall" + "github.com/grafana/terraform-provider-grafana/internal/resources/slo" "github.com/grafana/terraform-provider-grafana/internal/resources/syntheticmonitoring" ) @@ -147,6 +148,10 @@ func Provider(version string) func() *schema.Provider { }) ) + // Needs to be Refactored to match how Resources are Set Up + sloDatasources := make(map[string]*schema.Resource) + sloDatasources["grafana_slo"] = slo.DatasourceSlo() + return func() *schema.Provider { p := &schema.Provider{ Schema: map[string]*schema.Schema{ @@ -281,6 +286,7 @@ func Provider(version string) func() *schema.Provider { smClientDatasources, onCallClientDatasources, cloudClientDatasources, + sloDatasources, ), } diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go new file mode 100644 index 000000000..9481b21de --- /dev/null +++ b/internal/resources/slo/data_source_slo.go @@ -0,0 +1,371 @@ +package slo + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DatasourceSlo() *schema.Resource { + return &schema.Resource{ + ReadContext: datasourceSloRead, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "slos": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "uuid": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "labels": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "service": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "query": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "objectives": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "objective_value": &schema.Schema{ + Type: schema.TypeFloat, + Optional: true, + }, + "objective_window": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "dashboard_ref": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "alerting": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "labels": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "annotations": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "fastburn": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "labels": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "annotations": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "slowburn": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "labels": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "annotations": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + serverPort := 3000 + requestURL := fmt.Sprintf("http://localhost:%d/api/plugins/grafana-slo-app/resources/v1/slo", serverPort) + req, err := http.NewRequest(http.MethodGet, requestURL, nil) + if err != nil { + log.Fatalln(err) + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Fatalln(err) + } + + defer resp.Body.Close() + + b, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatalln(err) + } + + var sloList SloList + + err = json.Unmarshal(b, &sloList) + if err != nil { + fmt.Println("error:", err) + } + + terrformSlos := []interface{}{} + for _, slo := range sloList.Slos { + terraformSlo := convertDatasourceSlo(slo) + terrformSlos = append(terrformSlos, terraformSlo) + } + + d.Set("slos", terrformSlos) + d.SetId(sloList.Slos[0].Uuid) + + var diags diag.Diagnostics + return diags +} + +func convertDatasourceSlo(slo Slo) map[string]interface{} { + ret := make(map[string]interface{}) + + ret["uuid"] = slo.Uuid + ret["name"] = slo.Name + ret["description"] = slo.Description + ret["service"] = slo.Service + ret["query"] = unpackQuery(slo.Query) + + retLabels := unpackLabels(slo.Labels) + ret["labels"] = retLabels + + retDashboard := unpackDashboard(slo) + ret["dashboard_ref"] = retDashboard + + retObjectives := unpackObjectives(slo.Objectives) + ret["objectives"] = retObjectives + + retAlerting := unpackAlerting(slo.Alerting) + ret["alerting"] = retAlerting + + return ret + +} + +// TBD for Other Query Types Once Implemented +func unpackQuery(query Query) string { + if query.FreeformQuery.Query != "" { + return query.FreeformQuery.Query + } + + return "Query Type Not Implemented" + +} + +func unpackObjectives(objectives []Objective) []map[string]interface{} { + retObjectives := []map[string]interface{}{} + + for _, objective := range objectives { + retObjective := make(map[string]interface{}) + retObjective["objective_value"] = objective.Value + retObjective["objective_window"] = objective.Window + retObjectives = append(retObjectives, retObjective) + } + + return retObjectives +} + +func unpackLabels(labels *[]Label) []map[string]interface{} { + retLabels := []map[string]interface{}{} + + if labels != nil { + for _, label := range *labels { + retLabel := make(map[string]interface{}) + retLabel["key"] = label.Key + retLabel["value"] = label.Value + retLabels = append(retLabels, retLabel) + } + return retLabels + } + + return nil +} + +func unpackDashboard(slo Slo) map[string]interface{} { + retDashboard := make(map[string]interface{}) + + if slo.DrilldownDashboardRef != nil { + retDashboard["dashboard_id"] = strconv.Itoa(slo.DrilldownDashboardRef.ID) + retDashboard["dashboard_uid"] = slo.DrilldownDashboardRef.UID + } + + if slo.DrilldownDashboardUid != "" { + retDashboard["dashboard_uid"] = slo.DrilldownDashboardUid + } + + return retDashboard +} + +func unpackAlerting(AlertData *Alerting) []map[string]interface{} { + retAlertData := []map[string]interface{}{} + + alertObject := make(map[string]interface{}) + alertObject["name"] = AlertData.Name + alertObject["labels"] = unpackLabels(AlertData.Labels) + alertObject["annotations"] = unpackLabels(AlertData.Annotations) + alertObject["fastburn"] = unpackAlertingMetadata(*AlertData.FastBurn) + alertObject["slowburn"] = unpackAlertingMetadata(*AlertData.SlowBurn) + + retAlertData = append(retAlertData, alertObject) + + return retAlertData + +} + +func unpackAlertingMetadata(Metadata AlertMetadata) []map[string]interface{} { + retAlertMetaData := []map[string]interface{}{} + labelsAnnotsStruct := make(map[string]interface{}) + + if Metadata.Annotations != nil { + retAnnotations := unpackLabels(Metadata.Labels) + labelsAnnotsStruct["annotations"] = retAnnotations + } + + if Metadata.Labels != nil { + retLabels := unpackLabels(Metadata.Annotations) + labelsAnnotsStruct["labels"] = retLabels + } + + retAlertMetaData = append(retAlertMetaData, labelsAnnotsStruct) + + return retAlertMetaData +} diff --git a/internal/resources/slo/types.go b/internal/resources/slo/types.go new file mode 100644 index 000000000..9ef38496a --- /dev/null +++ b/internal/resources/slo/types.go @@ -0,0 +1,83 @@ +package slo + +type SloList struct { + Slos []Slo `json:"slos"` +} + +type Slo struct { + Uuid string `json:"uuid"` + Name string `json:"name"` + Description string `json:"description"` + Service string `json:"service,omitempty"` + Query Query `json:"query"` + Alerting *Alerting `json:"alerting,omitempty"` + Labels *[]Label `json:"labels,omitempty"` + Objectives []Objective `json:"objectives"` + DrilldownDashboardUid string `json:"dashboardUid,omitempty"` + DrilldownDashboardRef *DashboardRef `json:"drillDownDashboardRef,omitempty"` +} + +type Alerting struct { + Name string `json:"name"` + Annotations *[]Label `json:"annotations,omitempty"` + Labels *[]Label `json:"labels,omitempty"` + FastBurn *AlertMetadata `json:"fastBurn,omitempty"` + SlowBurn *AlertMetadata `json:"slowBurn,omitempty"` +} + +type AlertMetadata struct { + Annotations *[]Label `json:"annotations,omitempty"` + Labels *[]Label `json:"labels,omitempty"` +} + +type Label struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type Objective struct { + Value float64 `json:"value"` + Window string `json:"window"` +} + +type DashboardRef struct { + ID int `json:"id,omitempty"` + UID string `json:"uid,omitempty"` +} + +type FreeformQuery struct { + Query string `json:"freeformQuery,omitempty"` +} + +type ThresholdQuery struct { + ThresholdMetric *MetricDef `json:"thresholdMetric,omitempty"` +} + +type RatioQuery struct { + SuccessMetric *MetricDef `json:"successMetric,omitempty"` + TotalMetric *MetricDef `json:"totalMetric,omitempty"` +} + +type PercentileQuery struct { + HistogramMetric *MetricDef `json:"histogramMetric,omitempty"` + Percentile float64 `json:"percentile,omitempty"` +} + +type Threshold struct { + Value float64 `json:"value,omitempty"` + Operator string `json:"operator,omitempty"` +} + +type MetricDef struct { + PrometheusMetric string `json:"prometheusMetric,omitempty"` + Type string `json:"type,omitempty"` +} + +type Query struct { + ThresholdQuery + RatioQuery + PercentileQuery + FreeformQuery + Threshold *Threshold `json:"threshold,omitempty"` + GroupByLabels []string `json:"groupBy,omitempty"` +} diff --git a/slo_testing/.terraform.lock.hcl b/slo_testing/.terraform.lock.hcl new file mode 100644 index 000000000..8c2e19ff3 --- /dev/null +++ b/slo_testing/.terraform.lock.hcl @@ -0,0 +1,10 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/grafana/grafana" { + version = "0.2.0" + constraints = "0.2.0" + hashes = [ + "h1:ZT7DZIVP45KbmMtE7aYHzmVSs0vmz++3+vV4A5j9A7s=", + ] +} diff --git a/slo_testing/SLO_README.md b/slo_testing/SLO_README.md new file mode 100644 index 000000000..add388352 --- /dev/null +++ b/slo_testing/SLO_README.md @@ -0,0 +1,71 @@ +-- Local SLO Dev Environment -- +1. Start up the Local SLO Dev Environment +2. Send a POST Request with a sample request body +POST Request: http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo +{ + "name":"test name", + "description":"test description", + "service":"service", + "labels": [{"key": "name", "value": "testslo"}], + "objectives":[ + { + "value":0.995, + "window":"30d" + } + ], + "query":{ + "freeFormQuery":"sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + }, + "alerting":{ + "fastBurn":{ + "annotations":[ + { + "key":"annots-key", + "value":"annots-Fast Burn" + }, + { + "key":"Description", + "value":"Fast Burn Description" + } + ], + "labels":[ + { + "key":"Type", + "value":"SLO" + } + ] + }, + "slowBurn":{ + "annotations":[ + { + "key":"Name", + "value":"Slow Burn" + }, + { + "key":"Description", + "value":"Slow Burn Description" + } + ], + "labels":[ + { + "key":"Type", + "value":"SLO" + } + ] + } + } +} + +-- Terraform Provider -- +1. Within the root directory of the terraform-provider-grafana, run `make install`. This creates a Grafana Terraform Provider +2. Switch to the slo_testing directory `cd slo_testing` +3. Run the command `terraform init` +4. Run the command `terraform apply`. This will execute the `slo-ds-read.tf` state file, which currently just sends a GET request to the http://localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo endpoint, and returns it in Terraform. +5. Ensure to delete the `.terraform.lock.hcl` file that exists before rebuilding the terraform provider. + +-- TBD -- +1. Currently - I am just doing this in local dev, I need to set this up and test it with a HG Account +2. Client Wrapper - I am currently just sending requests by creating an HTTP Client, ideally we should create a Go Client wrapper around our API. This will be done and refactored at a later point in time. +3. CREATE, UPDATE, and DELETE methods TBD. +4. Tests TBD. +5. Authentication - currently it appears that our API endpoint isn't protected at all - a user doesn't need to send a Grafana Token or anything in order to access our API Endpoint. Did we want to change this? \ No newline at end of file diff --git a/slo_testing/slo-ds-read.tf b/slo_testing/slo-ds-read.tf new file mode 100644 index 000000000..8b09a8f6d --- /dev/null +++ b/slo_testing/slo-ds-read.tf @@ -0,0 +1,19 @@ +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} + +provider "grafana" { + auth = "auth" +} + +data "grafana_slo" "test" { +} + +output "test" { + value = data.grafana_slo.test +} \ No newline at end of file From b9e8d636be5af19c131dbdb66fe85ffb71b2b2a5 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Fri, 31 Mar 2023 15:56:40 -0400 Subject: [PATCH 02/51] slo: Support SLO Resource Create Functionality --- internal/provider/provider.go | 6 +- internal/resources/slo/data_source_slo.go | 4 +- internal/resources/slo/resource_slo.go | 383 ++++++++++++++++++++++ slo_testing/.terraform.lock.hcl | 2 +- slo_testing/SLO_README.md | 18 +- slo_testing/slo-create.tf | 134 ++++++++ slo_testing/slo-ds-read.tf | 32 +- 7 files changed, 558 insertions(+), 21 deletions(-) create mode 100644 internal/resources/slo/resource_slo.go create mode 100644 slo_testing/slo-create.tf diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 88cb6ac53..72f778a27 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -150,7 +150,10 @@ func Provider(version string) func() *schema.Provider { // Needs to be Refactored to match how Resources are Set Up sloDatasources := make(map[string]*schema.Resource) - sloDatasources["grafana_slo"] = slo.DatasourceSlo() + sloDatasources["grafana_slo_datasource"] = slo.DatasourceSlo() + + sloResources := make(map[string]*schema.Resource) + sloResources["grafana_slo_resource"] = slo.ResourceSlo() return func() *schema.Provider { p := &schema.Provider{ @@ -279,6 +282,7 @@ func Provider(version string) func() *schema.Provider { smClientResources, onCallClientResources, cloudClientResources, + sloResources, ), DataSourcesMap: mergeResourceMaps( diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 9481b21de..a90909743 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -356,12 +356,12 @@ func unpackAlertingMetadata(Metadata AlertMetadata) []map[string]interface{} { labelsAnnotsStruct := make(map[string]interface{}) if Metadata.Annotations != nil { - retAnnotations := unpackLabels(Metadata.Labels) + retAnnotations := unpackLabels(Metadata.Annotations) labelsAnnotsStruct["annotations"] = retAnnotations } if Metadata.Labels != nil { - retLabels := unpackLabels(Metadata.Annotations) + retLabels := unpackLabels(Metadata.Labels) labelsAnnotsStruct["labels"] = retLabels } diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go new file mode 100644 index 000000000..823c8085a --- /dev/null +++ b/internal/resources/slo/resource_slo.go @@ -0,0 +1,383 @@ +package slo + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func ResourceSlo() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceSloCreate, + ReadContext: resourceSloRead, + UpdateContext: resourceSloUpdate, + DeleteContext: resourceSloDelete, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "service": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "query": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "labels": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "objectives": &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "objective_value": &schema.Schema{ + Type: schema.TypeFloat, + Optional: true, + }, + "objective_window": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "dashboard_ref": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "alerting": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "labels": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "annotations": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "fastburn": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "labels": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "annotations": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "slowburn": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "labels": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "annotations": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + + sloPost := packSloResource(d) + + body, err := json.Marshal(sloPost) + if err != nil { + log.Fatalln(err) + } + bodyReader := bytes.NewReader(body) + + serverPort := 3000 + requestURL := fmt.Sprintf("http://localhost:%d/api/plugins/grafana-slo-app/resources/v1/slo", serverPort) + req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader) + if err != nil { + log.Fatalln(err) + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Fatalln(err) + } + + b, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatalln(err) + } + + var response POSTResponse + + err = json.Unmarshal(b, &response) + if err != nil { + fmt.Println("error:", err) + } + + d.SetId(response.Uuid) + + return diags +} + +type POSTResponse struct { + Message string `json:"message,omitempty"` + Uuid string `json:"uuid,omitempty"` +} + +func packSloResource(d *schema.ResourceData) Slo { + tfname := d.Get("name").(string) + tfdescription := d.Get("description").(string) + tfservice := d.Get("service").(string) + query := d.Get("query").(string) + tfquery := packQuery(query) + + // Assumes that each SLO only have one Objective Value and one Objective Window + objectives := d.Get("objectives").([]interface{}) + objective := objectives[0].(map[string]interface{}) + tfobjective := packObjective(objective) + + labels := d.Get("labels").([]interface{}) + tflabels := packLabels(labels) + + alerting := d.Get("alerting").([]interface{}) + alert := alerting[0].(map[string]interface{}) + tfalerting := packAlerting(alert) + + sloPost := Slo{ + Name: tfname, + Description: tfdescription, + Service: tfservice, + Objectives: tfobjective, + Query: tfquery, + Alerting: &tfalerting, + Labels: &tflabels, + } + + return sloPost + +} + +func packQuery(query string) Query { + sloQuery := Query{ + FreeformQuery: FreeformQuery{ + Query: query, + }, + } + + return sloQuery +} + +func packObjective(tfobjective map[string]interface{}) []Objective { + objective := Objective{ + Value: tfobjective["objective_value"].(float64), + Window: tfobjective["objective_window"].(string), + } + + objectiveSlice := []Objective{} + objectiveSlice = append(objectiveSlice, objective) + + return objectiveSlice +} + +func packLabels(tfLabels []interface{}) []Label { + labelSlice := []Label{} + + for ind := range tfLabels { + currLabel := tfLabels[ind].(map[string]interface{}) + curr := Label{ + Key: currLabel["key"].(string), + Value: currLabel["value"].(string), + } + + labelSlice = append(labelSlice, curr) + + } + + return labelSlice +} + +func packAlerting(tfAlerting map[string]interface{}) Alerting { + annots := tfAlerting["annotations"].([]interface{}) + tfAnnots := packLabels(annots) + + labels := tfAlerting["labels"].([]interface{}) + tfLabels := packLabels(labels) + + fastBurn := tfAlerting["fastburn"].([]interface{}) + tfFastBurn := packAlertMetadata(fastBurn) + + slowBurn := tfAlerting["slowburn"].([]interface{}) + tfSlowBurn := packAlertMetadata(slowBurn) + + alerting := Alerting{ + Name: tfAlerting["name"].(string), + Annotations: &tfAnnots, + Labels: &tfLabels, + FastBurn: &tfFastBurn, + SlowBurn: &tfSlowBurn, + } + + return alerting +} + +func packAlertMetadata(metadata []interface{}) AlertMetadata { + meta := metadata[0].(map[string]interface{}) + + labels := meta["labels"].([]interface{}) + tflabels := packLabels(labels) + + annots := meta["annotations"].([]interface{}) + tfannots := packLabels(annots) + + apiMetadata := AlertMetadata{ + Labels: &tflabels, + Annotations: &tfannots, + } + + return apiMetadata +} + +func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + + return diags +} + +func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + return resourceSloRead(ctx, d, m) +} + +func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + + return diags +} diff --git a/slo_testing/.terraform.lock.hcl b/slo_testing/.terraform.lock.hcl index 8c2e19ff3..fc2b985d0 100644 --- a/slo_testing/.terraform.lock.hcl +++ b/slo_testing/.terraform.lock.hcl @@ -5,6 +5,6 @@ provider "registry.terraform.io/grafana/grafana" { version = "0.2.0" constraints = "0.2.0" hashes = [ - "h1:ZT7DZIVP45KbmMtE7aYHzmVSs0vmz++3+vV4A5j9A7s=", + "h1:FQ1BP8cn9dE7kX5yYEWiThuKtuQC320Y/WPPVdEf6CQ=", ] } diff --git a/slo_testing/SLO_README.md b/slo_testing/SLO_README.md index add388352..c89abad55 100644 --- a/slo_testing/SLO_README.md +++ b/slo_testing/SLO_README.md @@ -68,4 +68,20 @@ POST Request: http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/reso 2. Client Wrapper - I am currently just sending requests by creating an HTTP Client, ideally we should create a Go Client wrapper around our API. This will be done and refactored at a later point in time. 3. CREATE, UPDATE, and DELETE methods TBD. 4. Tests TBD. -5. Authentication - currently it appears that our API endpoint isn't protected at all - a user doesn't need to send a Grafana Token or anything in order to access our API Endpoint. Did we want to change this? \ No newline at end of file + +-- Questions -- +1. Why is our `Objectives` within the Slo struct a slice of Objectives? Shouldn't each Slo only have one Objective? +type Slo struct { + Uuid string `json:"uuid"` + Name string `json:"name"` + Description string `json:"description"` + Service string `json:"service,omitempty"` + Query Query `json:"query"` + Alerting *Alerting `json:"alerting,omitempty"` + Labels *[]Label `json:"labels,omitempty"` + Objectives []Objective `json:"objectives"` + DrilldownDashboardUid string `json:"dashboardUid,omitempty"` + DrilldownDashboardRef *DashboardRef `json:"drillDownDashboardRef,omitempty"` +} + +2. I cannot seem to add custom labels to the "Alerting" structure for some reason. Might be something wrong with the API. diff --git a/slo_testing/slo-create.tf b/slo_testing/slo-create.tf new file mode 100644 index 000000000..c8470254c --- /dev/null +++ b/slo_testing/slo-create.tf @@ -0,0 +1,134 @@ +# 1. When testing the Create Method, start up the Local Dev Environment. Comment out the `slo-ds-read.tf` file. +# 2. Within the root directory of the terraform-provider-grafana, run `make install`. This creates a Grafana Terraform Provider. +# 3. Switch to the slo_testing directory `cd slo_testing` +# 4. Run the command `terraform init` +# 5. Run the command `terraform apply slo-create.tf`. This sends the information below as a POST request to the API at http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo +# 6. Ensure to delete the `.terraform.lock.hcl` and any hidden terraform (terraform.tfstate) state files that exists before rebuilding the terraform provider. +# 7. To determine that the new creation was successful, uncomment out lines 76-81, and run `terraform apply` again. +# 8. Within your terminal, you should see the output of the newly created SLO from within Terraform (should match the output from executing a GET to http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo) + +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} + +provider "grafana" { + auth = "auth" +} + +resource "grafana_slo_resource" "test1" { + name = "Hello1" + description = "Testing Hello 1 - I hope this works!" + service = "service1" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.85 + objective_window = "28d" + } + labels { + key = "label1a" + value = "value1a" + } + labels { + key = "label2a" + value = "value2a" + } + alerting { + name = "hihialerting1" + labels { + key = "alertinglabel1" + value = "alertingvalue1" + } + + annotations { + key = "alertingannot1" + value = "alertingvalue1" + } + + fastburn { + labels { + key = "labelsfastburnkey1" + value = "labelsfastburnvalue1" + } + annotations { + key = "annotsfastburnannot1" + value = "annotsfastburnvalue1" + } + } + + slowburn { + labels { + key = "labelsslowburnkey1" + value = "labelsslowburnvalue1" + } + annotations { + key = "annotsslowburnannot1" + value = "annotsslowburnvalue1" + } + } + } +} + +resource "grafana_slo_resource" "test2" { + name = "Hello2" + description = "Testing Hello 2 - I hope this works!" + service = "service2" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.85 + objective_window = "28d" + } + labels { + key = "label2a" + value = "value2a" + } + labels { + key = "label3a" + value = "value3a" + } + alerting { + name = "hihialerting2" + labels { + key = "alertinglabel2" + value = "alertingvalue2" + } + + annotations { + key = "alertingannot2" + value = "alertingvalue2" + } + + fastburn { + labels { + key = "labelsfastburnkey2" + value = "labelsfastburnvalue2" + } + annotations { + key = "annotsfastburnannot2" + value = "annotsfastburnvalue2" + } + } + + slowburn { + labels { + key = "labelsslowburnkey2" + value = "labelsslowburnvalue2" + } + annotations { + key = "annotsslowburnannot2" + value = "annotsslowburnvalue2" + } + } + } +} + +data "grafana_slo_datasource" "test" { +} + +output "test1" { + value = data.grafana_slo_datasource.test +} \ No newline at end of file diff --git a/slo_testing/slo-ds-read.tf b/slo_testing/slo-ds-read.tf index 8b09a8f6d..4b20f1d46 100644 --- a/slo_testing/slo-ds-read.tf +++ b/slo_testing/slo-ds-read.tf @@ -1,19 +1,19 @@ -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} +# terraform { +# required_providers { +# grafana = { +# version = "0.2" +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } -provider "grafana" { - auth = "auth" -} +# provider "grafana" { +# auth = "auth" +# } -data "grafana_slo" "test" { -} +# data "grafana_slo_datasource" "test1" { +# } -output "test" { - value = data.grafana_slo.test -} \ No newline at end of file +# output "test1" { +# value = data.grafana_slo_datasource.test1 +# } \ No newline at end of file From 64b90cef59b8921d129591b1f074c1d594444436 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Mon, 3 Apr 2023 14:20:52 -0400 Subject: [PATCH 03/51] slo: Adds a resourceSloRead method to the SLO Resource Create Functionality --- internal/resources/slo/data_source_slo.go | 6 +- internal/resources/slo/resource_slo.go | 38 +++- slo_testing/.terraform.lock.hcl | 2 +- slo_testing/SLO_README.md | 2 +- slo_testing/slo-create.tf | 244 +++++++++++----------- slo_testing/slo-ds-read.tf | 32 +-- 6 files changed, 179 insertions(+), 145 deletions(-) diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index a90909743..5e1b35c36 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -243,13 +243,13 @@ func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{ fmt.Println("error:", err) } - terrformSlos := []interface{}{} + terraformSlos := []interface{}{} for _, slo := range sloList.Slos { terraformSlo := convertDatasourceSlo(slo) - terrformSlos = append(terrformSlos, terraformSlo) + terraformSlos = append(terraformSlos, terraformSlo) } - d.Set("slos", terrformSlos) + d.Set("slos", terraformSlos) d.SetId(sloList.Slos[0].Uuid) var diags diag.Diagnostics diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 823c8085a..5584884ca 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -242,6 +242,7 @@ func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{ } d.SetId(response.Uuid) + resourceSloRead(ctx, d, m) return diags } @@ -365,9 +366,42 @@ func packAlertMetadata(metadata []interface{}) AlertMetadata { } func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - // Warning or errors can be collected in a slice type - var diags diag.Diagnostics + sloID := d.Id() + + serverPort := 3000 + requestURL := fmt.Sprintf("http://localhost:%d/api/plugins/grafana-slo-app/resources/v1/slo/%s", serverPort, sloID) + req, err := http.NewRequest(http.MethodGet, requestURL, nil) + if err != nil { + log.Fatalln(err) + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Fatalln(err) + } + + defer resp.Body.Close() + b, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatalln(err) + } + + var slo Slo + + err = json.Unmarshal(b, &slo) + if err != nil { + fmt.Println("error:", err) + } + + // When you return this, you only need to set the Id and the Dashboard - the information you get back from the API + retDashboard := unpackDashboard(slo) + + d.Set("dashboard_ref", retDashboard) + d.Set("name", slo.Name) + + var diags diag.Diagnostics return diags } diff --git a/slo_testing/.terraform.lock.hcl b/slo_testing/.terraform.lock.hcl index fc2b985d0..28f321a74 100644 --- a/slo_testing/.terraform.lock.hcl +++ b/slo_testing/.terraform.lock.hcl @@ -5,6 +5,6 @@ provider "registry.terraform.io/grafana/grafana" { version = "0.2.0" constraints = "0.2.0" hashes = [ - "h1:FQ1BP8cn9dE7kX5yYEWiThuKtuQC320Y/WPPVdEf6CQ=", + "h1:SDtxxSWeXZz67yBJYMGslQK5/HiPEKcRLkiCKVog7o0=", ] } diff --git a/slo_testing/SLO_README.md b/slo_testing/SLO_README.md index c89abad55..6dce93123 100644 --- a/slo_testing/SLO_README.md +++ b/slo_testing/SLO_README.md @@ -66,7 +66,7 @@ POST Request: http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/reso -- TBD -- 1. Currently - I am just doing this in local dev, I need to set this up and test it with a HG Account 2. Client Wrapper - I am currently just sending requests by creating an HTTP Client, ideally we should create a Go Client wrapper around our API. This will be done and refactored at a later point in time. -3. CREATE, UPDATE, and DELETE methods TBD. +3. UPDATE, and DELETE methods TBD. 4. Tests TBD. -- Questions -- diff --git a/slo_testing/slo-create.tf b/slo_testing/slo-create.tf index c8470254c..86e40f3c2 100644 --- a/slo_testing/slo-create.tf +++ b/slo_testing/slo-create.tf @@ -1,134 +1,134 @@ -# 1. When testing the Create Method, start up the Local Dev Environment. Comment out the `slo-ds-read.tf` file. -# 2. Within the root directory of the terraform-provider-grafana, run `make install`. This creates a Grafana Terraform Provider. -# 3. Switch to the slo_testing directory `cd slo_testing` -# 4. Run the command `terraform init` -# 5. Run the command `terraform apply slo-create.tf`. This sends the information below as a POST request to the API at http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo -# 6. Ensure to delete the `.terraform.lock.hcl` and any hidden terraform (terraform.tfstate) state files that exists before rebuilding the terraform provider. -# 7. To determine that the new creation was successful, uncomment out lines 76-81, and run `terraform apply` again. -# 8. Within your terminal, you should see the output of the newly created SLO from within Terraform (should match the output from executing a GET to http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo) +# # 1. When testing the Create Method, start up the Local Dev Environment. Comment out the `slo-ds-read.tf` file. +# # 2. Within the root directory of the terraform-provider-grafana, run `make install`. This creates a Grafana Terraform Provider. +# # 3. Switch to the slo_testing directory `cd slo_testing` +# # 4. Run the command `terraform init` +# # 5. Run the command `terraform apply slo-create.tf`. This sends the information below as a POST request to the API at http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo +# # 6. Ensure to delete the `.terraform.lock.hcl` and any hidden terraform (terraform.tfstate) state files that exists before rebuilding the terraform provider. +# # 7. Within your terminal, you should see the output of the two newly created SLO from within Terraform -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} +# terraform { +# required_providers { +# grafana = { +# version = "0.2" +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } -provider "grafana" { - auth = "auth" -} +# provider "grafana" { +# auth = "auth" +# } -resource "grafana_slo_resource" "test1" { - name = "Hello1" - description = "Testing Hello 1 - I hope this works!" - service = "service1" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.85 - objective_window = "28d" - } - labels { - key = "label1a" - value = "value1a" - } - labels { - key = "label2a" - value = "value2a" - } - alerting { - name = "hihialerting1" - labels { - key = "alertinglabel1" - value = "alertingvalue1" - } +# resource "grafana_slo_resource" "test1" { +# name = "Hello1" +# description = "Testing Hello 1 - I hope this works!" +# service = "service1" +# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" +# objectives { +# objective_value = 0.85 +# objective_window = "28d" +# } +# labels { +# key = "label1a" +# value = "value1a" +# } +# labels { +# key = "label2a" +# value = "value2a" +# } +# alerting { +# name = "hihialerting1" +# labels { +# key = "alertinglabel1" +# value = "alertingvalue1" +# } - annotations { - key = "alertingannot1" - value = "alertingvalue1" - } +# annotations { +# key = "alertingannot1" +# value = "alertingvalue1" +# } - fastburn { - labels { - key = "labelsfastburnkey1" - value = "labelsfastburnvalue1" - } - annotations { - key = "annotsfastburnannot1" - value = "annotsfastburnvalue1" - } - } +# fastburn { +# labels { +# key = "labelsfastburnkey1" +# value = "labelsfastburnvalue1" +# } +# annotations { +# key = "annotsfastburnannot1" +# value = "annotsfastburnvalue1" +# } +# } - slowburn { - labels { - key = "labelsslowburnkey1" - value = "labelsslowburnvalue1" - } - annotations { - key = "annotsslowburnannot1" - value = "annotsslowburnvalue1" - } - } - } -} +# slowburn { +# labels { +# key = "labelsslowburnkey1" +# value = "labelsslowburnvalue1" +# } +# annotations { +# key = "annotsslowburnannot1" +# value = "annotsslowburnvalue1" +# } +# } +# } +# } -resource "grafana_slo_resource" "test2" { - name = "Hello2" - description = "Testing Hello 2 - I hope this works!" - service = "service2" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.85 - objective_window = "28d" - } - labels { - key = "label2a" - value = "value2a" - } - labels { - key = "label3a" - value = "value3a" - } - alerting { - name = "hihialerting2" - labels { - key = "alertinglabel2" - value = "alertingvalue2" - } +# output "test1_order" { +# value = grafana_slo_resource.test1 +# } - annotations { - key = "alertingannot2" - value = "alertingvalue2" - } +# resource "grafana_slo_resource" "test2" { +# name = "Hello2" +# description = "Testing Hello 2 - I hope this works!" +# service = "service2" +# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" +# objectives { +# objective_value = 0.85 +# objective_window = "28d" +# } +# labels { +# key = "label2a" +# value = "value2a" +# } +# labels { +# key = "label3a" +# value = "value3a" +# } +# alerting { +# name = "hihialerting2" +# labels { +# key = "alertinglabel2" +# value = "alertingvalue2" +# } - fastburn { - labels { - key = "labelsfastburnkey2" - value = "labelsfastburnvalue2" - } - annotations { - key = "annotsfastburnannot2" - value = "annotsfastburnvalue2" - } - } +# annotations { +# key = "alertingannot2" +# value = "alertingvalue2" +# } - slowburn { - labels { - key = "labelsslowburnkey2" - value = "labelsslowburnvalue2" - } - annotations { - key = "annotsslowburnannot2" - value = "annotsslowburnvalue2" - } - } - } -} +# fastburn { +# labels { +# key = "labelsfastburnkey2" +# value = "labelsfastburnvalue2" +# } +# annotations { +# key = "annotsfastburnannot2" +# value = "annotsfastburnvalue2" +# } +# } -data "grafana_slo_datasource" "test" { -} +# slowburn { +# labels { +# key = "labelsslowburnkey2" +# value = "labelsslowburnvalue2" +# } +# annotations { +# key = "annotsslowburnannot2" +# value = "annotsslowburnvalue2" +# } +# } +# } +# } -output "test1" { - value = data.grafana_slo_datasource.test -} \ No newline at end of file +# output "test2_order" { +# value = grafana_slo_resource.test2 +# } \ No newline at end of file diff --git a/slo_testing/slo-ds-read.tf b/slo_testing/slo-ds-read.tf index 4b20f1d46..ed01aaef7 100644 --- a/slo_testing/slo-ds-read.tf +++ b/slo_testing/slo-ds-read.tf @@ -1,19 +1,19 @@ -# terraform { -# required_providers { -# grafana = { -# version = "0.2" -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} -# provider "grafana" { -# auth = "auth" -# } +provider "grafana" { + auth = "auth" +} -# data "grafana_slo_datasource" "test1" { -# } +data "grafana_slo_datasource" "test1" { +} -# output "test1" { -# value = data.grafana_slo_datasource.test1 -# } \ No newline at end of file +output "test1" { + value = data.grafana_slo_datasource.test1 +} \ No newline at end of file From 3f5a8fbbefea0202feecf65e1d9a8c64c8def0cb Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Mon, 3 Apr 2023 14:21:25 -0400 Subject: [PATCH 04/51] Modify README --- slo_testing/slo-create.tf | 230 ++++++++++++++++++------------------- slo_testing/slo-ds-read.tf | 32 +++--- 2 files changed, 131 insertions(+), 131 deletions(-) diff --git a/slo_testing/slo-create.tf b/slo_testing/slo-create.tf index 86e40f3c2..623ffba8b 100644 --- a/slo_testing/slo-create.tf +++ b/slo_testing/slo-create.tf @@ -6,129 +6,129 @@ # # 6. Ensure to delete the `.terraform.lock.hcl` and any hidden terraform (terraform.tfstate) state files that exists before rebuilding the terraform provider. # # 7. Within your terminal, you should see the output of the two newly created SLO from within Terraform -# terraform { -# required_providers { -# grafana = { -# version = "0.2" -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} -# provider "grafana" { -# auth = "auth" -# } +provider "grafana" { + auth = "auth" +} -# resource "grafana_slo_resource" "test1" { -# name = "Hello1" -# description = "Testing Hello 1 - I hope this works!" -# service = "service1" -# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" -# objectives { -# objective_value = 0.85 -# objective_window = "28d" -# } -# labels { -# key = "label1a" -# value = "value1a" -# } -# labels { -# key = "label2a" -# value = "value2a" -# } -# alerting { -# name = "hihialerting1" -# labels { -# key = "alertinglabel1" -# value = "alertingvalue1" -# } +resource "grafana_slo_resource" "test1" { + name = "Hello1" + description = "Testing Hello 1 - I hope this works!" + service = "service1" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.85 + objective_window = "28d" + } + labels { + key = "label1a" + value = "value1a" + } + labels { + key = "label2a" + value = "value2a" + } + alerting { + name = "hihialerting1" + labels { + key = "alertinglabel1" + value = "alertingvalue1" + } -# annotations { -# key = "alertingannot1" -# value = "alertingvalue1" -# } + annotations { + key = "alertingannot1" + value = "alertingvalue1" + } -# fastburn { -# labels { -# key = "labelsfastburnkey1" -# value = "labelsfastburnvalue1" -# } -# annotations { -# key = "annotsfastburnannot1" -# value = "annotsfastburnvalue1" -# } -# } + fastburn { + labels { + key = "labelsfastburnkey1" + value = "labelsfastburnvalue1" + } + annotations { + key = "annotsfastburnannot1" + value = "annotsfastburnvalue1" + } + } -# slowburn { -# labels { -# key = "labelsslowburnkey1" -# value = "labelsslowburnvalue1" -# } -# annotations { -# key = "annotsslowburnannot1" -# value = "annotsslowburnvalue1" -# } -# } -# } -# } + slowburn { + labels { + key = "labelsslowburnkey1" + value = "labelsslowburnvalue1" + } + annotations { + key = "annotsslowburnannot1" + value = "annotsslowburnvalue1" + } + } + } +} -# output "test1_order" { -# value = grafana_slo_resource.test1 -# } +output "test1_order" { + value = grafana_slo_resource.test1 +} -# resource "grafana_slo_resource" "test2" { -# name = "Hello2" -# description = "Testing Hello 2 - I hope this works!" -# service = "service2" -# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" -# objectives { -# objective_value = 0.85 -# objective_window = "28d" -# } -# labels { -# key = "label2a" -# value = "value2a" -# } -# labels { -# key = "label3a" -# value = "value3a" -# } -# alerting { -# name = "hihialerting2" -# labels { -# key = "alertinglabel2" -# value = "alertingvalue2" -# } +resource "grafana_slo_resource" "test2" { + name = "Hello2" + description = "Testing Hello 2 - I hope this works!" + service = "service2" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.85 + objective_window = "28d" + } + labels { + key = "label2a" + value = "value2a" + } + labels { + key = "label3a" + value = "value3a" + } + alerting { + name = "hihialerting2" + labels { + key = "alertinglabel2" + value = "alertingvalue2" + } -# annotations { -# key = "alertingannot2" -# value = "alertingvalue2" -# } + annotations { + key = "alertingannot2" + value = "alertingvalue2" + } -# fastburn { -# labels { -# key = "labelsfastburnkey2" -# value = "labelsfastburnvalue2" -# } -# annotations { -# key = "annotsfastburnannot2" -# value = "annotsfastburnvalue2" -# } -# } + fastburn { + labels { + key = "labelsfastburnkey2" + value = "labelsfastburnvalue2" + } + annotations { + key = "annotsfastburnannot2" + value = "annotsfastburnvalue2" + } + } -# slowburn { -# labels { -# key = "labelsslowburnkey2" -# value = "labelsslowburnvalue2" -# } -# annotations { -# key = "annotsslowburnannot2" -# value = "annotsslowburnvalue2" -# } -# } -# } -# } + slowburn { + labels { + key = "labelsslowburnkey2" + value = "labelsslowburnvalue2" + } + annotations { + key = "annotsslowburnannot2" + value = "annotsslowburnvalue2" + } + } + } +} -# output "test2_order" { -# value = grafana_slo_resource.test2 -# } \ No newline at end of file +output "test2_order" { + value = grafana_slo_resource.test2 +} \ No newline at end of file diff --git a/slo_testing/slo-ds-read.tf b/slo_testing/slo-ds-read.tf index ed01aaef7..4b20f1d46 100644 --- a/slo_testing/slo-ds-read.tf +++ b/slo_testing/slo-ds-read.tf @@ -1,19 +1,19 @@ -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} +# terraform { +# required_providers { +# grafana = { +# version = "0.2" +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } -provider "grafana" { - auth = "auth" -} +# provider "grafana" { +# auth = "auth" +# } -data "grafana_slo_datasource" "test1" { -} +# data "grafana_slo_datasource" "test1" { +# } -output "test1" { - value = data.grafana_slo_datasource.test1 -} \ No newline at end of file +# output "test1" { +# value = data.grafana_slo_datasource.test1 +# } \ No newline at end of file From e2ceedc10aa16a92bebfe3cff27da16330af32e3 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Mon, 3 Apr 2023 14:47:39 -0400 Subject: [PATCH 05/51] slo: Support SLO Resource Delete Functionality --- internal/resources/slo/resource_slo.go | 28 +++++++++++++++++++++----- slo_testing/.terraform.lock.hcl | 2 +- slo_testing/slo-create.tf | 22 +++++++++++++------- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 5584884ca..e3b40703e 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -405,13 +405,31 @@ func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) return diags } -func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return resourceSloRead(ctx, d, m) -} - func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - // Warning or errors can be collected in a slice type + sloID := d.Id() + var diags diag.Diagnostics + serverPort := 3000 + requestURL := fmt.Sprintf("http://localhost:%d/api/plugins/grafana-slo-app/resources/v1/slo/%s", serverPort, sloID) + req, err := http.NewRequest(http.MethodDelete, requestURL, nil) + if err != nil { + log.Fatalln(err) + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return diag.FromErr(err) + } + + defer resp.Body.Close() + + d.SetId("") + return diags } + +func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + return resourceSloRead(ctx, d, m) +} diff --git a/slo_testing/.terraform.lock.hcl b/slo_testing/.terraform.lock.hcl index 28f321a74..a68c0c581 100644 --- a/slo_testing/.terraform.lock.hcl +++ b/slo_testing/.terraform.lock.hcl @@ -5,6 +5,6 @@ provider "registry.terraform.io/grafana/grafana" { version = "0.2.0" constraints = "0.2.0" hashes = [ - "h1:SDtxxSWeXZz67yBJYMGslQK5/HiPEKcRLkiCKVog7o0=", + "h1:XefUUank5BUQu+55DbrRzbwt3afoK7Q6D/kCdS62Azc=", ] } diff --git a/slo_testing/slo-create.tf b/slo_testing/slo-create.tf index 623ffba8b..df072c9b6 100644 --- a/slo_testing/slo-create.tf +++ b/slo_testing/slo-create.tf @@ -1,10 +1,18 @@ -# # 1. When testing the Create Method, start up the Local Dev Environment. Comment out the `slo-ds-read.tf` file. -# # 2. Within the root directory of the terraform-provider-grafana, run `make install`. This creates a Grafana Terraform Provider. -# # 3. Switch to the slo_testing directory `cd slo_testing` -# # 4. Run the command `terraform init` -# # 5. Run the command `terraform apply slo-create.tf`. This sends the information below as a POST request to the API at http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo -# # 6. Ensure to delete the `.terraform.lock.hcl` and any hidden terraform (terraform.tfstate) state files that exists before rebuilding the terraform provider. -# # 7. Within your terminal, you should see the output of the two newly created SLO from within Terraform +# Testing the CREATE Method +# 1. When testing the Create Method, start up the Local Dev Environment. Comment out the `slo-ds-read.tf` file. +# 2. Within the root directory of the terraform-provider-grafana, run `make install`. This creates a Grafana Terraform Provider. +# 3. Switch to the slo_testing directory `cd slo_testing` +# 4. Run the command `terraform init` +# 5. Run the command `terraform apply slo-create.tf`. This sends the information below as a POST request to the API at http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo +# 6. Ensure to delete the `.terraform.lock.hcl` and any hidden terraform (terraform.tfstate) state files that exists before rebuilding the terraform provider. +# 7. Within your terminal, you should see the output of the two newly created SLO from within Terraform + +# Testing the DELETE Method / terraform destroy +# 1. Repeat the steps for the CREATE Method +# 2. Create a regular SLO using the UI. At this point, you should have 3 SLOs - 2 created from Terraform, and 1 created from the UI +# 3. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files +# 4. To delete all Terraformed SLO resources, execute the command `terraform destroy`, and type `yes` in the terminal to confirm the delete +# 5. The two newly created Terraformed SLO Resources should be automatically deleted. terraform { required_providers { From 1af04a022965a565d61877e24b7b4cffc62604fa Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Tue, 4 Apr 2023 13:55:39 -0400 Subject: [PATCH 06/51] slo: Support SLO Resource Update Functionality --- internal/resources/slo/resource_slo.go | 34 ++++++ slo_testing/.terraform.lock.hcl | 10 -- slo_testing/SLO_README.md | 3 +- slo_testing/slo-create.tf | 142 ------------------------- slo_testing/slo-rs-create-delete.tf | 142 +++++++++++++++++++++++++ slo_testing/slo-rs-update.tf | 78 ++++++++++++++ 6 files changed, 255 insertions(+), 154 deletions(-) delete mode 100644 slo_testing/.terraform.lock.hcl delete mode 100644 slo_testing/slo-create.tf create mode 100644 slo_testing/slo-rs-create-delete.tf create mode 100644 slo_testing/slo-rs-update.tf diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index e3b40703e..992125c3f 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -8,6 +8,7 @@ import ( "io" "log" "net/http" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -200,6 +201,11 @@ func ResourceSlo() *schema.Resource { }, }, }, + "last_updated": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, }, } } @@ -272,6 +278,7 @@ func packSloResource(d *schema.ResourceData) Slo { tfalerting := packAlerting(alert) sloPost := Slo{ + Uuid: d.Id(), Name: tfname, Description: tfdescription, Service: tfservice, @@ -431,5 +438,32 @@ func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{ } func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sloID := d.Id() + + if d.HasChange("name") || d.HasChange("description") || d.HasChange("service") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { + sloPut := packSloResource(d) + + body, err := json.Marshal(sloPut) + if err != nil { + log.Fatalln(err) + } + bodyReader := bytes.NewReader(body) + + serverPort := 3000 + requestURL := fmt.Sprintf("http://localhost:%d/api/plugins/grafana-slo-app/resources/v1/slo/%s", serverPort, sloID) + req, err := http.NewRequest(http.MethodPut, requestURL, bodyReader) + if err != nil { + log.Fatalln(err) + } + + client := &http.Client{} + _, err = client.Do(req) + if err != nil { + log.Fatalln(err) + } + + d.Set("last_updated", time.Now().Format(time.RFC850)) + } + return resourceSloRead(ctx, d, m) } diff --git a/slo_testing/.terraform.lock.hcl b/slo_testing/.terraform.lock.hcl deleted file mode 100644 index a68c0c581..000000000 --- a/slo_testing/.terraform.lock.hcl +++ /dev/null @@ -1,10 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/grafana/grafana" { - version = "0.2.0" - constraints = "0.2.0" - hashes = [ - "h1:XefUUank5BUQu+55DbrRzbwt3afoK7Q6D/kCdS62Azc=", - ] -} diff --git a/slo_testing/SLO_README.md b/slo_testing/SLO_README.md index 6dce93123..34336f687 100644 --- a/slo_testing/SLO_README.md +++ b/slo_testing/SLO_README.md @@ -66,8 +66,7 @@ POST Request: http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/reso -- TBD -- 1. Currently - I am just doing this in local dev, I need to set this up and test it with a HG Account 2. Client Wrapper - I am currently just sending requests by creating an HTTP Client, ideally we should create a Go Client wrapper around our API. This will be done and refactored at a later point in time. -3. UPDATE, and DELETE methods TBD. -4. Tests TBD. +3. Tests TBD. -- Questions -- 1. Why is our `Objectives` within the Slo struct a slice of Objectives? Shouldn't each Slo only have one Objective? diff --git a/slo_testing/slo-create.tf b/slo_testing/slo-create.tf deleted file mode 100644 index df072c9b6..000000000 --- a/slo_testing/slo-create.tf +++ /dev/null @@ -1,142 +0,0 @@ -# Testing the CREATE Method -# 1. When testing the Create Method, start up the Local Dev Environment. Comment out the `slo-ds-read.tf` file. -# 2. Within the root directory of the terraform-provider-grafana, run `make install`. This creates a Grafana Terraform Provider. -# 3. Switch to the slo_testing directory `cd slo_testing` -# 4. Run the command `terraform init` -# 5. Run the command `terraform apply slo-create.tf`. This sends the information below as a POST request to the API at http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo -# 6. Ensure to delete the `.terraform.lock.hcl` and any hidden terraform (terraform.tfstate) state files that exists before rebuilding the terraform provider. -# 7. Within your terminal, you should see the output of the two newly created SLO from within Terraform - -# Testing the DELETE Method / terraform destroy -# 1. Repeat the steps for the CREATE Method -# 2. Create a regular SLO using the UI. At this point, you should have 3 SLOs - 2 created from Terraform, and 1 created from the UI -# 3. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files -# 4. To delete all Terraformed SLO resources, execute the command `terraform destroy`, and type `yes` in the terminal to confirm the delete -# 5. The two newly created Terraformed SLO Resources should be automatically deleted. - -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} - -provider "grafana" { - auth = "auth" -} - -resource "grafana_slo_resource" "test1" { - name = "Hello1" - description = "Testing Hello 1 - I hope this works!" - service = "service1" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.85 - objective_window = "28d" - } - labels { - key = "label1a" - value = "value1a" - } - labels { - key = "label2a" - value = "value2a" - } - alerting { - name = "hihialerting1" - labels { - key = "alertinglabel1" - value = "alertingvalue1" - } - - annotations { - key = "alertingannot1" - value = "alertingvalue1" - } - - fastburn { - labels { - key = "labelsfastburnkey1" - value = "labelsfastburnvalue1" - } - annotations { - key = "annotsfastburnannot1" - value = "annotsfastburnvalue1" - } - } - - slowburn { - labels { - key = "labelsslowburnkey1" - value = "labelsslowburnvalue1" - } - annotations { - key = "annotsslowburnannot1" - value = "annotsslowburnvalue1" - } - } - } -} - -output "test1_order" { - value = grafana_slo_resource.test1 -} - -resource "grafana_slo_resource" "test2" { - name = "Hello2" - description = "Testing Hello 2 - I hope this works!" - service = "service2" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.85 - objective_window = "28d" - } - labels { - key = "label2a" - value = "value2a" - } - labels { - key = "label3a" - value = "value3a" - } - alerting { - name = "hihialerting2" - labels { - key = "alertinglabel2" - value = "alertingvalue2" - } - - annotations { - key = "alertingannot2" - value = "alertingvalue2" - } - - fastburn { - labels { - key = "labelsfastburnkey2" - value = "labelsfastburnvalue2" - } - annotations { - key = "annotsfastburnannot2" - value = "annotsfastburnvalue2" - } - } - - slowburn { - labels { - key = "labelsslowburnkey2" - value = "labelsslowburnvalue2" - } - annotations { - key = "annotsslowburnannot2" - value = "annotsslowburnvalue2" - } - } - } -} - -output "test2_order" { - value = grafana_slo_resource.test2 -} \ No newline at end of file diff --git a/slo_testing/slo-rs-create-delete.tf b/slo_testing/slo-rs-create-delete.tf new file mode 100644 index 000000000..5a34565da --- /dev/null +++ b/slo_testing/slo-rs-create-delete.tf @@ -0,0 +1,142 @@ +# Testing the CREATE Method +# 1. When testing the Create Method, start up the Local Dev Environment. Comment out the `slo-ds-read.tf` file. +# 2. Within the root directory of the terraform-provider-grafana, run `make install`. This creates a Grafana Terraform Provider. +# 3. Switch to the slo_testing directory `cd slo_testing` +# 4. Run the command `terraform init` +# 5. Run the command `terraform apply slo-create.tf`. This sends the information below as a POST request to the API at http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo +# 6. Ensure to delete the `.terraform.lock.hcl` and any hidden terraform (terraform.tfstate) state files that exists before rebuilding the terraform provider. +# 7. Within your terminal, you should see the output of the two newly created SLO from within Terraform + +# Testing the DELETE Method / terraform destroy +# 1. Repeat the steps for the CREATE Method +# 2. Create a regular SLO using the UI. At this point, you should have 3 SLOs - 2 created from Terraform, and 1 created from the UI +# 3. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files +# 4. To delete all Terraformed SLO resources, execute the command `terraform destroy`, and type `yes` in the terminal to confirm the delete +# 5. The two newly created Terraformed SLO Resources should be automatically deleted. + +# terraform { +# required_providers { +# grafana = { +# version = "0.2" +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } + +# provider "grafana" { +# auth = "auth" +# } + +# resource "grafana_slo_resource" "test1" { +# name = "Hello1" +# description = "Testing Hello 1 - I hope this works!" +# service = "service1" +# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" +# objectives { +# objective_value = 0.85 +# objective_window = "28d" +# } +# labels { +# key = "label1a" +# value = "value1a" +# } +# labels { +# key = "label2a" +# value = "value2a" +# } +# alerting { +# name = "hihialerting1" +# labels { +# key = "alertinglabel1" +# value = "alertingvalue1" +# } + +# annotations { +# key = "alertingannot1" +# value = "alertingvalue1" +# } + +# fastburn { +# labels { +# key = "labelsfastburnkey1" +# value = "labelsfastburnvalue1" +# } +# annotations { +# key = "annotsfastburnannot1" +# value = "annotsfastburnvalue1" +# } +# } + +# slowburn { +# labels { +# key = "labelsslowburnkey1" +# value = "labelsslowburnvalue1" +# } +# annotations { +# key = "annotsslowburnannot1" +# value = "annotsslowburnvalue1" +# } +# } +# } +# } + +# output "test1_order" { +# value = grafana_slo_resource.test1 +# } + +# resource "grafana_slo_resource" "test2" { +# name = "Hello2" +# description = "Testing Hello 2 - I hope this works!" +# service = "service2" +# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" +# objectives { +# objective_value = 0.85 +# objective_window = "28d" +# } +# labels { +# key = "label2a" +# value = "value2a" +# } +# labels { +# key = "label3a" +# value = "value3a" +# } +# alerting { +# name = "hihialerting2" +# labels { +# key = "alertinglabel2" +# value = "alertingvalue2" +# } + +# annotations { +# key = "alertingannot2" +# value = "alertingvalue2" +# } + +# fastburn { +# labels { +# key = "labelsfastburnkey2" +# value = "labelsfastburnvalue2" +# } +# annotations { +# key = "annotsfastburnannot2" +# value = "annotsfastburnvalue2" +# } +# } + +# slowburn { +# labels { +# key = "labelsslowburnkey2" +# value = "labelsslowburnvalue2" +# } +# annotations { +# key = "annotsslowburnannot2" +# value = "annotsslowburnvalue2" +# } +# } +# } +# } + +# output "test2_order" { +# value = grafana_slo_resource.test2 +# } \ No newline at end of file diff --git a/slo_testing/slo-rs-update.tf b/slo_testing/slo-rs-update.tf new file mode 100644 index 000000000..76198c7aa --- /dev/null +++ b/slo_testing/slo-rs-update.tf @@ -0,0 +1,78 @@ +# Testing the UPDATE Method +# 1. When testing the UPDATE Method, start up the Local Dev Environment. Comment out all the other `.tf` files. +# 2. Within the root directory of the terraform-provider-grafana, run `make install`. This creates a Grafana Terraform Provider. +# 3. Switch to the slo_testing directory `cd slo_testing` +# 4. Run the command `terraform init` +# 5. Run the command `terraform apply`. This creates the resource specified below. +# 6. To ensure that the PUT endpoint works, modify any of the values within the resource below, and re-run `terraform apply`. +# 7. Using Postman, send a GET Request to the endpoint to ensure that the resource was appropriately modified within the API. + +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} + +provider "grafana" { + auth = "auth" +} + +resource "grafana_slo_resource" "test1" { + name = "Hello1" + description = "Testing Hello 1 - I hope this works!" + service = "service1" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.85 + objective_window = "28d" + } + labels { + key = "label1a" + value = "value1a" + } + labels { + key = "label2a" + value = "value2a" + } + alerting { + name = "hihialerting1" + labels { + key = "alertinglabel1" + value = "alertingvalue1" + } + + annotations { + key = "alertingannot1" + value = "alertingvalue1" + } + + fastburn { + labels { + key = "labelsfastburnkey1" + value = "labelsfastburnvalue1" + } + annotations { + key = "annotsfastburnannot1" + value = "annotsfastburnvalue1" + } + } + + slowburn { + labels { + key = "labelsslowburnkey1" + value = "labelsslowburnvalue1" + } + annotations { + key = "annotsslowburnannot1" + value = "annotsslowburnvalue1" + } + } + } +} + +output "test1_order" { + value = grafana_slo_resource.test1 +} \ No newline at end of file From 0453983fb41457eb4001c1841ce7fc3c51d46068 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Tue, 4 Apr 2023 15:20:42 -0400 Subject: [PATCH 07/51] slo: Support SLO Resource Import Functionality --- internal/resources/slo/resource_slo.go | 42 ++++++++- slo_testing/.terraform.lock.hcl | 10 ++ slo_testing/SLO_README.md | 30 +++--- slo_testing/slo-rs-import.tf | 22 +++++ slo_testing/slo-rs-update.tf | 126 ++++++++++++------------- 5 files changed, 143 insertions(+), 87 deletions(-) create mode 100644 slo_testing/.terraform.lock.hcl create mode 100644 slo_testing/slo-rs-import.tf diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 992125c3f..ca07d9859 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -207,6 +207,9 @@ func ResourceSlo() *schema.Resource { Computed: true, }, }, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, } } @@ -331,6 +334,19 @@ func packLabels(tfLabels []interface{}) []Label { return labelSlice } +// func unpackLabels(labels []Label) []map[string]interface{} { +// tfLabels := []map[string]interface{}{} + +// for _, label := range labels { +// tfLabel := map[string]interface{}{} +// tfLabel["key"] = label.Key +// tfLabel["value"] = label.Value +// tfLabels = append(tfLabels, tfLabel) +// } + +// return tfLabels +// } + func packAlerting(tfAlerting map[string]interface{}) Alerting { annots := tfAlerting["annotations"].([]interface{}) tfAnnots := packLabels(annots) @@ -372,6 +388,7 @@ func packAlertMetadata(metadata []interface{}) AlertMetadata { return apiMetadata } +// resourceSloRead - sends a GET Request to the single SLO Endpoint func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { sloID := d.Id() @@ -402,16 +419,31 @@ func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) fmt.Println("error:", err) } - // When you return this, you only need to set the Id and the Dashboard - the information you get back from the API - retDashboard := unpackDashboard(slo) - - d.Set("dashboard_ref", retDashboard) - d.Set("name", slo.Name) + setTerraformState(d, slo) var diags diag.Diagnostics return diags } +func setTerraformState(d *schema.ResourceData, slo Slo) { + d.Set("name", slo.Name) + d.Set("description", slo.Description) + d.Set("service", slo.Service) + d.Set("query", unpackQuery(slo.Query)) + retLabels := unpackLabels(slo.Labels) + + d.Set("labels", retLabels) + + retDashboard := unpackDashboard(slo) + d.Set("dashboard_ref", retDashboard) + + retObjectives := unpackObjectives(slo.Objectives) + d.Set("objectives", retObjectives) + + retAlerting := unpackAlerting(slo.Alerting) + d.Set("alerting", retAlerting) +} + func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { sloID := d.Id() diff --git a/slo_testing/.terraform.lock.hcl b/slo_testing/.terraform.lock.hcl new file mode 100644 index 000000000..e9c5943fe --- /dev/null +++ b/slo_testing/.terraform.lock.hcl @@ -0,0 +1,10 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/grafana/grafana" { + version = "0.2.0" + constraints = "0.2.0" + hashes = [ + "h1:P1tT3IEncHzhjCzPfKF/pc/lZQRrOYvKYqDH9NVidUw=", + ] +} diff --git a/slo_testing/SLO_README.md b/slo_testing/SLO_README.md index 34336f687..9a244301c 100644 --- a/slo_testing/SLO_README.md +++ b/slo_testing/SLO_README.md @@ -3,10 +3,10 @@ 2. Send a POST Request with a sample request body POST Request: http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo { - "name":"test name", - "description":"test description", + "name":"test slo name", + "description":"test slo description", "service":"service", - "labels": [{"key": "name", "value": "testslo"}], + "labels": [{"key": "custom", "value": "value"}], "objectives":[ { "value":0.995, @@ -20,36 +20,28 @@ POST Request: http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/reso "fastBurn":{ "annotations":[ { - "key":"annots-key", - "value":"annots-Fast Burn" - }, - { - "key":"Description", - "value":"Fast Burn Description" + "key":"annotsfastburnkey", + "value":"annotsfastburnvalue" } ], "labels":[ { - "key":"Type", - "value":"SLO" + "key":"type", + "value":"slo" } ] }, "slowBurn":{ "annotations":[ { - "key":"Name", - "value":"Slow Burn" - }, - { - "key":"Description", - "value":"Slow Burn Description" + "key":"annotsslowburnkey", + "value":"annotsslowburnvalue" } ], "labels":[ { - "key":"Type", - "value":"SLO" + "key":"type", + "value":"slo" } ] } diff --git a/slo_testing/slo-rs-import.tf b/slo_testing/slo-rs-import.tf new file mode 100644 index 000000000..503d81aeb --- /dev/null +++ b/slo_testing/slo-rs-import.tf @@ -0,0 +1,22 @@ +# terraform import grafana_slo_resource.sample 7jglyd0d965pbmbikv62l +# terraform state show grafana_slo_resource.sample + +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} + +provider "grafana" { + auth = "auth" +} + +resource "grafana_slo_resource" "sample" {} + +output "sample_slo" { + value = grafana_slo_resource.sample +} + diff --git a/slo_testing/slo-rs-update.tf b/slo_testing/slo-rs-update.tf index 76198c7aa..3a7bf042d 100644 --- a/slo_testing/slo-rs-update.tf +++ b/slo_testing/slo-rs-update.tf @@ -7,72 +7,72 @@ # 6. To ensure that the PUT endpoint works, modify any of the values within the resource below, and re-run `terraform apply`. # 7. Using Postman, send a GET Request to the endpoint to ensure that the resource was appropriately modified within the API. -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} +# terraform { +# required_providers { +# grafana = { +# version = "0.2" +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } -provider "grafana" { - auth = "auth" -} +# provider "grafana" { +# auth = "auth" +# } -resource "grafana_slo_resource" "test1" { - name = "Hello1" - description = "Testing Hello 1 - I hope this works!" - service = "service1" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.85 - objective_window = "28d" - } - labels { - key = "label1a" - value = "value1a" - } - labels { - key = "label2a" - value = "value2a" - } - alerting { - name = "hihialerting1" - labels { - key = "alertinglabel1" - value = "alertingvalue1" - } +# resource "grafana_slo_resource" "test1" { +# name = "Hello1" +# description = "Testing Hello 1 - I hope this works!" +# service = "service1" +# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" +# objectives { +# objective_value = 0.85 +# objective_window = "28d" +# } +# labels { +# key = "label1a" +# value = "value1a" +# } +# labels { +# key = "label2a" +# value = "value2a" +# } +# alerting { +# name = "hihialerting1" +# labels { +# key = "alertinglabel1" +# value = "alertingvalue1" +# } - annotations { - key = "alertingannot1" - value = "alertingvalue1" - } +# annotations { +# key = "alertingannot1" +# value = "alertingvalue1" +# } - fastburn { - labels { - key = "labelsfastburnkey1" - value = "labelsfastburnvalue1" - } - annotations { - key = "annotsfastburnannot1" - value = "annotsfastburnvalue1" - } - } +# fastburn { +# labels { +# key = "labelsfastburnkey1" +# value = "labelsfastburnvalue1" +# } +# annotations { +# key = "annotsfastburnannot1" +# value = "annotsfastburnvalue1" +# } +# } - slowburn { - labels { - key = "labelsslowburnkey1" - value = "labelsslowburnvalue1" - } - annotations { - key = "annotsslowburnannot1" - value = "annotsslowburnvalue1" - } - } - } -} +# slowburn { +# labels { +# key = "labelsslowburnkey1" +# value = "labelsslowburnvalue1" +# } +# annotations { +# key = "annotsslowburnannot1" +# value = "annotsslowburnvalue1" +# } +# } +# } +# } -output "test1_order" { - value = grafana_slo_resource.test1 -} \ No newline at end of file +# output "test1_order" { +# value = grafana_slo_resource.test1 +# } \ No newline at end of file From f4d7f0151ab903ee303d548cc2e0f2dcbb2a8aed Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Tue, 11 Apr 2023 21:27:37 -0400 Subject: [PATCH 08/51] slo: Update Testing READMEs, implement SLO Datasource HG Functionality --- internal/provider/provider.go | 15 ++-- internal/resources/slo/data_source_slo.go | 28 ++++-- internal/resources/slo/resource_slo.go | 33 +++---- internal/resources/slo/types.go | 6 ++ slo_testing/.terraform.lock.hcl | 10 --- slo_testing/SLO_README.md | 78 ---------------- slo_testing/hg/hg_testing_README.md | 52 +++++++++++ slo_testing/hg/slo-datasource-read-hg.tf | 19 ++++ slo_testing/local/local_testing_README.md | 89 +++++++++++++++++++ slo_testing/local/slo-datasource-read.tf | 20 +++++ .../slo-resource-create.tf} | 29 ++---- slo_testing/local/slo-resource-import.tf | 19 ++++ .../slo-resource-update.tf} | 12 +-- slo_testing/local/slo_sample.json | 26 ++++++ slo_testing/slo-ds-read.tf | 19 ---- slo_testing/slo-rs-import.tf | 22 ----- 16 files changed, 275 insertions(+), 202 deletions(-) delete mode 100644 slo_testing/.terraform.lock.hcl delete mode 100644 slo_testing/SLO_README.md create mode 100644 slo_testing/hg/hg_testing_README.md create mode 100644 slo_testing/hg/slo-datasource-read-hg.tf create mode 100644 slo_testing/local/local_testing_README.md create mode 100644 slo_testing/local/slo-datasource-read.tf rename slo_testing/{slo-rs-create-delete.tf => local/slo-resource-create.tf} (63%) create mode 100644 slo_testing/local/slo-resource-import.tf rename slo_testing/{slo-rs-update.tf => local/slo-resource-update.tf} (67%) create mode 100644 slo_testing/local/slo_sample.json delete mode 100644 slo_testing/slo-ds-read.tf delete mode 100644 slo_testing/slo-rs-import.tf diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 72f778a27..b17bec359 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -78,6 +78,9 @@ func Provider(version string) func() *schema.Provider { "grafana_machine_learning_job": machinelearning.ResourceJob(), "grafana_machine_learning_holiday": machinelearning.ResourceHoliday(), "grafana_machine_learning_outlier_detector": machinelearning.ResourceOutlierDetector(), + + // SLO + "grafana_slo_resource": slo.ResourceSlo(), }) // Resources that require the Synthetic Monitoring client to exist. @@ -120,6 +123,9 @@ func Provider(version string) func() *schema.Provider { "grafana_team": grafana.DatasourceTeam(), "grafana_organization": grafana.DatasourceOrganization(), "grafana_organization_preferences": grafana.DatasourceOrganizationPreferences(), + + // SLO + "grafana_slo_datasource": slo.DatasourceSlo(), }) // Datasources that require the Synthetic Monitoring client to exist. @@ -148,13 +154,6 @@ func Provider(version string) func() *schema.Provider { }) ) - // Needs to be Refactored to match how Resources are Set Up - sloDatasources := make(map[string]*schema.Resource) - sloDatasources["grafana_slo_datasource"] = slo.DatasourceSlo() - - sloResources := make(map[string]*schema.Resource) - sloResources["grafana_slo_resource"] = slo.ResourceSlo() - return func() *schema.Provider { p := &schema.Provider{ Schema: map[string]*schema.Schema{ @@ -282,7 +281,6 @@ func Provider(version string) func() *schema.Provider { smClientResources, onCallClientResources, cloudClientResources, - sloResources, ), DataSourcesMap: mergeResourceMaps( @@ -290,7 +288,6 @@ func Provider(version string) func() *schema.Provider { smClientDatasources, onCallClientDatasources, cloudClientDatasources, - sloDatasources, ), } diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 5e1b35c36..4e3b1d800 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -9,6 +9,7 @@ import ( "net/http" "strconv" + "github.com/grafana/terraform-provider-grafana/internal/common" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -16,9 +17,9 @@ import ( func DatasourceSlo() *schema.Resource { return &schema.Resource{ ReadContext: datasourceSloRead, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, + // Importer: &schema.ResourceImporter{ + // StateContext: schema.ImportStatePassthroughContext, + // }, Schema: map[string]*schema.Schema{ "slos": &schema.Schema{ Type: schema.TypeList, @@ -215,14 +216,27 @@ func DatasourceSlo() *schema.Resource { } } +// Function sends a GET request to the SLO API Endpoint which returns a list of all SLOs +// Maps the API Response body to the Terraform Schema and displays as a READ in the terminal func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - serverPort := 3000 - requestURL := fmt.Sprintf("http://localhost:%d/api/plugins/grafana-slo-app/resources/v1/slo", serverPort) + grafanaClient := m.(*common.Client) + grafanaURL := grafanaClient.GrafanaAPIURL + + var diags diag.Diagnostics + + sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo" + requestURL := grafanaURL + sloPath + req, err := http.NewRequest(http.MethodGet, requestURL, nil) if err != nil { log.Fatalln(err) } + // If testing on Local Dev, comment on Lines 236-238 - it does not work if the Authorization Header is set + // token := grafanaClient.GrafanaAPIConfig.APIKey + // bearer := "Bearer " + token + // req.Header.Add("Authorization", bearer) + client := &http.Client{} resp, err := client.Do(req) if err != nil { @@ -252,7 +266,6 @@ func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{ d.Set("slos", terraformSlos) d.SetId(sloList.Slos[0].Uuid) - var diags diag.Diagnostics return diags } @@ -346,9 +359,7 @@ func unpackAlerting(AlertData *Alerting) []map[string]interface{} { alertObject["slowburn"] = unpackAlertingMetadata(*AlertData.SlowBurn) retAlertData = append(retAlertData, alertObject) - return retAlertData - } func unpackAlertingMetadata(Metadata AlertMetadata) []map[string]interface{} { @@ -366,6 +377,5 @@ func unpackAlertingMetadata(Metadata AlertMetadata) []map[string]interface{} { } retAlertMetaData = append(retAlertMetaData, labelsAnnotsStruct) - return retAlertMetaData } diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index ca07d9859..79ee5b429 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -213,12 +213,17 @@ func ResourceSlo() *schema.Resource { } } +// SLO Resource is defined by the user within the Terraform State file +// When 'terraform apply' is executed, it sends a POST Request and converts +// the data within the Terraform State into a JSON Object which is then sent to the API +// Following this, a READ is executed for the newly created SLO, which is then displayed within the +// terminal that Terraform is running in func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - // Warning or errors can be collected in a slice type var diags diag.Diagnostics sloPost := packSloResource(d) + // TBD - replace with a Go API Client Wrapper body, err := json.Marshal(sloPost) if err != nil { log.Fatalln(err) @@ -250,17 +255,17 @@ func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{ fmt.Println("error:", err) } + // Get the response back from the API, we need to set the ID of the Terraform Resource d.SetId(response.Uuid) + + // Executes a READ, displays the newly created SLO Resource within Terraform resourceSloRead(ctx, d, m) return diags } -type POSTResponse struct { - Message string `json:"message,omitempty"` - Uuid string `json:"uuid,omitempty"` -} - +// Fetches all the Properties defined on the Terraform SLO State Object and converts it +// to a Slo so that it can be converted to JSON and sent to the API func packSloResource(d *schema.ResourceData) Slo { tfname := d.Get("name").(string) tfdescription := d.Get("description").(string) @@ -268,7 +273,7 @@ func packSloResource(d *schema.ResourceData) Slo { query := d.Get("query").(string) tfquery := packQuery(query) - // Assumes that each SLO only have one Objective Value and one Objective Window + // Assumes that each SLO only has one Objective Value and one Objective Window objectives := d.Get("objectives").([]interface{}) objective := objectives[0].(map[string]interface{}) tfobjective := packObjective(objective) @@ -292,7 +297,6 @@ func packSloResource(d *schema.ResourceData) Slo { } return sloPost - } func packQuery(query string) Query { @@ -334,19 +338,6 @@ func packLabels(tfLabels []interface{}) []Label { return labelSlice } -// func unpackLabels(labels []Label) []map[string]interface{} { -// tfLabels := []map[string]interface{}{} - -// for _, label := range labels { -// tfLabel := map[string]interface{}{} -// tfLabel["key"] = label.Key -// tfLabel["value"] = label.Value -// tfLabels = append(tfLabels, tfLabel) -// } - -// return tfLabels -// } - func packAlerting(tfAlerting map[string]interface{}) Alerting { annots := tfAlerting["annotations"].([]interface{}) tfAnnots := packLabels(annots) diff --git a/internal/resources/slo/types.go b/internal/resources/slo/types.go index 9ef38496a..1c811a38b 100644 --- a/internal/resources/slo/types.go +++ b/internal/resources/slo/types.go @@ -1,3 +1,4 @@ +// file to be deleted, Types will be defined within the SLO API Go Client Wrapper package slo type SloList struct { @@ -81,3 +82,8 @@ type Query struct { Threshold *Threshold `json:"threshold,omitempty"` GroupByLabels []string `json:"groupBy,omitempty"` } + +type POSTResponse struct { + Message string `json:"message,omitempty"` + Uuid string `json:"uuid,omitempty"` +} diff --git a/slo_testing/.terraform.lock.hcl b/slo_testing/.terraform.lock.hcl deleted file mode 100644 index e9c5943fe..000000000 --- a/slo_testing/.terraform.lock.hcl +++ /dev/null @@ -1,10 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/grafana/grafana" { - version = "0.2.0" - constraints = "0.2.0" - hashes = [ - "h1:P1tT3IEncHzhjCzPfKF/pc/lZQRrOYvKYqDH9NVidUw=", - ] -} diff --git a/slo_testing/SLO_README.md b/slo_testing/SLO_README.md deleted file mode 100644 index 9a244301c..000000000 --- a/slo_testing/SLO_README.md +++ /dev/null @@ -1,78 +0,0 @@ --- Local SLO Dev Environment -- -1. Start up the Local SLO Dev Environment -2. Send a POST Request with a sample request body -POST Request: http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo -{ - "name":"test slo name", - "description":"test slo description", - "service":"service", - "labels": [{"key": "custom", "value": "value"}], - "objectives":[ - { - "value":0.995, - "window":"30d" - } - ], - "query":{ - "freeFormQuery":"sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - }, - "alerting":{ - "fastBurn":{ - "annotations":[ - { - "key":"annotsfastburnkey", - "value":"annotsfastburnvalue" - } - ], - "labels":[ - { - "key":"type", - "value":"slo" - } - ] - }, - "slowBurn":{ - "annotations":[ - { - "key":"annotsslowburnkey", - "value":"annotsslowburnvalue" - } - ], - "labels":[ - { - "key":"type", - "value":"slo" - } - ] - } - } -} - --- Terraform Provider -- -1. Within the root directory of the terraform-provider-grafana, run `make install`. This creates a Grafana Terraform Provider -2. Switch to the slo_testing directory `cd slo_testing` -3. Run the command `terraform init` -4. Run the command `terraform apply`. This will execute the `slo-ds-read.tf` state file, which currently just sends a GET request to the http://localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo endpoint, and returns it in Terraform. -5. Ensure to delete the `.terraform.lock.hcl` file that exists before rebuilding the terraform provider. - --- TBD -- -1. Currently - I am just doing this in local dev, I need to set this up and test it with a HG Account -2. Client Wrapper - I am currently just sending requests by creating an HTTP Client, ideally we should create a Go Client wrapper around our API. This will be done and refactored at a later point in time. -3. Tests TBD. - --- Questions -- -1. Why is our `Objectives` within the Slo struct a slice of Objectives? Shouldn't each Slo only have one Objective? -type Slo struct { - Uuid string `json:"uuid"` - Name string `json:"name"` - Description string `json:"description"` - Service string `json:"service,omitempty"` - Query Query `json:"query"` - Alerting *Alerting `json:"alerting,omitempty"` - Labels *[]Label `json:"labels,omitempty"` - Objectives []Objective `json:"objectives"` - DrilldownDashboardUid string `json:"dashboardUid,omitempty"` - DrilldownDashboardRef *DashboardRef `json:"drillDownDashboardRef,omitempty"` -} - -2. I cannot seem to add custom labels to the "Alerting" structure for some reason. Might be something wrong with the API. diff --git a/slo_testing/hg/hg_testing_README.md b/slo_testing/hg/hg_testing_README.md new file mode 100644 index 000000000..2d6d31dec --- /dev/null +++ b/slo_testing/hg/hg_testing_README.md @@ -0,0 +1,52 @@ +# How to Test the SLO Terraform Provider - Hosted Grafana + +## Create your HG Account and Get the SLO Plugin Deployed + +## Understanding Terraform Provider Code Flow +1. Within the terraform root directory, run `make install`. This command creates a binary of the Terraform Provider and moves it into the appropriate Terraform plugin directory, which allows for testing of a custom provider. + + * Note: you may need to modify the `OS_ARCH=darwin_arm64` property within the Makefile to match your operating system + +2. Within `provider.go`, we create two resources - a `grafana_slo_datasource` and a `grafana_slo_resource`. + +### Types of Resources +Datasource - datasources are resources that are external to Terraform (i.e. not managed by Terraform state). When interacting with a Datasource, they can be used to READ information, and datasources can also be imported (i.e. converted) into Resources, which allows Terraform state to control them. + +Resources - these are resources that can be managed by Terraform state. This means that you CREATE, READ, UPDATE, DELETE. If an IMPORT method is defined, you can also convert Datasources into Resources (i.e. this means that if you import a resource created by the UI (i.e. a Datasource) it can be converted into a Resrouce, which can be managed by Terraform). + +## Testing Datasource - READ +`internal/resources/slo/data_source_slo.go` + +This file defines a schema, that matches the response shape of a GET request to the SLO Endpoint. +``` +{ + "slos": [ + { + "uuid": "bik1rpvkvbzxnfkutzmkh", + "name": "test1", + ... + }, + { + "uuid": "94pqcghz92hybc3iwircy", + "name": "test2", + ... + }, + ] +} +``` + +Objective - we want to send a GET Request to the SLO Endpoint that returns a list of all SLOs, and we want to be able to READ that information and output it to the Terraform CLI. + +1. Within the terraform-provider-grafana root directory, run `make install`. +2. Within your SLO UI, create a SLO. +3. Set the GRAFANA_AUTH environment variable to your HG Grafana API Key +4. Within the `slo-datasource-read-hg.tf` file, set the url to be the url of your HG Instance. +5. Ensure that Lines 236-238 within the `data_source_slo.go` are UNCOMMENTED. +6. Comment out all the `.tf` files within the `slo_testing/hg` folder, EXCEPT for the `slo-datasource-read-hg.tf` file +7. Within the `slo_testing/hg` directory, run the commands `terraform init` and `terraform apply`. Ensure to remove the `.terraform.lock.hcl` and any `terraform.tfstate` files. +8. You should see a list of all SLOs within your Terraform CLI. + +### TBD ### +1. Testing HG for the SLO Resources for Create, Read, Update, and Import. +2. Client Wrapper - I am currently just sending requests by creating an HTTP Client, ideally we should create a Go Client wrapper around our API. This will be done and refactored at a later point in time. +3. Tests. \ No newline at end of file diff --git a/slo_testing/hg/slo-datasource-read-hg.tf b/slo_testing/hg/slo-datasource-read-hg.tf new file mode 100644 index 000000000..751d7bc88 --- /dev/null +++ b/slo_testing/hg/slo-datasource-read-hg.tf @@ -0,0 +1,19 @@ +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} + +provider "grafana" { + url = "https://elainetest.grafana.net" +} + +data "grafana_slo_datasource" "test1" { +} + +output "test1" { + value = data.grafana_slo_datasource.test1 +} \ No newline at end of file diff --git a/slo_testing/local/local_testing_README.md b/slo_testing/local/local_testing_README.md new file mode 100644 index 000000000..12e8f629b --- /dev/null +++ b/slo_testing/local/local_testing_README.md @@ -0,0 +1,89 @@ +# How to Test the SLO Terraform Provider - Locally + +## Set Up Local Environment +1. Start up the Local SLO Dev Environment which should be available at http://localhost:3000/a/grafana-slo-app/home + +## Understanding Terraform Provider Code Flow +1. Within the terraform root directory, run `make install`. This command creates a binary of the Terraform Provider and moves it into the appropriate Terraform plugin directory, which allows for testing of a custom provider. + + * Note: you may need to modify the `OS_ARCH=darwin_arm64` property within the Makefile to match your operating system + +2. Within `provider.go`, we create two resources - a `grafana_slo_datasource` and a `grafana_slo_resource`. + +### Types of Resources +Datasource - datasources are resources that are external to Terraform (i.e. not managed by Terraform state). When interacting with a Datasource, they can be used to READ information, and datasources can also be imported (i.e. converted) into Resources, which allows Terraform state to control them. + +Resources - these are resources that can be managed by Terraform state. This means that you CREATE, READ, UPDATE, DELETE. If an IMPORT method is defined, you can also convert Datasources into Resources (i.e. this means that if you import a resource created by the UI (i.e. a Datasource) it can be converted into a Resrouce, which can be managed by Terraform). + +## Testing Datasource - READ +`internal/resources/slo/data_source_slo.go` + +This file defines a schema, that matches the response shape of a GET request to the SLO Endpoint. +``` +{ + "slos": [ + { + "uuid": "bik1rpvkvbzxnfkutzmkh", + "name": "test1", + ... + }, + { + "uuid": "94pqcghz92hybc3iwircy", + "name": "test2", + ... + }, + ] +} +``` + +Objective - we want to send a GET Request to the SLO Endpoint that returns a list of all SLOs, and we want to be able to READ that information and output it to the Terraform CLI. + +1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Within the terraform-provider-grafana root directory, run `make install`. +2. Create a SLO / Send a POST Request to the endpoint (see `slo_sample.json` for an example) +3. Change to the `slo_testing/local` directory. +4. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-datasource-read.tf` file +5. Within `data_source_slo.go` - you MUST comment out L236-238 - for some reason, there is a bug that does not work if the Authorization Header is set. +6. Within the `slo_testing/local` directory, run the commands `terraform init` and `terraform apply`. Ensure to remove the `.terraform.lock.hcl` and any `terraform.tfstate` files. +7. You should see a list of all SLOs within the terminal that you've run terraform from. + +Elaine Questions - I'm open to modifying the shape of the Schema returned by Terraform on the read, so any thoughts here are welcome! Right now - I've just mirrored the structure we get back from the API. + +## Testing Resource - CREATE +Objective - we want to be able to define a SLO Resource within Terraform that should be created. Once the resource has successfully been created, we want to display the newly created SLO resource within the Terraform interface. + +The `slo-resource-create.tf` file will create two SLOs. + +Testing the CREATE Method +1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Within the terraform-provider-grafana root directory, run `make install`. +2. Change to the `slo_testing/local` directory. +3. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-create.tf` file +4. Within the `slo_testing` directory, run the commands `terraform init` and `terraform apply`. Ensure to remove the `.terraform.lock.hcl` and any `terraform.tfstate` files. +5. Within your terminal, you should see the output of the two newly created SLO from within Terraform, and within the SLO UI. + +## Testing the DELETE Method +Objective - we want to be able to delete a SLO Resource that was created with Terraform. +After creating the two SLO resources from the CREATE Method, we will DELETE them. + +Testing the DELETE Method / terraform destroy +1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files +2. Within the `slo_testing` directory, run the commands `terraform init`. Keep the `slo-resource-create.tf` file open, and execute `terraform apply`. This creates two new SLOs from the Terraform CLI. +3. Create a regular SLO using the UI. At this point, you should have 3 SLOs - 2 created from Terraform, and 1 created from the UI +4. To delete all Terraformed SLO resources, execute the command `terraform destroy`, and type `yes` in the terminal to confirm the delete +5. The two newly created Terraformed SLO Resources should be deleted, and you should still have the SLO that was created through the UI remaining. + +## Testing the UPDATE Method +1. Within the terraform-provider-grafana root directory, run `make install`. +2. Change to the `slo_testing/local` directory. +3. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-update.tf` file +4. Run the command `terraform init` +5. Run the command `terraform apply`. This creates the resource specified below. +6. To ensure that the PUT endpoint works, modify any of the values within the resource below, and re-run `terraform apply`. + +## Testing the IMPORT Method +1. Within the terraform-provider-grafana root directory, run `make install`. +2. Change to the `slo_testing/local` directory. +3. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-import.tf` file +4. Create a SLO using the UI or Postman. Take note of the SLO's UUID +5. Execute the command `terraform init` +6. Within the Terraform CLI directly, type in the command: `terraform import grafana_slo_resource.sample slo_UUID` +7. Now execute the command: `terraform state show grafana_slo_resource.sample` - you should see the data from the imported Resource. \ No newline at end of file diff --git a/slo_testing/local/slo-datasource-read.tf b/slo_testing/local/slo-datasource-read.tf new file mode 100644 index 000000000..4f7f422fb --- /dev/null +++ b/slo_testing/local/slo-datasource-read.tf @@ -0,0 +1,20 @@ +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} + +provider "grafana" { + url = "http://localhost:3000" + auth = "auth" +} + +data "grafana_slo_datasource" "test1" { +} + +output "test1" { + value = data.grafana_slo_datasource.test1 +} \ No newline at end of file diff --git a/slo_testing/slo-rs-create-delete.tf b/slo_testing/local/slo-resource-create.tf similarity index 63% rename from slo_testing/slo-rs-create-delete.tf rename to slo_testing/local/slo-resource-create.tf index 5a34565da..460d07dd3 100644 --- a/slo_testing/slo-rs-create-delete.tf +++ b/slo_testing/local/slo-resource-create.tf @@ -1,19 +1,3 @@ -# Testing the CREATE Method -# 1. When testing the Create Method, start up the Local Dev Environment. Comment out the `slo-ds-read.tf` file. -# 2. Within the root directory of the terraform-provider-grafana, run `make install`. This creates a Grafana Terraform Provider. -# 3. Switch to the slo_testing directory `cd slo_testing` -# 4. Run the command `terraform init` -# 5. Run the command `terraform apply slo-create.tf`. This sends the information below as a POST request to the API at http://grafana.k3d.localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo -# 6. Ensure to delete the `.terraform.lock.hcl` and any hidden terraform (terraform.tfstate) state files that exists before rebuilding the terraform provider. -# 7. Within your terminal, you should see the output of the two newly created SLO from within Terraform - -# Testing the DELETE Method / terraform destroy -# 1. Repeat the steps for the CREATE Method -# 2. Create a regular SLO using the UI. At this point, you should have 3 SLOs - 2 created from Terraform, and 1 created from the UI -# 3. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files -# 4. To delete all Terraformed SLO resources, execute the command `terraform destroy`, and type `yes` in the terminal to confirm the delete -# 5. The two newly created Terraformed SLO Resources should be automatically deleted. - # terraform { # required_providers { # grafana = { @@ -24,6 +8,7 @@ # } # provider "grafana" { +# url = "http://localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo" # auth = "auth" # } @@ -37,18 +22,14 @@ # objective_window = "28d" # } # labels { -# key = "label1a" -# value = "value1a" -# } -# labels { -# key = "label2a" -# value = "value2a" +# key = "name" +# value = "testslolabel" # } # alerting { # name = "hihialerting1" # labels { -# key = "alertinglabel1" -# value = "alertingvalue1" +# key = "name" +# value = "testsloalertinglabel" # } # annotations { diff --git a/slo_testing/local/slo-resource-import.tf b/slo_testing/local/slo-resource-import.tf new file mode 100644 index 000000000..fe8745f4d --- /dev/null +++ b/slo_testing/local/slo-resource-import.tf @@ -0,0 +1,19 @@ +# terraform { +# required_providers { +# grafana = { +# version = "0.2" +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } + +# provider "grafana" { +# url = "http://localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo" +# auth = "auth" +# } + +# resource "grafana_slo_resource" "sample" {} + +# output "sample_slo" { +# value = grafana_slo_resource.sample +# } diff --git a/slo_testing/slo-rs-update.tf b/slo_testing/local/slo-resource-update.tf similarity index 67% rename from slo_testing/slo-rs-update.tf rename to slo_testing/local/slo-resource-update.tf index 3a7bf042d..7470cdb05 100644 --- a/slo_testing/slo-rs-update.tf +++ b/slo_testing/local/slo-resource-update.tf @@ -1,12 +1,3 @@ -# Testing the UPDATE Method -# 1. When testing the UPDATE Method, start up the Local Dev Environment. Comment out all the other `.tf` files. -# 2. Within the root directory of the terraform-provider-grafana, run `make install`. This creates a Grafana Terraform Provider. -# 3. Switch to the slo_testing directory `cd slo_testing` -# 4. Run the command `terraform init` -# 5. Run the command `terraform apply`. This creates the resource specified below. -# 6. To ensure that the PUT endpoint works, modify any of the values within the resource below, and re-run `terraform apply`. -# 7. Using Postman, send a GET Request to the endpoint to ensure that the resource was appropriately modified within the API. - # terraform { # required_providers { # grafana = { @@ -17,11 +8,12 @@ # } # provider "grafana" { +# url = "http://localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo" # auth = "auth" # } # resource "grafana_slo_resource" "test1" { -# name = "Hello1" +# name = "Hello2" # description = "Testing Hello 1 - I hope this works!" # service = "service1" # query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" diff --git a/slo_testing/local/slo_sample.json b/slo_testing/local/slo_sample.json new file mode 100644 index 000000000..319a7b4bc --- /dev/null +++ b/slo_testing/local/slo_sample.json @@ -0,0 +1,26 @@ +{ + "name":"test name", + "description":"test description", + "service":"service", + "labels": [{"key": "name", "value": "testslo"}], + "objectives":[ + { + "value":0.995, + "window":"30d" + } + ], + "query":{ + "freeFormQuery":"sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + }, + "alerting":{ + "labels": [{"key": "name", "value": "testslo"}], + "fastBurn":{ + "annotations":[{ "key": "name", "value": "Custom Fast-burn Name"}, { "key": "description", "value": "Custom Fast-burn Desc"}], + "labels":[{"key":"type", "value":"slo"}] + }, + "slowBurn":{ + "annotations":[{ "key": "name", "value": "Custom Slow-burn Name"}, { "key": "description", "value": "Custom Slow-burn Desc"}], + "labels":[{"key":"type", "value":"slo"}] + } + } + } \ No newline at end of file diff --git a/slo_testing/slo-ds-read.tf b/slo_testing/slo-ds-read.tf deleted file mode 100644 index 4b20f1d46..000000000 --- a/slo_testing/slo-ds-read.tf +++ /dev/null @@ -1,19 +0,0 @@ -# terraform { -# required_providers { -# grafana = { -# version = "0.2" -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } - -# provider "grafana" { -# auth = "auth" -# } - -# data "grafana_slo_datasource" "test1" { -# } - -# output "test1" { -# value = data.grafana_slo_datasource.test1 -# } \ No newline at end of file diff --git a/slo_testing/slo-rs-import.tf b/slo_testing/slo-rs-import.tf deleted file mode 100644 index 503d81aeb..000000000 --- a/slo_testing/slo-rs-import.tf +++ /dev/null @@ -1,22 +0,0 @@ -# terraform import grafana_slo_resource.sample 7jglyd0d965pbmbikv62l -# terraform state show grafana_slo_resource.sample - -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} - -provider "grafana" { - auth = "auth" -} - -resource "grafana_slo_resource" "sample" {} - -output "sample_slo" { - value = grafana_slo_resource.sample -} - From 6b399bc3791872e1f7be398f3f6c2bf6f4dc4c87 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 12 Apr 2023 14:28:20 -0400 Subject: [PATCH 09/51] slo: Implement SLO Resource CREATE and READ HG Functionality and Testing READMEs --- internal/resources/slo/data_source_slo.go | 10 +- internal/resources/slo/resource_slo.go | 32 +++- slo_testing/hg/hg_testing_README.md | 13 ++ slo_testing/hg/slo-resource-create.tf | 122 ++++++++++++ slo_testing/local/local_testing_README.md | 11 +- slo_testing/local/slo-resource-create.tf | 224 +++++++++++----------- 6 files changed, 283 insertions(+), 129 deletions(-) create mode 100644 slo_testing/hg/slo-resource-create.tf diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 4e3b1d800..4e69063fa 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -219,11 +219,11 @@ func DatasourceSlo() *schema.Resource { // Function sends a GET request to the SLO API Endpoint which returns a list of all SLOs // Maps the API Response body to the Terraform Schema and displays as a READ in the terminal func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + grafanaClient := m.(*common.Client) grafanaURL := grafanaClient.GrafanaAPIURL - var diags diag.Diagnostics - sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo" requestURL := grafanaURL + sloPath @@ -233,9 +233,9 @@ func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{ } // If testing on Local Dev, comment on Lines 236-238 - it does not work if the Authorization Header is set - // token := grafanaClient.GrafanaAPIConfig.APIKey - // bearer := "Bearer " + token - // req.Header.Add("Authorization", bearer) + token := grafanaClient.GrafanaAPIConfig.APIKey + bearer := "Bearer " + token + req.Header.Add("Authorization", bearer) client := &http.Client{} resp, err := client.Do(req) diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 79ee5b429..b9b71680b 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -10,6 +10,7 @@ import ( "net/http" "time" + "github.com/grafana/terraform-provider-grafana/internal/common" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -222,21 +223,28 @@ func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{ var diags diag.Diagnostics sloPost := packSloResource(d) - - // TBD - replace with a Go API Client Wrapper body, err := json.Marshal(sloPost) if err != nil { log.Fatalln(err) } bodyReader := bytes.NewReader(body) - serverPort := 3000 - requestURL := fmt.Sprintf("http://localhost:%d/api/plugins/grafana-slo-app/resources/v1/slo", serverPort) + grafanaClient := m.(*common.Client) + grafanaURL := grafanaClient.GrafanaAPIURL + + sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo" + requestURL := fmt.Sprintf("%s%s", grafanaURL, sloPath) + req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader) if err != nil { log.Fatalln(err) } + // If testing on Local Dev, comment on Lines 244-246 - it does not work if the Authorization Header is set + token := grafanaClient.GrafanaAPIConfig.APIKey + bearer := "Bearer " + token + req.Header.Add("Authorization", bearer) + client := &http.Client{} resp, err := client.Do(req) if err != nil { @@ -381,15 +389,26 @@ func packAlertMetadata(metadata []interface{}) AlertMetadata { // resourceSloRead - sends a GET Request to the single SLO Endpoint func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + sloID := d.Id() - serverPort := 3000 - requestURL := fmt.Sprintf("http://localhost:%d/api/plugins/grafana-slo-app/resources/v1/slo/%s", serverPort, sloID) + grafanaClient := m.(*common.Client) + grafanaURL := grafanaClient.GrafanaAPIURL + + sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" + requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) + req, err := http.NewRequest(http.MethodGet, requestURL, nil) if err != nil { log.Fatalln(err) } + // If testing on Local Dev, comment on Lines 408-411 - it does not work if the Authorization Header is set + token := grafanaClient.GrafanaAPIConfig.APIKey + bearer := "Bearer " + token + req.Header.Add("Authorization", bearer) + client := &http.Client{} resp, err := client.Do(req) if err != nil { @@ -412,7 +431,6 @@ func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) setTerraformState(d, slo) - var diags diag.Diagnostics return diags } diff --git a/slo_testing/hg/hg_testing_README.md b/slo_testing/hg/hg_testing_README.md index 2d6d31dec..73972ea6a 100644 --- a/slo_testing/hg/hg_testing_README.md +++ b/slo_testing/hg/hg_testing_README.md @@ -46,6 +46,19 @@ Objective - we want to send a GET Request to the SLO Endpoint that returns a lis 7. Within the `slo_testing/hg` directory, run the commands `terraform init` and `terraform apply`. Ensure to remove the `.terraform.lock.hcl` and any `terraform.tfstate` files. 8. You should see a list of all SLOs within your Terraform CLI. +## Testing Resource - CREATE +Objective - we want to be able to define a SLO Resource within Terraform state that should be created. Once the resource has successfully been created, we want to display the newly created SLO resource within the Terraform CLI. + +The `slo-resource-create.tf` file will create two SLOs. + +Testing the CREATE Method +1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Within the terraform-provider-grafana root directory, run `make install`. +2. Change to the `slo_testing/hg` directory. +3. Comment out all the `.tf` files within the `slo_testing/hg` folder, EXCEPT for the `slo-resource-create.tf` file +4. Within the `slo_testing/hg` directory, run the commands `terraform init` and `terraform apply`. +5. Within your terminal, you should see the output of the two newly created SLO from within Terraform, and two newly created SLOs within the SLO UI. + + ### TBD ### 1. Testing HG for the SLO Resources for Create, Read, Update, and Import. 2. Client Wrapper - I am currently just sending requests by creating an HTTP Client, ideally we should create a Go Client wrapper around our API. This will be done and refactored at a later point in time. diff --git a/slo_testing/hg/slo-resource-create.tf b/slo_testing/hg/slo-resource-create.tf new file mode 100644 index 000000000..abf3f7e04 --- /dev/null +++ b/slo_testing/hg/slo-resource-create.tf @@ -0,0 +1,122 @@ +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} + +provider "grafana" { + url = "https://elainetest.grafana.net" +} + +resource "grafana_slo_resource" "test1" { + name = "Hello1" + description = "Testing Hello 1 - I hope this works!" + service = "service1" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.85 + objective_window = "28d" + } + labels { + key = "name" + value = "testslolabel" + } + alerting { + name = "hihialerting1" + labels { + key = "name" + value = "testsloalertinglabel" + } + + annotations { + key = "alertingannot1" + value = "alertingvalue1" + } + + fastburn { + labels { + key = "labelsfastburnkey1" + value = "labelsfastburnvalue1" + } + annotations { + key = "annotsfastburnannot1" + value = "annotsfastburnvalue1" + } + } + + slowburn { + labels { + key = "labelsslowburnkey1" + value = "labelsslowburnvalue1" + } + annotations { + key = "annotsslowburnannot1" + value = "annotsslowburnvalue1" + } + } + } +} + +output "test1_order" { + value = grafana_slo_resource.test1 +} + +resource "grafana_slo_resource" "test2" { + name = "Hello2" + description = "Testing Hello 2 - I hope this works!" + service = "service2" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.85 + objective_window = "28d" + } + labels { + key = "label2a" + value = "value2a" + } + labels { + key = "label3a" + value = "value3a" + } + alerting { + name = "hihialerting2" + labels { + key = "alertinglabel2" + value = "alertingvalue2" + } + + annotations { + key = "alertingannot2" + value = "alertingvalue2" + } + + fastburn { + labels { + key = "labelsfastburnkey2" + value = "labelsfastburnvalue2" + } + annotations { + key = "annotsfastburnannot2" + value = "annotsfastburnvalue2" + } + } + + slowburn { + labels { + key = "labelsslowburnkey2" + value = "labelsslowburnvalue2" + } + annotations { + key = "annotsslowburnannot2" + value = "annotsslowburnvalue2" + } + } + } +} + +output "test2_order" { + value = grafana_slo_resource.test2 +} \ No newline at end of file diff --git a/slo_testing/local/local_testing_README.md b/slo_testing/local/local_testing_README.md index 12e8f629b..1072dcbe4 100644 --- a/slo_testing/local/local_testing_README.md +++ b/slo_testing/local/local_testing_README.md @@ -54,11 +54,12 @@ Objective - we want to be able to define a SLO Resource within Terraform that sh The `slo-resource-create.tf` file will create two SLOs. Testing the CREATE Method -1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Within the terraform-provider-grafana root directory, run `make install`. -2. Change to the `slo_testing/local` directory. -3. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-create.tf` file -4. Within the `slo_testing` directory, run the commands `terraform init` and `terraform apply`. Ensure to remove the `.terraform.lock.hcl` and any `terraform.tfstate` files. -5. Within your terminal, you should see the output of the two newly created SLO from within Terraform, and within the SLO UI. +1. Within `resource_slo.go` - you MUST comment out Lines 244-246 and Lines 408-411, there is a bug that does not work if the Authorization Header is set. +2. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Within the terraform-provider-grafana root directory, run `make install`. +3. Change to the `slo_testing/local` directory. +4. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-create.tf` file +5. Within the `slo_testing` directory, run the commands `terraform init` and `terraform apply`. Ensure to remove the `.terraform.lock.hcl` and any `terraform.tfstate` files. +6. Within your terminal, you should see the output of the two newly created SLO from within Terraform, and two new SLOs within the SLO UI. ## Testing the DELETE Method Objective - we want to be able to delete a SLO Resource that was created with Terraform. diff --git a/slo_testing/local/slo-resource-create.tf b/slo_testing/local/slo-resource-create.tf index 460d07dd3..0bd13ed02 100644 --- a/slo_testing/local/slo-resource-create.tf +++ b/slo_testing/local/slo-resource-create.tf @@ -1,123 +1,123 @@ -# terraform { -# required_providers { -# grafana = { -# version = "0.2" -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} -# provider "grafana" { -# url = "http://localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo" -# auth = "auth" -# } +provider "grafana" { + url = "http://localhost:3000" + auth = "auth" +} -# resource "grafana_slo_resource" "test1" { -# name = "Hello1" -# description = "Testing Hello 1 - I hope this works!" -# service = "service1" -# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" -# objectives { -# objective_value = 0.85 -# objective_window = "28d" -# } -# labels { -# key = "name" -# value = "testslolabel" -# } -# alerting { -# name = "hihialerting1" -# labels { -# key = "name" -# value = "testsloalertinglabel" -# } +resource "grafana_slo_resource" "test1" { + name = "Hello1" + description = "Testing Hello 1 - I hope this works!" + service = "service1" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.85 + objective_window = "28d" + } + labels { + key = "name" + value = "testslolabel" + } + alerting { + name = "hihialerting1" + labels { + key = "name" + value = "testsloalertinglabel" + } -# annotations { -# key = "alertingannot1" -# value = "alertingvalue1" -# } + annotations { + key = "alertingannot1" + value = "alertingvalue1" + } -# fastburn { -# labels { -# key = "labelsfastburnkey1" -# value = "labelsfastburnvalue1" -# } -# annotations { -# key = "annotsfastburnannot1" -# value = "annotsfastburnvalue1" -# } -# } + fastburn { + labels { + key = "labelsfastburnkey1" + value = "labelsfastburnvalue1" + } + annotations { + key = "annotsfastburnannot1" + value = "annotsfastburnvalue1" + } + } -# slowburn { -# labels { -# key = "labelsslowburnkey1" -# value = "labelsslowburnvalue1" -# } -# annotations { -# key = "annotsslowburnannot1" -# value = "annotsslowburnvalue1" -# } -# } -# } -# } + slowburn { + labels { + key = "labelsslowburnkey1" + value = "labelsslowburnvalue1" + } + annotations { + key = "annotsslowburnannot1" + value = "annotsslowburnvalue1" + } + } + } +} -# output "test1_order" { -# value = grafana_slo_resource.test1 -# } +output "test1_order" { + value = grafana_slo_resource.test1 +} -# resource "grafana_slo_resource" "test2" { -# name = "Hello2" -# description = "Testing Hello 2 - I hope this works!" -# service = "service2" -# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" -# objectives { -# objective_value = 0.85 -# objective_window = "28d" -# } -# labels { -# key = "label2a" -# value = "value2a" -# } -# labels { -# key = "label3a" -# value = "value3a" -# } -# alerting { -# name = "hihialerting2" -# labels { -# key = "alertinglabel2" -# value = "alertingvalue2" -# } +resource "grafana_slo_resource" "test2" { + name = "Hello2" + description = "Testing Hello 2 - I hope this works!" + service = "service2" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.85 + objective_window = "28d" + } + labels { + key = "label2a" + value = "value2a" + } + labels { + key = "label3a" + value = "value3a" + } + alerting { + name = "hihialerting2" + labels { + key = "alertinglabel2" + value = "alertingvalue2" + } -# annotations { -# key = "alertingannot2" -# value = "alertingvalue2" -# } + annotations { + key = "alertingannot2" + value = "alertingvalue2" + } -# fastburn { -# labels { -# key = "labelsfastburnkey2" -# value = "labelsfastburnvalue2" -# } -# annotations { -# key = "annotsfastburnannot2" -# value = "annotsfastburnvalue2" -# } -# } + fastburn { + labels { + key = "labelsfastburnkey2" + value = "labelsfastburnvalue2" + } + annotations { + key = "annotsfastburnannot2" + value = "annotsfastburnvalue2" + } + } -# slowburn { -# labels { -# key = "labelsslowburnkey2" -# value = "labelsslowburnvalue2" -# } -# annotations { -# key = "annotsslowburnannot2" -# value = "annotsslowburnvalue2" -# } -# } -# } -# } + slowburn { + labels { + key = "labelsslowburnkey2" + value = "labelsslowburnvalue2" + } + annotations { + key = "annotsslowburnannot2" + value = "annotsslowburnvalue2" + } + } + } +} -# output "test2_order" { -# value = grafana_slo_resource.test2 -# } \ No newline at end of file +output "test2_order" { + value = grafana_slo_resource.test2 +} \ No newline at end of file From abc322e06a76f3b13ec866c133543d161ac0fe94 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 12 Apr 2023 15:01:12 -0400 Subject: [PATCH 10/51] slo: Implement SLO Resource UPDATE HG Functionality and Testing READMEs --- internal/resources/slo/resource_slo.go | 13 ++- slo_testing/hg/hg_testing_README.md | 9 +- slo_testing/hg/slo-resource-update.tf | 69 ++++++++++++ slo_testing/local/local_testing_README.md | 17 +-- slo_testing/local/slo-resource-import.tf | 32 +++--- slo_testing/local/slo-resource-update.tf | 128 +++++++++++----------- 6 files changed, 177 insertions(+), 91 deletions(-) create mode 100644 slo_testing/hg/slo-resource-update.tf diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index b9b71680b..1c4786bc0 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -490,13 +490,22 @@ func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{ } bodyReader := bytes.NewReader(body) - serverPort := 3000 - requestURL := fmt.Sprintf("http://localhost:%d/api/plugins/grafana-slo-app/resources/v1/slo/%s", serverPort, sloID) + grafanaClient := m.(*common.Client) + grafanaURL := grafanaClient.GrafanaAPIURL + + sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" + requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) + req, err := http.NewRequest(http.MethodPut, requestURL, bodyReader) if err != nil { log.Fatalln(err) } + // If testing on Local Dev, comment on Lines 505-507 - it does not work if the Authorization Header is set + token := grafanaClient.GrafanaAPIConfig.APIKey + bearer := "Bearer " + token + req.Header.Add("Authorization", bearer) + client := &http.Client{} _, err = client.Do(req) if err != nil { diff --git a/slo_testing/hg/hg_testing_README.md b/slo_testing/hg/hg_testing_README.md index 73972ea6a..b5120cb3c 100644 --- a/slo_testing/hg/hg_testing_README.md +++ b/slo_testing/hg/hg_testing_README.md @@ -58,8 +58,15 @@ Testing the CREATE Method 4. Within the `slo_testing/hg` directory, run the commands `terraform init` and `terraform apply`. 5. Within your terminal, you should see the output of the two newly created SLO from within Terraform, and two newly created SLOs within the SLO UI. +## Testing the UPDATE Method +1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Within the terraform-provider-grafana root directory, run `make install`. +2. Change to the `slo_testing/hg` directory. +3. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-update.tf` file +4. Within the `slo_testing/hg` directory, run the commands `terraform init` and `terraform apply`. This creates the resource specified below in the terraform state file. +5. To ensure that the PUT endpoint works, modify any of the values within the resource below, and re-run `terraform apply`. +6. Make a GET Request to the API Endpoint to ensure the resource was properly modified. ### TBD ### -1. Testing HG for the SLO Resources for Create, Read, Update, and Import. +1. Testing HG for the SLO Resources for Delete, and Import. 2. Client Wrapper - I am currently just sending requests by creating an HTTP Client, ideally we should create a Go Client wrapper around our API. This will be done and refactored at a later point in time. 3. Tests. \ No newline at end of file diff --git a/slo_testing/hg/slo-resource-update.tf b/slo_testing/hg/slo-resource-update.tf new file mode 100644 index 000000000..26b12eb4e --- /dev/null +++ b/slo_testing/hg/slo-resource-update.tf @@ -0,0 +1,69 @@ +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} + +provider "grafana" { + url = "https://elainetest.grafana.net" +} + +resource "grafana_slo_resource" "test1" { + name = "Hello1" + description = "Testing Hello 1 - I hope this works!" + service = "service5" + query = "sum(rate(apiserver_request_total{code!=\"300\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.95 + objective_window = "28d" + } + labels { + key = "label1a" + value = "value1a" + } + labels { + key = "label2a" + value = "value2a" + } + alerting { + name = "hihialerting1" + labels { + key = "alertinglabel1" + value = "alertingvalue1" + } + + annotations { + key = "alertingannot1" + value = "alertingvalue1" + } + + fastburn { + labels { + key = "labelsfastburnkey1" + value = "labelsfastburnvalue1" + } + annotations { + key = "annotsfastburnannot1" + value = "annotsfastburnvalue1" + } + } + + slowburn { + labels { + key = "labelsslowburnkey1" + value = "labelsslowburnvalue1" + } + annotations { + key = "annotsslowburnannot2" + value = "annotsslowburnvalue1" + } + } + } +} + +output "test1_order" { + value = grafana_slo_resource.test1 +} \ No newline at end of file diff --git a/slo_testing/local/local_testing_README.md b/slo_testing/local/local_testing_README.md index 1072dcbe4..4743ee6cf 100644 --- a/slo_testing/local/local_testing_README.md +++ b/slo_testing/local/local_testing_README.md @@ -61,6 +61,15 @@ Testing the CREATE Method 5. Within the `slo_testing` directory, run the commands `terraform init` and `terraform apply`. Ensure to remove the `.terraform.lock.hcl` and any `terraform.tfstate` files. 6. Within your terminal, you should see the output of the two newly created SLO from within Terraform, and two new SLOs within the SLO UI. +## Testing the UPDATE Method +1. Within `resource_slo.go` - you MUST comment out Lines 244-246, Lines 408-411, and Lines 505-507, there is a bug that does not work if the Authorization Header is set. +2. Within the terraform-provider-grafana root directory, run `make install`. +3. Change to the `slo_testing/local` directory. +4. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-update.tf` file +5. Run the command `terraform init` +6. Run the command `terraform apply`. This creates the resource specified below. +7. To ensure that the PUT endpoint works, modify any of the values within the resource below, and re-run `terraform apply`. + ## Testing the DELETE Method Objective - we want to be able to delete a SLO Resource that was created with Terraform. After creating the two SLO resources from the CREATE Method, we will DELETE them. @@ -72,14 +81,6 @@ Testing the DELETE Method / terraform destroy 4. To delete all Terraformed SLO resources, execute the command `terraform destroy`, and type `yes` in the terminal to confirm the delete 5. The two newly created Terraformed SLO Resources should be deleted, and you should still have the SLO that was created through the UI remaining. -## Testing the UPDATE Method -1. Within the terraform-provider-grafana root directory, run `make install`. -2. Change to the `slo_testing/local` directory. -3. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-update.tf` file -4. Run the command `terraform init` -5. Run the command `terraform apply`. This creates the resource specified below. -6. To ensure that the PUT endpoint works, modify any of the values within the resource below, and re-run `terraform apply`. - ## Testing the IMPORT Method 1. Within the terraform-provider-grafana root directory, run `make install`. 2. Change to the `slo_testing/local` directory. diff --git a/slo_testing/local/slo-resource-import.tf b/slo_testing/local/slo-resource-import.tf index fe8745f4d..6d91f4a54 100644 --- a/slo_testing/local/slo-resource-import.tf +++ b/slo_testing/local/slo-resource-import.tf @@ -1,19 +1,19 @@ -# terraform { -# required_providers { -# grafana = { -# version = "0.2" -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} -# provider "grafana" { -# url = "http://localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo" -# auth = "auth" -# } +provider "grafana" { + url = "http://localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo" + auth = "auth" +} -# resource "grafana_slo_resource" "sample" {} +resource "grafana_slo_resource" "sample" {} -# output "sample_slo" { -# value = grafana_slo_resource.sample -# } +output "sample_slo" { + value = grafana_slo_resource.sample +} diff --git a/slo_testing/local/slo-resource-update.tf b/slo_testing/local/slo-resource-update.tf index 7470cdb05..f4499acba 100644 --- a/slo_testing/local/slo-resource-update.tf +++ b/slo_testing/local/slo-resource-update.tf @@ -1,70 +1,70 @@ -# terraform { -# required_providers { -# grafana = { -# version = "0.2" -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} -# provider "grafana" { -# url = "http://localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo" -# auth = "auth" -# } +provider "grafana" { + url = "http://localhost:3000" + auth = "auth" +} -# resource "grafana_slo_resource" "test1" { -# name = "Hello2" -# description = "Testing Hello 1 - I hope this works!" -# service = "service1" -# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" -# objectives { -# objective_value = 0.85 -# objective_window = "28d" -# } -# labels { -# key = "label1a" -# value = "value1a" -# } -# labels { -# key = "label2a" -# value = "value2a" -# } -# alerting { -# name = "hihialerting1" -# labels { -# key = "alertinglabel1" -# value = "alertingvalue1" -# } +resource "grafana_slo_resource" "test1" { + name = "Hello1" + description = "Testing Hello 1 - I hope this works!" + service = "service1" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.85 + objective_window = "28d" + } + labels { + key = "label1a" + value = "value1a" + } + labels { + key = "label2a" + value = "value2a" + } + alerting { + name = "hihialerting1" + labels { + key = "alertinglabel1" + value = "alertingvalue1" + } -# annotations { -# key = "alertingannot1" -# value = "alertingvalue1" -# } + annotations { + key = "alertingannot1" + value = "alertingvalue1" + } -# fastburn { -# labels { -# key = "labelsfastburnkey1" -# value = "labelsfastburnvalue1" -# } -# annotations { -# key = "annotsfastburnannot1" -# value = "annotsfastburnvalue1" -# } -# } + fastburn { + labels { + key = "labelsfastburnkey1" + value = "labelsfastburnvalue1" + } + annotations { + key = "annotsfastburnannot1" + value = "annotsfastburnvalue1" + } + } -# slowburn { -# labels { -# key = "labelsslowburnkey1" -# value = "labelsslowburnvalue1" -# } -# annotations { -# key = "annotsslowburnannot1" -# value = "annotsslowburnvalue1" -# } -# } -# } -# } + slowburn { + labels { + key = "labelsslowburnkey1" + value = "labelsslowburnvalue1" + } + annotations { + key = "annotsslowburnannot1" + value = "annotsslowburnvalue1" + } + } + } +} -# output "test1_order" { -# value = grafana_slo_resource.test1 -# } \ No newline at end of file +output "test1_order" { + value = grafana_slo_resource.test1 +} \ No newline at end of file From 639bd4b4b213e2b1390b765196da3571d30007df Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 12 Apr 2023 16:49:14 -0400 Subject: [PATCH 11/51] slo: Implement SLO Resource DESTROY HG Functionality and Testing READMEs --- internal/resources/slo/data_source_slo.go | 5 +- internal/resources/slo/resource_slo.go | 27 +++-- slo_testing/hg/hg_testing_README.md | 12 +++ slo_testing/hg/slo-datasource-read-hg.tf | 32 +++--- slo_testing/hg/slo-resource-update.tf | 126 +++++++++++----------- slo_testing/local/local_testing_README.md | 17 +-- 6 files changed, 119 insertions(+), 100 deletions(-) diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 4e69063fa..5edd1e71f 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -17,9 +17,6 @@ import ( func DatasourceSlo() *schema.Resource { return &schema.Resource{ ReadContext: datasourceSloRead, - // Importer: &schema.ResourceImporter{ - // StateContext: schema.ImportStatePassthroughContext, - // }, Schema: map[string]*schema.Schema{ "slos": &schema.Schema{ Type: schema.TypeList, @@ -232,7 +229,7 @@ func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{ log.Fatalln(err) } - // If testing on Local Dev, comment on Lines 236-238 - it does not work if the Authorization Header is set + // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set token := grafanaClient.GrafanaAPIConfig.APIKey bearer := "Bearer " + token req.Header.Add("Authorization", bearer) diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 1c4786bc0..77f5cfea9 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -21,6 +21,9 @@ func ResourceSlo() *schema.Resource { ReadContext: resourceSloRead, UpdateContext: resourceSloUpdate, DeleteContext: resourceSloDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, Schema: map[string]*schema.Schema{ "name": &schema.Schema{ Type: schema.TypeString, @@ -208,9 +211,6 @@ func ResourceSlo() *schema.Resource { Computed: true, }, }, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, } } @@ -240,7 +240,7 @@ func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{ log.Fatalln(err) } - // If testing on Local Dev, comment on Lines 244-246 - it does not work if the Authorization Header is set + // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set token := grafanaClient.GrafanaAPIConfig.APIKey bearer := "Bearer " + token req.Header.Add("Authorization", bearer) @@ -404,7 +404,7 @@ func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) log.Fatalln(err) } - // If testing on Local Dev, comment on Lines 408-411 - it does not work if the Authorization Header is set + // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set token := grafanaClient.GrafanaAPIConfig.APIKey bearer := "Bearer " + token req.Header.Add("Authorization", bearer) @@ -454,17 +454,26 @@ func setTerraformState(d *schema.ResourceData, slo Slo) { } func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + sloID := d.Id() - var diags diag.Diagnostics + grafanaClient := m.(*common.Client) + grafanaURL := grafanaClient.GrafanaAPIURL + + sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" + requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) - serverPort := 3000 - requestURL := fmt.Sprintf("http://localhost:%d/api/plugins/grafana-slo-app/resources/v1/slo/%s", serverPort, sloID) req, err := http.NewRequest(http.MethodDelete, requestURL, nil) if err != nil { log.Fatalln(err) } + // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set + token := grafanaClient.GrafanaAPIConfig.APIKey + bearer := "Bearer " + token + req.Header.Add("Authorization", bearer) + client := &http.Client{} resp, err := client.Do(req) if err != nil { @@ -501,7 +510,7 @@ func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{ log.Fatalln(err) } - // If testing on Local Dev, comment on Lines 505-507 - it does not work if the Authorization Header is set + // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set token := grafanaClient.GrafanaAPIConfig.APIKey bearer := "Bearer " + token req.Header.Add("Authorization", bearer) diff --git a/slo_testing/hg/hg_testing_README.md b/slo_testing/hg/hg_testing_README.md index b5120cb3c..051f9c7d2 100644 --- a/slo_testing/hg/hg_testing_README.md +++ b/slo_testing/hg/hg_testing_README.md @@ -1,6 +1,7 @@ # How to Test the SLO Terraform Provider - Hosted Grafana ## Create your HG Account and Get the SLO Plugin Deployed +Generate a new Service Account Token, and set the environment variable GRAFANA_AUTH to the value of your token (or you can specify the `auth` field within the Terraform State file). ## Understanding Terraform Provider Code Flow 1. Within the terraform root directory, run `make install`. This command creates a binary of the Terraform Provider and moves it into the appropriate Terraform plugin directory, which allows for testing of a custom provider. @@ -66,6 +67,17 @@ Testing the CREATE Method 5. To ensure that the PUT endpoint works, modify any of the values within the resource below, and re-run `terraform apply`. 6. Make a GET Request to the API Endpoint to ensure the resource was properly modified. +## Testing the DELETE Method +Objective - we want to be able to delete a SLO Resource that was created with Terraform. +After creating the two SLO resources from the CREATE Method, we will DELETE them. + +Testing the DELETE Method / terraform destroy +1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files +2. Within the `slo_testing` directory, run the commands `terraform init`. Keep the `slo-resource-create.tf` file open, and execute `terraform apply`. This creates two new SLOs from the Terraform CLI. +3. Create a regular SLO using the UI. At this point, you should have 3 SLOs - 2 created from Terraform, and 1 created from the UI +4. To delete all Terraformed SLO resources, execute the command `terraform destroy`, and type `yes` in the terminal to confirm the delete +5. The two newly created Terraformed SLO Resources should be deleted, and you should still have the SLO that was created through the UI remaining. + ### TBD ### 1. Testing HG for the SLO Resources for Delete, and Import. 2. Client Wrapper - I am currently just sending requests by creating an HTTP Client, ideally we should create a Go Client wrapper around our API. This will be done and refactored at a later point in time. diff --git a/slo_testing/hg/slo-datasource-read-hg.tf b/slo_testing/hg/slo-datasource-read-hg.tf index 751d7bc88..6f5a47a46 100644 --- a/slo_testing/hg/slo-datasource-read-hg.tf +++ b/slo_testing/hg/slo-datasource-read-hg.tf @@ -1,19 +1,19 @@ -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} +# terraform { +# required_providers { +# grafana = { +# version = "0.2" +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } -provider "grafana" { - url = "https://elainetest.grafana.net" -} +# provider "grafana" { +# url = "https://elainetest.grafana.net" +# } -data "grafana_slo_datasource" "test1" { -} +# data "grafana_slo_datasource" "test1" { +# } -output "test1" { - value = data.grafana_slo_datasource.test1 -} \ No newline at end of file +# output "test1" { +# value = data.grafana_slo_datasource.test1 +# } \ No newline at end of file diff --git a/slo_testing/hg/slo-resource-update.tf b/slo_testing/hg/slo-resource-update.tf index 26b12eb4e..14858ec25 100644 --- a/slo_testing/hg/slo-resource-update.tf +++ b/slo_testing/hg/slo-resource-update.tf @@ -1,69 +1,69 @@ -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} +# terraform { +# required_providers { +# grafana = { +# version = "0.2" +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } -provider "grafana" { - url = "https://elainetest.grafana.net" -} +# provider "grafana" { +# url = "https://elainetest.grafana.net" +# } -resource "grafana_slo_resource" "test1" { - name = "Hello1" - description = "Testing Hello 1 - I hope this works!" - service = "service5" - query = "sum(rate(apiserver_request_total{code!=\"300\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.95 - objective_window = "28d" - } - labels { - key = "label1a" - value = "value1a" - } - labels { - key = "label2a" - value = "value2a" - } - alerting { - name = "hihialerting1" - labels { - key = "alertinglabel1" - value = "alertingvalue1" - } +# resource "grafana_slo_resource" "test1" { +# name = "Hello1" +# description = "Testing Hello 1 - I hope this works!" +# service = "service5" +# query = "sum(rate(apiserver_request_total{code!=\"300\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" +# objectives { +# objective_value = 0.95 +# objective_window = "28d" +# } +# labels { +# key = "label1a" +# value = "value1a" +# } +# labels { +# key = "label2a" +# value = "value2a" +# } +# alerting { +# name = "hihialerting1" +# labels { +# key = "alertinglabel1" +# value = "alertingvalue1" +# } - annotations { - key = "alertingannot1" - value = "alertingvalue1" - } +# annotations { +# key = "alertingannot1" +# value = "alertingvalue1" +# } - fastburn { - labels { - key = "labelsfastburnkey1" - value = "labelsfastburnvalue1" - } - annotations { - key = "annotsfastburnannot1" - value = "annotsfastburnvalue1" - } - } +# fastburn { +# labels { +# key = "labelsfastburnkey1" +# value = "labelsfastburnvalue1" +# } +# annotations { +# key = "annotsfastburnannot1" +# value = "annotsfastburnvalue1" +# } +# } - slowburn { - labels { - key = "labelsslowburnkey1" - value = "labelsslowburnvalue1" - } - annotations { - key = "annotsslowburnannot2" - value = "annotsslowburnvalue1" - } - } - } -} +# slowburn { +# labels { +# key = "labelsslowburnkey1" +# value = "labelsslowburnvalue1" +# } +# annotations { +# key = "annotsslowburnannot2" +# value = "annotsslowburnvalue1" +# } +# } +# } +# } -output "test1_order" { - value = grafana_slo_resource.test1 -} \ No newline at end of file +# output "test1_order" { +# value = grafana_slo_resource.test1 +# } \ No newline at end of file diff --git a/slo_testing/local/local_testing_README.md b/slo_testing/local/local_testing_README.md index 4743ee6cf..dac2113d8 100644 --- a/slo_testing/local/local_testing_README.md +++ b/slo_testing/local/local_testing_README.md @@ -42,27 +42,28 @@ Objective - we want to send a GET Request to the SLO Endpoint that returns a lis 2. Create a SLO / Send a POST Request to the endpoint (see `slo_sample.json` for an example) 3. Change to the `slo_testing/local` directory. 4. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-datasource-read.tf` file -5. Within `data_source_slo.go` - you MUST comment out L236-238 - for some reason, there is a bug that does not work if the Authorization Header is set. +5. Within `data_source_slo.go` - you MUST comment out L233-235 - for some reason, there is a bug that does not work if the Authorization Header is set. 6. Within the `slo_testing/local` directory, run the commands `terraform init` and `terraform apply`. Ensure to remove the `.terraform.lock.hcl` and any `terraform.tfstate` files. 7. You should see a list of all SLOs within the terminal that you've run terraform from. Elaine Questions - I'm open to modifying the shape of the Schema returned by Terraform on the read, so any thoughts here are welcome! Right now - I've just mirrored the structure we get back from the API. -## Testing Resource - CREATE +## Testing SLO Resource + +### Testing the CREATE Method Objective - we want to be able to define a SLO Resource within Terraform that should be created. Once the resource has successfully been created, we want to display the newly created SLO resource within the Terraform interface. The `slo-resource-create.tf` file will create two SLOs. -Testing the CREATE Method -1. Within `resource_slo.go` - you MUST comment out Lines 244-246 and Lines 408-411, there is a bug that does not work if the Authorization Header is set. +1. Within `resource_slo.go` - you MUST comment out Lines 244-246, Lines 408-410, Line 473-475, and Lines 514-516, there is a bug that does not work if the Authorization Header is set. 2. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Within the terraform-provider-grafana root directory, run `make install`. 3. Change to the `slo_testing/local` directory. 4. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-create.tf` file 5. Within the `slo_testing` directory, run the commands `terraform init` and `terraform apply`. Ensure to remove the `.terraform.lock.hcl` and any `terraform.tfstate` files. 6. Within your terminal, you should see the output of the two newly created SLO from within Terraform, and two new SLOs within the SLO UI. -## Testing the UPDATE Method -1. Within `resource_slo.go` - you MUST comment out Lines 244-246, Lines 408-411, and Lines 505-507, there is a bug that does not work if the Authorization Header is set. +### Testing the UPDATE Method +1. Within `resource_slo.go` - you MUST comment out Lines 244-246, Lines 408-410, Line 473-475, and Lines 514-516, there is a bug that does not work if the Authorization Header is set. 2. Within the terraform-provider-grafana root directory, run `make install`. 3. Change to the `slo_testing/local` directory. 4. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-update.tf` file @@ -70,7 +71,7 @@ Testing the CREATE Method 6. Run the command `terraform apply`. This creates the resource specified below. 7. To ensure that the PUT endpoint works, modify any of the values within the resource below, and re-run `terraform apply`. -## Testing the DELETE Method +### Testing the DELETE Method Objective - we want to be able to delete a SLO Resource that was created with Terraform. After creating the two SLO resources from the CREATE Method, we will DELETE them. @@ -81,7 +82,7 @@ Testing the DELETE Method / terraform destroy 4. To delete all Terraformed SLO resources, execute the command `terraform destroy`, and type `yes` in the terminal to confirm the delete 5. The two newly created Terraformed SLO Resources should be deleted, and you should still have the SLO that was created through the UI remaining. -## Testing the IMPORT Method +### Testing the IMPORT Method 1. Within the terraform-provider-grafana root directory, run `make install`. 2. Change to the `slo_testing/local` directory. 3. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-import.tf` file From e3cae37196d86d6177040f6fcd4a4a7ba64cd585 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 12 Apr 2023 17:32:42 -0400 Subject: [PATCH 12/51] slo: Implement SLO Resource IMPORT HG Functionality and Update Testing READMEs --- slo_testing/hg/hg_testing_README.md | 22 ++-- slo_testing/hg/slo-datasource-read-hg.tf | 32 +++--- slo_testing/hg/slo-resource-import.tf | 18 ++++ slo_testing/hg/slo-resource-update.tf | 126 +++++++++++----------- slo_testing/local/local_testing_README.md | 3 +- slo_testing/local/slo-resource-import.tf | 1 - 6 files changed, 115 insertions(+), 87 deletions(-) create mode 100644 slo_testing/hg/slo-resource-import.tf diff --git a/slo_testing/hg/hg_testing_README.md b/slo_testing/hg/hg_testing_README.md index 051f9c7d2..3777c2595 100644 --- a/slo_testing/hg/hg_testing_README.md +++ b/slo_testing/hg/hg_testing_README.md @@ -2,6 +2,7 @@ ## Create your HG Account and Get the SLO Plugin Deployed Generate a new Service Account Token, and set the environment variable GRAFANA_AUTH to the value of your token (or you can specify the `auth` field within the Terraform State file). +Within the `.tf` files within `slo_testing/hg`, ensure that you set the `url` field to be the `url` of your HG Instance. ## Understanding Terraform Provider Code Flow 1. Within the terraform root directory, run `make install`. This command creates a binary of the Terraform Provider and moves it into the appropriate Terraform plugin directory, which allows for testing of a custom provider. @@ -67,18 +68,27 @@ Testing the CREATE Method 5. To ensure that the PUT endpoint works, modify any of the values within the resource below, and re-run `terraform apply`. 6. Make a GET Request to the API Endpoint to ensure the resource was properly modified. -## Testing the DELETE Method +## Testing the DELETE/DESTROY Method Objective - we want to be able to delete a SLO Resource that was created with Terraform. After creating the two SLO resources from the CREATE Method, we will DELETE them. -Testing the DELETE Method / terraform destroy 1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files -2. Within the `slo_testing` directory, run the commands `terraform init`. Keep the `slo-resource-create.tf` file open, and execute `terraform apply`. This creates two new SLOs from the Terraform CLI. +2. Within the `slo_testing/hg` directory, ensure the `slo-resource-create.tf` file is uncommented. Run the commands `terraform init` and execute `terraform apply`. This creates two new SLOs from the Terraform CLI. 3. Create a regular SLO using the UI. At this point, you should have 3 SLOs - 2 created from Terraform, and 1 created from the UI 4. To delete all Terraformed SLO resources, execute the command `terraform destroy`, and type `yes` in the terminal to confirm the delete -5. The two newly created Terraformed SLO Resources should be deleted, and you should still have the SLO that was created through the UI remaining. +5. The two newly created Terraformed SLO Resources should be deleted, and you should still see the SLO that was created through the UI remaining. + +### Testing the IMPORT Method +1. Within the terraform-provider-grafana root directory, run `make install`. +2. Change to the `slo_testing/hg` directory. +3. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-import.tf` file +4. Create a SLO using the UI or Postman. Take note of the SLO's UUID +5. Execute the command `terraform init` +6. Within the Terraform CLI directly, type in the command: `terraform import grafana_slo_resource.sample slo_UUID` +7. Now execute the command: `terraform state show grafana_slo_resource.sample` - you should see the data from the imported Resource. +8. To verify that this resource is now under Terraform control, within the `slo-resource-import.tf` file, comment out lines 14-18. Then, within the CLI run `terraform destroy`. This should destroy the resource from within the Terraform CLI. ### TBD ### -1. Testing HG for the SLO Resources for Delete, and Import. -2. Client Wrapper - I am currently just sending requests by creating an HTTP Client, ideally we should create a Go Client wrapper around our API. This will be done and refactored at a later point in time. +2. Figure out the Bug after Creating Terraform Resources (cannot go and Edit a SLO - why?). +2. Integrate into the existing Grafana Golang Client (https://github.com/grafana/grafana-api-golang-client) 3. Tests. \ No newline at end of file diff --git a/slo_testing/hg/slo-datasource-read-hg.tf b/slo_testing/hg/slo-datasource-read-hg.tf index 6f5a47a46..751d7bc88 100644 --- a/slo_testing/hg/slo-datasource-read-hg.tf +++ b/slo_testing/hg/slo-datasource-read-hg.tf @@ -1,19 +1,19 @@ -# terraform { -# required_providers { -# grafana = { -# version = "0.2" -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} -# provider "grafana" { -# url = "https://elainetest.grafana.net" -# } +provider "grafana" { + url = "https://elainetest.grafana.net" +} -# data "grafana_slo_datasource" "test1" { -# } +data "grafana_slo_datasource" "test1" { +} -# output "test1" { -# value = data.grafana_slo_datasource.test1 -# } \ No newline at end of file +output "test1" { + value = data.grafana_slo_datasource.test1 +} \ No newline at end of file diff --git a/slo_testing/hg/slo-resource-import.tf b/slo_testing/hg/slo-resource-import.tf new file mode 100644 index 000000000..d9134dceb --- /dev/null +++ b/slo_testing/hg/slo-resource-import.tf @@ -0,0 +1,18 @@ +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} + +provider "grafana" { + url = "https://elainetest.grafana.net" +} + +resource "grafana_slo_resource" "sample" {} + +output "sample_slo" { + value = grafana_slo_resource.sample +} diff --git a/slo_testing/hg/slo-resource-update.tf b/slo_testing/hg/slo-resource-update.tf index 14858ec25..26b12eb4e 100644 --- a/slo_testing/hg/slo-resource-update.tf +++ b/slo_testing/hg/slo-resource-update.tf @@ -1,69 +1,69 @@ -# terraform { -# required_providers { -# grafana = { -# version = "0.2" -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} -# provider "grafana" { -# url = "https://elainetest.grafana.net" -# } +provider "grafana" { + url = "https://elainetest.grafana.net" +} -# resource "grafana_slo_resource" "test1" { -# name = "Hello1" -# description = "Testing Hello 1 - I hope this works!" -# service = "service5" -# query = "sum(rate(apiserver_request_total{code!=\"300\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" -# objectives { -# objective_value = 0.95 -# objective_window = "28d" -# } -# labels { -# key = "label1a" -# value = "value1a" -# } -# labels { -# key = "label2a" -# value = "value2a" -# } -# alerting { -# name = "hihialerting1" -# labels { -# key = "alertinglabel1" -# value = "alertingvalue1" -# } +resource "grafana_slo_resource" "test1" { + name = "Hello1" + description = "Testing Hello 1 - I hope this works!" + service = "service5" + query = "sum(rate(apiserver_request_total{code!=\"300\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.95 + objective_window = "28d" + } + labels { + key = "label1a" + value = "value1a" + } + labels { + key = "label2a" + value = "value2a" + } + alerting { + name = "hihialerting1" + labels { + key = "alertinglabel1" + value = "alertingvalue1" + } -# annotations { -# key = "alertingannot1" -# value = "alertingvalue1" -# } + annotations { + key = "alertingannot1" + value = "alertingvalue1" + } -# fastburn { -# labels { -# key = "labelsfastburnkey1" -# value = "labelsfastburnvalue1" -# } -# annotations { -# key = "annotsfastburnannot1" -# value = "annotsfastburnvalue1" -# } -# } + fastburn { + labels { + key = "labelsfastburnkey1" + value = "labelsfastburnvalue1" + } + annotations { + key = "annotsfastburnannot1" + value = "annotsfastburnvalue1" + } + } -# slowburn { -# labels { -# key = "labelsslowburnkey1" -# value = "labelsslowburnvalue1" -# } -# annotations { -# key = "annotsslowburnannot2" -# value = "annotsslowburnvalue1" -# } -# } -# } -# } + slowburn { + labels { + key = "labelsslowburnkey1" + value = "labelsslowburnvalue1" + } + annotations { + key = "annotsslowburnannot2" + value = "annotsslowburnvalue1" + } + } + } +} -# output "test1_order" { -# value = grafana_slo_resource.test1 -# } \ No newline at end of file +output "test1_order" { + value = grafana_slo_resource.test1 +} \ No newline at end of file diff --git a/slo_testing/local/local_testing_README.md b/slo_testing/local/local_testing_README.md index dac2113d8..8c6916c92 100644 --- a/slo_testing/local/local_testing_README.md +++ b/slo_testing/local/local_testing_README.md @@ -89,4 +89,5 @@ Testing the DELETE Method / terraform destroy 4. Create a SLO using the UI or Postman. Take note of the SLO's UUID 5. Execute the command `terraform init` 6. Within the Terraform CLI directly, type in the command: `terraform import grafana_slo_resource.sample slo_UUID` -7. Now execute the command: `terraform state show grafana_slo_resource.sample` - you should see the data from the imported Resource. \ No newline at end of file +7. Now execute the command: `terraform state show grafana_slo_resource.sample` - you should see the data from the imported Resource. +8. To verify that this resource is now under Terraform control, within the `slo-resource-import.tf` file, comment out lines 14-18. Then, within the CLI run `terraform destroy`. This should destroy the resource from within the Terraform CLI. \ No newline at end of file diff --git a/slo_testing/local/slo-resource-import.tf b/slo_testing/local/slo-resource-import.tf index 6d91f4a54..0d8829018 100644 --- a/slo_testing/local/slo-resource-import.tf +++ b/slo_testing/local/slo-resource-import.tf @@ -9,7 +9,6 @@ terraform { provider "grafana" { url = "http://localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo" - auth = "auth" } resource "grafana_slo_resource" "sample" {} From 51ee8af8718a225245a7789039d7e86a28a7f203 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Tue, 18 Apr 2023 11:02:55 -0400 Subject: [PATCH 13/51] Modify Grafana Client for the ListSLOs functionality for the DatasourceSLORead --- go.mod | 2 + internal/provider/provider.go | 2 +- internal/resources/slo/data_source_slo.go | 60 +- internal/resources/slo/resource_slo.go | 948 +++++++++++----------- slo_testing/hg/slo-resource-create.tf | 222 ++--- slo_testing/hg/slo-resource-import.tf | 30 +- slo_testing/hg/slo-resource-update.tf | 126 +-- 7 files changed, 678 insertions(+), 712 deletions(-) diff --git a/go.mod b/go.mod index 6e1fdb762..bdeeac505 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/grafana/terraform-provider-grafana go 1.18 +replace github.com/grafana/grafana-api-golang-client => /Users/elainevuong/go/src/github.com/grafana/grafana-api-golang-client + require ( github.com/Masterminds/semver/v3 v3.2.0 github.com/grafana/amixr-api-go-client v0.0.7 diff --git a/internal/provider/provider.go b/internal/provider/provider.go index b17bec359..a55d0d3e4 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -80,7 +80,7 @@ func Provider(version string) func() *schema.Provider { "grafana_machine_learning_outlier_detector": machinelearning.ResourceOutlierDetector(), // SLO - "grafana_slo_resource": slo.ResourceSlo(), + // "grafana_slo_resource": slo.ResourceSlo(), }) // Resources that require the Synthetic Monitoring client to exist. diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 5edd1e71f..b1e213b6b 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -2,13 +2,9 @@ package slo import ( "context" - "encoding/json" - "fmt" - "io" - "log" - "net/http" "strconv" + gapi "github.com/grafana/grafana-api-golang-client" "github.com/grafana/terraform-provider-grafana/internal/common" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -219,54 +215,22 @@ func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{ var diags diag.Diagnostics grafanaClient := m.(*common.Client) - grafanaURL := grafanaClient.GrafanaAPIURL - - sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo" - requestURL := grafanaURL + sloPath - - req, err := http.NewRequest(http.MethodGet, requestURL, nil) - if err != nil { - log.Fatalln(err) - } - - // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set - token := grafanaClient.GrafanaAPIConfig.APIKey - bearer := "Bearer " + token - req.Header.Add("Authorization", bearer) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - log.Fatalln(err) - } - - defer resp.Body.Close() - - b, err := io.ReadAll(resp.Body) - if err != nil { - log.Fatalln(err) - } - - var sloList SloList - - err = json.Unmarshal(b, &sloList) - if err != nil { - fmt.Println("error:", err) - } + apiSlos, _ := grafanaClient.GrafanaAPI.ListSLOs() terraformSlos := []interface{}{} - for _, slo := range sloList.Slos { + + for _, slo := range apiSlos.Slos { terraformSlo := convertDatasourceSlo(slo) terraformSlos = append(terraformSlos, terraformSlo) } d.Set("slos", terraformSlos) - d.SetId(sloList.Slos[0].Uuid) + d.SetId(apiSlos.Slos[0].Uuid) return diags } -func convertDatasourceSlo(slo Slo) map[string]interface{} { +func convertDatasourceSlo(slo gapi.Slo) map[string]interface{} { ret := make(map[string]interface{}) ret["uuid"] = slo.Uuid @@ -292,7 +256,7 @@ func convertDatasourceSlo(slo Slo) map[string]interface{} { } // TBD for Other Query Types Once Implemented -func unpackQuery(query Query) string { +func unpackQuery(query gapi.Query) string { if query.FreeformQuery.Query != "" { return query.FreeformQuery.Query } @@ -301,7 +265,7 @@ func unpackQuery(query Query) string { } -func unpackObjectives(objectives []Objective) []map[string]interface{} { +func unpackObjectives(objectives []gapi.Objective) []map[string]interface{} { retObjectives := []map[string]interface{}{} for _, objective := range objectives { @@ -314,7 +278,7 @@ func unpackObjectives(objectives []Objective) []map[string]interface{} { return retObjectives } -func unpackLabels(labels *[]Label) []map[string]interface{} { +func unpackLabels(labels *[]gapi.Label) []map[string]interface{} { retLabels := []map[string]interface{}{} if labels != nil { @@ -330,7 +294,7 @@ func unpackLabels(labels *[]Label) []map[string]interface{} { return nil } -func unpackDashboard(slo Slo) map[string]interface{} { +func unpackDashboard(slo gapi.Slo) map[string]interface{} { retDashboard := make(map[string]interface{}) if slo.DrilldownDashboardRef != nil { @@ -345,7 +309,7 @@ func unpackDashboard(slo Slo) map[string]interface{} { return retDashboard } -func unpackAlerting(AlertData *Alerting) []map[string]interface{} { +func unpackAlerting(AlertData *gapi.Alerting) []map[string]interface{} { retAlertData := []map[string]interface{}{} alertObject := make(map[string]interface{}) @@ -359,7 +323,7 @@ func unpackAlerting(AlertData *Alerting) []map[string]interface{} { return retAlertData } -func unpackAlertingMetadata(Metadata AlertMetadata) []map[string]interface{} { +func unpackAlertingMetadata(Metadata gapi.AlertMetadata) []map[string]interface{} { retAlertMetaData := []map[string]interface{}{} labelsAnnotsStruct := make(map[string]interface{}) diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 77f5cfea9..d86f5060d 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -1,528 +1,528 @@ package slo -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "time" - - "github.com/grafana/terraform-provider-grafana/internal/common" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func ResourceSlo() *schema.Resource { - return &schema.Resource{ - CreateContext: resourceSloCreate, - ReadContext: resourceSloRead, - UpdateContext: resourceSloUpdate, - DeleteContext: resourceSloDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "description": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "service": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "query": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "labels": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - "objectives": &schema.Schema{ - Type: schema.TypeList, - MaxItems: 1, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "objective_value": &schema.Schema{ - Type: schema.TypeFloat, - Optional: true, - }, - "objective_window": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - "dashboard_ref": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "alerting": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "labels": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - "annotations": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - "fastburn": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "labels": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - "annotations": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - }, - }, - }, - "slowburn": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "labels": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - "annotations": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - "last_updated": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - }, - } -} - -// SLO Resource is defined by the user within the Terraform State file -// When 'terraform apply' is executed, it sends a POST Request and converts -// the data within the Terraform State into a JSON Object which is then sent to the API -// Following this, a READ is executed for the newly created SLO, which is then displayed within the -// terminal that Terraform is running in -func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics - - sloPost := packSloResource(d) - body, err := json.Marshal(sloPost) - if err != nil { - log.Fatalln(err) - } - bodyReader := bytes.NewReader(body) - - grafanaClient := m.(*common.Client) - grafanaURL := grafanaClient.GrafanaAPIURL - - sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo" - requestURL := fmt.Sprintf("%s%s", grafanaURL, sloPath) - - req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader) - if err != nil { - log.Fatalln(err) - } - - // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set - token := grafanaClient.GrafanaAPIConfig.APIKey - bearer := "Bearer " + token - req.Header.Add("Authorization", bearer) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - log.Fatalln(err) - } - - b, err := io.ReadAll(resp.Body) - if err != nil { - log.Fatalln(err) - } - - var response POSTResponse - - err = json.Unmarshal(b, &response) - if err != nil { - fmt.Println("error:", err) - } - - // Get the response back from the API, we need to set the ID of the Terraform Resource - d.SetId(response.Uuid) - - // Executes a READ, displays the newly created SLO Resource within Terraform - resourceSloRead(ctx, d, m) - - return diags -} - -// Fetches all the Properties defined on the Terraform SLO State Object and converts it -// to a Slo so that it can be converted to JSON and sent to the API -func packSloResource(d *schema.ResourceData) Slo { - tfname := d.Get("name").(string) - tfdescription := d.Get("description").(string) - tfservice := d.Get("service").(string) - query := d.Get("query").(string) - tfquery := packQuery(query) - - // Assumes that each SLO only has one Objective Value and one Objective Window - objectives := d.Get("objectives").([]interface{}) - objective := objectives[0].(map[string]interface{}) - tfobjective := packObjective(objective) - - labels := d.Get("labels").([]interface{}) - tflabels := packLabels(labels) - - alerting := d.Get("alerting").([]interface{}) - alert := alerting[0].(map[string]interface{}) - tfalerting := packAlerting(alert) - - sloPost := Slo{ - Uuid: d.Id(), - Name: tfname, - Description: tfdescription, - Service: tfservice, - Objectives: tfobjective, - Query: tfquery, - Alerting: &tfalerting, - Labels: &tflabels, - } - - return sloPost -} - -func packQuery(query string) Query { - sloQuery := Query{ - FreeformQuery: FreeformQuery{ - Query: query, - }, - } - - return sloQuery -} - -func packObjective(tfobjective map[string]interface{}) []Objective { - objective := Objective{ - Value: tfobjective["objective_value"].(float64), - Window: tfobjective["objective_window"].(string), - } - - objectiveSlice := []Objective{} - objectiveSlice = append(objectiveSlice, objective) - - return objectiveSlice -} +// import ( +// "bytes" +// "context" +// "encoding/json" +// "fmt" +// "io" +// "log" +// "net/http" +// "time" + +// "github.com/grafana/terraform-provider-grafana/internal/common" +// "github.com/hashicorp/terraform-plugin-sdk/v2/diag" +// "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +// ) + +// func ResourceSlo() *schema.Resource { +// return &schema.Resource{ +// CreateContext: resourceSloCreate, +// ReadContext: resourceSloRead, +// UpdateContext: resourceSloUpdate, +// DeleteContext: resourceSloDelete, +// Importer: &schema.ResourceImporter{ +// StateContext: schema.ImportStatePassthroughContext, +// }, +// Schema: map[string]*schema.Schema{ +// "name": &schema.Schema{ +// Type: schema.TypeString, +// Required: true, +// }, +// "description": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// "service": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// "query": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// "labels": &schema.Schema{ +// Type: schema.TypeList, +// Optional: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "key": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// "value": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// }, +// }, +// }, +// "objectives": &schema.Schema{ +// Type: schema.TypeList, +// MaxItems: 1, +// Required: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "objective_value": &schema.Schema{ +// Type: schema.TypeFloat, +// Optional: true, +// }, +// "objective_window": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// }, +// }, +// }, +// "dashboard_ref": { +// Type: schema.TypeMap, +// Optional: true, +// Elem: &schema.Schema{ +// Type: schema.TypeString, +// }, +// }, +// "alerting": &schema.Schema{ +// Type: schema.TypeList, +// Optional: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "name": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// "labels": &schema.Schema{ +// Type: schema.TypeList, +// Optional: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "key": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// "value": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// }, +// }, +// }, +// "annotations": &schema.Schema{ +// Type: schema.TypeList, +// Optional: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "key": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// "value": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// }, +// }, +// }, +// "fastburn": &schema.Schema{ +// Type: schema.TypeList, +// Optional: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "labels": &schema.Schema{ +// Type: schema.TypeList, +// Optional: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "key": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// "value": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// }, +// }, +// }, +// "annotations": &schema.Schema{ +// Type: schema.TypeList, +// Optional: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "key": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// "value": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// "slowburn": &schema.Schema{ +// Type: schema.TypeList, +// Optional: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "labels": &schema.Schema{ +// Type: schema.TypeList, +// Optional: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "key": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// "value": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// }, +// }, +// }, +// "annotations": &schema.Schema{ +// Type: schema.TypeList, +// Optional: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "key": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// "value": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// "last_updated": &schema.Schema{ +// Type: schema.TypeString, +// Optional: true, +// Computed: true, +// }, +// }, +// } +// } + +// // SLO Resource is defined by the user within the Terraform State file +// // When 'terraform apply' is executed, it sends a POST Request and converts +// // the data within the Terraform State into a JSON Object which is then sent to the API +// // Following this, a READ is executed for the newly created SLO, which is then displayed within the +// // terminal that Terraform is running in +// func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +// var diags diag.Diagnostics + +// sloPost := packSloResource(d) +// body, err := json.Marshal(sloPost) +// if err != nil { +// log.Fatalln(err) +// } +// bodyReader := bytes.NewReader(body) + +// grafanaClient := m.(*common.Client) +// grafanaURL := grafanaClient.GrafanaAPIURL + +// sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo" +// requestURL := fmt.Sprintf("%s%s", grafanaURL, sloPath) + +// req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader) +// if err != nil { +// log.Fatalln(err) +// } + +// // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set +// token := grafanaClient.GrafanaAPIConfig.APIKey +// bearer := "Bearer " + token +// req.Header.Add("Authorization", bearer) + +// client := &http.Client{} +// resp, err := client.Do(req) +// if err != nil { +// log.Fatalln(err) +// } + +// b, err := io.ReadAll(resp.Body) +// if err != nil { +// log.Fatalln(err) +// } + +// var response POSTResponse + +// err = json.Unmarshal(b, &response) +// if err != nil { +// fmt.Println("error:", err) +// } + +// // Get the response back from the API, we need to set the ID of the Terraform Resource +// d.SetId(response.Uuid) + +// // Executes a READ, displays the newly created SLO Resource within Terraform +// resourceSloRead(ctx, d, m) + +// return diags +// } + +// // Fetches all the Properties defined on the Terraform SLO State Object and converts it +// // to a Slo so that it can be converted to JSON and sent to the API +// func packSloResource(d *schema.ResourceData) Slo { +// tfname := d.Get("name").(string) +// tfdescription := d.Get("description").(string) +// tfservice := d.Get("service").(string) +// query := d.Get("query").(string) +// tfquery := packQuery(query) + +// // Assumes that each SLO only has one Objective Value and one Objective Window +// objectives := d.Get("objectives").([]interface{}) +// objective := objectives[0].(map[string]interface{}) +// tfobjective := packObjective(objective) + +// labels := d.Get("labels").([]interface{}) +// tflabels := packLabels(labels) + +// alerting := d.Get("alerting").([]interface{}) +// alert := alerting[0].(map[string]interface{}) +// tfalerting := packAlerting(alert) + +// sloPost := Slo{ +// Uuid: d.Id(), +// Name: tfname, +// Description: tfdescription, +// Service: tfservice, +// Objectives: tfobjective, +// Query: tfquery, +// Alerting: &tfalerting, +// Labels: &tflabels, +// } + +// return sloPost +// } + +// func packQuery(query string) Query { +// sloQuery := Query{ +// FreeformQuery: FreeformQuery{ +// Query: query, +// }, +// } + +// return sloQuery +// } + +// func packObjective(tfobjective map[string]interface{}) []Objective { +// objective := Objective{ +// Value: tfobjective["objective_value"].(float64), +// Window: tfobjective["objective_window"].(string), +// } + +// objectiveSlice := []Objective{} +// objectiveSlice = append(objectiveSlice, objective) + +// return objectiveSlice +// } -func packLabels(tfLabels []interface{}) []Label { - labelSlice := []Label{} +// func packLabels(tfLabels []interface{}) []Label { +// labelSlice := []Label{} - for ind := range tfLabels { - currLabel := tfLabels[ind].(map[string]interface{}) - curr := Label{ - Key: currLabel["key"].(string), - Value: currLabel["value"].(string), - } - - labelSlice = append(labelSlice, curr) - - } +// for ind := range tfLabels { +// currLabel := tfLabels[ind].(map[string]interface{}) +// curr := Label{ +// Key: currLabel["key"].(string), +// Value: currLabel["value"].(string), +// } + +// labelSlice = append(labelSlice, curr) + +// } - return labelSlice -} +// return labelSlice +// } -func packAlerting(tfAlerting map[string]interface{}) Alerting { - annots := tfAlerting["annotations"].([]interface{}) - tfAnnots := packLabels(annots) +// func packAlerting(tfAlerting map[string]interface{}) Alerting { +// annots := tfAlerting["annotations"].([]interface{}) +// tfAnnots := packLabels(annots) - labels := tfAlerting["labels"].([]interface{}) - tfLabels := packLabels(labels) +// labels := tfAlerting["labels"].([]interface{}) +// tfLabels := packLabels(labels) - fastBurn := tfAlerting["fastburn"].([]interface{}) - tfFastBurn := packAlertMetadata(fastBurn) +// fastBurn := tfAlerting["fastburn"].([]interface{}) +// tfFastBurn := packAlertMetadata(fastBurn) - slowBurn := tfAlerting["slowburn"].([]interface{}) - tfSlowBurn := packAlertMetadata(slowBurn) +// slowBurn := tfAlerting["slowburn"].([]interface{}) +// tfSlowBurn := packAlertMetadata(slowBurn) - alerting := Alerting{ - Name: tfAlerting["name"].(string), - Annotations: &tfAnnots, - Labels: &tfLabels, - FastBurn: &tfFastBurn, - SlowBurn: &tfSlowBurn, - } +// alerting := Alerting{ +// Name: tfAlerting["name"].(string), +// Annotations: &tfAnnots, +// Labels: &tfLabels, +// FastBurn: &tfFastBurn, +// SlowBurn: &tfSlowBurn, +// } - return alerting -} +// return alerting +// } -func packAlertMetadata(metadata []interface{}) AlertMetadata { - meta := metadata[0].(map[string]interface{}) +// func packAlertMetadata(metadata []interface{}) AlertMetadata { +// meta := metadata[0].(map[string]interface{}) - labels := meta["labels"].([]interface{}) - tflabels := packLabels(labels) +// labels := meta["labels"].([]interface{}) +// tflabels := packLabels(labels) - annots := meta["annotations"].([]interface{}) - tfannots := packLabels(annots) +// annots := meta["annotations"].([]interface{}) +// tfannots := packLabels(annots) - apiMetadata := AlertMetadata{ - Labels: &tflabels, - Annotations: &tfannots, - } +// apiMetadata := AlertMetadata{ +// Labels: &tflabels, +// Annotations: &tfannots, +// } - return apiMetadata -} +// return apiMetadata +// } -// resourceSloRead - sends a GET Request to the single SLO Endpoint -func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics +// // resourceSloRead - sends a GET Request to the single SLO Endpoint +// func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +// var diags diag.Diagnostics - sloID := d.Id() +// sloID := d.Id() - grafanaClient := m.(*common.Client) - grafanaURL := grafanaClient.GrafanaAPIURL +// grafanaClient := m.(*common.Client) +// grafanaURL := grafanaClient.GrafanaAPIURL - sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" - requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) +// sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" +// requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) - req, err := http.NewRequest(http.MethodGet, requestURL, nil) - if err != nil { - log.Fatalln(err) - } +// req, err := http.NewRequest(http.MethodGet, requestURL, nil) +// if err != nil { +// log.Fatalln(err) +// } - // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set - token := grafanaClient.GrafanaAPIConfig.APIKey - bearer := "Bearer " + token - req.Header.Add("Authorization", bearer) +// // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set +// token := grafanaClient.GrafanaAPIConfig.APIKey +// bearer := "Bearer " + token +// req.Header.Add("Authorization", bearer) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - log.Fatalln(err) - } +// client := &http.Client{} +// resp, err := client.Do(req) +// if err != nil { +// log.Fatalln(err) +// } - defer resp.Body.Close() +// defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) - if err != nil { - log.Fatalln(err) - } +// b, err := io.ReadAll(resp.Body) +// if err != nil { +// log.Fatalln(err) +// } - var slo Slo +// var slo Slo - err = json.Unmarshal(b, &slo) - if err != nil { - fmt.Println("error:", err) - } +// err = json.Unmarshal(b, &slo) +// if err != nil { +// fmt.Println("error:", err) +// } - setTerraformState(d, slo) +// setTerraformState(d, slo) - return diags -} +// return diags +// } -func setTerraformState(d *schema.ResourceData, slo Slo) { - d.Set("name", slo.Name) - d.Set("description", slo.Description) - d.Set("service", slo.Service) - d.Set("query", unpackQuery(slo.Query)) - retLabels := unpackLabels(slo.Labels) +// func setTerraformState(d *schema.ResourceData, slo Slo) { +// d.Set("name", slo.Name) +// d.Set("description", slo.Description) +// d.Set("service", slo.Service) +// d.Set("query", unpackQuery(slo.Query)) +// retLabels := unpackLabels(slo.Labels) - d.Set("labels", retLabels) +// d.Set("labels", retLabels) - retDashboard := unpackDashboard(slo) - d.Set("dashboard_ref", retDashboard) +// retDashboard := unpackDashboard(slo) +// d.Set("dashboard_ref", retDashboard) - retObjectives := unpackObjectives(slo.Objectives) - d.Set("objectives", retObjectives) +// retObjectives := unpackObjectives(slo.Objectives) +// d.Set("objectives", retObjectives) - retAlerting := unpackAlerting(slo.Alerting) - d.Set("alerting", retAlerting) -} +// retAlerting := unpackAlerting(slo.Alerting) +// d.Set("alerting", retAlerting) +// } -func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics +// func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +// var diags diag.Diagnostics - sloID := d.Id() +// sloID := d.Id() - grafanaClient := m.(*common.Client) - grafanaURL := grafanaClient.GrafanaAPIURL +// grafanaClient := m.(*common.Client) +// grafanaURL := grafanaClient.GrafanaAPIURL - sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" - requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) +// sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" +// requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) - req, err := http.NewRequest(http.MethodDelete, requestURL, nil) - if err != nil { - log.Fatalln(err) - } +// req, err := http.NewRequest(http.MethodDelete, requestURL, nil) +// if err != nil { +// log.Fatalln(err) +// } - // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set - token := grafanaClient.GrafanaAPIConfig.APIKey - bearer := "Bearer " + token - req.Header.Add("Authorization", bearer) +// // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set +// token := grafanaClient.GrafanaAPIConfig.APIKey +// bearer := "Bearer " + token +// req.Header.Add("Authorization", bearer) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return diag.FromErr(err) - } +// client := &http.Client{} +// resp, err := client.Do(req) +// if err != nil { +// return diag.FromErr(err) +// } - defer resp.Body.Close() +// defer resp.Body.Close() - d.SetId("") +// d.SetId("") - return diags -} +// return diags +// } -func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - sloID := d.Id() +// func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +// sloID := d.Id() - if d.HasChange("name") || d.HasChange("description") || d.HasChange("service") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { - sloPut := packSloResource(d) +// if d.HasChange("name") || d.HasChange("description") || d.HasChange("service") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { +// sloPut := packSloResource(d) - body, err := json.Marshal(sloPut) - if err != nil { - log.Fatalln(err) - } - bodyReader := bytes.NewReader(body) +// body, err := json.Marshal(sloPut) +// if err != nil { +// log.Fatalln(err) +// } +// bodyReader := bytes.NewReader(body) - grafanaClient := m.(*common.Client) - grafanaURL := grafanaClient.GrafanaAPIURL +// grafanaClient := m.(*common.Client) +// grafanaURL := grafanaClient.GrafanaAPIURL - sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" - requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) +// sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" +// requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) - req, err := http.NewRequest(http.MethodPut, requestURL, bodyReader) - if err != nil { - log.Fatalln(err) - } +// req, err := http.NewRequest(http.MethodPut, requestURL, bodyReader) +// if err != nil { +// log.Fatalln(err) +// } - // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set - token := grafanaClient.GrafanaAPIConfig.APIKey - bearer := "Bearer " + token - req.Header.Add("Authorization", bearer) +// // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set +// token := grafanaClient.GrafanaAPIConfig.APIKey +// bearer := "Bearer " + token +// req.Header.Add("Authorization", bearer) - client := &http.Client{} - _, err = client.Do(req) - if err != nil { - log.Fatalln(err) - } +// client := &http.Client{} +// _, err = client.Do(req) +// if err != nil { +// log.Fatalln(err) +// } - d.Set("last_updated", time.Now().Format(time.RFC850)) - } +// d.Set("last_updated", time.Now().Format(time.RFC850)) +// } - return resourceSloRead(ctx, d, m) -} +// return resourceSloRead(ctx, d, m) +// } diff --git a/slo_testing/hg/slo-resource-create.tf b/slo_testing/hg/slo-resource-create.tf index abf3f7e04..0f04b3683 100644 --- a/slo_testing/hg/slo-resource-create.tf +++ b/slo_testing/hg/slo-resource-create.tf @@ -1,122 +1,122 @@ -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} +# terraform { +# required_providers { +# grafana = { +# version = "0.2" +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } -provider "grafana" { - url = "https://elainetest.grafana.net" -} +# provider "grafana" { +# url = "https://elainetest.grafana.net" +# } -resource "grafana_slo_resource" "test1" { - name = "Hello1" - description = "Testing Hello 1 - I hope this works!" - service = "service1" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.85 - objective_window = "28d" - } - labels { - key = "name" - value = "testslolabel" - } - alerting { - name = "hihialerting1" - labels { - key = "name" - value = "testsloalertinglabel" - } +# resource "grafana_slo_resource" "test1" { +# name = "Hello1" +# description = "Testing Hello 1 - I hope this works!" +# service = "service1" +# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" +# objectives { +# objective_value = 0.85 +# objective_window = "28d" +# } +# labels { +# key = "name" +# value = "testslolabel" +# } +# alerting { +# name = "hihialerting1" +# labels { +# key = "name" +# value = "testsloalertinglabel" +# } - annotations { - key = "alertingannot1" - value = "alertingvalue1" - } +# annotations { +# key = "alertingannot1" +# value = "alertingvalue1" +# } - fastburn { - labels { - key = "labelsfastburnkey1" - value = "labelsfastburnvalue1" - } - annotations { - key = "annotsfastburnannot1" - value = "annotsfastburnvalue1" - } - } +# fastburn { +# labels { +# key = "labelsfastburnkey1" +# value = "labelsfastburnvalue1" +# } +# annotations { +# key = "annotsfastburnannot1" +# value = "annotsfastburnvalue1" +# } +# } - slowburn { - labels { - key = "labelsslowburnkey1" - value = "labelsslowburnvalue1" - } - annotations { - key = "annotsslowburnannot1" - value = "annotsslowburnvalue1" - } - } - } -} +# slowburn { +# labels { +# key = "labelsslowburnkey1" +# value = "labelsslowburnvalue1" +# } +# annotations { +# key = "annotsslowburnannot1" +# value = "annotsslowburnvalue1" +# } +# } +# } +# } -output "test1_order" { - value = grafana_slo_resource.test1 -} +# output "test1_order" { +# value = grafana_slo_resource.test1 +# } -resource "grafana_slo_resource" "test2" { - name = "Hello2" - description = "Testing Hello 2 - I hope this works!" - service = "service2" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.85 - objective_window = "28d" - } - labels { - key = "label2a" - value = "value2a" - } - labels { - key = "label3a" - value = "value3a" - } - alerting { - name = "hihialerting2" - labels { - key = "alertinglabel2" - value = "alertingvalue2" - } +# resource "grafana_slo_resource" "test2" { +# name = "Hello2" +# description = "Testing Hello 2 - I hope this works!" +# service = "service2" +# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" +# objectives { +# objective_value = 0.85 +# objective_window = "28d" +# } +# labels { +# key = "label2a" +# value = "value2a" +# } +# labels { +# key = "label3a" +# value = "value3a" +# } +# alerting { +# name = "hihialerting2" +# labels { +# key = "alertinglabel2" +# value = "alertingvalue2" +# } - annotations { - key = "alertingannot2" - value = "alertingvalue2" - } +# annotations { +# key = "alertingannot2" +# value = "alertingvalue2" +# } - fastburn { - labels { - key = "labelsfastburnkey2" - value = "labelsfastburnvalue2" - } - annotations { - key = "annotsfastburnannot2" - value = "annotsfastburnvalue2" - } - } +# fastburn { +# labels { +# key = "labelsfastburnkey2" +# value = "labelsfastburnvalue2" +# } +# annotations { +# key = "annotsfastburnannot2" +# value = "annotsfastburnvalue2" +# } +# } - slowburn { - labels { - key = "labelsslowburnkey2" - value = "labelsslowburnvalue2" - } - annotations { - key = "annotsslowburnannot2" - value = "annotsslowburnvalue2" - } - } - } -} +# slowburn { +# labels { +# key = "labelsslowburnkey2" +# value = "labelsslowburnvalue2" +# } +# annotations { +# key = "annotsslowburnannot2" +# value = "annotsslowburnvalue2" +# } +# } +# } +# } -output "test2_order" { - value = grafana_slo_resource.test2 -} \ No newline at end of file +# output "test2_order" { +# value = grafana_slo_resource.test2 +# } \ No newline at end of file diff --git a/slo_testing/hg/slo-resource-import.tf b/slo_testing/hg/slo-resource-import.tf index d9134dceb..6b2ac519a 100644 --- a/slo_testing/hg/slo-resource-import.tf +++ b/slo_testing/hg/slo-resource-import.tf @@ -1,18 +1,18 @@ -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} +# terraform { +# required_providers { +# grafana = { +# version = "0.2" +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } -provider "grafana" { - url = "https://elainetest.grafana.net" -} +# provider "grafana" { +# url = "https://elainetest.grafana.net" +# } -resource "grafana_slo_resource" "sample" {} +# resource "grafana_slo_resource" "sample" {} -output "sample_slo" { - value = grafana_slo_resource.sample -} +# output "sample_slo" { +# value = grafana_slo_resource.sample +# } diff --git a/slo_testing/hg/slo-resource-update.tf b/slo_testing/hg/slo-resource-update.tf index 26b12eb4e..14858ec25 100644 --- a/slo_testing/hg/slo-resource-update.tf +++ b/slo_testing/hg/slo-resource-update.tf @@ -1,69 +1,69 @@ -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} +# terraform { +# required_providers { +# grafana = { +# version = "0.2" +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } -provider "grafana" { - url = "https://elainetest.grafana.net" -} +# provider "grafana" { +# url = "https://elainetest.grafana.net" +# } -resource "grafana_slo_resource" "test1" { - name = "Hello1" - description = "Testing Hello 1 - I hope this works!" - service = "service5" - query = "sum(rate(apiserver_request_total{code!=\"300\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.95 - objective_window = "28d" - } - labels { - key = "label1a" - value = "value1a" - } - labels { - key = "label2a" - value = "value2a" - } - alerting { - name = "hihialerting1" - labels { - key = "alertinglabel1" - value = "alertingvalue1" - } +# resource "grafana_slo_resource" "test1" { +# name = "Hello1" +# description = "Testing Hello 1 - I hope this works!" +# service = "service5" +# query = "sum(rate(apiserver_request_total{code!=\"300\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" +# objectives { +# objective_value = 0.95 +# objective_window = "28d" +# } +# labels { +# key = "label1a" +# value = "value1a" +# } +# labels { +# key = "label2a" +# value = "value2a" +# } +# alerting { +# name = "hihialerting1" +# labels { +# key = "alertinglabel1" +# value = "alertingvalue1" +# } - annotations { - key = "alertingannot1" - value = "alertingvalue1" - } +# annotations { +# key = "alertingannot1" +# value = "alertingvalue1" +# } - fastburn { - labels { - key = "labelsfastburnkey1" - value = "labelsfastburnvalue1" - } - annotations { - key = "annotsfastburnannot1" - value = "annotsfastburnvalue1" - } - } +# fastburn { +# labels { +# key = "labelsfastburnkey1" +# value = "labelsfastburnvalue1" +# } +# annotations { +# key = "annotsfastburnannot1" +# value = "annotsfastburnvalue1" +# } +# } - slowburn { - labels { - key = "labelsslowburnkey1" - value = "labelsslowburnvalue1" - } - annotations { - key = "annotsslowburnannot2" - value = "annotsslowburnvalue1" - } - } - } -} +# slowburn { +# labels { +# key = "labelsslowburnkey1" +# value = "labelsslowburnvalue1" +# } +# annotations { +# key = "annotsslowburnannot2" +# value = "annotsslowburnvalue1" +# } +# } +# } +# } -output "test1_order" { - value = grafana_slo_resource.test1 -} \ No newline at end of file +# output "test1_order" { +# value = grafana_slo_resource.test1 +# } \ No newline at end of file From ec907b3e66127d9fc3363c3e305648ff29867646 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Tue, 18 Apr 2023 12:04:46 -0400 Subject: [PATCH 14/51] Modify Grafana Client and Terraform Provider for the GetSLO and CreateSLO functionality for the ResourceSLOCreate and ResourceSLORead Methods --- internal/provider/provider.go | 2 +- internal/resources/slo/resource_slo.go | 903 +++++++++++------------ slo_testing/hg/slo-datasource-read-hg.tf | 32 +- slo_testing/hg/slo-resource-create.tf | 218 +++--- 4 files changed, 536 insertions(+), 619 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index a55d0d3e4..b17bec359 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -80,7 +80,7 @@ func Provider(version string) func() *schema.Provider { "grafana_machine_learning_outlier_detector": machinelearning.ResourceOutlierDetector(), // SLO - // "grafana_slo_resource": slo.ResourceSlo(), + "grafana_slo_resource": slo.ResourceSlo(), }) // Resources that require the Synthetic Monitoring client to exist. diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index d86f5060d..7fc2b1925 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -1,528 +1,459 @@ package slo -// import ( -// "bytes" -// "context" -// "encoding/json" -// "fmt" -// "io" -// "log" -// "net/http" -// "time" - -// "github.com/grafana/terraform-provider-grafana/internal/common" -// "github.com/hashicorp/terraform-plugin-sdk/v2/diag" -// "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -// ) - -// func ResourceSlo() *schema.Resource { -// return &schema.Resource{ -// CreateContext: resourceSloCreate, -// ReadContext: resourceSloRead, -// UpdateContext: resourceSloUpdate, -// DeleteContext: resourceSloDelete, -// Importer: &schema.ResourceImporter{ -// StateContext: schema.ImportStatePassthroughContext, -// }, -// Schema: map[string]*schema.Schema{ -// "name": &schema.Schema{ -// Type: schema.TypeString, -// Required: true, -// }, -// "description": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// "service": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// "query": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// "labels": &schema.Schema{ -// Type: schema.TypeList, -// Optional: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "key": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// "value": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// }, -// }, -// }, -// "objectives": &schema.Schema{ -// Type: schema.TypeList, -// MaxItems: 1, -// Required: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "objective_value": &schema.Schema{ -// Type: schema.TypeFloat, -// Optional: true, -// }, -// "objective_window": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// }, -// }, -// }, -// "dashboard_ref": { -// Type: schema.TypeMap, -// Optional: true, -// Elem: &schema.Schema{ -// Type: schema.TypeString, -// }, -// }, -// "alerting": &schema.Schema{ -// Type: schema.TypeList, -// Optional: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "name": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// "labels": &schema.Schema{ -// Type: schema.TypeList, -// Optional: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "key": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// "value": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// }, -// }, -// }, -// "annotations": &schema.Schema{ -// Type: schema.TypeList, -// Optional: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "key": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// "value": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// }, -// }, -// }, -// "fastburn": &schema.Schema{ -// Type: schema.TypeList, -// Optional: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "labels": &schema.Schema{ -// Type: schema.TypeList, -// Optional: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "key": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// "value": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// }, -// }, -// }, -// "annotations": &schema.Schema{ -// Type: schema.TypeList, -// Optional: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "key": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// "value": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// "slowburn": &schema.Schema{ -// Type: schema.TypeList, -// Optional: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "labels": &schema.Schema{ -// Type: schema.TypeList, -// Optional: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "key": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// "value": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// }, -// }, -// }, -// "annotations": &schema.Schema{ -// Type: schema.TypeList, -// Optional: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "key": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// "value": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// "last_updated": &schema.Schema{ -// Type: schema.TypeString, -// Optional: true, -// Computed: true, -// }, -// }, -// } -// } - -// // SLO Resource is defined by the user within the Terraform State file -// // When 'terraform apply' is executed, it sends a POST Request and converts -// // the data within the Terraform State into a JSON Object which is then sent to the API -// // Following this, a READ is executed for the newly created SLO, which is then displayed within the -// // terminal that Terraform is running in -// func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { -// var diags diag.Diagnostics - -// sloPost := packSloResource(d) -// body, err := json.Marshal(sloPost) -// if err != nil { -// log.Fatalln(err) -// } -// bodyReader := bytes.NewReader(body) - -// grafanaClient := m.(*common.Client) -// grafanaURL := grafanaClient.GrafanaAPIURL - -// sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo" -// requestURL := fmt.Sprintf("%s%s", grafanaURL, sloPath) - -// req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader) -// if err != nil { -// log.Fatalln(err) -// } - -// // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set -// token := grafanaClient.GrafanaAPIConfig.APIKey -// bearer := "Bearer " + token -// req.Header.Add("Authorization", bearer) - -// client := &http.Client{} -// resp, err := client.Do(req) -// if err != nil { -// log.Fatalln(err) -// } - -// b, err := io.ReadAll(resp.Body) -// if err != nil { -// log.Fatalln(err) -// } - -// var response POSTResponse - -// err = json.Unmarshal(b, &response) -// if err != nil { -// fmt.Println("error:", err) -// } - -// // Get the response back from the API, we need to set the ID of the Terraform Resource -// d.SetId(response.Uuid) - -// // Executes a READ, displays the newly created SLO Resource within Terraform -// resourceSloRead(ctx, d, m) - -// return diags -// } - -// // Fetches all the Properties defined on the Terraform SLO State Object and converts it -// // to a Slo so that it can be converted to JSON and sent to the API -// func packSloResource(d *schema.ResourceData) Slo { -// tfname := d.Get("name").(string) -// tfdescription := d.Get("description").(string) -// tfservice := d.Get("service").(string) -// query := d.Get("query").(string) -// tfquery := packQuery(query) - -// // Assumes that each SLO only has one Objective Value and one Objective Window -// objectives := d.Get("objectives").([]interface{}) -// objective := objectives[0].(map[string]interface{}) -// tfobjective := packObjective(objective) - -// labels := d.Get("labels").([]interface{}) -// tflabels := packLabels(labels) - -// alerting := d.Get("alerting").([]interface{}) -// alert := alerting[0].(map[string]interface{}) -// tfalerting := packAlerting(alert) - -// sloPost := Slo{ -// Uuid: d.Id(), -// Name: tfname, -// Description: tfdescription, -// Service: tfservice, -// Objectives: tfobjective, -// Query: tfquery, -// Alerting: &tfalerting, -// Labels: &tflabels, -// } - -// return sloPost -// } - -// func packQuery(query string) Query { -// sloQuery := Query{ -// FreeformQuery: FreeformQuery{ -// Query: query, -// }, -// } - -// return sloQuery -// } - -// func packObjective(tfobjective map[string]interface{}) []Objective { -// objective := Objective{ -// Value: tfobjective["objective_value"].(float64), -// Window: tfobjective["objective_window"].(string), -// } - -// objectiveSlice := []Objective{} -// objectiveSlice = append(objectiveSlice, objective) - -// return objectiveSlice -// } +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "time" + + gapi "github.com/grafana/grafana-api-golang-client" + "github.com/grafana/terraform-provider-grafana/internal/common" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func ResourceSlo() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceSloCreate, + ReadContext: resourceSloRead, + UpdateContext: resourceSloUpdate, + DeleteContext: resourceSloDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "service": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "query": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "labels": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "objectives": &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "objective_value": &schema.Schema{ + Type: schema.TypeFloat, + Optional: true, + }, + "objective_window": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "dashboard_ref": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "alerting": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "labels": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "annotations": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "fastburn": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "labels": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "annotations": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "slowburn": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "labels": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "annotations": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "last_updated": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +// SLO Resource is defined by the user within the Terraform State file +// When 'terraform apply' is executed, it sends a POST Request and converts +// the data within the Terraform State into a JSON Object which is then sent to the API +// Following this, a READ is executed for the newly created SLO, which is then displayed within the +// terminal that Terraform is running in +func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + sloPost := packSloResource(d) + + grafanaClient := m.(*common.Client) + response, _ := grafanaClient.GrafanaAPI.CreateSLO(sloPost) + + // Get the response back from the API, we need to set the ID of the Terraform Resource + d.SetId(response.Uuid) + + // Executes a READ, displays the newly created SLO Resource within Terraform + resourceSloRead(ctx, d, m) + + return diags +} + +// resourceSloRead - sends a GET Request to the single SLO Endpoint +func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + sloId := d.Id() + + grafanaClient := m.(*common.Client) + slo, _ := grafanaClient.GrafanaAPI.GetSlo(sloId) + + setTerraformState(d, slo) + + return diags +} + +// Fetches all the Properties defined on the Terraform SLO State Object and converts it +// to a Slo so that it can be converted to JSON and sent to the API +func packSloResource(d *schema.ResourceData) gapi.Slo { + tfname := d.Get("name").(string) + tfdescription := d.Get("description").(string) + tfservice := d.Get("service").(string) + query := d.Get("query").(string) + tfquery := packQuery(query) + + // Assumes that each SLO only has one Objective Value and one Objective Window + objectives := d.Get("objectives").([]interface{}) + objective := objectives[0].(map[string]interface{}) + tfobjective := packObjective(objective) + + labels := d.Get("labels").([]interface{}) + tflabels := packLabels(labels) + + alerting := d.Get("alerting").([]interface{}) + alert := alerting[0].(map[string]interface{}) + tfalerting := packAlerting(alert) + + sloPost := gapi.Slo{ + Uuid: d.Id(), + Name: tfname, + Description: tfdescription, + Service: tfservice, + Objectives: tfobjective, + Query: tfquery, + Alerting: &tfalerting, + Labels: &tflabels, + } + + return sloPost +} + +func packQuery(query string) gapi.Query { + sloQuery := gapi.Query{ + FreeformQuery: gapi.FreeformQuery{ + Query: query, + }, + } + + return sloQuery +} + +func packObjective(tfobjective map[string]interface{}) []gapi.Objective { + objective := gapi.Objective{ + Value: tfobjective["objective_value"].(float64), + Window: tfobjective["objective_window"].(string), + } + + objectiveSlice := []gapi.Objective{} + objectiveSlice = append(objectiveSlice, objective) + + return objectiveSlice +} -// func packLabels(tfLabels []interface{}) []Label { -// labelSlice := []Label{} +func packLabels(tfLabels []interface{}) []gapi.Label { + labelSlice := []gapi.Label{} -// for ind := range tfLabels { -// currLabel := tfLabels[ind].(map[string]interface{}) -// curr := Label{ -// Key: currLabel["key"].(string), -// Value: currLabel["value"].(string), -// } - -// labelSlice = append(labelSlice, curr) - -// } + for ind := range tfLabels { + currLabel := tfLabels[ind].(map[string]interface{}) + curr := gapi.Label{ + Key: currLabel["key"].(string), + Value: currLabel["value"].(string), + } -// return labelSlice -// } + labelSlice = append(labelSlice, curr) -// func packAlerting(tfAlerting map[string]interface{}) Alerting { -// annots := tfAlerting["annotations"].([]interface{}) -// tfAnnots := packLabels(annots) + } -// labels := tfAlerting["labels"].([]interface{}) -// tfLabels := packLabels(labels) + return labelSlice +} -// fastBurn := tfAlerting["fastburn"].([]interface{}) -// tfFastBurn := packAlertMetadata(fastBurn) +func packAlerting(tfAlerting map[string]interface{}) gapi.Alerting { + annots := tfAlerting["annotations"].([]interface{}) + tfAnnots := packLabels(annots) -// slowBurn := tfAlerting["slowburn"].([]interface{}) -// tfSlowBurn := packAlertMetadata(slowBurn) + labels := tfAlerting["labels"].([]interface{}) + tfLabels := packLabels(labels) -// alerting := Alerting{ -// Name: tfAlerting["name"].(string), -// Annotations: &tfAnnots, -// Labels: &tfLabels, -// FastBurn: &tfFastBurn, -// SlowBurn: &tfSlowBurn, -// } + fastBurn := tfAlerting["fastburn"].([]interface{}) + tfFastBurn := packAlertMetadata(fastBurn) -// return alerting -// } + slowBurn := tfAlerting["slowburn"].([]interface{}) + tfSlowBurn := packAlertMetadata(slowBurn) -// func packAlertMetadata(metadata []interface{}) AlertMetadata { -// meta := metadata[0].(map[string]interface{}) + alerting := gapi.Alerting{ + Name: tfAlerting["name"].(string), + Annotations: &tfAnnots, + Labels: &tfLabels, + FastBurn: &tfFastBurn, + SlowBurn: &tfSlowBurn, + } -// labels := meta["labels"].([]interface{}) -// tflabels := packLabels(labels) + return alerting +} -// annots := meta["annotations"].([]interface{}) -// tfannots := packLabels(annots) +func packAlertMetadata(metadata []interface{}) gapi.AlertMetadata { + meta := metadata[0].(map[string]interface{}) + + labels := meta["labels"].([]interface{}) + tflabels := packLabels(labels) -// apiMetadata := AlertMetadata{ -// Labels: &tflabels, -// Annotations: &tfannots, -// } + annots := meta["annotations"].([]interface{}) + tfannots := packLabels(annots) -// return apiMetadata -// } + apiMetadata := gapi.AlertMetadata{ + Labels: &tflabels, + Annotations: &tfannots, + } -// // resourceSloRead - sends a GET Request to the single SLO Endpoint -// func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { -// var diags diag.Diagnostics + return apiMetadata +} -// sloID := d.Id() +func setTerraformState(d *schema.ResourceData, slo gapi.Slo) { + d.Set("name", slo.Name) + d.Set("description", slo.Description) + d.Set("service", slo.Service) + d.Set("query", unpackQuery(slo.Query)) + retLabels := unpackLabels(slo.Labels) -// grafanaClient := m.(*common.Client) -// grafanaURL := grafanaClient.GrafanaAPIURL + d.Set("labels", retLabels) -// sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" -// requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) + retDashboard := unpackDashboard(slo) + d.Set("dashboard_ref", retDashboard) -// req, err := http.NewRequest(http.MethodGet, requestURL, nil) -// if err != nil { -// log.Fatalln(err) -// } + retObjectives := unpackObjectives(slo.Objectives) + d.Set("objectives", retObjectives) -// // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set -// token := grafanaClient.GrafanaAPIConfig.APIKey -// bearer := "Bearer " + token -// req.Header.Add("Authorization", bearer) + retAlerting := unpackAlerting(slo.Alerting) + d.Set("alerting", retAlerting) +} -// client := &http.Client{} -// resp, err := client.Do(req) -// if err != nil { -// log.Fatalln(err) -// } +func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics -// defer resp.Body.Close() + sloID := d.Id() -// b, err := io.ReadAll(resp.Body) -// if err != nil { -// log.Fatalln(err) -// } + grafanaClient := m.(*common.Client) + grafanaURL := grafanaClient.GrafanaAPIURL -// var slo Slo + sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" + requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) -// err = json.Unmarshal(b, &slo) -// if err != nil { -// fmt.Println("error:", err) -// } + req, err := http.NewRequest(http.MethodDelete, requestURL, nil) + if err != nil { + log.Fatalln(err) + } -// setTerraformState(d, slo) + // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set + token := grafanaClient.GrafanaAPIConfig.APIKey + bearer := "Bearer " + token + req.Header.Add("Authorization", bearer) -// return diags -// } + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return diag.FromErr(err) + } -// func setTerraformState(d *schema.ResourceData, slo Slo) { -// d.Set("name", slo.Name) -// d.Set("description", slo.Description) -// d.Set("service", slo.Service) -// d.Set("query", unpackQuery(slo.Query)) -// retLabels := unpackLabels(slo.Labels) + defer resp.Body.Close() -// d.Set("labels", retLabels) + d.SetId("") -// retDashboard := unpackDashboard(slo) -// d.Set("dashboard_ref", retDashboard) + return diags +} -// retObjectives := unpackObjectives(slo.Objectives) -// d.Set("objectives", retObjectives) +func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sloID := d.Id() -// retAlerting := unpackAlerting(slo.Alerting) -// d.Set("alerting", retAlerting) -// } + if d.HasChange("name") || d.HasChange("description") || d.HasChange("service") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { + sloPut := packSloResource(d) -// func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { -// var diags diag.Diagnostics + body, err := json.Marshal(sloPut) + if err != nil { + log.Fatalln(err) + } + bodyReader := bytes.NewReader(body) -// sloID := d.Id() + grafanaClient := m.(*common.Client) + grafanaURL := grafanaClient.GrafanaAPIURL -// grafanaClient := m.(*common.Client) -// grafanaURL := grafanaClient.GrafanaAPIURL + sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" + requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) -// sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" -// requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) + req, err := http.NewRequest(http.MethodPut, requestURL, bodyReader) + if err != nil { + log.Fatalln(err) + } -// req, err := http.NewRequest(http.MethodDelete, requestURL, nil) -// if err != nil { -// log.Fatalln(err) -// } + // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set + token := grafanaClient.GrafanaAPIConfig.APIKey + bearer := "Bearer " + token + req.Header.Add("Authorization", bearer) -// // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set -// token := grafanaClient.GrafanaAPIConfig.APIKey -// bearer := "Bearer " + token -// req.Header.Add("Authorization", bearer) + client := &http.Client{} + _, err = client.Do(req) + if err != nil { + log.Fatalln(err) + } -// client := &http.Client{} -// resp, err := client.Do(req) -// if err != nil { -// return diag.FromErr(err) -// } + d.Set("last_updated", time.Now().Format(time.RFC850)) + } -// defer resp.Body.Close() - -// d.SetId("") - -// return diags -// } - -// func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { -// sloID := d.Id() - -// if d.HasChange("name") || d.HasChange("description") || d.HasChange("service") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { -// sloPut := packSloResource(d) - -// body, err := json.Marshal(sloPut) -// if err != nil { -// log.Fatalln(err) -// } -// bodyReader := bytes.NewReader(body) - -// grafanaClient := m.(*common.Client) -// grafanaURL := grafanaClient.GrafanaAPIURL - -// sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" -// requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) - -// req, err := http.NewRequest(http.MethodPut, requestURL, bodyReader) -// if err != nil { -// log.Fatalln(err) -// } - -// // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set -// token := grafanaClient.GrafanaAPIConfig.APIKey -// bearer := "Bearer " + token -// req.Header.Add("Authorization", bearer) - -// client := &http.Client{} -// _, err = client.Do(req) -// if err != nil { -// log.Fatalln(err) -// } - -// d.Set("last_updated", time.Now().Format(time.RFC850)) -// } - -// return resourceSloRead(ctx, d, m) -// } + return resourceSloRead(ctx, d, m) +} diff --git a/slo_testing/hg/slo-datasource-read-hg.tf b/slo_testing/hg/slo-datasource-read-hg.tf index 751d7bc88..6f5a47a46 100644 --- a/slo_testing/hg/slo-datasource-read-hg.tf +++ b/slo_testing/hg/slo-datasource-read-hg.tf @@ -1,19 +1,19 @@ -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} +# terraform { +# required_providers { +# grafana = { +# version = "0.2" +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } -provider "grafana" { - url = "https://elainetest.grafana.net" -} +# provider "grafana" { +# url = "https://elainetest.grafana.net" +# } -data "grafana_slo_datasource" "test1" { -} +# data "grafana_slo_datasource" "test1" { +# } -output "test1" { - value = data.grafana_slo_datasource.test1 -} \ No newline at end of file +# output "test1" { +# value = data.grafana_slo_datasource.test1 +# } \ No newline at end of file diff --git a/slo_testing/hg/slo-resource-create.tf b/slo_testing/hg/slo-resource-create.tf index 0f04b3683..23225b5be 100644 --- a/slo_testing/hg/slo-resource-create.tf +++ b/slo_testing/hg/slo-resource-create.tf @@ -1,122 +1,108 @@ -# terraform { -# required_providers { -# grafana = { -# version = "0.2" -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} -# provider "grafana" { -# url = "https://elainetest.grafana.net" -# } +provider "grafana" { + url = "https://elainetest.grafana.net" +} -# resource "grafana_slo_resource" "test1" { -# name = "Hello1" -# description = "Testing Hello 1 - I hope this works!" -# service = "service1" -# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" -# objectives { -# objective_value = 0.85 -# objective_window = "28d" -# } -# labels { -# key = "name" -# value = "testslolabel" -# } -# alerting { -# name = "hihialerting1" -# labels { -# key = "name" -# value = "testsloalertinglabel" -# } +resource "grafana_slo_resource" "test1" { + name = "Terraform1 - 99.5% of Responses from Kubernetes API Server Valid" + description = "Terraform1 - Measures that 99.5% of responses from the Kubernetes API Server are valid (i.e. not HTTP 500 Errors)" + service = "service1" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.995 + objective_window = "30d" + } + labels { + key = "custom" + value = "value" + } + alerting { + fastburn { + annotations { + key = "name" + value = "Critical - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 14.4x. This means that within 1 Hour, 2% of your SLO Error Budget may be consumed. Recommended action: Page" + } + labels { + key = "type" + value = "slo" + } + } -# annotations { -# key = "alertingannot1" -# value = "alertingvalue1" -# } + slowburn { + annotations { + key = "name" + value = "Warning - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 1x. This means that within 72 Hours, 10% of your SLO Error Budget may be consumed. Recommended action: Page/Ticket" + } + labels { + key = "type" + value = "slo" + } + } + } +} -# fastburn { -# labels { -# key = "labelsfastburnkey1" -# value = "labelsfastburnvalue1" -# } -# annotations { -# key = "annotsfastburnannot1" -# value = "annotsfastburnvalue1" -# } -# } +output "test2_order" { + value = grafana_slo_resource.test2 +} -# slowburn { -# labels { -# key = "labelsslowburnkey1" -# value = "labelsslowburnvalue1" -# } -# annotations { -# key = "annotsslowburnannot1" -# value = "annotsslowburnvalue1" -# } -# } -# } -# } +resource "grafana_slo_resource" "test2" { + name = "Terraform2 - 99.5% of Responses from Kubernetes API Server Valid" + description = "Terraform2 - Measures that 99.5% of responses from the Kubernetes API Server are valid (i.e. not HTTP 500 Errors)" + service = "service2" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.995 + objective_window = "30d" + } + labels { + key = "custom" + value = "value" + } + alerting { + fastburn { + annotations { + key = "name" + value = "Critical - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 14.4x. This means that within 1 Hour, 2% of your SLO Error Budget may be consumed. Recommended action: Page" + } + labels { + key = "type" + value = "slo" + } + } -# output "test1_order" { -# value = grafana_slo_resource.test1 -# } - -# resource "grafana_slo_resource" "test2" { -# name = "Hello2" -# description = "Testing Hello 2 - I hope this works!" -# service = "service2" -# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" -# objectives { -# objective_value = 0.85 -# objective_window = "28d" -# } -# labels { -# key = "label2a" -# value = "value2a" -# } -# labels { -# key = "label3a" -# value = "value3a" -# } -# alerting { -# name = "hihialerting2" -# labels { -# key = "alertinglabel2" -# value = "alertingvalue2" -# } - -# annotations { -# key = "alertingannot2" -# value = "alertingvalue2" -# } - -# fastburn { -# labels { -# key = "labelsfastburnkey2" -# value = "labelsfastburnvalue2" -# } -# annotations { -# key = "annotsfastburnannot2" -# value = "annotsfastburnvalue2" -# } -# } - -# slowburn { -# labels { -# key = "labelsslowburnkey2" -# value = "labelsslowburnvalue2" -# } -# annotations { -# key = "annotsslowburnannot2" -# value = "annotsslowburnvalue2" -# } -# } -# } -# } - -# output "test2_order" { -# value = grafana_slo_resource.test2 -# } \ No newline at end of file + slowburn { + annotations { + key = "name" + value = "Warning - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 1x. This means that within 72 Hours, 10% of your SLO Error Budget may be consumed. Recommended action: Page/Ticket" + } + labels { + key = "type" + value = "slo" + } + } + } +} \ No newline at end of file From 16e94a9a403cc218f51676e986935a075463f012 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Tue, 18 Apr 2023 14:24:42 -0400 Subject: [PATCH 15/51] Modify Grafana Client and Terraform Provider for the DeleteSLO functionality for the resourceSloDelete Methods --- internal/resources/slo/resource_slo.go | 127 +++++++++++-------------- 1 file changed, 53 insertions(+), 74 deletions(-) diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 7fc2b1925..bedfbe109 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -250,6 +250,59 @@ func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) return diags } +func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sloID := d.Id() + + if d.HasChange("name") || d.HasChange("description") || d.HasChange("service") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { + sloPut := packSloResource(d) + + body, err := json.Marshal(sloPut) + if err != nil { + log.Fatalln(err) + } + bodyReader := bytes.NewReader(body) + + grafanaClient := m.(*common.Client) + grafanaURL := grafanaClient.GrafanaAPIURL + + sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" + requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) + + req, err := http.NewRequest(http.MethodPut, requestURL, bodyReader) + if err != nil { + log.Fatalln(err) + } + + // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set + token := grafanaClient.GrafanaAPIConfig.APIKey + bearer := "Bearer " + token + req.Header.Add("Authorization", bearer) + + client := &http.Client{} + _, err = client.Do(req) + if err != nil { + log.Fatalln(err) + } + + d.Set("last_updated", time.Now().Format(time.RFC850)) + } + + return resourceSloRead(ctx, d, m) +} + +func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + sloID := d.Id() + + grafanaClient := m.(*common.Client) + grafanaClient.GrafanaAPI.DeleteSLO(sloID) + + d.SetId("") + + return diags +} + // Fetches all the Properties defined on the Terraform SLO State Object and converts it // to a Slo so that it can be converted to JSON and sent to the API func packSloResource(d *schema.ResourceData) gapi.Slo { @@ -383,77 +436,3 @@ func setTerraformState(d *schema.ResourceData, slo gapi.Slo) { retAlerting := unpackAlerting(slo.Alerting) d.Set("alerting", retAlerting) } - -func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics - - sloID := d.Id() - - grafanaClient := m.(*common.Client) - grafanaURL := grafanaClient.GrafanaAPIURL - - sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" - requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) - - req, err := http.NewRequest(http.MethodDelete, requestURL, nil) - if err != nil { - log.Fatalln(err) - } - - // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set - token := grafanaClient.GrafanaAPIConfig.APIKey - bearer := "Bearer " + token - req.Header.Add("Authorization", bearer) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return diag.FromErr(err) - } - - defer resp.Body.Close() - - d.SetId("") - - return diags -} - -func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - sloID := d.Id() - - if d.HasChange("name") || d.HasChange("description") || d.HasChange("service") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { - sloPut := packSloResource(d) - - body, err := json.Marshal(sloPut) - if err != nil { - log.Fatalln(err) - } - bodyReader := bytes.NewReader(body) - - grafanaClient := m.(*common.Client) - grafanaURL := grafanaClient.GrafanaAPIURL - - sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" - requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) - - req, err := http.NewRequest(http.MethodPut, requestURL, bodyReader) - if err != nil { - log.Fatalln(err) - } - - // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set - token := grafanaClient.GrafanaAPIConfig.APIKey - bearer := "Bearer " + token - req.Header.Add("Authorization", bearer) - - client := &http.Client{} - _, err = client.Do(req) - if err != nil { - log.Fatalln(err) - } - - d.Set("last_updated", time.Now().Format(time.RFC850)) - } - - return resourceSloRead(ctx, d, m) -} From 924fee84b188f8248cd8c3a4fa15aeac21ff4ed7 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Tue, 18 Apr 2023 14:44:34 -0400 Subject: [PATCH 16/51] Modify Grafana Client and Terraform Provider for the UpdateSLO functionality for the resourceSloUpdate method, removes the Local Dev Testing Files --- internal/resources/slo/data_source_slo.go | 2 +- internal/resources/slo/resource_slo.go | 40 +----- internal/resources/slo/types.go | 89 ------------ slo_testing/hg/hg_testing_README.md | 4 +- slo_testing/hg/slo-datasource-read-hg.tf | 32 ++--- slo_testing/hg/slo-resource-import.tf | 30 ++-- slo_testing/hg/slo-resource-update.tf | 165 +++++++++++++--------- slo_testing/local/local_testing_README.md | 93 ------------ slo_testing/local/slo-datasource-read.tf | 20 --- slo_testing/local/slo-resource-create.tf | 123 ---------------- slo_testing/local/slo-resource-import.tf | 18 --- slo_testing/local/slo-resource-update.tf | 70 --------- slo_testing/local/slo_sample.json | 26 ---- 13 files changed, 140 insertions(+), 572 deletions(-) delete mode 100644 internal/resources/slo/types.go delete mode 100644 slo_testing/local/local_testing_README.md delete mode 100644 slo_testing/local/slo-datasource-read.tf delete mode 100644 slo_testing/local/slo-resource-create.tf delete mode 100644 slo_testing/local/slo-resource-import.tf delete mode 100644 slo_testing/local/slo-resource-update.tf delete mode 100644 slo_testing/local/slo_sample.json diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index b1e213b6b..b30235297 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -215,7 +215,7 @@ func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{ var diags diag.Diagnostics grafanaClient := m.(*common.Client) - apiSlos, _ := grafanaClient.GrafanaAPI.ListSLOs() + apiSlos, _ := grafanaClient.GrafanaAPI.ListSlos() terraformSlos := []interface{}{} diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index bedfbe109..7ce2c9657 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -1,12 +1,7 @@ package slo import ( - "bytes" "context" - "encoding/json" - "fmt" - "log" - "net/http" "time" gapi "github.com/grafana/grafana-api-golang-client" @@ -222,10 +217,10 @@ func ResourceSlo() *schema.Resource { func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics - sloPost := packSloResource(d) + slo := packSloResource(d) grafanaClient := m.(*common.Client) - response, _ := grafanaClient.GrafanaAPI.CreateSLO(sloPost) + response, _ := grafanaClient.GrafanaAPI.CreateSlo(slo) // Get the response back from the API, we need to set the ID of the Terraform Resource d.SetId(response.Uuid) @@ -254,35 +249,10 @@ func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{ sloID := d.Id() if d.HasChange("name") || d.HasChange("description") || d.HasChange("service") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { - sloPut := packSloResource(d) - - body, err := json.Marshal(sloPut) - if err != nil { - log.Fatalln(err) - } - bodyReader := bytes.NewReader(body) + slo := packSloResource(d) grafanaClient := m.(*common.Client) - grafanaURL := grafanaClient.GrafanaAPIURL - - sloPath := "/api/plugins/grafana-slo-app/resources/v1/slo/" - requestURL := fmt.Sprintf("%s%s%s", grafanaURL, sloPath, sloID) - - req, err := http.NewRequest(http.MethodPut, requestURL, bodyReader) - if err != nil { - log.Fatalln(err) - } - - // If testing on Local Dev, comment out the three lines below - it does not work if the Authorization Header is set - token := grafanaClient.GrafanaAPIConfig.APIKey - bearer := "Bearer " + token - req.Header.Add("Authorization", bearer) - - client := &http.Client{} - _, err = client.Do(req) - if err != nil { - log.Fatalln(err) - } + grafanaClient.GrafanaAPI.UpdateSlo(sloID, slo) d.Set("last_updated", time.Now().Format(time.RFC850)) } @@ -296,7 +266,7 @@ func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{ sloID := d.Id() grafanaClient := m.(*common.Client) - grafanaClient.GrafanaAPI.DeleteSLO(sloID) + grafanaClient.GrafanaAPI.DeleteSlo(sloID) d.SetId("") diff --git a/internal/resources/slo/types.go b/internal/resources/slo/types.go deleted file mode 100644 index 1c811a38b..000000000 --- a/internal/resources/slo/types.go +++ /dev/null @@ -1,89 +0,0 @@ -// file to be deleted, Types will be defined within the SLO API Go Client Wrapper -package slo - -type SloList struct { - Slos []Slo `json:"slos"` -} - -type Slo struct { - Uuid string `json:"uuid"` - Name string `json:"name"` - Description string `json:"description"` - Service string `json:"service,omitempty"` - Query Query `json:"query"` - Alerting *Alerting `json:"alerting,omitempty"` - Labels *[]Label `json:"labels,omitempty"` - Objectives []Objective `json:"objectives"` - DrilldownDashboardUid string `json:"dashboardUid,omitempty"` - DrilldownDashboardRef *DashboardRef `json:"drillDownDashboardRef,omitempty"` -} - -type Alerting struct { - Name string `json:"name"` - Annotations *[]Label `json:"annotations,omitempty"` - Labels *[]Label `json:"labels,omitempty"` - FastBurn *AlertMetadata `json:"fastBurn,omitempty"` - SlowBurn *AlertMetadata `json:"slowBurn,omitempty"` -} - -type AlertMetadata struct { - Annotations *[]Label `json:"annotations,omitempty"` - Labels *[]Label `json:"labels,omitempty"` -} - -type Label struct { - Key string `json:"key"` - Value string `json:"value"` -} - -type Objective struct { - Value float64 `json:"value"` - Window string `json:"window"` -} - -type DashboardRef struct { - ID int `json:"id,omitempty"` - UID string `json:"uid,omitempty"` -} - -type FreeformQuery struct { - Query string `json:"freeformQuery,omitempty"` -} - -type ThresholdQuery struct { - ThresholdMetric *MetricDef `json:"thresholdMetric,omitempty"` -} - -type RatioQuery struct { - SuccessMetric *MetricDef `json:"successMetric,omitempty"` - TotalMetric *MetricDef `json:"totalMetric,omitempty"` -} - -type PercentileQuery struct { - HistogramMetric *MetricDef `json:"histogramMetric,omitempty"` - Percentile float64 `json:"percentile,omitempty"` -} - -type Threshold struct { - Value float64 `json:"value,omitempty"` - Operator string `json:"operator,omitempty"` -} - -type MetricDef struct { - PrometheusMetric string `json:"prometheusMetric,omitempty"` - Type string `json:"type,omitempty"` -} - -type Query struct { - ThresholdQuery - RatioQuery - PercentileQuery - FreeformQuery - Threshold *Threshold `json:"threshold,omitempty"` - GroupByLabels []string `json:"groupBy,omitempty"` -} - -type POSTResponse struct { - Message string `json:"message,omitempty"` - Uuid string `json:"uuid,omitempty"` -} diff --git a/slo_testing/hg/hg_testing_README.md b/slo_testing/hg/hg_testing_README.md index 3777c2595..bed931d88 100644 --- a/slo_testing/hg/hg_testing_README.md +++ b/slo_testing/hg/hg_testing_README.md @@ -89,6 +89,4 @@ After creating the two SLO resources from the CREATE Method, we will DELETE them 8. To verify that this resource is now under Terraform control, within the `slo-resource-import.tf` file, comment out lines 14-18. Then, within the CLI run `terraform destroy`. This should destroy the resource from within the Terraform CLI. ### TBD ### -2. Figure out the Bug after Creating Terraform Resources (cannot go and Edit a SLO - why?). -2. Integrate into the existing Grafana Golang Client (https://github.com/grafana/grafana-api-golang-client) -3. Tests. \ No newline at end of file +1. Tests. \ No newline at end of file diff --git a/slo_testing/hg/slo-datasource-read-hg.tf b/slo_testing/hg/slo-datasource-read-hg.tf index 6f5a47a46..751d7bc88 100644 --- a/slo_testing/hg/slo-datasource-read-hg.tf +++ b/slo_testing/hg/slo-datasource-read-hg.tf @@ -1,19 +1,19 @@ -# terraform { -# required_providers { -# grafana = { -# version = "0.2" -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} -# provider "grafana" { -# url = "https://elainetest.grafana.net" -# } +provider "grafana" { + url = "https://elainetest.grafana.net" +} -# data "grafana_slo_datasource" "test1" { -# } +data "grafana_slo_datasource" "test1" { +} -# output "test1" { -# value = data.grafana_slo_datasource.test1 -# } \ No newline at end of file +output "test1" { + value = data.grafana_slo_datasource.test1 +} \ No newline at end of file diff --git a/slo_testing/hg/slo-resource-import.tf b/slo_testing/hg/slo-resource-import.tf index 6b2ac519a..d9134dceb 100644 --- a/slo_testing/hg/slo-resource-import.tf +++ b/slo_testing/hg/slo-resource-import.tf @@ -1,18 +1,18 @@ -# terraform { -# required_providers { -# grafana = { -# version = "0.2" -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} -# provider "grafana" { -# url = "https://elainetest.grafana.net" -# } +provider "grafana" { + url = "https://elainetest.grafana.net" +} -# resource "grafana_slo_resource" "sample" {} +resource "grafana_slo_resource" "sample" {} -# output "sample_slo" { -# value = grafana_slo_resource.sample -# } +output "sample_slo" { + value = grafana_slo_resource.sample +} diff --git a/slo_testing/hg/slo-resource-update.tf b/slo_testing/hg/slo-resource-update.tf index 14858ec25..f53662cfc 100644 --- a/slo_testing/hg/slo-resource-update.tf +++ b/slo_testing/hg/slo-resource-update.tf @@ -1,69 +1,108 @@ -# terraform { -# required_providers { -# grafana = { -# version = "0.2" -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } +terraform { + required_providers { + grafana = { + version = "0.2" + source = "registry.terraform.io/grafana/grafana" + } + } +} -# provider "grafana" { -# url = "https://elainetest.grafana.net" -# } +provider "grafana" { + url = "https://elainetest.grafana.net" +} -# resource "grafana_slo_resource" "test1" { -# name = "Hello1" -# description = "Testing Hello 1 - I hope this works!" -# service = "service5" -# query = "sum(rate(apiserver_request_total{code!=\"300\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" -# objectives { -# objective_value = 0.95 -# objective_window = "28d" -# } -# labels { -# key = "label1a" -# value = "value1a" -# } -# labels { -# key = "label2a" -# value = "value2a" -# } -# alerting { -# name = "hihialerting1" -# labels { -# key = "alertinglabel1" -# value = "alertingvalue1" -# } +resource "grafana_slo_resource" "test1" { + name = "Terraform1 - 99.5% of Responses from Kubernetes API Server Valido" + description = "Terraform1 - Measures that 99.5% of responses from the Kubernetes API Server are valid (i.e. not HTTP 500 Errors)" + service = "service1" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.995 + objective_window = "30d" + } + labels { + key = "custom" + value = "value" + } + alerting { + fastburn { + annotations { + key = "name" + value = "Critical - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 14.4x. This means that within 1 Hour, 2% of your SLO Error Budget may be consumed. Recommended action: Page" + } + labels { + key = "type" + value = "slo" + } + } -# annotations { -# key = "alertingannot1" -# value = "alertingvalue1" -# } + slowburn { + annotations { + key = "name" + value = "Warning - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 1x. This means that within 72 Hours, 10% of your SLO Error Budget may be consumed. Recommended action: Page/Ticket" + } + labels { + key = "type" + value = "slo" + } + } + } +} -# fastburn { -# labels { -# key = "labelsfastburnkey1" -# value = "labelsfastburnvalue1" -# } -# annotations { -# key = "annotsfastburnannot1" -# value = "annotsfastburnvalue1" -# } -# } +output "test2_order" { + value = grafana_slo_resource.test2 +} -# slowburn { -# labels { -# key = "labelsslowburnkey1" -# value = "labelsslowburnvalue1" -# } -# annotations { -# key = "annotsslowburnannot2" -# value = "annotsslowburnvalue1" -# } -# } -# } -# } +resource "grafana_slo_resource" "test2" { + name = "Terraform2 - 99.5% of Responses from Kubernetes API Server Valid123" + description = "Terraform2 - Measures that 99.5% of responses from the Kubernetes API Server are valid (i.e. not HTTP 500 Errors)" + service = "service2" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.995 + objective_window = "30d" + } + labels { + key = "custom" + value = "value" + } + alerting { + fastburn { + annotations { + key = "name" + value = "Critical - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 14.4x. This means that within 1 Hour, 2% of your SLO Error Budget may be consumed. Recommended action: Page" + } + labels { + key = "type" + value = "slo" + } + } -# output "test1_order" { -# value = grafana_slo_resource.test1 -# } \ No newline at end of file + slowburn { + annotations { + key = "name" + value = "Warning - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 1x. This means that within 72 Hours, 10% of your SLO Error Budget may be consumed. Recommended action: Page/Ticket" + } + labels { + key = "type" + value = "slo" + } + } + } +} \ No newline at end of file diff --git a/slo_testing/local/local_testing_README.md b/slo_testing/local/local_testing_README.md deleted file mode 100644 index 8c6916c92..000000000 --- a/slo_testing/local/local_testing_README.md +++ /dev/null @@ -1,93 +0,0 @@ -# How to Test the SLO Terraform Provider - Locally - -## Set Up Local Environment -1. Start up the Local SLO Dev Environment which should be available at http://localhost:3000/a/grafana-slo-app/home - -## Understanding Terraform Provider Code Flow -1. Within the terraform root directory, run `make install`. This command creates a binary of the Terraform Provider and moves it into the appropriate Terraform plugin directory, which allows for testing of a custom provider. - - * Note: you may need to modify the `OS_ARCH=darwin_arm64` property within the Makefile to match your operating system - -2. Within `provider.go`, we create two resources - a `grafana_slo_datasource` and a `grafana_slo_resource`. - -### Types of Resources -Datasource - datasources are resources that are external to Terraform (i.e. not managed by Terraform state). When interacting with a Datasource, they can be used to READ information, and datasources can also be imported (i.e. converted) into Resources, which allows Terraform state to control them. - -Resources - these are resources that can be managed by Terraform state. This means that you CREATE, READ, UPDATE, DELETE. If an IMPORT method is defined, you can also convert Datasources into Resources (i.e. this means that if you import a resource created by the UI (i.e. a Datasource) it can be converted into a Resrouce, which can be managed by Terraform). - -## Testing Datasource - READ -`internal/resources/slo/data_source_slo.go` - -This file defines a schema, that matches the response shape of a GET request to the SLO Endpoint. -``` -{ - "slos": [ - { - "uuid": "bik1rpvkvbzxnfkutzmkh", - "name": "test1", - ... - }, - { - "uuid": "94pqcghz92hybc3iwircy", - "name": "test2", - ... - }, - ] -} -``` - -Objective - we want to send a GET Request to the SLO Endpoint that returns a list of all SLOs, and we want to be able to READ that information and output it to the Terraform CLI. - -1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Within the terraform-provider-grafana root directory, run `make install`. -2. Create a SLO / Send a POST Request to the endpoint (see `slo_sample.json` for an example) -3. Change to the `slo_testing/local` directory. -4. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-datasource-read.tf` file -5. Within `data_source_slo.go` - you MUST comment out L233-235 - for some reason, there is a bug that does not work if the Authorization Header is set. -6. Within the `slo_testing/local` directory, run the commands `terraform init` and `terraform apply`. Ensure to remove the `.terraform.lock.hcl` and any `terraform.tfstate` files. -7. You should see a list of all SLOs within the terminal that you've run terraform from. - -Elaine Questions - I'm open to modifying the shape of the Schema returned by Terraform on the read, so any thoughts here are welcome! Right now - I've just mirrored the structure we get back from the API. - -## Testing SLO Resource - -### Testing the CREATE Method -Objective - we want to be able to define a SLO Resource within Terraform that should be created. Once the resource has successfully been created, we want to display the newly created SLO resource within the Terraform interface. - -The `slo-resource-create.tf` file will create two SLOs. - -1. Within `resource_slo.go` - you MUST comment out Lines 244-246, Lines 408-410, Line 473-475, and Lines 514-516, there is a bug that does not work if the Authorization Header is set. -2. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Within the terraform-provider-grafana root directory, run `make install`. -3. Change to the `slo_testing/local` directory. -4. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-create.tf` file -5. Within the `slo_testing` directory, run the commands `terraform init` and `terraform apply`. Ensure to remove the `.terraform.lock.hcl` and any `terraform.tfstate` files. -6. Within your terminal, you should see the output of the two newly created SLO from within Terraform, and two new SLOs within the SLO UI. - -### Testing the UPDATE Method -1. Within `resource_slo.go` - you MUST comment out Lines 244-246, Lines 408-410, Line 473-475, and Lines 514-516, there is a bug that does not work if the Authorization Header is set. -2. Within the terraform-provider-grafana root directory, run `make install`. -3. Change to the `slo_testing/local` directory. -4. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-update.tf` file -5. Run the command `terraform init` -6. Run the command `terraform apply`. This creates the resource specified below. -7. To ensure that the PUT endpoint works, modify any of the values within the resource below, and re-run `terraform apply`. - -### Testing the DELETE Method -Objective - we want to be able to delete a SLO Resource that was created with Terraform. -After creating the two SLO resources from the CREATE Method, we will DELETE them. - -Testing the DELETE Method / terraform destroy -1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files -2. Within the `slo_testing` directory, run the commands `terraform init`. Keep the `slo-resource-create.tf` file open, and execute `terraform apply`. This creates two new SLOs from the Terraform CLI. -3. Create a regular SLO using the UI. At this point, you should have 3 SLOs - 2 created from Terraform, and 1 created from the UI -4. To delete all Terraformed SLO resources, execute the command `terraform destroy`, and type `yes` in the terminal to confirm the delete -5. The two newly created Terraformed SLO Resources should be deleted, and you should still have the SLO that was created through the UI remaining. - -### Testing the IMPORT Method -1. Within the terraform-provider-grafana root directory, run `make install`. -2. Change to the `slo_testing/local` directory. -3. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-import.tf` file -4. Create a SLO using the UI or Postman. Take note of the SLO's UUID -5. Execute the command `terraform init` -6. Within the Terraform CLI directly, type in the command: `terraform import grafana_slo_resource.sample slo_UUID` -7. Now execute the command: `terraform state show grafana_slo_resource.sample` - you should see the data from the imported Resource. -8. To verify that this resource is now under Terraform control, within the `slo-resource-import.tf` file, comment out lines 14-18. Then, within the CLI run `terraform destroy`. This should destroy the resource from within the Terraform CLI. \ No newline at end of file diff --git a/slo_testing/local/slo-datasource-read.tf b/slo_testing/local/slo-datasource-read.tf deleted file mode 100644 index 4f7f422fb..000000000 --- a/slo_testing/local/slo-datasource-read.tf +++ /dev/null @@ -1,20 +0,0 @@ -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} - -provider "grafana" { - url = "http://localhost:3000" - auth = "auth" -} - -data "grafana_slo_datasource" "test1" { -} - -output "test1" { - value = data.grafana_slo_datasource.test1 -} \ No newline at end of file diff --git a/slo_testing/local/slo-resource-create.tf b/slo_testing/local/slo-resource-create.tf deleted file mode 100644 index 0bd13ed02..000000000 --- a/slo_testing/local/slo-resource-create.tf +++ /dev/null @@ -1,123 +0,0 @@ -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} - -provider "grafana" { - url = "http://localhost:3000" - auth = "auth" -} - -resource "grafana_slo_resource" "test1" { - name = "Hello1" - description = "Testing Hello 1 - I hope this works!" - service = "service1" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.85 - objective_window = "28d" - } - labels { - key = "name" - value = "testslolabel" - } - alerting { - name = "hihialerting1" - labels { - key = "name" - value = "testsloalertinglabel" - } - - annotations { - key = "alertingannot1" - value = "alertingvalue1" - } - - fastburn { - labels { - key = "labelsfastburnkey1" - value = "labelsfastburnvalue1" - } - annotations { - key = "annotsfastburnannot1" - value = "annotsfastburnvalue1" - } - } - - slowburn { - labels { - key = "labelsslowburnkey1" - value = "labelsslowburnvalue1" - } - annotations { - key = "annotsslowburnannot1" - value = "annotsslowburnvalue1" - } - } - } -} - -output "test1_order" { - value = grafana_slo_resource.test1 -} - -resource "grafana_slo_resource" "test2" { - name = "Hello2" - description = "Testing Hello 2 - I hope this works!" - service = "service2" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.85 - objective_window = "28d" - } - labels { - key = "label2a" - value = "value2a" - } - labels { - key = "label3a" - value = "value3a" - } - alerting { - name = "hihialerting2" - labels { - key = "alertinglabel2" - value = "alertingvalue2" - } - - annotations { - key = "alertingannot2" - value = "alertingvalue2" - } - - fastburn { - labels { - key = "labelsfastburnkey2" - value = "labelsfastburnvalue2" - } - annotations { - key = "annotsfastburnannot2" - value = "annotsfastburnvalue2" - } - } - - slowburn { - labels { - key = "labelsslowburnkey2" - value = "labelsslowburnvalue2" - } - annotations { - key = "annotsslowburnannot2" - value = "annotsslowburnvalue2" - } - } - } -} - -output "test2_order" { - value = grafana_slo_resource.test2 -} \ No newline at end of file diff --git a/slo_testing/local/slo-resource-import.tf b/slo_testing/local/slo-resource-import.tf deleted file mode 100644 index 0d8829018..000000000 --- a/slo_testing/local/slo-resource-import.tf +++ /dev/null @@ -1,18 +0,0 @@ -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} - -provider "grafana" { - url = "http://localhost:3000/api/plugins/grafana-slo-app/resources/v1/slo" -} - -resource "grafana_slo_resource" "sample" {} - -output "sample_slo" { - value = grafana_slo_resource.sample -} diff --git a/slo_testing/local/slo-resource-update.tf b/slo_testing/local/slo-resource-update.tf deleted file mode 100644 index f4499acba..000000000 --- a/slo_testing/local/slo-resource-update.tf +++ /dev/null @@ -1,70 +0,0 @@ -terraform { - required_providers { - grafana = { - version = "0.2" - source = "registry.terraform.io/grafana/grafana" - } - } -} - -provider "grafana" { - url = "http://localhost:3000" - auth = "auth" -} - -resource "grafana_slo_resource" "test1" { - name = "Hello1" - description = "Testing Hello 1 - I hope this works!" - service = "service1" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.85 - objective_window = "28d" - } - labels { - key = "label1a" - value = "value1a" - } - labels { - key = "label2a" - value = "value2a" - } - alerting { - name = "hihialerting1" - labels { - key = "alertinglabel1" - value = "alertingvalue1" - } - - annotations { - key = "alertingannot1" - value = "alertingvalue1" - } - - fastburn { - labels { - key = "labelsfastburnkey1" - value = "labelsfastburnvalue1" - } - annotations { - key = "annotsfastburnannot1" - value = "annotsfastburnvalue1" - } - } - - slowburn { - labels { - key = "labelsslowburnkey1" - value = "labelsslowburnvalue1" - } - annotations { - key = "annotsslowburnannot1" - value = "annotsslowburnvalue1" - } - } - } -} - -output "test1_order" { - value = grafana_slo_resource.test1 -} \ No newline at end of file diff --git a/slo_testing/local/slo_sample.json b/slo_testing/local/slo_sample.json deleted file mode 100644 index 319a7b4bc..000000000 --- a/slo_testing/local/slo_sample.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name":"test name", - "description":"test description", - "service":"service", - "labels": [{"key": "name", "value": "testslo"}], - "objectives":[ - { - "value":0.995, - "window":"30d" - } - ], - "query":{ - "freeFormQuery":"sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - }, - "alerting":{ - "labels": [{"key": "name", "value": "testslo"}], - "fastBurn":{ - "annotations":[{ "key": "name", "value": "Custom Fast-burn Name"}, { "key": "description", "value": "Custom Fast-burn Desc"}], - "labels":[{"key":"type", "value":"slo"}] - }, - "slowBurn":{ - "annotations":[{ "key": "name", "value": "Custom Slow-burn Name"}, { "key": "description", "value": "Custom Slow-burn Desc"}], - "labels":[{"key":"type", "value":"slo"}] - } - } - } \ No newline at end of file From 98c6ec3ee0569d39ee914dc2949adeb97501f150 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Tue, 18 Apr 2023 18:26:55 -0400 Subject: [PATCH 17/51] Testing - Checking Initial Resource Attributes and Attribute Sets --- GNUmakefile | 62 +++++++++++++++++++++ Makefile | 37 ------------ examples/resources/grafana_slo/resource.tf | 49 ++++++++++++++++ internal/resources/slo/data_source_slo.go | 28 +++++----- internal/resources/slo/resource_slo.go | 21 +++---- internal/resources/slo/resource_slo_test.go | 36 ++++++++++++ 6 files changed, 171 insertions(+), 62 deletions(-) create mode 100644 GNUmakefile delete mode 100644 Makefile create mode 100644 examples/resources/grafana_slo/resource.tf create mode 100644 internal/resources/slo/resource_slo_test.go diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 000000000..43f1b3635 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,62 @@ +GRAFANA_VERSION ?= 9.4.3 + +testacc: + TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m + +# Test OSS features +testacc-oss: + TF_ACC_OSS=true make testacc + +# Test Enterprise features +testacc-enterprise: + TF_ACC_ENTERPRISE=true make testacc + +# Test Cloud API features +testacc-cloud-api: + TF_ACC_CLOUD_API=true make testacc + +# Test Cloud instance features (ex: Machine Learning and Synthetic Monitoring) +testacc-cloud-instance: + TF_ACC_CLOUD_INSTANCE=true make testacc + +testacc-docker: + make -C testdata generate + docker-compose -f ./docker-compose.yml stop + GRAFANA_VERSION=$(GRAFANA_VERSION) \ + docker-compose \ + -f ./docker-compose.yml \ + run --rm -e TESTARGS="$(TESTARGS)" \ + grafana-provider \ + make testacc-oss + +testacc-docker-tls: + make -C testdata generate + docker-compose -f ./docker-compose.yml -f ./docker-compose.tls.yml stop + GRAFANA_VERSION=$(GRAFANA_VERSION) \ + docker-compose \ + -f ./docker-compose.yml \ + -f ./docker-compose.tls.yml \ + run --rm -e TESTARGS="$(TESTARGS)" \ + grafana-provider \ + make testacc-oss + +release: + @test $${RELEASE_VERSION?Please set environment variable RELEASE_VERSION} + @git tag $$RELEASE_VERSION + @git push origin $$RELEASE_VERSION + +DRONE_DOCKER := docker run --rm -e DRONE_SERVER -e DRONE_TOKEN -v ${PWD}:${PWD} -w "${PWD}" drone/cli:1.6.1 +drone: + $(DRONE_DOCKER) jsonnet --stream --source .drone/drone.jsonnet --target .drone/drone.yml --format + $(DRONE_DOCKER) lint .drone/drone.yml + $(DRONE_DOCKER) sign --save grafana/terraform-provider-grafana .drone/drone.yml + +golangci-lint: + docker run \ + --rm \ + --volume "$(shell pwd):/src" \ + --workdir "/src" \ + golangci/golangci-lint:v1.49 golangci-lint run ./... + +linkcheck: + docker run -it --entrypoint sh -v "$$PWD:$$PWD" -w "$$PWD" python:3.9-alpine -c "pip3 install linkchecker && linkchecker --config .linkcheckerrc docs" \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 5cb8be006..000000000 --- a/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -TEST?=$$(go list ./... | grep -v 'vendor') -HOSTNAME=registry.terraform.io -NAMESPACE=grafana -NAME=grafana -BINARY=terraform-provider-${NAME} -VERSION=0.2 -OS_ARCH=darwin_arm64 - -default: install - -build: - go build -o ${BINARY} - -release: - GOOS=darwin GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_darwin_amd64 - GOOS=freebsd GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_freebsd_386 - GOOS=freebsd GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_freebsd_amd64 - GOOS=freebsd GOARCH=arm go build -o ./bin/${BINARY}_${VERSION}_freebsd_arm - GOOS=linux GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_linux_386 - GOOS=linux GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_linux_amd64 - GOOS=linux GOARCH=arm go build -o ./bin/${BINARY}_${VERSION}_linux_arm - GOOS=openbsd GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_openbsd_386 - GOOS=openbsd GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_openbsd_amd64 - GOOS=solaris GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_solaris_amd64 - GOOS=windows GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_windows_386 - GOOS=windows GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_windows_amd64 - -install: build - mkdir -p ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} - mv ${BINARY} ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} - -test: - go test -i $(TEST) || exit 1 - echo $(TEST) | xargs -t -n4 go test $(TESTARGS) -timeout=30s -parallel=4 - -testacc: - TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 120m \ No newline at end of file diff --git a/examples/resources/grafana_slo/resource.tf b/examples/resources/grafana_slo/resource.tf new file mode 100644 index 000000000..372383056 --- /dev/null +++ b/examples/resources/grafana_slo/resource.tf @@ -0,0 +1,49 @@ +resource "grafana_slo_resource" "test" { + name = "Terraform Testing" + description = "Terraform Description" + service = "serviceA" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.995 + objective_window = "30d" + } + labels { + key = "custom" + value = "value" + } + alerting { + fastburn { + annotations { + key = "name" + value = "Critical - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 14.4x." + } + labels { + key = "type" + value = "slo" + } + } + + slowburn { + annotations { + key = "name" + value = "Warning - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 1x." + } + labels { + key = "type" + value = "slo" + } + } + } +} + +# output "foo" { +# value = grafana_slo_resource.test +# } \ No newline at end of file diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index b30235297..192e6663e 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -2,7 +2,6 @@ package slo import ( "context" - "strconv" gapi "github.com/grafana/grafana-api-golang-client" "github.com/grafana/terraform-provider-grafana/internal/common" @@ -12,6 +11,10 @@ import ( func DatasourceSlo() *schema.Resource { return &schema.Resource{ + Description: ` + * [Official documentation](https://grafana.com/docs/grafana-cloud/slo/) + * [API documentation](https://grafana.com/docs/grafana-cloud/slo/api/) + `, ReadContext: datasourceSloRead, Schema: map[string]*schema.Schema{ "slos": &schema.Schema{ @@ -71,12 +74,9 @@ func DatasourceSlo() *schema.Resource { }, }, }, - "dashboard_ref": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, + "dashboard_uid": { + Type: schema.TypeString, + Computed: true, }, "alerting": &schema.Schema{ Type: schema.TypeList, @@ -243,7 +243,7 @@ func convertDatasourceSlo(slo gapi.Slo) map[string]interface{} { ret["labels"] = retLabels retDashboard := unpackDashboard(slo) - ret["dashboard_ref"] = retDashboard + ret["dashboard_uid"] = retDashboard retObjectives := unpackObjectives(slo.Objectives) ret["objectives"] = retObjectives @@ -262,7 +262,6 @@ func unpackQuery(query gapi.Query) string { } return "Query Type Not Implemented" - } func unpackObjectives(objectives []gapi.Objective) []map[string]interface{} { @@ -294,19 +293,18 @@ func unpackLabels(labels *[]gapi.Label) []map[string]interface{} { return nil } -func unpackDashboard(slo gapi.Slo) map[string]interface{} { - retDashboard := make(map[string]interface{}) +func unpackDashboard(slo gapi.Slo) string { + var dashboard string if slo.DrilldownDashboardRef != nil { - retDashboard["dashboard_id"] = strconv.Itoa(slo.DrilldownDashboardRef.ID) - retDashboard["dashboard_uid"] = slo.DrilldownDashboardRef.UID + dashboard = slo.DrilldownDashboardRef.UID } if slo.DrilldownDashboardUid != "" { - retDashboard["dashboard_uid"] = slo.DrilldownDashboardUid + dashboard = slo.DrilldownDashboardUid } - return retDashboard + return dashboard } func unpackAlerting(AlertData *gapi.Alerting) []map[string]interface{} { diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 7ce2c9657..6ca029620 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -12,6 +12,10 @@ import ( func ResourceSlo() *schema.Resource { return &schema.Resource{ + Description: ` + * [Official documentation](https://grafana.com/docs/grafana-cloud/slo/) + * [API documentation](https://grafana.com/docs/grafana-cloud/slo/api/) + `, CreateContext: resourceSloCreate, ReadContext: resourceSloRead, UpdateContext: resourceSloUpdate, @@ -34,7 +38,7 @@ func ResourceSlo() *schema.Resource { }, "query": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, "labels": &schema.Schema{ Type: schema.TypeList, @@ -69,12 +73,9 @@ func ResourceSlo() *schema.Resource { }, }, }, - "dashboard_ref": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, + "dashboard_uid": { + Type: schema.TypeString, + Computed: true, }, "alerting": &schema.Schema{ Type: schema.TypeList, @@ -294,7 +295,7 @@ func packSloResource(d *schema.ResourceData) gapi.Slo { alert := alerting[0].(map[string]interface{}) tfalerting := packAlerting(alert) - sloPost := gapi.Slo{ + slo := gapi.Slo{ Uuid: d.Id(), Name: tfname, Description: tfdescription, @@ -305,7 +306,7 @@ func packSloResource(d *schema.ResourceData) gapi.Slo { Labels: &tflabels, } - return sloPost + return slo } func packQuery(query string) gapi.Query { @@ -398,7 +399,7 @@ func setTerraformState(d *schema.ResourceData, slo gapi.Slo) { d.Set("labels", retLabels) retDashboard := unpackDashboard(slo) - d.Set("dashboard_ref", retDashboard) + d.Set("dashboard_uid", retDashboard) retObjectives := unpackObjectives(slo.Objectives) d.Set("objectives", retObjectives) diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go new file mode 100644 index 000000000..129e0d76e --- /dev/null +++ b/internal/resources/slo/resource_slo_test.go @@ -0,0 +1,36 @@ +package slo_test + +import ( + "testing" + + "github.com/grafana/terraform-provider-grafana/internal/testutils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccResourceSlo(t *testing.T) { + testutils.CheckCloudInstanceTestsEnabled(t) + + // var job mlapi.Job - TBD + resource.ParallelTest(t, resource.TestCase{ + ProviderFactories: testutils.ProviderFactories, + // CheckDestroy: testAccMLJobCheckDestroy(&job), - TBD + Steps: []resource.TestStep{ + { + Config: testutils.TestAccExample(t, "resources/grafana_slo/resource.tf"), + Check: resource.ComposeTestCheckFunc( + // testAccMLJobCheckExists("grafana_machine_learning_job.test_job", &job), - TBD + resource.TestCheckResourceAttrSet("grafana_slo_resource.test", "id"), + resource.TestCheckResourceAttrSet("grafana_slo_resource.test", "dashboard_uid"), + resource.TestCheckResourceAttr("grafana_slo_resource.test", "name", "Terraform Testing"), + resource.TestCheckResourceAttr("grafana_slo_resource.test", "description", "Terraform Description"), + resource.TestCheckResourceAttr("grafana_slo_resource.test", "service", "serviceA"), + resource.TestCheckResourceAttr("grafana_slo_resource.test", "query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), + resource.TestCheckResourceAttr("grafana_slo_resource.test", "objectives.0.objective_value", "0.995"), + resource.TestCheckResourceAttr("grafana_slo_resource.test", "objectives.0.objective_window", "30d"), + resource.TestCheckResourceAttr("grafana_slo_resource.test", "labels.0.key", "custom"), + resource.TestCheckResourceAttr("grafana_slo_resource.test", "labels.0.value", "value"), + ), + }, + }, + }) +} From 1c4e53a87603086b4ecd91749d57b999b236ebe0 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 19 Apr 2023 12:00:35 -0400 Subject: [PATCH 18/51] Updates READMEs for testing --- slo_testing/hg/hg_testing_README.md | 27 ++++++++++++++++++------ slo_testing/hg/slo-datasource-read-hg.tf | 1 - slo_testing/hg/slo-resource-create.tf | 1 - slo_testing/hg/slo-resource-import.tf | 1 - slo_testing/hg/slo-resource-update.tf | 1 - 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/slo_testing/hg/hg_testing_README.md b/slo_testing/hg/hg_testing_README.md index bed931d88..3279b37a5 100644 --- a/slo_testing/hg/hg_testing_README.md +++ b/slo_testing/hg/hg_testing_README.md @@ -2,14 +2,27 @@ ## Create your HG Account and Get the SLO Plugin Deployed Generate a new Service Account Token, and set the environment variable GRAFANA_AUTH to the value of your token (or you can specify the `auth` field within the Terraform State file). +`export GRAFANA_AUTH= Within the `.tf` files within `slo_testing/hg`, ensure that you set the `url` field to be the `url` of your HG Instance. ## Understanding Terraform Provider Code Flow -1. Within the terraform root directory, run `make install`. This command creates a binary of the Terraform Provider and moves it into the appropriate Terraform plugin directory, which allows for testing of a custom provider. +1. Within the terraform provider root directory, run `go build`. This creates a binary of the terraform-provider-grafana. +2. Within the terraform provider root directory, create a file called `.terraformrc` with the following contents. This ensures that it will use the local binary version of the terraform-provider-grafana. +``` +provider_installation { + dev_overrides { + "grafana/grafana" = "/Users/elainevuong/go/src/github.com/grafana/terraform-provider-grafana" // use the path specifies for where you built the binary for the terraform-provider-grafana + } + # For all other providers, install them directly from their origin provider + # registries as normal. If you omit this, Terraform will _only_ use + # the dev_overrides block, and so no other providers will be available. + direct {} +} +``` - * Note: you may need to modify the `OS_ARCH=darwin_arm64` property within the Makefile to match your operating system +3. Using the Grafana HTTP Golang API Client - within the `go.mod` file of the `terraform-provider-grafana`, insert the following line (point it to the path of your gapi client, which should be from this branch https://github.com/grafana/grafana-api-golang-client/tree/elainevuong/slo) +`replace github.com/grafana/grafana-api-golang-client => /Users/elainevuong/go/src/github.com/grafana/grafana-api-golang-client` -2. Within `provider.go`, we create two resources - a `grafana_slo_datasource` and a `grafana_slo_resource`. ### Types of Resources Datasource - datasources are resources that are external to Terraform (i.e. not managed by Terraform state). When interacting with a Datasource, they can be used to READ information, and datasources can also be imported (i.e. converted) into Resources, which allows Terraform state to control them. @@ -17,8 +30,6 @@ Datasource - datasources are resources that are external to Terraform (i.e. not Resources - these are resources that can be managed by Terraform state. This means that you CREATE, READ, UPDATE, DELETE. If an IMPORT method is defined, you can also convert Datasources into Resources (i.e. this means that if you import a resource created by the UI (i.e. a Datasource) it can be converted into a Resrouce, which can be managed by Terraform). ## Testing Datasource - READ -`internal/resources/slo/data_source_slo.go` - This file defines a schema, that matches the response shape of a GET request to the SLO Endpoint. ``` { @@ -84,9 +95,11 @@ After creating the two SLO resources from the CREATE Method, we will DELETE them 3. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-import.tf` file 4. Create a SLO using the UI or Postman. Take note of the SLO's UUID 5. Execute the command `terraform init` -6. Within the Terraform CLI directly, type in the command: `terraform import grafana_slo_resource.sample slo_UUID` +6. Within the Terraform CLI directly, execute the command: `terraform import grafana_slo_resource.sample slo_UUID` 7. Now execute the command: `terraform state show grafana_slo_resource.sample` - you should see the data from the imported Resource. 8. To verify that this resource is now under Terraform control, within the `slo-resource-import.tf` file, comment out lines 14-18. Then, within the CLI run `terraform destroy`. This should destroy the resource from within the Terraform CLI. ### TBD ### -1. Tests. \ No newline at end of file +1. Testing. +2. Remove `slo_testing` folder +3. Documentation. \ No newline at end of file diff --git a/slo_testing/hg/slo-datasource-read-hg.tf b/slo_testing/hg/slo-datasource-read-hg.tf index 751d7bc88..f3a7e7c50 100644 --- a/slo_testing/hg/slo-datasource-read-hg.tf +++ b/slo_testing/hg/slo-datasource-read-hg.tf @@ -1,7 +1,6 @@ terraform { required_providers { grafana = { - version = "0.2" source = "registry.terraform.io/grafana/grafana" } } diff --git a/slo_testing/hg/slo-resource-create.tf b/slo_testing/hg/slo-resource-create.tf index 23225b5be..d9fb6c701 100644 --- a/slo_testing/hg/slo-resource-create.tf +++ b/slo_testing/hg/slo-resource-create.tf @@ -1,7 +1,6 @@ terraform { required_providers { grafana = { - version = "0.2" source = "registry.terraform.io/grafana/grafana" } } diff --git a/slo_testing/hg/slo-resource-import.tf b/slo_testing/hg/slo-resource-import.tf index d9134dceb..77f2ba466 100644 --- a/slo_testing/hg/slo-resource-import.tf +++ b/slo_testing/hg/slo-resource-import.tf @@ -1,7 +1,6 @@ terraform { required_providers { grafana = { - version = "0.2" source = "registry.terraform.io/grafana/grafana" } } diff --git a/slo_testing/hg/slo-resource-update.tf b/slo_testing/hg/slo-resource-update.tf index f53662cfc..f073b7c49 100644 --- a/slo_testing/hg/slo-resource-update.tf +++ b/slo_testing/hg/slo-resource-update.tf @@ -1,7 +1,6 @@ terraform { required_providers { grafana = { - version = "0.2" source = "registry.terraform.io/grafana/grafana" } } From 067d2b9a4c8d295aa1aac1c984b535ce628312e9 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 19 Apr 2023 18:24:42 -0400 Subject: [PATCH 19/51] Updated to replace the specific branch on the Grafana API Golang Client --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bdeeac505..2ac3908da 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/grafana/terraform-provider-grafana go 1.18 -replace github.com/grafana/grafana-api-golang-client => /Users/elainevuong/go/src/github.com/grafana/grafana-api-golang-client +replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.19.1-0.20230418184243-bc47d1e58e1c require ( github.com/Masterminds/semver/v3 v3.2.0 diff --git a/go.sum b/go.sum index 6debd0870..d8cfd855a 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/amixr-api-go-client v0.0.7 h1:U6W6yKxMMybI+Qz4zl+Vih48o6CczLaU/vjk2m7omvU= github.com/grafana/amixr-api-go-client v0.0.7/go.mod h1:N6x26XUrM5zGtK5zL5vNJnAn2JFMxLFPPLTw/6pDkFE= -github.com/grafana/grafana-api-golang-client v0.18.4 h1:YpxmbG1OWssE1ko80Ixd/XioSrIqY/ZpwQ4h1D0xJ2E= -github.com/grafana/grafana-api-golang-client v0.18.4/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= +github.com/grafana/grafana-api-golang-client v0.19.1-0.20230418184243-bc47d1e58e1c h1:DLVdfveoop5Oz6sUXbFr1xu5VTssNOLelEPjmcH3/y0= +github.com/grafana/grafana-api-golang-client v0.19.1-0.20230418184243-bc47d1e58e1c/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= github.com/grafana/machine-learning-go-client v0.4.0 h1:UAkJPE7xujzFTm0d9ctbX/FsCID8rqejWjnkRPGNM6E= github.com/grafana/machine-learning-go-client v0.4.0/go.mod h1:QFfZz8NkqVF8++skjkKQXJEZfpCYd8S0yTWJUpsLLTA= github.com/grafana/synthetic-monitoring-agent v0.14.3 h1:M7HZmTJKJLF53Fv7KZeb2KyzHSx7gbHiJ3Mr/ApfCRA= From aa6c7c93582182fe6b8d52a7ccb5b9481cc4dcd4 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 19 Apr 2023 20:16:12 -0400 Subject: [PATCH 20/51] Adding Testing Functions to check for Slo existence and Slo destroy --- internal/resources/slo/data_source_slo.go | 4 +- internal/resources/slo/resource_slo.go | 12 +++--- internal/resources/slo/resource_slo_test.go | 47 +++++++++++++++++++-- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 192e6663e..b3f928572 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -214,8 +214,8 @@ func DatasourceSlo() *schema.Resource { func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics - grafanaClient := m.(*common.Client) - apiSlos, _ := grafanaClient.GrafanaAPI.ListSlos() + client := m.(*common.Client).GrafanaAPI + apiSlos, _ := client.ListSlos() terraformSlos := []interface{}{} diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 6ca029620..26f3bb5b5 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -220,8 +220,8 @@ func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{ slo := packSloResource(d) - grafanaClient := m.(*common.Client) - response, _ := grafanaClient.GrafanaAPI.CreateSlo(slo) + client := m.(*common.Client).GrafanaAPI + response, _ := client.CreateSlo(slo) // Get the response back from the API, we need to set the ID of the Terraform Resource d.SetId(response.Uuid) @@ -238,8 +238,8 @@ func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) sloId := d.Id() - grafanaClient := m.(*common.Client) - slo, _ := grafanaClient.GrafanaAPI.GetSlo(sloId) + client := m.(*common.Client).GrafanaAPI + slo, _ := client.GetSlo(sloId) setTerraformState(d, slo) @@ -266,8 +266,8 @@ func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{ sloID := d.Id() - grafanaClient := m.(*common.Client) - grafanaClient.GrafanaAPI.DeleteSlo(sloID) + client := m.(*common.Client).GrafanaAPI + client.DeleteSlo(sloID) d.SetId("") diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index 129e0d76e..1b211bf1e 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -1,24 +1,28 @@ package slo_test import ( + "fmt" "testing" + gapi "github.com/grafana/grafana-api-golang-client" + "github.com/grafana/terraform-provider-grafana/internal/common" "github.com/grafana/terraform-provider-grafana/internal/testutils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccResourceSlo(t *testing.T) { testutils.CheckCloudInstanceTestsEnabled(t) - // var job mlapi.Job - TBD + var slo gapi.Slo resource.ParallelTest(t, resource.TestCase{ ProviderFactories: testutils.ProviderFactories, - // CheckDestroy: testAccMLJobCheckDestroy(&job), - TBD + CheckDestroy: testAccSloCheckDestroy(&slo), Steps: []resource.TestStep{ { Config: testutils.TestAccExample(t, "resources/grafana_slo/resource.tf"), Check: resource.ComposeTestCheckFunc( - // testAccMLJobCheckExists("grafana_machine_learning_job.test_job", &job), - TBD + testAccSloCheckExists("grafana_slo_resource.test", &slo), resource.TestCheckResourceAttrSet("grafana_slo_resource.test", "id"), resource.TestCheckResourceAttrSet("grafana_slo_resource.test", "dashboard_uid"), resource.TestCheckResourceAttr("grafana_slo_resource.test", "name", "Terraform Testing"), @@ -34,3 +38,40 @@ func TestAccResourceSlo(t *testing.T) { }, }) } + +func testAccSloCheckExists(rn string, slo *gapi.Slo) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[rn] + if !ok { + return fmt.Errorf("resource not found: %s\n %#v", rn, s.RootModule().Resources) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("resource id not set") + } + + client := testutils.Provider.Meta().(*common.Client).GrafanaAPI + gotSlo, err := client.GetSlo(rs.Primary.ID) + + if err != nil { + return fmt.Errorf("error getting SLO: %s", err) + } + + *slo = gotSlo + + return nil + } +} + +func testAccSloCheckDestroy(slo *gapi.Slo) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testutils.Provider.Meta().(*common.Client).GrafanaAPI + err := client.DeleteSlo(slo.Uuid) + + if err == nil { + return fmt.Errorf("SLO with a UUID %s still exists after destroy", slo.Uuid) + } + + return nil + } +} From 10cd20c4bbe8be61939228a5426c6357d968eeca Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Thu, 20 Apr 2023 16:34:29 -0400 Subject: [PATCH 21/51] Updated the Grafana API Wrapper Update Method on UpdateSlo --- go.mod | 2 +- go.sum | 4 +- internal/resources/slo/data_source_slo.go | 34 +++----------- internal/resources/slo/resource_slo.go | 26 +++-------- slo_testing/hg/slo-resource-create.tf | 55 ++--------------------- 5 files changed, 20 insertions(+), 101 deletions(-) diff --git a/go.mod b/go.mod index 2ac3908da..99c19be0c 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/grafana/terraform-provider-grafana go 1.18 -replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.19.1-0.20230418184243-bc47d1e58e1c +replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420202946-3c57a88508a6 require ( github.com/Masterminds/semver/v3 v3.2.0 diff --git a/go.sum b/go.sum index d8cfd855a..83f9316b7 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/amixr-api-go-client v0.0.7 h1:U6W6yKxMMybI+Qz4zl+Vih48o6CczLaU/vjk2m7omvU= github.com/grafana/amixr-api-go-client v0.0.7/go.mod h1:N6x26XUrM5zGtK5zL5vNJnAn2JFMxLFPPLTw/6pDkFE= -github.com/grafana/grafana-api-golang-client v0.19.1-0.20230418184243-bc47d1e58e1c h1:DLVdfveoop5Oz6sUXbFr1xu5VTssNOLelEPjmcH3/y0= -github.com/grafana/grafana-api-golang-client v0.19.1-0.20230418184243-bc47d1e58e1c/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= +github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420202946-3c57a88508a6 h1:GkODrD17JNdcvQX+8zob14e5FVed+rxJKp2bUFW95MQ= +github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420202946-3c57a88508a6/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= github.com/grafana/machine-learning-go-client v0.4.0 h1:UAkJPE7xujzFTm0d9ctbX/FsCID8rqejWjnkRPGNM6E= github.com/grafana/machine-learning-go-client v0.4.0/go.mod h1:QFfZz8NkqVF8++skjkKQXJEZfpCYd8S0yTWJUpsLLTA= github.com/grafana/synthetic-monitoring-agent v0.14.3 h1:M7HZmTJKJLF53Fv7KZeb2KyzHSx7gbHiJ3Mr/ApfCRA= diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index b3f928572..b613a4c96 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -11,11 +11,10 @@ import ( func DatasourceSlo() *schema.Resource { return &schema.Resource{ - Description: ` - * [Official documentation](https://grafana.com/docs/grafana-cloud/slo/) - * [API documentation](https://grafana.com/docs/grafana-cloud/slo/api/) - `, ReadContext: datasourceSloRead, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, Schema: map[string]*schema.Schema{ "slos": &schema.Schema{ Type: schema.TypeList, @@ -50,10 +49,6 @@ func DatasourceSlo() *schema.Resource { }, }, }, - "service": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, "query": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -74,9 +69,9 @@ func DatasourceSlo() *schema.Resource { }, }, }, - "dashboard_uid": { + "dashboard_uid": &schema.Schema{ Type: schema.TypeString, - Computed: true, + Optional: true, }, "alerting": &schema.Schema{ Type: schema.TypeList, @@ -236,15 +231,12 @@ func convertDatasourceSlo(slo gapi.Slo) map[string]interface{} { ret["uuid"] = slo.Uuid ret["name"] = slo.Name ret["description"] = slo.Description - ret["service"] = slo.Service + ret["dashboard_uid"] = slo.DrilldownDashboardRef.UID ret["query"] = unpackQuery(slo.Query) retLabels := unpackLabels(slo.Labels) ret["labels"] = retLabels - retDashboard := unpackDashboard(slo) - ret["dashboard_uid"] = retDashboard - retObjectives := unpackObjectives(slo.Objectives) ret["objectives"] = retObjectives @@ -293,20 +285,6 @@ func unpackLabels(labels *[]gapi.Label) []map[string]interface{} { return nil } -func unpackDashboard(slo gapi.Slo) string { - var dashboard string - - if slo.DrilldownDashboardRef != nil { - dashboard = slo.DrilldownDashboardRef.UID - } - - if slo.DrilldownDashboardUid != "" { - dashboard = slo.DrilldownDashboardUid - } - - return dashboard -} - func unpackAlerting(AlertData *gapi.Alerting) []map[string]interface{} { retAlertData := []map[string]interface{}{} diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 26f3bb5b5..f730bd680 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -20,9 +20,6 @@ func ResourceSlo() *schema.Resource { ReadContext: resourceSloRead, UpdateContext: resourceSloUpdate, DeleteContext: resourceSloDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, Schema: map[string]*schema.Schema{ "name": &schema.Schema{ Type: schema.TypeString, @@ -32,13 +29,9 @@ func ResourceSlo() *schema.Resource { Type: schema.TypeString, Optional: true, }, - "service": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, "query": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, }, "labels": &schema.Schema{ Type: schema.TypeList, @@ -73,7 +66,7 @@ func ResourceSlo() *schema.Resource { }, }, }, - "dashboard_uid": { + "dashboard_uid": &schema.Schema{ Type: schema.TypeString, Computed: true, }, @@ -249,11 +242,11 @@ func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { sloID := d.Id() - if d.HasChange("name") || d.HasChange("description") || d.HasChange("service") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { + if d.HasChange("name") || d.HasChange("description") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { slo := packSloResource(d) grafanaClient := m.(*common.Client) - grafanaClient.GrafanaAPI.UpdateSlo(sloID, slo) + grafanaClient.GrafanaAPI.UpdateSlo(string(sloID), slo) d.Set("last_updated", time.Now().Format(time.RFC850)) } @@ -279,7 +272,6 @@ func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{ func packSloResource(d *schema.ResourceData) gapi.Slo { tfname := d.Get("name").(string) tfdescription := d.Get("description").(string) - tfservice := d.Get("service").(string) query := d.Get("query").(string) tfquery := packQuery(query) @@ -296,10 +288,9 @@ func packSloResource(d *schema.ResourceData) gapi.Slo { tfalerting := packAlerting(alert) slo := gapi.Slo{ - Uuid: d.Id(), + Uuid: string(d.Id()), Name: tfname, Description: tfdescription, - Service: tfservice, Objectives: tfobjective, Query: tfquery, Alerting: &tfalerting, @@ -392,15 +383,12 @@ func packAlertMetadata(metadata []interface{}) gapi.AlertMetadata { func setTerraformState(d *schema.ResourceData, slo gapi.Slo) { d.Set("name", slo.Name) d.Set("description", slo.Description) - d.Set("service", slo.Service) + d.Set("dashboard_uid", slo.DrilldownDashboardRef.UID) d.Set("query", unpackQuery(slo.Query)) - retLabels := unpackLabels(slo.Labels) + retLabels := unpackLabels(slo.Labels) d.Set("labels", retLabels) - retDashboard := unpackDashboard(slo) - d.Set("dashboard_uid", retDashboard) - retObjectives := unpackObjectives(slo.Objectives) d.Set("objectives", retObjectives) diff --git a/slo_testing/hg/slo-resource-create.tf b/slo_testing/hg/slo-resource-create.tf index d9fb6c701..5735713ad 100644 --- a/slo_testing/hg/slo-resource-create.tf +++ b/slo_testing/hg/slo-resource-create.tf @@ -11,9 +11,8 @@ provider "grafana" { } resource "grafana_slo_resource" "test1" { - name = "Terraform1 - 99.5% of Responses from Kubernetes API Server Valid" - description = "Terraform1 - Measures that 99.5% of responses from the Kubernetes API Server are valid (i.e. not HTTP 500 Errors)" - service = "service1" + name = "Terraform - 99.5% of Responses from Kubernetes API Server Valid" + description = "Terraform - Measures that 99.5% of responses from the Kubernetes API Server are valid (i.e. not HTTP 500 Errors)" query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" objectives { objective_value = 0.995 @@ -56,52 +55,6 @@ resource "grafana_slo_resource" "test1" { } } -output "test2_order" { - value = grafana_slo_resource.test2 +output "test1" { + value = grafana_slo_resource.test1 } - -resource "grafana_slo_resource" "test2" { - name = "Terraform2 - 99.5% of Responses from Kubernetes API Server Valid" - description = "Terraform2 - Measures that 99.5% of responses from the Kubernetes API Server are valid (i.e. not HTTP 500 Errors)" - service = "service2" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.995 - objective_window = "30d" - } - labels { - key = "custom" - value = "value" - } - alerting { - fastburn { - annotations { - key = "name" - value = "Critical - SLO Burn Rate Alert" - } - annotations { - key = "description" - value = "Error Budget is burning at a rate greater than 14.4x. This means that within 1 Hour, 2% of your SLO Error Budget may be consumed. Recommended action: Page" - } - labels { - key = "type" - value = "slo" - } - } - - slowburn { - annotations { - key = "name" - value = "Warning - SLO Burn Rate Alert" - } - annotations { - key = "description" - value = "Error Budget is burning at a rate greater than 1x. This means that within 72 Hours, 10% of your SLO Error Budget may be consumed. Recommended action: Page/Ticket" - } - labels { - key = "type" - value = "slo" - } - } - } -} \ No newline at end of file From 8159de5821e65d74d366008dbcd250948af4fa43 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Thu, 20 Apr 2023 16:43:54 -0400 Subject: [PATCH 22/51] Modify UUID Naming in GAPI --- go.mod | 2 +- go.sum | 4 ++-- internal/resources/slo/data_source_slo.go | 4 ++-- internal/resources/slo/resource_slo.go | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 99c19be0c..b113de30a 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/grafana/terraform-provider-grafana go 1.18 -replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420202946-3c57a88508a6 +replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420203910-394bebdb48f2 require ( github.com/Masterminds/semver/v3 v3.2.0 diff --git a/go.sum b/go.sum index 83f9316b7..3dd9b8e87 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/amixr-api-go-client v0.0.7 h1:U6W6yKxMMybI+Qz4zl+Vih48o6CczLaU/vjk2m7omvU= github.com/grafana/amixr-api-go-client v0.0.7/go.mod h1:N6x26XUrM5zGtK5zL5vNJnAn2JFMxLFPPLTw/6pDkFE= -github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420202946-3c57a88508a6 h1:GkODrD17JNdcvQX+8zob14e5FVed+rxJKp2bUFW95MQ= -github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420202946-3c57a88508a6/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= +github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420203910-394bebdb48f2 h1:IjcPtFLweryQevD3G8nsBi7SzxkodzO0fLuDFpv/umU= +github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420203910-394bebdb48f2/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= github.com/grafana/machine-learning-go-client v0.4.0 h1:UAkJPE7xujzFTm0d9ctbX/FsCID8rqejWjnkRPGNM6E= github.com/grafana/machine-learning-go-client v0.4.0/go.mod h1:QFfZz8NkqVF8++skjkKQXJEZfpCYd8S0yTWJUpsLLTA= github.com/grafana/synthetic-monitoring-agent v0.14.3 h1:M7HZmTJKJLF53Fv7KZeb2KyzHSx7gbHiJ3Mr/ApfCRA= diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index b613a4c96..ee6a34ff1 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -220,7 +220,7 @@ func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{ } d.Set("slos", terraformSlos) - d.SetId(apiSlos.Slos[0].Uuid) + d.SetId(apiSlos.Slos[0].UUID) return diags } @@ -228,7 +228,7 @@ func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{ func convertDatasourceSlo(slo gapi.Slo) map[string]interface{} { ret := make(map[string]interface{}) - ret["uuid"] = slo.Uuid + ret["uuid"] = slo.UUID ret["name"] = slo.Name ret["description"] = slo.Description ret["dashboard_uid"] = slo.DrilldownDashboardRef.UID diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index f730bd680..4dc068818 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -217,7 +217,7 @@ func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{ response, _ := client.CreateSlo(slo) // Get the response back from the API, we need to set the ID of the Terraform Resource - d.SetId(response.Uuid) + d.SetId(response.UUID) // Executes a READ, displays the newly created SLO Resource within Terraform resourceSloRead(ctx, d, m) @@ -288,7 +288,7 @@ func packSloResource(d *schema.ResourceData) gapi.Slo { tfalerting := packAlerting(alert) slo := gapi.Slo{ - Uuid: string(d.Id()), + UUID: d.Id(), Name: tfname, Description: tfdescription, Objectives: tfobjective, From 3fa6c780c1626d89d5d702bb07abb81c11c0d445 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Thu, 20 Apr 2023 16:55:30 -0400 Subject: [PATCH 23/51] Update Linting Comments --- go.mod | 2 +- go.sum | 4 ++-- internal/resources/slo/data_source_slo.go | 23 ++++++++++----------- internal/resources/slo/resource_slo.go | 9 ++++---- internal/resources/slo/resource_slo_test.go | 4 ++-- 5 files changed, 20 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index b113de30a..3bab13572 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/grafana/terraform-provider-grafana go 1.18 -replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420203910-394bebdb48f2 +replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420205450-747f0274255c require ( github.com/Masterminds/semver/v3 v3.2.0 diff --git a/go.sum b/go.sum index 3dd9b8e87..b4c735e72 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/amixr-api-go-client v0.0.7 h1:U6W6yKxMMybI+Qz4zl+Vih48o6CczLaU/vjk2m7omvU= github.com/grafana/amixr-api-go-client v0.0.7/go.mod h1:N6x26XUrM5zGtK5zL5vNJnAn2JFMxLFPPLTw/6pDkFE= -github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420203910-394bebdb48f2 h1:IjcPtFLweryQevD3G8nsBi7SzxkodzO0fLuDFpv/umU= -github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420203910-394bebdb48f2/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= +github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420205450-747f0274255c h1:ojSxDoFLNdtmNF9O/tD6tadn5qjJ8YzSgtjFImkABsY= +github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420205450-747f0274255c/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= github.com/grafana/machine-learning-go-client v0.4.0 h1:UAkJPE7xujzFTm0d9ctbX/FsCID8rqejWjnkRPGNM6E= github.com/grafana/machine-learning-go-client v0.4.0/go.mod h1:QFfZz8NkqVF8++skjkKQXJEZfpCYd8S0yTWJUpsLLTA= github.com/grafana/synthetic-monitoring-agent v0.14.3 h1:M7HZmTJKJLF53Fv7KZeb2KyzHSx7gbHiJ3Mr/ApfCRA= diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index ee6a34ff1..179ce31e3 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -244,7 +244,6 @@ func convertDatasourceSlo(slo gapi.Slo) map[string]interface{} { ret["alerting"] = retAlerting return ret - } // TBD for Other Query Types Once Implemented @@ -285,31 +284,31 @@ func unpackLabels(labels *[]gapi.Label) []map[string]interface{} { return nil } -func unpackAlerting(AlertData *gapi.Alerting) []map[string]interface{} { +func unpackAlerting(alertData *gapi.Alerting) []map[string]interface{} { retAlertData := []map[string]interface{}{} alertObject := make(map[string]interface{}) - alertObject["name"] = AlertData.Name - alertObject["labels"] = unpackLabels(AlertData.Labels) - alertObject["annotations"] = unpackLabels(AlertData.Annotations) - alertObject["fastburn"] = unpackAlertingMetadata(*AlertData.FastBurn) - alertObject["slowburn"] = unpackAlertingMetadata(*AlertData.SlowBurn) + alertObject["name"] = alertData.Name + alertObject["labels"] = unpackLabels(alertData.Labels) + alertObject["annotations"] = unpackLabels(alertData.Annotations) + alertObject["fastburn"] = unpackAlertingMetadata(*alertData.FastBurn) + alertObject["slowburn"] = unpackAlertingMetadata(*alertData.SlowBurn) retAlertData = append(retAlertData, alertObject) return retAlertData } -func unpackAlertingMetadata(Metadata gapi.AlertMetadata) []map[string]interface{} { +func unpackAlertingMetadata(metaData gapi.AlertMetadata) []map[string]interface{} { retAlertMetaData := []map[string]interface{}{} labelsAnnotsStruct := make(map[string]interface{}) - if Metadata.Annotations != nil { - retAnnotations := unpackLabels(Metadata.Annotations) + if metaData.Annotations != nil { + retAnnotations := unpackLabels(metaData.Annotations) labelsAnnotsStruct["annotations"] = retAnnotations } - if Metadata.Labels != nil { - retLabels := unpackLabels(Metadata.Labels) + if metaData.Labels != nil { + retLabels := unpackLabels(metaData.Labels) labelsAnnotsStruct["labels"] = retLabels } diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 4dc068818..2704cd2cc 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -229,10 +229,10 @@ func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{ func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics - sloId := d.Id() + sloID := d.Id() client := m.(*common.Client).GrafanaAPI - slo, _ := client.GetSlo(sloId) + slo, _ := client.GetSlo(sloID) setTerraformState(d, slo) @@ -245,8 +245,8 @@ func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{ if d.HasChange("name") || d.HasChange("description") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { slo := packSloResource(d) - grafanaClient := m.(*common.Client) - grafanaClient.GrafanaAPI.UpdateSlo(string(sloID), slo) + client := m.(*common.Client).GrafanaAPI + client.UpdateSlo(sloID, slo) d.Set("last_updated", time.Now().Format(time.RFC850)) } @@ -333,7 +333,6 @@ func packLabels(tfLabels []interface{}) []gapi.Label { } labelSlice = append(labelSlice, curr) - } return labelSlice diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index 1b211bf1e..7f32a9258 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -66,10 +66,10 @@ func testAccSloCheckExists(rn string, slo *gapi.Slo) resource.TestCheckFunc { func testAccSloCheckDestroy(slo *gapi.Slo) resource.TestCheckFunc { return func(s *terraform.State) error { client := testutils.Provider.Meta().(*common.Client).GrafanaAPI - err := client.DeleteSlo(slo.Uuid) + err := client.DeleteSlo(slo.UUID) if err == nil { - return fmt.Errorf("SLO with a UUID %s still exists after destroy", slo.Uuid) + return fmt.Errorf("SLO with a UUID %s still exists after destroy", slo.UUID) } return nil From c55688611b67bc312e30a21b867cf6c47c7df67b Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Mon, 24 Apr 2023 12:38:30 -0400 Subject: [PATCH 24/51] Tests Passing for SLO Resources --- .terraformrc | 9 +++++++++ examples/resources/grafana_slo/resource.tf | 1 - go.mod | 2 +- go.sum | 2 -- internal/resources/slo/data_source_slo.go | 6 +++--- internal/resources/slo/resource_slo.go | 9 +-------- internal/resources/slo/resource_slo_test.go | 1 - 7 files changed, 14 insertions(+), 16 deletions(-) create mode 100644 .terraformrc diff --git a/.terraformrc b/.terraformrc new file mode 100644 index 000000000..2b1557879 --- /dev/null +++ b/.terraformrc @@ -0,0 +1,9 @@ +provider_installation { + dev_overrides { + "grafana/grafana" = "/Users/elainevuong/go/src/github.com/grafana/terraform-provider-grafana" # this path is the diretory where the binary is built + } + # For all other providers, install them directly from their origin provider + # registries as normal. If you omit this, Terraform will _only_ use + # the dev_overrides block, and so no other providers will be available. + direct {} +} \ No newline at end of file diff --git a/examples/resources/grafana_slo/resource.tf b/examples/resources/grafana_slo/resource.tf index 372383056..ccf92d8cf 100644 --- a/examples/resources/grafana_slo/resource.tf +++ b/examples/resources/grafana_slo/resource.tf @@ -1,7 +1,6 @@ resource "grafana_slo_resource" "test" { name = "Terraform Testing" description = "Terraform Description" - service = "serviceA" query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" objectives { objective_value = 0.995 diff --git a/go.mod b/go.mod index 73af34a30..a8a2357a9 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/grafana/terraform-provider-grafana go 1.18 -replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420205450-747f0274255c +replace github.com/grafana/grafana-api-golang-client => /Users/elainevuong/go/src/github.com/grafana/grafana-api-golang-client require ( github.com/Masterminds/semver/v3 v3.2.1 diff --git a/go.sum b/go.sum index 4d4638996..a0f507fad 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,6 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/amixr-api-go-client v0.0.7 h1:U6W6yKxMMybI+Qz4zl+Vih48o6CczLaU/vjk2m7omvU= github.com/grafana/amixr-api-go-client v0.0.7/go.mod h1:N6x26XUrM5zGtK5zL5vNJnAn2JFMxLFPPLTw/6pDkFE= -github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420205450-747f0274255c h1:ojSxDoFLNdtmNF9O/tD6tadn5qjJ8YzSgtjFImkABsY= -github.com/grafana/grafana-api-golang-client v0.19.1-0.20230420205450-747f0274255c/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= github.com/grafana/machine-learning-go-client v0.4.0 h1:UAkJPE7xujzFTm0d9ctbX/FsCID8rqejWjnkRPGNM6E= github.com/grafana/machine-learning-go-client v0.4.0/go.mod h1:QFfZz8NkqVF8++skjkKQXJEZfpCYd8S0yTWJUpsLLTA= github.com/grafana/synthetic-monitoring-agent v0.14.3 h1:M7HZmTJKJLF53Fv7KZeb2KyzHSx7gbHiJ3Mr/ApfCRA= diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 179ce31e3..5922a0cab 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -116,7 +116,7 @@ func DatasourceSlo() *schema.Resource { }, "fastburn": &schema.Schema{ Type: schema.TypeList, - Computed: true, + Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "labels": &schema.Schema{ @@ -156,7 +156,7 @@ func DatasourceSlo() *schema.Resource { }, "slowburn": &schema.Schema{ Type: schema.TypeList, - Computed: true, + Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "labels": &schema.Schema{ @@ -231,7 +231,7 @@ func convertDatasourceSlo(slo gapi.Slo) map[string]interface{} { ret["uuid"] = slo.UUID ret["name"] = slo.Name ret["description"] = slo.Description - ret["dashboard_uid"] = slo.DrilldownDashboardRef.UID + ret["dashboard_uid"] = slo.DrilldownDashboardRef.Uid ret["query"] = unpackQuery(slo.Query) retLabels := unpackLabels(slo.Labels) diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 2704cd2cc..16d02b660 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -2,7 +2,6 @@ package slo import ( "context" - "time" gapi "github.com/grafana/grafana-api-golang-client" "github.com/grafana/terraform-provider-grafana/internal/common" @@ -194,11 +193,6 @@ func ResourceSlo() *schema.Resource { }, }, }, - "last_updated": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - }, }, } } @@ -248,7 +242,6 @@ func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{ client := m.(*common.Client).GrafanaAPI client.UpdateSlo(sloID, slo) - d.Set("last_updated", time.Now().Format(time.RFC850)) } return resourceSloRead(ctx, d, m) @@ -382,7 +375,7 @@ func packAlertMetadata(metadata []interface{}) gapi.AlertMetadata { func setTerraformState(d *schema.ResourceData, slo gapi.Slo) { d.Set("name", slo.Name) d.Set("description", slo.Description) - d.Set("dashboard_uid", slo.DrilldownDashboardRef.UID) + d.Set("dashboard_uid", slo.DrilldownDashboardRef.Uid) d.Set("query", unpackQuery(slo.Query)) retLabels := unpackLabels(slo.Labels) diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index 7f32a9258..b57a1f37e 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -27,7 +27,6 @@ func TestAccResourceSlo(t *testing.T) { resource.TestCheckResourceAttrSet("grafana_slo_resource.test", "dashboard_uid"), resource.TestCheckResourceAttr("grafana_slo_resource.test", "name", "Terraform Testing"), resource.TestCheckResourceAttr("grafana_slo_resource.test", "description", "Terraform Description"), - resource.TestCheckResourceAttr("grafana_slo_resource.test", "service", "serviceA"), resource.TestCheckResourceAttr("grafana_slo_resource.test", "query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), resource.TestCheckResourceAttr("grafana_slo_resource.test", "objectives.0.objective_value", "0.995"), resource.TestCheckResourceAttr("grafana_slo_resource.test", "objectives.0.objective_window", "30d"), From 7c4838e8c1e78968a4b967d5cb8cc2098b13be47 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Mon, 24 Apr 2023 12:47:30 -0400 Subject: [PATCH 25/51] Update Variable Naming and Branch Reference in go.mod --- go.mod | 2 +- go.sum | 2 ++ internal/resources/slo/data_source_slo.go | 2 +- internal/resources/slo/resource_slo.go | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a8a2357a9..da9d31857 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/grafana/terraform-provider-grafana go 1.18 -replace github.com/grafana/grafana-api-golang-client => /Users/elainevuong/go/src/github.com/grafana/grafana-api-golang-client +replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.19.1-0.20230424164304-445fde8bbb57 require ( github.com/Masterminds/semver/v3 v3.2.1 diff --git a/go.sum b/go.sum index a0f507fad..fd179ff2a 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/amixr-api-go-client v0.0.7 h1:U6W6yKxMMybI+Qz4zl+Vih48o6CczLaU/vjk2m7omvU= github.com/grafana/amixr-api-go-client v0.0.7/go.mod h1:N6x26XUrM5zGtK5zL5vNJnAn2JFMxLFPPLTw/6pDkFE= +github.com/grafana/grafana-api-golang-client v0.19.1-0.20230424164304-445fde8bbb57 h1:po0O42X2oycqs3qaV5pCtGd3sHwK5zhsvZ/sHMd5lJU= +github.com/grafana/grafana-api-golang-client v0.19.1-0.20230424164304-445fde8bbb57/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= github.com/grafana/machine-learning-go-client v0.4.0 h1:UAkJPE7xujzFTm0d9ctbX/FsCID8rqejWjnkRPGNM6E= github.com/grafana/machine-learning-go-client v0.4.0/go.mod h1:QFfZz8NkqVF8++skjkKQXJEZfpCYd8S0yTWJUpsLLTA= github.com/grafana/synthetic-monitoring-agent v0.14.3 h1:M7HZmTJKJLF53Fv7KZeb2KyzHSx7gbHiJ3Mr/ApfCRA= diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 5922a0cab..554026856 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -231,7 +231,7 @@ func convertDatasourceSlo(slo gapi.Slo) map[string]interface{} { ret["uuid"] = slo.UUID ret["name"] = slo.Name ret["description"] = slo.Description - ret["dashboard_uid"] = slo.DrilldownDashboardRef.Uid + ret["dashboard_uid"] = slo.DrilldownDashboardRef.UID ret["query"] = unpackQuery(slo.Query) retLabels := unpackLabels(slo.Labels) diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 16d02b660..68f4f0a7f 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -375,7 +375,7 @@ func packAlertMetadata(metadata []interface{}) gapi.AlertMetadata { func setTerraformState(d *schema.ResourceData, slo gapi.Slo) { d.Set("name", slo.Name) d.Set("description", slo.Description) - d.Set("dashboard_uid", slo.DrilldownDashboardRef.Uid) + d.Set("dashboard_uid", slo.DrilldownDashboardRef.UID) d.Set("query", unpackQuery(slo.Query)) retLabels := unpackLabels(slo.Labels) From 0f45deca4344ce86dc275bb74ab2fb98f3d44007 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Mon, 24 Apr 2023 13:17:17 -0400 Subject: [PATCH 26/51] Added Update Test for SLO Resources --- examples/resources/grafana_slo/resource.tf | 6 +-- .../resources/grafana_slo/resource_update.tf | 44 +++++++++++++++++++ internal/resources/slo/resource_slo_test.go | 15 +++++++ 3 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 examples/resources/grafana_slo/resource_update.tf diff --git a/examples/resources/grafana_slo/resource.tf b/examples/resources/grafana_slo/resource.tf index ccf92d8cf..c2b7f8cc4 100644 --- a/examples/resources/grafana_slo/resource.tf +++ b/examples/resources/grafana_slo/resource.tf @@ -41,8 +41,4 @@ resource "grafana_slo_resource" "test" { } } } -} - -# output "foo" { -# value = grafana_slo_resource.test -# } \ No newline at end of file +} \ No newline at end of file diff --git a/examples/resources/grafana_slo/resource_update.tf b/examples/resources/grafana_slo/resource_update.tf new file mode 100644 index 000000000..7a124f40c --- /dev/null +++ b/examples/resources/grafana_slo/resource_update.tf @@ -0,0 +1,44 @@ +resource "grafana_slo_resource" "update" { + name = "Modified - Terraform Testing" + description = "Modified - Terraform Description" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.9995 + objective_window = "7d" + } + labels { + key = "customkey" + value = "customvalue" + } + alerting { + fastburn { + annotations { + key = "name" + value = "Critical - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 14.4x." + } + labels { + key = "type" + value = "slo" + } + } + + slowburn { + annotations { + key = "name" + value = "Warning - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 1x." + } + labels { + key = "type" + value = "slo" + } + } + } +} \ No newline at end of file diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index b57a1f37e..5cf4e1407 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -34,6 +34,21 @@ func TestAccResourceSlo(t *testing.T) { resource.TestCheckResourceAttr("grafana_slo_resource.test", "labels.0.value", "value"), ), }, + { + Config: testutils.TestAccExample(t, "resources/grafana_slo/resource_update.tf"), + Check: resource.ComposeTestCheckFunc( + testAccSloCheckExists("grafana_slo_resource.update", &slo), + resource.TestCheckResourceAttrSet("grafana_slo_resource.update", "id"), + resource.TestCheckResourceAttrSet("grafana_slo_resource.update", "dashboard_uid"), + resource.TestCheckResourceAttr("grafana_slo_resource.update", "name", "Modified - Terraform Testing"), + resource.TestCheckResourceAttr("grafana_slo_resource.update", "description", "Modified - Terraform Description"), + resource.TestCheckResourceAttr("grafana_slo_resource.update", "query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), + resource.TestCheckResourceAttr("grafana_slo_resource.update", "objectives.0.objective_value", "0.9995"), + resource.TestCheckResourceAttr("grafana_slo_resource.update", "objectives.0.objective_window", "7d"), + resource.TestCheckResourceAttr("grafana_slo_resource.update", "labels.0.key", "customkey"), + resource.TestCheckResourceAttr("grafana_slo_resource.update", "labels.0.value", "customvalue"), + ), + }, }, }) } From 0266403724495c2e315b70cd5f98c10c96de7285 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Mon, 24 Apr 2023 13:33:23 -0400 Subject: [PATCH 27/51] Adding Checks for Empty SLOs for Datasource ListSlos Read --- internal/resources/slo/data_source_slo.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 554026856..0ddce30b4 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -214,6 +214,10 @@ func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{ terraformSlos := []interface{}{} + if len(apiSlos.Slos) == 0 { + return diags + } + for _, slo := range apiSlos.Slos { terraformSlo := convertDatasourceSlo(slo) terraformSlos = append(terraformSlos, terraformSlo) From a7f79700512f863c551f3f24a706f96838eb799c Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Mon, 24 Apr 2023 14:49:25 -0400 Subject: [PATCH 28/51] Adds checks for nil alertData, fastBurn/slowBurn and tests --- internal/resources/slo/data_source_slo.go | 14 +++++++++-- internal/resources/slo/resource_slo.go | 28 ++++++++++++++++++--- internal/resources/slo/resource_slo_test.go | 27 ++++++++++++++++++++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 0ddce30b4..3e4febb85 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -291,12 +291,22 @@ func unpackLabels(labels *[]gapi.Label) []map[string]interface{} { func unpackAlerting(alertData *gapi.Alerting) []map[string]interface{} { retAlertData := []map[string]interface{}{} + if alertData == nil { + return retAlertData + } + alertObject := make(map[string]interface{}) alertObject["name"] = alertData.Name alertObject["labels"] = unpackLabels(alertData.Labels) alertObject["annotations"] = unpackLabels(alertData.Annotations) - alertObject["fastburn"] = unpackAlertingMetadata(*alertData.FastBurn) - alertObject["slowburn"] = unpackAlertingMetadata(*alertData.SlowBurn) + + if alertData.FastBurn != nil { + alertObject["fastburn"] = unpackAlertingMetadata(*alertData.FastBurn) + } + + if alertData.SlowBurn != nil { + alertObject["slowburn"] = unpackAlertingMetadata(*alertData.SlowBurn) + } retAlertData = append(retAlertData, alertObject) return retAlertData diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 68f4f0a7f..b024c4bab 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -2,6 +2,7 @@ package slo import ( "context" + "fmt" gapi "github.com/grafana/grafana-api-golang-client" "github.com/grafana/terraform-provider-grafana/internal/common" @@ -208,7 +209,17 @@ func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{ slo := packSloResource(d) client := m.(*common.Client).GrafanaAPI - response, _ := client.CreateSlo(slo) + response, err := client.CreateSlo(slo) + + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Unable to create SLO", + Detail: fmt.Sprintf("API Error Message:%s", err.Error()), + }) + + return diags + } // Get the response back from the API, we need to set the ID of the Terraform Resource d.SetId(response.UUID) @@ -263,6 +274,11 @@ func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{ // Fetches all the Properties defined on the Terraform SLO State Object and converts it // to a Slo so that it can be converted to JSON and sent to the API func packSloResource(d *schema.ResourceData) gapi.Slo { + var ( + tfalerting gapi.Alerting + tflabels []gapi.Label + ) + tfname := d.Get("name").(string) tfdescription := d.Get("description").(string) query := d.Get("query").(string) @@ -274,11 +290,15 @@ func packSloResource(d *schema.ResourceData) gapi.Slo { tfobjective := packObjective(objective) labels := d.Get("labels").([]interface{}) - tflabels := packLabels(labels) + if labels != nil { + tflabels = packLabels(labels) + } alerting := d.Get("alerting").([]interface{}) - alert := alerting[0].(map[string]interface{}) - tfalerting := packAlerting(alert) + if len(alerting) > 0 { + alert := alerting[0].(map[string]interface{}) + tfalerting = packAlerting(alert) + } slo := gapi.Slo{ UUID: d.Id(), diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index 5cf4e1407..950bc28f2 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -2,6 +2,7 @@ package slo_test import ( "fmt" + "regexp" "testing" gapi "github.com/grafana/grafana-api-golang-client" @@ -89,3 +90,29 @@ func testAccSloCheckDestroy(slo *gapi.Slo) resource.TestCheckFunc { return nil } } + +const sloQueryInvalid = ` +resource "grafana_slo_resource" "invalid" { + name = "Test SLO" + description = "Description Test SLO" + query = "Invalid Query" + objectives { + objective_value = 0.995 + objective_window = "30d" + } +} +` + +func TestAccResourceInvalidSlo(t *testing.T) { + testutils.CheckCloudInstanceTestsEnabled(t) + + resource.ParallelTest(t, resource.TestCase{ + ProviderFactories: testutils.ProviderFactories, + Steps: []resource.TestStep{ + { + Config: sloQueryInvalid, + ExpectError: regexp.MustCompile("Unable to create SLO"), + }, + }, + }) +} From 87852c4145ad96407125b3b3d7f0e310c599cac6 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Mon, 24 Apr 2023 15:10:49 -0400 Subject: [PATCH 29/51] Added Tests for Invalid Queries and Invalid Objectives --- internal/resources/slo/resource_slo.go | 4 ++-- internal/resources/slo/resource_slo_test.go | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index b024c4bab..4ef22a1f4 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -31,7 +31,7 @@ func ResourceSlo() *schema.Resource { }, "query": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, "labels": &schema.Schema{ Type: schema.TypeList, @@ -214,7 +214,7 @@ func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{ if err != nil { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, - Summary: "Unable to create SLO", + Summary: "Unable to create SLO - API", Detail: fmt.Sprintf("API Error Message:%s", err.Error()), }) diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index 950bc28f2..a6a178a4e 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -103,6 +103,18 @@ resource "grafana_slo_resource" "invalid" { } ` +const sloObjectivesInvalid = ` +resource "grafana_slo_resource" "invalid" { + name = "Test SLO" + description = "Description Test SLO" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 1.5 + objective_window = "1m" + } +} +` + func TestAccResourceInvalidSlo(t *testing.T) { testutils.CheckCloudInstanceTestsEnabled(t) @@ -113,6 +125,10 @@ func TestAccResourceInvalidSlo(t *testing.T) { Config: sloQueryInvalid, ExpectError: regexp.MustCompile("Unable to create SLO"), }, + { + Config: sloObjectivesInvalid, + ExpectError: regexp.MustCompile("Unable to create SLO"), + }, }, }) } From d95d024f6458c9d478732787ce4703178c93bf36 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Mon, 24 Apr 2023 16:50:59 -0400 Subject: [PATCH 30/51] Updating Tests --- examples/resources/grafana_slo/resource.tf | 18 ++++--- .../resources/grafana_slo/resource_complex.tf | 51 +++++++++++++++++++ .../resources/grafana_slo/resource_update.tf | 22 ++++---- internal/resources/slo/resource_slo_test.go | 8 +-- 4 files changed, 75 insertions(+), 24 deletions(-) create mode 100644 examples/resources/grafana_slo/resource_complex.tf diff --git a/examples/resources/grafana_slo/resource.tf b/examples/resources/grafana_slo/resource.tf index c2b7f8cc4..abcb1c189 100644 --- a/examples/resources/grafana_slo/resource.tf +++ b/examples/resources/grafana_slo/resource.tf @@ -1,3 +1,13 @@ +# resource "grafana_slo_resource" "test" { +# name = "Terraform Testing" +# description = "Terraform Description" +# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" +# objectives { +# objective_value = 0.995 +# objective_window = "30d" +# } +# } + resource "grafana_slo_resource" "test" { name = "Terraform Testing" description = "Terraform Description" @@ -16,10 +26,6 @@ resource "grafana_slo_resource" "test" { key = "name" value = "Critical - SLO Burn Rate Alert" } - annotations { - key = "description" - value = "Error Budget is burning at a rate greater than 14.4x." - } labels { key = "type" value = "slo" @@ -31,10 +37,6 @@ resource "grafana_slo_resource" "test" { key = "name" value = "Warning - SLO Burn Rate Alert" } - annotations { - key = "description" - value = "Error Budget is burning at a rate greater than 1x." - } labels { key = "type" value = "slo" diff --git a/examples/resources/grafana_slo/resource_complex.tf b/examples/resources/grafana_slo/resource_complex.tf new file mode 100644 index 000000000..362e176b6 --- /dev/null +++ b/examples/resources/grafana_slo/resource_complex.tf @@ -0,0 +1,51 @@ +resource "grafana_slo_resource" "test" { + name = "Complex Resource - Terraform Testing" + description = "Complex Resource - Terraform Description" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.995 + objective_window = "30d" + } + labels { + key = "slokey" + value = "slokey" + } + alerting { + name = "alertingname" + + labels { + key = "alertingkey" + value = "alertingvalue" + } + + fastburn { + annotations { + key = "name" + value = "Critical - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 14.4x." + } + labels { + key = "type" + value = "slo" + } + } + + slowburn { + annotations { + key = "name" + value = "Warning - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget is burning at a rate greater than 1x." + } + labels { + key = "type" + value = "slo" + } + } + } +} \ No newline at end of file diff --git a/examples/resources/grafana_slo/resource_update.tf b/examples/resources/grafana_slo/resource_update.tf index 7a124f40c..577b8767f 100644 --- a/examples/resources/grafana_slo/resource_update.tf +++ b/examples/resources/grafana_slo/resource_update.tf @@ -1,6 +1,16 @@ +# resource "grafana_slo_resource" "update" { +# name = "Updated - Terraform Testing" +# description = "Updated - Terraform Description" +# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" +# objectives { +# objective_value = 0.9995 +# objective_window = "7d" +# } +# } + resource "grafana_slo_resource" "update" { - name = "Modified - Terraform Testing" - description = "Modified - Terraform Description" + name = "Updated - Terraform Testing" + description = "Updated - Terraform Description" query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" objectives { objective_value = 0.9995 @@ -16,10 +26,6 @@ resource "grafana_slo_resource" "update" { key = "name" value = "Critical - SLO Burn Rate Alert" } - annotations { - key = "description" - value = "Error Budget is burning at a rate greater than 14.4x." - } labels { key = "type" value = "slo" @@ -31,10 +37,6 @@ resource "grafana_slo_resource" "update" { key = "name" value = "Warning - SLO Burn Rate Alert" } - annotations { - key = "description" - value = "Error Budget is burning at a rate greater than 1x." - } labels { key = "type" value = "slo" diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index a6a178a4e..d18b97fa5 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -31,8 +31,6 @@ func TestAccResourceSlo(t *testing.T) { resource.TestCheckResourceAttr("grafana_slo_resource.test", "query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), resource.TestCheckResourceAttr("grafana_slo_resource.test", "objectives.0.objective_value", "0.995"), resource.TestCheckResourceAttr("grafana_slo_resource.test", "objectives.0.objective_window", "30d"), - resource.TestCheckResourceAttr("grafana_slo_resource.test", "labels.0.key", "custom"), - resource.TestCheckResourceAttr("grafana_slo_resource.test", "labels.0.value", "value"), ), }, { @@ -41,13 +39,11 @@ func TestAccResourceSlo(t *testing.T) { testAccSloCheckExists("grafana_slo_resource.update", &slo), resource.TestCheckResourceAttrSet("grafana_slo_resource.update", "id"), resource.TestCheckResourceAttrSet("grafana_slo_resource.update", "dashboard_uid"), - resource.TestCheckResourceAttr("grafana_slo_resource.update", "name", "Modified - Terraform Testing"), - resource.TestCheckResourceAttr("grafana_slo_resource.update", "description", "Modified - Terraform Description"), + resource.TestCheckResourceAttr("grafana_slo_resource.update", "name", "Updated - Terraform Testing"), + resource.TestCheckResourceAttr("grafana_slo_resource.update", "description", "Updated - Terraform Description"), resource.TestCheckResourceAttr("grafana_slo_resource.update", "query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), resource.TestCheckResourceAttr("grafana_slo_resource.update", "objectives.0.objective_value", "0.9995"), resource.TestCheckResourceAttr("grafana_slo_resource.update", "objectives.0.objective_window", "7d"), - resource.TestCheckResourceAttr("grafana_slo_resource.update", "labels.0.key", "customkey"), - resource.TestCheckResourceAttr("grafana_slo_resource.update", "labels.0.value", "customvalue"), ), }, }, From 461da7f7936ddcc5af0004f1909cf80c8bd01dd9 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Tue, 25 Apr 2023 16:51:58 -0400 Subject: [PATCH 31/51] Adding Error Messages and Datasource SLO Tests --- .../data-sources/grafana_slo/data_source.tf | 1 + examples/resources/grafana_slo/resource.tf | 10 -------- .../resources/grafana_slo/resource_update.tf | 10 -------- internal/resources/slo/data_source_slo.go | 8 +++++- .../resources/slo/data_source_slo_test.go | 23 +++++++++++++++++ internal/resources/slo/resource_slo.go | 25 +++++++++++++++++-- 6 files changed, 54 insertions(+), 23 deletions(-) create mode 100644 examples/data-sources/grafana_slo/data_source.tf create mode 100644 internal/resources/slo/data_source_slo_test.go diff --git a/examples/data-sources/grafana_slo/data_source.tf b/examples/data-sources/grafana_slo/data_source.tf new file mode 100644 index 000000000..b080cdbf1 --- /dev/null +++ b/examples/data-sources/grafana_slo/data_source.tf @@ -0,0 +1 @@ +data "grafana_slo_datasource" "slos" {} \ No newline at end of file diff --git a/examples/resources/grafana_slo/resource.tf b/examples/resources/grafana_slo/resource.tf index abcb1c189..bbfd74905 100644 --- a/examples/resources/grafana_slo/resource.tf +++ b/examples/resources/grafana_slo/resource.tf @@ -1,13 +1,3 @@ -# resource "grafana_slo_resource" "test" { -# name = "Terraform Testing" -# description = "Terraform Description" -# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" -# objectives { -# objective_value = 0.995 -# objective_window = "30d" -# } -# } - resource "grafana_slo_resource" "test" { name = "Terraform Testing" description = "Terraform Description" diff --git a/examples/resources/grafana_slo/resource_update.tf b/examples/resources/grafana_slo/resource_update.tf index 577b8767f..1ad0774db 100644 --- a/examples/resources/grafana_slo/resource_update.tf +++ b/examples/resources/grafana_slo/resource_update.tf @@ -1,13 +1,3 @@ -# resource "grafana_slo_resource" "update" { -# name = "Updated - Terraform Testing" -# description = "Updated - Terraform Description" -# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" -# objectives { -# objective_value = 0.9995 -# objective_window = "7d" -# } -# } - resource "grafana_slo_resource" "update" { name = "Updated - Terraform Testing" description = "Updated - Terraform Description" diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 3e4febb85..f32a01724 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -215,6 +215,12 @@ func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{ terraformSlos := []interface{}{} if len(apiSlos.Slos) == 0 { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "No SLOs Exist", + Detail: "No SLOs currently exist. Create a new one.", + }) + return diags } @@ -223,8 +229,8 @@ func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{ terraformSlos = append(terraformSlos, terraformSlo) } + d.SetId("slos") d.Set("slos", terraformSlos) - d.SetId(apiSlos.Slos[0].UUID) return diags } diff --git a/internal/resources/slo/data_source_slo_test.go b/internal/resources/slo/data_source_slo_test.go new file mode 100644 index 000000000..71a57a593 --- /dev/null +++ b/internal/resources/slo/data_source_slo_test.go @@ -0,0 +1,23 @@ +package slo_test + +import ( + "regexp" + "testing" + + "github.com/grafana/terraform-provider-grafana/internal/testutils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceSlo(t *testing.T) { + testutils.CheckCloudInstanceTestsEnabled(t) + + resource.ParallelTest(t, resource.TestCase{ + ProviderFactories: testutils.ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testutils.TestAccExample(t, "data-sources/grafana_slo/data_source.tf"), + ExpectError: regexp.MustCompile(`No SLOs Exist`), + }, + }, + }) +} diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 4ef22a1f4..53b3d31b0 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -237,7 +237,17 @@ func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) sloID := d.Id() client := m.(*common.Client).GrafanaAPI - slo, _ := client.GetSlo(sloID) + slo, err := client.GetSlo(sloID) + + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("Unable to Fetch Slo with ID: %s", sloID), + Detail: fmt.Sprintf("API Error Message:%s", err.Error()), + }) + + return diags + } setTerraformState(d, slo) @@ -245,13 +255,24 @@ func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) } func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics sloID := d.Id() if d.HasChange("name") || d.HasChange("description") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { slo := packSloResource(d) client := m.(*common.Client).GrafanaAPI - client.UpdateSlo(sloID, slo) + err := client.UpdateSlo(sloID, slo) + + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("Unable to Update Slo with ID: %s", sloID), + Detail: fmt.Sprintf("API Error Message:%s", err.Error()), + }) + + return diags + } } From c42efe9984c85af25df34f766eafc1b49fd89754 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Tue, 25 Apr 2023 19:30:26 -0400 Subject: [PATCH 32/51] Modifies GAPI Branch Reference --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index da9d31857..34471912e 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/grafana/terraform-provider-grafana go 1.18 -replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.19.1-0.20230424164304-445fde8bbb57 +replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.19.1-0.20230425232935-96c655f8d67e require ( github.com/Masterminds/semver/v3 v3.2.1 diff --git a/go.sum b/go.sum index fd179ff2a..4192d6bfb 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/amixr-api-go-client v0.0.7 h1:U6W6yKxMMybI+Qz4zl+Vih48o6CczLaU/vjk2m7omvU= github.com/grafana/amixr-api-go-client v0.0.7/go.mod h1:N6x26XUrM5zGtK5zL5vNJnAn2JFMxLFPPLTw/6pDkFE= -github.com/grafana/grafana-api-golang-client v0.19.1-0.20230424164304-445fde8bbb57 h1:po0O42X2oycqs3qaV5pCtGd3sHwK5zhsvZ/sHMd5lJU= -github.com/grafana/grafana-api-golang-client v0.19.1-0.20230424164304-445fde8bbb57/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= +github.com/grafana/grafana-api-golang-client v0.19.1-0.20230425232935-96c655f8d67e h1:KdyJLIZxSjvKEju385IEwkqDiPD5Se+IelObFyG0u7A= +github.com/grafana/grafana-api-golang-client v0.19.1-0.20230425232935-96c655f8d67e/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= github.com/grafana/machine-learning-go-client v0.4.0 h1:UAkJPE7xujzFTm0d9ctbX/FsCID8rqejWjnkRPGNM6E= github.com/grafana/machine-learning-go-client v0.4.0/go.mod h1:QFfZz8NkqVF8++skjkKQXJEZfpCYd8S0yTWJUpsLLTA= github.com/grafana/synthetic-monitoring-agent v0.14.3 h1:M7HZmTJKJLF53Fv7KZeb2KyzHSx7gbHiJ3Mr/ApfCRA= From 59b24ed409dbe3f42feb6a266e6c326e97b31a84 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 26 Apr 2023 12:41:45 -0400 Subject: [PATCH 33/51] Updating Docs and Descriptions on Resources --- docs/data-sources/slo.md | 146 ++++++++++++++ docs/resources/slo.md | 183 ++++++++++++++++++ .../data-sources/grafana_slo/data-source.tf | 1 + .../data-sources/grafana_slo/data_source.tf | 1 - examples/resources/grafana_slo/resource.tf | 2 +- .../resources/grafana_slo/resource_complex.tf | 2 +- .../resources/grafana_slo/resource_update.tf | 2 +- internal/provider/provider.go | 4 +- internal/resources/slo/data_source_slo.go | 6 + internal/resources/slo/resource_slo.go | 96 +++++---- internal/resources/slo/resource_slo_test.go | 36 ++-- slo_testing/hg/hg_testing_README.md | 2 +- slo_testing/hg/slo-resource-create.tf | 8 +- slo_testing/hg/slo-resource-import.tf | 14 +- slo_testing/hg/slo-resource-update.tf | 107 ---------- tools/subcategories.json | 2 + 16 files changed, 438 insertions(+), 174 deletions(-) create mode 100644 docs/data-sources/slo.md create mode 100644 docs/resources/slo.md create mode 100644 examples/data-sources/grafana_slo/data-source.tf delete mode 100644 examples/data-sources/grafana_slo/data_source.tf delete mode 100644 slo_testing/hg/slo-resource-update.tf diff --git a/docs/data-sources/slo.md b/docs/data-sources/slo.md new file mode 100644 index 000000000..7964a584c --- /dev/null +++ b/docs/data-sources/slo.md @@ -0,0 +1,146 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "grafana_slo Data Source - terraform-provider-grafana" +subcategory: "SLO" +description: |- + Datasource for retrieving all SLOs. + Official documentation https://grafana.com/docs/grafana-cloud/slo/API documentation https://grafana.com/docs/grafana-cloud/slo/api/ +--- + +# grafana_slo (Data Source) + +Datasource for retrieving all SLOs. + +* [Official documentation](https://grafana.com/docs/grafana-cloud/slo/) +* [API documentation](https://grafana.com/docs/grafana-cloud/slo/api/) + +## Example Usage + +```terraform +data "grafana_slo" "slos" {} +``` + + +## Schema + +### Read-Only + +- `id` (String) The ID of this resource. +- `slos` (List of Object) (see [below for nested schema](#nestedatt--slos)) + + +### Nested Schema for `slos` + +Read-Only: + +- `alerting` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting)) +- `dashboard_uid` (String) +- `description` (String) +- `labels` (List of Object) (see [below for nested schema](#nestedobjatt--slos--labels)) +- `name` (String) +- `objectives` (List of Object) (see [below for nested schema](#nestedobjatt--slos--objectives)) +- `query` (String) +- `uuid` (String) + + +### Nested Schema for `slos.alerting` + +Read-Only: + +- `annotations` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--annotations)) +- `fastburn` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--fastburn)) +- `labels` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--labels)) +- `name` (String) +- `slowburn` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--slowburn)) + + +### Nested Schema for `slos.alerting.annotations` + +Read-Only: + +- `key` (String) +- `value` (String) + + + +### Nested Schema for `slos.alerting.fastburn` + +Read-Only: + +- `annotations` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--fastburn--annotations)) +- `labels` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--fastburn--labels)) + + +### Nested Schema for `slos.alerting.fastburn.labels` + +Read-Only: + +- `key` (String) +- `value` (String) + + + +### Nested Schema for `slos.alerting.fastburn.labels` + +Read-Only: + +- `key` (String) +- `value` (String) + + + + +### Nested Schema for `slos.alerting.labels` + +Read-Only: + +- `key` (String) +- `value` (String) + + + +### Nested Schema for `slos.alerting.slowburn` + +Read-Only: + +- `annotations` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--slowburn--annotations)) +- `labels` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--slowburn--labels)) + + +### Nested Schema for `slos.alerting.slowburn.labels` + +Read-Only: + +- `key` (String) +- `value` (String) + + + +### Nested Schema for `slos.alerting.slowburn.labels` + +Read-Only: + +- `key` (String) +- `value` (String) + + + + + +### Nested Schema for `slos.labels` + +Read-Only: + +- `key` (String) +- `value` (String) + + + +### Nested Schema for `slos.objectives` + +Read-Only: + +- `objective_value` (Number) +- `objective_window` (String) + + diff --git a/docs/resources/slo.md b/docs/resources/slo.md new file mode 100644 index 000000000..9e51ba678 --- /dev/null +++ b/docs/resources/slo.md @@ -0,0 +1,183 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "grafana_slo Resource - terraform-provider-grafana" +subcategory: "SLO" +description: |- + Resource manages Grafana SLOs. + Official documentation https://grafana.com/docs/grafana-cloud/slo/API documentation https://grafana.com/docs/grafana-cloud/slo/api/ +--- + +# grafana_slo (Resource) + +Resource manages Grafana SLOs. + +* [Official documentation](https://grafana.com/docs/grafana-cloud/slo/) +* [API documentation](https://grafana.com/docs/grafana-cloud/slo/api/) + +## Example Usage + +```terraform +resource "grafana_slo" "test" { + name = "Terraform Testing" + description = "Terraform Description" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.995 + objective_window = "30d" + } + labels { + key = "custom" + value = "value" + } + alerting { + fastburn { + annotations { + key = "name" + value = "Critical - SLO Burn Rate Alert" + } + labels { + key = "type" + value = "slo" + } + } + + slowburn { + annotations { + key = "name" + value = "Warning - SLO Burn Rate Alert" + } + labels { + key = "type" + value = "slo" + } + } + } +} +``` + + +## Schema + +### Required + +- `description` (String) Description is a free-text field that can provide more context to an SLO. +- `name` (String) Name should be a short description of your indicator. Consider names like "API Availability" +- `objectives` (Block List, Min: 1, Max: 1) Over each rolling time window, the remaining error budget will be calculated, and separate alerts can be generated for each time window based on the SLO burn rate or remaining error budget. (see [below for nested schema](#nestedblock--objectives)) +- `query` (String) Query describes the indicator that will be measured against the objective. Freeform Query types are currently supported. + +### Optional + +- `alerting` (Block List) Configures the alerting rules that will be generated for each + time window associated with the SLO. Grafana SLOs can generate + alerts when the short-term error budget burn is very high, the + long-term error budget burn rate is high, or when the remaining + error budget is below a certain threshold. (see [below for nested schema](#nestedblock--alerting)) +- `labels` (Block List) Additional labels that will be attached to all metrics generated from the query. These labels are useful for grouping SLOs in dashboard views that you create by hand. (see [below for nested schema](#nestedblock--labels)) + +### Read-Only + +- `dashboard_uid` (String) A reference to a dashboard that the plugin has installed in Grafana based on this SLO. This field is read-only, it is generated by the Grafana SLO Plugin. +- `id` (String) The ID of this resource. + + +### Nested Schema for `objectives` + +Required: + +- `objective_value` (Number) Value between 0 and 1. If the value of the query is above the objective, the SLO is met. +- `objective_window` (String) A Prometheus-parsable time duration string like 24h, 60m. This is the time window the objective is measured over. + + + +### Nested Schema for `alerting` + +Optional: + +- `annotations` (Block List) Annotations will be attached to all alerts generated by any of these rules. (see [below for nested schema](#nestedblock--alerting--annotations)) +- `fastburn` (Block List) Alerting Rules generated for Fast Burn alerts (see [below for nested schema](#nestedblock--alerting--fastburn)) +- `labels` (Block List) Labels will be attached to all alerts generated by any of these rules. (see [below for nested schema](#nestedblock--alerting--labels)) +- `name` (String) +- `slowburn` (Block List) Alerting Rules generated for Slow Burn alerts (see [below for nested schema](#nestedblock--alerting--slowburn)) + + +### Nested Schema for `alerting.annotations` + +Optional: + +- `key` (String) +- `value` (String) + + + +### Nested Schema for `alerting.fastburn` + +Optional: + +- `annotations` (Block List) Annotations to attach only to Fast Burn alerts. (see [below for nested schema](#nestedblock--alerting--fastburn--annotations)) +- `labels` (Block List) Labels to attach only to Fast Burn alerts. (see [below for nested schema](#nestedblock--alerting--fastburn--labels)) + + +### Nested Schema for `alerting.fastburn.annotations` + +Optional: + +- `key` (String) +- `value` (String) + + + +### Nested Schema for `alerting.fastburn.labels` + +Optional: + +- `key` (String) +- `value` (String) + + + + +### Nested Schema for `alerting.labels` + +Optional: + +- `key` (String) +- `value` (String) + + + +### Nested Schema for `alerting.slowburn` + +Optional: + +- `annotations` (Block List) Annotations to attach only to Slow Burn alerts. (see [below for nested schema](#nestedblock--alerting--slowburn--annotations)) +- `labels` (Block List) Labels to attach only to Slow Burn alerts. (see [below for nested schema](#nestedblock--alerting--slowburn--labels)) + + +### Nested Schema for `alerting.slowburn.annotations` + +Optional: + +- `key` (String) +- `value` (String) + + + +### Nested Schema for `alerting.slowburn.labels` + +Optional: + +- `key` (String) +- `value` (String) + + + + + +### Nested Schema for `labels` + +Optional: + +- `key` (String) +- `value` (String) + + diff --git a/examples/data-sources/grafana_slo/data-source.tf b/examples/data-sources/grafana_slo/data-source.tf new file mode 100644 index 000000000..d69b5dc75 --- /dev/null +++ b/examples/data-sources/grafana_slo/data-source.tf @@ -0,0 +1 @@ +data "grafana_slo" "slos" {} \ No newline at end of file diff --git a/examples/data-sources/grafana_slo/data_source.tf b/examples/data-sources/grafana_slo/data_source.tf deleted file mode 100644 index b080cdbf1..000000000 --- a/examples/data-sources/grafana_slo/data_source.tf +++ /dev/null @@ -1 +0,0 @@ -data "grafana_slo_datasource" "slos" {} \ No newline at end of file diff --git a/examples/resources/grafana_slo/resource.tf b/examples/resources/grafana_slo/resource.tf index bbfd74905..8ed491c7a 100644 --- a/examples/resources/grafana_slo/resource.tf +++ b/examples/resources/grafana_slo/resource.tf @@ -1,4 +1,4 @@ -resource "grafana_slo_resource" "test" { +resource "grafana_slo" "test" { name = "Terraform Testing" description = "Terraform Description" query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" diff --git a/examples/resources/grafana_slo/resource_complex.tf b/examples/resources/grafana_slo/resource_complex.tf index 362e176b6..293ecb9d7 100644 --- a/examples/resources/grafana_slo/resource_complex.tf +++ b/examples/resources/grafana_slo/resource_complex.tf @@ -1,4 +1,4 @@ -resource "grafana_slo_resource" "test" { +resource "grafana_slo" "test" { name = "Complex Resource - Terraform Testing" description = "Complex Resource - Terraform Description" query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" diff --git a/examples/resources/grafana_slo/resource_update.tf b/examples/resources/grafana_slo/resource_update.tf index 1ad0774db..0a3898ab4 100644 --- a/examples/resources/grafana_slo/resource_update.tf +++ b/examples/resources/grafana_slo/resource_update.tf @@ -1,4 +1,4 @@ -resource "grafana_slo_resource" "update" { +resource "grafana_slo" "update" { name = "Updated - Terraform Testing" description = "Updated - Terraform Description" query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 254fa9aa7..ab5d4ee4e 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -80,7 +80,7 @@ func Provider(version string) func() *schema.Provider { "grafana_machine_learning_outlier_detector": machinelearning.ResourceOutlierDetector(), // SLO - "grafana_slo_resource": slo.ResourceSlo(), + "grafana_slo": slo.ResourceSlo(), }) // Resources that require the Synthetic Monitoring client to exist. @@ -127,7 +127,7 @@ func Provider(version string) func() *schema.Provider { "grafana_organization_preferences": grafana.DatasourceOrganizationPreferences(), // SLO - "grafana_slo_datasource": slo.DatasourceSlo(), + "grafana_slo": slo.DatasourceSlo(), }) // Datasources that require the Synthetic Monitoring client to exist. diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index f32a01724..ab2839a09 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -11,6 +11,12 @@ import ( func DatasourceSlo() *schema.Resource { return &schema.Resource{ + Description: ` +Datasource for retrieving all SLOs. + +* [Official documentation](https://grafana.com/docs/grafana-cloud/slo/) +* [API documentation](https://grafana.com/docs/grafana-cloud/slo/api/) + `, ReadContext: datasourceSloRead, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 53b3d31b0..3594527c5 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -13,29 +13,38 @@ import ( func ResourceSlo() *schema.Resource { return &schema.Resource{ Description: ` - * [Official documentation](https://grafana.com/docs/grafana-cloud/slo/) - * [API documentation](https://grafana.com/docs/grafana-cloud/slo/api/) +Resource manages Grafana SLOs. + +* [Official documentation](https://grafana.com/docs/grafana-cloud/slo/) +* [API documentation](https://grafana.com/docs/grafana-cloud/slo/api/) `, CreateContext: resourceSloCreate, ReadContext: resourceSloRead, UpdateContext: resourceSloUpdate, DeleteContext: resourceSloDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, Schema: map[string]*schema.Schema{ "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + Description: `Name should be a short description of your indicator. Consider names like "API Availability"`, }, "description": &schema.Schema{ - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Required: true, + Description: `Description is a free-text field that can provide more context to an SLO.`, }, "query": &schema.Schema{ - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + Description: `Query describes the indicator that will be measured against the objective. Freeform Query types are currently supported.`, }, "labels": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Optional: true, + Description: `Additional labels that will be attached to all metrics generated from the query. These labels are useful for grouping SLOs in dashboard views that you create by hand.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ @@ -50,29 +59,38 @@ func ResourceSlo() *schema.Resource { }, }, "objectives": &schema.Schema{ - Type: schema.TypeList, - MaxItems: 1, - Required: true, + Type: schema.TypeList, + MaxItems: 1, + Required: true, + Description: `Over each rolling time window, the remaining error budget will be calculated, and separate alerts can be generated for each time window based on the SLO burn rate or remaining error budget.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "objective_value": &schema.Schema{ - Type: schema.TypeFloat, - Optional: true, + Type: schema.TypeFloat, + Required: true, + Description: `Value between 0 and 1. If the value of the query is above the objective, the SLO is met.`, }, "objective_window": &schema.Schema{ - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Required: true, + Description: `A Prometheus-parsable time duration string like 24h, 60m. This is the time window the objective is measured over.`, }, }, }, }, "dashboard_uid": &schema.Schema{ - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Computed: true, + Description: `A reference to a dashboard that the plugin has installed in Grafana based on this SLO. This field is read-only, it is generated by the Grafana SLO Plugin.`, }, "alerting": &schema.Schema{ Type: schema.TypeList, Optional: true, + Description: `Configures the alerting rules that will be generated for each + time window associated with the SLO. Grafana SLOs can generate + alerts when the short-term error budget burn is very high, the + long-term error budget burn rate is high, or when the remaining + error budget is below a certain threshold.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": &schema.Schema{ @@ -80,8 +98,9 @@ func ResourceSlo() *schema.Resource { Optional: true, }, "labels": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Optional: true, + Description: `Labels will be attached to all alerts generated by any of these rules.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ @@ -96,8 +115,9 @@ func ResourceSlo() *schema.Resource { }, }, "annotations": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Optional: true, + Description: `Annotations will be attached to all alerts generated by any of these rules.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ @@ -112,13 +132,15 @@ func ResourceSlo() *schema.Resource { }, }, "fastburn": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Optional: true, + Description: "Alerting Rules generated for Fast Burn alerts", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "labels": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Optional: true, + Description: "Labels to attach only to Fast Burn alerts.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ @@ -133,8 +155,9 @@ func ResourceSlo() *schema.Resource { }, }, "annotations": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Optional: true, + Description: "Annotations to attach only to Fast Burn alerts.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ @@ -152,13 +175,15 @@ func ResourceSlo() *schema.Resource { }, }, "slowburn": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Optional: true, + Description: "Alerting Rules generated for Slow Burn alerts", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "labels": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Optional: true, + Description: "Labels to attach only to Slow Burn alerts.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ @@ -173,8 +198,9 @@ func ResourceSlo() *schema.Resource { }, }, "annotations": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Optional: true, + Description: "Annotations to attach only to Slow Burn alerts.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index d18b97fa5..0bcb59269 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -23,27 +23,27 @@ func TestAccResourceSlo(t *testing.T) { { Config: testutils.TestAccExample(t, "resources/grafana_slo/resource.tf"), Check: resource.ComposeTestCheckFunc( - testAccSloCheckExists("grafana_slo_resource.test", &slo), - resource.TestCheckResourceAttrSet("grafana_slo_resource.test", "id"), - resource.TestCheckResourceAttrSet("grafana_slo_resource.test", "dashboard_uid"), - resource.TestCheckResourceAttr("grafana_slo_resource.test", "name", "Terraform Testing"), - resource.TestCheckResourceAttr("grafana_slo_resource.test", "description", "Terraform Description"), - resource.TestCheckResourceAttr("grafana_slo_resource.test", "query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), - resource.TestCheckResourceAttr("grafana_slo_resource.test", "objectives.0.objective_value", "0.995"), - resource.TestCheckResourceAttr("grafana_slo_resource.test", "objectives.0.objective_window", "30d"), + testAccSloCheckExists("grafana_slo.test", &slo), + resource.TestCheckResourceAttrSet("grafana_slo.test", "id"), + resource.TestCheckResourceAttrSet("grafana_slo.test", "dashboard_uid"), + resource.TestCheckResourceAttr("grafana_slo.test", "name", "Terraform Testing"), + resource.TestCheckResourceAttr("grafana_slo.test", "description", "Terraform Description"), + resource.TestCheckResourceAttr("grafana_slo.test", "query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), + resource.TestCheckResourceAttr("grafana_slo.test", "objectives.0.objective_value", "0.995"), + resource.TestCheckResourceAttr("grafana_slo.test", "objectives.0.objective_window", "30d"), ), }, { Config: testutils.TestAccExample(t, "resources/grafana_slo/resource_update.tf"), Check: resource.ComposeTestCheckFunc( - testAccSloCheckExists("grafana_slo_resource.update", &slo), - resource.TestCheckResourceAttrSet("grafana_slo_resource.update", "id"), - resource.TestCheckResourceAttrSet("grafana_slo_resource.update", "dashboard_uid"), - resource.TestCheckResourceAttr("grafana_slo_resource.update", "name", "Updated - Terraform Testing"), - resource.TestCheckResourceAttr("grafana_slo_resource.update", "description", "Updated - Terraform Description"), - resource.TestCheckResourceAttr("grafana_slo_resource.update", "query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), - resource.TestCheckResourceAttr("grafana_slo_resource.update", "objectives.0.objective_value", "0.9995"), - resource.TestCheckResourceAttr("grafana_slo_resource.update", "objectives.0.objective_window", "7d"), + testAccSloCheckExists("grafana_slo.update", &slo), + resource.TestCheckResourceAttrSet("grafana_slo.update", "id"), + resource.TestCheckResourceAttrSet("grafana_slo.update", "dashboard_uid"), + resource.TestCheckResourceAttr("grafana_slo.update", "name", "Updated - Terraform Testing"), + resource.TestCheckResourceAttr("grafana_slo.update", "description", "Updated - Terraform Description"), + resource.TestCheckResourceAttr("grafana_slo.update", "query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), + resource.TestCheckResourceAttr("grafana_slo.update", "objectives.0.objective_value", "0.9995"), + resource.TestCheckResourceAttr("grafana_slo.update", "objectives.0.objective_window", "7d"), ), }, }, @@ -88,7 +88,7 @@ func testAccSloCheckDestroy(slo *gapi.Slo) resource.TestCheckFunc { } const sloQueryInvalid = ` -resource "grafana_slo_resource" "invalid" { +resource "grafana_slo" "invalid" { name = "Test SLO" description = "Description Test SLO" query = "Invalid Query" @@ -100,7 +100,7 @@ resource "grafana_slo_resource" "invalid" { ` const sloObjectivesInvalid = ` -resource "grafana_slo_resource" "invalid" { +resource "grafana_slo" "invalid" { name = "Test SLO" description = "Description Test SLO" query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" diff --git a/slo_testing/hg/hg_testing_README.md b/slo_testing/hg/hg_testing_README.md index 3279b37a5..eb83d9acf 100644 --- a/slo_testing/hg/hg_testing_README.md +++ b/slo_testing/hg/hg_testing_README.md @@ -1,4 +1,4 @@ -# How to Test the SLO Terraform Provider - Hosted Grafana +gr# How to Test the SLO Terraform Provider - Hosted Grafana ## Create your HG Account and Get the SLO Plugin Deployed Generate a new Service Account Token, and set the environment variable GRAFANA_AUTH to the value of your token (or you can specify the `auth` field within the Terraform State file). diff --git a/slo_testing/hg/slo-resource-create.tf b/slo_testing/hg/slo-resource-create.tf index 5735713ad..30ab0e8f2 100644 --- a/slo_testing/hg/slo-resource-create.tf +++ b/slo_testing/hg/slo-resource-create.tf @@ -11,8 +11,8 @@ provider "grafana" { } resource "grafana_slo_resource" "test1" { - name = "Terraform - 99.5% of Responses from Kubernetes API Server Valid" - description = "Terraform - Measures that 99.5% of responses from the Kubernetes API Server are valid (i.e. not HTTP 500 Errors)" + name = "Terraform - Name Test" + description = "Terraform - Description Test" query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" objectives { objective_value = 0.995 @@ -30,7 +30,7 @@ resource "grafana_slo_resource" "test1" { } annotations { key = "description" - value = "Error Budget is burning at a rate greater than 14.4x. This means that within 1 Hour, 2% of your SLO Error Budget may be consumed. Recommended action: Page" + value = "Error Budget Burning Very Quickly" } labels { key = "type" @@ -45,7 +45,7 @@ resource "grafana_slo_resource" "test1" { } annotations { key = "description" - value = "Error Budget is burning at a rate greater than 1x. This means that within 72 Hours, 10% of your SLO Error Budget may be consumed. Recommended action: Page/Ticket" + value = "Error Budget Burning Quickly" } labels { key = "type" diff --git a/slo_testing/hg/slo-resource-import.tf b/slo_testing/hg/slo-resource-import.tf index 77f2ba466..7c3822b4d 100644 --- a/slo_testing/hg/slo-resource-import.tf +++ b/slo_testing/hg/slo-resource-import.tf @@ -1,7 +1,7 @@ terraform { required_providers { - grafana = { - source = "registry.terraform.io/grafana/grafana" + grafana = { + source = "registry.terraform.io/grafana/grafana" } } } @@ -10,7 +10,15 @@ provider "grafana" { url = "https://elainetest.grafana.net" } -resource "grafana_slo_resource" "sample" {} +resource "grafana_slo_resource" "sample" { + name = "Terraform - Import Test123" + description = "Terraform - Import Test" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.995 + objective_window = "30d" + } +} output "sample_slo" { value = grafana_slo_resource.sample diff --git a/slo_testing/hg/slo-resource-update.tf b/slo_testing/hg/slo-resource-update.tf deleted file mode 100644 index f073b7c49..000000000 --- a/slo_testing/hg/slo-resource-update.tf +++ /dev/null @@ -1,107 +0,0 @@ -terraform { - required_providers { - grafana = { - source = "registry.terraform.io/grafana/grafana" - } - } -} - -provider "grafana" { - url = "https://elainetest.grafana.net" -} - -resource "grafana_slo_resource" "test1" { - name = "Terraform1 - 99.5% of Responses from Kubernetes API Server Valido" - description = "Terraform1 - Measures that 99.5% of responses from the Kubernetes API Server are valid (i.e. not HTTP 500 Errors)" - service = "service1" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.995 - objective_window = "30d" - } - labels { - key = "custom" - value = "value" - } - alerting { - fastburn { - annotations { - key = "name" - value = "Critical - SLO Burn Rate Alert" - } - annotations { - key = "description" - value = "Error Budget is burning at a rate greater than 14.4x. This means that within 1 Hour, 2% of your SLO Error Budget may be consumed. Recommended action: Page" - } - labels { - key = "type" - value = "slo" - } - } - - slowburn { - annotations { - key = "name" - value = "Warning - SLO Burn Rate Alert" - } - annotations { - key = "description" - value = "Error Budget is burning at a rate greater than 1x. This means that within 72 Hours, 10% of your SLO Error Budget may be consumed. Recommended action: Page/Ticket" - } - labels { - key = "type" - value = "slo" - } - } - } -} - -output "test2_order" { - value = grafana_slo_resource.test2 -} - -resource "grafana_slo_resource" "test2" { - name = "Terraform2 - 99.5% of Responses from Kubernetes API Server Valid123" - description = "Terraform2 - Measures that 99.5% of responses from the Kubernetes API Server are valid (i.e. not HTTP 500 Errors)" - service = "service2" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.995 - objective_window = "30d" - } - labels { - key = "custom" - value = "value" - } - alerting { - fastburn { - annotations { - key = "name" - value = "Critical - SLO Burn Rate Alert" - } - annotations { - key = "description" - value = "Error Budget is burning at a rate greater than 14.4x. This means that within 1 Hour, 2% of your SLO Error Budget may be consumed. Recommended action: Page" - } - labels { - key = "type" - value = "slo" - } - } - - slowburn { - annotations { - key = "name" - value = "Warning - SLO Burn Rate Alert" - } - annotations { - key = "description" - value = "Error Budget is burning at a rate greater than 1x. This means that within 72 Hours, 10% of your SLO Error Budget may be consumed. Recommended action: Page/Ticket" - } - labels { - key = "type" - value = "slo" - } - } - } -} \ No newline at end of file diff --git a/tools/subcategories.json b/tools/subcategories.json index fb4073dd5..1e757dec0 100644 --- a/tools/subcategories.json +++ b/tools/subcategories.json @@ -47,6 +47,7 @@ "resources/oncall_outgoing_webhook": "OnCall", "resources/oncall_route": "OnCall", "resources/oncall_schedule": "OnCall", + "resources/slo": "SLO", "resources/synthetic_monitoring_check": "Synthetic Monitoring", "resources/synthetic_monitoring_installation": "Synthetic Monitoring", "resources/synthetic_monitoring_probe": "Synthetic Monitoring", @@ -72,6 +73,7 @@ "data-sources/oncall_team": "OnCall", "data-sources/oncall_user": "OnCall", "data-sources/oncall_user_group": "OnCall", + "data-sources/slo": "SLO", "data-sources/synthetic_monitoring_probe": "Synthetic Monitoring", "data-sources/synthetic_monitoring_probes": "Synthetic Monitoring" } From b1603f8630c10c078a93886713e7563bcfe08b3d Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 26 Apr 2023 14:20:03 -0400 Subject: [PATCH 34/51] Updating Docs and Description on SLO Datasource --- docs/data-sources/slo.md | 2 +- docs/resources/slo.md | 14 +- internal/resources/slo/data_source_slo.go | 131 ++++++++++-------- .../resources/slo/data_source_slo_test.go | 2 +- internal/resources/slo/resource_slo.go | 28 ++-- slo_testing/hg/hg_testing_README.md | 105 -------------- slo_testing/hg/slo-datasource-read-hg.tf | 18 --- slo_testing/hg/slo-resource-create.tf | 60 -------- slo_testing/hg/slo-resource-import.tf | 25 ---- 9 files changed, 100 insertions(+), 285 deletions(-) delete mode 100644 slo_testing/hg/hg_testing_README.md delete mode 100644 slo_testing/hg/slo-datasource-read-hg.tf delete mode 100644 slo_testing/hg/slo-resource-create.tf delete mode 100644 slo_testing/hg/slo-resource-import.tf diff --git a/docs/data-sources/slo.md b/docs/data-sources/slo.md index 7964a584c..f712b1388 100644 --- a/docs/data-sources/slo.md +++ b/docs/data-sources/slo.md @@ -26,7 +26,7 @@ data "grafana_slo" "slos" {} ### Read-Only - `id` (String) The ID of this resource. -- `slos` (List of Object) (see [below for nested schema](#nestedatt--slos)) +- `slos` (List of Object) Returns a list of all SLOs" (see [below for nested schema](#nestedatt--slos)) ### Nested Schema for `slos` diff --git a/docs/resources/slo.md b/docs/resources/slo.md index 9e51ba678..e603674ec 100644 --- a/docs/resources/slo.md +++ b/docs/resources/slo.md @@ -102,7 +102,7 @@ Optional: ### Nested Schema for `alerting.annotations` -Optional: +Required: - `key` (String) - `value` (String) @@ -119,7 +119,7 @@ Optional: ### Nested Schema for `alerting.fastburn.annotations` -Optional: +Required: - `key` (String) - `value` (String) @@ -128,7 +128,7 @@ Optional: ### Nested Schema for `alerting.fastburn.labels` -Optional: +Required: - `key` (String) - `value` (String) @@ -138,7 +138,7 @@ Optional: ### Nested Schema for `alerting.labels` -Optional: +Required: - `key` (String) - `value` (String) @@ -155,7 +155,7 @@ Optional: ### Nested Schema for `alerting.slowburn.annotations` -Optional: +Required: - `key` (String) - `value` (String) @@ -164,7 +164,7 @@ Optional: ### Nested Schema for `alerting.slowburn.labels` -Optional: +Required: - `key` (String) - `value` (String) @@ -175,7 +175,7 @@ Optional: ### Nested Schema for `labels` -Optional: +Required: - `key` (String) - `value` (String) diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index ab2839a09..83cc5fa0b 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -23,136 +23,156 @@ Datasource for retrieving all SLOs. }, Schema: map[string]*schema.Schema{ "slos": &schema.Schema{ - Type: schema.TypeList, - Computed: true, + Type: schema.TypeList, + Computed: true, + Description: `Returns a list of all SLOs"`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "uuid": &schema.Schema{ - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Description: `A unique, random identifier. This value will also be the name of the resource stored in the API server. This value is read-only.`, + Computed: true, }, "name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Computed: true, + Description: `Name should be a short description of your indicator. Consider names like "API Availability"`, }, "description": &schema.Schema{ - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Computed: true, + Description: `Description is a free-text field that can provide more context to an SLO.`, + }, + "query": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Description: `Query describes the indicator that will be measured against the objective. Freeform Query types are currently supported.`, }, "labels": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Computed: true, + Description: `Additional labels that will be attached to all metrics generated from the query. These labels are useful for grouping SLOs in dashboard views that you create by hand.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, }, }, }, - "query": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, "objectives": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Computed: true, + Description: `Over each rolling time window, the remaining error budget will be calculated, and separate alerts can be generated for each time window based on the SLO burn rate or remaining error budget.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "objective_value": &schema.Schema{ - Type: schema.TypeFloat, - Optional: true, + Type: schema.TypeFloat, + Computed: true, + Description: `Value between 0 and 1. If the value of the query is above the objective, the SLO is met.`, }, "objective_window": &schema.Schema{ - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Computed: true, + Description: `A Prometheus-parsable time duration string like 24h, 60m. This is the time window the objective is measured over.`, }, }, }, }, "dashboard_uid": &schema.Schema{ - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Computed: true, + Description: `A reference to a dashboard that the plugin has installed in Grafana based on this SLO. This field is read-only, it is generated by the Grafana SLO Plugin.`, }, "alerting": &schema.Schema{ Type: schema.TypeList, - Optional: true, + Computed: true, + Description: `Configures the alerting rules that will be generated for each + time window associated with the SLO. Grafana SLOs can generate + alerts when the short-term error budget burn is very high, the + long-term error budget burn rate is high, or when the remaining + error budget is below a certain threshold.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, "labels": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Computed: true, + Description: `Labels will be attached to all alerts generated by any of these rules.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, }, }, }, "annotations": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Computed: true, + Description: `Annotations will be attached to all alerts generated by any of these rules.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, }, }, }, "fastburn": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Computed: true, + Description: "Alerting Rules generated for Fast Burn alerts", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "labels": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Computed: true, + Description: "Labels to attach only to Fast Burn alerts.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, }, }, }, "annotations": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Computed: true, + Description: "Annotations to attach only to Fast Burn alerts.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, }, }, @@ -161,38 +181,41 @@ Datasource for retrieving all SLOs. }, }, "slowburn": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Computed: true, + Description: "Alerting Rules generated for Slow Burn alerts", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "labels": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Computed: true, + Description: "Labels to attach only to Slow Burn alerts.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, }, }, }, "annotations": &schema.Schema{ - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Computed: true, + Description: "Annotations to attach only to Slow Burn alerts.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, }, }, }, diff --git a/internal/resources/slo/data_source_slo_test.go b/internal/resources/slo/data_source_slo_test.go index 71a57a593..770bcff98 100644 --- a/internal/resources/slo/data_source_slo_test.go +++ b/internal/resources/slo/data_source_slo_test.go @@ -15,7 +15,7 @@ func TestAccDataSourceSlo(t *testing.T) { ProviderFactories: testutils.ProviderFactories, Steps: []resource.TestStep{ { - Config: testutils.TestAccExample(t, "data-sources/grafana_slo/data_source.tf"), + Config: testutils.TestAccExample(t, "data-sources/grafana_slo/data-source.tf"), ExpectError: regexp.MustCompile(`No SLOs Exist`), }, }, diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 3594527c5..d0e06b96a 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -49,11 +49,11 @@ Resource manages Grafana SLOs. Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, }, }, @@ -105,11 +105,11 @@ Resource manages Grafana SLOs. Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, }, }, @@ -122,11 +122,11 @@ Resource manages Grafana SLOs. Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, }, }, @@ -145,11 +145,11 @@ Resource manages Grafana SLOs. Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, }, }, @@ -162,11 +162,11 @@ Resource manages Grafana SLOs. Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, }, }, @@ -188,11 +188,11 @@ Resource manages Grafana SLOs. Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, }, }, @@ -205,11 +205,11 @@ Resource manages Grafana SLOs. Schema: map[string]*schema.Schema{ "key": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, "value": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Required: true, }, }, }, diff --git a/slo_testing/hg/hg_testing_README.md b/slo_testing/hg/hg_testing_README.md deleted file mode 100644 index eb83d9acf..000000000 --- a/slo_testing/hg/hg_testing_README.md +++ /dev/null @@ -1,105 +0,0 @@ -gr# How to Test the SLO Terraform Provider - Hosted Grafana - -## Create your HG Account and Get the SLO Plugin Deployed -Generate a new Service Account Token, and set the environment variable GRAFANA_AUTH to the value of your token (or you can specify the `auth` field within the Terraform State file). -`export GRAFANA_AUTH= -Within the `.tf` files within `slo_testing/hg`, ensure that you set the `url` field to be the `url` of your HG Instance. - -## Understanding Terraform Provider Code Flow -1. Within the terraform provider root directory, run `go build`. This creates a binary of the terraform-provider-grafana. -2. Within the terraform provider root directory, create a file called `.terraformrc` with the following contents. This ensures that it will use the local binary version of the terraform-provider-grafana. -``` -provider_installation { - dev_overrides { - "grafana/grafana" = "/Users/elainevuong/go/src/github.com/grafana/terraform-provider-grafana" // use the path specifies for where you built the binary for the terraform-provider-grafana - } - # For all other providers, install them directly from their origin provider - # registries as normal. If you omit this, Terraform will _only_ use - # the dev_overrides block, and so no other providers will be available. - direct {} -} -``` - -3. Using the Grafana HTTP Golang API Client - within the `go.mod` file of the `terraform-provider-grafana`, insert the following line (point it to the path of your gapi client, which should be from this branch https://github.com/grafana/grafana-api-golang-client/tree/elainevuong/slo) -`replace github.com/grafana/grafana-api-golang-client => /Users/elainevuong/go/src/github.com/grafana/grafana-api-golang-client` - - -### Types of Resources -Datasource - datasources are resources that are external to Terraform (i.e. not managed by Terraform state). When interacting with a Datasource, they can be used to READ information, and datasources can also be imported (i.e. converted) into Resources, which allows Terraform state to control them. - -Resources - these are resources that can be managed by Terraform state. This means that you CREATE, READ, UPDATE, DELETE. If an IMPORT method is defined, you can also convert Datasources into Resources (i.e. this means that if you import a resource created by the UI (i.e. a Datasource) it can be converted into a Resrouce, which can be managed by Terraform). - -## Testing Datasource - READ -This file defines a schema, that matches the response shape of a GET request to the SLO Endpoint. -``` -{ - "slos": [ - { - "uuid": "bik1rpvkvbzxnfkutzmkh", - "name": "test1", - ... - }, - { - "uuid": "94pqcghz92hybc3iwircy", - "name": "test2", - ... - }, - ] -} -``` - -Objective - we want to send a GET Request to the SLO Endpoint that returns a list of all SLOs, and we want to be able to READ that information and output it to the Terraform CLI. - -1. Within the terraform-provider-grafana root directory, run `make install`. -2. Within your SLO UI, create a SLO. -3. Set the GRAFANA_AUTH environment variable to your HG Grafana API Key -4. Within the `slo-datasource-read-hg.tf` file, set the url to be the url of your HG Instance. -5. Ensure that Lines 236-238 within the `data_source_slo.go` are UNCOMMENTED. -6. Comment out all the `.tf` files within the `slo_testing/hg` folder, EXCEPT for the `slo-datasource-read-hg.tf` file -7. Within the `slo_testing/hg` directory, run the commands `terraform init` and `terraform apply`. Ensure to remove the `.terraform.lock.hcl` and any `terraform.tfstate` files. -8. You should see a list of all SLOs within your Terraform CLI. - -## Testing Resource - CREATE -Objective - we want to be able to define a SLO Resource within Terraform state that should be created. Once the resource has successfully been created, we want to display the newly created SLO resource within the Terraform CLI. - -The `slo-resource-create.tf` file will create two SLOs. - -Testing the CREATE Method -1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Within the terraform-provider-grafana root directory, run `make install`. -2. Change to the `slo_testing/hg` directory. -3. Comment out all the `.tf` files within the `slo_testing/hg` folder, EXCEPT for the `slo-resource-create.tf` file -4. Within the `slo_testing/hg` directory, run the commands `terraform init` and `terraform apply`. -5. Within your terminal, you should see the output of the two newly created SLO from within Terraform, and two newly created SLOs within the SLO UI. - -## Testing the UPDATE Method -1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Within the terraform-provider-grafana root directory, run `make install`. -2. Change to the `slo_testing/hg` directory. -3. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-update.tf` file -4. Within the `slo_testing/hg` directory, run the commands `terraform init` and `terraform apply`. This creates the resource specified below in the terraform state file. -5. To ensure that the PUT endpoint works, modify any of the values within the resource below, and re-run `terraform apply`. -6. Make a GET Request to the API Endpoint to ensure the resource was properly modified. - -## Testing the DELETE/DESTROY Method -Objective - we want to be able to delete a SLO Resource that was created with Terraform. -After creating the two SLO resources from the CREATE Method, we will DELETE them. - -1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files -2. Within the `slo_testing/hg` directory, ensure the `slo-resource-create.tf` file is uncommented. Run the commands `terraform init` and execute `terraform apply`. This creates two new SLOs from the Terraform CLI. -3. Create a regular SLO using the UI. At this point, you should have 3 SLOs - 2 created from Terraform, and 1 created from the UI -4. To delete all Terraformed SLO resources, execute the command `terraform destroy`, and type `yes` in the terminal to confirm the delete -5. The two newly created Terraformed SLO Resources should be deleted, and you should still see the SLO that was created through the UI remaining. - -### Testing the IMPORT Method -1. Within the terraform-provider-grafana root directory, run `make install`. -2. Change to the `slo_testing/hg` directory. -3. Comment out all the `.tf` files within the `slo_testing/local` folder, EXCEPT for the `slo-resource-import.tf` file -4. Create a SLO using the UI or Postman. Take note of the SLO's UUID -5. Execute the command `terraform init` -6. Within the Terraform CLI directly, execute the command: `terraform import grafana_slo_resource.sample slo_UUID` -7. Now execute the command: `terraform state show grafana_slo_resource.sample` - you should see the data from the imported Resource. -8. To verify that this resource is now under Terraform control, within the `slo-resource-import.tf` file, comment out lines 14-18. Then, within the CLI run `terraform destroy`. This should destroy the resource from within the Terraform CLI. - -### TBD ### -1. Testing. -2. Remove `slo_testing` folder -3. Documentation. \ No newline at end of file diff --git a/slo_testing/hg/slo-datasource-read-hg.tf b/slo_testing/hg/slo-datasource-read-hg.tf deleted file mode 100644 index f3a7e7c50..000000000 --- a/slo_testing/hg/slo-datasource-read-hg.tf +++ /dev/null @@ -1,18 +0,0 @@ -terraform { - required_providers { - grafana = { - source = "registry.terraform.io/grafana/grafana" - } - } -} - -provider "grafana" { - url = "https://elainetest.grafana.net" -} - -data "grafana_slo_datasource" "test1" { -} - -output "test1" { - value = data.grafana_slo_datasource.test1 -} \ No newline at end of file diff --git a/slo_testing/hg/slo-resource-create.tf b/slo_testing/hg/slo-resource-create.tf deleted file mode 100644 index 30ab0e8f2..000000000 --- a/slo_testing/hg/slo-resource-create.tf +++ /dev/null @@ -1,60 +0,0 @@ -terraform { - required_providers { - grafana = { - source = "registry.terraform.io/grafana/grafana" - } - } -} - -provider "grafana" { - url = "https://elainetest.grafana.net" -} - -resource "grafana_slo_resource" "test1" { - name = "Terraform - Name Test" - description = "Terraform - Description Test" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.995 - objective_window = "30d" - } - labels { - key = "custom" - value = "value" - } - alerting { - fastburn { - annotations { - key = "name" - value = "Critical - SLO Burn Rate Alert" - } - annotations { - key = "description" - value = "Error Budget Burning Very Quickly" - } - labels { - key = "type" - value = "slo" - } - } - - slowburn { - annotations { - key = "name" - value = "Warning - SLO Burn Rate Alert" - } - annotations { - key = "description" - value = "Error Budget Burning Quickly" - } - labels { - key = "type" - value = "slo" - } - } - } -} - -output "test1" { - value = grafana_slo_resource.test1 -} diff --git a/slo_testing/hg/slo-resource-import.tf b/slo_testing/hg/slo-resource-import.tf deleted file mode 100644 index 7c3822b4d..000000000 --- a/slo_testing/hg/slo-resource-import.tf +++ /dev/null @@ -1,25 +0,0 @@ -terraform { - required_providers { - grafana = { - source = "registry.terraform.io/grafana/grafana" - } - } -} - -provider "grafana" { - url = "https://elainetest.grafana.net" -} - -resource "grafana_slo_resource" "sample" { - name = "Terraform - Import Test123" - description = "Terraform - Import Test" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - objectives { - objective_value = 0.995 - objective_window = "30d" - } -} - -output "sample_slo" { - value = grafana_slo_resource.sample -} From 5f6d81b47ab2a37fa90a165f705baca96c89e4e7 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 26 Apr 2023 15:38:22 -0400 Subject: [PATCH 35/51] Update Formatting --- .drone/drone.yml | 24 ++++++++++++++++++++++++ internal/resources/slo/resource_slo.go | 1 - 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.drone/drone.yml b/.drone/drone.yml index e24ae5273..c1eda0fe3 100644 --- a/.drone/drone.yml +++ b/.drone/drone.yml @@ -132,6 +132,30 @@ steps: - chmod a+x /drone/terraform-provider-grafana/terraform image: hashicorp/terraform name: download-terraform + +- name: install-SLO-plugin + image: grafana/grafana-plugin-ci:1.7.3-alpine + commands: + - yarn install --immutable +- name: package-SLO-plugin + image: grafana/grafana-plugin-ci:1.7.3-alpine + environment: + GCOM_TOKEN: + from_secret: gcom_api_key + commands: + - make package + depends_on: + - install-SLO-plugin +- name: upload-plugin + image: plugins/gcs + settings: + source: artifact/ + target: grafana-slo-app-dev/builds/ + token: + from_secret: plugin_gcs_service_account + depends_on: + - package-SLO-plugin + - commands: - .drone/wait-for-instance.sh $${GRAFANA_URL} environment: diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index d0e06b96a..2d1ba7fe8 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -299,7 +299,6 @@ func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{ return diags } - } return resourceSloRead(ctx, d, m) From 91c0b75de79e7ea105f279c4c8678306fac35665 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 26 Apr 2023 15:43:38 -0400 Subject: [PATCH 36/51] Revert Drone Changes --- .drone/drone.yml | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/.drone/drone.yml b/.drone/drone.yml index c1eda0fe3..e24ae5273 100644 --- a/.drone/drone.yml +++ b/.drone/drone.yml @@ -132,30 +132,6 @@ steps: - chmod a+x /drone/terraform-provider-grafana/terraform image: hashicorp/terraform name: download-terraform - -- name: install-SLO-plugin - image: grafana/grafana-plugin-ci:1.7.3-alpine - commands: - - yarn install --immutable -- name: package-SLO-plugin - image: grafana/grafana-plugin-ci:1.7.3-alpine - environment: - GCOM_TOKEN: - from_secret: gcom_api_key - commands: - - make package - depends_on: - - install-SLO-plugin -- name: upload-plugin - image: plugins/gcs - settings: - source: artifact/ - target: grafana-slo-app-dev/builds/ - token: - from_secret: plugin_gcs_service_account - depends_on: - - package-SLO-plugin - - commands: - .drone/wait-for-instance.sh $${GRAFANA_URL} environment: From f79086f9f31fbea2308c49ffd439628b3ef4daf5 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 26 Apr 2023 15:45:00 -0400 Subject: [PATCH 37/51] Formatting --- examples/resources/grafana_slo/resource_complex.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/resources/grafana_slo/resource_complex.tf b/examples/resources/grafana_slo/resource_complex.tf index 293ecb9d7..d017b47bf 100644 --- a/examples/resources/grafana_slo/resource_complex.tf +++ b/examples/resources/grafana_slo/resource_complex.tf @@ -12,7 +12,7 @@ resource "grafana_slo" "test" { } alerting { name = "alertingname" - + labels { key = "alertingkey" value = "alertingvalue" From 995f9531d9170ba8052fd9483fca04f03c4c9a39 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Thu, 27 Apr 2023 10:23:52 -0400 Subject: [PATCH 38/51] Updates GAPI Branch --- .terraformrc | 9 --------- go.mod | 2 +- go.sum | 6 ++---- 3 files changed, 3 insertions(+), 14 deletions(-) delete mode 100644 .terraformrc diff --git a/.terraformrc b/.terraformrc deleted file mode 100644 index 2b1557879..000000000 --- a/.terraformrc +++ /dev/null @@ -1,9 +0,0 @@ -provider_installation { - dev_overrides { - "grafana/grafana" = "/Users/elainevuong/go/src/github.com/grafana/terraform-provider-grafana" # this path is the diretory where the binary is built - } - # For all other providers, install them directly from their origin provider - # registries as normal. If you omit this, Terraform will _only_ use - # the dev_overrides block, and so no other providers will be available. - direct {} -} \ No newline at end of file diff --git a/go.mod b/go.mod index 39342deb7..7b3a95bf4 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/grafana/terraform-provider-grafana go 1.18 -replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.19.1-0.20230425232935-96c655f8d67e +replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.20.2-0.20230427142219-5148c466dc7d require ( github.com/Masterminds/semver/v3 v3.2.1 diff --git a/go.sum b/go.sum index db4aaab58..2caf56f58 100644 --- a/go.sum +++ b/go.sum @@ -72,12 +72,10 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/amixr-api-go-client v0.0.7 h1:U6W6yKxMMybI+Qz4zl+Vih48o6CczLaU/vjk2m7omvU= github.com/grafana/amixr-api-go-client v0.0.7/go.mod h1:N6x26XUrM5zGtK5zL5vNJnAn2JFMxLFPPLTw/6pDkFE= - -github.com/grafana/grafana-api-golang-client v0.19.1-0.20230425232935-96c655f8d67e h1:KdyJLIZxSjvKEju385IEwkqDiPD5Se+IelObFyG0u7A= -github.com/grafana/grafana-api-golang-client v0.19.1-0.20230425232935-96c655f8d67e/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= +github.com/grafana/grafana-api-golang-client v0.20.2-0.20230427142219-5148c466dc7d h1:uA8QVOSW5pKLIHeafdfxm0p5KPyUWccE3S4nRAHk09E= +github.com/grafana/grafana-api-golang-client v0.20.2-0.20230427142219-5148c466dc7d/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= github.com/grafana/machine-learning-go-client v0.5.0 h1:Q1K+MPSy8vfMm2jsk3WQ7O77cGr2fM5hxwtPSoPc5NU= github.com/grafana/machine-learning-go-client v0.5.0/go.mod h1:QFfZz8NkqVF8++skjkKQXJEZfpCYd8S0yTWJUpsLLTA= - github.com/grafana/synthetic-monitoring-agent v0.14.3 h1:M7HZmTJKJLF53Fv7KZeb2KyzHSx7gbHiJ3Mr/ApfCRA= github.com/grafana/synthetic-monitoring-agent v0.14.3/go.mod h1:izjAJZb+Q+/GZogJ8Nls6HQlmIUaHUfCtinPCU3HhSw= github.com/grafana/synthetic-monitoring-api-go-client v0.7.0 h1:3ZfQzmXDBPcQTTgMAIIiTw5Dwxm/B4lzf34Sto0d0YY= From d6817011129f97c4bb80dac614818f95b3348369 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Thu, 27 Apr 2023 11:26:29 -0400 Subject: [PATCH 39/51] Add Instructions for Testing SLO Resources --- slo_testing/slo-datasource-read.tf | 18 +++++++ slo_testing/slo-resource-create.tf | 60 ++++++++++++++++++++++ slo_testing/slo-resource-import.tf | 25 +++++++++ slo_testing/slo-testing-README.md | 82 ++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 slo_testing/slo-datasource-read.tf create mode 100644 slo_testing/slo-resource-create.tf create mode 100644 slo_testing/slo-resource-import.tf create mode 100644 slo_testing/slo-testing-README.md diff --git a/slo_testing/slo-datasource-read.tf b/slo_testing/slo-datasource-read.tf new file mode 100644 index 000000000..858d7fa51 --- /dev/null +++ b/slo_testing/slo-datasource-read.tf @@ -0,0 +1,18 @@ +# terraform { +# required_providers { +# grafana = { +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } + +# provider "grafana" { +# url = "https://elainetest.grafana.net/" +# } + +# data "grafana_slo" "test1" { +# } + +# output "test1" { +# value = data.grafana_slo.test1 +# } \ No newline at end of file diff --git a/slo_testing/slo-resource-create.tf b/slo_testing/slo-resource-create.tf new file mode 100644 index 000000000..d2937ab00 --- /dev/null +++ b/slo_testing/slo-resource-create.tf @@ -0,0 +1,60 @@ +# terraform { +# required_providers { +# grafana = { +# source = "registry.terraform.io/grafana/grafana" +# } +# } +# } + +# provider "grafana" { +# url = "https://elainetest.grafana.net/" +# } + +# resource "grafana_slo" "test1" { +# name = "Terraform - Name Test" +# description = "Terraform - Description Test" +# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" +# objectives { +# objective_value = 0.995 +# objective_window = "30d" +# } +# labels { +# key = "custom" +# value = "value" +# } +# alerting { +# fastburn { +# annotations { +# key = "name" +# value = "Critical - SLO Burn Rate Alert" +# } +# annotations { +# key = "description" +# value = "Error Budget Burning Very Quickly" +# } +# labels { +# key = "type" +# value = "slo" +# } +# } + +# slowburn { +# annotations { +# key = "name" +# value = "Warning - SLO Burn Rate Alert" +# } +# annotations { +# key = "description" +# value = "Error Budget Burning Quickly" +# } +# labels { +# key = "type" +# value = "slo" +# } +# } +# } +# } + +# output "test1" { +# value = grafana_slo.test1 +# } diff --git a/slo_testing/slo-resource-import.tf b/slo_testing/slo-resource-import.tf new file mode 100644 index 000000000..43e0619ef --- /dev/null +++ b/slo_testing/slo-resource-import.tf @@ -0,0 +1,25 @@ +terraform { + required_providers { + grafana = { + source = "registry.terraform.io/grafana/grafana" + } + } +} + +provider "grafana" { + url = "https://elainetest.grafana.net/" +} + +resource "grafana_slo" "sample" { + name = "Terraform - Import Test Name" + description = "Terraform - Import Test Description" + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + objectives { + objective_value = 0.995 + objective_window = "30d" + } +} + +output "sample_slo" { + value = grafana_slo.sample +} diff --git a/slo_testing/slo-testing-README.md b/slo_testing/slo-testing-README.md new file mode 100644 index 000000000..4d7a62097 --- /dev/null +++ b/slo_testing/slo-testing-README.md @@ -0,0 +1,82 @@ +# How to Test the SLO Terraform Provider - Hosted Grafana + +# Installation +Install Terraform here - https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli#install-terraform. + +## HG Account Set Up +For members of the SLO Team, you should be able to use the `sloapp.grafana-dev.net` for testing or your own HG Instance. +Within Administration, generate a new Service Account Token. + +Set the environment variable GRAFANA_AUTH to the value of your token `export GRAFANA_AUTH=` +Within the `.tf` files within `slo_testing/hg`, ensure that you set the `url` field to be the `url` of your HG Instance. + +## Creating the TF Binary +1. Within the grafana-terraform-provider root directory, run `go build`. This creates a binary of the terraform-provider-grafana. +2. Within the grafana-terraform-provider root directory, create a file called `.terraformrc` with the following contents. Update the path to the path of your local `grafana-terraform-provider`. This ensures that it will use the local binary version of the terraform-provider-grafana. +``` +provider_installation { + dev_overrides { + "grafana/grafana" = "/path/to/your/grafana/terraform-provider" # this path is the directory where the binary is built + } + # For all other providers, install them directly from their origin provider + # registries as normal. If you omit this, Terraform will _only_ use + # the dev_overrides block, and so no other providers will be available. + direct {} + } +``` + +### Types of Resources +Datasource - datasources are resources that are external to Terraform (i.e. not managed by Terraform state). When interacting with a Datasource, they can be used to READ information, and datasources can also be imported (i.e. converted) into Resources, which allows Terraform state to control them. + +Resources - these are resources that can be managed by Terraform state. This means that you CREATE, READ, UPDATE, DELETE them. + +## Testing Datasource - READ +Objective - we want to send a GET Request to the SLO Endpoint that returns a list of all SLOs, and we want to be able to READ that information and output it to the Terraform CLI. + +1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Within the terraform-provider-grafana root directory, run `go build`. Set the GRAFANA_AUTH environment variable to your HG Grafana API Key, if not already done. +2. Change to the `slo_testing` directory `cd slo_testing`. +3. Within your SLO UI, create a SLO if one does not already exist. +4. Within the `slo-datasource-read.tf` file, ensure the url is set to the url of your HG Instance. +5. Comment out all the `.tf` files within the `slo_testing` folder, EXCEPT for the `slo-datasource-read.tf` file +6. Within the `slo_testing` directory, run the commands `terraform init` and `terraform apply`. +7. You should see a list of all SLOs within your Terraform CLI. + +## Testing Resource - CREATE +Objective - we want to be able to define a SLO Resource within Terraform state that should be created. Once the resource has successfully been created, we want to display the newly created SLO resource within the Terraform CLI. + +The `slo-resource-create.tf` file will create two SLOs. + +1. Change to the `slo_testing/hg` directory. +2. Comment out all the `.tf` files within the `slo_testing` folder, EXCEPT for the `slo-resource-create.tf` file +3. Within the `slo_testing` directory, run the command `terraform apply`. +4. Within your terminal, you should see the output of the a newly created SLO from within Terraform, and the same newly created SLO within the SLO UI. + +## Testing the UPDATE Method +Objective - we want to be able to update a SLO Resource created within Terraform. Once the resource has successfully been modified, we want to display the newly created SLO resource within the Terraform CLI. + +1. Do NOT delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Ensure that this step is executed after testing the CREATE method. +2. Change to the `slo_testing` directory. +3. Comment out all the `.tf` files within the `slo_testing` folder, EXCEPT for the `slo-resource-create.tf` file +4. Modify any of the fields within the `slo-resource-create.tf` file - for example, you can change the `name` field to read `"Updated Terraform - Name Test"`. +5. Within the `slo_testing` directory, run the command `terraform apply`. This should update the resource specified in the terraform state file. +6. Check within the UI that the update was successful. + +## Testing the DELETE/DESTROY Method +Objective - we want to be able to delete a SLO Resource that was created with Terraform. + +1. Do NOT delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Ensure that this step is executed after testing the UPDATE method. +2. Change to the `slo_testing` directory. +3. To delete all Terraformed SLO resources, execute the command `terraform destroy`, and type `yes` in the terminal to confirm the delete +4. Any SLO Resources created with Terraform should be deleted. + +### Testing the IMPORT Method +1. Change to the `slo_testing` directory. +2. Comment out all the `.tf` files within the `slo_testing` folder, EXCEPT for the `slo-resource-import.tf` file +3. Create a SLO using the UI or Postman. Take note of the SLO's UUID +4. Within the Terraform CLI directly, execute the command: `terraform import grafana_slo.sample slo_UUID` +5. Now execute the command: `terraform state show grafana_slo.sample` - you should see the data from the imported Resource. +6. To verify that this resource is now under Terraform control, execute the command `terraform destroy`. This should destroy the resource from within the Terraform CLI. + +### TBD ### +1. Once the GAPI Branch has been approved, remove the `replace` within `go.mod` +2. Remove `slo_testing` folder From 4876002a8ef194214fe5dbc6fefe597b334309ac Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 3 May 2023 12:57:25 -0400 Subject: [PATCH 40/51] Updated Query Types to Support Freeform with Query Type Selector String --- examples/resources/grafana_slo/resource.tf | 5 +- .../resources/grafana_slo/resource_complex.tf | 5 +- .../resources/grafana_slo/resource_update.tf | 5 +- go.mod | 2 +- go.sum | 4 +- internal/resources/slo/data_source_slo.go | 52 +++++--- .../resources/slo/data_source_slo_test.go | 36 +++--- internal/resources/slo/resource_slo.go | 94 ++++++++------ internal/resources/slo/resource_slo_test.go | 27 ++-- slo_testing/slo-datasource-read.tf | 30 ++--- slo_testing/slo-resource-create.tf | 115 +++++++++--------- slo_testing/slo-resource-import.tf | 5 +- 12 files changed, 211 insertions(+), 169 deletions(-) diff --git a/examples/resources/grafana_slo/resource.tf b/examples/resources/grafana_slo/resource.tf index 8ed491c7a..f65af34d8 100644 --- a/examples/resources/grafana_slo/resource.tf +++ b/examples/resources/grafana_slo/resource.tf @@ -1,7 +1,10 @@ resource "grafana_slo" "test" { name = "Terraform Testing" description = "Terraform Description" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + query { + freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + type = "freeform" + } objectives { objective_value = 0.995 objective_window = "30d" diff --git a/examples/resources/grafana_slo/resource_complex.tf b/examples/resources/grafana_slo/resource_complex.tf index d017b47bf..fac3bf56c 100644 --- a/examples/resources/grafana_slo/resource_complex.tf +++ b/examples/resources/grafana_slo/resource_complex.tf @@ -1,7 +1,10 @@ resource "grafana_slo" "test" { name = "Complex Resource - Terraform Testing" description = "Complex Resource - Terraform Description" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + query { + freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + type = "freeform" + } objectives { objective_value = 0.995 objective_window = "30d" diff --git a/examples/resources/grafana_slo/resource_update.tf b/examples/resources/grafana_slo/resource_update.tf index 0a3898ab4..86feb6e52 100644 --- a/examples/resources/grafana_slo/resource_update.tf +++ b/examples/resources/grafana_slo/resource_update.tf @@ -1,7 +1,10 @@ resource "grafana_slo" "update" { name = "Updated - Terraform Testing" description = "Updated - Terraform Description" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + query { + freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + type = "freeform" + } objectives { objective_value = 0.9995 objective_window = "7d" diff --git a/go.mod b/go.mod index 21c404836..47d6839a8 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/grafana/terraform-provider-grafana go 1.18 -replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.20.2-0.20230427142219-5148c466dc7d +replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.20.2-0.20230502174907-adbfb5ee6ca1 require ( github.com/Masterminds/semver/v3 v3.2.1 diff --git a/go.sum b/go.sum index 6a11751fe..cb6458985 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/amixr-api-go-client v0.0.7 h1:U6W6yKxMMybI+Qz4zl+Vih48o6CczLaU/vjk2m7omvU= github.com/grafana/amixr-api-go-client v0.0.7/go.mod h1:N6x26XUrM5zGtK5zL5vNJnAn2JFMxLFPPLTw/6pDkFE= -github.com/grafana/grafana-api-golang-client v0.20.2-0.20230427142219-5148c466dc7d h1:uA8QVOSW5pKLIHeafdfxm0p5KPyUWccE3S4nRAHk09E= -github.com/grafana/grafana-api-golang-client v0.20.2-0.20230427142219-5148c466dc7d/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= +github.com/grafana/grafana-api-golang-client v0.20.2-0.20230502174907-adbfb5ee6ca1 h1:4dUwKiT1+jo//DJbhsPmrUhblCyTZercw2JD72Vpdlk= +github.com/grafana/grafana-api-golang-client v0.20.2-0.20230502174907-adbfb5ee6ca1/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= github.com/grafana/machine-learning-go-client v0.5.0 h1:Q1K+MPSy8vfMm2jsk3WQ7O77cGr2fM5hxwtPSoPc5NU= github.com/grafana/machine-learning-go-client v0.5.0/go.mod h1:QFfZz8NkqVF8++skjkKQXJEZfpCYd8S0yTWJUpsLLTA= github.com/grafana/synthetic-monitoring-agent v0.14.4 h1:amLwPpBvWnqoYHg4Dn2IBdkDy9szcRLr7yCJHMXNhG8= diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 83cc5fa0b..594770067 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -44,9 +44,22 @@ Datasource for retrieving all SLOs. Description: `Description is a free-text field that can provide more context to an SLO.`, }, "query": &schema.Schema{ - Type: schema.TypeString, - Computed: true, + Type: schema.TypeList, + MaxItems: 1, + Required: true, Description: `Query describes the indicator that will be measured against the objective. Freeform Query types are currently supported.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "freeformquery": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, }, "labels": &schema.Schema{ Type: schema.TypeList, @@ -244,12 +257,8 @@ func datasourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{ terraformSlos := []interface{}{} if len(apiSlos.Slos) == 0 { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "No SLOs Exist", - Detail: "No SLOs currently exist. Create a new one.", - }) - + d.SetId("slos") + d.Set("slos", terraformSlos) return diags } @@ -270,10 +279,10 @@ func convertDatasourceSlo(slo gapi.Slo) map[string]interface{} { ret["uuid"] = slo.UUID ret["name"] = slo.Name ret["description"] = slo.Description - ret["dashboard_uid"] = slo.DrilldownDashboardRef.UID + ret["dashboard_uid"] = slo.DrillDownDashboardRef.UID ret["query"] = unpackQuery(slo.Query) - retLabels := unpackLabels(slo.Labels) + retLabels := unpackLabels(&slo.Labels) ret["labels"] = retLabels retObjectives := unpackObjectives(slo.Objectives) @@ -286,12 +295,16 @@ func convertDatasourceSlo(slo gapi.Slo) map[string]interface{} { } // TBD for Other Query Types Once Implemented -func unpackQuery(query gapi.Query) string { - if query.FreeformQuery.Query != "" { - return query.FreeformQuery.Query +func unpackQuery(apiquery gapi.Query) []map[string]interface{} { + retQuery := []map[string]interface{}{} + if apiquery.Freeform.Query != "" { + query := make(map[string]interface{}) + query["freeformquery"] = apiquery.Freeform.Query + query["type"] = "freeform" + retQuery = append(retQuery, query) } - return "Query Type Not Implemented" + return retQuery } func unpackObjectives(objectives []gapi.Objective) []map[string]interface{} { @@ -331,9 +344,8 @@ func unpackAlerting(alertData *gapi.Alerting) []map[string]interface{} { } alertObject := make(map[string]interface{}) - alertObject["name"] = alertData.Name - alertObject["labels"] = unpackLabels(alertData.Labels) - alertObject["annotations"] = unpackLabels(alertData.Annotations) + alertObject["labels"] = unpackLabels(&alertData.Labels) + alertObject["annotations"] = unpackLabels(&alertData.Annotations) if alertData.FastBurn != nil { alertObject["fastburn"] = unpackAlertingMetadata(*alertData.FastBurn) @@ -347,17 +359,17 @@ func unpackAlerting(alertData *gapi.Alerting) []map[string]interface{} { return retAlertData } -func unpackAlertingMetadata(metaData gapi.AlertMetadata) []map[string]interface{} { +func unpackAlertingMetadata(metaData gapi.AlertingMetadata) []map[string]interface{} { retAlertMetaData := []map[string]interface{}{} labelsAnnotsStruct := make(map[string]interface{}) if metaData.Annotations != nil { - retAnnotations := unpackLabels(metaData.Annotations) + retAnnotations := unpackLabels(&metaData.Annotations) labelsAnnotsStruct["annotations"] = retAnnotations } if metaData.Labels != nil { - retLabels := unpackLabels(metaData.Labels) + retLabels := unpackLabels(&metaData.Labels) labelsAnnotsStruct["labels"] = retLabels } diff --git a/internal/resources/slo/data_source_slo_test.go b/internal/resources/slo/data_source_slo_test.go index 770bcff98..c6a17065f 100644 --- a/internal/resources/slo/data_source_slo_test.go +++ b/internal/resources/slo/data_source_slo_test.go @@ -1,23 +1,23 @@ package slo_test -import ( - "regexp" - "testing" +// import ( +// "regexp" +// "testing" - "github.com/grafana/terraform-provider-grafana/internal/testutils" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) +// "github.com/grafana/terraform-provider-grafana/internal/testutils" +// "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +// ) -func TestAccDataSourceSlo(t *testing.T) { - testutils.CheckCloudInstanceTestsEnabled(t) +// func TestAccDataSourceSlo(t *testing.T) { +// testutils.CheckCloudInstanceTestsEnabled(t) - resource.ParallelTest(t, resource.TestCase{ - ProviderFactories: testutils.ProviderFactories, - Steps: []resource.TestStep{ - { - Config: testutils.TestAccExample(t, "data-sources/grafana_slo/data-source.tf"), - ExpectError: regexp.MustCompile(`No SLOs Exist`), - }, - }, - }) -} +// resource.ParallelTest(t, resource.TestCase{ +// ProviderFactories: testutils.ProviderFactories, +// Steps: []resource.TestStep{ +// { +// Config: testutils.TestAccExample(t, "data-sources/grafana_slo/data-source.tf"), +// ExpectError: regexp.MustCompile(`No SLOs Exist`), +// }, +// }, +// }) +// } diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 2d1ba7fe8..6e195e0c1 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -2,6 +2,7 @@ package slo import ( "context" + "errors" "fmt" gapi "github.com/grafana/grafana-api-golang-client" @@ -37,9 +38,23 @@ Resource manages Grafana SLOs. Description: `Description is a free-text field that can provide more context to an SLO.`, }, "query": &schema.Schema{ - Type: schema.TypeString, + Type: schema.TypeList, + MaxItems: 1, Required: true, Description: `Query describes the indicator that will be measured against the objective. Freeform Query types are currently supported.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": &schema.Schema{ + Type: schema.TypeString, + Description: `Query type must be one of: "freeform", "query", "ratio", or "threshold"`, + Required: true, + }, + "freeformquery": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, }, "labels": &schema.Schema{ Type: schema.TypeList, @@ -93,10 +108,6 @@ Resource manages Grafana SLOs. error budget is below a certain threshold.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, "labels": &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -232,7 +243,15 @@ Resource manages Grafana SLOs. func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics - slo := packSloResource(d) + slo, err := packSloResource(d) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Unable to Pack SLO", + Detail: err.Error(), + }) + return diags + } client := m.(*common.Client).GrafanaAPI response, err := client.CreateSlo(slo) @@ -243,14 +262,10 @@ func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{ Summary: "Unable to create SLO - API", Detail: fmt.Sprintf("API Error Message:%s", err.Error()), }) - return diags } - // Get the response back from the API, we need to set the ID of the Terraform Resource d.SetId(response.UUID) - - // Executes a READ, displays the newly created SLO Resource within Terraform resourceSloRead(ctx, d, m) return diags @@ -271,7 +286,6 @@ func resourceSloRead(ctx context.Context, d *schema.ResourceData, m interface{}) Summary: fmt.Sprintf("Unable to Fetch Slo with ID: %s", sloID), Detail: fmt.Sprintf("API Error Message:%s", err.Error()), }) - return diags } @@ -285,18 +299,25 @@ func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{ sloID := d.Id() if d.HasChange("name") || d.HasChange("description") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { - slo := packSloResource(d) + slo, err := packSloResource(d) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Unable to Pack SLO", + Detail: err.Error(), + }) + return diags + } client := m.(*common.Client).GrafanaAPI - err := client.UpdateSlo(sloID, slo) + err = client.UpdateSlo(sloID, slo) if err != nil { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: fmt.Sprintf("Unable to Update Slo with ID: %s", sloID), Detail: fmt.Sprintf("API Error Message:%s", err.Error()), }) - return diags } } @@ -319,7 +340,7 @@ func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{ // Fetches all the Properties defined on the Terraform SLO State Object and converts it // to a Slo so that it can be converted to JSON and sent to the API -func packSloResource(d *schema.ResourceData) gapi.Slo { +func packSloResource(d *schema.ResourceData) (gapi.Slo, error) { var ( tfalerting gapi.Alerting tflabels []gapi.Label @@ -327,8 +348,11 @@ func packSloResource(d *schema.ResourceData) gapi.Slo { tfname := d.Get("name").(string) tfdescription := d.Get("description").(string) - query := d.Get("query").(string) - tfquery := packQuery(query) + query := d.Get("query").([]interface{})[0].(map[string]interface{}) + tfquery, err := packQuery(query) + if err != nil { + return gapi.Slo{}, err + } // Assumes that each SLO only has one Objective Value and one Objective Window objectives := d.Get("objectives").([]interface{}) @@ -353,20 +377,23 @@ func packSloResource(d *schema.ResourceData) gapi.Slo { Objectives: tfobjective, Query: tfquery, Alerting: &tfalerting, - Labels: &tflabels, + Labels: tflabels, } - return slo + return slo, nil } -func packQuery(query string) gapi.Query { - sloQuery := gapi.Query{ - FreeformQuery: gapi.FreeformQuery{ - Query: query, - }, +func packQuery(query map[string]interface{}) (gapi.Query, error) { + + if query["type"] == "freeform" { + sloQuery := gapi.Query{ + Freeform: &gapi.FreeformQuery{Query: query["freeformquery"].(string)}, + Type: gapi.QueryTypeFreeform, + } + return sloQuery, nil } - return sloQuery + return gapi.Query{}, errors.New("Query type not implemented") } func packObjective(tfobjective map[string]interface{}) []gapi.Objective { @@ -411,9 +438,8 @@ func packAlerting(tfAlerting map[string]interface{}) gapi.Alerting { tfSlowBurn := packAlertMetadata(slowBurn) alerting := gapi.Alerting{ - Name: tfAlerting["name"].(string), - Annotations: &tfAnnots, - Labels: &tfLabels, + Annotations: tfAnnots, + Labels: tfLabels, FastBurn: &tfFastBurn, SlowBurn: &tfSlowBurn, } @@ -421,7 +447,7 @@ func packAlerting(tfAlerting map[string]interface{}) gapi.Alerting { return alerting } -func packAlertMetadata(metadata []interface{}) gapi.AlertMetadata { +func packAlertMetadata(metadata []interface{}) gapi.AlertingMetadata { meta := metadata[0].(map[string]interface{}) labels := meta["labels"].([]interface{}) @@ -430,9 +456,9 @@ func packAlertMetadata(metadata []interface{}) gapi.AlertMetadata { annots := meta["annotations"].([]interface{}) tfannots := packLabels(annots) - apiMetadata := gapi.AlertMetadata{ - Labels: &tflabels, - Annotations: &tfannots, + apiMetadata := gapi.AlertingMetadata{ + Labels: tflabels, + Annotations: tfannots, } return apiMetadata @@ -441,10 +467,10 @@ func packAlertMetadata(metadata []interface{}) gapi.AlertMetadata { func setTerraformState(d *schema.ResourceData, slo gapi.Slo) { d.Set("name", slo.Name) d.Set("description", slo.Description) - d.Set("dashboard_uid", slo.DrilldownDashboardRef.UID) + d.Set("dashboard_uid", slo.DrillDownDashboardRef.UID) d.Set("query", unpackQuery(slo.Query)) - retLabels := unpackLabels(slo.Labels) + retLabels := unpackLabels(&slo.Labels) d.Set("labels", retLabels) retObjectives := unpackObjectives(slo.Objectives) diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index 0bcb59269..04735881c 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -28,7 +28,8 @@ func TestAccResourceSlo(t *testing.T) { resource.TestCheckResourceAttrSet("grafana_slo.test", "dashboard_uid"), resource.TestCheckResourceAttr("grafana_slo.test", "name", "Terraform Testing"), resource.TestCheckResourceAttr("grafana_slo.test", "description", "Terraform Description"), - resource.TestCheckResourceAttr("grafana_slo.test", "query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), + resource.TestCheckResourceAttr("grafana_slo.test", "query.0.type", "freeform"), + resource.TestCheckResourceAttr("grafana_slo.test", "query.0.freeformquery", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), resource.TestCheckResourceAttr("grafana_slo.test", "objectives.0.objective_value", "0.995"), resource.TestCheckResourceAttr("grafana_slo.test", "objectives.0.objective_window", "30d"), ), @@ -41,7 +42,8 @@ func TestAccResourceSlo(t *testing.T) { resource.TestCheckResourceAttrSet("grafana_slo.update", "dashboard_uid"), resource.TestCheckResourceAttr("grafana_slo.update", "name", "Updated - Terraform Testing"), resource.TestCheckResourceAttr("grafana_slo.update", "description", "Updated - Terraform Description"), - resource.TestCheckResourceAttr("grafana_slo.update", "query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), + resource.TestCheckResourceAttr("grafana_slo.update", "query.0.type", "freeform"), + resource.TestCheckResourceAttr("grafana_slo.update", "query.0.freeformquery", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), resource.TestCheckResourceAttr("grafana_slo.update", "objectives.0.objective_value", "0.9995"), resource.TestCheckResourceAttr("grafana_slo.update", "objectives.0.objective_window", "7d"), ), @@ -87,23 +89,14 @@ func testAccSloCheckDestroy(slo *gapi.Slo) resource.TestCheckFunc { } } -const sloQueryInvalid = ` -resource "grafana_slo" "invalid" { - name = "Test SLO" - description = "Description Test SLO" - query = "Invalid Query" - objectives { - objective_value = 0.995 - objective_window = "30d" - } -} -` - const sloObjectivesInvalid = ` resource "grafana_slo" "invalid" { name = "Test SLO" description = "Description Test SLO" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + query { + freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + type = "freeform" + } objectives { objective_value = 1.5 objective_window = "1m" @@ -117,10 +110,6 @@ func TestAccResourceInvalidSlo(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ ProviderFactories: testutils.ProviderFactories, Steps: []resource.TestStep{ - { - Config: sloQueryInvalid, - ExpectError: regexp.MustCompile("Unable to create SLO"), - }, { Config: sloObjectivesInvalid, ExpectError: regexp.MustCompile("Unable to create SLO"), diff --git a/slo_testing/slo-datasource-read.tf b/slo_testing/slo-datasource-read.tf index 858d7fa51..db9294e70 100644 --- a/slo_testing/slo-datasource-read.tf +++ b/slo_testing/slo-datasource-read.tf @@ -1,18 +1,18 @@ -# terraform { -# required_providers { -# grafana = { -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } +terraform { + required_providers { + grafana = { + source = "registry.terraform.io/grafana/grafana" + } + } +} -# provider "grafana" { -# url = "https://elainetest.grafana.net/" -# } +provider "grafana" { + url = "https://elainetest.grafana.net/" +} -# data "grafana_slo" "test1" { -# } +data "grafana_slo" "test1" { +} -# output "test1" { -# value = data.grafana_slo.test1 -# } \ No newline at end of file +output "test1" { + value = data.grafana_slo.test1 +} \ No newline at end of file diff --git a/slo_testing/slo-resource-create.tf b/slo_testing/slo-resource-create.tf index d2937ab00..2b1ca20a4 100644 --- a/slo_testing/slo-resource-create.tf +++ b/slo_testing/slo-resource-create.tf @@ -1,60 +1,63 @@ -# terraform { -# required_providers { -# grafana = { -# source = "registry.terraform.io/grafana/grafana" -# } -# } -# } +terraform { + required_providers { + grafana = { + source = "registry.terraform.io/grafana/grafana" + } + } +} -# provider "grafana" { -# url = "https://elainetest.grafana.net/" -# } +provider "grafana" { + url = "https://elainetest.grafana.net/" +} -# resource "grafana_slo" "test1" { -# name = "Terraform - Name Test" -# description = "Terraform - Description Test" -# query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" -# objectives { -# objective_value = 0.995 -# objective_window = "30d" -# } -# labels { -# key = "custom" -# value = "value" -# } -# alerting { -# fastburn { -# annotations { -# key = "name" -# value = "Critical - SLO Burn Rate Alert" -# } -# annotations { -# key = "description" -# value = "Error Budget Burning Very Quickly" -# } -# labels { -# key = "type" -# value = "slo" -# } -# } +resource "grafana_slo" "test2" { + name = "Terraform - Name Test" + description = "Terraform - Description Test" + query { + freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + type = "freeform" + } + objectives { + objective_value = 0.995 + objective_window = "30d" + } + labels { + key = "custom" + value = "value" + } + alerting { + fastburn { + annotations { + key = "name" + value = "Critical - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget Burning Very Quickly" + } + labels { + key = "type" + value = "slo" + } + } -# slowburn { -# annotations { -# key = "name" -# value = "Warning - SLO Burn Rate Alert" -# } -# annotations { -# key = "description" -# value = "Error Budget Burning Quickly" -# } -# labels { -# key = "type" -# value = "slo" -# } -# } -# } -# } + slowburn { + annotations { + key = "name" + value = "Warning - SLO Burn Rate Alert" + } + annotations { + key = "description" + value = "Error Budget Burning Quickly" + } + labels { + key = "type" + value = "slo" + } + } + } +} -# output "test1" { -# value = grafana_slo.test1 -# } +output "test2" { + value = grafana_slo.test2 +} diff --git a/slo_testing/slo-resource-import.tf b/slo_testing/slo-resource-import.tf index 43e0619ef..30ecbd99d 100644 --- a/slo_testing/slo-resource-import.tf +++ b/slo_testing/slo-resource-import.tf @@ -13,7 +13,10 @@ provider "grafana" { resource "grafana_slo" "sample" { name = "Terraform - Import Test Name" description = "Terraform - Import Test Description" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + query { + freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + type = "freeform" + } objectives { objective_value = 0.995 objective_window = "30d" From 3a782583fa0a00c19b3a98fb3c0bff732b9dab02 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 3 May 2023 14:41:07 -0400 Subject: [PATCH 41/51] Added a Datasource Test - CRUDs a Resource and then READs using the Datasource Method --- .../data-sources/grafana_slo/data-source.tf | 40 ++++++++++++++ examples/resources/grafana_slo/resource.tf | 4 +- .../resources/grafana_slo/resource_complex.tf | 4 +- .../resources/grafana_slo/resource_update.tf | 4 +- internal/resources/slo/data_source_slo.go | 14 +++-- .../resources/slo/data_source_slo_test.go | 53 ++++++++++++------- internal/resources/slo/resource_slo.go | 28 +++++----- internal/resources/slo/resource_slo_test.go | 14 ++--- 8 files changed, 112 insertions(+), 49 deletions(-) diff --git a/examples/data-sources/grafana_slo/data-source.tf b/examples/data-sources/grafana_slo/data-source.tf index d69b5dc75..1d4bdec7e 100644 --- a/examples/data-sources/grafana_slo/data-source.tf +++ b/examples/data-sources/grafana_slo/data-source.tf @@ -1 +1,41 @@ +resource "grafana_slo" "test" { + name = "Terraform Testing" + description = "Terraform Description" + query { + freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + type = "freeform" + } + objectives { + value = 0.995 + window = "30d" + } + labels { + key = "custom" + value = "value" + } + alerting { + fastburn { + annotations { + key = "name" + value = "Critical - SLO Burn Rate Alert" + } + labels { + key = "type" + value = "slo" + } + } + + slowburn { + annotations { + key = "name" + value = "Warning - SLO Burn Rate Alert" + } + labels { + key = "type" + value = "slo" + } + } + } +} + data "grafana_slo" "slos" {} \ No newline at end of file diff --git a/examples/resources/grafana_slo/resource.tf b/examples/resources/grafana_slo/resource.tf index f65af34d8..00cc53dea 100644 --- a/examples/resources/grafana_slo/resource.tf +++ b/examples/resources/grafana_slo/resource.tf @@ -6,8 +6,8 @@ resource "grafana_slo" "test" { type = "freeform" } objectives { - objective_value = 0.995 - objective_window = "30d" + value = 0.995 + window = "30d" } labels { key = "custom" diff --git a/examples/resources/grafana_slo/resource_complex.tf b/examples/resources/grafana_slo/resource_complex.tf index fac3bf56c..cdf990e9d 100644 --- a/examples/resources/grafana_slo/resource_complex.tf +++ b/examples/resources/grafana_slo/resource_complex.tf @@ -6,8 +6,8 @@ resource "grafana_slo" "test" { type = "freeform" } objectives { - objective_value = 0.995 - objective_window = "30d" + value = 0.995 + window = "30d" } labels { key = "slokey" diff --git a/examples/resources/grafana_slo/resource_update.tf b/examples/resources/grafana_slo/resource_update.tf index 86feb6e52..11cbb3708 100644 --- a/examples/resources/grafana_slo/resource_update.tf +++ b/examples/resources/grafana_slo/resource_update.tf @@ -6,8 +6,8 @@ resource "grafana_slo" "update" { type = "freeform" } objectives { - objective_value = 0.9995 - objective_window = "7d" + value = 0.9995 + window = "7d" } labels { key = "customkey" diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 594770067..4b6b5c645 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -84,12 +84,12 @@ Datasource for retrieving all SLOs. Description: `Over each rolling time window, the remaining error budget will be calculated, and separate alerts can be generated for each time window based on the SLO burn rate or remaining error budget.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "objective_value": &schema.Schema{ + "value": &schema.Schema{ Type: schema.TypeFloat, Computed: true, Description: `Value between 0 and 1. If the value of the query is above the objective, the SLO is met.`, }, - "objective_window": &schema.Schema{ + "window": &schema.Schema{ Type: schema.TypeString, Computed: true, Description: `A Prometheus-parsable time duration string like 24h, 60m. This is the time window the objective is measured over.`, @@ -279,7 +279,11 @@ func convertDatasourceSlo(slo gapi.Slo) map[string]interface{} { ret["uuid"] = slo.UUID ret["name"] = slo.Name ret["description"] = slo.Description - ret["dashboard_uid"] = slo.DrillDownDashboardRef.UID + + if slo.DrillDownDashboardRef != nil { + ret["dashboard_uid"] = slo.DrillDownDashboardRef.UID + } + ret["query"] = unpackQuery(slo.Query) retLabels := unpackLabels(&slo.Labels) @@ -312,8 +316,8 @@ func unpackObjectives(objectives []gapi.Objective) []map[string]interface{} { for _, objective := range objectives { retObjective := make(map[string]interface{}) - retObjective["objective_value"] = objective.Value - retObjective["objective_window"] = objective.Window + retObjective["value"] = objective.Value + retObjective["window"] = objective.Window retObjectives = append(retObjectives, retObjective) } diff --git a/internal/resources/slo/data_source_slo_test.go b/internal/resources/slo/data_source_slo_test.go index c6a17065f..527124254 100644 --- a/internal/resources/slo/data_source_slo_test.go +++ b/internal/resources/slo/data_source_slo_test.go @@ -1,23 +1,40 @@ package slo_test -// import ( -// "regexp" -// "testing" +import ( + "testing" -// "github.com/grafana/terraform-provider-grafana/internal/testutils" -// "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -// ) + gapi "github.com/grafana/grafana-api-golang-client" + "github.com/grafana/terraform-provider-grafana/internal/testutils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) -// func TestAccDataSourceSlo(t *testing.T) { -// testutils.CheckCloudInstanceTestsEnabled(t) +func TestAccDataSourceSlo(t *testing.T) { + testutils.CheckCloudInstanceTestsEnabled(t) -// resource.ParallelTest(t, resource.TestCase{ -// ProviderFactories: testutils.ProviderFactories, -// Steps: []resource.TestStep{ -// { -// Config: testutils.TestAccExample(t, "data-sources/grafana_slo/data-source.tf"), -// ExpectError: regexp.MustCompile(`No SLOs Exist`), -// }, -// }, -// }) -// } + var slo gapi.Slo + resource.ParallelTest(t, resource.TestCase{ + ProviderFactories: testutils.ProviderFactories, + Steps: []resource.TestStep{ + { + // Creates a SLO Resource + Config: testutils.TestAccExample(t, "resources/grafana_slo/resource.tf"), + Check: resource.ComposeTestCheckFunc( + testAccSloCheckExists("grafana_slo.test", &slo), + resource.TestCheckResourceAttrSet("grafana_slo.test", "id"), + resource.TestCheckResourceAttr("grafana_slo.test", "name", "Terraform Testing"), + resource.TestCheckResourceAttr("grafana_slo.test", "description", "Terraform Description"), + ), + }, + { + // Verifies that the created SLO Resource is read by the Datasource Read Method + Config: testutils.TestAccExample(t, "data-sources/grafana_slo/data-source.tf"), + Check: resource.ComposeTestCheckFunc( + + resource.TestCheckResourceAttrSet("data.grafana_slo.slos", "slos.0.uuid"), + resource.TestCheckResourceAttr("data.grafana_slo.slos", "slos.0.name", "Terraform Testing"), + resource.TestCheckResourceAttr("data.grafana_slo.slos", "slos.0.description", "Terraform Description"), + ), + }, + }, + }) +} diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 6e195e0c1..c37cad808 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -75,17 +75,16 @@ Resource manages Grafana SLOs. }, "objectives": &schema.Schema{ Type: schema.TypeList, - MaxItems: 1, Required: true, Description: `Over each rolling time window, the remaining error budget will be calculated, and separate alerts can be generated for each time window based on the SLO burn rate or remaining error budget.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "objective_value": &schema.Schema{ + "value": &schema.Schema{ Type: schema.TypeFloat, Required: true, Description: `Value between 0 and 1. If the value of the query is above the objective, the SLO is met.`, }, - "objective_window": &schema.Schema{ + "window": &schema.Schema{ Type: schema.TypeString, Required: true, Description: `A Prometheus-parsable time duration string like 24h, 60m. This is the time window the objective is measured over.`, @@ -356,8 +355,8 @@ func packSloResource(d *schema.ResourceData) (gapi.Slo, error) { // Assumes that each SLO only has one Objective Value and one Objective Window objectives := d.Get("objectives").([]interface{}) - objective := objectives[0].(map[string]interface{}) - tfobjective := packObjective(objective) + // objective := objectives[0].(map[string]interface{}) + tfobjective := packObjectives(objectives) labels := d.Get("labels").([]interface{}) if labels != nil { @@ -396,16 +395,19 @@ func packQuery(query map[string]interface{}) (gapi.Query, error) { return gapi.Query{}, errors.New("Query type not implemented") } -func packObjective(tfobjective map[string]interface{}) []gapi.Objective { - objective := gapi.Objective{ - Value: tfobjective["objective_value"].(float64), - Window: tfobjective["objective_window"].(string), - } +func packObjectives(tfobjectives []interface{}) []gapi.Objective { + objectives := []gapi.Objective{} - objectiveSlice := []gapi.Objective{} - objectiveSlice = append(objectiveSlice, objective) + for ind := range tfobjectives { + tfobjective := tfobjectives[ind].(map[string]interface{}) + objective := gapi.Objective{ + Value: tfobjective["value"].(float64), + Window: tfobjective["window"].(string), + } + objectives = append(objectives, objective) + } - return objectiveSlice + return objectives } func packLabels(tfLabels []interface{}) []gapi.Label { diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index 04735881c..bf0df5bec 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -18,7 +18,7 @@ func TestAccResourceSlo(t *testing.T) { var slo gapi.Slo resource.ParallelTest(t, resource.TestCase{ ProviderFactories: testutils.ProviderFactories, - CheckDestroy: testAccSloCheckDestroy(&slo), + // CheckDestroy: testAccSloCheckDestroy(&slo), Steps: []resource.TestStep{ { Config: testutils.TestAccExample(t, "resources/grafana_slo/resource.tf"), @@ -30,8 +30,8 @@ func TestAccResourceSlo(t *testing.T) { resource.TestCheckResourceAttr("grafana_slo.test", "description", "Terraform Description"), resource.TestCheckResourceAttr("grafana_slo.test", "query.0.type", "freeform"), resource.TestCheckResourceAttr("grafana_slo.test", "query.0.freeformquery", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), - resource.TestCheckResourceAttr("grafana_slo.test", "objectives.0.objective_value", "0.995"), - resource.TestCheckResourceAttr("grafana_slo.test", "objectives.0.objective_window", "30d"), + resource.TestCheckResourceAttr("grafana_slo.test", "objectives.0.value", "0.995"), + resource.TestCheckResourceAttr("grafana_slo.test", "objectives.0.window", "30d"), ), }, { @@ -44,8 +44,8 @@ func TestAccResourceSlo(t *testing.T) { resource.TestCheckResourceAttr("grafana_slo.update", "description", "Updated - Terraform Description"), resource.TestCheckResourceAttr("grafana_slo.update", "query.0.type", "freeform"), resource.TestCheckResourceAttr("grafana_slo.update", "query.0.freeformquery", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), - resource.TestCheckResourceAttr("grafana_slo.update", "objectives.0.objective_value", "0.9995"), - resource.TestCheckResourceAttr("grafana_slo.update", "objectives.0.objective_window", "7d"), + resource.TestCheckResourceAttr("grafana_slo.update", "objectives.0.value", "0.9995"), + resource.TestCheckResourceAttr("grafana_slo.update", "objectives.0.window", "7d"), ), }, }, @@ -98,8 +98,8 @@ resource "grafana_slo" "invalid" { type = "freeform" } objectives { - objective_value = 1.5 - objective_window = "1m" + value = 1.5 + window = "1m" } } ` From 1d99d70ac7a2c4b2b0b08f0e10ab01da66b83d04 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 3 May 2023 14:47:06 -0400 Subject: [PATCH 42/51] Generate Documentation --- docs/data-sources/{slo.md => slos.md} | 61 +++++++++++++++++-- docs/resources/slo.md | 27 +++++--- .../data-source.tf | 2 +- internal/provider/provider.go | 2 +- .../resources/slo/data_source_slo_test.go | 8 +-- slo_testing/slo-datasource-read.tf | 4 +- tools/subcategories.json | 2 +- 7 files changed, 83 insertions(+), 23 deletions(-) rename docs/data-sources/{slo.md => slos.md} (75%) rename examples/data-sources/{grafana_slo => grafana_slos}/data-source.tf (96%) diff --git a/docs/data-sources/slo.md b/docs/data-sources/slos.md similarity index 75% rename from docs/data-sources/slo.md rename to docs/data-sources/slos.md index f712b1388..e63ad757f 100644 --- a/docs/data-sources/slo.md +++ b/docs/data-sources/slos.md @@ -1,13 +1,13 @@ --- # generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "grafana_slo Data Source - terraform-provider-grafana" +page_title: "grafana_slos Data Source - terraform-provider-grafana" subcategory: "SLO" description: |- Datasource for retrieving all SLOs. Official documentation https://grafana.com/docs/grafana-cloud/slo/API documentation https://grafana.com/docs/grafana-cloud/slo/api/ --- -# grafana_slo (Data Source) +# grafana_slos (Data Source) Datasource for retrieving all SLOs. @@ -17,7 +17,47 @@ Datasource for retrieving all SLOs. ## Example Usage ```terraform -data "grafana_slo" "slos" {} +resource "grafana_slos" "test" { + name = "Terraform Testing" + description = "Terraform Description" + query { + freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + type = "freeform" + } + objectives { + value = 0.995 + window = "30d" + } + labels { + key = "custom" + value = "value" + } + alerting { + fastburn { + annotations { + key = "name" + value = "Critical - SLO Burn Rate Alert" + } + labels { + key = "type" + value = "slo" + } + } + + slowburn { + annotations { + key = "name" + value = "Warning - SLO Burn Rate Alert" + } + labels { + key = "type" + value = "slo" + } + } + } +} + +data "grafana_slos" "slos" {} ``` @@ -39,7 +79,7 @@ Read-Only: - `labels` (List of Object) (see [below for nested schema](#nestedobjatt--slos--labels)) - `name` (String) - `objectives` (List of Object) (see [below for nested schema](#nestedobjatt--slos--objectives)) -- `query` (String) +- `query` (List of Object) (see [below for nested schema](#nestedobjatt--slos--query)) - `uuid` (String) @@ -140,7 +180,16 @@ Read-Only: Read-Only: -- `objective_value` (Number) -- `objective_window` (String) +- `value` (Number) +- `window` (String) + + + +### Nested Schema for `slos.query` + +Read-Only: + +- `freeformquery` (String) +- `type` (String) diff --git a/docs/resources/slo.md b/docs/resources/slo.md index e603674ec..3a677f08a 100644 --- a/docs/resources/slo.md +++ b/docs/resources/slo.md @@ -20,10 +20,13 @@ Resource manages Grafana SLOs. resource "grafana_slo" "test" { name = "Terraform Testing" description = "Terraform Description" - query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + query { + freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + type = "freeform" + } objectives { - objective_value = 0.995 - objective_window = "30d" + value = 0.995 + window = "30d" } labels { key = "custom" @@ -62,8 +65,8 @@ resource "grafana_slo" "test" { - `description` (String) Description is a free-text field that can provide more context to an SLO. - `name` (String) Name should be a short description of your indicator. Consider names like "API Availability" -- `objectives` (Block List, Min: 1, Max: 1) Over each rolling time window, the remaining error budget will be calculated, and separate alerts can be generated for each time window based on the SLO burn rate or remaining error budget. (see [below for nested schema](#nestedblock--objectives)) -- `query` (String) Query describes the indicator that will be measured against the objective. Freeform Query types are currently supported. +- `objectives` (Block List, Min: 1) Over each rolling time window, the remaining error budget will be calculated, and separate alerts can be generated for each time window based on the SLO burn rate or remaining error budget. (see [below for nested schema](#nestedblock--objectives)) +- `query` (Block List, Min: 1, Max: 1) Query describes the indicator that will be measured against the objective. Freeform Query types are currently supported. (see [below for nested schema](#nestedblock--query)) ### Optional @@ -84,8 +87,17 @@ resource "grafana_slo" "test" { Required: -- `objective_value` (Number) Value between 0 and 1. If the value of the query is above the objective, the SLO is met. -- `objective_window` (String) A Prometheus-parsable time duration string like 24h, 60m. This is the time window the objective is measured over. +- `value` (Number) Value between 0 and 1. If the value of the query is above the objective, the SLO is met. +- `window` (String) A Prometheus-parsable time duration string like 24h, 60m. This is the time window the objective is measured over. + + + +### Nested Schema for `query` + +Required: + +- `freeformquery` (String) +- `type` (String) Query type must be one of: "freeform", "query", "ratio", or "threshold" @@ -96,7 +108,6 @@ Optional: - `annotations` (Block List) Annotations will be attached to all alerts generated by any of these rules. (see [below for nested schema](#nestedblock--alerting--annotations)) - `fastburn` (Block List) Alerting Rules generated for Fast Burn alerts (see [below for nested schema](#nestedblock--alerting--fastburn)) - `labels` (Block List) Labels will be attached to all alerts generated by any of these rules. (see [below for nested schema](#nestedblock--alerting--labels)) -- `name` (String) - `slowburn` (Block List) Alerting Rules generated for Slow Burn alerts (see [below for nested schema](#nestedblock--alerting--slowburn)) diff --git a/examples/data-sources/grafana_slo/data-source.tf b/examples/data-sources/grafana_slos/data-source.tf similarity index 96% rename from examples/data-sources/grafana_slo/data-source.tf rename to examples/data-sources/grafana_slos/data-source.tf index 1d4bdec7e..ad838be88 100644 --- a/examples/data-sources/grafana_slo/data-source.tf +++ b/examples/data-sources/grafana_slos/data-source.tf @@ -38,4 +38,4 @@ resource "grafana_slo" "test" { } } -data "grafana_slo" "slos" {} \ No newline at end of file +data "grafana_slos" "slos" {} \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index c7526b1f3..cece5933c 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -127,7 +127,7 @@ func Provider(version string) func() *schema.Provider { "grafana_organization_preferences": grafana.DatasourceOrganizationPreferences(), // SLO - "grafana_slo": slo.DatasourceSlo(), + "grafana_slos": slo.DatasourceSlo(), }) // Datasources that require the Synthetic Monitoring client to exist. diff --git a/internal/resources/slo/data_source_slo_test.go b/internal/resources/slo/data_source_slo_test.go index 527124254..aaa4de0bf 100644 --- a/internal/resources/slo/data_source_slo_test.go +++ b/internal/resources/slo/data_source_slo_test.go @@ -27,12 +27,12 @@ func TestAccDataSourceSlo(t *testing.T) { }, { // Verifies that the created SLO Resource is read by the Datasource Read Method - Config: testutils.TestAccExample(t, "data-sources/grafana_slo/data-source.tf"), + Config: testutils.TestAccExample(t, "data-sources/grafana_slos/data-source.tf"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("data.grafana_slo.slos", "slos.0.uuid"), - resource.TestCheckResourceAttr("data.grafana_slo.slos", "slos.0.name", "Terraform Testing"), - resource.TestCheckResourceAttr("data.grafana_slo.slos", "slos.0.description", "Terraform Description"), + resource.TestCheckResourceAttrSet("data.grafana_slos.slos", "slos.0.uuid"), + resource.TestCheckResourceAttr("data.grafana_slos.slos", "slos.0.name", "Terraform Testing"), + resource.TestCheckResourceAttr("data.grafana_slos.slos", "slos.0.description", "Terraform Description"), ), }, }, diff --git a/slo_testing/slo-datasource-read.tf b/slo_testing/slo-datasource-read.tf index db9294e70..34cfdc2d1 100644 --- a/slo_testing/slo-datasource-read.tf +++ b/slo_testing/slo-datasource-read.tf @@ -10,9 +10,9 @@ provider "grafana" { url = "https://elainetest.grafana.net/" } -data "grafana_slo" "test1" { +data "grafana_slos" "test1" { } output "test1" { - value = data.grafana_slo.test1 + value = data.grafana_slos.test1 } \ No newline at end of file diff --git a/tools/subcategories.json b/tools/subcategories.json index 1e757dec0..b23638e20 100644 --- a/tools/subcategories.json +++ b/tools/subcategories.json @@ -73,7 +73,7 @@ "data-sources/oncall_team": "OnCall", "data-sources/oncall_user": "OnCall", "data-sources/oncall_user_group": "OnCall", - "data-sources/slo": "SLO", + "data-sources/slos": "SLO", "data-sources/synthetic_monitoring_probe": "Synthetic Monitoring", "data-sources/synthetic_monitoring_probes": "Synthetic Monitoring" } From 52806adda9406285b4886fcdb0dc55f771eed4dc Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 3 May 2023 14:57:55 -0400 Subject: [PATCH 43/51] Linting --- docs/data-sources/slos.md | 2 +- internal/resources/slo/resource_slo.go | 3 +-- internal/resources/slo/resource_slo_test.go | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/data-sources/slos.md b/docs/data-sources/slos.md index e63ad757f..f5a961163 100644 --- a/docs/data-sources/slos.md +++ b/docs/data-sources/slos.md @@ -17,7 +17,7 @@ Datasource for retrieving all SLOs. ## Example Usage ```terraform -resource "grafana_slos" "test" { +resource "grafana_slo" "test" { name = "Terraform Testing" description = "Terraform Description" query { diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index c37cad808..641c88e9f 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -383,7 +383,6 @@ func packSloResource(d *schema.ResourceData) (gapi.Slo, error) { } func packQuery(query map[string]interface{}) (gapi.Query, error) { - if query["type"] == "freeform" { sloQuery := gapi.Query{ Freeform: &gapi.FreeformQuery{Query: query["freeformquery"].(string)}, @@ -392,7 +391,7 @@ func packQuery(query map[string]interface{}) (gapi.Query, error) { return sloQuery, nil } - return gapi.Query{}, errors.New("Query type not implemented") + return gapi.Query{}, errors.New("query type not implemented") } func packObjectives(tfobjectives []interface{}) []gapi.Objective { diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index bf0df5bec..abb992c73 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -18,7 +18,7 @@ func TestAccResourceSlo(t *testing.T) { var slo gapi.Slo resource.ParallelTest(t, resource.TestCase{ ProviderFactories: testutils.ProviderFactories, - // CheckDestroy: testAccSloCheckDestroy(&slo), + CheckDestroy: testAccSloCheckDestroy(&slo), Steps: []resource.TestStep{ { Config: testutils.TestAccExample(t, "resources/grafana_slo/resource.tf"), From c342794e6c424b231403a3e1462e7c265b8a6440 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Wed, 3 May 2023 15:00:15 -0400 Subject: [PATCH 44/51] Terraform fmt --- docs/data-sources/slos.md | 2 +- docs/resources/slo.md | 2 +- examples/data-sources/grafana_slos/data-source.tf | 2 +- examples/resources/grafana_slo/resource.tf | 2 +- examples/resources/grafana_slo/resource_complex.tf | 2 +- examples/resources/grafana_slo/resource_update.tf | 2 +- slo_testing/slo-datasource-read.tf | 2 +- slo_testing/slo-resource-create.tf | 6 +++--- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/data-sources/slos.md b/docs/data-sources/slos.md index f5a961163..f96c2dcea 100644 --- a/docs/data-sources/slos.md +++ b/docs/data-sources/slos.md @@ -22,7 +22,7 @@ resource "grafana_slo" "test" { description = "Terraform Description" query { freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - type = "freeform" + type = "freeform" } objectives { value = 0.995 diff --git a/docs/resources/slo.md b/docs/resources/slo.md index 3a677f08a..26836cd89 100644 --- a/docs/resources/slo.md +++ b/docs/resources/slo.md @@ -22,7 +22,7 @@ resource "grafana_slo" "test" { description = "Terraform Description" query { freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - type = "freeform" + type = "freeform" } objectives { value = 0.995 diff --git a/examples/data-sources/grafana_slos/data-source.tf b/examples/data-sources/grafana_slos/data-source.tf index ad838be88..7287d8b4f 100644 --- a/examples/data-sources/grafana_slos/data-source.tf +++ b/examples/data-sources/grafana_slos/data-source.tf @@ -3,7 +3,7 @@ resource "grafana_slo" "test" { description = "Terraform Description" query { freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - type = "freeform" + type = "freeform" } objectives { value = 0.995 diff --git a/examples/resources/grafana_slo/resource.tf b/examples/resources/grafana_slo/resource.tf index 00cc53dea..600c9541e 100644 --- a/examples/resources/grafana_slo/resource.tf +++ b/examples/resources/grafana_slo/resource.tf @@ -3,7 +3,7 @@ resource "grafana_slo" "test" { description = "Terraform Description" query { freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - type = "freeform" + type = "freeform" } objectives { value = 0.995 diff --git a/examples/resources/grafana_slo/resource_complex.tf b/examples/resources/grafana_slo/resource_complex.tf index cdf990e9d..d41273499 100644 --- a/examples/resources/grafana_slo/resource_complex.tf +++ b/examples/resources/grafana_slo/resource_complex.tf @@ -3,7 +3,7 @@ resource "grafana_slo" "test" { description = "Complex Resource - Terraform Description" query { freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - type = "freeform" + type = "freeform" } objectives { value = 0.995 diff --git a/examples/resources/grafana_slo/resource_update.tf b/examples/resources/grafana_slo/resource_update.tf index 11cbb3708..5326d9baf 100644 --- a/examples/resources/grafana_slo/resource_update.tf +++ b/examples/resources/grafana_slo/resource_update.tf @@ -3,7 +3,7 @@ resource "grafana_slo" "update" { description = "Updated - Terraform Description" query { freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - type = "freeform" + type = "freeform" } objectives { value = 0.9995 diff --git a/slo_testing/slo-datasource-read.tf b/slo_testing/slo-datasource-read.tf index 34cfdc2d1..ef2450f81 100644 --- a/slo_testing/slo-datasource-read.tf +++ b/slo_testing/slo-datasource-read.tf @@ -1,7 +1,7 @@ terraform { required_providers { grafana = { - source = "registry.terraform.io/grafana/grafana" + source = "registry.terraform.io/grafana/grafana" } } } diff --git a/slo_testing/slo-resource-create.tf b/slo_testing/slo-resource-create.tf index 2b1ca20a4..a48d91853 100644 --- a/slo_testing/slo-resource-create.tf +++ b/slo_testing/slo-resource-create.tf @@ -1,7 +1,7 @@ terraform { required_providers { - grafana = { - source = "registry.terraform.io/grafana/grafana" + grafana = { + source = "registry.terraform.io/grafana/grafana" } } } @@ -15,7 +15,7 @@ resource "grafana_slo" "test2" { description = "Terraform - Description Test" query { freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - type = "freeform" + type = "freeform" } objectives { objective_value = 0.995 From 0979a5b27b6407ce6a26ac8e957d6fefeff72830 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Thu, 4 May 2023 10:52:21 -0400 Subject: [PATCH 45/51] Formatting --- internal/resources/slo/resource_slo.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 641c88e9f..b52d040bb 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -234,11 +234,6 @@ Resource manages Grafana SLOs. } } -// SLO Resource is defined by the user within the Terraform State file -// When 'terraform apply' is executed, it sends a POST Request and converts -// the data within the Terraform State into a JSON Object which is then sent to the API -// Following this, a READ is executed for the newly created SLO, which is then displayed within the -// terminal that Terraform is running in func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics From a6e826cec405474e28ce826b839ee1bde7a5be11 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Fri, 5 May 2023 13:42:47 -0400 Subject: [PATCH 46/51] Address Julien's Comments --- go.mod | 4 +- go.sum | 4 +- .../resources/slo/data_source_slo_test.go | 1 + internal/resources/slo/resource_slo.go | 156 ++++++------------ internal/resources/slo/resource_slo_test.go | 2 +- slo_testing/slo-datasource-read.tf | 18 -- slo_testing/slo-resource-create.tf | 63 ------- slo_testing/slo-resource-import.tf | 28 ---- slo_testing/slo-testing-README.md | 82 --------- 9 files changed, 51 insertions(+), 307 deletions(-) delete mode 100644 slo_testing/slo-datasource-read.tf delete mode 100644 slo_testing/slo-resource-create.tf delete mode 100644 slo_testing/slo-resource-import.tf delete mode 100644 slo_testing/slo-testing-README.md diff --git a/go.mod b/go.mod index df17d862f..66874fea7 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,10 @@ module github.com/grafana/terraform-provider-grafana go 1.20 -replace github.com/grafana/grafana-api-golang-client => github.com/grafana/grafana-api-golang-client v0.20.2-0.20230502174907-adbfb5ee6ca1 - require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/grafana/amixr-api-go-client v0.0.7 - github.com/grafana/grafana-api-golang-client v0.20.1 + github.com/grafana/grafana-api-golang-client v0.21.0 github.com/grafana/machine-learning-go-client v0.5.0 github.com/grafana/synthetic-monitoring-agent v0.14.5 github.com/grafana/synthetic-monitoring-api-go-client v0.7.0 diff --git a/go.sum b/go.sum index e27e9043c..731dc0b59 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/amixr-api-go-client v0.0.7 h1:U6W6yKxMMybI+Qz4zl+Vih48o6CczLaU/vjk2m7omvU= github.com/grafana/amixr-api-go-client v0.0.7/go.mod h1:N6x26XUrM5zGtK5zL5vNJnAn2JFMxLFPPLTw/6pDkFE= -github.com/grafana/grafana-api-golang-client v0.20.2-0.20230502174907-adbfb5ee6ca1 h1:4dUwKiT1+jo//DJbhsPmrUhblCyTZercw2JD72Vpdlk= -github.com/grafana/grafana-api-golang-client v0.20.2-0.20230502174907-adbfb5ee6ca1/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= +github.com/grafana/grafana-api-golang-client v0.21.0 h1:PQ2Wfo9jMMiftC4VRMlJxbUNvYCXMV1YFDKm7Ny3SaM= +github.com/grafana/grafana-api-golang-client v0.21.0/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E= github.com/grafana/machine-learning-go-client v0.5.0 h1:Q1K+MPSy8vfMm2jsk3WQ7O77cGr2fM5hxwtPSoPc5NU= github.com/grafana/machine-learning-go-client v0.5.0/go.mod h1:QFfZz8NkqVF8++skjkKQXJEZfpCYd8S0yTWJUpsLLTA= github.com/grafana/synthetic-monitoring-agent v0.14.5 h1:zYuzieZeDNczPZAMIqCNh8QKJ28V571iCKiOheic/g8= diff --git a/internal/resources/slo/data_source_slo_test.go b/internal/resources/slo/data_source_slo_test.go index aaa4de0bf..c723ac8e4 100644 --- a/internal/resources/slo/data_source_slo_test.go +++ b/internal/resources/slo/data_source_slo_test.go @@ -14,6 +14,7 @@ func TestAccDataSourceSlo(t *testing.T) { var slo gapi.Slo resource.ParallelTest(t, resource.TestCase{ ProviderFactories: testutils.ProviderFactories, + CheckDestroy: testAccSloCheckDestroy(&slo), Steps: []resource.TestStep{ { // Creates a SLO Resource diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index b52d040bb..69a81b931 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -4,11 +4,14 @@ import ( "context" "errors" "fmt" + "regexp" gapi "github.com/grafana/grafana-api-golang-client" + "github.com/grafana/terraform-provider-grafana/internal/common" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) func ResourceSlo() *schema.Resource { @@ -28,26 +31,28 @@ Resource manages Grafana SLOs. }, Schema: map[string]*schema.Schema{ "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - Description: `Name should be a short description of your indicator. Consider names like "API Availability"`, + Type: schema.TypeString, + Required: true, + Description: `Name should be a short description of your indicator. Consider names like "API Availability"`, + ValidateFunc: validation.StringLenBetween(0, 128), }, "description": &schema.Schema{ - Type: schema.TypeString, - Required: true, - Description: `Description is a free-text field that can provide more context to an SLO.`, + Type: schema.TypeString, + Required: true, + Description: `Description is a free-text field that can provide more context to an SLO.`, + ValidateFunc: validation.StringLenBetween(0, 1024), }, "query": &schema.Schema{ Type: schema.TypeList, - MaxItems: 1, Required: true, Description: `Query describes the indicator that will be measured against the objective. Freeform Query types are currently supported.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "type": &schema.Schema{ - Type: schema.TypeString, - Description: `Query type must be one of: "freeform", "query", "ratio", or "threshold"`, - Required: true, + Type: schema.TypeString, + Description: `Query type must be one of: "freeform", "query", "ratio", or "threshold"`, + ValidateFunc: validation.StringInSlice([]string{"freeform", "query", "ratio", "threshold"}, false), + Required: true, }, "freeformquery": &schema.Schema{ Type: schema.TypeString, @@ -60,18 +65,7 @@ Resource manages Grafana SLOs. Type: schema.TypeList, Optional: true, Description: `Additional labels that will be attached to all metrics generated from the query. These labels are useful for grouping SLOs in dashboard views that you create by hand.`, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - }, - }, + Elem: keyvalueSchema, }, "objectives": &schema.Schema{ Type: schema.TypeList, @@ -80,14 +74,16 @@ Resource manages Grafana SLOs. Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "value": &schema.Schema{ - Type: schema.TypeFloat, - Required: true, - Description: `Value between 0 and 1. If the value of the query is above the objective, the SLO is met.`, + Type: schema.TypeFloat, + Required: true, + ValidateFunc: validation.FloatBetween(0, 1), + Description: `Value between 0 and 1. If the value of the query is above the objective, the SLO is met.`, }, "window": &schema.Schema{ - Type: schema.TypeString, - Required: true, - Description: `A Prometheus-parsable time duration string like 24h, 60m. This is the time window the objective is measured over.`, + Type: schema.TypeString, + Required: true, + Description: `A Prometheus-parsable time duration string like 24h, 60m. This is the time window the objective is measured over.`, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^\d+(ms|s|m|h|d|w|y)$`), "Objective window must be a Prometheus-parsable time duration"), }, }, }, @@ -111,35 +107,13 @@ Resource manages Grafana SLOs. Type: schema.TypeList, Optional: true, Description: `Labels will be attached to all alerts generated by any of these rules.`, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - }, - }, + Elem: keyvalueSchema, }, "annotations": &schema.Schema{ Type: schema.TypeList, Optional: true, Description: `Annotations will be attached to all alerts generated by any of these rules.`, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - }, - }, + Elem: keyvalueSchema, }, "fastburn": &schema.Schema{ Type: schema.TypeList, @@ -151,35 +125,13 @@ Resource manages Grafana SLOs. Type: schema.TypeList, Optional: true, Description: "Labels to attach only to Fast Burn alerts.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - }, - }, + Elem: keyvalueSchema, }, "annotations": &schema.Schema{ Type: schema.TypeList, Optional: true, Description: "Annotations to attach only to Fast Burn alerts.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - }, - }, + Elem: keyvalueSchema, }, }, }, @@ -194,35 +146,13 @@ Resource manages Grafana SLOs. Type: schema.TypeList, Optional: true, Description: "Labels to attach only to Slow Burn alerts.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - }, - }, + Elem: keyvalueSchema, }, "annotations": &schema.Schema{ Type: schema.TypeList, Optional: true, Description: "Annotations to attach only to Slow Burn alerts.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - }, - }, + Elem: keyvalueSchema, }, }, }, @@ -234,6 +164,19 @@ Resource manages Grafana SLOs. } } +var keyvalueSchema = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, +} + func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics @@ -262,7 +205,7 @@ func resourceSloCreate(ctx context.Context, d *schema.ResourceData, m interface{ d.SetId(response.UUID) resourceSloRead(ctx, d, m) - return diags + return resourceSloRead(ctx, d, m) } // resourceSloRead - sends a GET Request to the single SLO Endpoint @@ -320,16 +263,11 @@ func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{ } func resourceSloDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics - sloID := d.Id() client := m.(*common.Client).GrafanaAPI - client.DeleteSlo(sloID) - d.SetId("") - - return diags + return diag.FromErr(client.DeleteSlo(sloID)) } // Fetches all the Properties defined on the Terraform SLO State Object and converts it @@ -348,9 +286,7 @@ func packSloResource(d *schema.ResourceData) (gapi.Slo, error) { return gapi.Slo{}, err } - // Assumes that each SLO only has one Objective Value and one Objective Window objectives := d.Get("objectives").([]interface{}) - // objective := objectives[0].(map[string]interface{}) tfobjective := packObjectives(objectives) labels := d.Get("labels").([]interface{}) @@ -386,7 +322,7 @@ func packQuery(query map[string]interface{}) (gapi.Query, error) { return sloQuery, nil } - return gapi.Query{}, errors.New("query type not implemented") + return gapi.Query{}, errors.New(fmt.Sprintf("%s query type not implemented", query["type"])) } func packObjectives(tfobjectives []interface{}) []gapi.Objective { diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index abb992c73..52b991714 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -112,7 +112,7 @@ func TestAccResourceInvalidSlo(t *testing.T) { Steps: []resource.TestStep{ { Config: sloObjectivesInvalid, - ExpectError: regexp.MustCompile("Unable to create SLO"), + ExpectError: regexp.MustCompile("Error:"), }, }, }) diff --git a/slo_testing/slo-datasource-read.tf b/slo_testing/slo-datasource-read.tf deleted file mode 100644 index ef2450f81..000000000 --- a/slo_testing/slo-datasource-read.tf +++ /dev/null @@ -1,18 +0,0 @@ -terraform { - required_providers { - grafana = { - source = "registry.terraform.io/grafana/grafana" - } - } -} - -provider "grafana" { - url = "https://elainetest.grafana.net/" -} - -data "grafana_slos" "test1" { -} - -output "test1" { - value = data.grafana_slos.test1 -} \ No newline at end of file diff --git a/slo_testing/slo-resource-create.tf b/slo_testing/slo-resource-create.tf deleted file mode 100644 index a48d91853..000000000 --- a/slo_testing/slo-resource-create.tf +++ /dev/null @@ -1,63 +0,0 @@ -terraform { - required_providers { - grafana = { - source = "registry.terraform.io/grafana/grafana" - } - } -} - -provider "grafana" { - url = "https://elainetest.grafana.net/" -} - -resource "grafana_slo" "test2" { - name = "Terraform - Name Test" - description = "Terraform - Description Test" - query { - freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - type = "freeform" - } - objectives { - objective_value = 0.995 - objective_window = "30d" - } - labels { - key = "custom" - value = "value" - } - alerting { - fastburn { - annotations { - key = "name" - value = "Critical - SLO Burn Rate Alert" - } - annotations { - key = "description" - value = "Error Budget Burning Very Quickly" - } - labels { - key = "type" - value = "slo" - } - } - - slowburn { - annotations { - key = "name" - value = "Warning - SLO Burn Rate Alert" - } - annotations { - key = "description" - value = "Error Budget Burning Quickly" - } - labels { - key = "type" - value = "slo" - } - } - } -} - -output "test2" { - value = grafana_slo.test2 -} diff --git a/slo_testing/slo-resource-import.tf b/slo_testing/slo-resource-import.tf deleted file mode 100644 index 30ecbd99d..000000000 --- a/slo_testing/slo-resource-import.tf +++ /dev/null @@ -1,28 +0,0 @@ -terraform { - required_providers { - grafana = { - source = "registry.terraform.io/grafana/grafana" - } - } -} - -provider "grafana" { - url = "https://elainetest.grafana.net/" -} - -resource "grafana_slo" "sample" { - name = "Terraform - Import Test Name" - description = "Terraform - Import Test Description" - query { - freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" - type = "freeform" - } - objectives { - objective_value = 0.995 - objective_window = "30d" - } -} - -output "sample_slo" { - value = grafana_slo.sample -} diff --git a/slo_testing/slo-testing-README.md b/slo_testing/slo-testing-README.md deleted file mode 100644 index 4d7a62097..000000000 --- a/slo_testing/slo-testing-README.md +++ /dev/null @@ -1,82 +0,0 @@ -# How to Test the SLO Terraform Provider - Hosted Grafana - -# Installation -Install Terraform here - https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli#install-terraform. - -## HG Account Set Up -For members of the SLO Team, you should be able to use the `sloapp.grafana-dev.net` for testing or your own HG Instance. -Within Administration, generate a new Service Account Token. - -Set the environment variable GRAFANA_AUTH to the value of your token `export GRAFANA_AUTH=` -Within the `.tf` files within `slo_testing/hg`, ensure that you set the `url` field to be the `url` of your HG Instance. - -## Creating the TF Binary -1. Within the grafana-terraform-provider root directory, run `go build`. This creates a binary of the terraform-provider-grafana. -2. Within the grafana-terraform-provider root directory, create a file called `.terraformrc` with the following contents. Update the path to the path of your local `grafana-terraform-provider`. This ensures that it will use the local binary version of the terraform-provider-grafana. -``` -provider_installation { - dev_overrides { - "grafana/grafana" = "/path/to/your/grafana/terraform-provider" # this path is the directory where the binary is built - } - # For all other providers, install them directly from their origin provider - # registries as normal. If you omit this, Terraform will _only_ use - # the dev_overrides block, and so no other providers will be available. - direct {} - } -``` - -### Types of Resources -Datasource - datasources are resources that are external to Terraform (i.e. not managed by Terraform state). When interacting with a Datasource, they can be used to READ information, and datasources can also be imported (i.e. converted) into Resources, which allows Terraform state to control them. - -Resources - these are resources that can be managed by Terraform state. This means that you CREATE, READ, UPDATE, DELETE them. - -## Testing Datasource - READ -Objective - we want to send a GET Request to the SLO Endpoint that returns a list of all SLOs, and we want to be able to READ that information and output it to the Terraform CLI. - -1. Delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Within the terraform-provider-grafana root directory, run `go build`. Set the GRAFANA_AUTH environment variable to your HG Grafana API Key, if not already done. -2. Change to the `slo_testing` directory `cd slo_testing`. -3. Within your SLO UI, create a SLO if one does not already exist. -4. Within the `slo-datasource-read.tf` file, ensure the url is set to the url of your HG Instance. -5. Comment out all the `.tf` files within the `slo_testing` folder, EXCEPT for the `slo-datasource-read.tf` file -6. Within the `slo_testing` directory, run the commands `terraform init` and `terraform apply`. -7. You should see a list of all SLOs within your Terraform CLI. - -## Testing Resource - CREATE -Objective - we want to be able to define a SLO Resource within Terraform state that should be created. Once the resource has successfully been created, we want to display the newly created SLO resource within the Terraform CLI. - -The `slo-resource-create.tf` file will create two SLOs. - -1. Change to the `slo_testing/hg` directory. -2. Comment out all the `.tf` files within the `slo_testing` folder, EXCEPT for the `slo-resource-create.tf` file -3. Within the `slo_testing` directory, run the command `terraform apply`. -4. Within your terminal, you should see the output of the a newly created SLO from within Terraform, and the same newly created SLO within the SLO UI. - -## Testing the UPDATE Method -Objective - we want to be able to update a SLO Resource created within Terraform. Once the resource has successfully been modified, we want to display the newly created SLO resource within the Terraform CLI. - -1. Do NOT delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Ensure that this step is executed after testing the CREATE method. -2. Change to the `slo_testing` directory. -3. Comment out all the `.tf` files within the `slo_testing` folder, EXCEPT for the `slo-resource-create.tf` file -4. Modify any of the fields within the `slo-resource-create.tf` file - for example, you can change the `name` field to read `"Updated Terraform - Name Test"`. -5. Within the `slo_testing` directory, run the command `terraform apply`. This should update the resource specified in the terraform state file. -6. Check within the UI that the update was successful. - -## Testing the DELETE/DESTROY Method -Objective - we want to be able to delete a SLO Resource that was created with Terraform. - -1. Do NOT delete any `.terraform.lock.hcl` and `terraform.tfstate` and `terraform.tfstate.backup` files. Ensure that this step is executed after testing the UPDATE method. -2. Change to the `slo_testing` directory. -3. To delete all Terraformed SLO resources, execute the command `terraform destroy`, and type `yes` in the terminal to confirm the delete -4. Any SLO Resources created with Terraform should be deleted. - -### Testing the IMPORT Method -1. Change to the `slo_testing` directory. -2. Comment out all the `.tf` files within the `slo_testing` folder, EXCEPT for the `slo-resource-import.tf` file -3. Create a SLO using the UI or Postman. Take note of the SLO's UUID -4. Within the Terraform CLI directly, execute the command: `terraform import grafana_slo.sample slo_UUID` -5. Now execute the command: `terraform state show grafana_slo.sample` - you should see the data from the imported Resource. -6. To verify that this resource is now under Terraform control, execute the command `terraform destroy`. This should destroy the resource from within the Terraform CLI. - -### TBD ### -1. Once the GAPI Branch has been approved, remove the `replace` within `go.mod` -2. Remove `slo_testing` folder From b42ed36d734208fa1bf1ad564e5b685e96ed18f2 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Fri, 5 May 2023 13:52:49 -0400 Subject: [PATCH 47/51] Regenerate Docs and Linting --- docs/resources/slo.md | 2 +- internal/resources/slo/resource_slo.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/resources/slo.md b/docs/resources/slo.md index 26836cd89..ad4d0dbae 100644 --- a/docs/resources/slo.md +++ b/docs/resources/slo.md @@ -66,7 +66,7 @@ resource "grafana_slo" "test" { - `description` (String) Description is a free-text field that can provide more context to an SLO. - `name` (String) Name should be a short description of your indicator. Consider names like "API Availability" - `objectives` (Block List, Min: 1) Over each rolling time window, the remaining error budget will be calculated, and separate alerts can be generated for each time window based on the SLO burn rate or remaining error budget. (see [below for nested schema](#nestedblock--objectives)) -- `query` (Block List, Min: 1, Max: 1) Query describes the indicator that will be measured against the objective. Freeform Query types are currently supported. (see [below for nested schema](#nestedblock--query)) +- `query` (Block List, Min: 1) Query describes the indicator that will be measured against the objective. Freeform Query types are currently supported. (see [below for nested schema](#nestedblock--query)) ### Optional diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 69a81b931..ab775a53b 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -2,7 +2,6 @@ package slo import ( "context" - "errors" "fmt" "regexp" @@ -322,7 +321,7 @@ func packQuery(query map[string]interface{}) (gapi.Query, error) { return sloQuery, nil } - return gapi.Query{}, errors.New(fmt.Sprintf("%s query type not implemented", query["type"])) + return gapi.Query{}, fmt.Errorf("%s query type not implemented", query["type"]) } func packObjectives(tfobjectives []interface{}) []gapi.Objective { From fd28d96ec09d50b24a57a0f955a933553699275d Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Fri, 5 May 2023 14:01:32 -0400 Subject: [PATCH 48/51] Modify DatasourceSlo to use the Cloned Resource Schema --- docs/data-sources/slos.md | 1 - internal/resources/slo/data_source_slo.go | 210 +--------------------- 2 files changed, 2 insertions(+), 209 deletions(-) diff --git a/docs/data-sources/slos.md b/docs/data-sources/slos.md index f96c2dcea..22b70a117 100644 --- a/docs/data-sources/slos.md +++ b/docs/data-sources/slos.md @@ -90,7 +90,6 @@ Read-Only: - `annotations` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--annotations)) - `fastburn` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--fastburn)) - `labels` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--labels)) -- `name` (String) - `slowburn` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--slowburn)) diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 4b6b5c645..25d80766a 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -27,219 +27,13 @@ Datasource for retrieving all SLOs. Computed: true, Description: `Returns a list of all SLOs"`, Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ + Schema: common.CloneResourceSchemaForDatasource(ResourceSlo(), map[string]*schema.Schema{ "uuid": &schema.Schema{ Type: schema.TypeString, Description: `A unique, random identifier. This value will also be the name of the resource stored in the API server. This value is read-only.`, Computed: true, }, - "name": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - Description: `Name should be a short description of your indicator. Consider names like "API Availability"`, - }, - "description": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - Description: `Description is a free-text field that can provide more context to an SLO.`, - }, - "query": &schema.Schema{ - Type: schema.TypeList, - MaxItems: 1, - Required: true, - Description: `Query describes the indicator that will be measured against the objective. Freeform Query types are currently supported.`, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "freeformquery": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - "labels": &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Description: `Additional labels that will be attached to all metrics generated from the query. These labels are useful for grouping SLOs in dashboard views that you create by hand.`, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "objectives": &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Description: `Over each rolling time window, the remaining error budget will be calculated, and separate alerts can be generated for each time window based on the SLO burn rate or remaining error budget.`, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": &schema.Schema{ - Type: schema.TypeFloat, - Computed: true, - Description: `Value between 0 and 1. If the value of the query is above the objective, the SLO is met.`, - }, - "window": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - Description: `A Prometheus-parsable time duration string like 24h, 60m. This is the time window the objective is measured over.`, - }, - }, - }, - }, - "dashboard_uid": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - Description: `A reference to a dashboard that the plugin has installed in Grafana based on this SLO. This field is read-only, it is generated by the Grafana SLO Plugin.`, - }, - "alerting": &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Description: `Configures the alerting rules that will be generated for each - time window associated with the SLO. Grafana SLOs can generate - alerts when the short-term error budget burn is very high, the - long-term error budget burn rate is high, or when the remaining - error budget is below a certain threshold.`, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - "labels": &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Description: `Labels will be attached to all alerts generated by any of these rules.`, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "annotations": &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Description: `Annotations will be attached to all alerts generated by any of these rules.`, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "fastburn": &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Description: "Alerting Rules generated for Fast Burn alerts", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "labels": &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Description: "Labels to attach only to Fast Burn alerts.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "annotations": &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Description: "Annotations to attach only to Fast Burn alerts.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - }, - }, - }, - "slowburn": &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Description: "Alerting Rules generated for Slow Burn alerts", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "labels": &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Description: "Labels to attach only to Slow Burn alerts.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "annotations": &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Description: "Annotations to attach only to Slow Burn alerts.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - "value": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, + }), }, }, }, From 8a2682e093804ddfdc484079addd60814b3b9c5a Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Mon, 8 May 2023 13:01:11 -0400 Subject: [PATCH 49/51] Modifies field naming, adds nested query string to the freeform field --- docs/data-sources/slos.md | 65 ++++++++++-------- docs/resources/slo.md | 66 +++++++++++-------- .../data-sources/grafana_slos/data-source.tf | 14 ++-- examples/resources/grafana_slo/resource.tf | 14 ++-- .../resources/grafana_slo/resource_complex.tf | 20 +++--- .../resources/grafana_slo/resource_update.tf | 14 ++-- internal/resources/slo/data_source_slo.go | 24 +++---- internal/resources/slo/resource_slo.go | 46 ++++++++----- internal/resources/slo/resource_slo_test.go | 8 ++- 9 files changed, 157 insertions(+), 114 deletions(-) diff --git a/docs/data-sources/slos.md b/docs/data-sources/slos.md index 22b70a117..39a314186 100644 --- a/docs/data-sources/slos.md +++ b/docs/data-sources/slos.md @@ -21,35 +21,37 @@ resource "grafana_slo" "test" { name = "Terraform Testing" description = "Terraform Description" query { - freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + freeformquery { + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + } type = "freeform" } objectives { value = 0.995 window = "30d" } - labels { + label { key = "custom" value = "value" } alerting { fastburn { - annotations { + annotation { key = "name" value = "Critical - SLO Burn Rate Alert" } - labels { + label { key = "type" value = "slo" } } slowburn { - annotations { + annotation { key = "name" value = "Warning - SLO Burn Rate Alert" } - labels { + label { key = "type" value = "slo" } @@ -76,7 +78,7 @@ Read-Only: - `alerting` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting)) - `dashboard_uid` (String) - `description` (String) -- `labels` (List of Object) (see [below for nested schema](#nestedobjatt--slos--labels)) +- `label` (List of Object) (see [below for nested schema](#nestedobjatt--slos--label)) - `name` (String) - `objectives` (List of Object) (see [below for nested schema](#nestedobjatt--slos--objectives)) - `query` (List of Object) (see [below for nested schema](#nestedobjatt--slos--query)) @@ -87,13 +89,13 @@ Read-Only: Read-Only: -- `annotations` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--annotations)) +- `annotation` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--annotation)) - `fastburn` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--fastburn)) -- `labels` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--labels)) +- `label` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--label)) - `slowburn` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--slowburn)) - -### Nested Schema for `slos.alerting.annotations` + +### Nested Schema for `slos.alerting.annotation` Read-Only: @@ -106,11 +108,11 @@ Read-Only: Read-Only: -- `annotations` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--fastburn--annotations)) -- `labels` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--fastburn--labels)) +- `annotation` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--fastburn--annotation)) +- `label` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--fastburn--label)) - -### Nested Schema for `slos.alerting.fastburn.labels` + +### Nested Schema for `slos.alerting.fastburn.label` Read-Only: @@ -118,8 +120,8 @@ Read-Only: - `value` (String) - -### Nested Schema for `slos.alerting.fastburn.labels` + +### Nested Schema for `slos.alerting.fastburn.label` Read-Only: @@ -128,8 +130,8 @@ Read-Only: - -### Nested Schema for `slos.alerting.labels` + +### Nested Schema for `slos.alerting.label` Read-Only: @@ -142,11 +144,11 @@ Read-Only: Read-Only: -- `annotations` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--slowburn--annotations)) -- `labels` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--slowburn--labels)) +- `annotation` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--slowburn--annotation)) +- `label` (List of Object) (see [below for nested schema](#nestedobjatt--slos--alerting--slowburn--label)) - -### Nested Schema for `slos.alerting.slowburn.labels` + +### Nested Schema for `slos.alerting.slowburn.label` Read-Only: @@ -154,8 +156,8 @@ Read-Only: - `value` (String) - -### Nested Schema for `slos.alerting.slowburn.labels` + +### Nested Schema for `slos.alerting.slowburn.label` Read-Only: @@ -165,8 +167,8 @@ Read-Only: - -### Nested Schema for `slos.labels` + +### Nested Schema for `slos.label` Read-Only: @@ -188,7 +190,14 @@ Read-Only: Read-Only: -- `freeformquery` (String) +- `freeformquery` (List of Object) (see [below for nested schema](#nestedobjatt--slos--query--freeformquery)) - `type` (String) + +### Nested Schema for `slos.query.freeformquery` + +Read-Only: + +- `query` (String) + diff --git a/docs/resources/slo.md b/docs/resources/slo.md index ad4d0dbae..43623aa33 100644 --- a/docs/resources/slo.md +++ b/docs/resources/slo.md @@ -21,35 +21,37 @@ resource "grafana_slo" "test" { name = "Terraform Testing" description = "Terraform Description" query { - freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + freeformquery { + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + } type = "freeform" } objectives { value = 0.995 window = "30d" } - labels { + label { key = "custom" value = "value" } alerting { fastburn { - annotations { + annotation { key = "name" value = "Critical - SLO Burn Rate Alert" } - labels { + label { key = "type" value = "slo" } } slowburn { - annotations { + annotation { key = "name" value = "Warning - SLO Burn Rate Alert" } - labels { + label { key = "type" value = "slo" } @@ -75,7 +77,7 @@ resource "grafana_slo" "test" { alerts when the short-term error budget burn is very high, the long-term error budget burn rate is high, or when the remaining error budget is below a certain threshold. (see [below for nested schema](#nestedblock--alerting)) -- `labels` (Block List) Additional labels that will be attached to all metrics generated from the query. These labels are useful for grouping SLOs in dashboard views that you create by hand. (see [below for nested schema](#nestedblock--labels)) +- `label` (Block List) Additional labels that will be attached to all metrics generated from the query. These labels are useful for grouping SLOs in dashboard views that you create by hand. (see [below for nested schema](#nestedblock--label)) ### Read-Only @@ -96,22 +98,30 @@ Required: Required: -- `freeformquery` (String) +- `freeformquery` (Block List, Min: 1, Max: 1) (see [below for nested schema](#nestedblock--query--freeformquery)) - `type` (String) Query type must be one of: "freeform", "query", "ratio", or "threshold" + +### Nested Schema for `query.freeformquery` + +Optional: + +- `query` (String) Freeform Query Field + + ### Nested Schema for `alerting` Optional: -- `annotations` (Block List) Annotations will be attached to all alerts generated by any of these rules. (see [below for nested schema](#nestedblock--alerting--annotations)) +- `annotation` (Block List) Annotations will be attached to all alerts generated by any of these rules. (see [below for nested schema](#nestedblock--alerting--annotation)) - `fastburn` (Block List) Alerting Rules generated for Fast Burn alerts (see [below for nested schema](#nestedblock--alerting--fastburn)) -- `labels` (Block List) Labels will be attached to all alerts generated by any of these rules. (see [below for nested schema](#nestedblock--alerting--labels)) +- `label` (Block List) Labels will be attached to all alerts generated by any of these rules. (see [below for nested schema](#nestedblock--alerting--label)) - `slowburn` (Block List) Alerting Rules generated for Slow Burn alerts (see [below for nested schema](#nestedblock--alerting--slowburn)) - -### Nested Schema for `alerting.annotations` + +### Nested Schema for `alerting.annotation` Required: @@ -124,11 +134,11 @@ Required: Optional: -- `annotations` (Block List) Annotations to attach only to Fast Burn alerts. (see [below for nested schema](#nestedblock--alerting--fastburn--annotations)) -- `labels` (Block List) Labels to attach only to Fast Burn alerts. (see [below for nested schema](#nestedblock--alerting--fastburn--labels)) +- `annotation` (Block List) Annotations to attach only to Fast Burn alerts. (see [below for nested schema](#nestedblock--alerting--fastburn--annotation)) +- `label` (Block List) Labels to attach only to Fast Burn alerts. (see [below for nested schema](#nestedblock--alerting--fastburn--label)) - -### Nested Schema for `alerting.fastburn.annotations` + +### Nested Schema for `alerting.fastburn.annotation` Required: @@ -136,8 +146,8 @@ Required: - `value` (String) - -### Nested Schema for `alerting.fastburn.labels` + +### Nested Schema for `alerting.fastburn.label` Required: @@ -146,8 +156,8 @@ Required: - -### Nested Schema for `alerting.labels` + +### Nested Schema for `alerting.label` Required: @@ -160,11 +170,11 @@ Required: Optional: -- `annotations` (Block List) Annotations to attach only to Slow Burn alerts. (see [below for nested schema](#nestedblock--alerting--slowburn--annotations)) -- `labels` (Block List) Labels to attach only to Slow Burn alerts. (see [below for nested schema](#nestedblock--alerting--slowburn--labels)) +- `annotation` (Block List) Annotations to attach only to Slow Burn alerts. (see [below for nested schema](#nestedblock--alerting--slowburn--annotation)) +- `label` (Block List) Labels to attach only to Slow Burn alerts. (see [below for nested schema](#nestedblock--alerting--slowburn--label)) - -### Nested Schema for `alerting.slowburn.annotations` + +### Nested Schema for `alerting.slowburn.annotation` Required: @@ -172,8 +182,8 @@ Required: - `value` (String) - -### Nested Schema for `alerting.slowburn.labels` + +### Nested Schema for `alerting.slowburn.label` Required: @@ -183,8 +193,8 @@ Required: - -### Nested Schema for `labels` + +### Nested Schema for `label` Required: diff --git a/examples/data-sources/grafana_slos/data-source.tf b/examples/data-sources/grafana_slos/data-source.tf index 7287d8b4f..34de71dda 100644 --- a/examples/data-sources/grafana_slos/data-source.tf +++ b/examples/data-sources/grafana_slos/data-source.tf @@ -2,35 +2,37 @@ resource "grafana_slo" "test" { name = "Terraform Testing" description = "Terraform Description" query { - freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + freeformquery { + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + } type = "freeform" } objectives { value = 0.995 window = "30d" } - labels { + label { key = "custom" value = "value" } alerting { fastburn { - annotations { + annotation { key = "name" value = "Critical - SLO Burn Rate Alert" } - labels { + label { key = "type" value = "slo" } } slowburn { - annotations { + annotation { key = "name" value = "Warning - SLO Burn Rate Alert" } - labels { + label { key = "type" value = "slo" } diff --git a/examples/resources/grafana_slo/resource.tf b/examples/resources/grafana_slo/resource.tf index 600c9541e..9a37aa9b5 100644 --- a/examples/resources/grafana_slo/resource.tf +++ b/examples/resources/grafana_slo/resource.tf @@ -2,35 +2,37 @@ resource "grafana_slo" "test" { name = "Terraform Testing" description = "Terraform Description" query { - freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + freeformquery { + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + } type = "freeform" } objectives { value = 0.995 window = "30d" } - labels { + label { key = "custom" value = "value" } alerting { fastburn { - annotations { + annotation { key = "name" value = "Critical - SLO Burn Rate Alert" } - labels { + label { key = "type" value = "slo" } } slowburn { - annotations { + annotation { key = "name" value = "Warning - SLO Burn Rate Alert" } - labels { + label { key = "type" value = "slo" } diff --git a/examples/resources/grafana_slo/resource_complex.tf b/examples/resources/grafana_slo/resource_complex.tf index d41273499..6663da853 100644 --- a/examples/resources/grafana_slo/resource_complex.tf +++ b/examples/resources/grafana_slo/resource_complex.tf @@ -2,50 +2,52 @@ resource "grafana_slo" "test" { name = "Complex Resource - Terraform Testing" description = "Complex Resource - Terraform Description" query { - freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + freeformquery { + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + } type = "freeform" } objectives { value = 0.995 window = "30d" } - labels { + label { key = "slokey" value = "slokey" } alerting { name = "alertingname" - labels { + label { key = "alertingkey" value = "alertingvalue" } fastburn { - annotations { + annotation { key = "name" value = "Critical - SLO Burn Rate Alert" } - annotations { + annotation { key = "description" value = "Error Budget is burning at a rate greater than 14.4x." } - labels { + label { key = "type" value = "slo" } } slowburn { - annotations { + annotation { key = "name" value = "Warning - SLO Burn Rate Alert" } - annotations { + annotation { key = "description" value = "Error Budget is burning at a rate greater than 1x." } - labels { + label { key = "type" value = "slo" } diff --git a/examples/resources/grafana_slo/resource_update.tf b/examples/resources/grafana_slo/resource_update.tf index 5326d9baf..7e9713431 100644 --- a/examples/resources/grafana_slo/resource_update.tf +++ b/examples/resources/grafana_slo/resource_update.tf @@ -2,35 +2,37 @@ resource "grafana_slo" "update" { name = "Updated - Terraform Testing" description = "Updated - Terraform Description" query { - freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + freeformquery { + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + } type = "freeform" } objectives { value = 0.9995 window = "7d" } - labels { + label { key = "customkey" value = "customvalue" } alerting { fastburn { - annotations { + annotation { key = "name" value = "Critical - SLO Burn Rate Alert" } - labels { + label { key = "type" value = "slo" } } slowburn { - annotations { + annotation { key = "name" value = "Warning - SLO Burn Rate Alert" } - labels { + label { key = "type" value = "slo" } diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 25d80766a..2d9b5b65a 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -18,9 +18,6 @@ Datasource for retrieving all SLOs. * [API documentation](https://grafana.com/docs/grafana-cloud/slo/api/) `, ReadContext: datasourceSloRead, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, Schema: map[string]*schema.Schema{ "slos": &schema.Schema{ Type: schema.TypeList, @@ -81,7 +78,7 @@ func convertDatasourceSlo(slo gapi.Slo) map[string]interface{} { ret["query"] = unpackQuery(slo.Query) retLabels := unpackLabels(&slo.Labels) - ret["labels"] = retLabels + ret["label"] = retLabels retObjectives := unpackObjectives(slo.Objectives) ret["objectives"] = retObjectives @@ -92,13 +89,16 @@ func convertDatasourceSlo(slo gapi.Slo) map[string]interface{} { return ret } -// TBD for Other Query Types Once Implemented func unpackQuery(apiquery gapi.Query) []map[string]interface{} { retQuery := []map[string]interface{}{} if apiquery.Freeform.Query != "" { - query := make(map[string]interface{}) - query["freeformquery"] = apiquery.Freeform.Query - query["type"] = "freeform" + query := map[string]interface{}{"type": "freeform"} + + freeformquerystring := map[string]interface{}{"query": apiquery.Freeform.Query} + freeform := []map[string]interface{}{} + freeform = append(freeform, freeformquerystring) + query["freeformquery"] = freeform + retQuery = append(retQuery, query) } @@ -142,8 +142,8 @@ func unpackAlerting(alertData *gapi.Alerting) []map[string]interface{} { } alertObject := make(map[string]interface{}) - alertObject["labels"] = unpackLabels(&alertData.Labels) - alertObject["annotations"] = unpackLabels(&alertData.Annotations) + alertObject["label"] = unpackLabels(&alertData.Labels) + alertObject["annotation"] = unpackLabels(&alertData.Annotations) if alertData.FastBurn != nil { alertObject["fastburn"] = unpackAlertingMetadata(*alertData.FastBurn) @@ -163,12 +163,12 @@ func unpackAlertingMetadata(metaData gapi.AlertingMetadata) []map[string]interfa if metaData.Annotations != nil { retAnnotations := unpackLabels(&metaData.Annotations) - labelsAnnotsStruct["annotations"] = retAnnotations + labelsAnnotsStruct["annotation"] = retAnnotations } if metaData.Labels != nil { retLabels := unpackLabels(&metaData.Labels) - labelsAnnotsStruct["labels"] = retLabels + labelsAnnotsStruct["label"] = retLabels } retAlertMetaData = append(retAlertMetaData, labelsAnnotsStruct) diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index ab775a53b..23ede3f6f 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -54,13 +54,23 @@ Resource manages Grafana SLOs. Required: true, }, "freeformquery": &schema.Schema{ - Type: schema.TypeString, + Type: schema.TypeList, + MaxItems: 1, Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "query": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Freeform Query Field", + }, + }, + }, }, }, }, }, - "labels": &schema.Schema{ + "label": &schema.Schema{ Type: schema.TypeList, Optional: true, Description: `Additional labels that will be attached to all metrics generated from the query. These labels are useful for grouping SLOs in dashboard views that you create by hand.`, @@ -102,13 +112,13 @@ Resource manages Grafana SLOs. error budget is below a certain threshold.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "labels": &schema.Schema{ + "label": &schema.Schema{ Type: schema.TypeList, Optional: true, Description: `Labels will be attached to all alerts generated by any of these rules.`, Elem: keyvalueSchema, }, - "annotations": &schema.Schema{ + "annotation": &schema.Schema{ Type: schema.TypeList, Optional: true, Description: `Annotations will be attached to all alerts generated by any of these rules.`, @@ -120,13 +130,13 @@ Resource manages Grafana SLOs. Description: "Alerting Rules generated for Fast Burn alerts", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "labels": &schema.Schema{ + "label": &schema.Schema{ Type: schema.TypeList, Optional: true, Description: "Labels to attach only to Fast Burn alerts.", Elem: keyvalueSchema, }, - "annotations": &schema.Schema{ + "annotation": &schema.Schema{ Type: schema.TypeList, Optional: true, Description: "Annotations to attach only to Fast Burn alerts.", @@ -141,13 +151,13 @@ Resource manages Grafana SLOs. Description: "Alerting Rules generated for Slow Burn alerts", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "labels": &schema.Schema{ + "label": &schema.Schema{ Type: schema.TypeList, Optional: true, Description: "Labels to attach only to Slow Burn alerts.", Elem: keyvalueSchema, }, - "annotations": &schema.Schema{ + "annotation": &schema.Schema{ Type: schema.TypeList, Optional: true, Description: "Annotations to attach only to Slow Burn alerts.", @@ -234,7 +244,7 @@ func resourceSloUpdate(ctx context.Context, d *schema.ResourceData, m interface{ var diags diag.Diagnostics sloID := d.Id() - if d.HasChange("name") || d.HasChange("description") || d.HasChange("query") || d.HasChange("labels") || d.HasChange("objectives") || d.HasChange("alerting") { + if d.HasChange("name") || d.HasChange("description") || d.HasChange("query") || d.HasChange("label") || d.HasChange("objectives") || d.HasChange("alerting") { slo, err := packSloResource(d) if err != nil { diags = append(diags, diag.Diagnostic{ @@ -288,7 +298,7 @@ func packSloResource(d *schema.ResourceData) (gapi.Slo, error) { objectives := d.Get("objectives").([]interface{}) tfobjective := packObjectives(objectives) - labels := d.Get("labels").([]interface{}) + labels := d.Get("label").([]interface{}) if labels != nil { tflabels = packLabels(labels) } @@ -314,10 +324,14 @@ func packSloResource(d *schema.ResourceData) (gapi.Slo, error) { func packQuery(query map[string]interface{}) (gapi.Query, error) { if query["type"] == "freeform" { + freeformquery := query["freeformquery"].([]interface{})[0].(map[string]interface{}) + querystring := freeformquery["query"].(string) + sloQuery := gapi.Query{ - Freeform: &gapi.FreeformQuery{Query: query["freeformquery"].(string)}, + Freeform: &gapi.FreeformQuery{Query: querystring}, Type: gapi.QueryTypeFreeform, } + return sloQuery, nil } @@ -356,10 +370,10 @@ func packLabels(tfLabels []interface{}) []gapi.Label { } func packAlerting(tfAlerting map[string]interface{}) gapi.Alerting { - annots := tfAlerting["annotations"].([]interface{}) + annots := tfAlerting["annotation"].([]interface{}) tfAnnots := packLabels(annots) - labels := tfAlerting["labels"].([]interface{}) + labels := tfAlerting["label"].([]interface{}) tfLabels := packLabels(labels) fastBurn := tfAlerting["fastburn"].([]interface{}) @@ -381,10 +395,10 @@ func packAlerting(tfAlerting map[string]interface{}) gapi.Alerting { func packAlertMetadata(metadata []interface{}) gapi.AlertingMetadata { meta := metadata[0].(map[string]interface{}) - labels := meta["labels"].([]interface{}) + labels := meta["label"].([]interface{}) tflabels := packLabels(labels) - annots := meta["annotations"].([]interface{}) + annots := meta["annotation"].([]interface{}) tfannots := packLabels(annots) apiMetadata := gapi.AlertingMetadata{ @@ -402,7 +416,7 @@ func setTerraformState(d *schema.ResourceData, slo gapi.Slo) { d.Set("query", unpackQuery(slo.Query)) retLabels := unpackLabels(&slo.Labels) - d.Set("labels", retLabels) + d.Set("label", retLabels) retObjectives := unpackObjectives(slo.Objectives) d.Set("objectives", retObjectives) diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index 52b991714..97b8ea6a8 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -29,7 +29,7 @@ func TestAccResourceSlo(t *testing.T) { resource.TestCheckResourceAttr("grafana_slo.test", "name", "Terraform Testing"), resource.TestCheckResourceAttr("grafana_slo.test", "description", "Terraform Description"), resource.TestCheckResourceAttr("grafana_slo.test", "query.0.type", "freeform"), - resource.TestCheckResourceAttr("grafana_slo.test", "query.0.freeformquery", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), + resource.TestCheckResourceAttr("grafana_slo.test", "query.0.freeformquery.0.query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), resource.TestCheckResourceAttr("grafana_slo.test", "objectives.0.value", "0.995"), resource.TestCheckResourceAttr("grafana_slo.test", "objectives.0.window", "30d"), ), @@ -43,7 +43,7 @@ func TestAccResourceSlo(t *testing.T) { resource.TestCheckResourceAttr("grafana_slo.update", "name", "Updated - Terraform Testing"), resource.TestCheckResourceAttr("grafana_slo.update", "description", "Updated - Terraform Description"), resource.TestCheckResourceAttr("grafana_slo.update", "query.0.type", "freeform"), - resource.TestCheckResourceAttr("grafana_slo.update", "query.0.freeformquery", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), + resource.TestCheckResourceAttr("grafana_slo.update", "query.0.freeformquery.0.query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), resource.TestCheckResourceAttr("grafana_slo.update", "objectives.0.value", "0.9995"), resource.TestCheckResourceAttr("grafana_slo.update", "objectives.0.window", "7d"), ), @@ -94,7 +94,9 @@ resource "grafana_slo" "invalid" { name = "Test SLO" description = "Description Test SLO" query { - freeformquery = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + freeformquery { + query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" + } type = "freeform" } objectives { From 5d8112b5aca1b5df399cd25b5036d14a252c38b6 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Mon, 8 May 2023 13:05:54 -0400 Subject: [PATCH 50/51] Reformatting Docs --- docs/data-sources/slos.md | 2 +- docs/resources/slo.md | 2 +- examples/data-sources/grafana_slos/data-source.tf | 2 +- examples/resources/grafana_slo/resource.tf | 2 +- examples/resources/grafana_slo/resource_complex.tf | 2 +- examples/resources/grafana_slo/resource_update.tf | 2 +- internal/resources/slo/resource_slo.go | 6 +++++- 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/data-sources/slos.md b/docs/data-sources/slos.md index 39a314186..99f54492d 100644 --- a/docs/data-sources/slos.md +++ b/docs/data-sources/slos.md @@ -24,7 +24,7 @@ resource "grafana_slo" "test" { freeformquery { query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" } - type = "freeform" + type = "freeform" } objectives { value = 0.995 diff --git a/docs/resources/slo.md b/docs/resources/slo.md index 43623aa33..3602d2945 100644 --- a/docs/resources/slo.md +++ b/docs/resources/slo.md @@ -24,7 +24,7 @@ resource "grafana_slo" "test" { freeformquery { query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" } - type = "freeform" + type = "freeform" } objectives { value = 0.995 diff --git a/examples/data-sources/grafana_slos/data-source.tf b/examples/data-sources/grafana_slos/data-source.tf index 34de71dda..7ecaf2f22 100644 --- a/examples/data-sources/grafana_slos/data-source.tf +++ b/examples/data-sources/grafana_slos/data-source.tf @@ -5,7 +5,7 @@ resource "grafana_slo" "test" { freeformquery { query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" } - type = "freeform" + type = "freeform" } objectives { value = 0.995 diff --git a/examples/resources/grafana_slo/resource.tf b/examples/resources/grafana_slo/resource.tf index 9a37aa9b5..d800802a3 100644 --- a/examples/resources/grafana_slo/resource.tf +++ b/examples/resources/grafana_slo/resource.tf @@ -5,7 +5,7 @@ resource "grafana_slo" "test" { freeformquery { query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" } - type = "freeform" + type = "freeform" } objectives { value = 0.995 diff --git a/examples/resources/grafana_slo/resource_complex.tf b/examples/resources/grafana_slo/resource_complex.tf index 6663da853..00eee7eaf 100644 --- a/examples/resources/grafana_slo/resource_complex.tf +++ b/examples/resources/grafana_slo/resource_complex.tf @@ -5,7 +5,7 @@ resource "grafana_slo" "test" { freeformquery { query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" } - type = "freeform" + type = "freeform" } objectives { value = 0.995 diff --git a/examples/resources/grafana_slo/resource_update.tf b/examples/resources/grafana_slo/resource_update.tf index 7e9713431..634c2f1ce 100644 --- a/examples/resources/grafana_slo/resource_update.tf +++ b/examples/resources/grafana_slo/resource_update.tf @@ -5,7 +5,7 @@ resource "grafana_slo" "update" { freeformquery { query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" } - type = "freeform" + type = "freeform" } objectives { value = 0.9995 diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 23ede3f6f..15efbee7c 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -412,7 +412,11 @@ func packAlertMetadata(metadata []interface{}) gapi.AlertingMetadata { func setTerraformState(d *schema.ResourceData, slo gapi.Slo) { d.Set("name", slo.Name) d.Set("description", slo.Description) - d.Set("dashboard_uid", slo.DrillDownDashboardRef.UID) + + if slo.DrillDownDashboardRef != nil { + d.Set("dashboard_uid", slo.DrillDownDashboardRef.UID) + } + d.Set("query", unpackQuery(slo.Query)) retLabels := unpackLabels(&slo.Labels) From 2707f64ff0a821b790afc455f4c0380c801a4126 Mon Sep 17 00:00:00 2001 From: Elaine Vuong Date: Tue, 9 May 2023 09:23:52 -0400 Subject: [PATCH 51/51] Regenerates Documentation and Updates Freeform Query Schema --- docs/data-sources/slos.md | 8 ++++---- docs/resources/slo.md | 8 ++++---- examples/data-sources/grafana_slos/data-source.tf | 2 +- examples/resources/grafana_slo/resource.tf | 2 +- examples/resources/grafana_slo/resource_complex.tf | 4 +--- examples/resources/grafana_slo/resource_update.tf | 2 +- internal/provider/provider.go | 9 ++++++--- internal/resources/slo/data_source_slo.go | 2 +- internal/resources/slo/resource_slo.go | 4 ++-- internal/resources/slo/resource_slo_test.go | 6 +++--- 10 files changed, 24 insertions(+), 23 deletions(-) diff --git a/docs/data-sources/slos.md b/docs/data-sources/slos.md index 99f54492d..157cf1c18 100644 --- a/docs/data-sources/slos.md +++ b/docs/data-sources/slos.md @@ -21,7 +21,7 @@ resource "grafana_slo" "test" { name = "Terraform Testing" description = "Terraform Description" query { - freeformquery { + freeform { query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" } type = "freeform" @@ -190,11 +190,11 @@ Read-Only: Read-Only: -- `freeformquery` (List of Object) (see [below for nested schema](#nestedobjatt--slos--query--freeformquery)) +- `freeform` (List of Object) (see [below for nested schema](#nestedobjatt--slos--query--freeform)) - `type` (String) - -### Nested Schema for `slos.query.freeformquery` + +### Nested Schema for `slos.query.freeform` Read-Only: diff --git a/docs/resources/slo.md b/docs/resources/slo.md index 3602d2945..590bbb8cd 100644 --- a/docs/resources/slo.md +++ b/docs/resources/slo.md @@ -21,7 +21,7 @@ resource "grafana_slo" "test" { name = "Terraform Testing" description = "Terraform Description" query { - freeformquery { + freeform { query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" } type = "freeform" @@ -98,11 +98,11 @@ Required: Required: -- `freeformquery` (Block List, Min: 1, Max: 1) (see [below for nested schema](#nestedblock--query--freeformquery)) +- `freeform` (Block List, Min: 1, Max: 1) (see [below for nested schema](#nestedblock--query--freeform)) - `type` (String) Query type must be one of: "freeform", "query", "ratio", or "threshold" - -### Nested Schema for `query.freeformquery` + +### Nested Schema for `query.freeform` Optional: diff --git a/examples/data-sources/grafana_slos/data-source.tf b/examples/data-sources/grafana_slos/data-source.tf index 7ecaf2f22..bde83bbfc 100644 --- a/examples/data-sources/grafana_slos/data-source.tf +++ b/examples/data-sources/grafana_slos/data-source.tf @@ -2,7 +2,7 @@ resource "grafana_slo" "test" { name = "Terraform Testing" description = "Terraform Description" query { - freeformquery { + freeform { query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" } type = "freeform" diff --git a/examples/resources/grafana_slo/resource.tf b/examples/resources/grafana_slo/resource.tf index d800802a3..d57c5d99d 100644 --- a/examples/resources/grafana_slo/resource.tf +++ b/examples/resources/grafana_slo/resource.tf @@ -2,7 +2,7 @@ resource "grafana_slo" "test" { name = "Terraform Testing" description = "Terraform Description" query { - freeformquery { + freeform { query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" } type = "freeform" diff --git a/examples/resources/grafana_slo/resource_complex.tf b/examples/resources/grafana_slo/resource_complex.tf index 00eee7eaf..8804b1867 100644 --- a/examples/resources/grafana_slo/resource_complex.tf +++ b/examples/resources/grafana_slo/resource_complex.tf @@ -2,7 +2,7 @@ resource "grafana_slo" "test" { name = "Complex Resource - Terraform Testing" description = "Complex Resource - Terraform Description" query { - freeformquery { + freeform { query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" } type = "freeform" @@ -16,8 +16,6 @@ resource "grafana_slo" "test" { value = "slokey" } alerting { - name = "alertingname" - label { key = "alertingkey" value = "alertingvalue" diff --git a/examples/resources/grafana_slo/resource_update.tf b/examples/resources/grafana_slo/resource_update.tf index 634c2f1ce..aedc50227 100644 --- a/examples/resources/grafana_slo/resource_update.tf +++ b/examples/resources/grafana_slo/resource_update.tf @@ -2,7 +2,7 @@ resource "grafana_slo" "update" { name = "Updated - Terraform Testing" description = "Updated - Terraform Description" query { - freeformquery { + freeform { query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" } type = "freeform" diff --git a/internal/provider/provider.go b/internal/provider/provider.go index cece5933c..0ccf8d7c3 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -446,7 +446,10 @@ func createOnCallClient(d *schema.ResourceData) (*onCallAPI.Client, error) { return onCallAPI.New(baseURL, aToken) } +// Sets a custom HTTP Header on all requests coming from the Grafana Terraform Provider to Grafana-Terraform-Provider: true +// in addition to any headers set within the `http_headers` field or the `GRAFANA_HTTP_HEADERS` environment variable func getHTTPHeadersMap(d *schema.ResourceData) (map[string]string, error) { + headers := map[string]string{"Grafana-Terraform-Provider": "true"} headersMap := d.Get("http_headers").(map[string]interface{}) if len(headersMap) == 0 { // We cannot use a DefaultFunc because they do not work on maps @@ -456,16 +459,16 @@ func getHTTPHeadersMap(d *schema.ResourceData) (map[string]string, error) { return nil, fmt.Errorf("invalid http_headers config: %w", err) } } + if len(headersMap) > 0 { - headers := make(map[string]string) for k, v := range headersMap { if v, ok := v.(string); ok { headers[k] = v } } - return headers, nil } - return map[string]string{}, nil + + return headers, nil } // getJSONMap is a helper function that parses the given environment variable as a JSON object diff --git a/internal/resources/slo/data_source_slo.go b/internal/resources/slo/data_source_slo.go index 2d9b5b65a..afd13153f 100644 --- a/internal/resources/slo/data_source_slo.go +++ b/internal/resources/slo/data_source_slo.go @@ -97,7 +97,7 @@ func unpackQuery(apiquery gapi.Query) []map[string]interface{} { freeformquerystring := map[string]interface{}{"query": apiquery.Freeform.Query} freeform := []map[string]interface{}{} freeform = append(freeform, freeformquerystring) - query["freeformquery"] = freeform + query["freeform"] = freeform retQuery = append(retQuery, query) } diff --git a/internal/resources/slo/resource_slo.go b/internal/resources/slo/resource_slo.go index 15efbee7c..3327b29b0 100644 --- a/internal/resources/slo/resource_slo.go +++ b/internal/resources/slo/resource_slo.go @@ -53,7 +53,7 @@ Resource manages Grafana SLOs. ValidateFunc: validation.StringInSlice([]string{"freeform", "query", "ratio", "threshold"}, false), Required: true, }, - "freeformquery": &schema.Schema{ + "freeform": &schema.Schema{ Type: schema.TypeList, MaxItems: 1, Required: true, @@ -324,7 +324,7 @@ func packSloResource(d *schema.ResourceData) (gapi.Slo, error) { func packQuery(query map[string]interface{}) (gapi.Query, error) { if query["type"] == "freeform" { - freeformquery := query["freeformquery"].([]interface{})[0].(map[string]interface{}) + freeformquery := query["freeform"].([]interface{})[0].(map[string]interface{}) querystring := freeformquery["query"].(string) sloQuery := gapi.Query{ diff --git a/internal/resources/slo/resource_slo_test.go b/internal/resources/slo/resource_slo_test.go index 97b8ea6a8..c4dfde612 100644 --- a/internal/resources/slo/resource_slo_test.go +++ b/internal/resources/slo/resource_slo_test.go @@ -29,7 +29,7 @@ func TestAccResourceSlo(t *testing.T) { resource.TestCheckResourceAttr("grafana_slo.test", "name", "Terraform Testing"), resource.TestCheckResourceAttr("grafana_slo.test", "description", "Terraform Description"), resource.TestCheckResourceAttr("grafana_slo.test", "query.0.type", "freeform"), - resource.TestCheckResourceAttr("grafana_slo.test", "query.0.freeformquery.0.query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), + resource.TestCheckResourceAttr("grafana_slo.test", "query.0.freeform.0.query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), resource.TestCheckResourceAttr("grafana_slo.test", "objectives.0.value", "0.995"), resource.TestCheckResourceAttr("grafana_slo.test", "objectives.0.window", "30d"), ), @@ -43,7 +43,7 @@ func TestAccResourceSlo(t *testing.T) { resource.TestCheckResourceAttr("grafana_slo.update", "name", "Updated - Terraform Testing"), resource.TestCheckResourceAttr("grafana_slo.update", "description", "Updated - Terraform Description"), resource.TestCheckResourceAttr("grafana_slo.update", "query.0.type", "freeform"), - resource.TestCheckResourceAttr("grafana_slo.update", "query.0.freeformquery.0.query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), + resource.TestCheckResourceAttr("grafana_slo.update", "query.0.freeform.0.query", "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"), resource.TestCheckResourceAttr("grafana_slo.update", "objectives.0.value", "0.9995"), resource.TestCheckResourceAttr("grafana_slo.update", "objectives.0.window", "7d"), ), @@ -94,7 +94,7 @@ resource "grafana_slo" "invalid" { name = "Test SLO" description = "Description Test SLO" query { - freeformquery { + freeform { query = "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" } type = "freeform"