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

Add a resource for group attribute mapping #1850

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ services:
- GF_SERVER_ROOT_URL=${GRAFANA_URL}
- GF_ENTERPRISE_LICENSE_TEXT=${GF_ENTERPRISE_LICENSE_TEXT:-}
- GF_SERVER_SERVE_FROM_SUB_PATH=${GF_SERVER_SERVE_FROM_SUB_PATH:-}
- GF_FEATURE_TOGGLES_ENABLE=nestedFolders
- GF_FEATURE_TOGGLES_ENABLE=nestedFolders,groupAttributeSync
healthcheck:
test: wget --no-verbose --tries=1 --spider http://0.0.0.0:3000/api/health || exit 1 # Use wget because older versions of Grafana don't have curl
interval: 10s
Expand Down
64 changes: 64 additions & 0 deletions docs/resources/group_attribute_mapping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "grafana_group_attribute_mapping Resource - terraform-provider-grafana"
subcategory: "Grafana Enterprise"
description: |-
Group attribute mapping is used to map groups from an external identity provider to Grafana attributes. This resource maps groups to fixed and custom role-based access control roles.
!> Warning: The resource is experimental and will be subject to change. To use the resource, you need to enable groupAttributeSync feature flag.
This resource requires Grafana Enterprise or Cloud >=11.4.0.
---

# grafana_group_attribute_mapping (Resource)

Group attribute mapping is used to map groups from an external identity provider to Grafana attributes. This resource maps groups to fixed and custom role-based access control roles.

!> Warning: The resource is experimental and will be subject to change. To use the resource, you need to enable groupAttributeSync feature flag.

This resource requires Grafana Enterprise or Cloud >=11.4.0.

## Example Usage

```terraform
resource "grafana_role" "report_admin_role" {
name = "Report Administrator"
uid = "report_admin_role_uid"
auto_increment_version = true
permissions {
action = "reports:create"
}
permissions {
action = "reports:read"
scope = "reports:*"
}
}

resource "grafana_group_attribute_mapping" "report_admin_mapping" {
group_id = "business_dev_group_id"
role_uids = [grafana_role.report_admin_role.uid]
}
```

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

### Required

- `group_id` (String) Group ID from the identity provider.
- `role_uids` (Set of String) Fixed or custom Grafana role-based access control role UIDs.

### 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.

## Import

Import is supported using the following syntax:

```shell
terraform import grafana_group_attribute_mapping.name "{{ group_id }}"
terraform import grafana_group_attribute_mapping.name "{{ orgID }}:{{ group_id }}"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
terraform import grafana_group_attribute_mapping.name "{{ group_id }}"
terraform import grafana_group_attribute_mapping.name "{{ orgID }}:{{ group_id }}"
17 changes: 17 additions & 0 deletions examples/resources/grafana_group_attribute_mapping/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
resource "grafana_role" "report_admin_role" {
name = "Report Administrator"
uid = "report_admin_role_uid"
auto_increment_version = true
permissions {
action = "reports:create"
}
permissions {
action = "reports:read"
scope = "reports:*"
}
}

resource "grafana_group_attribute_mapping" "report_admin_mapping" {
group_id = "business_dev_group_id"
role_uids = [grafana_role.report_admin_role.uid]
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/go-openapi/strfmt v0.23.0
github.com/grafana/amixr-api-go-client v0.0.16 // main branch
github.com/grafana/grafana-com-public-clients/go/gcom v0.0.0-20240807172819-ac10800522a3
github.com/grafana/grafana-openapi-client-go v0.0.0-20240723170622-ae2c94b7c9a3
github.com/grafana/grafana-openapi-client-go v0.0.0-20241018134006-9d96c2007bd8
github.com/grafana/machine-learning-go-client v0.8.2
github.com/grafana/slo-openapi-client/go/slo v0.0.0-20240807172758-1b7d00838fc7
github.com/grafana/synthetic-monitoring-agent v0.28.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ github.com/grafana/grafana-com-public-clients/go/gcom v0.0.0-20240807172819-ac10
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-openapi-client-go v0.0.0-20241018134006-9d96c2007bd8 h1:fRwRuI/kDT0feopB1jrcA2Jpn/s4elfidFl+v9Y8qpo=
github.com/grafana/grafana-openapi-client-go v0.0.0-20241018134006-9d96c2007bd8/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
45 changes: 30 additions & 15 deletions internal/resources/grafana/common_check_exists_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ var (
func(t *models.NotificationTemplate) string { return t.Name },
func(client *goapi.GrafanaHTTPAPI, id string) (*models.NotificationTemplate, error) {
resp, err := client.Provisioning.GetTemplate(id)
return payloadOrError(resp, err)
return payloadOrError[models.NotificationTemplate](resp, err)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't make it to automatically infer types, but I am not sure why. Let me know if you have any ideas.

},
)
alertingMuteTimingCheckExists = newCheckExistsHelper(
func(t *models.MuteTimeInterval) string { return t.Name },
func(client *goapi.GrafanaHTTPAPI, id string) (*models.MuteTimeInterval, error) {
resp, err := client.Provisioning.GetMuteTiming(id)
return payloadOrError(resp, err)
return payloadOrError[models.MuteTimeInterval](resp, err)
},
)
alertingNotificationPolicyCheckExists = newCheckExistsHelper(
Expand All @@ -71,7 +71,7 @@ var (
func(client *goapi.GrafanaHTTPAPI, id string) (*models.AlertRuleGroup, error) {
folder, title, _ := strings.Cut(id, ":")
resp, err := client.Provisioning.GetAlertRuleGroup(title, folder)
return payloadOrError(resp, err)
return payloadOrError[models.AlertRuleGroup](resp, err)
},
)
annotationsCheckExists = newCheckExistsHelper(
Expand All @@ -98,22 +98,22 @@ var (
},
func(client *goapi.GrafanaHTTPAPI, id string) (*models.DashboardFullWithMeta, error) {
resp, err := client.Dashboards.GetDashboardByUID(id)
return payloadOrError(resp, err)
return payloadOrError[models.DashboardFullWithMeta](resp, err)
},
)
dashboardPublicCheckExists = newCheckExistsHelper(
func(d *models.PublicDashboard) string { return d.DashboardUID + ":" + d.UID },
func(client *goapi.GrafanaHTTPAPI, id string) (*models.PublicDashboard, error) {
dashboardUID, _, _ := strings.Cut(id, ":")
resp, err := client.DashboardPublic.GetPublicDashboard(dashboardUID)
return payloadOrError(resp, err)
return payloadOrError[models.PublicDashboard](resp, err)
},
)
datasourceCheckExists = newCheckExistsHelper(
func(d *models.DataSource) string { return d.UID },
func(client *goapi.GrafanaHTTPAPI, uid string) (*models.DataSource, error) {
resp, err := client.Datasources.GetDataSourceByUID(uid)
return payloadOrError(resp, err)
return payloadOrError[models.DataSource](resp, err)
},
)
datasourcePermissionsCheckExists = newCheckExistsHelper(
Expand Down Expand Up @@ -144,14 +144,14 @@ var (
func(f *models.Folder) string { return f.UID },
func(client *goapi.GrafanaHTTPAPI, uid string) (*models.Folder, error) {
resp, err := client.Folders.GetFolderByUID(uid)
return payloadOrError(resp, err)
return payloadOrError[models.Folder](resp, err)
},
)
libraryPanelCheckExists = newCheckExistsHelper(
func(t *models.LibraryElementResponse) string { return t.Result.UID },
func(client *goapi.GrafanaHTTPAPI, id string) (*models.LibraryElementResponse, error) {
resp, err := client.LibraryElements.GetLibraryElementByUID(id)
return payloadOrError(resp, err)
return payloadOrError[models.LibraryElementResponse](resp, err)
},
)
orgCheckExists = newCheckExistsHelper(
Expand All @@ -161,7 +161,7 @@ var (
if err, ok := err.(runtime.ClientResponseStatus); ok && err.IsCode(403) {
return nil, &runtime.APIError{Code: 404, Response: "forbidden. The org either does not exist or the user does not have access to it"}
}
return payloadOrError(resp, err)
return payloadOrError[models.OrgDetailsDTO](resp, err)
},
)
playlistCheckExists = newCheckExistsHelper(
Expand All @@ -173,14 +173,14 @@ var (
},
func(client *goapi.GrafanaHTTPAPI, id string) (*models.Playlist, error) {
resp, err := client.Playlists.GetPlaylist(id)
return payloadOrError(resp, err)
return payloadOrError[models.Playlist](resp, err)
},
)
roleCheckExists = newCheckExistsHelper(
func(r *models.RoleDTO) string { return r.UID },
func(client *goapi.GrafanaHTTPAPI, id string) (*models.RoleDTO, error) {
resp, err := client.AccessControl.GetRole(id)
return payloadOrError(resp, err)
return payloadOrError[models.RoleDTO](resp, err)
},
)
roleAssignmentCheckExists = newCheckExistsHelper(
Expand All @@ -205,7 +205,7 @@ var (
func(t *models.ServiceAccountDTO) string { return strconv.FormatInt(t.ID, 10) },
func(client *goapi.GrafanaHTTPAPI, id string) (*models.ServiceAccountDTO, error) {
resp, err := client.ServiceAccounts.RetrieveServiceAccount(mustParseInt64(id))
return payloadOrError(resp, err)
return payloadOrError[models.ServiceAccountDTO](resp, err)
},
)
serviceAccountPermissionsCheckExists = newCheckExistsHelper(
Expand Down Expand Up @@ -236,22 +236,37 @@ var (
func(t *models.TeamDTO) string { return strconv.FormatInt(t.ID, 10) },
func(client *goapi.GrafanaHTTPAPI, id string) (*models.TeamDTO, error) {
resp, err := client.Teams.GetTeamByID(id)
return payloadOrError(resp, err)
return payloadOrError[models.TeamDTO](resp, err)
},
)
userCheckExists = newCheckExistsHelper(
func(u *models.UserProfileDTO) string { return strconv.FormatInt(u.ID, 10) },
func(client *goapi.GrafanaHTTPAPI, id string) (*models.UserProfileDTO, error) {
resp, err := client.Users.GetUserByID(mustParseInt64(id))
return payloadOrError(resp, err)
return payloadOrError[models.UserProfileDTO](resp, err)
},
)

reportCheckExists = newCheckExistsHelper(
func(u *models.Report) string { return strconv.FormatInt(u.ID, 10) },
func(client *goapi.GrafanaHTTPAPI, id string) (*models.Report, error) {
resp, err := client.Reports.GetReport(mustParseInt64(id))
return payloadOrError(resp, err)
return payloadOrError[models.Report](resp, err)
},
)
groupAttrMappingCheckExists = newCheckExistsHelper(
func(g *models.Group) string { return g.GroupID },
func(client *goapi.GrafanaHTTPAPI, id string) (*models.Group, error) {
resp, err := client.GroupAttributeSync.GetMappedGroups()
if err != nil {
return nil, err
}
for _, group := range resp.Payload.Groups {
if group.GroupID == id {
return group, nil
}
}
return nil, &runtime.APIError{Code: 404, Response: "mapping not found"}
},
)
)
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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the openAPI spec has changed slightly for alerting.

diag, _ := common.CheckReadError("message template", data, err)
return diag
}
Loading
Loading