Skip to content

Commit

Permalink
Add public dashboards support (#1026)
Browse files Browse the repository at this point in the history
* Add support for public dashboards

* replace golang client by a specific branch for testing

* fix lint errors and add min grafana version for tests

* fix lint errors

* update go mod and remove optional attribute from schema description

* update docs

* add import test

* add orgid to public dashboard resource

* fix comment

* fix lint

* add notice about only being available in 10.2

---------

Co-authored-by: Julien Duchesne <[email protected]>
  • Loading branch information
evictorero and julienduchesne authored Nov 1, 2023
1 parent 4e8aa22 commit 633b640
Show file tree
Hide file tree
Showing 7 changed files with 389 additions and 0 deletions.
105 changes: 105 additions & 0 deletions docs/resources/dashboard_public.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "grafana_dashboard_public Resource - terraform-provider-grafana"
subcategory: "Grafana OSS"
description: |-
Manages Grafana public dashboards.
Note: This resource is available only with Grafana 10.2+.
Official documentation https://grafana.com/docs/grafana/latest/dashboards/dashboard-public/HTTP API https://grafana.com/docs/grafana/next/developers/http_api/dashboard_public/
---

# grafana_dashboard_public (Resource)

Manages Grafana public dashboards.

**Note:** This resource is available only with Grafana 10.2+.

* [Official documentation](https://grafana.com/docs/grafana/latest/dashboards/dashboard-public/)
* [HTTP API](https://grafana.com/docs/grafana/next/developers/http_api/dashboard_public/)

## Example Usage

```terraform
// Optional (On-premise, not supported in Grafana Cloud): Create an organization
resource "grafana_organization" "my_org" {
name = "test 1"
}
// Create resources (optional: within the organization)
resource "grafana_folder" "my_folder" {
org_id = grafana_organization.my_org.org_id
title = "test Folder"
}
resource "grafana_dashboard" "test_dash" {
org_id = grafana_organization.my_org.org_id
folder = grafana_folder.my_folder.id
config_json = jsonencode({
"title" : "My Terraform Dashboard",
"uid" : "my-dashboard-uid"
})
}
resource "grafana_dashboard_public" "my_public_dashboard" {
org_id = grafana_organization.my_org.org_id
dashboard_uid = grafana_dashboard.test_dash.uid
uid = "my-custom-public-uid"
access_token = "e99e4275da6f410d83760eefa934d8d2"
time_selection_enabled = true
is_enabled = true
annotations_enabled = true
share = "public"
}
// Optional (On-premise, not supported in Grafana Cloud): Create an organization
resource "grafana_organization" "my_org2" {
name = "test 2"
}
resource "grafana_dashboard" "test_dash2" {
org_id = grafana_organization.my_org2.org_id
config_json = jsonencode({
"title" : "My Terraform Dashboard2",
"uid" : "my-dashboard-uid2"
})
}
resource "grafana_dashboard_public" "my_public_dashboard2" {
org_id = grafana_organization.my_org2.org_id
dashboard_uid = grafana_dashboard.test_dash2.uid
share = "public"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `dashboard_uid` (String) The unique identifier of the original dashboard.

### Optional

- `access_token` (String) A public unique identifier of a public dashboard. This is used to construct its URL. It's automatically generated if not provided when creating a public dashboard.
- `annotations_enabled` (Boolean) Set to `true` to show annotations. The default value is `false`.
- `is_enabled` (Boolean) Set to `true` to enable the public dashboard. The default value is `false`.
- `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used.
- `share` (String) Set the share mode. The default value is `public`.
- `time_selection_enabled` (Boolean) Set to `true` to enable the time picker in the public dashboard. The default value is `false`.
- `uid` (String) The unique identifier of a public dashboard. It's automatically generated if not provided when creating a public dashboard.

### Read-Only

- `id` (String) The ID of this resource.

## Import

Import is supported using the following syntax:

```shell
terraform import grafana_dashboard_public.dashboard_name {{dashboard_uid}}:{{public_dashboard_uid}} # To use the default provider org
terraform import grafana_dashboard_public.dashboard_name {org_id}}:{{dashboard_uid}}:{{public_dashboard_uid}} # When "org_id" is set on the resource
```
2 changes: 2 additions & 0 deletions examples/resources/grafana_dashboard_public/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
terraform import grafana_dashboard_public.dashboard_name {{dashboard_uid}}:{{public_dashboard_uid}} # To use the default provider org
terraform import grafana_dashboard_public.dashboard_name {org_id}}:{{dashboard_uid}}:{{public_dashboard_uid}} # When "org_id" is set on the resource
52 changes: 52 additions & 0 deletions examples/resources/grafana_dashboard_public/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Optional (On-premise, not supported in Grafana Cloud): Create an organization
resource "grafana_organization" "my_org" {
name = "test 1"
}

// Create resources (optional: within the organization)
resource "grafana_folder" "my_folder" {
org_id = grafana_organization.my_org.org_id
title = "test Folder"
}

resource "grafana_dashboard" "test_dash" {
org_id = grafana_organization.my_org.org_id
folder = grafana_folder.my_folder.id
config_json = jsonencode({
"title" : "My Terraform Dashboard",
"uid" : "my-dashboard-uid"
})
}

resource "grafana_dashboard_public" "my_public_dashboard" {
org_id = grafana_organization.my_org.org_id
dashboard_uid = grafana_dashboard.test_dash.uid

uid = "my-custom-public-uid"
access_token = "e99e4275da6f410d83760eefa934d8d2"

time_selection_enabled = true
is_enabled = true
annotations_enabled = true
share = "public"
}

// Optional (On-premise, not supported in Grafana Cloud): Create an organization
resource "grafana_organization" "my_org2" {
name = "test 2"
}

resource "grafana_dashboard" "test_dash2" {
org_id = grafana_organization.my_org2.org_id
config_json = jsonencode({
"title" : "My Terraform Dashboard2",
"uid" : "my-dashboard-uid2"
})
}

resource "grafana_dashboard_public" "my_public_dashboard2" {
org_id = grafana_organization.my_org2.org_id
dashboard_uid = grafana_dashboard.test_dash2.uid

share = "public"
}
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func Provider(version string) func() *schema.Provider {
"grafana_api_key": grafana.ResourceAPIKey(),
"grafana_contact_point": grafana.ResourceContactPoint(),
"grafana_dashboard": grafana.ResourceDashboard(),
"grafana_dashboard_public": grafana.ResourcePublicDashboard(),
"grafana_dashboard_permission": grafana.ResourceDashboardPermission(),
"grafana_data_source": grafana.ResourceDataSource(),
"grafana_data_source_permission": grafana.ResourceDatasourcePermission(),
Expand Down
151 changes: 151 additions & 0 deletions internal/resources/grafana/resource_dashboard_public.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package grafana

import (
"context"
"fmt"
"strconv"
"strings"

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 ResourcePublicDashboard() *schema.Resource {
return &schema.Resource{

Description: `
Manages Grafana public dashboards.
**Note:** This resource is available only with Grafana 10.2+.
* [Official documentation](https://grafana.com/docs/grafana/latest/dashboards/dashboard-public/)
* [HTTP API](https://grafana.com/docs/grafana/next/developers/http_api/dashboard_public/)
`,

CreateContext: CreatePublicDashboard,
ReadContext: ReadPublicDashboard,
UpdateContext: UpdatePublicDashboard,
DeleteContext: DeletePublicDashboard,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: map[string]*schema.Schema{
"org_id": orgIDAttribute(),
"uid": {
Type: schema.TypeString,
Computed: true,
Optional: true,
Description: "The unique identifier of a public dashboard. " +
"It's automatically generated if not provided when creating a public dashboard. ",
},
"dashboard_uid": {
Type: schema.TypeString,
Required: true,
Description: "The unique identifier of the original dashboard.",
},
"access_token": {
Type: schema.TypeString,
Computed: true,
Optional: true,
Description: "A public unique identifier of a public dashboard. This is used to construct its URL. " +
"It's automatically generated if not provided when creating a public dashboard. ",
},
"time_selection_enabled": {
Type: schema.TypeBool,
Optional: true,
Description: "Set to `true` to enable the time picker in the public dashboard. The default value is `false`.",
},
"is_enabled": {
Type: schema.TypeBool,
Optional: true,
Description: "Set to `true` to enable the public dashboard. The default value is `false`.",
},
"annotations_enabled": {
Type: schema.TypeBool,
Optional: true,
Description: "Set to `true` to show annotations. The default value is `false`.",
},
"share": {
Type: schema.TypeString,
Optional: true,
Description: "Set the share mode. The default value is `public`.",
},
},
}
}

func CreatePublicDashboard(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, orgID := ClientFromNewOrgResource(meta, d)
dashboardUID := d.Get("dashboard_uid").(string)

publicDashboardPayload := makePublicDashboard(d)
pd, err := client.NewPublicDashboard(dashboardUID, publicDashboardPayload)
if err != nil {
return diag.FromErr(err)
}

d.SetId(fmt.Sprintf("%d:%s:%s", orgID, pd.DashboardUID, pd.UID))
return ReadPublicDashboard(ctx, d, meta)
}
func UpdatePublicDashboard(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
orgID, dashboardUID, publicDashboardUID := SplitPublicDashboardID(d.Id())
client := meta.(*common.Client).GrafanaAPI.WithOrgID(orgID)

publicDashboard := makePublicDashboard(d)
pd, err := client.UpdatePublicDashboard(dashboardUID, publicDashboardUID, publicDashboard)
if err != nil {
return diag.FromErr(err)
}

d.SetId(fmt.Sprintf("%d:%s:%s", orgID, pd.DashboardUID, pd.UID))
return ReadPublicDashboard(ctx, d, meta)
}

func DeletePublicDashboard(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
orgID, dashboardUID, publicDashboardUID := SplitPublicDashboardID(d.Id())
client := meta.(*common.Client).GrafanaAPI.WithOrgID(orgID)
return diag.FromErr(client.DeletePublicDashboard(dashboardUID, publicDashboardUID))
}

func makePublicDashboard(d *schema.ResourceData) gapi.PublicDashboardPayload {
return gapi.PublicDashboardPayload{
UID: d.Get("uid").(string),
AccessToken: d.Get("access_token").(string),
TimeSelectionEnabled: d.Get("time_selection_enabled").(bool),
IsEnabled: d.Get("is_enabled").(bool),
AnnotationsEnabled: d.Get("annotations_enabled").(bool),
Share: d.Get("share").(string),
}
}

func ReadPublicDashboard(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
orgID, dashboardUID, _ := SplitPublicDashboardID(d.Id())
client := meta.(*common.Client).GrafanaAPI.WithOrgID(orgID)
pd, err := client.PublicDashboardbyUID(dashboardUID)
if err, shouldReturn := common.CheckReadError("dashboard", d, err); shouldReturn {
return err
}

d.Set("org_id", strconv.FormatInt(orgID, 10))

d.Set("uid", pd.UID)
d.Set("dashboard_uid", pd.DashboardUID)
d.Set("access_token", pd.AccessToken)
d.Set("time_selection_enabled", pd.TimeSelectionEnabled)
d.Set("is_enabled", pd.IsEnabled)
d.Set("annotations_enabled", pd.AnnotationsEnabled)
d.Set("share", pd.Share)

d.SetId(fmt.Sprintf("%d:%s:%s", orgID, pd.DashboardUID, pd.UID))

return nil
}

func SplitPublicDashboardID(id string) (int64, string, string) {
ids := strings.Split(id, ":")
orgID, _ := strconv.ParseInt(ids[0], 10, 64)
return orgID, ids[1], ids[2]
}
77 changes: 77 additions & 0 deletions internal/resources/grafana/resource_dashboard_public_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package grafana_test

import (
"fmt"
"testing"

"github.com/grafana/terraform-provider-grafana/internal/common"
"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"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func TestAccPublicDashboard_basic(t *testing.T) {
testutils.CheckOSSTestsEnabled(t, ">=10.2.0")

resource.Test(t, resource.TestCase{
ProviderFactories: testutils.ProviderFactories,
Steps: []resource.TestStep{
{
Config: testutils.TestAccExample(t, "resources/grafana_dashboard_public/resource.tf"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccPublicDashboardCheckExistsUID("grafana_dashboard_public.my_public_dashboard"),
resource.TestCheckResourceAttr("grafana_dashboard_public.my_public_dashboard", "uid", "my-custom-public-uid"),
resource.TestCheckResourceAttr("grafana_dashboard_public.my_public_dashboard", "dashboard_uid", "my-dashboard-uid"),
resource.TestCheckResourceAttr("grafana_dashboard_public.my_public_dashboard", "access_token", "e99e4275da6f410d83760eefa934d8d2"),
resource.TestCheckResourceAttr("grafana_dashboard_public.my_public_dashboard", "is_enabled", "true"),
resource.TestCheckResourceAttr("grafana_dashboard_public.my_public_dashboard", "share", "public"),
resource.TestCheckResourceAttr("grafana_dashboard_public.my_public_dashboard", "time_selection_enabled", "true"),
resource.TestCheckResourceAttr("grafana_dashboard_public.my_public_dashboard", "annotations_enabled", "true"),
checkResourceIsInOrg("grafana_dashboard_public.my_public_dashboard", "grafana_organization.my_org"),

// my_public_dashboard2 belong to a different org_id
checkResourceIsInOrg("grafana_dashboard_public.my_public_dashboard2", "grafana_organization.my_org2"),
resource.TestCheckResourceAttr("grafana_dashboard_public.my_public_dashboard2", "dashboard_uid", "my-dashboard-uid2"),
resource.TestCheckResourceAttr("grafana_dashboard_public.my_public_dashboard2", "is_enabled", "false"),
resource.TestCheckResourceAttr("grafana_dashboard_public.my_public_dashboard2", "share", "public"),
resource.TestCheckResourceAttr("grafana_dashboard_public.my_public_dashboard2", "time_selection_enabled", "false"),
resource.TestCheckResourceAttr("grafana_dashboard_public.my_public_dashboard2", "annotations_enabled", "false"),
),
},
{
ResourceName: "grafana_dashboard_public.my_public_dashboard",
ImportState: true,
ImportStateVerify: true,
},
{
ResourceName: "grafana_dashboard_public.my_public_dashboard2",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccPublicDashboardCheckExistsUID(rn string) 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")
}

orgID, dashboardUID, _ := grafana.SplitPublicDashboardID(rs.Primary.ID)

client := testutils.Provider.Meta().(*common.Client).GrafanaAPI.WithOrgID(orgID)
pd, err := client.PublicDashboardbyUID(dashboardUID)
if pd == nil || err != nil {
return fmt.Errorf("Error getting public dashboard: %s", err)
}

return nil
}
}
Loading

0 comments on commit 633b640

Please sign in to comment.