-
Notifications
You must be signed in to change notification settings - Fork 235
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Resource: Add LBAC for datasources
data_source_config_lbac_rules
(#…
…1797) * WIP * WIP * WIP * WIP * working experimental * revert one file * reset to main * commit deleted file * made the id from the datasourceUID * Update to use uid * added example for lbac rules * added constraint about >=11.4.0 * fix example test * skip the lbac rules for example tests * refactor to updateRules on create,delete and update * review comments
- Loading branch information
1 parent
1fd2410
commit a2ff67e
Showing
7 changed files
with
445 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
--- | ||
# generated by https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "grafana_data_source_config_lbac_rules Resource - terraform-provider-grafana" | ||
subcategory: "Grafana Enterprise" | ||
description: |- | ||
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/HTTP API https://grafana.com/docs/grafana/latest/developers/http_api/datasource_lbac_rules/ | ||
This resource requires Grafana >=11.5.0. | ||
--- | ||
|
||
# grafana_data_source_config_lbac_rules (Resource) | ||
|
||
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/) | ||
* [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/datasource_lbac_rules/) | ||
|
||
This resource requires Grafana >=11.5.0. | ||
|
||
## Example Usage | ||
|
||
```terraform | ||
resource "grafana_team" "team" { | ||
name = "Team Name" | ||
} | ||
resource "grafana_data_source" "test" { | ||
type = "loki" | ||
name = "loki-from-terraform" | ||
url = "https://mylokiurl.net" | ||
basic_auth_enabled = true | ||
basic_auth_username = "username" | ||
json_data_encoded = jsonencode({ | ||
authType = "default" | ||
basicAuthPassword = "password" | ||
}) | ||
} | ||
# resource "grafana_data_source_config_lbac_rules" "test_rule" { | ||
# datasource_uid = grafana_data_source.test.uid | ||
# rules = jsonencode({ | ||
# "${grafana_team.team.team_uid}" = [ | ||
# "{ cluster = \"dev-us-central-0\", namespace = \"hosted-grafana\" }", | ||
# "{ foo = \"qux\" }" | ||
# ] | ||
# }) | ||
# depends_on = [ | ||
# grafana_team.team, | ||
# grafana_data_source.test | ||
# ] | ||
# } | ||
``` | ||
|
||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Required | ||
|
||
- `datasource_uid` (String) The UID of the datasource. | ||
- `rules` (String) JSON-encoded LBAC rules for the data source. Map of team UIDs to lists of rule strings. | ||
|
||
### Read-Only | ||
|
||
- `id` (String) The ID of this resource. | ||
|
||
## Import | ||
|
||
Import is supported using the following syntax: | ||
|
||
```shell | ||
terraform import grafana_data_source_config_lbac_rules.name "{{ datasource_uid }}" | ||
``` |
1 change: 1 addition & 0 deletions
1
examples/resources/grafana_data_source_config_lbac_rules/import.sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
terraform import grafana_data_source_config_lbac_rules.name "{{ datasource_uid }}" |
32 changes: 32 additions & 0 deletions
32
examples/resources/grafana_data_source_config_lbac_rules/resource.tf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
resource "grafana_team" "team" { | ||
name = "Team Name" | ||
} | ||
|
||
resource "grafana_data_source" "test" { | ||
type = "loki" | ||
name = "loki-from-terraform" | ||
url = "https://mylokiurl.net" | ||
basic_auth_enabled = true | ||
basic_auth_username = "username" | ||
|
||
json_data_encoded = jsonencode({ | ||
authType = "default" | ||
basicAuthPassword = "password" | ||
}) | ||
} | ||
|
||
# resource "grafana_data_source_config_lbac_rules" "test_rule" { | ||
# datasource_uid = grafana_data_source.test.uid | ||
# rules = jsonencode({ | ||
# "${grafana_team.team.team_uid}" = [ | ||
# "{ cluster = \"dev-us-central-0\", namespace = \"hosted-grafana\" }", | ||
# "{ foo = \"qux\" }" | ||
# ] | ||
# }) | ||
|
||
# depends_on = [ | ||
# grafana_team.team, | ||
# grafana_data_source.test | ||
# ] | ||
# } | ||
|
231 changes: 231 additions & 0 deletions
231
internal/resources/grafana/resource_data_source_config_lbac_rules.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
package grafana | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
|
||
"github.com/grafana/grafana-openapi-client-go/client/enterprise" | ||
"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" | ||
) | ||
|
||
// Note: The LBAC Rules API only supports GET and UPDATE operations. | ||
// There is no CREATE or DELETE API endpoint. The UPDATE operation is used | ||
// for all modifications (create/update/delete) by sending the complete desired | ||
// state. Deleting rules is done by sending an empty rules list. | ||
|
||
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 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) { | ||
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/) | ||
* [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/datasource_lbac_rules/) | ||
This resource requires Grafana >=11.5.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 UIDs to lists of rule strings.", | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func (r *resourceDataSourceConfigLBACRules) Configure(ctx context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { | ||
if req.ProviderData == nil { | ||
return | ||
} | ||
r.client = req.ProviderData.(*common.Client) | ||
} | ||
|
||
// Add this helper function to handle the common update logic | ||
func (r *resourceDataSourceConfigLBACRules) updateRules(ctx context.Context, data *resourceDataSourceConfigLBACRulesModel, rules map[string][]string) error { | ||
apiRules := make([]*models.TeamLBACRule, 0, len(rules)) | ||
for teamUID, ruleList := range rules { | ||
apiRules = append(apiRules, &models.TeamLBACRule{ | ||
TeamUID: teamUID, | ||
Rules: ruleList, | ||
}) | ||
} | ||
|
||
params := &enterprise.UpdateTeamLBACRulesAPIParams{ | ||
Context: ctx, | ||
UID: data.DatasourceUID.ValueString(), | ||
Body: &models.UpdateTeamLBACCommand{Rules: apiRules}, | ||
} | ||
|
||
_, err := r.client.GrafanaAPI.Enterprise.UpdateTeamLBACRulesAPI(params) | ||
if err != nil { | ||
return fmt.Errorf("failed to update LBAC rules for datasource %q: %w", data.DatasourceUID.ValueString(), err) | ||
} | ||
return nil | ||
} | ||
|
||
func (r *resourceDataSourceConfigLBACRules) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { | ||
var data resourceDataSourceConfigLBACRulesModel | ||
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
rulesMap := make(map[string][]string) | ||
if err := json.Unmarshal([]byte(data.Rules.ValueString()), &rulesMap); err != nil { | ||
resp.Diagnostics.AddError( | ||
"Invalid rules JSON", | ||
fmt.Sprintf("Failed to parse rules for datasource %q: %v. Please ensure the rules are valid JSON.", data.DatasourceUID.ValueString(), err), | ||
) | ||
return | ||
} | ||
|
||
if err := r.updateRules(ctx, &data, rulesMap); err != nil { | ||
resp.Diagnostics.AddError("Failed to create LBAC rules", err.Error()) | ||
return | ||
} | ||
|
||
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", | ||
fmt.Sprintf("Could not read LBAC rules for datasource %q: %v", datasourceUID, err), | ||
) | ||
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 { | ||
// Marshal error should never happen for a valid map | ||
resp.Diagnostics.AddError( | ||
"Failed to encode rules", | ||
fmt.Sprintf("Could not encode LBAC rules for datasource %q: %v. This is an internal error, please report it.", datasourceUID, err), | ||
) | ||
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) { | ||
var data resourceDataSourceConfigLBACRulesModel | ||
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
rulesMap := make(map[string][]string) | ||
if err := json.Unmarshal([]byte(data.Rules.ValueString()), &rulesMap); err != nil { | ||
resp.Diagnostics.AddError( | ||
"Invalid rules JSON", | ||
fmt.Sprintf("Failed to parse updated rules for datasource %q: %v. Please ensure the rules are valid JSON.", data.DatasourceUID.ValueString(), err), | ||
) | ||
return | ||
} | ||
|
||
if err := r.updateRules(ctx, &data, rulesMap); err != nil { | ||
resp.Diagnostics.AddError("Failed to update LBAC rules", err.Error()) | ||
return | ||
} | ||
|
||
data.ID = types.StringValue(data.DatasourceUID.ValueString()) | ||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) | ||
} | ||
|
||
func (r *resourceDataSourceConfigLBACRules) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { | ||
var state resourceDataSourceConfigLBACRulesModel | ||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
// Pass empty rules map to clear all rules | ||
if err := r.updateRules(ctx, &state, make(map[string][]string)); err != nil { | ||
resp.Diagnostics.AddError( | ||
"Failed to delete LBAC rules", | ||
fmt.Sprintf("Could not delete LBAC rules for datasource %q: %v", state.DatasourceUID.ValueString(), err), | ||
) | ||
return | ||
} | ||
} | ||
|
||
func (r *resourceDataSourceConfigLBACRules) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { | ||
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)...) | ||
} |
Oops, something went wrong.