diff --git a/docs/data-sources/team_member.md b/docs/data-sources/team_member.md
index 254313a6..9d43ef01 100644
--- a/docs/data-sources/team_member.md
+++ b/docs/data-sources/team_member.md
@@ -28,6 +28,10 @@ data "launchdarkly_team_member" "example" {
- `email` (String) The unique email address associated with the team member.
+### Optional
+
+- `role_attributes` (Block Set) A role attributes block. One block must be defined per role attribute. The key is the role attribute key and the value is a string array of resource keys that apply. (see [below for nested schema](#nestedblock--role_attributes))
+
### Read-Only
- `custom_roles` (Set of String) The list of custom roles keys associated with the team member. Custom roles are only available to customers on an Enterprise plan. To learn more, [read about our pricing](https://launchdarkly.com/pricing/). To upgrade your plan, [contact LaunchDarkly Sales](https://launchdarkly.com/contact-sales/).
@@ -35,3 +39,11 @@ data "launchdarkly_team_member" "example" {
- `id` (String) The 24 character alphanumeric ID of the team member.
- `last_name` (String) The team member's family name.
- `role` (String) The role associated with team member. Possible roles are `owner`, `reader`, `writer`, or `admin`.
+
+
+### Nested Schema for `role_attributes`
+
+Required:
+
+- `key` (String) The key / name of your role attribute. In the example `$${roleAttribute/testAttribute}`, the key is `testAttribute`.
+- `values` (List of String) A list of values for your role attribute. For example, if your policy statement defines the resource `"proj/$${roleAttribute/testAttribute}"`, the values would be the keys of the projects you wanted to assign access to.
diff --git a/docs/data-sources/team_members.md b/docs/data-sources/team_members.md
index 603b2b8c..71d5008e 100644
--- a/docs/data-sources/team_members.md
+++ b/docs/data-sources/team_members.md
@@ -58,3 +58,12 @@ Read-Only:
- `id` (String)
- `last_name` (String)
- `role` (String)
+- `role_attributes` (Set of Object) (see [below for nested schema](#nestedobjatt--team_members--role_attributes))
+
+
+### Nested Schema for `team_members.role_attributes`
+
+Read-Only:
+
+- `key` (String)
+- `values` (List of String)
diff --git a/docs/resources/team_member.md b/docs/resources/team_member.md
index 8b7fea32..63d57049 100644
--- a/docs/resources/team_member.md
+++ b/docs/resources/team_member.md
@@ -42,11 +42,20 @@ resource "launchdarkly_team_member" "example" {
- `first_name` (String) The team member's given name. Once created, this cannot be updated except by the team member.
- `last_name` (String) TThe team member's family name. Once created, this cannot be updated except by the team member.
- `role` (String) The role associated with team member. Supported roles are `reader`, `writer`, `no_access`, or `admin`. If you don't specify a role, `reader` is assigned by default.
+- `role_attributes` (Block Set) A role attributes block. One block must be defined per role attribute. The key is the role attribute key and the value is a string array of resource keys that apply. (see [below for nested schema](#nestedblock--role_attributes))
### Read-Only
- `id` (String) The 24 character alphanumeric ID of the team member.
+
+### Nested Schema for `role_attributes`
+
+Required:
+
+- `key` (String) The key / name of your role attribute. In the example `$${roleAttribute/testAttribute}`, the key is `testAttribute`.
+- `values` (List of String) A list of values for your role attribute. For example, if your policy statement defines the resource `"proj/$${roleAttribute/testAttribute}"`, the values would be the keys of the projects you wanted to assign access to.
+
## Import
Import is supported using the following syntax:
diff --git a/launchdarkly/data_source_launchdarkly_team_member.go b/launchdarkly/data_source_launchdarkly_team_member.go
index 70ccfb9b..7433ec0c 100644
--- a/launchdarkly/data_source_launchdarkly_team_member.go
+++ b/launchdarkly/data_source_launchdarkly_team_member.go
@@ -44,6 +44,7 @@ func memberSchema() map[string]*schema.Schema {
Computed: true,
Description: `The list of custom roles keys associated with the team member. Custom roles are only available to customers on an Enterprise plan. To learn more, [read about our pricing](https://launchdarkly.com/pricing/). To upgrade your plan, [contact LaunchDarkly Sales](https://launchdarkly.com/contact-sales/).`,
},
+ ROLE_ATTRIBUTES: roleAttributesSchema(true),
}
}
@@ -63,7 +64,7 @@ func getTeamMemberByEmail(client *Client, memberEmail string) (*ldapi.Member, er
teamMemberLimit := int64(1000)
// After changing this to query by member email, we shouldn't need the limit and recursion on requests, but leaving it in just to be extra safe
- members, _, err := client.ld.AccountMembersApi.GetMembers(client.ctx).Filter(fmt.Sprintf("query:%s", url.QueryEscape(memberEmail))).Execute()
+ members, _, err := client.ld.AccountMembersApi.GetMembers(client.ctx).Filter(fmt.Sprintf("query:%s", url.QueryEscape(memberEmail))).Expand("roleAttributes").Execute()
if err != nil {
return nil, fmt.Errorf("failed to read team member with email: %s: %v", memberEmail, handleLdapiErr(err))
@@ -111,6 +112,10 @@ func dataSourceTeamMemberRead(ctx context.Context, d *schema.ResourceData, meta
if err != nil {
return diag.Errorf("failed to set custom roles on team member with email %q: %v", member.Email, err)
}
+ err = d.Set(ROLE_ATTRIBUTES, roleAttributesToResourceData(member.RoleAttributes))
+ if err != nil {
+ return diag.Errorf("failed to set role attributes on team member with id %q: %v", member.Id, err)
+ }
return diags
}
diff --git a/launchdarkly/data_source_launchdarkly_team_member_test.go b/launchdarkly/data_source_launchdarkly_team_member_test.go
index 801c927b..698a9426 100644
--- a/launchdarkly/data_source_launchdarkly_team_member_test.go
+++ b/launchdarkly/data_source_launchdarkly_team_member_test.go
@@ -25,6 +25,9 @@ func testAccDataSourceTeamMemberCreate(client *Client, email string) (*ldapi.Mem
Email: email,
FirstName: ldapi.PtrString("Test"),
LastName: ldapi.PtrString("Account"),
+ RoleAttributes: &map[string][]string{
+ "testAttribute": []string{"testValue"},
+ },
}}
members, _, err := client.ld.AccountMembersApi.PostMembers(client.ctx).NewMemberForm(membersBody).Execute()
if err != nil {
@@ -92,6 +95,7 @@ func TestAccDataSourceTeamMember_exists(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, FIRST_NAME, *testMember.FirstName),
resource.TestCheckResourceAttr(resourceName, LAST_NAME, *testMember.LastName),
resource.TestCheckResourceAttr(resourceName, ID, testMember.Id),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.#", "1"),
),
},
},
diff --git a/launchdarkly/keys.go b/launchdarkly/keys.go
index 9bba43ea..d7769d29 100644
--- a/launchdarkly/keys.go
+++ b/launchdarkly/keys.go
@@ -94,6 +94,7 @@ const (
RESOURCE = "resource"
RESOURCES = "resources"
ROLE = "role"
+ ROLE_ATTRIBUTES = "role_attributes"
ROLLOUT_CONTEXT_KIND = "rollout_context_kind"
ROLLOUT_WEIGHTS = "rollout_weights"
RULES = "rules"
diff --git a/launchdarkly/resource_launchdarkly_team_member.go b/launchdarkly/resource_launchdarkly_team_member.go
index 879db441..e6b578c3 100644
--- a/launchdarkly/resource_launchdarkly_team_member.go
+++ b/launchdarkly/resource_launchdarkly_team_member.go
@@ -62,6 +62,7 @@ func resourceTeamMember() *schema.Resource {
Description: "The list of custom roles keys associated with the team member. Custom roles are only available to customers on an Enterprise plan. To learn more, [read about our pricing](https://launchdarkly.com/pricing/). To upgrade your plan, [contact LaunchDarkly Sales](https://launchdarkly.com/contact-sales/).\n\n-> **Note:** each `launchdarkly_team_member` must have either a `role` or `custom_roles` argument.",
AtLeastOneOf: []string{ROLE, CUSTOM_ROLES},
},
+ ROLE_ATTRIBUTES: roleAttributesSchema(false),
},
Description: `Provides a LaunchDarkly team member resource.
@@ -79,6 +80,7 @@ func resourceTeamMemberCreate(ctx context.Context, d *schema.ResourceData, metaR
lastName := d.Get(LAST_NAME).(string)
memberRole := d.Get(ROLE).(string)
customRolesRaw := d.Get(CUSTOM_ROLES).(*schema.Set).List()
+ roleAttributes := roleAttributesFromResourceData(d.Get(ROLE_ATTRIBUTES).(*schema.Set).List())
customRoles := make([]string, len(customRolesRaw))
for i, cr := range customRolesRaw {
@@ -86,13 +88,15 @@ func resourceTeamMemberCreate(ctx context.Context, d *schema.ResourceData, metaR
}
membersBody := ldapi.NewMemberForm{
- Email: memberEmail,
- FirstName: &firstName,
- LastName: &lastName,
- Role: &memberRole,
- CustomRoles: customRoles,
+ Email: memberEmail,
+ FirstName: &firstName,
+ LastName: &lastName,
+ Role: &memberRole,
+ CustomRoles: customRoles,
+ RoleAttributes: roleAttributes,
}
+ // role attributes will not come back here because we have to set an expand query param
members, _, err := client.ld.AccountMembersApi.PostMembers(client.ctx).NewMemberForm([]ldapi.NewMemberForm{membersBody}).Execute()
if err != nil {
return diag.Errorf("failed to create team member with email: %s: %v", memberEmail, handleLdapiErr(err))
@@ -108,7 +112,7 @@ func resourceTeamMemberRead(ctx context.Context, d *schema.ResourceData, metaRaw
client := metaRaw.(*Client)
memberID := d.Id()
- member, res, err := client.ld.AccountMembersApi.GetMember(client.ctx, memberID).Execute()
+ member, res, err := client.ld.AccountMembersApi.GetMember(client.ctx, memberID).Expand("roleAttributes").Execute()
if isStatusNotFound(res) {
log.Printf("[WARN] failed to find member with id %q, removing from state", memberID)
diags = append(diags, diag.Diagnostic{
@@ -136,6 +140,10 @@ func resourceTeamMemberRead(ctx context.Context, d *schema.ResourceData, metaRaw
if err != nil {
return diag.Errorf("failed to set custom roles on team member with id %q: %v", member.Id, err)
}
+ err = d.Set(ROLE_ATTRIBUTES, roleAttributesToResourceData(member.RoleAttributes))
+ if err != nil {
+ return diag.Errorf("failed to set role attributes on team member with id %q: %v", member.Id, err)
+ }
return diags
}
@@ -159,6 +167,7 @@ func resourceTeamMemberUpdate(ctx context.Context, d *schema.ResourceData, metaR
patchReplace("/role", &memberRole),
patchReplace("/customRoles", &customRoleIds),
}
+ patch = append(patch, getRoleAttributePatches(d)...)
_, _, err = client.ld.AccountMembersApi.PatchMember(client.ctx, memberID).PatchOperation(patch).Execute()
if err != nil {
diff --git a/launchdarkly/resource_launchdarkly_team_member_test.go b/launchdarkly/resource_launchdarkly_team_member_test.go
index f73bae81..597f5ee1 100644
--- a/launchdarkly/resource_launchdarkly_team_member_test.go
+++ b/launchdarkly/resource_launchdarkly_team_member_test.go
@@ -75,12 +75,85 @@ resource "launchdarkly_team_member" "custom_role_test" {
email = "%s+wbteste2e@launchdarkly.com"
first_name = "first"
last_name = "last"
- custom_roles = [launchdarkly_custom_role.test_2.key]
+ custom_roles = [launchdarkly_custom_role.test.key]
+}
+`
+ testAccTeamMemberCustomRoleWithRoleAttributes = `
+resource "launchdarkly_custom_role" "test" {
+ key = "%s"
+ name = "Updated - %s"
+ description= "Allow all actions on testAttribute environments"
+ policy_statements {
+ actions = ["*"]
+ effect = "allow"
+ resources = ["proj/*:env/$${roleAttribute/testAttribute}"]
+ }
+}
+
+resource "launchdarkly_team_member" "custom_role_test" {
+ email = "%s+wbteste2e@launchdarkly.com"
+ first_name = "first"
+ last_name = "last"
+ custom_roles = [launchdarkly_custom_role.test.key]
+ role_attributes {
+ key = "testAttribute"
+ values = ["staging", "production"]
+ }
+ role_attributes {
+ key = "nonexistentAttribute"
+ values = ["someValue"]
+ }
+}
+`
+ testAccTeamMemberCustomRoleWithRoleAttributesUpdate = `
+resource "launchdarkly_custom_role" "test" {
+ key = "%s"
+ name = "Updated - %s"
+ description= "Allow all actions on testAttribute environments"
+ policy_statements {
+ actions = ["*"]
+ effect = "allow"
+ resources = ["proj/*:env/$${roleAttribute/testAttribute}"]
+ }
+}
+
+resource "launchdarkly_team_member" "custom_role_test" {
+ email = "%s+wbteste2e@launchdarkly.com"
+ first_name = "first"
+ last_name = "last"
+ custom_roles = [launchdarkly_custom_role.test.key]
+ role_attributes {
+ key = "newAttribute"
+ values = ["value1", "value2"]
+ }
+ role_attributes {
+ key = "testAttribute"
+ values = ["staging"]
+ }
+}
+`
+ testAccTeamMemberCustomRoleWithRoleAttributesRemove = `
+resource "launchdarkly_custom_role" "test" {
+ key = "%s"
+ name = "Updated - %s"
+ description= "Allow all actions on testAttribute environments"
+ policy_statements {
+ actions = ["*"]
+ effect = "allow"
+ resources = ["proj/*:env/$${roleAttribute/testAttribute}"]
+ }
+}
+
+resource "launchdarkly_team_member" "custom_role_test" {
+ email = "%s+wbteste2e@launchdarkly.com"
+ first_name = "first"
+ last_name = "last"
+ custom_roles = [launchdarkly_custom_role.test.key]
}
`
)
-func TestAccTeamMember_CreateGeneric(t *testing.T) {
+func TestAccTeamMember_CreateAndUpdateGeneric(t *testing.T) {
randomName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
resourceName := "launchdarkly_team_member.test"
resource.ParallelTest(t, resource.TestCase{
@@ -105,30 +178,6 @@ func TestAccTeamMember_CreateGeneric(t *testing.T) {
ImportState: true,
ImportStateVerify: true,
},
- },
- })
-}
-
-func TestAccTeamMember_UpdateGeneric(t *testing.T) {
- randomName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
- resourceName := "launchdarkly_team_member.test"
- resource.ParallelTest(t, resource.TestCase{
- PreCheck: func() {
- testAccPreCheck(t)
- },
- Providers: testAccProviders,
- Steps: []resource.TestStep{
- {
- Config: fmt.Sprintf(testAccTeamMemberCreate, randomName),
- Check: resource.ComposeTestCheckFunc(
- testAccCheckMemberExists(resourceName),
- resource.TestCheckResourceAttr(resourceName, EMAIL, fmt.Sprintf("%s+wbteste2e@launchdarkly.com", randomName)),
- resource.TestCheckResourceAttr(resourceName, FIRST_NAME, "first"),
- resource.TestCheckResourceAttr(resourceName, LAST_NAME, "last"),
- resource.TestCheckResourceAttr(resourceName, ROLE, "admin"),
- resource.TestCheckResourceAttr(resourceName, "custom_roles.#", "0"),
- ),
- },
{
Config: fmt.Sprintf(testAccTeamMemberUpdate, randomName),
Check: resource.ComposeTestCheckFunc(
@@ -140,6 +189,11 @@ func TestAccTeamMember_UpdateGeneric(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "custom_roles.#", "0"),
),
},
+ {
+ ResourceName: resourceName,
+ ImportState: true,
+ ImportStateVerify: true,
+ },
},
})
}
@@ -183,7 +237,84 @@ func TestAccTeamMember_WithCustomRole(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, FIRST_NAME, "first"),
resource.TestCheckResourceAttr(resourceName, LAST_NAME, "last"),
resource.TestCheckResourceAttr(resourceName, "custom_roles.#", "1"),
- resource.TestCheckResourceAttr(resourceName, "custom_roles.0", roleKey2),
+ resource.TestCheckResourceAttr(resourceName, "custom_roles.0", roleKey1),
+ ),
+ },
+ {
+ ResourceName: resourceName,
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ {
+ // delete launchdarkly_custom_role.test_2, udpate launchdarkly_custom_role.test with role attributes
+ // and add role attribute values to the team member
+ Config: fmt.Sprintf(testAccTeamMemberCustomRoleWithRoleAttributes, roleKey1, roleKey1, randomName),
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckCustomRoleExists(roleResourceName1),
+ testAccCheckMemberExists(resourceName),
+ resource.TestCheckResourceAttr(resourceName, EMAIL, fmt.Sprintf("%s+wbteste2e@launchdarkly.com", randomName)),
+ resource.TestCheckResourceAttr(resourceName, FIRST_NAME, "first"),
+ resource.TestCheckResourceAttr(resourceName, LAST_NAME, "last"),
+ resource.TestCheckResourceAttr(resourceName, "custom_roles.#", "1"),
+ resource.TestCheckResourceAttr(resourceName, "custom_roles.0", roleKey1),
+
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.#", "2"),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.1.key", "testAttribute"),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.1.values.#", "2"),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.1.values.0", "staging"),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.1.values.1", "production"),
+ // we allow the setting of role attributes to be set even if they do not otherwise exist
+ // on a custom role
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.0.key", "nonexistentAttribute"),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.0.values.#", "1"),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.0.values.0", "someValue"),
+ ),
+ },
+ {
+ ResourceName: resourceName,
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ {
+ // remove the nonexistentAttribute block, reorder testAttribute block and add a newAttribute block,
+ // and remove the production value from the testAttribute block
+ Config: fmt.Sprintf(testAccTeamMemberCustomRoleWithRoleAttributesUpdate, roleKey1, roleKey1, randomName),
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckCustomRoleExists(roleResourceName1),
+ testAccCheckMemberExists(resourceName),
+ resource.TestCheckResourceAttr(resourceName, EMAIL, fmt.Sprintf("%s+wbteste2e@launchdarkly.com", randomName)),
+ resource.TestCheckResourceAttr(resourceName, FIRST_NAME, "first"),
+ resource.TestCheckResourceAttr(resourceName, LAST_NAME, "last"),
+ resource.TestCheckResourceAttr(resourceName, "custom_roles.#", "1"),
+ resource.TestCheckResourceAttr(resourceName, "custom_roles.0", roleKey1),
+
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.#", "2"),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.1.key", "testAttribute"),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.1.values.#", "1"),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.1.values.0", "staging"),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.0.key", "newAttribute"),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.0.values.#", "2"),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.0.values.0", "value1"),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.0.values.1", "value2"),
+ ),
+ },
+ {
+ ResourceName: resourceName,
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ {
+ // remove role attributes from the team member
+ Config: fmt.Sprintf(testAccTeamMemberCustomRoleWithRoleAttributesRemove, roleKey1, roleKey1, randomName),
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckCustomRoleExists(roleResourceName1),
+ testAccCheckMemberExists(resourceName),
+ resource.TestCheckResourceAttr(resourceName, EMAIL, fmt.Sprintf("%s+wbteste2e@launchdarkly.com", randomName)),
+ resource.TestCheckResourceAttr(resourceName, FIRST_NAME, "first"),
+ resource.TestCheckResourceAttr(resourceName, LAST_NAME, "last"),
+ resource.TestCheckResourceAttr(resourceName, "custom_roles.#", "1"),
+ resource.TestCheckResourceAttr(resourceName, "custom_roles.0", roleKey1),
+ resource.TestCheckResourceAttr(resourceName, "role_attributes.#", "0"),
),
},
{
diff --git a/launchdarkly/role_attributes_helper.go b/launchdarkly/role_attributes_helper.go
new file mode 100644
index 00000000..5e5e0c0b
--- /dev/null
+++ b/launchdarkly/role_attributes_helper.go
@@ -0,0 +1,80 @@
+package launchdarkly
+
+import (
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ ldapi "github.com/launchdarkly/api-client-go/v17"
+)
+
+func roleAttributesSchema(isDataSource bool) *schema.Schema {
+ return &schema.Schema{
+ Type: schema.TypeSet,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ KEY: {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The key / name of your role attribute. In the example `$${roleAttribute/testAttribute}`, the key is `testAttribute`.",
+ },
+ VALUES: {
+ Type: schema.TypeList,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Required: true,
+ Description: "A list of values for your role attribute. For example, if your policy statement defines the resource `\"proj/$${roleAttribute/testAttribute}\"`, the values would be the keys of the projects you wanted to assign access to.",
+ },
+ },
+ },
+ Optional: true,
+ Computed: isDataSource,
+ Description: "A role attributes block. One block must be defined per role attribute. The key is the role attribute key and the value is a string array of resource keys that apply.",
+ }
+}
+
+func roleAttributesFromResourceData(rawRoleAttributes []interface{}) *map[string][]string {
+ if len(rawRoleAttributes) == 0 {
+ return nil
+ }
+ roleAttributes := make(map[string][]string)
+ for _, attribute := range rawRoleAttributes {
+ roleAttribute := attribute.(map[string]interface{})
+ key := roleAttribute[KEY].(string)
+ rawValues := roleAttribute[VALUES].([]interface{})
+ roleAttributes[key] = make([]string, 0, len(rawValues))
+ for _, v := range rawValues {
+ roleAttributes[key] = append(roleAttributes[key], v.(string))
+ }
+ }
+ return &roleAttributes
+}
+
+func roleAttributesToResourceData(roleAttributes *map[string][]string) *[]interface{} {
+ if roleAttributes == nil {
+ return nil
+ }
+ resourceData := make([]interface{}, 0, len(*roleAttributes))
+ for key, values := range *roleAttributes {
+ rawValues := make([]interface{}, 0, len(values))
+ for _, v := range values {
+ rawValues = append(rawValues, v)
+ }
+ resourceData = append(resourceData, map[string]interface{}{
+ KEY: key,
+ VALUES: rawValues,
+ })
+ }
+ return &resourceData
+}
+
+func getRoleAttributePatches(d *schema.ResourceData) []ldapi.PatchOperation {
+ var patch []ldapi.PatchOperation
+ if o, n := d.GetChange(ROLE_ATTRIBUTES); o != n {
+ new := roleAttributesFromResourceData(d.Get(ROLE_ATTRIBUTES).(*schema.Set).List())
+ if new != nil {
+ patch = append(patch, patchReplace("/roleAttributes", new))
+ } else {
+ patch = append(patch, patchReplace("/roleAttributes", make(map[string][]string)))
+ }
+ }
+ return patch
+}