From 190f7153321a68b62045ad31382aaf362ee3f3ee Mon Sep 17 00:00:00 2001 From: shichangkuo Date: Wed, 20 Mar 2024 15:02:51 +0800 Subject: [PATCH 1/2] feat(iam): add group role assignment resource --- .../identity_group_role_assignment.md | 154 ++++++++ flexibleengine/acceptance/acceptance.go | 6 + ...ine_identity_group_role_assignment_test.go | 356 ++++++++++++++++++ flexibleengine/provider.go | 3 +- 4 files changed, 518 insertions(+), 1 deletion(-) create mode 100644 docs/resources/identity_group_role_assignment.md create mode 100644 flexibleengine/acceptance/resource_flexibleengine_identity_group_role_assignment_test.go diff --git a/docs/resources/identity_group_role_assignment.md b/docs/resources/identity_group_role_assignment.md new file mode 100644 index 000000000..2c5de7b8f --- /dev/null +++ b/docs/resources/identity_group_role_assignment.md @@ -0,0 +1,154 @@ +--- +subcategory: "Identity and Access Management (IAM)" +--- + +# flexibleengine_identity_group_role_assignment + +Manages an IAM user group role assignment within FlexibleEngine IAM Service. +This is an alternative to `flexibleengine_identity_role_assignment_v3` + +-> **NOTE:** 1. You *must* have admin privileges to use this resource. +
2. When the resource is created, the permissions will take effect after 15 to 30 minutes. + +## Example Usage + +### Assign role with project + +```hcl +variable "project_id" {} + +data "flexibleengine_identity_role_v3" "test" { + # RDS Administrator + name = "rds_adm" +} + +resource "flexibleengine_identity_group_v3" "test" { + name = "group_1" +} + +resource "flexibleengine_identity_group_role_assignment" "test" { + group_id = flexibleengine_identity_group_v3.test.id + role_id = data.flexibleengine_identity_role_v3.test.id + project_id = var.project_id +} +``` + +### Assign role with all projects + +```hcl +data "flexibleengine_identity_role_v3" "test" { + # RDS Administrator + name = "rds_adm" +} + +resource "flexibleengine_identity_group_v3" "test" { + name = "group_1" +} + +resource "flexibleengine_identity_group_role_assignment" "all" { + group_id = flexibleengine_identity_group_v3.test.id + role_id = data.flexibleengine_identity_role_v3.test.id + project_id = "all" +} +``` + +### Assign role with domain + +```hcl +variable "domain_id" {} + +data "flexibleengine_identity_role_v3" "test" { + # OBS Administrator + name = "obs_adm" +} + +resource "flexibleengine_identity_group_v3" "test" { + name = "group_1" +} + +resource "flexibleengine_identity_group_role_assignment" "test" { + group_id = flexibleengine_identity_group_v3.test.id + role_id = data.flexibleengine_identity_role_v3.test.id + domain_id = var.domain_id +} +``` + +### Assign role with enterprise project + +```hcl +variable "enterprise_project_id" {} + +data "flexibleengine_identity_role_v3" "test" { + # RDS Administrator + name = "rds_adm" +} + +resource "flexibleengine_identity_group_v3" "test" { + name = "group_1" +} + +resource "flexibleengine_identity_group_role_assignment" "test" { + group_id = flexibleengine_identity_group_v3.test.id + role_id = data.flexibleengine_identity_role_v3.test.id + enterprise_project_id = var.enterprise_project_id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `group_id` - (Required, String, ForceNew) Specifies the group to assign the role to. + Changing this parameter will create a new resource. + +* `role_id` - (Required, String, ForceNew) Specifies the role to assign. + Changing this parameter will create a new resource. + +* `domain_id` - (Optional, String, ForceNew) Specifies the domain to assign the role in. + Changing this parameter will create a new resource. + +* `project_id` - (Optional, String, ForceNew) Specifies the project to assign the role in. + If `project_id` is set to **all**, it means that the specified user group will be able to use all projects, + including existing and future projects. + + Changing this parameter will create a new resource. + +* `enterprise_project_id` - (Optional, String, ForceNew) Specifies the enterprise project to assign the role in. + Changing this parameter will create a new resource. + + ~> Exactly one of `domain_id`, `project_id` or `enterprise_project_id` must be specified. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The resource ID. When assign in domain, the format is `//`; + when assign in project, the format is `//`; + when assign in enterprise project, the format is `//`; + +## Import + +The role assignments can be imported using the `group_id`, `role_id` and `domain_id`, `project_id`, + `enterprise_project_id`, e.g. + +```bash +$ terraform import flexibleengine_identity_group_role_assignment.test // +``` + +or + +```bash +$ terraform import flexibleengine_identity_group_role_assignment.test // +``` + +or + +```bash +$ terraform import flexibleengine_identity_group_role_assignment.test //all +``` + +or + +```bash +$ terraform import flexibleengine_identity_group_role_assignment.test // +``` diff --git a/flexibleengine/acceptance/acceptance.go b/flexibleengine/acceptance/acceptance.go index 94b7c88f9..e93b13d76 100644 --- a/flexibleengine/acceptance/acceptance.go +++ b/flexibleengine/acceptance/acceptance.go @@ -172,3 +172,9 @@ func testAccPrecheckDomainId(t *testing.T) { t.Skip("OS_DOMAIN_ID must be set for acceptance tests") } } + +func testAccPreCheckProjectID(t *testing.T) { + if OS_PROJECT_ID == "" { + t.Skip("OS_PROJECT_ID must be set for acceptance tests") + } +} diff --git a/flexibleengine/acceptance/resource_flexibleengine_identity_group_role_assignment_test.go b/flexibleengine/acceptance/resource_flexibleengine_identity_group_role_assignment_test.go new file mode 100644 index 000000000..de51ba1f7 --- /dev/null +++ b/flexibleengine/acceptance/resource_flexibleengine_identity_group_role_assignment_test.go @@ -0,0 +1,356 @@ +package acceptance + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/chnsz/golangsdk" + "github.com/chnsz/golangsdk/openstack/identity/v3.0/eps_permissions" + "github.com/chnsz/golangsdk/openstack/identity/v3/roles" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/iam" +) + +func getIdentityGroupRoleAssignmentResourceFunc(c *config.Config, state *terraform.ResourceState) (interface{}, error) { + identityClient, err := c.IdentityV3Client(OS_REGION_NAME) + if err != nil { + return nil, fmt.Errorf("error creating IAM v3 client: %s", err) + } + + iamClient, err := c.IAMV3Client(OS_REGION_NAME) + if err != nil { + return nil, fmt.Errorf("error creating IAM v3.0 client: %s", err) + } + + groupID := state.Primary.Attributes["group_id"] + roleID := state.Primary.Attributes["role_id"] + domainID := state.Primary.Attributes["domain_id"] + projectID := state.Primary.Attributes["project_id"] + enterpriseProjectID := state.Primary.Attributes["enterprise_project_id"] + + if domainID != "" { + return iam.GetGroupRoleAssignmentWithDomainID(identityClient, groupID, roleID, domainID) + } + + if projectID != "" { + if projectID == "all" { + specifiedRole := roles.Role{ + ID: roleID, + } + err = roles.CheckAllResourcesPermission(identityClient, c.DomainID, groupID, roleID).ExtractErr() + return specifiedRole, err + } + + return iam.GetGroupRoleAssignmentWithProjectID(identityClient, groupID, roleID, projectID) + } + + if enterpriseProjectID != "" { + return iam.GetGroupRoleAssignmentWithEpsID(iamClient, groupID, roleID, enterpriseProjectID) + } + + return nil, golangsdk.ErrDefault404{} +} + +func TestAccIdentityGroupRoleAssignment_basic(t *testing.T) { + rName := acceptance.RandomAccResourceName() + resourceName := "flexibleengine_identity_group_role_assignment.test" + var role roles.Role + + rc := acceptance.InitResourceCheck( + resourceName, + &role, + getIdentityGroupRoleAssignmentResourceFunc, + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAdminOnly(t) + testAccPrecheckDomainId(t) + }, + ProviderFactories: TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccIdentityGroupRoleAssignment_domain(rName), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttrPair(resourceName, "group_id", + "flexibleengine_identity_group_v3.test", "id"), + resource.TestCheckResourceAttrPair(resourceName, "role_id", + "flexibleengine_identity_role_v3.test", "id"), + resource.TestCheckResourceAttr(resourceName, "domain_id", + OS_DOMAIN_ID), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccIdentityGroupRoleAssignmentDomainImportStateFunc(resourceName), + }, + }, + }) +} + +func TestAccIdentityGroupRoleAssignment_project(t *testing.T) { + rName := acceptance.RandomAccResourceName() + resourceName := "flexibleengine_identity_group_role_assignment.test" + var role roles.Role + + rc := acceptance.InitResourceCheck( + resourceName, + &role, + getIdentityGroupRoleAssignmentResourceFunc, + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAdminOnly(t) + testAccPreCheckProjectID(t) + }, + ProviderFactories: TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccIdentityGroupRoleAssignment_project(rName), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + + resource.TestCheckResourceAttrPair(resourceName, "group_id", + "flexibleengine_identity_group_v3.test", "id"), + resource.TestCheckResourceAttrPair(resourceName, "role_id", + "flexibleengine_identity_role_v3.test", "id"), + resource.TestCheckResourceAttr(resourceName, "project_id", + OS_PROJECT_ID), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccIdentityGroupRoleAssignmentProjectImportStateFunc(resourceName), + }, + }, + }) +} + +func TestAccIdentityGroupRoleAssignment_allProjects(t *testing.T) { + rName := acceptance.RandomAccResourceName() + resourceName := "flexibleengine_identity_group_role_assignment.test" + var role roles.Role + + rc := acceptance.InitResourceCheck( + resourceName, + &role, + getIdentityGroupRoleAssignmentResourceFunc, + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAdminOnly(t) + }, + ProviderFactories: TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccIdentityGroupRoleAssignment_allProjects(rName), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(resourceName, "project_id", "all"), + resource.TestCheckResourceAttrPair(resourceName, "group_id", + "flexibleengine_identity_group_v3.test", "id"), + resource.TestCheckResourceAttrPair(resourceName, "role_id", + "flexibleengine_identity_role_v3.test", "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccIdentityGroupRoleAssignmentProjectImportStateFunc(resourceName), + }, + }, + }) +} + +func TestAccIdentityGroupRoleAssignment_epsID(t *testing.T) { + rName := acceptance.RandomAccResourceName() + resourceName := "flexibleengine_identity_group_role_assignment.test" + var role eps_permissions.Role + + rc := acceptance.InitResourceCheck( + resourceName, + &role, + getIdentityGroupRoleAssignmentResourceFunc, + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAdminOnly(t) + testAccPreCheckEpsID(t) + }, + ProviderFactories: TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccIdentityGroupRoleAssignment_epsID(rName), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttrPair(resourceName, "group_id", + "flexibleengine_identity_group_v3.test", "id"), + resource.TestCheckResourceAttrPair(resourceName, "role_id", + "flexibleengine_identity_role_v3.test", "id"), + resource.TestCheckResourceAttr(resourceName, "enterprise_project_id", + OS_ENTERPRISE_PROJECT_ID_TEST), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccIdentityGroupRoleAssignmentEpsImportStateFunc(resourceName), + }, + }, + }) +} + +func testAccIdentityGroupRoleAssignmentDomainImportStateFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("resource (%s) not found: %s", resourceName, rs) + } + if rs.Primary.Attributes["group_id"] == "" || + rs.Primary.Attributes["role_id"] == "" || rs.Primary.Attributes["domain_id"] == "" { + return "", fmt.Errorf("invalid format specified for import ID,"+ + " want '//', but got '%s/%s/%s'", + rs.Primary.Attributes["group_id"], rs.Primary.Attributes["role_id"], + rs.Primary.Attributes["domain_id"]) + } + return fmt.Sprintf("%s/%s/%s", rs.Primary.Attributes["group_id"], + rs.Primary.Attributes["role_id"], rs.Primary.Attributes["domain_id"]), nil + } +} + +func testAccIdentityGroupRoleAssignmentProjectImportStateFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("resource (%s) not found: %s", resourceName, rs) + } + if rs.Primary.Attributes["group_id"] == "" || + rs.Primary.Attributes["role_id"] == "" || rs.Primary.Attributes["project_id"] == "" { + return "", fmt.Errorf("invalid format specified for import ID,"+ + " want '//', but got '%s/%s/%s'", + rs.Primary.Attributes["group_id"], rs.Primary.Attributes["role_id"], + rs.Primary.Attributes["project_id"]) + } + return fmt.Sprintf("%s/%s/%s", rs.Primary.Attributes["group_id"], + rs.Primary.Attributes["role_id"], rs.Primary.Attributes["project_id"]), nil + } +} + +func testAccIdentityGroupRoleAssignmentEpsImportStateFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("resource (%s) not found: %s", resourceName, rs) + } + if rs.Primary.Attributes["group_id"] == "" || + rs.Primary.Attributes["role_id"] == "" || rs.Primary.Attributes["enterprise_project_id"] == "" { + return "", fmt.Errorf("invalid format specified for import ID,"+ + " want '//', but got '%s/%s/%s'", + rs.Primary.Attributes["group_id"], rs.Primary.Attributes["role_id"], + rs.Primary.Attributes["enterprise_project_id"]) + } + return fmt.Sprintf("%s/%s/%s", rs.Primary.Attributes["group_id"], + rs.Primary.Attributes["role_id"], rs.Primary.Attributes["enterprise_project_id"]), nil + } +} + +func testAccIdentityGroupRoleAssignment_base(rName string) string { + return fmt.Sprintf(` +resource "flexibleengine_identity_role_v3" test { + name = "%[1]s" + description = "created by terraform" + type = "AX" + policy = < Date: Wed, 20 Mar 2024 15:15:22 +0800 Subject: [PATCH 2/2] chore(iam): deprecate role_assignment_v3 resource --- docs/resources/identity_role_assignment_v3.md | 4 +++- flexibleengine/provider.go | 3 ++- .../resource_flexibleengine_identity_role_assignment_v3.go | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/resources/identity_role_assignment_v3.md b/docs/resources/identity_role_assignment_v3.md index a1d1f3639..8edd44f9c 100644 --- a/docs/resources/identity_role_assignment_v3.md +++ b/docs/resources/identity_role_assignment_v3.md @@ -1,5 +1,5 @@ --- -subcategory: "Identity and Access Management (IAM)" +subcategory: "Deprecated" description: "" page_title: "flexibleengine_identity_role_assignment_v3" --- @@ -10,6 +10,8 @@ Manages a V3 Role assignment within group on FlexibleEngine IAM Service. -> You *must* have admin privileges in your FlexibleEngine cloud to use this resource. +!> **Warning:** It has been deprecated, please use `flexibleengine_identity_group_role_assignment` instead. + ## Example Usage: Assign Role On Project Level ```hcl diff --git a/flexibleengine/provider.go b/flexibleengine/provider.go index 089bf1c2d..08daae080 100644 --- a/flexibleengine/provider.go +++ b/flexibleengine/provider.go @@ -457,7 +457,6 @@ func Provider() *schema.Provider { "flexibleengine_identity_group_membership_v3": resourceIdentityGroupMembershipV3(), "flexibleengine_identity_project_v3": resourceIdentityProjectV3(), "flexibleengine_identity_role_v3": resourceIdentityRoleV3(), - "flexibleengine_identity_role_assignment_v3": resourceIdentityRoleAssignmentV3(), "flexibleengine_identity_user_v3": resourceIdentityUserV3(), "flexibleengine_identity_provider": resourceIdentityProvider(), "flexibleengine_identity_provider_conversion": resourceIAMProviderConversion(), @@ -706,6 +705,8 @@ func Provider() *schema.Provider { "flexibleengine_vpc_eip_v1": resourceVpcEIPV1(), "flexibleengine_vpc_route_v2": resourceVPCRouteV2(), + "flexibleengine_identity_role_assignment_v3": resourceIdentityRoleAssignmentV3(), + "flexibleengine_mrs_hybrid_cluster_v1": resourceMRSHybridClusterV1(), "flexibleengine_mrs_cluster_v1": resourceMRSClusterV1(), "flexibleengine_mrs_job_v1": resourceMRSJobV1(), diff --git a/flexibleengine/resource_flexibleengine_identity_role_assignment_v3.go b/flexibleengine/resource_flexibleengine_identity_role_assignment_v3.go index 913b35e3c..0028d66bd 100644 --- a/flexibleengine/resource_flexibleengine_identity_role_assignment_v3.go +++ b/flexibleengine/resource_flexibleengine_identity_role_assignment_v3.go @@ -20,6 +20,7 @@ func resourceIdentityRoleAssignmentV3() *schema.Resource { State: schema.ImportStatePassthrough, }, + DeprecationMessage: `It has been deprecated, please use 'flexibleengine_identity_group_role_assignment' instead`, Schema: map[string]*schema.Schema{ "group_id": { Type: schema.TypeString,