Skip to content

Commit

Permalink
grafana_library_panels datasource (#1651)
Browse files Browse the repository at this point in the history
Closes #376
Just doing this one to have a first plugin framework datasource
  • Loading branch information
julienduchesne authored Jul 10, 2024
1 parent 8ab0a2c commit f14dcad
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 6 deletions.
73 changes: 73 additions & 0 deletions docs/data-sources/library_panels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "grafana_library_panels Data Source - terraform-provider-grafana"
subcategory: "Grafana OSS"
description: |-
---

# grafana_library_panels (Data Source)



## Example Usage

```terraform
resource "grafana_library_panel" "test" {
name = "panelname"
model_json = jsonencode({
title = "test name"
type = "text"
version = 0
description = "test description"
})
}
resource "grafana_folder" "test" {
title = "Panel Folder"
uid = "panelname-folder"
}
resource "grafana_library_panel" "folder" {
name = "panelname In Folder"
folder_uid = grafana_folder.test.uid
model_json = jsonencode({
gridPos = {
x = 0
y = 0
h = 10
w = 10
}
title = "panel"
type = "text"
version = 0
})
}
data "grafana_library_panels" "all" {
depends_on = [grafana_library_panel.folder, grafana_library_panel.test]
}
```

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

### Optional

- `org_id` (String) The Organization ID. If not set, the default organization is used for basic authentication, or the one that owns your service account for token authentication.

### Read-Only

- `id` (String) The ID of this resource.
- `panels` (Set of Object) (see [below for nested schema](#nestedatt--panels))

<a id="nestedatt--panels"></a>
### Nested Schema for `panels`

Read-Only:

- `description` (String)
- `folder_uid` (String)
- `model_json` (String)
- `name` (String)
- `uid` (String)
2 changes: 1 addition & 1 deletion docs/resources/dashboard_permission_item.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ resource "grafana_dashboard_permission_item" "team" {

### Optional

- `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used.
- `org_id` (String) The Organization ID. If not set, the default organization is used for basic authentication, or the one that owns your service account for token authentication.
- `role` (String) the role onto which the permission is to be assigned
- `team` (String) the team onto which the permission is to be assigned
- `user` (String) the user or service account onto which the permission is to be assigned
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/data_source_permission_item.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ resource "grafana_data_source_permission_item" "service_account" {

### Optional

- `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used.
- `org_id` (String) The Organization ID. If not set, the default organization is used for basic authentication, or the one that owns your service account for token authentication.
- `role` (String) the role onto which the permission is to be assigned
- `team` (String) the team onto which the permission is to be assigned
- `user` (String) the user or service account onto which the permission is to be assigned
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/folder_permission_item.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ resource "grafana_folder_permission_item" "on_user" {

### Optional

- `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used.
- `org_id` (String) The Organization ID. If not set, the default organization is used for basic authentication, or the one that owns your service account for token authentication.
- `role` (String) the role onto which the permission is to be assigned
- `team` (String) the team onto which the permission is to be assigned
- `user` (String) the user or service account onto which the permission is to be assigned
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/role_assignment_item.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ resource "grafana_role_assignment_item" "service_account" {

### Optional

- `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used.
- `org_id` (String) The Organization ID. If not set, the default organization is used for basic authentication, or the one that owns your service account for token authentication.
- `service_account_id` (String) the service account onto which the role is to be assigned
- `team_id` (String) the team onto which the role is to be assigned
- `user_id` (String) the user onto which the role is to be assigned
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/service_account_permission_item.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ resource "grafana_service_account_permission_item" "on_user" {

### Optional

- `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used.
- `org_id` (String) The Organization ID. If not set, the default organization is used for basic authentication, or the one that owns your service account for token authentication.
- `team` (String) the team onto which the permission is to be assigned
- `user` (String) the user or service account onto which the permission is to be assigned

Expand Down
34 changes: 34 additions & 0 deletions examples/data-sources/grafana_library_panels/data-source.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
resource "grafana_library_panel" "test" {
name = "panelname"
model_json = jsonencode({
title = "test name"
type = "text"
version = 0
description = "test description"
})
}

resource "grafana_folder" "test" {
title = "Panel Folder"
uid = "panelname-folder"
}

resource "grafana_library_panel" "folder" {
name = "panelname In Folder"
folder_uid = grafana_folder.test.uid
model_json = jsonencode({
gridPos = {
x = 0
y = 0
h = 10
w = 10
}
title = "panel"
type = "text"
version = 0
})
}

data "grafana_library_panels" "all" {
depends_on = [grafana_library_panel.folder, grafana_library_panel.test]
}
46 changes: 45 additions & 1 deletion internal/resources/grafana/common_plugin_framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

goapi "github.com/grafana/grafana-openapi-client-go/client"
"github.com/grafana/terraform-provider-grafana/v3/internal/common"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
frameworkSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
Expand All @@ -15,6 +16,49 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)

type basePluginFrameworkDataSource struct {
client *goapi.GrafanaHTTPAPI
config *goapi.TransportConfig
}

func (r *basePluginFrameworkDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
// Configure is called multiple times (sometimes when ProviderData is not yet available), we only want to configure once
if req.ProviderData == nil || r.client != nil {
return
}

client, ok := req.ProviderData.(*common.Client)

if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *common.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

r.client = client.GrafanaAPI
r.config = client.GrafanaAPIConfig
}

// clientFromNewOrgResource creates an OpenAPI client from the `org_id` attribute of a resource
// This client is meant to be used in `Create` functions when the ID hasn't already been baked into the resource ID
func (r *basePluginFrameworkDataSource) clientFromNewOrgResource(orgIDStr string) (*goapi.GrafanaHTTPAPI, int64, error) {
if r.client == nil {
return nil, 0, fmt.Errorf("client not configured")
}

client := r.client.Clone()
orgID, _ := strconv.ParseInt(orgIDStr, 10, 64)
if orgID == 0 {
orgID = client.OrgID()
} else if orgID > 0 {
client = client.WithOrgID(orgID)
}
return client, orgID, nil
}

type basePluginFrameworkResource struct {
client *goapi.GrafanaHTTPAPI
config *goapi.TransportConfig
Expand Down Expand Up @@ -98,7 +142,7 @@ func pluginFrameworkOrgIDAttribute() frameworkSchema.Attribute {
return frameworkSchema.StringAttribute{
Optional: true,
Computed: true,
Description: "The Organization ID. If not set, the Org ID defined in the provider block will be used.",
Description: "The Organization ID. If not set, the default organization is used for basic authentication, or the one that owns your service account for token authentication.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
&orgIDAttributePlanModifier{},
Expand Down
106 changes: 106 additions & 0 deletions internal/resources/grafana/data_source_library_panels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package grafana

import (
"context"
"encoding/json"

"github.com/grafana/grafana-openapi-client-go/client/library_elements"
"github.com/grafana/terraform-provider-grafana/v3/internal/common"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
)

var dataSourceLibraryPanelsName = "grafana_library_panels"

func datasourceLibraryPanels() *common.DataSource {
return common.NewDataSource(
common.CategoryGrafanaOSS,
dataSourceLibraryPanelsName,
&libraryPanelsDataSource{},
)
}

type libraryPanelsDataSource struct {
basePluginFrameworkDataSource
}

func (r *libraryPanelsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = dataSourceLibraryPanelsName
}

func (r *libraryPanelsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
"org_id": pluginFrameworkOrgIDAttribute(),
"panels": schema.SetAttribute{
Computed: true,
ElementType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"name": types.StringType,
"uid": types.StringType,
"description": types.StringType,
"folder_uid": types.StringType,
"model_json": types.StringType,
},
},
},
},
}
}

type libraryPanelsDataSourcePanelModel struct {
Name types.String `tfsdk:"name"`
UID types.String `tfsdk:"uid"`
Description types.String `tfsdk:"description"`
FolderUID types.String `tfsdk:"folder_uid"`
ModelJSON types.String `tfsdk:"model_json"`
}

type libraryPanelsDataSourceModel struct {
ID types.String `tfsdk:"id"`
OrgID types.String `tfsdk:"org_id"`
Panels []libraryPanelsDataSourcePanelModel `tfsdk:"panels"`
}

func (r *libraryPanelsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
// Read Terraform state data into the model
var data libraryPanelsDataSourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

// Read from API
client, _, err := r.clientFromNewOrgResource(data.OrgID.ValueString())
if err != nil {
resp.Diagnostics = diag.Diagnostics{diag.NewErrorDiagnostic("Failed to create client", err.Error())}
return
}
params := library_elements.NewGetLibraryElementsParams().WithKind(common.Ref(libraryPanelKind))
apiResp, err := client.LibraryElements.GetLibraryElements(params)
if err != nil {
resp.Diagnostics = diag.Diagnostics{diag.NewErrorDiagnostic("Failed to get library panels", err.Error())}
return
}
for _, panel := range apiResp.Payload.Result.Elements {
modelJSONBytes, err := json.Marshal(panel.Model)
if err != nil {
resp.Diagnostics = diag.Diagnostics{diag.NewErrorDiagnostic("Failed to get library panel JSON", err.Error())}
return
}
data.Panels = append(data.Panels, libraryPanelsDataSourcePanelModel{
Name: types.StringValue(panel.Name),
UID: types.StringValue(panel.UID),
Description: types.StringValue(panel.Description),
FolderUID: types.StringValue(panel.Meta.FolderUID),
ModelJSON: types.StringValue(string(modelJSONBytes)),
})
}
data.ID = types.StringValue(data.OrgID.ValueString())

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, data)...)
}
38 changes: 38 additions & 0 deletions internal/resources/grafana/data_source_library_panels_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package grafana_test

import (
"testing"

"github.com/grafana/terraform-provider-grafana/v3/internal/testutils"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccDatasourceLibraryPanels_basic(t *testing.T) {
testutils.CheckOSSTestsEnabled(t, ">=8.0.0")

randomName := acctest.RandString(10)

resource.ParallelTest(t, resource.TestCase{
ProtoV5ProviderFactories: testutils.ProtoV5ProviderFactories,
Steps: []resource.TestStep{
{
Config: testutils.TestAccExampleWithReplace(t, "data-sources/grafana_library_panels/data-source.tf", map[string]string{
"panelname": randomName,
}),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemNestedAttrs("data.grafana_library_panels.all", "panels.*", map[string]string{
"description": "test description",
"folder_uid": "",
"panels.0.name": randomName,
}),
resource.TestCheckTypeSetElemNestedAttrs("data.grafana_library_panels.all", "panels.*", map[string]string{
"description": "",
"folder_uid": randomName + "-folder",
"panels.0.name": randomName + " In Folder",
}),
),
},
},
})
}
1 change: 1 addition & 0 deletions internal/resources/grafana/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ var DataSources = addValidationToDataSources(
datasourceFolder(),
datasourceFolders(),
datasourceLibraryPanel(),
datasourceLibraryPanels(),
datasourceUser(),
datasourceUsers(),
datasourceRole(),
Expand Down

0 comments on commit f14dcad

Please sign in to comment.