diff --git a/docs/resources/dashboard_public.md b/docs/resources/dashboard_public.md new file mode 100644 index 000000000..02116e4fc --- /dev/null +++ b/docs/resources/dashboard_public.md @@ -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 + +### 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 +``` diff --git a/examples/resources/grafana_dashboard_public/import.sh b/examples/resources/grafana_dashboard_public/import.sh new file mode 100644 index 000000000..d426c422b --- /dev/null +++ b/examples/resources/grafana_dashboard_public/import.sh @@ -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 diff --git a/examples/resources/grafana_dashboard_public/resource.tf b/examples/resources/grafana_dashboard_public/resource.tf new file mode 100644 index 000000000..59a1cc22d --- /dev/null +++ b/examples/resources/grafana_dashboard_public/resource.tf @@ -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" +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index b523bafb5..a5fe62e27 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -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(), diff --git a/internal/resources/grafana/resource_dashboard_public.go b/internal/resources/grafana/resource_dashboard_public.go new file mode 100644 index 000000000..99049fdac --- /dev/null +++ b/internal/resources/grafana/resource_dashboard_public.go @@ -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] +} diff --git a/internal/resources/grafana/resource_dashboard_public_test.go b/internal/resources/grafana/resource_dashboard_public_test.go new file mode 100644 index 000000000..34f9f874d --- /dev/null +++ b/internal/resources/grafana/resource_dashboard_public_test.go @@ -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 + } +} diff --git a/tools/subcategories.json b/tools/subcategories.json index 032d7e8d3..34f701b16 100644 --- a/tools/subcategories.json +++ b/tools/subcategories.json @@ -9,6 +9,7 @@ "resources/annotation": "Grafana OSS", "resources/api_key": "Grafana OSS", "resources/dashboard": "Grafana OSS", + "resources/dashboard_public": "Grafana OSS", "resources/dashboard_permission": "Grafana OSS", "resources/data_source": "Grafana OSS", "resources/folder": "Grafana OSS",