diff --git a/docs/resources/dashboard_permission.md b/docs/resources/dashboard_permission.md index cd27ad838..1745ce074 100644 --- a/docs/resources/dashboard_permission.md +++ b/docs/resources/dashboard_permission.md @@ -71,7 +71,7 @@ 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` (Number) ID of the user 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 diff --git a/docs/resources/data_source_permission.md b/docs/resources/data_source_permission.md index 8a3406df1..0cb247860 100644 --- a/docs/resources/data_source_permission.md +++ b/docs/resources/data_source_permission.md @@ -39,6 +39,11 @@ resource "grafana_user" "user" { password = "hunter2" } +resource "grafana_service_account" "sa" { + name = "test-ds-permissions" + role = "Viewer" +} + resource "grafana_data_source_permission" "fooPermissions" { datasource_id = grafana_data_source.foo.id permissions { @@ -53,6 +58,10 @@ resource "grafana_data_source_permission" "fooPermissions" { built_in_role = "Viewer" permission = "Query" } + permissions { + user_id = grafana_service_account.sa.id + permission = "Query" + } } ``` @@ -83,4 +92,4 @@ Optional: - `built_in_role` (String) Name of the basic role to manage permissions for. Options: `Viewer`, `Editor` or `Admin`. Can only be set from Grafana v9.2.3+. Defaults to ``. - `team_id` (String) ID of the team to manage permissions for. Defaults to `0`. -- `user_id` (Number) ID of the user to manage permissions for. Defaults to `0`. +- `user_id` (String) ID of the user or service account to manage permissions for. Defaults to `0`. diff --git a/docs/resources/folder_permission.md b/docs/resources/folder_permission.md index d813791e9..603da58ef 100644 --- a/docs/resources/folder_permission.md +++ b/docs/resources/folder_permission.md @@ -70,4 +70,4 @@ 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` (Number) ID of the user to manage permissions for. Defaults to `0`. +- `user_id` (String) ID of the user or service account to manage permissions for. Defaults to `0`. diff --git a/docs/resources/service_account_permission.md b/docs/resources/service_account_permission.md index 6ab35e7c6..1c7c28360 100644 --- a/docs/resources/service_account_permission.md +++ b/docs/resources/service_account_permission.md @@ -72,4 +72,4 @@ Required: Optional: - `team_id` (String) ID of the team to manage permissions for. Specify either this or `user_id`. Defaults to `0`. -- `user_id` (Number) ID of the user to manage permissions for. Specify either this or `team_id`. Defaults to `0`. +- `user_id` (String) ID of the user or service account to manage permissions for. Specify either this or `team_id`. Defaults to `0`. diff --git a/examples/resources/grafana_data_source_permission/resource.tf b/examples/resources/grafana_data_source_permission/resource.tf index f7e77c4c5..963988e29 100644 --- a/examples/resources/grafana_data_source_permission/resource.tf +++ b/examples/resources/grafana_data_source_permission/resource.tf @@ -24,6 +24,11 @@ resource "grafana_user" "user" { password = "hunter2" } +resource "grafana_service_account" "sa" { + name = "test-ds-permissions" + role = "Viewer" +} + resource "grafana_data_source_permission" "fooPermissions" { datasource_id = grafana_data_source.foo.id permissions { @@ -38,4 +43,8 @@ resource "grafana_data_source_permission" "fooPermissions" { built_in_role = "Viewer" permission = "Query" } + permissions { + user_id = grafana_service_account.sa.id + permission = "Query" + } } diff --git a/internal/resources/grafana/resource_dashboard_permission.go b/internal/resources/grafana/resource_dashboard_permission.go index 746c8f351..00cc5d4cd 100644 --- a/internal/resources/grafana/resource_dashboard_permission.go +++ b/internal/resources/grafana/resource_dashboard_permission.go @@ -51,11 +51,12 @@ func ResourceDashboardPermission() *schema.Resource { Type: schema.TypeSet, Required: true, 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. + // Ignore the org ID of the team/SA when hashing. It works with or without it. Set: func(i interface{}) int { m := i.(map[string]interface{}) _, teamID := SplitOrgResourceID(m["team_id"].(string)) - return schema.HashString(m["role"].(string) + teamID + strconv.Itoa(m["user_id"].(int)) + m["permission"].(string)) + _, userID := SplitOrgResourceID((m["user_id"].(string))) + return schema.HashString(m["role"].(string) + teamID + userID + m["permission"].(string)) }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -72,10 +73,10 @@ func ResourceDashboardPermission() *schema.Resource { Description: "ID of the team to manage permissions for.", }, "user_id": { - Type: schema.TypeInt, + Type: schema.TypeString, Optional: true, - Default: 0, - Description: "ID of the user to manage permissions for.", + Default: "0", + Description: "ID of the user or service account to manage permissions for.", }, "permission": { Type: schema.TypeString, @@ -109,8 +110,10 @@ func UpdateDashboardPermissions(ctx context.Context, d *schema.ResourceData, met if teamID > 0 { permissionItem.TeamID = teamID } - if permission["user_id"].(int) != -1 { - permissionItem.UserID = int64(permission["user_id"].(int)) + _, userIDStr := SplitOrgResourceID(permission["user_id"].(string)) + userID, _ := strconv.ParseInt(userIDStr, 10, 64) + if userID > 0 { + permissionItem.UserID = userID } permissionItem.Permission = mapPermissionStringToInt64(permission["permission"].(string)) permissionList.Items = append(permissionList.Items, &permissionItem) @@ -161,7 +164,7 @@ func ReadDashboardPermissions(ctx context.Context, d *schema.ResourceData, meta permissionItem := make(map[string]interface{}) permissionItem["role"] = permission.Role permissionItem["team_id"] = strconv.FormatInt(permission.TeamID, 10) - permissionItem["user_id"] = permission.UserID + permissionItem["user_id"] = strconv.FormatInt(permission.UserID, 10) permissionItem["permission"] = mapPermissionInt64ToString(permission.Permission) permissionItems[count] = permissionItem diff --git a/internal/resources/grafana/resource_dashboard_permission_test.go b/internal/resources/grafana/resource_dashboard_permission_test.go index e43ad70f7..227100c3f 100644 --- a/internal/resources/grafana/resource_dashboard_permission_test.go +++ b/internal/resources/grafana/resource_dashboard_permission_test.go @@ -26,7 +26,7 @@ func TestAccDashboardPermission_basic(t *testing.T) { Config: testAccDashboardPermissionConfig_Basic, Check: resource.ComposeAggregateTestCheckFunc( testAccDashboardPermissionsCheckExistsUID("grafana_dashboard_permission.testPermission", &dashboardUID), - resource.TestCheckResourceAttr("grafana_dashboard_permission.testPermission", "permissions.#", "4"), + resource.TestCheckResourceAttr("grafana_dashboard_permission.testPermission", "permissions.#", "5"), ), }, { @@ -159,6 +159,11 @@ resource "grafana_user" "testAdminUser" { password = "zyx987" } +resource "grafana_service_account" "test" { + name = "terraform-test-service-account-dashboard-perms" + role = "Editor" +} + resource "grafana_dashboard_permission" "testPermission" { dashboard_uid = grafana_dashboard.testDashboard.uid permissions { @@ -177,6 +182,10 @@ resource "grafana_dashboard_permission" "testPermission" { user_id = grafana_user.testAdminUser.id permission = "Admin" } + permissions { + user_id = grafana_service_account.test.id + permission = "Admin" + } } ` const testAccDashboardPermissionConfig_Remove = ` diff --git a/internal/resources/grafana/resource_data_source_permission.go b/internal/resources/grafana/resource_data_source_permission.go index 7531a9c03..40646a33c 100644 --- a/internal/resources/grafana/resource_data_source_permission.go +++ b/internal/resources/grafana/resource_data_source_permission.go @@ -37,11 +37,12 @@ func ResourceDatasourcePermission() *schema.Resource { Type: schema.TypeSet, Required: true, 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. + // Ignore the org ID of the team/SA when hashing. It works with or without it. Set: func(i interface{}) int { m := i.(map[string]interface{}) _, teamID := SplitOrgResourceID(m["team_id"].(string)) - return schema.HashString(m["built_in_role"].(string) + teamID + strconv.Itoa(m["user_id"].(int)) + m["permission"].(string)) + _, userID := SplitOrgResourceID((m["user_id"].(string))) + return schema.HashString(m["built_in_role"].(string) + teamID + userID + m["permission"].(string)) }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -52,10 +53,10 @@ func ResourceDatasourcePermission() *schema.Resource { Description: "ID of the team to manage permissions for.", }, "user_id": { - Type: schema.TypeInt, + Type: schema.TypeString, Optional: true, - Default: 0, - Description: "ID of the user to manage permissions for.", + Default: "0", + Description: "ID of the user or service account to manage permissions for.", }, "built_in_role": { Type: schema.TypeString, @@ -97,8 +98,10 @@ func UpdateDatasourcePermissions(ctx context.Context, d *schema.ResourceData, me if teamID > 0 { permissionItem.TeamID = teamID } - if permission["user_id"].(int) != -1 { - permissionItem.UserID = int64(permission["user_id"].(int)) + _, userIDStr := SplitOrgResourceID(permission["user_id"].(string)) + userID, _ := strconv.ParseInt(userIDStr, 10, 64) + if userID > 0 { + permissionItem.UserID = userID } if permission["built_in_role"].(string) != "" { permissionItem.BuiltInRole = permission["built_in_role"].(string) @@ -137,7 +140,7 @@ func ReadDatasourcePermissions(ctx context.Context, d *schema.ResourceData, meta permissionItem := make(map[string]interface{}) permissionItem["built_in_role"] = permission.BuiltInRole permissionItem["team_id"] = strconv.FormatInt(permission.TeamID, 10) - permissionItem["user_id"] = permission.UserID + permissionItem["user_id"] = strconv.FormatInt(permission.UserID, 10) if permissionItem["permission"], err = mapDatasourcePermissionTypeToString(permission.Permission); err != nil { return diag.FromErr(err) diff --git a/internal/resources/grafana/resource_data_source_permission_test.go b/internal/resources/grafana/resource_data_source_permission_test.go index a54396812..793a2ed55 100644 --- a/internal/resources/grafana/resource_data_source_permission_test.go +++ b/internal/resources/grafana/resource_data_source_permission_test.go @@ -24,7 +24,7 @@ func TestAccDatasourcePermission_basic(t *testing.T) { Config: testutils.TestAccExample(t, "resources/grafana_data_source_permission/resource.tf"), Check: resource.ComposeAggregateTestCheckFunc( testAccDatasourcePermissionsCheckExists("grafana_data_source_permission.fooPermissions", &datasourceID), - resource.TestCheckResourceAttr("grafana_data_source_permission.fooPermissions", "permissions.#", "3"), + resource.TestCheckResourceAttr("grafana_data_source_permission.fooPermissions", "permissions.#", "4"), ), }, { @@ -35,7 +35,6 @@ func TestAccDatasourcePermission_basic(t *testing.T) { }) } -//nolint:unused func testAccDatasourcePermissionsCheckExists(rn string, datasourceID *int64) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[rn] @@ -66,7 +65,6 @@ func testAccDatasourcePermissionsCheckExists(rn string, datasourceID *int64) res } } -//nolint:unused func testAccDatasourcePermissionCheckDestroy(datasourceID *int64) resource.TestCheckFunc { return func(s *terraform.State) error { client := testutils.Provider.Meta().(*common.Client).GrafanaAPI diff --git a/internal/resources/grafana/resource_folder_permission.go b/internal/resources/grafana/resource_folder_permission.go index 307a6d99b..4a4238a0a 100644 --- a/internal/resources/grafana/resource_folder_permission.go +++ b/internal/resources/grafana/resource_folder_permission.go @@ -37,11 +37,12 @@ func ResourceFolderPermission() *schema.Resource { Type: schema.TypeSet, Required: true, 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. + // Ignore the org ID of the team/SA when hashing. It works with or without it. Set: func(i interface{}) int { m := i.(map[string]interface{}) _, teamID := SplitOrgResourceID(m["team_id"].(string)) - return schema.HashString(m["role"].(string) + teamID + strconv.Itoa(m["user_id"].(int)) + m["permission"].(string)) + _, userID := SplitOrgResourceID(m["user_id"].(string)) + return schema.HashString(m["role"].(string) + teamID + userID + m["permission"].(string)) }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -58,10 +59,10 @@ func ResourceFolderPermission() *schema.Resource { Description: "ID of the team to manage permissions for.", }, "user_id": { - Type: schema.TypeInt, + Type: schema.TypeString, Optional: true, - Default: 0, - Description: "ID of the user to manage permissions for.", + Default: "0", + Description: "ID of the user or service account to manage permissions for.", }, "permission": { Type: schema.TypeString, @@ -95,8 +96,10 @@ func UpdateFolderPermissions(ctx context.Context, d *schema.ResourceData, meta i if teamID > 0 { permissionItem.TeamID = teamID } - if permission["user_id"].(int) != -1 { - permissionItem.UserID = int64(permission["user_id"].(int)) + _, userIDStr := SplitOrgResourceID(permission["user_id"].(string)) + userID, _ := strconv.ParseInt(userIDStr, 10, 64) + if userID > 0 { + permissionItem.UserID = userID } permissionItem.Permission = mapPermissionStringToInt64(permission["permission"].(string)) permissionList.Items = append(permissionList.Items, &permissionItem) @@ -129,7 +132,7 @@ func ReadFolderPermissions(ctx context.Context, d *schema.ResourceData, meta int permissionItem := make(map[string]interface{}) permissionItem["role"] = permission.Role permissionItem["team_id"] = strconv.FormatInt(permission.TeamID, 10) - permissionItem["user_id"] = permission.UserID + permissionItem["user_id"] = strconv.FormatInt(permission.UserID, 10) permissionItem["permission"] = mapPermissionInt64ToString(permission.Permission) permissionItems[count] = permissionItem diff --git a/internal/resources/grafana/resource_folder_permission_test.go b/internal/resources/grafana/resource_folder_permission_test.go index bc278150a..5f6cc17d6 100644 --- a/internal/resources/grafana/resource_folder_permission_test.go +++ b/internal/resources/grafana/resource_folder_permission_test.go @@ -13,6 +13,7 @@ import ( func TestAccFolderPermission_basic(t *testing.T) { testutils.CheckOSSTestsEnabled(t) + testutils.CheckOSSTestsSemver(t, ">=9.0.0") // Folder permissions only work for service accounts in Grafana 9+, so we're just not testing versions before 9. folderUID := "uninitialized" @@ -24,7 +25,7 @@ func TestAccFolderPermission_basic(t *testing.T) { Config: testAccFolderPermissionConfig_Basic, Check: resource.ComposeAggregateTestCheckFunc( testAccFolderPermissionsCheckExists("grafana_folder_permission.testPermission", &folderUID), - resource.TestCheckResourceAttr("grafana_folder_permission.testPermission", "permissions.#", "4"), + resource.TestCheckResourceAttr("grafana_folder_permission.testPermission", "permissions.#", "5"), ), }, { @@ -100,6 +101,12 @@ resource "grafana_user" "testAdminUser" { 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 permissions { @@ -118,6 +125,10 @@ resource "grafana_folder_permission" "testPermission" { user_id = grafana_user.testAdminUser.id permission = "Admin" } + permissions { + user_id = grafana_service_account.test.id + permission = "Admin" + } } ` const testAccFolderPermissionConfig_Remove = ` diff --git a/internal/resources/grafana/resource_service_account_permission.go b/internal/resources/grafana/resource_service_account_permission.go index 0db77e761..922320577 100644 --- a/internal/resources/grafana/resource_service_account_permission.go +++ b/internal/resources/grafana/resource_service_account_permission.go @@ -41,7 +41,8 @@ func ResourceServiceAccountPermission() *schema.Resource { Set: func(i interface{}) int { m := i.(map[string]interface{}) _, teamID := SplitOrgResourceID(m["team_id"].(string)) - return schema.HashString(teamID + strconv.Itoa(m["user_id"].(int)) + m["permission"].(string)) + _, userID := SplitOrgResourceID((m["user_id"].(string))) + return schema.HashString(teamID + userID + m["permission"].(string)) }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -52,10 +53,10 @@ func ResourceServiceAccountPermission() *schema.Resource { Description: "ID of the team to manage permissions for. Specify either this or `user_id`.", }, "user_id": { - Type: schema.TypeInt, + Type: schema.TypeString, Optional: true, - Default: 0, - Description: "ID of the user to manage permissions for. Specify either this or `team_id`.", + Default: "0", + Description: "ID of the user or service account to manage permissions for. Specify either this or `team_id`.", }, "permission": { Type: schema.TypeString, @@ -91,7 +92,7 @@ func ReadServiceAccountPermissions(ctx context.Context, d *schema.ResourceData, } permMap := map[string]interface{}{ "team_id": strconv.FormatInt(p.TeamID, 10), - "user_id": p.UserID, + "user_id": strconv.FormatInt(p.UserID, 10), "permission": p.Permission, } saPerms = append(saPerms, permMap) @@ -117,7 +118,8 @@ func UpdateServiceAccountPermissions(ctx context.Context, d *schema.ResourceData perm := p.(map[string]interface{}) _, teamIDStr := SplitOrgResourceID(perm["team_id"].(string)) teamID, _ := strconv.ParseInt(teamIDStr, 10, 64) - userID := int64(perm["user_id"].(int)) + _, userIDStr := SplitOrgResourceID(perm["user_id"].(string)) + userID, _ := strconv.ParseInt(userIDStr, 10, 64) if teamID > 0 { oldTeamPerms[teamID] = perm["permission"].(string) } @@ -134,7 +136,8 @@ func UpdateServiceAccountPermissions(ctx context.Context, d *schema.ResourceData permissionItem := gapi.ServiceAccountPermissionItem{} _, teamIDStr := SplitOrgResourceID(permission["team_id"].(string)) teamID, _ := strconv.ParseInt(teamIDStr, 10, 64) - userID := int64(permission["user_id"].(int)) + _, userIDStr := SplitOrgResourceID(permission["user_id"].(string)) + userID, _ := strconv.ParseInt(userIDStr, 10, 64) if teamID > 0 { perm, has := oldTeamPerms[teamID] if has { @@ -197,7 +200,8 @@ func DeleteServiceAccountPermissions(ctx context.Context, d *schema.ResourceData perm := p.(map[string]interface{}) _, teamIDStr := SplitOrgResourceID(perm["team_id"].(string)) teamID, _ := strconv.ParseInt(teamIDStr, 10, 64) - userID := int64(perm["user_id"].(int)) + _, userIDStr := SplitOrgResourceID(perm["user_id"].(string)) + userID, _ := strconv.ParseInt(userIDStr, 10, 64) permissionItem := gapi.ServiceAccountPermissionItem{} if teamID > 0 {