Skip to content

Commit

Permalink
data_source_dashboards: Use OpenAPI client (#1159)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
julienduchesne authored Nov 15, 2023
1 parent 9d9b3af commit 11b101e
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 103 deletions.
56 changes: 37 additions & 19 deletions docs/data-sources/dashboards.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
55 changes: 36 additions & 19 deletions examples/data-sources/grafana_dashboards/data-source.tf
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions internal/common/adapters.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{} {
Expand Down
53 changes: 18 additions & 35 deletions internal/resources/grafana/data_source_dashboards.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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
}
52 changes: 31 additions & 21 deletions internal/resources/grafana/data_source_dashboards_test.go
Original file line number Diff line number Diff line change
@@ -1,41 +1,51 @@
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"
)

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"),
),
},
},
})
Expand Down
Loading

0 comments on commit 11b101e

Please sign in to comment.