diff --git a/docs/resources/idp_group_mapping.md b/docs/resources/idp_group_mapping.md index f5a9b8a0..58deb292 100644 --- a/docs/resources/idp_group_mapping.md +++ b/docs/resources/idp_group_mapping.md @@ -28,7 +28,7 @@ resource "spacelift_idp_group_mapping" "test" { ### Required - `name` (String) Name of the user group - should be unique in one account -- `policy` (Block List, Min: 1) (see [below for nested schema](#nestedblock--policy)) +- `policy` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--policy)) ### Read-Only diff --git a/docs/resources/user.md b/docs/resources/user.md index e08aa9db..ee8f862f 100644 --- a/docs/resources/user.md +++ b/docs/resources/user.md @@ -17,7 +17,7 @@ description: |- ### Required -- `policy` (Block List, Min: 1) (see [below for nested schema](#nestedblock--policy)) +- `policy` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--policy)) - `username` (String) Username of the user ### Optional diff --git a/spacelift/resource_idp_group_mapping.go b/spacelift/resource_idp_group_mapping.go index 9d72226f..af0f7bb8 100644 --- a/spacelift/resource_idp_group_mapping.go +++ b/spacelift/resource_idp_group_mapping.go @@ -43,7 +43,7 @@ func resourceIdpGroupMapping() *schema.Resource { ValidateDiagFunc: validations.DisallowEmptyString, }, "policy": { - Type: schema.TypeList, + Type: schema.TypeSet, MinItems: 1, Required: true, Elem: &schema.Resource{ @@ -63,6 +63,7 @@ func resourceIdpGroupMapping() *schema.Resource { }, }, }, + Set: userPolicyHash, }, }, } @@ -163,12 +164,15 @@ func resourceIdpGroupMappingDelete(ctx context.Context, d *schema.ResourceData, func getAccessRules(d *schema.ResourceData) []structs.SpaceAccessRuleInput { var accessRules []structs.SpaceAccessRuleInput - for _, a := range d.Get("policy").([]interface{}) { - access := a.(map[string]interface{}) - accessRules = append(accessRules, structs.SpaceAccessRuleInput{ - Space: toID(access["space_id"]), - SpaceAccessLevel: structs.SpaceAccessLevel(access["role"].(string)), - }) + if policies, ok := d.Get("policy").(*schema.Set); ok { + for _, a := range policies.List() { + access := a.(map[string]interface{}) + accessRules = append(accessRules, structs.SpaceAccessRuleInput{ + Space: toID(access["space_id"]), + SpaceAccessLevel: structs.SpaceAccessLevel(access["role"].(string)), + }) + } } + return accessRules } diff --git a/spacelift/resource_user.go b/spacelift/resource_user.go index b83d1004..9a39d051 100644 --- a/spacelift/resource_user.go +++ b/spacelift/resource_user.go @@ -35,7 +35,7 @@ func resourceUser() *schema.Resource { Description: "Username of the user", }, "policy": { - Type: schema.TypeList, + Type: schema.TypeSet, MinItems: 1, Required: true, Elem: &schema.Resource{ @@ -55,6 +55,7 @@ func resourceUser() *schema.Resource { }, }, }, + Set: userPolicyHash, }, "invitation_email": { Type: schema.TypeString, @@ -65,6 +66,19 @@ func resourceUser() *schema.Resource { } } +func userPolicyHash(v interface{}) int { + m, ok := v.(map[string]interface{}) + if !ok { + return 0 + } + + spaceID, _ := m["space_id"].(string) + role, _ := m["role"].(string) + + key := spaceID + "-" + role + return schema.HashString(key) +} + func resourceUserCreate(ctx context.Context, d *schema.ResourceData, i interface{}) diag.Diagnostics { // send an Invite (create) mutation to the API var mutation struct { diff --git a/spacelift/resource_user_test.go b/spacelift/resource_user_test.go index 020d91b6..dc7b5220 100644 --- a/spacelift/resource_user_test.go +++ b/spacelift/resource_user_test.go @@ -37,6 +37,21 @@ resource "spacelift_user" "test" { } ` +var userWithTwoAccessesDifferentOrder = ` +resource "spacelift_user" "test" { + invitation_email = "%s" + username = "%s" + policy { + space_id = "legacy" + role = "READ" + } + policy { + space_id = "root" + role = "ADMIN" + } +} +` + func TestUserResource(t *testing.T) { const resourceName = "spacelift_user.test" @@ -157,4 +172,30 @@ func TestUserResource(t *testing.T) { }) }) + t.Run("can change policy order without update", func(t *testing.T) { + randomUsername := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + exampleEmail := fmt.Sprintf("%s@example.com", randomUsername) + + testSteps(t, []resource.TestStep{ + { + Config: fmt.Sprintf(userWithTwoAccesses, exampleEmail, randomUsername), + Check: Resource( + resourceName, + Attribute("invitation_email", Equals(exampleEmail)), + Attribute("username", Equals(randomUsername)), + SetContains("policy", "root", "ADMIN"), + SetContains("policy", "legacy", "READ")), + }, + { + Config: fmt.Sprintf(userWithTwoAccessesDifferentOrder, exampleEmail, randomUsername), + Check: Resource( + resourceName, + Attribute("invitation_email", Equals(exampleEmail)), + Attribute("username", Equals(randomUsername)), + SetContains("policy", "root", "ADMIN"), + SetContains("policy", "legacy", "READ")), + }, + }) + + }) }