From 11b101e4805177e69cbc0c8af7e668ae8bd3447a Mon Sep 17 00:00:00 2001 From: Julien Duchesne Date: Wed, 15 Nov 2023 08:33:10 -0500 Subject: [PATCH] data_source_dashboards: Use OpenAPI client (#1159) * data_source_dashboards: Use OpenAPI client Use https://github.com/grafana/grafana-openapi-client-go instead of the manually generated client Also in this PR: - Add `org_id` parameter - Rework the tests so that all the features are I also reworked the tests so that they're actually te * Generate docs --- docs/data-sources/dashboards.md | 56 ++++++++++++------- .../grafana_dashboards/data-source.tf | 55 +++++++++++------- internal/common/adapters.go | 10 ++-- .../grafana/data_source_dashboards.go | 53 ++++++------------ .../grafana/data_source_dashboards_test.go | 52 ++++++++++------- internal/resources/oncall/resource_shift.go | 8 +-- 6 files changed, 131 insertions(+), 103 deletions(-) diff --git a/docs/data-sources/dashboards.md b/docs/data-sources/dashboards.md index 48f6ca01b..2c87074ef 100644 --- a/docs/data-sources/dashboards.md +++ b/docs/data-sources/dashboards.md @@ -18,48 +18,56 @@ Datasource for retrieving all dashboards. Specify list of folder IDs to search i ## Example Usage ```terraform +resource "grafana_organization" "test" { + name = "testing dashboards data source" +} + resource "grafana_folder" "data_source_dashboards" { + org_id = grafana_organization.test.id + title = "test folder data_source_dashboards" } -// retrieve dashboards by tags, folderIDs, or both resource "grafana_dashboard" "data_source_dashboards1" { + org_id = grafana_organization.test.id + folder = grafana_folder.data_source_dashboards.id config_json = jsonencode({ - id = 23456 - title = "data_source_dashboards 1" - tags = ["data_source_dashboards"] - timezone = "browser" - schemaVersion = 16 + uid = "data-source-dashboards-1" + title = "data_source_dashboards 1" + tags = ["dev"] + }) +} + +resource "grafana_dashboard" "data_source_dashboards2" { + org_id = grafana_organization.test.id + + config_json = jsonencode({ + uid = "data-source-dashboards-2" + title = "data_source_dashboards 2" + tags = ["prod"] }) } data "grafana_dashboards" "tags" { - tags = jsondecode(grafana_dashboard.data_source_dashboards1.config_json)["tags"] + org_id = grafana_organization.test.id + tags = jsondecode(grafana_dashboard.data_source_dashboards1.config_json)["tags"] } data "grafana_dashboards" "folder_ids" { + org_id = grafana_organization.test.id folder_ids = [grafana_dashboard.data_source_dashboards1.folder] } data "grafana_dashboards" "folder_ids_tags" { + org_id = grafana_organization.test.id folder_ids = [grafana_dashboard.data_source_dashboards1.folder] tags = jsondecode(grafana_dashboard.data_source_dashboards1.config_json)["tags"] } -resource "grafana_dashboard" "data_source_dashboards2" { - folder = 0 // General folder - config_json = jsonencode({ - id = 23456 - title = "data_source_dashboards 2" - tags = ["prod"] - timezone = "browser" - schemaVersion = 16 - }) -} - // use depends_on to wait for dashboard resource to be created before searching data "grafana_dashboards" "all" { + org_id = grafana_organization.test.id depends_on = [ grafana_dashboard.data_source_dashboards1, grafana_dashboard.data_source_dashboards2 @@ -68,7 +76,16 @@ data "grafana_dashboards" "all" { // get only one result data "grafana_dashboards" "limit_one" { - limit = 1 + org_id = grafana_organization.test.id + limit = 1 + depends_on = [ + grafana_dashboard.data_source_dashboards1, + grafana_dashboard.data_source_dashboards2 + ] +} + +// The dashboards are not in the default org so this should return an empty list +data "grafana_dashboards" "wrong_org" { depends_on = [ grafana_dashboard.data_source_dashboards1, grafana_dashboard.data_source_dashboards2 @@ -83,6 +100,7 @@ data "grafana_dashboards" "limit_one" { - `folder_ids` (List of Number) Numerical IDs of Grafana folders containing dashboards. Specify to filter for dashboards by folder (eg. `[0]` for General folder), or leave blank to get all dashboards in all folders. - `limit` (Number) Maximum number of dashboard search results to return. Defaults to `5000`. +- `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used. - `tags` (List of String) List of string Grafana dashboard tags to search for, eg. `["prod"]`. Used only as search input, i.e., attribute value will remain unchanged. ### Read-Only diff --git a/examples/data-sources/grafana_dashboards/data-source.tf b/examples/data-sources/grafana_dashboards/data-source.tf index fcc3da014..874cbf8c2 100644 --- a/examples/data-sources/grafana_dashboards/data-source.tf +++ b/examples/data-sources/grafana_dashboards/data-source.tf @@ -1,45 +1,53 @@ +resource "grafana_organization" "test" { + name = "testing dashboards data source" +} + resource "grafana_folder" "data_source_dashboards" { + org_id = grafana_organization.test.id + title = "test folder data_source_dashboards" } -// retrieve dashboards by tags, folderIDs, or both resource "grafana_dashboard" "data_source_dashboards1" { + org_id = grafana_organization.test.id + folder = grafana_folder.data_source_dashboards.id config_json = jsonencode({ - id = 23456 - title = "data_source_dashboards 1" - tags = ["data_source_dashboards"] - timezone = "browser" - schemaVersion = 16 + uid = "data-source-dashboards-1" + title = "data_source_dashboards 1" + tags = ["dev"] + }) +} + +resource "grafana_dashboard" "data_source_dashboards2" { + org_id = grafana_organization.test.id + + config_json = jsonencode({ + uid = "data-source-dashboards-2" + title = "data_source_dashboards 2" + tags = ["prod"] }) } data "grafana_dashboards" "tags" { - tags = jsondecode(grafana_dashboard.data_source_dashboards1.config_json)["tags"] + org_id = grafana_organization.test.id + tags = jsondecode(grafana_dashboard.data_source_dashboards1.config_json)["tags"] } data "grafana_dashboards" "folder_ids" { + org_id = grafana_organization.test.id folder_ids = [grafana_dashboard.data_source_dashboards1.folder] } data "grafana_dashboards" "folder_ids_tags" { + org_id = grafana_organization.test.id folder_ids = [grafana_dashboard.data_source_dashboards1.folder] tags = jsondecode(grafana_dashboard.data_source_dashboards1.config_json)["tags"] } -resource "grafana_dashboard" "data_source_dashboards2" { - folder = 0 // General folder - config_json = jsonencode({ - id = 23456 - title = "data_source_dashboards 2" - tags = ["prod"] - timezone = "browser" - schemaVersion = 16 - }) -} - // use depends_on to wait for dashboard resource to be created before searching data "grafana_dashboards" "all" { + org_id = grafana_organization.test.id depends_on = [ grafana_dashboard.data_source_dashboards1, grafana_dashboard.data_source_dashboards2 @@ -48,7 +56,16 @@ data "grafana_dashboards" "all" { // get only one result data "grafana_dashboards" "limit_one" { - limit = 1 + org_id = grafana_organization.test.id + limit = 1 + depends_on = [ + grafana_dashboard.data_source_dashboards1, + grafana_dashboard.data_source_dashboards2 + ] +} + +// The dashboards are not in the default org so this should return an empty list +data "grafana_dashboards" "wrong_org" { depends_on = [ grafana_dashboard.data_source_dashboards1, grafana_dashboard.data_source_dashboards2 diff --git a/internal/common/adapters.go b/internal/common/adapters.go index 5cae73f9e..c5b9f2f31 100644 --- a/internal/common/adapters.go +++ b/internal/common/adapters.go @@ -20,20 +20,20 @@ func SetToStringSlice(src *schema.Set) []string { return ListToStringSlice(src.List()) } -func ListToIntSlice(src []interface{}) []int { - dst := make([]int, 0, len(src)) +func ListToIntSlice[T int | int64](src []interface{}) []T { + dst := make([]T, 0, len(src)) for _, s := range src { val, ok := s.(int) if !ok { val = 0 } - dst = append(dst, val) + dst = append(dst, T(val)) } return dst } -func SetToIntSlice(src *schema.Set) []int { - return ListToIntSlice(src.List()) +func SetToIntSlice[T int | int64](src *schema.Set) []T { + return ListToIntSlice[T](src.List()) } func StringSliceToList(list []string) []interface{} { diff --git a/internal/resources/grafana/data_source_dashboards.go b/internal/resources/grafana/data_source_dashboards.go index 926f6f2d2..c2dfbbaed 100644 --- a/internal/resources/grafana/data_source_dashboards.go +++ b/internal/resources/grafana/data_source_dashboards.go @@ -4,10 +4,8 @@ import ( "context" "crypto/sha256" "fmt" - "net/url" - "sort" - "strings" + "github.com/grafana/grafana-openapi-client-go/client/search" "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" @@ -24,6 +22,7 @@ Datasource for retrieving all dashboards. Specify list of folder IDs to search i `, ReadContext: dataSourceReadDashboards, Schema: map[string]*schema.Schema{ + "org_id": orgIDAttribute(), "folder_ids": { Type: schema.TypeList, Optional: true, @@ -66,52 +65,36 @@ Datasource for retrieving all dashboards. Specify list of folder IDs to search i } } -func HashDashboardSearchParameters(params map[string][]string) string { - // hash a sorted slice of all string parameters and corresponding values - hashOut := sha256.New() - - var paramsList []string - for key, vals := range params { - paramsList = append(paramsList, key) - paramsList = append(paramsList, vals...) - } +func dataSourceReadDashboards(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, orgID := OAPIClientFromNewOrgResource(meta, d) - sort.Strings(paramsList) - hashIn := strings.Join(paramsList, "") - hashOut.Write([]byte(hashIn)) - return fmt.Sprintf("%x", hashOut.Sum(nil))[:23] -} + limit := int64(d.Get("limit").(int)) + searchType := "dash-db" + params := search.NewSearchParams().WithLimit(&limit).WithType(&searchType) -func dataSourceReadDashboards(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*common.Client).GrafanaAPI - var diags diag.Diagnostics - params := url.Values{ - "limit": {fmt.Sprint(d.Get("limit"))}, - "type": {"dash-db"}, - } + id := sha256.New() + id.Write([]byte(fmt.Sprintf("%d", limit))) // add tags and folder IDs from attributes to dashboard search parameters if list, ok := d.GetOk("folder_ids"); ok { - for _, elem := range list.([]interface{}) { - params.Add("folderIds", fmt.Sprint(elem)) - } + params.FolderIds = common.ListToIntSlice[int64](list.([]interface{})) + id.Write([]byte(fmt.Sprintf("%v", params.FolderIds))) } if list, ok := d.GetOk("tags"); ok { - for _, elem := range list.([]interface{}) { - params.Add("tag", fmt.Sprint(elem)) - } + params.Tag = common.ListToStringSlice(list.([]interface{})) + id.Write([]byte(fmt.Sprintf("%v", params.Tag))) } - d.SetId(HashDashboardSearchParameters(params)) + d.SetId(MakeOrgResourceID(orgID, id)) - results, err := client.FolderDashboardSearch(params) + resp, err := client.Search.Search(params, nil) if err != nil { return diag.FromErr(err) } - dashboards := make([]map[string]interface{}, len(results)) - for i, result := range results { + dashboards := make([]map[string]interface{}, len(resp.GetPayload())) + for i, result := range resp.GetPayload() { dashboards[i] = map[string]interface{}{ "title": result.Title, "uid": result.UID, @@ -123,5 +106,5 @@ func dataSourceReadDashboards(ctx context.Context, d *schema.ResourceData, meta return diag.Errorf("error setting dashboards attribute: %s", err) } - return diags + return nil } diff --git a/internal/resources/grafana/data_source_dashboards_test.go b/internal/resources/grafana/data_source_dashboards_test.go index ad91f903c..3d8eae23c 100644 --- a/internal/resources/grafana/data_source_dashboards_test.go +++ b/internal/resources/grafana/data_source_dashboards_test.go @@ -1,10 +1,8 @@ package grafana_test import ( - "net/url" "testing" - "github.com/grafana/terraform-provider-grafana/internal/resources/grafana" "github.com/grafana/terraform-provider-grafana/internal/testutils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) @@ -12,30 +10,42 @@ import ( func TestAccDataSourceDashboardsAllAndByFolderID(t *testing.T) { testutils.CheckOSSTestsEnabled(t) - params := url.Values{ - "limit": {"5000"}, - "type": {"dash-db"}, - } - idAll := grafana.HashDashboardSearchParameters(params) - - params["tag"] = []string{"data_source_dashboards"} - idTags := grafana.HashDashboardSearchParameters(params) - - checks := []resource.TestCheckFunc{ - resource.TestCheckResourceAttr("data.grafana_dashboards.all", "id", idAll), - resource.TestCheckResourceAttr("data.grafana_dashboards.tags", "id", idTags), - resource.TestCheckResourceAttr("data.grafana_dashboards.tags", "dashboards.#", "1"), - resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids", "dashboards.#", "1"), - resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids_tags", "dashboards.#", "1"), - resource.TestCheckResourceAttr("data.grafana_dashboards.limit_one", "dashboards.#", "1"), - } - resource.ParallelTest(t, resource.TestCase{ ProviderFactories: testutils.ProviderFactories, Steps: []resource.TestStep{ { Config: testutils.TestAccExample(t, "data-sources/grafana_dashboards/data-source.tf"), - Check: resource.ComposeTestCheckFunc(checks...), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.grafana_dashboards.tags", "dashboards.#", "1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.tags", "dashboards.0.uid", "data-source-dashboards-1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.tags", "dashboards.0.title", "data_source_dashboards 1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.tags", "dashboards.0.folder_title", "test folder data_source_dashboards"), + + resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids", "dashboards.#", "1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids", "dashboards.0.uid", "data-source-dashboards-1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids", "dashboards.0.title", "data_source_dashboards 1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids", "dashboards.0.folder_title", "test folder data_source_dashboards"), + + resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids_tags", "dashboards.#", "1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids_tags", "dashboards.0.uid", "data-source-dashboards-1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids_tags", "dashboards.0.title", "data_source_dashboards 1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids_tags", "dashboards.0.folder_title", "test folder data_source_dashboards"), + + resource.TestCheckResourceAttr("data.grafana_dashboards.limit_one", "dashboards.#", "1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.limit_one", "dashboards.0.uid", "data-source-dashboards-1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.limit_one", "dashboards.0.title", "data_source_dashboards 1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.limit_one", "dashboards.0.folder_title", "test folder data_source_dashboards"), + + resource.TestCheckResourceAttr("data.grafana_dashboards.all", "dashboards.#", "2"), + resource.TestCheckResourceAttr("data.grafana_dashboards.all", "dashboards.0.uid", "data-source-dashboards-1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.all", "dashboards.0.title", "data_source_dashboards 1"), + resource.TestCheckResourceAttr("data.grafana_dashboards.all", "dashboards.0.folder_title", "test folder data_source_dashboards"), + resource.TestCheckResourceAttr("data.grafana_dashboards.all", "dashboards.1.uid", "data-source-dashboards-2"), + resource.TestCheckResourceAttr("data.grafana_dashboards.all", "dashboards.1.title", "data_source_dashboards 2"), + resource.TestCheckResourceAttr("data.grafana_dashboards.all", "dashboards.1.folder_title", ""), + + resource.TestCheckResourceAttr("data.grafana_dashboards.wrong_org", "dashboards.#", "0"), + ), }, }, }) diff --git a/internal/resources/oncall/resource_shift.go b/internal/resources/oncall/resource_shift.go index 4150eb448..6d720696f 100644 --- a/internal/resources/oncall/resource_shift.go +++ b/internal/resources/oncall/resource_shift.go @@ -250,7 +250,7 @@ func ResourceOnCallShiftCreate(ctx context.Context, d *schema.ResourceData, m in byMonthData, byMonthOk := d.GetOk("by_month") if byMonthOk { if typeData != singleEvent { - byMonthDataSlice := common.SetToIntSlice(byMonthData.(*schema.Set)) + byMonthDataSlice := common.SetToIntSlice[int](byMonthData.(*schema.Set)) createOptions.ByMonth = &byMonthDataSlice } else { return diag.Errorf("by_month can not be set with type: %s", typeData) @@ -260,7 +260,7 @@ func ResourceOnCallShiftCreate(ctx context.Context, d *schema.ResourceData, m in byMonthdayData, byMonthdayOk := d.GetOk("by_monthday") if byMonthdayOk { if typeData != singleEvent { - byMonthdayDataSlice := common.SetToIntSlice(byMonthdayData.(*schema.Set)) + byMonthdayDataSlice := common.SetToIntSlice[int](byMonthdayData.(*schema.Set)) createOptions.ByMonthday = &byMonthdayDataSlice } else { return diag.Errorf("by_monthday can not be set with type: %s", typeData) @@ -373,7 +373,7 @@ func ResourceOnCallShiftUpdate(ctx context.Context, d *schema.ResourceData, m in byMonthData, byMonthOk := d.GetOk("by_month") if byMonthOk { if typeData != singleEvent { - byMonthDataSlice := common.SetToIntSlice(byMonthData.(*schema.Set)) + byMonthDataSlice := common.SetToIntSlice[int](byMonthData.(*schema.Set)) updateOptions.ByMonth = &byMonthDataSlice } else { return diag.Errorf("by_month can not be set with type: %s", typeData) @@ -383,7 +383,7 @@ func ResourceOnCallShiftUpdate(ctx context.Context, d *schema.ResourceData, m in byMonthDayData, byMonthDayOk := d.GetOk("by_monthday") if byMonthDayOk { if typeData != singleEvent { - byMonthDayData := common.SetToIntSlice(byMonthDayData.(*schema.Set)) + byMonthDayData := common.SetToIntSlice[int](byMonthDayData.(*schema.Set)) updateOptions.ByMonthday = &byMonthDayData } else { return diag.Errorf("by_monthday can not be set with type: %s", typeData)