Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resource: Add LBAC for datasources data_source_lbac_rules #1797

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ require (
gopkg.in/yaml.v2 v2.4.0
)

require github.com/hashicorp/terraform-plugin-log v0.9.0

require (
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect
Expand Down Expand Up @@ -95,7 +97,6 @@ require (
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.2 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
Expand Down Expand Up @@ -178,3 +179,5 @@ require (
)

replace github.com/hashicorp/terraform-exec v0.21.0 => github.com/hrmsk66/terraform-exec v0.21.0

replace github.com/grafana/grafana-openapi-client-go => /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,6 @@ github.com/grafana/amixr-api-go-client v0.0.16 h1:CXdqnLKjvo6QoNPBKxmZ2kGKCcUoAl
github.com/grafana/amixr-api-go-client v0.0.16/go.mod h1:N6x26XUrM5zGtK5zL5vNJnAn2JFMxLFPPLTw/6pDkFE=
github.com/grafana/grafana-com-public-clients/go/gcom v0.0.0-20240807172819-ac10800522a3 h1:CVLTffnWgBGvVaXfUUcSgFrZbiMzvj0/Hpi909zdeG0=
github.com/grafana/grafana-com-public-clients/go/gcom v0.0.0-20240807172819-ac10800522a3/go.mod h1:u9d0BESoKlztYm93CpoRleQjMbYBcZ+JOLHHP2nN6Wg=
github.com/grafana/grafana-openapi-client-go v0.0.0-20240723170622-ae2c94b7c9a3 h1:W35ScJIkeyLuDlOo3F+u1JSRRvmoIYYf/ghA/17Y18Q=
github.com/grafana/grafana-openapi-client-go v0.0.0-20240723170622-ae2c94b7c9a3/go.mod h1:hiZnMmXc9KXNUlvkV2BKFsiWuIFF/fF4wGgYWEjBitI=
github.com/grafana/grafana-plugin-sdk-go v0.250.0 h1:9EBucp9jLqMx2b8NTlOXH+4OuQWUh6L85c6EJUN8Jdo=
github.com/grafana/grafana-plugin-sdk-go v0.250.0/go.mod h1:gCGN9kHY3KeX4qyni3+Kead38Q+85pYOrsDcxZp6AIk=
github.com/grafana/machine-learning-go-client v0.8.2 h1:TvU4e+Kgg4GhwBNYTMjBUNq4tbhcxe0L8w1eo/UfV2M=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ func putMessageTemplate(ctx context.Context, data *schema.ResourceData, meta int
func deleteMessageTemplate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, _, name := OAPIClientFromExistingOrgResource(meta, data.Id())

_, err := client.Provisioning.DeleteTemplate(name)
params := provisioning.NewDeleteTemplateParams().WithName(name)
_, err := client.Provisioning.DeleteTemplate(params)
diag, _ := common.CheckReadError("message template", data, err)
return diag
}
241 changes: 241 additions & 0 deletions internal/resources/grafana/resource_data_source_config_lbac_rules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package grafana

import (
"context"
"encoding/json"
"fmt"

"github.com/grafana/grafana-openapi-client-go/client/enterprise"

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / integration

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / unit tests

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / docs

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / cloudinstance

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 9.5.18 - oss - long

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 11.0.0 - oss - examples

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 10.4.3 - enterprise - enterprise

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 10.4.3 - oss - basic

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 9.5.18 - enterprise - enterprise

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 9.5.18 - oss - other

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 10.4.3 - oss - other

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 10.4.3 - oss - long

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 11.0.0 - oss - other

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 11.0.0 - subpath - basic

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 11.0.0 - oss - basic

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 11.0.0 - oss - long

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 11.0.0 - enterprise - enterprise

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 11.0.0 - subpath - other

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 10.4.3 - enterprise - generate

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 9.5.18 - oss - basic

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 11.0.0 - enterprise - generate

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist

Check failure on line 8 in internal/resources/grafana/resource_data_source_config_lbac_rules.go

View workflow job for this annotation

GitHub Actions / 11.0.0 - tls - basic

github.com/grafana/[email protected]: replacement directory /Users/eleijonmarck/dev/grafana/grafana-openapi-client-go does not exist
"github.com/grafana/grafana-openapi-client-go/models"
"github.com/grafana/terraform-provider-grafana/v3/internal/common"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

var (
// Check interface
_ resource.ResourceWithImportState = (*resourceDataSourceConfigLBACRules)(nil)
)

var (
resourceDataSourceConfigLBACRulesName = "grafana_data_source_config_lbac_rules"
resourceDataSourceConfigLBACRulesID = common.NewResourceID(
common.StringIDField("datasource_uid"),
)
)

func makeResourceDataSourceConfigLBACRules() *common.Resource {
resourceStruct := &resourceDataSourceConfigLBACRules{}
return common.NewResource(
common.CategoryGrafanaEnterprise,
resourceDataSourceConfigLBACRulesName,
resourceDataSourceConfigLBACRulesID,
resourceStruct,
)
}

type LBACRule struct {
TeamID types.String `tfsdk:"team_id"`
TeamUID types.String `tfsdk:"team_uid"`
Rules []types.String `tfsdk:"rules"`
}

type resourceDataSourceConfigLBACRulesModel struct {
ID types.String `tfsdk:"id"`
DatasourceUID types.String `tfsdk:"datasource_uid"`
Rules types.String `tfsdk:"rules"`
}

type resourceDataSourceConfigLBACRules struct {
client *common.Client
}

func (r *resourceDataSourceConfigLBACRules) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = resourceDataSourceConfigLBACRulesName
}

func (r *resourceDataSourceConfigLBACRules) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
tflog.Info(ctx, "Creating LBAC rules Schema")
resp.Schema = schema.Schema{
MarkdownDescription: `
Manages LBAC rules for a data source.

!> Warning: The resource is experimental and will be subject to change. This resource manages the entire LBAC rules tree, and will overwrite any existing rules.

* [Official documentation](https://grafana.com/docs/grafana/latest/administration/data-source-management/teamlbac/)
* [TODO: HTTP API](no api yet)

This resource requires Grafana >=11.0.0.
`,
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
"datasource_uid": schema.StringAttribute{
Required: true,
Description: "The UID of the datasource.",
},
"rules": schema.StringAttribute{
Required: true,
Description: "JSON-encoded LBAC rules for the data source. Map of team IDs to lists of rule strings.",
},
},
}
}

func (r *resourceDataSourceConfigLBACRules) Configure(ctx context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) {
tflog.Info(ctx, "Configuring LBAC rules")
if req.ProviderData == nil {
return
}
r.client = req.ProviderData.(*common.Client)
}

func (r *resourceDataSourceConfigLBACRules) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
tflog.Info(ctx, "Creating LBAC rules")
var data resourceDataSourceConfigLBACRulesModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

tflog.Info(ctx, "Creating LBAC rules", map[string]interface{}{"datasource_uid": data.DatasourceUID.ValueString()})

rulesMap := make(map[string][]string)
err := json.Unmarshal([]byte(data.Rules.ValueString()), &rulesMap)
if err != nil {
resp.Diagnostics.AddError("Invalid rules JSON", fmt.Sprintf("Failed to parse rule s: %v", err))
}

apiRules := make([]*models.TeamLBACRule, 0, len(rulesMap))
for teamUID, rules := range rulesMap {
apiRules = append(apiRules, &models.TeamLBACRule{
TeamID: "",
TeamUID: teamUID,
Rules: rules,
})
}

tflog.Info(ctx, "Creating LBAC rules with the new rulesmaps", map[string]interface{}{"rulesmaps": fmt.Sprintf("%+v", apiRules)})

client := r.client.GrafanaAPI

params := &enterprise.UpdateTeamLBACRulesAPIParams{
Context: ctx,
UID: data.DatasourceUID.ValueString(),
Body: &models.UpdateTeamLBACCommand{Rules: apiRules},
}

_, err = client.Enterprise.UpdateTeamLBACRulesAPI(params)
if err != nil {
resp.Diagnostics.AddError("Failed to create LBAC rules", err.Error())
return
}

tflog.Info(ctx, "LBAC rules created successfully", map[string]interface{}{"datasource_uid": data.DatasourceUID.ValueString()})

data.ID = types.StringValue(data.DatasourceUID.ValueString())
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *resourceDataSourceConfigLBACRules) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data resourceDataSourceConfigLBACRulesModel
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

datasourceUID := data.DatasourceUID.ValueString()
client := r.client.GrafanaAPI

getResp, err := client.Enterprise.GetTeamLBACRulesAPI(datasourceUID)
if err != nil {
resp.Diagnostics.AddError("Failed to get LBAC rules", err.Error())
return
}

rulesMap := make(map[string][]string)
for _, rule := range getResp.Payload.Rules {
rulesMap[rule.TeamUID] = rule.Rules
}

rulesJSON, err := json.Marshal(rulesMap)
if err != nil {
resp.Diagnostics.AddError("Failed to encode rules", err.Error())
return
}

data = resourceDataSourceConfigLBACRulesModel{
ID: types.StringValue(datasourceUID),
DatasourceUID: types.StringValue(datasourceUID),
Rules: types.StringValue(string(rulesJSON)),
}

resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *resourceDataSourceConfigLBACRules) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
tflog.Info(ctx, "Updating LBAC rules")
var data resourceDataSourceConfigLBACRulesModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

tflog.Info(ctx, "Updating LBAC rules", map[string]interface{}{"datasource_uid": data.DatasourceUID.ValueString()})

rulesMap := make(map[string][]string)
err := json.Unmarshal([]byte(data.Rules.ValueString()), &rulesMap)
if err != nil {
resp.Diagnostics.AddError("Invalid rules JSON", fmt.Sprintf("Failed to parse rules: %v", err))
return
}

apiRules := make([]*models.TeamLBACRule, 0, len(rulesMap))
for teamUID, rules := range rulesMap {
apiRules = append(apiRules, &models.TeamLBACRule{
TeamID: "",
TeamUID: teamUID,
Rules: rules,
})
}
tflog.Info(ctx, "Updating LBAC rules with the new rulesmaps", map[string]interface{}{"rulesmaps": fmt.Sprintf("%v+", apiRules)})

datasourceUID := data.DatasourceUID.ValueString()
client := r.client.GrafanaAPI

params := &enterprise.UpdateTeamLBACRulesAPIParams{
Context: ctx,
UID: datasourceUID,
Body: &models.UpdateTeamLBACCommand{Rules: apiRules},
}

_, err = client.Enterprise.UpdateTeamLBACRulesAPI(params)
if err != nil {
resp.Diagnostics.AddError("Failed to update LBAC rules", err.Error())
return
}

tflog.Info(ctx, "LBAC rules updated successfully", map[string]interface{}{"datasource_uid": data.DatasourceUID.ValueString()})

data.ID = types.StringValue(datasourceUID)
data.DatasourceUID = types.StringValue(datasourceUID)
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *resourceDataSourceConfigLBACRules) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
tflog.Warn(ctx, "Delete operation not supported for LBAC rules")
resp.Diagnostics.AddWarning("Operation not supported", "Delete operation is not supported for LBAC rules")
}

func (r *resourceDataSourceConfigLBACRules) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
tflog.Info(ctx, "Importing LBAC rules", map[string]interface{}{"id": req.ID})

datasourceUID := req.ID

resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), datasourceUID)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("datasource_uid"), datasourceUID)...)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package grafana_test

import (
"encoding/json"
"fmt"
"reflect"
"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 TestAccDataSourceConfigLBACRules_basic(t *testing.T) {
testutils.CheckEnterpriseTestsEnabled(t, ">=11.0.0")

name := acctest.RandString(10)

resource.ParallelTest(t, resource.TestCase{
ProtoV5ProviderFactories: testutils.ProtoV5ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccDataSourceConfigLBACRules(name),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("grafana_data_source_config_lbac_rules.test", "rules"),
resource.TestCheckResourceAttrWith("grafana_data_source_config_lbac_rules.test", "rules", func(value string) error {
var rulesMap map[string][]string
err := json.Unmarshal([]byte(value), &rulesMap)
if err != nil {
return fmt.Errorf("failed to parse rules JSON: %v", err)
}

expectedRules := []string{
"{ foo != \"bar\", foo !~ \"baz\" }",
"{ foo = \"qux\" }",
}

if len(rulesMap) != 1 {
return fmt.Errorf("expected 1 team id of rules, got %d", len(rulesMap))
}

for teamUID, teamRules := range rulesMap {
if !reflect.DeepEqual(teamRules, expectedRules) {
return fmt.Errorf("for team %s, expected rules %v, got %v", teamUID, expectedRules, teamRules)
}
}

return nil
}),
resource.TestCheckResourceAttrWith("grafana_data_source.test", "json_data_encoded", func(value string) error {
var jsonData map[string]interface{}
err := json.Unmarshal([]byte(value), &jsonData)
if err != nil {
return fmt.Errorf("failed to parse json_data_encoded: %v", err)
}
return nil
}),
),
},
{
ResourceName: "grafana_data_source_config_lbac_rules.test",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccDataSourceConfigLBACRules(name string) string {
return fmt.Sprintf(`
resource "grafana_team" "test" {
name = "%[1]s-team"
}

resource "grafana_data_source" "test" {
name = "%[1]s"
type = "loki"

basic_auth_enabled = true
basic_auth_username = "admin"

lifecycle {
ignore_changes = [json_data_encoded]
}
}

resource "grafana_data_source_config_lbac_rules" "test" {
datasource_uid = grafana_data_source.test.uid
rules = jsonencode({
"${grafana_team.test.team_uid}" = [
"{ foo != \"bar\", foo !~ \"baz\" }",
"{ foo = \"qux\" }"
]
})

depends_on = [
grafana_team.test,
grafana_data_source.test
]
}
`, name)
}
1 change: 1 addition & 0 deletions internal/resources/grafana/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ var Resources = addValidationToResources(
makeResourceFolderPermissionItem(),
makeResourceDashboardPermissionItem(),
makeResourceDatasourcePermissionItem(),
makeResourceDataSourceConfigLBACRules(),
makeResourceRoleAssignmentItem(),
makeResourceServiceAccountPermissionItem(),
resourceAnnotation(),
Expand Down
Loading