From 19650d7e00ff74a3cbea70634a77ee6155fe9e54 Mon Sep 17 00:00:00 2001 From: Markus Schwer Date: Fri, 27 Mar 2026 12:32:19 +0100 Subject: [PATCH] feat(authorization): add custom role resources and data sources for folder and organization --- .../authorization_folder_custom_role.md | 28 ++ .../authorization_organization_custom_role.md | 28 ++ .../authorization_folder_custom_role.md | 51 +++ .../authorization_organization_custom_role.md | 45 +++ .../resource.tf | 21 ++ .../resource.tf | 15 + .../authorization/authorization_acc_test.go | 328 +++++++++++++++--- .../authorization/customrole/resource.go | 2 + .../authorization/customrole/resource_test.go | 227 ++++++------ .../testdata/resource-folder-custom-role.tf | 20 ++ .../resource-organization-custom-role.tf | 12 + ...ole.tf => resource-project-custom-role.tf} | 2 +- 12 files changed, 618 insertions(+), 161 deletions(-) create mode 100644 docs/data-sources/authorization_folder_custom_role.md create mode 100644 docs/data-sources/authorization_organization_custom_role.md create mode 100644 docs/resources/authorization_folder_custom_role.md create mode 100644 docs/resources/authorization_organization_custom_role.md create mode 100644 examples/resources/stackit_authorization_folder_custom_role/resource.tf create mode 100644 examples/resources/stackit_authorization_organization_custom_role/resource.tf create mode 100644 stackit/internal/services/authorization/testdata/resource-folder-custom-role.tf create mode 100644 stackit/internal/services/authorization/testdata/resource-organization-custom-role.tf rename stackit/internal/services/authorization/testdata/{resource-custom-role.tf => resource-project-custom-role.tf} (76%) diff --git a/docs/data-sources/authorization_folder_custom_role.md b/docs/data-sources/authorization_folder_custom_role.md new file mode 100644 index 000000000..78a485fb5 --- /dev/null +++ b/docs/data-sources/authorization_folder_custom_role.md @@ -0,0 +1,28 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_authorization_folder_custom_role Data Source - stackit" +subcategory: "" +description: |- + Custom Role resource schema. +--- + +# stackit_authorization_folder_custom_role (Data Source) + +Custom Role resource schema. + + + + +## Schema + +### Required + +- `resource_id` (String) Resource to add the custom role to. +- `role_id` (String) The ID of the role. + +### Read-Only + +- `description` (String) A human readable description of the role. +- `id` (String) Terraform's internal resource identifier. It is structured as "[resource_id],[role_id]". +- `name` (String) Name of the role +- `permissions` (List of String) Permissions for the role diff --git a/docs/data-sources/authorization_organization_custom_role.md b/docs/data-sources/authorization_organization_custom_role.md new file mode 100644 index 000000000..4ade07402 --- /dev/null +++ b/docs/data-sources/authorization_organization_custom_role.md @@ -0,0 +1,28 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_authorization_organization_custom_role Data Source - stackit" +subcategory: "" +description: |- + Custom Role resource schema. +--- + +# stackit_authorization_organization_custom_role (Data Source) + +Custom Role resource schema. + + + + +## Schema + +### Required + +- `resource_id` (String) Resource to add the custom role to. +- `role_id` (String) The ID of the role. + +### Read-Only + +- `description` (String) A human readable description of the role. +- `id` (String) Terraform's internal resource identifier. It is structured as "[resource_id],[role_id]". +- `name` (String) Name of the role +- `permissions` (List of String) Permissions for the role diff --git a/docs/resources/authorization_folder_custom_role.md b/docs/resources/authorization_folder_custom_role.md new file mode 100644 index 000000000..c11f4da5b --- /dev/null +++ b/docs/resources/authorization_folder_custom_role.md @@ -0,0 +1,51 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_authorization_folder_custom_role Resource - stackit" +subcategory: "" +description: |- + Custom Role resource schema. +--- + +# stackit_authorization_folder_custom_role (Resource) + +Custom Role resource schema. + +## Example Usage + +```terraform +resource "stackit_resourcemanager_folder" "example" { + name = "example_folder" + owner_email = "foo.bar@stackit.cloud" + parent_container_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} + +resource "stackit_authorization_folder_custom_role" "example" { + resource_id = stackit_resourcemanager_folder.example.folder_id + name = "my.custom.role" + description = "Some description" + permissions = [ + "iam.subject.get" + ] +} + +# Only use the import statement, if you want to import an existing custom role +import { + to = stackit_authorization_folder_custom_role.import-example + id = "${var.folder_id},${var.custom_role_id}" +} +``` + + +## Schema + +### Required + +- `description` (String) A human readable description of the role. +- `name` (String) Name of the role +- `permissions` (List of String) Permissions for the role +- `resource_id` (String) Resource to add the custom role to. + +### Read-Only + +- `id` (String) Terraform's internal resource identifier. It is structured as "[resource_id],[role_id]". +- `role_id` (String) The ID of the role. diff --git a/docs/resources/authorization_organization_custom_role.md b/docs/resources/authorization_organization_custom_role.md new file mode 100644 index 000000000..c6ea057f7 --- /dev/null +++ b/docs/resources/authorization_organization_custom_role.md @@ -0,0 +1,45 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_authorization_organization_custom_role Resource - stackit" +subcategory: "" +description: |- + Custom Role resource schema. +--- + +# stackit_authorization_organization_custom_role (Resource) + +Custom Role resource schema. + +## Example Usage + +```terraform +resource "stackit_authorization_organization_custom_role" "example" { + resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "my.custom.role" + description = "Some description" + permissions = [ + "iam.subject.get" + ] +} + +# Only use the import statement, if you want to import an existing custom role +import { + to = stackit_authorization_organization_custom_role.import-example + id = "${var.organization_id},${var.custom_role_id}" +} +``` + + +## Schema + +### Required + +- `description` (String) A human readable description of the role. +- `name` (String) Name of the role +- `permissions` (List of String) Permissions for the role +- `resource_id` (String) Resource to add the custom role to. + +### Read-Only + +- `id` (String) Terraform's internal resource identifier. It is structured as "[resource_id],[role_id]". +- `role_id` (String) The ID of the role. diff --git a/examples/resources/stackit_authorization_folder_custom_role/resource.tf b/examples/resources/stackit_authorization_folder_custom_role/resource.tf new file mode 100644 index 000000000..de88df59d --- /dev/null +++ b/examples/resources/stackit_authorization_folder_custom_role/resource.tf @@ -0,0 +1,21 @@ +resource "stackit_resourcemanager_folder" "example" { + name = "example_folder" + owner_email = "foo.bar@stackit.cloud" + parent_container_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} + +resource "stackit_authorization_folder_custom_role" "example" { + resource_id = stackit_resourcemanager_folder.example.folder_id + name = "my.custom.role" + description = "Some description" + permissions = [ + "iam.subject.get" + ] +} + +# Only use the import statement, if you want to import an existing custom role +import { + to = stackit_authorization_folder_custom_role.import-example + id = "${var.folder_id},${var.custom_role_id}" +} + diff --git a/examples/resources/stackit_authorization_organization_custom_role/resource.tf b/examples/resources/stackit_authorization_organization_custom_role/resource.tf new file mode 100644 index 000000000..4e1fd28b9 --- /dev/null +++ b/examples/resources/stackit_authorization_organization_custom_role/resource.tf @@ -0,0 +1,15 @@ +resource "stackit_authorization_organization_custom_role" "example" { + resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "my.custom.role" + description = "Some description" + permissions = [ + "iam.subject.get" + ] +} + +# Only use the import statement, if you want to import an existing custom role +import { + to = stackit_authorization_organization_custom_role.import-example + id = "${var.organization_id},${var.custom_role_id}" +} + diff --git a/stackit/internal/services/authorization/authorization_acc_test.go b/stackit/internal/services/authorization/authorization_acc_test.go index 274d7f0d6..797d31ecc 100644 --- a/stackit/internal/services/authorization/authorization_acc_test.go +++ b/stackit/internal/services/authorization/authorization_acc_test.go @@ -43,8 +43,14 @@ var ( //go:embed testdata/resource-org-role-assignment-duplicate.tf resourceOrgRoleAssignmentDuplicate string - //go:embed testdata/resource-custom-role.tf - resourceCustomRole string + //go:embed testdata/resource-project-custom-role.tf + resourceProjectCustomRole string + + //go:embed testdata/resource-folder-custom-role.tf + resourceFolderCustomRole string + + //go:embed testdata/resource-organization-custom-role.tf + resourceOrganizationCustomRole string //go:embed testdata/resource-service-account-role-assignment.tf resourceServiceAccountRoleAssignment string @@ -54,8 +60,9 @@ var ( ) var ( - testProjectName = fmt.Sprintf("proj-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)) - testFolderName = fmt.Sprintf("folder-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)) + testProjectName = fmt.Sprintf("proj-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)) + testFolderName = fmt.Sprintf("folder-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)) + testCustomRoleFolderName = fmt.Sprintf("folder-custom-role-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)) ) var testConfigVarsProjectRoleAssignment = config.Variables{ @@ -80,22 +87,50 @@ var testConfigVarsOrgRoleAssignment = config.Variables{ "subject": config.StringVariable(testutil.TestProjectServiceAccountEmail), } -var testConfigVarsCustomRole = config.Variables{ - "project_id": config.StringVariable(testutil.ProjectId), - "test_service_account": config.StringVariable(testutil.TestProjectServiceAccountEmail), - "organization_id": config.StringVariable(testutil.OrganizationId), - "role_name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlpha))), - "role_description": config.StringVariable("Some description"), - "role_permissions_0": config.StringVariable("iam.role.list"), +var testConfigVarsProjectCustomRole = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "role_name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlpha))), + "role_description": config.StringVariable("Some description"), + "role_permissions_0": config.StringVariable("iam.role.list"), +} + +var testConfigVarsProjectCustomRoleUpdated = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "role_name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlpha))), + "role_description": config.StringVariable("Updated description"), + "role_permissions_0": config.StringVariable("iam.role.edit"), +} + +var testConfigVarsFolderCustomRole = config.Variables{ + "folder_name": config.StringVariable(testCustomRoleFolderName), + "owner_email": config.StringVariable(testutil.TestProjectServiceAccountEmail), + "parent_container_id": config.StringVariable(testutil.OrganizationId), + "role_name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlpha))), + "role_description": config.StringVariable("Some description"), + "role_permissions_0": config.StringVariable("iam.role.list"), +} + +var testConfigVarsFolderCustomRoleUpdated = config.Variables{ + "folder_name": config.StringVariable(testCustomRoleFolderName), + "owner_email": config.StringVariable(testutil.TestProjectServiceAccountEmail), + "parent_container_id": config.StringVariable(testutil.OrganizationId), + "role_name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlpha))), + "role_description": config.StringVariable("Updated description"), + "role_permissions_0": config.StringVariable("iam.role.edit"), +} + +var testConfigVarsOrganizationCustomRole = config.Variables{ + "organization_id": config.StringVariable(testutil.OrganizationId), + "role_name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlpha))), + "role_description": config.StringVariable("Some description"), + "role_permissions_0": config.StringVariable("iam.role.list"), } -var testConfigVarsCustomRoleUpdated = config.Variables{ - "project_id": config.StringVariable(testutil.ProjectId), - "test_service_account": config.StringVariable(testutil.TestProjectServiceAccountEmail), - "organization_id": config.StringVariable(testutil.OrganizationId), - "role_name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlpha))), - "role_description": config.StringVariable("Updated description"), - "role_permissions_0": config.StringVariable("iam.role.edit"), +var testConfigVarsOrganizationCustomRoleUpdated = config.Variables{ + "organization_id": config.StringVariable(testutil.OrganizationId), + "role_name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlpha))), + "role_description": config.StringVariable("Updated description"), + "role_permissions_0": config.StringVariable("iam.role.edit"), } var testConfigVarsServiceAccountRoleAssignment = config.Variables{ @@ -435,67 +470,67 @@ func TestAccServiceAccountRoleAssignmentResource(t *testing.T) { } func TestAccProjectCustomRoleResource(t *testing.T) { - t.Log("Testing org role assignment resource") + t.Log("Testing project custom role resource") resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - ConfigVariables: testConfigVarsCustomRole, - Config: testutil.AuthorizationProviderConfig() + resourceCustomRole, + ConfigVariables: testConfigVarsProjectCustomRole, + Config: testutil.AuthorizationProviderConfig() + resourceProjectCustomRole, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom_role", "resource_id", testutil.ConvertConfigVariable(testConfigVarsCustomRole["project_id"])), - resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom_role", "name", testutil.ConvertConfigVariable(testConfigVarsCustomRole["role_name"])), - resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom_role", "description", testutil.ConvertConfigVariable(testConfigVarsCustomRole["role_description"])), - resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom_role", "permissions.#", "1"), - resource.TestCheckTypeSetElemAttr("stackit_authorization_project_custom_role.custom_role", "permissions.*", testutil.ConvertConfigVariable(testConfigVarsCustomRole["role_permissions_0"])), - resource.TestCheckResourceAttrSet("stackit_authorization_project_custom_role.custom_role", "role_id"), + resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.project_custom_role", "resource_id", testutil.ConvertConfigVariable(testConfigVarsProjectCustomRole["project_id"])), + resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.project_custom_role", "name", testutil.ConvertConfigVariable(testConfigVarsProjectCustomRole["role_name"])), + resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.project_custom_role", "description", testutil.ConvertConfigVariable(testConfigVarsProjectCustomRole["role_description"])), + resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.project_custom_role", "permissions.#", "1"), + resource.TestCheckTypeSetElemAttr("stackit_authorization_project_custom_role.project_custom_role", "permissions.*", testutil.ConvertConfigVariable(testConfigVarsProjectCustomRole["role_permissions_0"])), + resource.TestCheckResourceAttrSet("stackit_authorization_project_custom_role.project_custom_role", "role_id"), ), }, // Data source { - ConfigVariables: testConfigVarsCustomRole, + ConfigVariables: testConfigVarsProjectCustomRole, Config: fmt.Sprintf(` %s - data "stackit_authorization_project_custom_role" "custom_role" { - resource_id = stackit_authorization_project_custom_role.custom_role.resource_id - role_id = stackit_authorization_project_custom_role.custom_role.role_id + data "stackit_authorization_project_custom_role" "project_custom_role" { + resource_id = stackit_authorization_project_custom_role.project_custom_role.resource_id + role_id = stackit_authorization_project_custom_role.project_custom_role.role_id } `, - testutil.AuthorizationProviderConfig()+resourceCustomRole, + testutil.AuthorizationProviderConfig()+resourceProjectCustomRole, ), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("data.stackit_authorization_project_custom_role.custom_role", "resource_id", testutil.ConvertConfigVariable(testConfigVarsCustomRole["project_id"])), + resource.TestCheckResourceAttr("data.stackit_authorization_project_custom_role.project_custom_role", "resource_id", testutil.ConvertConfigVariable(testConfigVarsProjectCustomRole["project_id"])), resource.TestCheckResourceAttrPair( - "stackit_authorization_project_custom_role.custom_role", "resource_id", - "data.stackit_authorization_project_custom_role.custom_role", "resource_id", + "stackit_authorization_project_custom_role.project_custom_role", "resource_id", + "data.stackit_authorization_project_custom_role.project_custom_role", "resource_id", ), resource.TestCheckResourceAttrPair( - "stackit_authorization_project_custom_role.custom_role", "role_id", - "data.stackit_authorization_project_custom_role.custom_role", "role_id", + "stackit_authorization_project_custom_role.project_custom_role", "role_id", + "data.stackit_authorization_project_custom_role.project_custom_role", "role_id", ), resource.TestCheckResourceAttrPair( - "stackit_authorization_project_custom_role.custom_role", "name", - "data.stackit_authorization_project_custom_role.custom_role", "name", + "stackit_authorization_project_custom_role.project_custom_role", "name", + "data.stackit_authorization_project_custom_role.project_custom_role", "name", ), resource.TestCheckResourceAttrPair( - "stackit_authorization_project_custom_role.custom_role", "description", - "data.stackit_authorization_project_custom_role.custom_role", "description", + "stackit_authorization_project_custom_role.project_custom_role", "description", + "data.stackit_authorization_project_custom_role.project_custom_role", "description", ), resource.TestCheckResourceAttrPair( - "stackit_authorization_project_custom_role.custom_role", "permissions", - "data.stackit_authorization_project_custom_role.custom_role", "permissions", + "stackit_authorization_project_custom_role.project_custom_role", "permissions", + "data.stackit_authorization_project_custom_role.project_custom_role", "permissions", ), ), }, // Import { - ConfigVariables: testConfigVarsCustomRole, - ResourceName: "stackit_authorization_project_custom_role.custom_role", + ConfigVariables: testConfigVarsProjectCustomRole, + ResourceName: "stackit_authorization_project_custom_role.project_custom_role", ImportStateIdFunc: func(s *terraform.State) (string, error) { - r, ok := s.RootModule().Resources["stackit_authorization_project_custom_role.custom_role"] + r, ok := s.RootModule().Resources["stackit_authorization_project_custom_role.project_custom_role"] if !ok { - return "", fmt.Errorf("couldn't find resource stackit_authorization_project_custom_role.custom_role") + return "", fmt.Errorf("couldn't find resource stackit_authorization_project_custom_role.project_custom_role") } roleId, ok := r.Primary.Attributes["role_id"] if !ok { @@ -509,15 +544,198 @@ func TestAccProjectCustomRoleResource(t *testing.T) { }, // Update { - ConfigVariables: testConfigVarsCustomRoleUpdated, - Config: testutil.AuthorizationProviderConfig() + resourceCustomRole, + ConfigVariables: testConfigVarsProjectCustomRoleUpdated, + Config: testutil.AuthorizationProviderConfig() + resourceProjectCustomRole, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.project_custom_role", "resource_id", testutil.ConvertConfigVariable(testConfigVarsProjectCustomRoleUpdated["project_id"])), + resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.project_custom_role", "name", testutil.ConvertConfigVariable(testConfigVarsProjectCustomRoleUpdated["role_name"])), + resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.project_custom_role", "description", testutil.ConvertConfigVariable(testConfigVarsProjectCustomRoleUpdated["role_description"])), + resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.project_custom_role", "permissions.#", "1"), + resource.TestCheckTypeSetElemAttr("stackit_authorization_project_custom_role.project_custom_role", "permissions.*", testutil.ConvertConfigVariable(testConfigVarsProjectCustomRoleUpdated["role_permissions_0"])), + resource.TestCheckResourceAttrSet("stackit_authorization_project_custom_role.project_custom_role", "role_id"), + ), + }, + // Deletion is done by the framework implicitly + }, + }) +} + +func TestAccFolderCustomRoleResource(t *testing.T) { + t.Log("Testing folder custom role resource") + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + ConfigVariables: testConfigVarsFolderCustomRole, + Config: testutil.AuthorizationProviderConfig() + resourceFolderCustomRole, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_authorization_folder_custom_role.folder_custom_role", "name", testutil.ConvertConfigVariable(testConfigVarsFolderCustomRole["role_name"])), + resource.TestCheckResourceAttr("stackit_authorization_folder_custom_role.folder_custom_role", "description", testutil.ConvertConfigVariable(testConfigVarsFolderCustomRole["role_description"])), + resource.TestCheckResourceAttr("stackit_authorization_folder_custom_role.folder_custom_role", "permissions.#", "1"), + resource.TestCheckTypeSetElemAttr("stackit_authorization_folder_custom_role.folder_custom_role", "permissions.*", testutil.ConvertConfigVariable(testConfigVarsFolderCustomRole["role_permissions_0"])), + resource.TestCheckResourceAttrSet("stackit_authorization_folder_custom_role.folder_custom_role", "role_id"), + ), + }, + // Data source + { + ConfigVariables: testConfigVarsFolderCustomRole, + Config: fmt.Sprintf(` + %s + + data "stackit_authorization_folder_custom_role" "folder_custom_role" { + resource_id = stackit_authorization_folder_custom_role.folder_custom_role.resource_id + role_id = stackit_authorization_folder_custom_role.folder_custom_role.role_id + } + `, + testutil.AuthorizationProviderConfig()+resourceFolderCustomRole, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair( + "stackit_authorization_folder_custom_role.folder_custom_role", "resource_id", + "data.stackit_authorization_folder_custom_role.folder_custom_role", "resource_id", + ), + resource.TestCheckResourceAttrPair( + "stackit_authorization_folder_custom_role.folder_custom_role", "role_id", + "data.stackit_authorization_folder_custom_role.folder_custom_role", "role_id", + ), + resource.TestCheckResourceAttrPair( + "stackit_authorization_folder_custom_role.folder_custom_role", "name", + "data.stackit_authorization_folder_custom_role.folder_custom_role", "name", + ), + resource.TestCheckResourceAttrPair( + "stackit_authorization_folder_custom_role.folder_custom_role", "description", + "data.stackit_authorization_folder_custom_role.folder_custom_role", "description", + ), + resource.TestCheckResourceAttrPair( + "stackit_authorization_folder_custom_role.folder_custom_role", "permissions", + "data.stackit_authorization_folder_custom_role.folder_custom_role", "permissions", + ), + ), + }, + // Import + { + ConfigVariables: testConfigVarsFolderCustomRole, + ResourceName: "stackit_authorization_folder_custom_role.folder_custom_role", + ImportStateIdFunc: func(s *terraform.State) (string, error) { + r, ok := s.RootModule().Resources["stackit_authorization_folder_custom_role.folder_custom_role"] + if !ok { + return "", fmt.Errorf("couldn't find resource stackit_authorization_folder_custom_role.folder_custom_role") + } + roleId, ok := r.Primary.Attributes["role_id"] + if !ok { + return "", fmt.Errorf("couldn't find attribute role_id") + } + folderId, ok := r.Primary.Attributes["resource_id"] + if !ok { + return "", fmt.Errorf("couldn't find attribute resource_id") + } + + return fmt.Sprintf("%s,%s", folderId, roleId), nil + }, + ImportState: true, + ImportStateVerify: true, + }, + // Update + { + ConfigVariables: testConfigVarsFolderCustomRoleUpdated, + Config: testutil.AuthorizationProviderConfig() + resourceFolderCustomRole, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_authorization_folder_custom_role.folder_custom_role", "name", testutil.ConvertConfigVariable(testConfigVarsFolderCustomRoleUpdated["role_name"])), + resource.TestCheckResourceAttr("stackit_authorization_folder_custom_role.folder_custom_role", "description", testutil.ConvertConfigVariable(testConfigVarsFolderCustomRoleUpdated["role_description"])), + resource.TestCheckResourceAttr("stackit_authorization_folder_custom_role.folder_custom_role", "permissions.#", "1"), + resource.TestCheckTypeSetElemAttr("stackit_authorization_folder_custom_role.folder_custom_role", "permissions.*", testutil.ConvertConfigVariable(testConfigVarsFolderCustomRoleUpdated["role_permissions_0"])), + resource.TestCheckResourceAttrSet("stackit_authorization_folder_custom_role.folder_custom_role", "role_id"), + ), + }, + // Deletion is done by the framework implicitly + }, + }) +} + +func TestAccOrganizationCustomRoleResource(t *testing.T) { + t.Log("Testing org custom role resource") + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + ConfigVariables: testConfigVarsOrganizationCustomRole, + Config: testutil.AuthorizationProviderConfig() + resourceOrganizationCustomRole, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_authorization_organization_custom_role.organization_custom_role", "resource_id", testutil.ConvertConfigVariable(testConfigVarsOrganizationCustomRole["organization_id"])), + resource.TestCheckResourceAttr("stackit_authorization_organization_custom_role.organization_custom_role", "name", testutil.ConvertConfigVariable(testConfigVarsOrganizationCustomRole["role_name"])), + resource.TestCheckResourceAttr("stackit_authorization_organization_custom_role.organization_custom_role", "description", testutil.ConvertConfigVariable(testConfigVarsOrganizationCustomRole["role_description"])), + resource.TestCheckResourceAttr("stackit_authorization_organization_custom_role.organization_custom_role", "permissions.#", "1"), + resource.TestCheckTypeSetElemAttr("stackit_authorization_organization_custom_role.organization_custom_role", "permissions.*", testutil.ConvertConfigVariable(testConfigVarsOrganizationCustomRole["role_permissions_0"])), + resource.TestCheckResourceAttrSet("stackit_authorization_organization_custom_role.organization_custom_role", "role_id"), + ), + }, + // Data source + { + ConfigVariables: testConfigVarsOrganizationCustomRole, + Config: fmt.Sprintf(` + %s + + data "stackit_authorization_organization_custom_role" "organization_custom_role" { + resource_id = stackit_authorization_organization_custom_role.organization_custom_role.resource_id + role_id = stackit_authorization_organization_custom_role.organization_custom_role.role_id + } + `, + testutil.AuthorizationProviderConfig()+resourceOrganizationCustomRole, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_authorization_organization_custom_role.organization_custom_role", "resource_id", testutil.ConvertConfigVariable(testConfigVarsOrganizationCustomRole["organization_id"])), + resource.TestCheckResourceAttrPair( + "stackit_authorization_organization_custom_role.organization_custom_role", "resource_id", + "data.stackit_authorization_organization_custom_role.organization_custom_role", "resource_id", + ), + resource.TestCheckResourceAttrPair( + "stackit_authorization_organization_custom_role.organization_custom_role", "role_id", + "data.stackit_authorization_organization_custom_role.organization_custom_role", "role_id", + ), + resource.TestCheckResourceAttrPair( + "stackit_authorization_organization_custom_role.organization_custom_role", "name", + "data.stackit_authorization_organization_custom_role.organization_custom_role", "name", + ), + resource.TestCheckResourceAttrPair( + "stackit_authorization_organization_custom_role.organization_custom_role", "description", + "data.stackit_authorization_organization_custom_role.organization_custom_role", "description", + ), + resource.TestCheckResourceAttrPair( + "stackit_authorization_organization_custom_role.organization_custom_role", "permissions", + "data.stackit_authorization_organization_custom_role.organization_custom_role", "permissions", + ), + ), + }, + // Import + { + ConfigVariables: testConfigVarsOrganizationCustomRole, + ResourceName: "stackit_authorization_organization_custom_role.organization_custom_role", + ImportStateIdFunc: func(s *terraform.State) (string, error) { + r, ok := s.RootModule().Resources["stackit_authorization_organization_custom_role.organization_custom_role"] + if !ok { + return "", fmt.Errorf("couldn't find resource stackit_authorization_organization_custom_role.organization_custom_role") + } + roleId, ok := r.Primary.Attributes["role_id"] + if !ok { + return "", fmt.Errorf("couldn't find attribute role_id") + } + + return fmt.Sprintf("%s,%s", testutil.OrganizationId, roleId), nil + }, + ImportState: true, + ImportStateVerify: true, + }, + // Update + { + ConfigVariables: testConfigVarsOrganizationCustomRoleUpdated, + Config: testutil.AuthorizationProviderConfig() + resourceOrganizationCustomRole, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom_role", "resource_id", testutil.ConvertConfigVariable(testConfigVarsCustomRoleUpdated["project_id"])), - resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom_role", "name", testutil.ConvertConfigVariable(testConfigVarsCustomRoleUpdated["role_name"])), - resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom_role", "description", testutil.ConvertConfigVariable(testConfigVarsCustomRoleUpdated["role_description"])), - resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom_role", "permissions.#", "1"), - resource.TestCheckTypeSetElemAttr("stackit_authorization_project_custom_role.custom_role", "permissions.*", testutil.ConvertConfigVariable(testConfigVarsCustomRoleUpdated["role_permissions_0"])), - resource.TestCheckResourceAttrSet("stackit_authorization_project_custom_role.custom_role", "role_id"), + resource.TestCheckResourceAttr("stackit_authorization_organization_custom_role.organization_custom_role", "resource_id", testutil.ConvertConfigVariable(testConfigVarsOrganizationCustomRoleUpdated["organization_id"])), + resource.TestCheckResourceAttr("stackit_authorization_organization_custom_role.organization_custom_role", "name", testutil.ConvertConfigVariable(testConfigVarsOrganizationCustomRoleUpdated["role_name"])), + resource.TestCheckResourceAttr("stackit_authorization_organization_custom_role.organization_custom_role", "description", testutil.ConvertConfigVariable(testConfigVarsOrganizationCustomRoleUpdated["role_description"])), + resource.TestCheckResourceAttr("stackit_authorization_organization_custom_role.organization_custom_role", "permissions.#", "1"), + resource.TestCheckTypeSetElemAttr("stackit_authorization_organization_custom_role.organization_custom_role", "permissions.*", testutil.ConvertConfigVariable(testConfigVarsOrganizationCustomRoleUpdated["role_permissions_0"])), + resource.TestCheckResourceAttrSet("stackit_authorization_organization_custom_role.organization_custom_role", "role_id"), ), }, // Deletion is done by the framework implicitly diff --git a/stackit/internal/services/authorization/customrole/resource.go b/stackit/internal/services/authorization/customrole/resource.go index fde1618e3..cf65c460d 100644 --- a/stackit/internal/services/authorization/customrole/resource.go +++ b/stackit/internal/services/authorization/customrole/resource.go @@ -28,6 +28,8 @@ import ( // List of resource types which can have custom roles. var resourceTypes = []string{ + "organization", + "folder", "project", } diff --git a/stackit/internal/services/authorization/customrole/resource_test.go b/stackit/internal/services/authorization/customrole/resource_test.go index b8c4f3cbd..6a1c154ae 100644 --- a/stackit/internal/services/authorization/customrole/resource_test.go +++ b/stackit/internal/services/authorization/customrole/resource_test.go @@ -14,88 +14,138 @@ import ( ) var ( - testRoleId = uuid.New().String() - testProjectId = uuid.New().String() + testRoleId = uuid.New().String() + testResourceId = uuid.New().String() ) +type testCase struct { + description string + input *authorization.GetRoleResponse + expected *Model + isValid bool +} + +func allResourceTypes(fn func(resourceType string) []testCase) []testCase { + var tests []testCase + + for _, resourceType := range resourceTypes { + tests = append(tests, fn(resourceType)...) + } + + return tests +} + func TestMapFields(t *testing.T) { - tests := []struct { - description string - input *authorization.GetRoleResponse - expected *Model - isValid bool - }{ - { - description: "full_input", - input: &authorization.GetRoleResponse{ - ResourceId: &testProjectId, - ResourceType: utils.Ptr("project"), - Role: utils.Ptr(authorization.Role{ - Id: &testRoleId, - Name: utils.Ptr("role-name"), - Description: utils.Ptr("Some description"), - Permissions: utils.Ptr([]authorization.Permission{ - { - Name: utils.Ptr("iam.subject.get"), - Description: utils.Ptr("Can read subjects."), - }, + tests := allResourceTypes(func(resourceType string) []testCase { + return []testCase{ + { + description: fmt.Sprintf("full_input_%s", resourceType), + input: &authorization.GetRoleResponse{ + ResourceId: &testResourceId, + ResourceType: &resourceType, + Role: utils.Ptr(authorization.Role{ + Id: &testRoleId, + Name: utils.Ptr("role-name"), + Description: utils.Ptr("Some description"), + Permissions: utils.Ptr([]authorization.Permission{ + { + Name: utils.Ptr("iam.subject.get"), + Description: utils.Ptr("Can read subjects."), + }, + }), }), - }), + }, + expected: &Model{ + Id: types.StringValue(fmt.Sprintf("%s,%s", testResourceId, testRoleId)), + RoleId: types.StringValue(testRoleId), + ResourceId: types.StringValue(testResourceId), + Name: types.StringValue("role-name"), + Description: types.StringValue("Some description"), + Permissions: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("iam.subject.get"), + }), + }, + isValid: true, }, - expected: &Model{ - Id: types.StringValue(fmt.Sprintf("%s,%s", testProjectId, testRoleId)), - RoleId: types.StringValue(testRoleId), - ResourceId: types.StringValue(testProjectId), - Name: types.StringValue("role-name"), - Description: types.StringValue("Some description"), - Permissions: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("iam.subject.get"), - }), + { + description: fmt.Sprintf("partial_input_%s", resourceType), + input: &authorization.GetRoleResponse{ + ResourceId: &testResourceId, + ResourceType: &resourceType, + Role: utils.Ptr(authorization.Role{ + Id: &testRoleId, + Permissions: utils.Ptr([]authorization.Permission{ + { + Name: utils.Ptr("iam.subject.get"), + }, + }), + }), + }, + expected: &Model{ + Id: types.StringValue(fmt.Sprintf("%s,%s", testResourceId, testRoleId)), + RoleId: types.StringValue(testRoleId), + ResourceId: types.StringValue(testResourceId), + Permissions: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("iam.subject.get"), + }), + }, + isValid: true, }, - isValid: true, - }, - { - description: "partial_input", - input: &authorization.GetRoleResponse{ - ResourceId: &testProjectId, - ResourceType: utils.Ptr("project"), - Role: utils.Ptr(authorization.Role{ - Id: &testRoleId, - Permissions: utils.Ptr([]authorization.Permission{ - { - Name: utils.Ptr("iam.subject.get"), - }, + { + description: fmt.Sprintf("partial_input_without_permissions_%s", resourceType), + input: &authorization.GetRoleResponse{ + ResourceId: &testResourceId, + ResourceType: &resourceType, + Role: utils.Ptr(authorization.Role{ + Id: &testRoleId, + Permissions: utils.Ptr([]authorization.Permission{}), }), - }), + }, + expected: &Model{ + Id: types.StringValue(fmt.Sprintf("%s,%s", testResourceId, testRoleId)), + RoleId: types.StringValue(testRoleId), + ResourceId: types.StringValue(testResourceId), + Permissions: types.ListNull(types.StringType), + }, + isValid: true, }, - expected: &Model{ - Id: types.StringValue(fmt.Sprintf("%s,%s", testProjectId, testRoleId)), - RoleId: types.StringValue(testRoleId), - ResourceId: types.StringValue(testProjectId), - Permissions: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("iam.subject.get"), - }), + { + description: fmt.Sprintf("missing_role_%s", resourceType), + input: &authorization.GetRoleResponse{ + ResourceId: &testResourceId, + ResourceType: &resourceType, + }, + expected: nil, + isValid: false, }, - isValid: true, - }, - { - description: "partial_input_without_permissions", - input: &authorization.GetRoleResponse{ - ResourceId: &testProjectId, - ResourceType: utils.Ptr("project"), - Role: utils.Ptr(authorization.Role{ - Id: &testRoleId, - Permissions: utils.Ptr([]authorization.Permission{}), - }), + { + description: fmt.Sprintf("missing_permissions_%s", resourceType), + input: &authorization.GetRoleResponse{ + ResourceId: &testResourceId, + ResourceType: &resourceType, + Role: utils.Ptr(authorization.Role{ + Id: &testRoleId, + }), + }, + expected: nil, + isValid: false, }, - expected: &Model{ - Id: types.StringValue(fmt.Sprintf("%s,%s", testProjectId, testRoleId)), - RoleId: types.StringValue(testRoleId), - ResourceId: types.StringValue(testProjectId), - Permissions: types.ListNull(types.StringType), + { + description: fmt.Sprintf("missing_role_id_%s", resourceType), + input: &authorization.GetRoleResponse{ + ResourceId: &testResourceId, + ResourceType: &resourceType, + Role: utils.Ptr(authorization.Role{ + Permissions: utils.Ptr([]authorization.Permission{}), + }), + }, + expected: nil, + isValid: false, }, - isValid: true, - }, + } + }) + + tests = append(tests, []testCase{ { description: "nil_instance", input: nil, @@ -108,40 +158,7 @@ func TestMapFields(t *testing.T) { expected: nil, isValid: false, }, - { - description: "missing_role", - input: &authorization.GetRoleResponse{ - ResourceId: &testProjectId, - ResourceType: utils.Ptr("project"), - }, - expected: nil, - isValid: false, - }, - { - description: "missing_permissions", - input: &authorization.GetRoleResponse{ - ResourceId: &testProjectId, - ResourceType: utils.Ptr("project"), - Role: utils.Ptr(authorization.Role{ - Id: &testRoleId, - }), - }, - expected: nil, - isValid: false, - }, - { - description: "missing_role_id", - input: &authorization.GetRoleResponse{ - ResourceId: &testProjectId, - ResourceType: utils.Ptr("project"), - Role: utils.Ptr(authorization.Role{ - Permissions: utils.Ptr([]authorization.Permission{}), - }), - }, - expected: nil, - isValid: false, - }, - } + }...) for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { diff --git a/stackit/internal/services/authorization/testdata/resource-folder-custom-role.tf b/stackit/internal/services/authorization/testdata/resource-folder-custom-role.tf new file mode 100644 index 000000000..6bbd136aa --- /dev/null +++ b/stackit/internal/services/authorization/testdata/resource-folder-custom-role.tf @@ -0,0 +1,20 @@ + +variable "folder_name" {} +variable "owner_email" {} +variable "parent_container_id" {} +variable "role_name" {} +variable "role_description" {} +variable "role_permissions_0" {} + +resource "stackit_resourcemanager_folder" "folder" { + name = var.folder_name + owner_email = var.owner_email + parent_container_id = var.parent_container_id +} + +resource "stackit_authorization_folder_custom_role" "folder_custom_role" { + resource_id = stackit_resourcemanager_folder.folder.folder_id + name = var.role_name + description = var.role_description + permissions = [var.role_permissions_0] +} diff --git a/stackit/internal/services/authorization/testdata/resource-organization-custom-role.tf b/stackit/internal/services/authorization/testdata/resource-organization-custom-role.tf new file mode 100644 index 000000000..fd1ec99be --- /dev/null +++ b/stackit/internal/services/authorization/testdata/resource-organization-custom-role.tf @@ -0,0 +1,12 @@ + +variable "organization_id" {} +variable "role_name" {} +variable "role_description" {} +variable "role_permissions_0" {} + +resource "stackit_authorization_organziation_custom_role" "organization_custom_role" { + resource_id = var.organization_id + name = var.role_name + description = var.role_description + permissions = [var.role_permissions_0] +} diff --git a/stackit/internal/services/authorization/testdata/resource-custom-role.tf b/stackit/internal/services/authorization/testdata/resource-project-custom-role.tf similarity index 76% rename from stackit/internal/services/authorization/testdata/resource-custom-role.tf rename to stackit/internal/services/authorization/testdata/resource-project-custom-role.tf index b84f3ce1f..56207eff9 100644 --- a/stackit/internal/services/authorization/testdata/resource-custom-role.tf +++ b/stackit/internal/services/authorization/testdata/resource-project-custom-role.tf @@ -4,7 +4,7 @@ variable "role_name" {} variable "role_description" {} variable "role_permissions_0" {} -resource "stackit_authorization_project_custom_role" "custom_role" { +resource "stackit_authorization_project_custom_role" "project_custom_role" { resource_id = var.project_id name = var.role_name description = var.role_description