diff --git a/docs/resources/identity_group_role_assignment.md b/docs/resources/identity_group_role_assignment.md
new file mode 100644
index 00000000..2c5de7b8
--- /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/docs/resources/identity_role_assignment_v3.md b/docs/resources/identity_role_assignment_v3.md
index a1d1f363..8edd44f9 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/acceptance/acceptance.go b/flexibleengine/acceptance/acceptance.go
index 94b7c88f..e93b13d7 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 00000000..de51ba1f
--- /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 = <