-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DXCDT-188: Assigning org members via
auth0_organization_member
reso…
…urce (#256) * Adding auth0_organization_member resource along with tests * Adding temporary code to get tests to pass * Adding comments to testing workarounds * Removing placeholder configuration, adding comments to test cases * Adding recordings * Removing reference of member ID in favor of technically more correct user ID * Removing stale TODO * Adding stronger test assertions * Setting unique ID for member * Update auth0/resource_auth0_organization_member.go Co-authored-by: Sergiu Ghitea <[email protected]> * Update auth0/resource_auth0_organization_member.go Co-authored-by: Sergiu Ghitea <[email protected]> * Update auth0/resource_auth0_organization_member.go Co-authored-by: Sergiu Ghitea <[email protected]> * Update auth0/resource_auth0_organization_member.go Co-authored-by: Sergiu Ghitea <[email protected]> * Update auth0/resource_auth0_organization_member.go Co-authored-by: Sergiu Ghitea <[email protected]> * Update auth0/resource_auth0_organization_member.go Co-authored-by: Sergiu Ghitea <[email protected]> * Splitting functions * Adding check for 404 * Adding example * Adding missing comma * Adding corrected Get functions * Refining HTTP not found checks, removing sweepers * Allowing any order for test results * Chaining roles for testing predictability Co-authored-by: Will Vedder <[email protected]> Co-authored-by: Sergiu Ghitea <[email protected]>
- Loading branch information
1 parent
e9d3fb5
commit cad77de
Showing
6 changed files
with
2,425 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
package auth0 | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
|
||
"github.com/auth0/go-auth0/management" | ||
"github.com/hashicorp/go-multierror" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
) | ||
|
||
func newOrganizationMember() *schema.Resource { | ||
return &schema.Resource{ | ||
Description: "This resource is used to manage the assignment of members and their roles within an organization.", | ||
CreateContext: createOrganizationMember, | ||
ReadContext: readOrganizationMember, | ||
UpdateContext: updateOrganizationMember, | ||
DeleteContext: deleteOrganizationMember, | ||
Importer: &schema.ResourceImporter{ | ||
StateContext: schema.ImportStatePassthroughContext, | ||
}, | ||
Schema: map[string]*schema.Schema{ | ||
"organization_id": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Description: "The ID of the organization to assign the member to.", | ||
}, | ||
"user_id": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Description: "ID of the user to add as an organization member.", | ||
}, | ||
"roles": { | ||
Type: schema.TypeSet, | ||
Elem: &schema.Schema{Type: schema.TypeString}, | ||
Description: "The role ID(s) to assign to the organization member.", | ||
Optional: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func createOrganizationMember(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
userID := d.Get("user_id").(string) | ||
orgID := d.Get("organization_id").(string) | ||
|
||
api := m.(*management.Management) | ||
if err := api.Organization.AddMembers(orgID, []string{userID}); err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
d.SetId(resource.UniqueId()) | ||
|
||
if err := assignRoles(d, m); err != nil { | ||
return diag.FromErr(fmt.Errorf("failed to assign roles to organization member: %w", err)) | ||
} | ||
|
||
return readOrganizationMember(ctx, d, m) | ||
} | ||
|
||
func assignRoles(d *schema.ResourceData, m interface{}) error { | ||
orgID := d.Get("organization_id").(string) | ||
userID := d.Get("user_id").(string) | ||
|
||
add, rm := Diff(d, "roles") | ||
|
||
err := addMemberRoles(orgID, userID, add.List(), m) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = removeMemberRoles(orgID, userID, rm.List(), m) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func removeMemberRoles(orgID string, userID string, roles []interface{}, m interface{}) error { | ||
api := m.(*management.Management) | ||
|
||
rolesToRemove := []string{} | ||
for _, r := range roles { | ||
rolesToRemove = append(rolesToRemove, r.(string)) | ||
} | ||
if len(rolesToRemove) == 0 { | ||
return nil | ||
} | ||
|
||
err := api.Organization.DeleteMemberRoles(orgID, userID, rolesToRemove) | ||
if err != nil { | ||
// Ignore 404 errors as the role may have been deleted | ||
// prior to un-assigning them from the member. | ||
if err, ok := err.(management.Error); ok && err.Status() == http.StatusNotFound { | ||
return nil | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func addMemberRoles(orgID string, userID string, roles []interface{}, m interface{}) error { | ||
api := m.(*management.Management) | ||
|
||
rolesToAssign := []string{} | ||
for _, r := range roles { | ||
rolesToAssign = append(rolesToAssign, r.(string)) | ||
} | ||
if len(rolesToAssign) == 0 { | ||
return nil | ||
} | ||
|
||
err := api.Organization.AssignMemberRoles(orgID, userID, rolesToAssign) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func readOrganizationMember(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
api := m.(*management.Management) | ||
|
||
orgID := d.Get("organization_id").(string) | ||
userID := d.Get("user_id").(string) | ||
|
||
roles, err := api.Organization.MemberRoles(orgID, userID) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
rolesToSet := []interface{}{} | ||
for _, role := range roles.Roles { | ||
rolesToSet = append(rolesToSet, role.ID) | ||
} | ||
|
||
result := multierror.Append( | ||
d.Set("roles", rolesToSet), | ||
) | ||
|
||
return diag.FromErr(result.ErrorOrNil()) | ||
} | ||
|
||
func updateOrganizationMember(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
if err := assignRoles(d, m); err != nil { | ||
return diag.FromErr(fmt.Errorf("failed to assign members to organization. %w", err)) | ||
} | ||
|
||
return readOrganizationMember(ctx, d, m) | ||
} | ||
|
||
func deleteOrganizationMember(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
api := m.(*management.Management) | ||
|
||
orgID := d.Get("organization_id").(string) | ||
userID := d.Get("user_id").(string) | ||
|
||
if err := api.Organization.DeleteMember(orgID, []string{userID}); err != nil { | ||
if err, ok := err.(management.Error); ok && err.Status() == http.StatusNotFound { | ||
return nil | ||
} | ||
return diag.FromErr(err) | ||
} | ||
|
||
d.SetId("") | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package auth0 | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
|
||
"github.com/auth0/terraform-provider-auth0/auth0/internal/template" | ||
) | ||
|
||
func TestAccOrganizationMember(t *testing.T) { | ||
httpRecorder := configureHTTPRecorder(t) | ||
|
||
testName := strings.ToLower(t.Name()) | ||
|
||
resource.Test(t, resource.TestCase{ | ||
ProviderFactories: testProviders(httpRecorder), | ||
Steps: []resource.TestStep{{ | ||
Config: template.ParseTestName(testAccOrganizationMembersAux+` | ||
resource auth0_organization_member test_member { | ||
depends_on = [ auth0_user.user, auth0_organization.some_org ] | ||
organization_id = auth0_organization.some_org.id | ||
user_id = auth0_user.user.id | ||
} | ||
`, testName), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr("auth0_organization_member.test_member", "roles.#", "0"), | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "organization_id", "auth0_organization.some_org", "id"), | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "user_id", "auth0_user.user", "id"), | ||
), | ||
}, | ||
{ | ||
Config: template.ParseTestName(testAccOrganizationMembersAux+` | ||
resource auth0_organization_member test_member { | ||
depends_on = [ auth0_user.user, auth0_organization.some_org, auth0_role.reader ] | ||
organization_id = auth0_organization.some_org.id | ||
user_id = auth0_user.user.id | ||
roles = [ auth0_role.reader.id ] // Adding role | ||
} | ||
`, testName), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "organization_id", "auth0_organization.some_org", "id"), | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "user_id", "auth0_user.user", "id"), | ||
resource.TestCheckResourceAttr("auth0_organization_member.test_member", "roles.#", "1"), | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "roles.*", "auth0_role.reader", "id"), | ||
), | ||
}, | ||
{ | ||
Config: template.ParseTestName(testAccOrganizationMembersAux+` | ||
resource auth0_organization_member test_member { | ||
depends_on = [ auth0_user.user, auth0_organization.some_org, auth0_role.reader, auth0_role.admin ] | ||
organization_id = auth0_organization.some_org.id | ||
user_id = auth0_user.user.id | ||
roles = [ auth0_role.reader.id, auth0_role.admin.id ] // Adding an additional role | ||
} | ||
`, testName), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "organization_id", "auth0_organization.some_org", "id"), | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "user_id", "auth0_user.user", "id"), | ||
resource.TestCheckResourceAttr("auth0_organization_member.test_member", "roles.#", "2"), | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "roles.*", "auth0_role.reader", "id"), | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "roles.*", "auth0_role.admin", "id"), | ||
), | ||
}, | ||
{ | ||
Config: template.ParseTestName(testAccOrganizationMembersAux+` | ||
resource auth0_organization_member test_member { | ||
depends_on = [ auth0_user.user, auth0_organization.some_org, auth0_role.reader, auth0_role.admin ] | ||
organization_id = auth0_organization.some_org.id | ||
user_id = auth0_user.user.id | ||
roles = [ auth0_role.admin.id ] // Removing a role | ||
} | ||
`, testName), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "organization_id", "auth0_organization.some_org", "id"), | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "user_id", "auth0_user.user", "id"), | ||
resource.TestCheckResourceAttr("auth0_organization_member.test_member", "roles.#", "1"), | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "roles.*", "auth0_role.admin", "id"), | ||
), | ||
}, | ||
{ | ||
Config: template.ParseTestName(testAccOrganizationMembersAux+ | ||
` | ||
resource auth0_organization_member test_member { | ||
depends_on = [ auth0_user.user, auth0_organization.some_org, auth0_role.reader, auth0_role.admin ] | ||
organization_id = auth0_organization.some_org.id | ||
user_id = auth0_user.user.id | ||
// Removing roles entirely | ||
}`, testName), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr("auth0_organization_member.test_member", "roles.#", "0"), | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "organization_id", "auth0_organization.some_org", "id"), | ||
resource.TestCheckTypeSetElemAttrPair("auth0_organization_member.test_member", "user_id", "auth0_user.user", "id"), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
const testAccOrganizationMembersAux = ` | ||
resource auth0_role reader { | ||
name = "Reader - {{.testName}}" | ||
} | ||
resource auth0_role admin { | ||
depends_on = [ auth0_role.reader ] | ||
name = "Admin - {{.testName}}" | ||
} | ||
resource auth0_user user { | ||
email = "{{.testName}}@auth0.com" | ||
connection_name = "Username-Password-Authentication" | ||
email_verified = true | ||
password = "MyPass123$" | ||
} | ||
resource auth0_organization some_org{ | ||
name = "some-org-{{.testName}}" | ||
display_name = "{{.testName}}" | ||
} | ||
` |
Oops, something went wrong.