From 998a6d5d26eb584ab82a3e77dbaa338c1799936f Mon Sep 17 00:00:00 2001 From: Julien Duchesne Date: Wed, 22 Nov 2023 14:07:48 -0500 Subject: [PATCH] Permission Resources: Allow empty permissions Closes https://github.com/grafana/terraform-provider-grafana/issues/142 When programatically managing permissions, it can be useful to keep the permissions resource but remove all permissions Also, without this change, resources with no permissions cannot be imported because the permissions resources are invalid with an empty set of permissions Also updated the docs to make it clear that it manages the whole set of permissions. --- docs/resources/dashboard_permission.md | 10 ++-- docs/resources/data_source_permission.md | 6 ++- docs/resources/folder_permission.md | 16 +++++- docs/resources/role_assignment.md | 2 + docs/resources/service_account_permission.md | 5 +- .../grafana_folder_permission/import.sh | 2 + .../grafana/resource_dashboard_permission.go | 17 ++++--- .../resource_data_source_permission.go | 16 +++--- .../grafana/resource_folder_permission.go | 20 +++++--- .../resource_folder_permission_test.go | 49 +++++++++++++++++++ .../grafana/resource_role_assignment.go | 1 + .../resource_service_account_permission.go | 9 +++- 12 files changed, 123 insertions(+), 30 deletions(-) create mode 100644 examples/resources/grafana_folder_permission/import.sh diff --git a/docs/resources/dashboard_permission.md b/docs/resources/dashboard_permission.md index 1745ce074..33432176a 100644 --- a/docs/resources/dashboard_permission.md +++ b/docs/resources/dashboard_permission.md @@ -3,11 +3,14 @@ page_title: "grafana_dashboard_permission Resource - terraform-provider-grafana" subcategory: "Grafana OSS" description: |- - Official documentation https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/HTTP API https://grafana.com/docs/grafana/latest/developers/http_api/dashboard_permissions/ + Manages the entire set of permissions for a dashboard. Permissions that aren't specified when applying this resource will be removed. + * Official documentation https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/ + * HTTP API https://grafana.com/docs/grafana/latest/developers/http_api/dashboard_permissions/ --- # grafana_dashboard_permission (Resource) +Manages the entire set of permissions for a dashboard. Permissions that aren't specified when applying this resource will be removed. * [Official documentation](https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/) * [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/dashboard_permissions/) @@ -46,15 +49,12 @@ resource "grafana_dashboard_permission" "collectionPermission" { ## Schema -### Required - -- `permissions` (Block Set, Min: 1) The permission items to add/update. Items that are omitted from the list will be removed. (see [below for nested schema](#nestedblock--permissions)) - ### Optional - `dashboard_id` (Number, Deprecated) ID of the dashboard to apply permissions to. Deprecated: use `dashboard_uid` instead. - `dashboard_uid` (String) UID of the dashboard to apply permissions to. - `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used. +- `permissions` (Block Set) The permission items to add/update. Items that are omitted from the list will be removed. (see [below for nested schema](#nestedblock--permissions)) ### Read-Only diff --git a/docs/resources/data_source_permission.md b/docs/resources/data_source_permission.md index 439b57d3e..c7a803913 100644 --- a/docs/resources/data_source_permission.md +++ b/docs/resources/data_source_permission.md @@ -3,11 +3,13 @@ page_title: "grafana_data_source_permission Resource - terraform-provider-grafana" subcategory: "Grafana Enterprise" description: |- - HTTP API https://grafana.com/docs/grafana/latest/developers/http_api/datasource_permissions/ + Manages the entire set of permissions for a datasource. Permissions that aren't specified when applying this resource will be removed. + * HTTP API https://grafana.com/docs/grafana/latest/developers/http_api/datasource_permissions/ --- # grafana_data_source_permission (Resource) +Manages the entire set of permissions for a datasource. Permissions that aren't specified when applying this resource will be removed. * [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/datasource_permissions/) ## Example Usage @@ -71,11 +73,11 @@ resource "grafana_data_source_permission" "fooPermissions" { ### Required - `datasource_id` (String) ID of the datasource to apply permissions to. -- `permissions` (Block Set, Min: 1) The permission items to add/update. Items that are omitted from the list will be removed. (see [below for nested schema](#nestedblock--permissions)) ### Optional - `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used. +- `permissions` (Block Set) The permission items to add/update. Items that are omitted from the list will be removed. (see [below for nested schema](#nestedblock--permissions)) ### Read-Only diff --git a/docs/resources/folder_permission.md b/docs/resources/folder_permission.md index 603da58ef..addc61a62 100644 --- a/docs/resources/folder_permission.md +++ b/docs/resources/folder_permission.md @@ -3,11 +3,14 @@ page_title: "grafana_folder_permission Resource - terraform-provider-grafana" subcategory: "Grafana OSS" description: |- - Official documentation https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/HTTP API https://grafana.com/docs/grafana/latest/developers/http_api/folder_permissions/ + Manages the entire set of permissions for a folder. Permissions that aren't specified when applying this resource will be removed. + * Official documentation https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/ + * HTTP API https://grafana.com/docs/grafana/latest/developers/http_api/folder_permissions/ --- # grafana_folder_permission (Resource) +Manages the entire set of permissions for a folder. Permissions that aren't specified when applying this resource will be removed. * [Official documentation](https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/) * [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/folder_permissions/) @@ -49,11 +52,11 @@ resource "grafana_folder_permission" "collectionPermission" { ### Required - `folder_uid` (String) The UID of the folder. -- `permissions` (Block Set, Min: 1) The permission items to add/update. Items that are omitted from the list will be removed. (see [below for nested schema](#nestedblock--permissions)) ### Optional - `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used. +- `permissions` (Block Set) The permission items to add/update. Items that are omitted from the list will be removed. (see [below for nested schema](#nestedblock--permissions)) ### Read-Only @@ -71,3 +74,12 @@ Optional: - `role` (String) Manage permissions for `Viewer` or `Editor` roles. - `team_id` (String) ID of the team to manage permissions for. Defaults to `0`. - `user_id` (String) ID of the user or service account to manage permissions for. Defaults to `0`. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import grafana_folder_permission.my_folder {{folder_uid}} # To use the default provider org +terraform import grafana_folder_permission.my_folder {{org_id}}:{{folder_uid}} # When "org_id" is set on the resource +``` diff --git a/docs/resources/role_assignment.md b/docs/resources/role_assignment.md index 88f032ae9..419cd72f4 100644 --- a/docs/resources/role_assignment.md +++ b/docs/resources/role_assignment.md @@ -3,6 +3,7 @@ page_title: "grafana_role_assignment Resource - terraform-provider-grafana" subcategory: "Grafana Enterprise" description: |- + Manages the entire set of assignments for a role. Assignments that aren't specified when applying this resource will be removed. Note: This resource is available only with Grafana Enterprise 9.2+. * Official documentation https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/ * HTTP API https://grafana.com/docs/grafana/latest/developers/http_api/access_control/ @@ -10,6 +11,7 @@ description: |- # grafana_role_assignment (Resource) +Manages the entire set of assignments for a role. Assignments that aren't specified when applying this resource will be removed. **Note:** This resource is available only with Grafana Enterprise 9.2+. * [Official documentation](https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/) * [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/access_control/) diff --git a/docs/resources/service_account_permission.md b/docs/resources/service_account_permission.md index 1c7c28360..f340e1710 100644 --- a/docs/resources/service_account_permission.md +++ b/docs/resources/service_account_permission.md @@ -3,12 +3,15 @@ page_title: "grafana_service_account_permission Resource - terraform-provider-grafana" subcategory: "Grafana OSS" description: |- + Manages the entire set of permissions for a service account. Permissions that aren't specified when applying this resource will be removed. Note: This resource is available from Grafana 9.2.4 onwards. Official documentation https://grafana.com/docs/grafana/latest/administration/service-accounts/#manage-users-and-teams-permissions-for-a-service-account-in-grafana --- # grafana_service_account_permission (Resource) +Manages the entire set of permissions for a service account. Permissions that aren't specified when applying this resource will be removed. + **Note:** This resource is available from Grafana 9.2.4 onwards. * [Official documentation](https://grafana.com/docs/grafana/latest/administration/service-accounts/#manage-users-and-teams-permissions-for-a-service-account-in-grafana) @@ -51,12 +54,12 @@ resource "grafana_service_account_permission" "test_permissions" { ### Required -- `permissions` (Block Set, Min: 1) The permission items to add/update. Items that are omitted from the list will be removed. (see [below for nested schema](#nestedblock--permissions)) - `service_account_id` (String) The id of the service account. ### Optional - `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used. +- `permissions` (Block Set) The permission items to add/update. Items that are omitted from the list will be removed. (see [below for nested schema](#nestedblock--permissions)) ### Read-Only diff --git a/examples/resources/grafana_folder_permission/import.sh b/examples/resources/grafana_folder_permission/import.sh new file mode 100644 index 000000000..acfaae431 --- /dev/null +++ b/examples/resources/grafana_folder_permission/import.sh @@ -0,0 +1,2 @@ +terraform import grafana_folder_permission.my_folder {{folder_uid}} # To use the default provider org +terraform import grafana_folder_permission.my_folder {{org_id}}:{{folder_uid}} # When "org_id" is set on the resource diff --git a/internal/resources/grafana/resource_dashboard_permission.go b/internal/resources/grafana/resource_dashboard_permission.go index c291f6862..5359ff3cf 100644 --- a/internal/resources/grafana/resource_dashboard_permission.go +++ b/internal/resources/grafana/resource_dashboard_permission.go @@ -18,6 +18,7 @@ func ResourceDashboardPermission() *schema.Resource { return &schema.Resource{ Description: ` +Manages the entire set of permissions for a dashboard. Permissions that aren't specified when applying this resource will be removed. * [Official documentation](https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/) * [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/dashboard_permissions/) `, @@ -50,8 +51,11 @@ func ResourceDashboardPermission() *schema.Resource { Description: "UID of the dashboard to apply permissions to.", }, "permissions": { - Type: schema.TypeSet, - Required: true, + Type: schema.TypeSet, + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, Description: "The permission items to add/update. Items that are omitted from the list will be removed.", // Ignore the org ID of the team/SA when hashing. It works with or without it. Set: func(i interface{}) int { @@ -96,12 +100,13 @@ func ResourceDashboardPermission() *schema.Resource { func UpdateDashboardPermissions(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client, orgID := OAPIClientFromNewOrgResource(meta, d) - v, ok := d.GetOk("permissions") - if !ok { - return nil + var list []interface{} + if v, ok := d.GetOk("permissions"); ok { + list = v.(*schema.Set).List() } + permissionList := models.UpdateDashboardACLCommand{} - for _, permission := range v.(*schema.Set).List() { + for _, permission := range list { permission := permission.(map[string]interface{}) permissionItem := models.DashboardACLUpdateItem{} if permission["role"].(string) != "" { diff --git a/internal/resources/grafana/resource_data_source_permission.go b/internal/resources/grafana/resource_data_source_permission.go index 73c89b4d7..05a066124 100644 --- a/internal/resources/grafana/resource_data_source_permission.go +++ b/internal/resources/grafana/resource_data_source_permission.go @@ -16,6 +16,7 @@ func ResourceDatasourcePermission() *schema.Resource { return &schema.Resource{ Description: ` +Manages the entire set of permissions for a datasource. Permissions that aren't specified when applying this resource will be removed. * [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/datasource_permissions/) `, @@ -33,8 +34,11 @@ func ResourceDatasourcePermission() *schema.Resource { Description: "ID of the datasource to apply permissions to.", }, "permissions": { - Type: schema.TypeSet, - Required: true, + Type: schema.TypeSet, + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, Description: "The permission items to add/update. Items that are omitted from the list will be removed.", // Ignore the org ID of the team/SA when hashing. It works with or without it. Set: func(i interface{}) int { @@ -80,9 +84,9 @@ func ResourceDatasourcePermission() *schema.Resource { func UpdateDatasourcePermissions(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client, orgID := ClientFromNewOrgResource(meta, d) - v, ok := d.GetOk("permissions") - if !ok { - return nil + var list []interface{} + if v, ok := d.GetOk("permissions"); ok { + list = v.(*schema.Set).List() } _, datasourceIDStr := SplitOrgResourceID(d.Get("datasource_id").(string)) @@ -94,7 +98,7 @@ func UpdateDatasourcePermissions(ctx context.Context, d *schema.ResourceData, me } var configuredPermissions []gapi.SetResourcePermissionItem - for _, permission := range v.(*schema.Set).List() { + for _, permission := range list { permission := permission.(map[string]interface{}) var permissionItem gapi.SetResourcePermissionItem _, teamIDStr := SplitOrgResourceID(permission["team_id"].(string)) diff --git a/internal/resources/grafana/resource_folder_permission.go b/internal/resources/grafana/resource_folder_permission.go index bbc0b0649..a17c4b123 100644 --- a/internal/resources/grafana/resource_folder_permission.go +++ b/internal/resources/grafana/resource_folder_permission.go @@ -17,6 +17,7 @@ func ResourceFolderPermission() *schema.Resource { return &schema.Resource{ Description: ` +Manages the entire set of permissions for a folder. Permissions that aren't specified when applying this resource will be removed. * [Official documentation](https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/) * [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/folder_permissions/) `, @@ -25,6 +26,9 @@ func ResourceFolderPermission() *schema.Resource { ReadContext: ReadFolderPermissions, UpdateContext: UpdateFolderPermissions, DeleteContext: DeleteFolderPermissions, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, Schema: map[string]*schema.Schema{ "org_id": orgIDAttribute(), @@ -35,8 +39,11 @@ func ResourceFolderPermission() *schema.Resource { Description: "The UID of the folder.", }, "permissions": { - Type: schema.TypeSet, - Required: true, + Type: schema.TypeSet, + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, Description: "The permission items to add/update. Items that are omitted from the list will be removed.", // Ignore the org ID of the team/SA when hashing. It works with or without it. Set: func(i interface{}) int { @@ -81,12 +88,12 @@ func ResourceFolderPermission() *schema.Resource { func UpdateFolderPermissions(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client, orgID := OAPIClientFromNewOrgResource(meta, d) - v, ok := d.GetOk("permissions") - if !ok { - return nil + var list []interface{} + if v, ok := d.GetOk("permissions"); ok { + list = v.(*schema.Set).List() } permissionList := models.UpdateDashboardACLCommand{} - for _, permission := range v.(*schema.Set).List() { + for _, permission := range list { permission := permission.(map[string]interface{}) permissionItem := models.DashboardACLUpdateItem{} if permission["role"].(string) != "" { @@ -145,6 +152,7 @@ func ReadFolderPermissions(ctx context.Context, d *schema.ResourceData, meta int d.SetId(MakeOrgResourceID(orgID, folderUID)) d.Set("org_id", strconv.FormatInt(orgID, 10)) + d.Set("folder_uid", folderUID) d.Set("permissions", permissionItems) return nil diff --git a/internal/resources/grafana/resource_folder_permission_test.go b/internal/resources/grafana/resource_folder_permission_test.go index ecb79e3d1..01cfbc130 100644 --- a/internal/resources/grafana/resource_folder_permission_test.go +++ b/internal/resources/grafana/resource_folder_permission_test.go @@ -27,6 +27,27 @@ func TestAccFolderPermission_basic(t *testing.T) { resource.TestCheckResourceAttr("grafana_folder_permission.testPermission", "permissions.#", "5"), ), }, + { + ResourceName: "grafana_folder_permission.testPermission", + ImportState: true, + ImportStateVerify: true, + }, + // Test remove permissions by not setting any permissions + { + Config: testAccFolderPermissionConfig_NoPermissions, + Check: resource.ComposeAggregateTestCheckFunc( + testAccFolderPermissionsCheckEmpty(&folderUID), + ), + }, + // Reapply permissions + { + Config: testAccFolderPermissionConfig_Basic, + Check: resource.ComposeAggregateTestCheckFunc( + testAccFolderPermissionsCheckExists("grafana_folder_permission.testPermission", &folderUID), + resource.TestCheckResourceAttr("grafana_folder_permission.testPermission", "permissions.#", "5"), + ), + }, + // Test remove permissions by removing the resource { Config: testAccFolderPermissionConfig_Remove, Check: resource.ComposeAggregateTestCheckFunc( @@ -130,6 +151,34 @@ resource "grafana_folder_permission" "testPermission" { } } ` + +const testAccFolderPermissionConfig_NoPermissions = ` +resource "grafana_folder" "testFolder" { + title = "terraform-test-folder-permissions" +} + +resource "grafana_team" "testTeam" { + name = "terraform-test-team-permissions" +} + +resource "grafana_user" "testAdminUser" { + email = "terraform-test-permissions@localhost" + name = "Terraform Test Permissions" + login = "ttp" + password = "zyx987" +} + +resource "grafana_service_account" "test" { + name = "terraform-test-service-account-folder-perms" + role = "Editor" + is_disabled = false +} + +resource "grafana_folder_permission" "testPermission" { + folder_uid = grafana_folder.testFolder.uid +} +` + const testAccFolderPermissionConfig_Remove = ` resource "grafana_folder" "testFolder" { title = "terraform-test-folder-permissions" diff --git a/internal/resources/grafana/resource_role_assignment.go b/internal/resources/grafana/resource_role_assignment.go index 63b794b38..0cd3f22a0 100644 --- a/internal/resources/grafana/resource_role_assignment.go +++ b/internal/resources/grafana/resource_role_assignment.go @@ -14,6 +14,7 @@ import ( func ResourceRoleAssignment() *schema.Resource { return &schema.Resource{ Description: ` +Manages the entire set of assignments for a role. Assignments that aren't specified when applying this resource will be removed. **Note:** This resource is available only with Grafana Enterprise 9.2+. * [Official documentation](https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/) * [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/access_control/) diff --git a/internal/resources/grafana/resource_service_account_permission.go b/internal/resources/grafana/resource_service_account_permission.go index 453d20860..d26017c08 100644 --- a/internal/resources/grafana/resource_service_account_permission.go +++ b/internal/resources/grafana/resource_service_account_permission.go @@ -14,6 +14,8 @@ import ( func ResourceServiceAccountPermission() *schema.Resource { return &schema.Resource{ Description: ` +Manages the entire set of permissions for a service account. Permissions that aren't specified when applying this resource will be removed. + **Note:** This resource is available from Grafana 9.2.4 onwards. * [Official documentation](https://grafana.com/docs/grafana/latest/administration/service-accounts/#manage-users-and-teams-permissions-for-a-service-account-in-grafana)`, @@ -34,8 +36,11 @@ func ResourceServiceAccountPermission() *schema.Resource { Description: "The id of the service account.", }, "permissions": { - Type: schema.TypeSet, - Required: true, + Type: schema.TypeSet, + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, Description: "The permission items to add/update. Items that are omitted from the list will be removed.", // Ignore the org ID of the team when hashing. It works with or without it. Set: func(i interface{}) int {