diff --git a/.changelog/567.txt b/.changelog/567.txt new file mode 100644 index 000000000..00e9d1e2a --- /dev/null +++ b/.changelog/567.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/harness_platform_usergroup - ignore the order of users and user_emails when doing CRUD. +``` diff --git a/internal/service/platform/usergroup/usergroup.go b/internal/service/platform/usergroup/usergroup.go index 899dd9363..0817d9e03 100644 --- a/internal/service/platform/usergroup/usergroup.go +++ b/internal/service/platform/usergroup/usergroup.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "net/http" + "reflect" + "sort" "github.com/harness/harness-go-sdk/harness/nextgen" "github.com/harness/terraform-provider-harness/helpers" @@ -167,12 +169,11 @@ func resourceUserGroupRead(ctx context.Context, d *schema.ResourceData, meta int return helpers.HandleReadApiError(err, d, httpResp) } - if attr, ok := d.GetOk("user_emails"); ok { d.Set("user_emails", attr) } - if _, ok := d.GetOk("users"); ok { - d.Set("users", resp.Data.Users) + if attr, ok := d.GetOk("users"); ok { + d.Set("users", attr) } readUserGroup(d, resp.Data) @@ -209,6 +210,10 @@ func resourceUserGroupCreateOrUpdate(ctx context.Context, d *schema.ResourceData readUserGroup(d, resp.Data) + if resp.Data.Users != nil { + d.Set("users", ignoreOrderIfAllElementsMatch(ug.Users, resp.Data.Users)) + } + return nil } @@ -236,7 +241,7 @@ func resourceUserGroupCreateOrUpdate(ctx context.Context, d *schema.ResourceData return helpers.HandleApiError(err, d, httpResp) } - readUserGroupV2(d, resp.Data) + readUserGroupV2(d, resp.Data, ug.Users) return nil } @@ -396,7 +401,22 @@ func buildUserGroupV2(d *schema.ResourceData) nextgen.UserGroupRequestV2 { return *userGroup } -func readUserGroupV2(d *schema.ResourceData, env *nextgen.UserGroupResponseV2) { +func ignoreOrderIfAllElementsMatch(input []string, output []string) []string { + sorted_input := make([]string, len(input)) + copy(sorted_input, input[:]) + sort.Strings(sorted_input) + + sorted_output := make([]string, len(output)) + copy(sorted_output, output[:]) + sort.Strings(sorted_output) + + if reflect.DeepEqual(sorted_input, sorted_output) { + return input + } + return output +} + +func readUserGroupV2(d *schema.ResourceData, env *nextgen.UserGroupResponseV2, user_emails []string) { d.SetId(env.Identifier) d.Set("identifier", env.Identifier) d.Set("org_id", env.OrgIdentifier) @@ -404,7 +424,7 @@ func readUserGroupV2(d *schema.ResourceData, env *nextgen.UserGroupResponseV2) { d.Set("name", env.Name) d.Set("description", env.Description) d.Set("tags", helpers.FlattenTags(env.Tags)) - d.Set("user_emails", flattenUserInfo(env.Users)) + d.Set("user_emails", ignoreOrderIfAllElementsMatch(user_emails, flattenUserInfo(env.Users))) d.Set("notification_configs", flattenNotificationConfig(env.NotificationConfigs)) d.Set("linked_sso_id", env.LinkedSsoId) d.Set("linked_sso_display_name", env.LinkedSsoDisplayName) diff --git a/internal/service/platform/usergroup/usergroup_test.go b/internal/service/platform/usergroup/usergroup_test.go index 1edc13ed2..65b95c9c5 100644 --- a/internal/service/platform/usergroup/usergroup_test.go +++ b/internal/service/platform/usergroup/usergroup_test.go @@ -142,8 +142,11 @@ func TestAccResourceUserGroup_emails(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "id", id), resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "user_emails.#", "1"), + resource.TestCheckResourceAttr(resourceName, "user_emails.#", "4"), resource.TestCheckResourceAttr(resourceName, "user_emails.0", "rathod.meetsatish@harness.io"), + resource.TestCheckResourceAttr(resourceName, "user_emails.1", "vikas.maddukuri@harness.io"), + resource.TestCheckResourceAttr(resourceName, "user_emails.2", "arkajyoti.mukherjee@harness.io"), + resource.TestCheckResourceAttr(resourceName, "user_emails.3", "mankrit.singh@harness.io"), ), }, { @@ -151,8 +154,11 @@ func TestAccResourceUserGroup_emails(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "id", id), resource.TestCheckResourceAttr(resourceName, "name", updatedName), - resource.TestCheckResourceAttr(resourceName, "user_emails.#", "1"), + resource.TestCheckResourceAttr(resourceName, "user_emails.#", "4"), resource.TestCheckResourceAttr(resourceName, "user_emails.0", "rathod.meetsatish@harness.io"), + resource.TestCheckResourceAttr(resourceName, "user_emails.1", "vikas.maddukuri@harness.io"), + resource.TestCheckResourceAttr(resourceName, "user_emails.2", "arkajyoti.mukherjee@harness.io"), + resource.TestCheckResourceAttr(resourceName, "user_emails.3", "mankrit.singh@harness.io"), ), }, { @@ -166,6 +172,53 @@ func TestAccResourceUserGroup_emails(t *testing.T) { }) } +func TestAccResourceUserGroup_userIds(t *testing.T) { + + id := fmt.Sprintf("%s_%s", t.Name(), utils.RandStringBytes(5)) + name := id + updatedName := fmt.Sprintf("%s_updated", name) + resourceName := "harness_platform_usergroup.test" + + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccUserGroupDestroy(resourceName), + Steps: []resource.TestStep{ + { + Config: testAccResourceUserGroup_userIds(id, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "id", id), + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "users.#", "4"), + resource.TestCheckResourceAttr(resourceName, "users.0", "FZ-_NefESDmVvjrhu53MWQ"), + resource.TestCheckResourceAttr(resourceName, "users.1", "TRqwkV-jSvyPdW-4C1c3eg"), + resource.TestCheckResourceAttr(resourceName, "users.2", "0qBvYLghQqCnY9RrmuLJdg"), + resource.TestCheckResourceAttr(resourceName, "users.3", "4PuRra9dTOCbT7RnG3-PRw"), + ), + }, + { + Config: testAccResourceUserGroup_userIds(id, updatedName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "id", id), + resource.TestCheckResourceAttr(resourceName, "name", updatedName), + resource.TestCheckResourceAttr(resourceName, "users.#", "4"), + resource.TestCheckResourceAttr(resourceName, "users.0", "FZ-_NefESDmVvjrhu53MWQ"), + resource.TestCheckResourceAttr(resourceName, "users.1", "TRqwkV-jSvyPdW-4C1c3eg"), + resource.TestCheckResourceAttr(resourceName, "users.2", "0qBvYLghQqCnY9RrmuLJdg"), + resource.TestCheckResourceAttr(resourceName, "users.3", "4PuRra9dTOCbT7RnG3-PRw"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: acctest.ProjectResourceImportStateIdFunc(resourceName), + ImportStateVerifyIgnore: []string{"users"}, + }, + }, + }) +} + func TestProjectResourceUserGroup_emails(t *testing.T) { id := fmt.Sprintf("%s_%s", t.Name(), utils.RandStringBytes(5)) @@ -447,7 +500,41 @@ func testAccResourceUserGroup_emails(id string, name string) string { name = "%[2]s" linked_sso_id = "linked_sso_id" externally_managed = false - user_emails = ["rathod.meetsatish@harness.io"] + user_emails = ["rathod.meetsatish@harness.io", "vikas.maddukuri@harness.io", "arkajyoti.mukherjee@harness.io", "mankrit.singh@harness.io"] + notification_configs { + type = "SLACK" + slack_webhook_url = "https://google.com" + } + notification_configs { + type = "EMAIL" + group_email = "email@email.com" + send_email_to_all_users = true + } + notification_configs { + type = "MSTEAMS" + microsoft_teams_webhook_url = "https://google.com" + } + notification_configs { + type = "PAGERDUTY" + pager_duty_key = "pagerDutyKey" + } + linked_sso_display_name = "linked_sso_display_name" + sso_group_id = "sso_group_id" + sso_group_name = "sso_group_name" + linked_sso_type = "SAML" + sso_linked = true + } +`, id, name) +} + +func testAccResourceUserGroup_userIds(id string, name string) string { + return fmt.Sprintf(` + resource "harness_platform_usergroup" "test" { + identifier = "%[1]s" + name = "%[2]s" + linked_sso_id = "linked_sso_id" + externally_managed = false + users = ["FZ-_NefESDmVvjrhu53MWQ", "TRqwkV-jSvyPdW-4C1c3eg", "0qBvYLghQqCnY9RrmuLJdg", "4PuRra9dTOCbT7RnG3-PRw"] notification_configs { type = "SLACK" slack_webhook_url = "https://google.com"