diff --git a/mongodbatlas/data_source_mongodbatlas_org_invitation.go b/mongodbatlas/data_source_mongodbatlas_org_invitation.go new file mode 100644 index 0000000000..14b015d052 --- /dev/null +++ b/mongodbatlas/data_source_mongodbatlas_org_invitation.go @@ -0,0 +1,108 @@ +package mongodbatlas + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceMongoDBAtlasOrgInvitation() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceMongoDBAtlasOrgInvitationRead, + Schema: map[string]*schema.Schema{ + "org_id": { + Type: schema.TypeString, + Required: true, + }, + "username": { + Type: schema.TypeString, + Required: true, + }, + "invitation_id": { + Type: schema.TypeString, + Required: true, + }, + "expires_at": { + Type: schema.TypeString, + Computed: true, + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + "inviter_username": { + Type: schema.TypeString, + Computed: true, + }, + "teams_ids": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "roles": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func dataSourceMongoDBAtlasOrgInvitationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + // Get client connection. + conn := meta.(*MongoDBClient).Atlas + orgID := d.Get("org_id").(string) + username := d.Get("username").(string) + invitationID := d.Get("invitation_id").(string) + + orgInvitation, _, err := conn.Organizations.Invitation(ctx, orgID, invitationID) + if err != nil { + return diag.FromErr(fmt.Errorf("error getting Organization Invitation information: %w", err)) + } + + if err := d.Set("username", orgInvitation.Username); err != nil { + return diag.FromErr(fmt.Errorf("error getting `username` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("org_id", orgInvitation.OrgID); err != nil { + return diag.FromErr(fmt.Errorf("error getting `username` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("invitation_id", orgInvitation.ID); err != nil { + return diag.FromErr(fmt.Errorf("error getting `invitation_id` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("expires_at", orgInvitation.ExpiresAt); err != nil { + return diag.FromErr(fmt.Errorf("error getting `expires_at` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("created_at", orgInvitation.CreatedAt); err != nil { + return diag.FromErr(fmt.Errorf("error getting `created_at` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("inviter_username", orgInvitation.InviterUsername); err != nil { + return diag.FromErr(fmt.Errorf("error getting `inviter_username` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("teams_ids", orgInvitation.TeamIDs); err != nil { + return diag.FromErr(fmt.Errorf("error getting `teams_ids` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("roles", orgInvitation.Roles); err != nil { + return diag.FromErr(fmt.Errorf("error getting `roles` for Organization Invitation (%s): %w", d.Id(), err)) + } + + d.SetId(encodeStateID(map[string]string{ + "username": username, + "org_id": orgID, + "invitation_id": invitationID, + })) + + return nil +} diff --git a/mongodbatlas/data_source_mongodbatlas_org_invitation_test.go b/mongodbatlas/data_source_mongodbatlas_org_invitation_test.go new file mode 100644 index 0000000000..754afd3bb1 --- /dev/null +++ b/mongodbatlas/data_source_mongodbatlas_org_invitation_test.go @@ -0,0 +1,55 @@ +package mongodbatlas + +import ( + "fmt" + "os" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceMongoDBAtlasOrgInvitation_basic(t *testing.T) { + var ( + dataSourceName = "mongodbatlas_org_invitation.test" + orgID = os.Getenv("MONGODB_ATLAS_ORG_ID") + name = fmt.Sprintf("test-acc-%s@mongodb.com", acctest.RandString(10)) + initialRole = []string{"ORG_OWNER"} + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckMongoDBAtlasOrgInvitationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceMongoDBAtlasOrgInvitationConfig(orgID, name, initialRole), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceName, "org_id"), + resource.TestCheckResourceAttrSet(dataSourceName, "username"), + resource.TestCheckResourceAttrSet(dataSourceName, "invitation_id"), + resource.TestCheckResourceAttr(dataSourceName, "username", name), + resource.TestCheckResourceAttr(dataSourceName, "roles.#", "1"), + ), + }, + }, + }) +} + +func testAccDataSourceMongoDBAtlasOrgInvitationConfig(orgID, username string, roles []string) string { + return fmt.Sprintf(` + resource "mongodbatlas_org_invitation" "test" { + org_id = %[1]q + username = %[2]q + roles = ["%[3]s"] + } + + data "mongodbatlas_org_invitation" "test" { + org_id = mongodbatlas_org_invitation.test.org_id + username = mongodbatlas_org_invitation.test.username + invitation_id = mongodbatlas_org_invitation.test.invitation_id + }`, orgID, username, + strings.Join(roles, `", "`), + ) +} diff --git a/mongodbatlas/data_source_mongodbatlas_project_invitation.go b/mongodbatlas/data_source_mongodbatlas_project_invitation.go new file mode 100644 index 0000000000..ddde6dce76 --- /dev/null +++ b/mongodbatlas/data_source_mongodbatlas_project_invitation.go @@ -0,0 +1,97 @@ +package mongodbatlas + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceMongoDBAtlasProjectInvitation() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceMongoDBAtlasProjectInvitationRead, + Schema: map[string]*schema.Schema{ + "project_id": { + Type: schema.TypeString, + Required: true, + }, + "username": { + Type: schema.TypeString, + Required: true, + }, + "invitation_id": { + Type: schema.TypeString, + Required: true, + }, + "expires_at": { + Type: schema.TypeString, + Computed: true, + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + "inviter_username": { + Type: schema.TypeString, + Computed: true, + }, + "roles": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func dataSourceMongoDBAtlasProjectInvitationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + // Get client connection. + conn := meta.(*MongoDBClient).Atlas + projectID := d.Get("project_id").(string) + username := d.Get("username").(string) + invitationID := d.Get("invitation_id").(string) + + projectInvitation, _, err := conn.Projects.Invitation(ctx, projectID, invitationID) + if err != nil { + return diag.FromErr(fmt.Errorf("error getting Project Invitation information: %w", err)) + } + + if err := d.Set("username", projectInvitation.Username); err != nil { + return diag.FromErr(fmt.Errorf("error getting `username` for Project Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("project_id", projectInvitation.GroupID); err != nil { + return diag.FromErr(fmt.Errorf("error getting `username` for Project Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("invitation_id", projectInvitation.ID); err != nil { + return diag.FromErr(fmt.Errorf("error getting `invitation_id` for Project Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("expires_at", projectInvitation.ExpiresAt); err != nil { + return diag.FromErr(fmt.Errorf("error getting `expires_at` for Project Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("created_at", projectInvitation.CreatedAt); err != nil { + return diag.FromErr(fmt.Errorf("error getting `created_at` for Project Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("inviter_username", projectInvitation.InviterUsername); err != nil { + return diag.FromErr(fmt.Errorf("error getting `inviter_username` for Project Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("roles", projectInvitation.Roles); err != nil { + return diag.FromErr(fmt.Errorf("error getting `roles` for Project Invitation (%s): %s", d.Id(), err)) + } + + d.SetId(encodeStateID(map[string]string{ + "username": username, + "project_id": projectID, + "invitation_id": invitationID, + })) + + return nil +} diff --git a/mongodbatlas/data_source_mongodbatlas_project_invitation_test.go b/mongodbatlas/data_source_mongodbatlas_project_invitation_test.go new file mode 100644 index 0000000000..1b6eb26409 --- /dev/null +++ b/mongodbatlas/data_source_mongodbatlas_project_invitation_test.go @@ -0,0 +1,55 @@ +package mongodbatlas + +import ( + "fmt" + "os" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceMongoDBAtlasProjectInvitation_basic(t *testing.T) { + var ( + dataSourceName = "mongodbatlas_project_invitation.test" + projectID = os.Getenv("MONGODB_ATLAS_PROJECT_ID") + name = fmt.Sprintf("test-acc-%s@mongodb.com", acctest.RandString(10)) + initialRole = []string{"GROUP_OWNER"} + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckMongoDBAtlasProjectInvitationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceMongoDBAtlasProjectInvitationConfig(projectID, name, initialRole), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceName, "project_id"), + resource.TestCheckResourceAttrSet(dataSourceName, "username"), + resource.TestCheckResourceAttrSet(dataSourceName, "invitation_id"), + resource.TestCheckResourceAttr(dataSourceName, "username", name), + resource.TestCheckResourceAttr(dataSourceName, "roles.#", "1"), + ), + }, + }, + }) +} + +func testAccDataSourceMongoDBAtlasProjectInvitationConfig(projectID, username string, roles []string) string { + return fmt.Sprintf(` + resource "mongodbatlas_project_invitation" "test" { + project_id = %[1]q + username = %[2]q + roles = ["%[3]s"] + } + + data "mongodbatlas_project_invitation" "test" { + project_id = mongodbatlas_project_invitation.test.project_id + username = mongodbatlas_project_invitation.test.username + invitation_id = mongodbatlas_project_invitation.test.invitation_id + }`, projectID, username, + strings.Join(roles, `", "`), + ) +} diff --git a/mongodbatlas/provider.go b/mongodbatlas/provider.go index 8e2871a7a8..5a2bd1454f 100644 --- a/mongodbatlas/provider.go +++ b/mongodbatlas/provider.go @@ -116,6 +116,8 @@ func getDataSourcesMap() map[string]*schema.Resource { "mongodbatlas_data_lakes": dataSourceMongoDBAtlasDataLakes(), "mongodbatlas_event_trigger": dataSourceMongoDBAtlasEventTrigger(), "mongodbatlas_event_triggers": dataSourceMongoDBAtlasEventTriggers(), + "mongodbatlas_project_invitation": dataSourceMongoDBAtlasProjectInvitation(), + "mongodbatlas_org_invitation": dataSourceMongoDBAtlasOrgInvitation(), "mongodbatlas_cloud_backup_snapshot": dataSourceMongoDBAtlasCloudBackupSnapshot(), "mongodbatlas_cloud_backup_snapshots": dataSourceMongoDBAtlasCloudBackupSnapshots(), "mongodbatlas_cloud_backup_snapshot_restore_job": dataSourceMongoDBAtlasCloudBackupSnapshotRestoreJob(), @@ -160,6 +162,8 @@ func getResourcesMap() map[string]*schema.Resource { "mongodbatlas_data_lake": resourceMongoDBAtlasDataLake(), "mongodbatlas_event_trigger": resourceMongoDBAtlasEventTriggers(), "mongodbatlas_cloud_backup_schedule": resourceMongoDBAtlasCloudBackupSchedule(), + "mongodbatlas_project_invitation": resourceMongoDBAtlasProjectInvitation(), + "mongodbatlas_org_invitation": resourceMongoDBAtlasOrgInvitation(), "mongodbatlas_cloud_backup_snapshot": resourceMongoDBAtlasCloudBackupSnapshot(), "mongodbatlas_cloud_backup_snapshot_restore_job": resourceMongoDBAtlasCloudBackupSnapshotRestoreJob(), } diff --git a/mongodbatlas/resource_mongodbatlas_org_invitation.go b/mongodbatlas/resource_mongodbatlas_org_invitation.go new file mode 100644 index 0000000000..c97458325e --- /dev/null +++ b/mongodbatlas/resource_mongodbatlas_org_invitation.go @@ -0,0 +1,247 @@ +package mongodbatlas + +import ( + "context" + "errors" + "fmt" + "regexp" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + matlas "go.mongodb.org/atlas/mongodbatlas" +) + +func resourceMongoDBAtlasOrgInvitation() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceMongoDBAtlasOrgInvitationCreate, + ReadContext: resourceMongoDBAtlasOrgInvitationRead, + DeleteContext: resourceMongoDBAtlasOrgInvitationDelete, + UpdateContext: resourceMongoDBAtlasOrgInvitationUpdate, + Importer: &schema.ResourceImporter{ + StateContext: resourceMongoDBAtlasOrgInvitationImportState, + }, + Schema: map[string]*schema.Schema{ + "org_id": { + Type: schema.TypeString, + Required: true, + }, + "username": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "invitation_id": { + Type: schema.TypeString, + Computed: true, + }, + "expires_at": { + Type: schema.TypeString, + Computed: true, + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + "inviter_username": { + Type: schema.TypeString, + Computed: true, + }, + "teams_ids": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "roles": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "ORG_OWNER", + "ORG_GROUP_CREATOR", + "ORG_BILLING_ADMIN", + "ORG_READ_ONLY", + "ORG_MEMBER", + }, false), + }, + }, + }, + } +} + +func resourceMongoDBAtlasOrgInvitationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + // Get client connection. + conn := meta.(*MongoDBClient).Atlas + ids := decodeStateID(d.Id()) + orgID := ids["org_id"] + username := ids["username"] + invitationID := ids["invitation_id"] + + orgInvitation, _, err := conn.Organizations.Invitation(ctx, orgID, invitationID) + if err != nil { + // case 404 + // deleted in the backend case + var target *matlas.ErrorResponse + if errors.As(err, &target) && target.ErrorCode == "NOT_FOUND" { + d.SetId("") + return nil + } + + return diag.FromErr(fmt.Errorf("error getting Organization Invitation information: %w", err)) + } + + if err := d.Set("username", orgInvitation.Username); err != nil { + return diag.FromErr(fmt.Errorf("error getting `username` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("org_id", orgInvitation.OrgID); err != nil { + return diag.FromErr(fmt.Errorf("error getting `username` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("invitation_id", orgInvitation.ID); err != nil { + return diag.FromErr(fmt.Errorf("error getting `invitation_id` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("expires_at", orgInvitation.ExpiresAt); err != nil { + return diag.FromErr(fmt.Errorf("error getting `expires_at` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("created_at", orgInvitation.CreatedAt); err != nil { + return diag.FromErr(fmt.Errorf("error getting `created_at` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("inviter_username", orgInvitation.InviterUsername); err != nil { + return diag.FromErr(fmt.Errorf("error getting `inviter_username` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("teams_ids", orgInvitation.TeamIDs); err != nil { + return diag.FromErr(fmt.Errorf("error getting `teams_ids` for Organization Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("roles", orgInvitation.Roles); err != nil { + return diag.FromErr(fmt.Errorf("error getting `roles` for Organization Invitation (%s): %w", d.Id(), err)) + } + + d.SetId(encodeStateID(map[string]string{ + "username": username, + "org_id": orgID, + "invitation_id": invitationID, + })) + + return nil +} + +func resourceMongoDBAtlasOrgInvitationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + // Get client connection. + conn := meta.(*MongoDBClient).Atlas + orgID := d.Get("org_id").(string) + + invitationReq := &matlas.Invitation{ + Roles: expandStringListFromSetSchema(d.Get("roles").(*schema.Set)), + TeamIDs: expandStringListFromSetSchema(d.Get("teams_ids").(*schema.Set)), + Username: d.Get("username").(string), + } + + invitationRes, _, err := conn.Organizations.InviteUser(ctx, orgID, invitationReq) + if err != nil { + return diag.FromErr(fmt.Errorf("error creating Organization invitation for user %s: %w", d.Get("username").(string), err)) + } + + d.SetId(encodeStateID(map[string]string{ + "username": invitationRes.Username, + "org_id": invitationRes.OrgID, + "invitation_id": invitationRes.ID, + })) + + return resourceMongoDBAtlasOrgInvitationRead(ctx, d, meta) +} + +func resourceMongoDBAtlasOrgInvitationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*MongoDBClient).Atlas + ids := decodeStateID(d.Id()) + orgID := ids["org_id"] + username := ids["username"] + invitationID := ids["invitation_id"] + + _, err := conn.Organizations.DeleteInvitation(ctx, orgID, invitationID) + if err != nil { + return diag.FromErr(fmt.Errorf("error deleting Organization invitation for user %s: %w", username, err)) + } + + return nil +} + +func resourceMongoDBAtlasOrgInvitationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*MongoDBClient).Atlas + ids := decodeStateID(d.Id()) + orgID := ids["org_id"] + username := ids["username"] + invitationID := ids["invitation_id"] + + invitationReq := &matlas.Invitation{ + Roles: expandStringListFromSetSchema(d.Get("roles").(*schema.Set)), + } + + _, _, err := conn.Organizations.UpdateInvitationByID(ctx, orgID, invitationID, invitationReq) + if err != nil { + return diag.FromErr(fmt.Errorf("error updating Organization invitation for user %s: for %w", username, err)) + } + + return resourceMongoDBAtlasOrgInvitationRead(ctx, d, meta) +} + +func resourceMongoDBAtlasOrgInvitationImportState(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + conn := meta.(*MongoDBClient).Atlas + orgID, username, err := splitOrgInvitationImportID(d.Id()) + if err != nil { + return nil, err + } + + orgInvitations, _, err := conn.Organizations.Invitations(ctx, orgID, nil) + if err != nil { + return nil, fmt.Errorf("couldn't import Organization invitations, error: %w", err) + } + + for _, orgInvitation := range orgInvitations { + if orgInvitation.Username != username { + continue + } + + if err := d.Set("username", orgInvitation.Username); err != nil { + return nil, fmt.Errorf("error getting `username` for Organization Invitation (%s): %w", username, err) + } + if err := d.Set("org_id", orgInvitation.GroupID); err != nil { + return nil, fmt.Errorf("error getting `org_id` for Organization Invitation (%s): %w", username, err) + } + if err := d.Set("invitation_id", orgInvitation.ID); err != nil { + return nil, fmt.Errorf("error getting `invitation_id` for Organization Invitation (%s): %w", username, err) + } + d.SetId(encodeStateID(map[string]string{ + "username": username, + "org_id": orgID, + "invitation_id": orgInvitation.ID, + })) + return []*schema.ResourceData{d}, nil + } + + return nil, fmt.Errorf("could not import Organization Invitation for %s", d.Id()) +} + +func splitOrgInvitationImportID(id string) (orgID, username string, err error) { + var re = regexp.MustCompile(`(?s)^([0-9a-fA-F]{24})-(.*)$`) + parts := re.FindStringSubmatch(id) + + if len(parts) != 3 { + err = fmt.Errorf("import format error: to import a Organization Invitation, use the format {org_id}-{username}") + return + } + + orgID = parts[1] + username = parts[2] + + return +} diff --git a/mongodbatlas/resource_mongodbatlas_org_invitation_test.go b/mongodbatlas/resource_mongodbatlas_org_invitation_test.go new file mode 100644 index 0000000000..a32b8a0e07 --- /dev/null +++ b/mongodbatlas/resource_mongodbatlas_org_invitation_test.go @@ -0,0 +1,194 @@ +package mongodbatlas + +import ( + "context" + "fmt" + "os" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + matlas "go.mongodb.org/atlas/mongodbatlas" +) + +func TestAccResourceMongoDBAtlasOrgInvitation_basic(t *testing.T) { + var ( + invitation matlas.Invitation + resourceName = "mongodbatlas_org_invitation.test" + orgID = os.Getenv("MONGODB_ATLAS_ORG_ID") + name = fmt.Sprintf("test-acc-%s@mongodb.com", acctest.RandString(10)) + initialRole = []string{"ORG_OWNER"} + updateRoles = []string{"ORG_GROUP_CREATOR", "ORG_BILLING_ADMIN"} + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckMongoDBAtlasOrgInvitationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccMongoDBAtlasOrgInvitationConfig(orgID, name, initialRole), + Check: resource.ComposeTestCheckFunc( + testAccCheckMongoDBAtlasOrgInvitationExists(t, resourceName, &invitation), + testAccCheckMongoDBAtlasOrgInvitationUsernameAttribute(&invitation, name), + testAccCheckMongoDBAtlasOrgInvitationRoleAttribute(&invitation, initialRole), + resource.TestCheckResourceAttrSet(resourceName, "org_id"), + resource.TestCheckResourceAttrSet(resourceName, "username"), + resource.TestCheckResourceAttrSet(resourceName, "roles.#"), + resource.TestCheckResourceAttrSet(resourceName, "invitation_id"), + ), + }, + { + Config: testAccMongoDBAtlasOrgInvitationConfig(orgID, name, updateRoles), + Check: resource.ComposeTestCheckFunc( + testAccCheckMongoDBAtlasOrgInvitationExists(t, resourceName, &invitation), + testAccCheckMongoDBAtlasOrgInvitationUsernameAttribute(&invitation, name), + testAccCheckMongoDBAtlasOrgInvitationRoleAttribute(&invitation, updateRoles), + resource.TestCheckResourceAttrSet(resourceName, "username"), + resource.TestCheckResourceAttrSet(resourceName, "invitation_id"), + resource.TestCheckResourceAttr(resourceName, "roles.#", "2"), + ), + }, + }, + }) +} + +func TestAccResourceMongoDBAtlasOrgInvitation_importBasic(t *testing.T) { + var ( + resourceName = "mongodbatlas_org_invitation.test" + orgID = os.Getenv("MONGODB_ATLAS_ORG_ID") + name = fmt.Sprintf("test-acc-%s@mongodb.com", acctest.RandString(10)) + initialRole = []string{"ORG_OWNER"} + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckMongoDBAtlasOrgInvitationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccMongoDBAtlasOrgInvitationConfig(orgID, name, initialRole), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "org_id"), + resource.TestCheckResourceAttrSet(resourceName, "username"), + resource.TestCheckResourceAttrSet(resourceName, "roles.#"), + resource.TestCheckResourceAttrSet(resourceName, "invitation_id"), + + resource.TestCheckResourceAttr(resourceName, "org_id", orgID), + resource.TestCheckResourceAttr(resourceName, "username", name), + resource.TestCheckResourceAttr(resourceName, "roles.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccCheckMongoDBAtlasOrgInvitationStateIDFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckMongoDBAtlasOrgInvitationExists(t *testing.T, resourceName string, invitation *matlas.Invitation) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*MongoDBClient).Atlas + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("not found: %s", resourceName) + } + + ids := decodeStateID(rs.Primary.ID) + + orgID := ids["org_id"] + username := ids["username"] + invitationID := ids["invitation_id"] + + if orgID == "" && username == "" && invitationID == "" { + return fmt.Errorf("no ID is set") + } + + t.Logf("orgID: %s", orgID) + t.Logf("username: %s", username) + t.Logf("invitationID: %s", invitationID) + + invitationResp, _, err := conn.Organizations.Invitation(context.Background(), orgID, invitationID) + if err == nil { + *invitation = *invitationResp + return nil + } + + return fmt.Errorf("invitation(%s) does not exist", invitationID) + } +} + +func testAccCheckMongoDBAtlasOrgInvitationUsernameAttribute(invitation *matlas.Invitation, username string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if invitation.Username != username { + return fmt.Errorf("bad name: %s", invitation.Username) + } + + return nil + } +} + +func testAccCheckMongoDBAtlasOrgInvitationRoleAttribute(invitation *matlas.Invitation, roles []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, role := range roles { + for _, currentRole := range invitation.Roles { + if currentRole == role { + return nil + } + } + } + + return fmt.Errorf("bad role: %s", invitation.Roles) + } +} + +func testAccCheckMongoDBAtlasOrgInvitationDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*MongoDBClient).Atlas + + for _, rs := range s.RootModule().Resources { + if rs.Type != "mongodbatlas_invitations" { + continue + } + + ids := decodeStateID(rs.Primary.ID) + + orgID := ids["org_id"] + invitationID := ids["invitation_id"] + + // Try to find the invitation + _, _, err := conn.Organizations.Invitation(context.Background(), orgID, invitationID) + if err == nil { + return fmt.Errorf("invitation (%s) still exists", invitationID) + } + } + + return nil +} + +func testAccCheckMongoDBAtlasOrgInvitationStateIDFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("not found: %s", resourceName) + } + + return fmt.Sprintf("%s-%s", rs.Primary.Attributes["org_id"], rs.Primary.Attributes["username"]), nil + } +} + +func testAccMongoDBAtlasOrgInvitationConfig(orgID, username string, roles []string) string { + return fmt.Sprintf(` + resource "mongodbatlas_org_invitation" "test" { + org_id = %[1]q + username = %[2]q + roles = ["%[3]s"] + }`, orgID, username, + strings.Join(roles, `", "`), + ) +} diff --git a/mongodbatlas/resource_mongodbatlas_project_invitation.go b/mongodbatlas/resource_mongodbatlas_project_invitation.go new file mode 100644 index 0000000000..ca71023ead --- /dev/null +++ b/mongodbatlas/resource_mongodbatlas_project_invitation.go @@ -0,0 +1,245 @@ +package mongodbatlas + +import ( + "context" + "fmt" + "net/http" + "regexp" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + matlas "go.mongodb.org/atlas/mongodbatlas" +) + +func resourceMongoDBAtlasProjectInvitation() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceMongoDBAtlasProjectInvitationCreate, + ReadContext: resourceMongoDBAtlasProjectInvitationRead, + DeleteContext: resourceMongoDBAtlasProjectInvitationDelete, + UpdateContext: resourceMongoDBAtlasProjectInvitationUpdate, + Importer: &schema.ResourceImporter{ + StateContext: resourceMongoDBAtlasProjectInvitationImportState, + }, + Schema: map[string]*schema.Schema{ + "project_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "username": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "invitation_id": { + Type: schema.TypeString, + Computed: true, + }, + "expires_at": { + Type: schema.TypeString, + Computed: true, + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + "inviter_username": { + Type: schema.TypeString, + Computed: true, + }, + "roles": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "GROUP_OWNER", + "GROUP_CLUSTER_MANAGER", + "GROUP_READ_ONLY", + "GROUP_DATA_ACCESS_ADMIN", + "GROUP_DATA_ACCESS_READ_WRITE", + "GROUP_DATA_ACCESS_READ_ONLY", + }, false), + }, + }, + }, + } +} + +func resourceMongoDBAtlasProjectInvitationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + // Get client connection. + conn := meta.(*MongoDBClient).Atlas + ids := decodeStateID(d.Id()) + projectID := ids["project_id"] + username := ids["username"] + invitationID := ids["invitation_id"] + + projectInvitation, resp, err := conn.Projects.Invitation(ctx, projectID, invitationID) + if err != nil { + // case 404 + // deleted in the backend case + if resp != nil && resp.StatusCode == http.StatusNotFound { + d.SetId("") + return nil + } + + return diag.FromErr(fmt.Errorf("error getting Project Invitation information: %w", err)) + } + + if err := d.Set("username", projectInvitation.Username); err != nil { + return diag.FromErr(fmt.Errorf("error getting `username` for Project Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("project_id", projectInvitation.GroupID); err != nil { + return diag.FromErr(fmt.Errorf("error getting `project_id` for Project Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("invitation_id", projectInvitation.ID); err != nil { + return diag.FromErr(fmt.Errorf("error getting `invitation_id` for Project Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("expires_at", projectInvitation.ExpiresAt); err != nil { + return diag.FromErr(fmt.Errorf("error getting `expires_at` for Project Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("created_at", projectInvitation.CreatedAt); err != nil { + return diag.FromErr(fmt.Errorf("error getting `created_at` for Project Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("inviter_username", projectInvitation.InviterUsername); err != nil { + return diag.FromErr(fmt.Errorf("error getting `inviter_username` for Project Invitation (%s): %w", d.Id(), err)) + } + + if err := d.Set("roles", projectInvitation.Roles); err != nil { + return diag.FromErr(fmt.Errorf("error getting `roles` for Project Invitation (%s): %w", d.Id(), err)) + } + + d.SetId(encodeStateID(map[string]string{ + "username": username, + "project_id": projectID, + "invitation_id": invitationID, + })) + + return nil +} + +func resourceMongoDBAtlasProjectInvitationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + // Get client connection. + conn := meta.(*MongoDBClient).Atlas + projectID := d.Get("project_id").(string) + + invitationReq := &matlas.Invitation{ + Roles: createProjectStringListFromSetSchema(d.Get("roles").(*schema.Set)), + Username: d.Get("username").(string), + } + + invitationRes, _, err := conn.Projects.InviteUser(ctx, projectID, invitationReq) + if err != nil { + return diag.FromErr(fmt.Errorf("error creating Project invitation for user %s: %w", d.Get("username").(string), err)) + } + + d.SetId(encodeStateID(map[string]string{ + "username": invitationRes.Username, + "project_id": invitationRes.GroupID, + "invitation_id": invitationRes.ID, + })) + + return resourceMongoDBAtlasProjectInvitationRead(ctx, d, meta) +} + +func resourceMongoDBAtlasProjectInvitationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*MongoDBClient).Atlas + ids := decodeStateID(d.Id()) + projectID := ids["project_id"] + username := ids["username"] + invitationID := ids["invitation_id"] + + _, err := conn.Projects.DeleteInvitation(ctx, projectID, invitationID) + if err != nil { + return diag.FromErr(fmt.Errorf("error deleting Project invitation for user %s: %w", username, err)) + } + + return nil +} + +func resourceMongoDBAtlasProjectInvitationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*MongoDBClient).Atlas + ids := decodeStateID(d.Id()) + projectID := ids["project_id"] + username := ids["username"] + invitationID := ids["invitation_id"] + + invitationReq := &matlas.Invitation{ + Roles: expandStringListFromSetSchema(d.Get("roles").(*schema.Set)), + } + + _, _, err := conn.Projects.UpdateInvitationByID(ctx, projectID, invitationID, invitationReq) + if err != nil { + return diag.FromErr(fmt.Errorf("error updating Project invitation for user %s: %w", username, err)) + } + + return resourceMongoDBAtlasProjectInvitationRead(ctx, d, meta) +} + +func resourceMongoDBAtlasProjectInvitationImportState(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + conn := meta.(*MongoDBClient).Atlas + projectID, username, err := splitProjectInvitationImportID(d.Id()) + if err != nil { + return nil, err + } + + projectInvitations, _, err := conn.Projects.Invitations(ctx, projectID, nil) + if err != nil { + return nil, fmt.Errorf("couldn't import Project invitations, error: %s", err) + } + + for _, projectInvitation := range projectInvitations { + if projectInvitation.Username != username { + continue + } + + if err := d.Set("username", projectInvitation.Username); err != nil { + return nil, fmt.Errorf("error getting `username` for Project Invitation (%s): %w", username, err) + } + if err := d.Set("project_id", projectInvitation.GroupID); err != nil { + return nil, fmt.Errorf("error getting `project_id` for Project Invitation (%s): %w", username, err) + } + if err := d.Set("invitation_id", projectInvitation.ID); err != nil { + return nil, fmt.Errorf("error getting `invitation_id` for Project Invitation (%s): %w", username, err) + } + d.SetId(encodeStateID(map[string]string{ + "username": username, + "project_id": projectID, + "invitation_id": projectInvitation.ID, + })) + return []*schema.ResourceData{d}, nil + } + + return nil, fmt.Errorf("could not import Project Invitation for %s", d.Id()) +} + +func splitProjectInvitationImportID(id string) (projectID, username string, err error) { + var re = regexp.MustCompile(`(?s)^([0-9a-fA-F]{24})-(.*)$`) + parts := re.FindStringSubmatch(id) + + if len(parts) != 3 { + err = fmt.Errorf("import format error: to import a Project Invitation, use the format {project_id}-{username}") + return + } + + projectID = parts[1] + username = parts[2] + + return +} + +func createProjectStringListFromSetSchema(list *schema.Set) []string { + res := make([]string, list.Len()) + for i, v := range list.List() { + res[i] = v.(string) + } + + return res +} diff --git a/mongodbatlas/resource_mongodbatlas_project_invitation_test.go b/mongodbatlas/resource_mongodbatlas_project_invitation_test.go new file mode 100644 index 0000000000..068d919eff --- /dev/null +++ b/mongodbatlas/resource_mongodbatlas_project_invitation_test.go @@ -0,0 +1,203 @@ +package mongodbatlas + +import ( + "context" + "fmt" + "os" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + matlas "go.mongodb.org/atlas/mongodbatlas" +) + +func TestAccResourceMongoDBAtlasProjectInvitation_basic(t *testing.T) { + var ( + invitation matlas.Invitation + resourceName = "mongodbatlas_project_invitation.test" + projectID = os.Getenv("MONGODB_ATLAS_PROJECT_ID") + name = fmt.Sprintf("test-acc-%s@mongodb.com", acctest.RandString(10)) + initialRole = []string{"GROUP_OWNER"} + updateRoles = []string{"GROUP_DATA_ACCESS_ADMIN", "GROUP_CLUSTER_MANAGER"} + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckMongoDBAtlasProjectInvitationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccMongoDBAtlasProjectInvitationConfig(projectID, name, initialRole), + Check: resource.ComposeTestCheckFunc( + testAccCheckMongoDBAtlasProjectInvitationExists(t, resourceName, &invitation), + testAccCheckMongoDBAtlasProjectInvitationUsernameAttribute(&invitation, name), + testAccCheckMongoDBAtlasProjectInvitationRoleAttribute(&invitation, initialRole), + resource.TestCheckResourceAttrSet(resourceName, "project_id"), + resource.TestCheckResourceAttrSet(resourceName, "username"), + resource.TestCheckResourceAttrSet(resourceName, "invitation_id"), + resource.TestCheckResourceAttrSet(resourceName, "roles.#"), + resource.TestCheckResourceAttr(resourceName, "project_id", projectID), + resource.TestCheckResourceAttr(resourceName, "username", name), + resource.TestCheckResourceAttr(resourceName, "roles.#", "1"), + ), + }, + { + Config: testAccMongoDBAtlasProjectInvitationConfig(projectID, name, updateRoles), + Check: resource.ComposeTestCheckFunc( + testAccCheckMongoDBAtlasProjectInvitationExists(t, resourceName, &invitation), + testAccCheckMongoDBAtlasProjectInvitationUsernameAttribute(&invitation, name), + testAccCheckMongoDBAtlasProjectInvitationRoleAttribute(&invitation, updateRoles), + resource.TestCheckResourceAttrSet(resourceName, "project_id"), + resource.TestCheckResourceAttrSet(resourceName, "username"), + resource.TestCheckResourceAttrSet(resourceName, "invitation_id"), + resource.TestCheckResourceAttrSet(resourceName, "roles.#"), + resource.TestCheckResourceAttr(resourceName, "project_id", projectID), + resource.TestCheckResourceAttr(resourceName, "username", name), + resource.TestCheckResourceAttr(resourceName, "roles.#", "2"), + ), + }, + }, + }) +} + +func TestAccResourceMongoDBAtlasProjectInvitation_importBasic(t *testing.T) { + var ( + resourceName = "mongodbatlas_project_invitation.test" + projectID = os.Getenv("MONGODB_ATLAS_PROJECT_ID") + name = fmt.Sprintf("test-acc-%s@mongodb.com", acctest.RandString(10)) + initialRole = []string{"GROUP_OWNER"} + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckMongoDBAtlasProjectInvitationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccMongoDBAtlasProjectInvitationConfig(projectID, name, initialRole), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "project_id"), + resource.TestCheckResourceAttrSet(resourceName, "username"), + resource.TestCheckResourceAttrSet(resourceName, "roles.#"), + resource.TestCheckResourceAttrSet(resourceName, "invitation_id"), + + resource.TestCheckResourceAttr(resourceName, "project_id", projectID), + resource.TestCheckResourceAttr(resourceName, "username", name), + resource.TestCheckResourceAttr(resourceName, "roles.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccCheckMongoDBAtlasProjectInvitationStateIDFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckMongoDBAtlasProjectInvitationExists(t *testing.T, resourceName string, invitation *matlas.Invitation) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*MongoDBClient).Atlas + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("not found: %s", resourceName) + } + + ids := decodeStateID(rs.Primary.ID) + + projectID := ids["project_id"] + username := ids["username"] + invitationID := ids["invitation_id"] + + if projectID == "" && username == "" && invitationID == "" { + return fmt.Errorf("no ID is set") + } + + t.Logf("projectID: %s", projectID) + t.Logf("username: %s", username) + t.Logf("invitationID: %s", invitationID) + + invitationResp, _, err := conn.Projects.Invitation(context.Background(), projectID, invitationID) + if err == nil { + *invitation = *invitationResp + return nil + } + + return fmt.Errorf("invitation(%s) does not exist", invitationID) + } +} + +func testAccCheckMongoDBAtlasProjectInvitationUsernameAttribute(invitation *matlas.Invitation, username string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if invitation.Username != username { + return fmt.Errorf("bad name: %s", invitation.Username) + } + + return nil + } +} + +func testAccCheckMongoDBAtlasProjectInvitationRoleAttribute(invitation *matlas.Invitation, roles []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(roles) > 0 { + for _, role := range roles { + for _, currentRole := range invitation.Roles { + if currentRole == role { + return nil + } + } + } + } + + return fmt.Errorf("bad role: %s", invitation.Roles) + } +} + +func testAccCheckMongoDBAtlasProjectInvitationDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*MongoDBClient).Atlas + + for _, rs := range s.RootModule().Resources { + if rs.Type != "mongodbatlas_invitations" { + continue + } + + ids := decodeStateID(rs.Primary.ID) + + projectID := ids["project_id"] + invitationID := ids["invitation_id"] + + // Try to find the invitation + _, _, err := conn.Projects.Invitation(context.Background(), projectID, invitationID) + if err == nil { + return fmt.Errorf("invitation (%s) still exists", invitationID) + } + } + + return nil +} + +func testAccCheckMongoDBAtlasProjectInvitationStateIDFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("not found: %s", resourceName) + } + + return fmt.Sprintf("%s-%s", rs.Primary.Attributes["project_id"], rs.Primary.Attributes["username"]), nil + } +} + +func testAccMongoDBAtlasProjectInvitationConfig(projectID, username string, roles []string) string { + return fmt.Sprintf(` + resource "mongodbatlas_project_invitation" "test" { + project_id = %[1]q + username = %[2]q + roles = ["%[3]s"] + }`, projectID, username, + strings.Join(roles, `", "`), + ) +} diff --git a/website/docs/d/org_invitation.html.markdown b/website/docs/d/org_invitation.html.markdown new file mode 100644 index 0000000000..3c1eb468de --- /dev/null +++ b/website/docs/d/org_invitation.html.markdown @@ -0,0 +1,50 @@ +--- +layout: "mongodbatlas" +page_title: "MongoDB Atlas: org_invitation" +sidebar_current: "docs-mongodbatlas-datasource-organization-invitation" +description: |- + Provides an Atlas Organization Invitation. +--- + +# mongodbatlas_org_invitation + +`mongodbatlas_org_invitation` describes an invitation for a user to join an Atlas organization. + +## Example Usage + +```hcl +resource "mongodbatlas_org_invitation" "test" { + username = "test-acc-username" + org_id = "" + roles = [ "GROUP_DATA_ACCESS_READ_WRITE" ] +} + +data "mongodbatlas_org_user" "test" { + org_id = mongodbatlas_org_user.test.org_id + username = mongodbatlas_org_user.test.username +} +``` + +## Argument Reference + +* `org_id` - (Required) Unique 24-hexadecimal digit string that identifies the organization to which you invited the user. +* `username` - (Required) Email address of the invited user. This is the address to which Atlas sends the invite. If the user accepts the invitation, they log in to Atlas with this username. +* `invitation_id` - (Required) Unique 24-hexadecimal digit string that identifies the invitation in Atlas. + +## Attributes Reference + +In addition to the arguments, this data source exports the following attributes: + +* `id` - Autogenerated unique string that identifies this data source. +* `created_at` - Timestamp in ISO 8601 date and time format in UTC when Atlas sent the invitation. +* `expires_at` - Timestamp in ISO 8601 date and time format in UTC when the invitation expires. Users have 30 days to accept an invitation. +* `inviter_username` - Atlas user who invited `username` to the organization. +* `teams_ids` - An array of unique 24-hexadecimal digit strings that identify the teams that the user was invited to join. +* `roles` - Atlas roles to assign to the invited user. If the user accepts the invitation, Atlas assigns these roles to them. The following options are available: + * ORG_OWNER + * ORG_GROUP_CREATOR + * ORG_BILLING_ADMIN + * ORG_READ_ONLY + * ORG_MEMBER + +See the [MongoDB Atlas Administration API](https://docs.atlas.mongodb.com/reference/api/organization-get-one-invitation/) documentation for more information. \ No newline at end of file diff --git a/website/docs/d/project_invitation.html.markdown b/website/docs/d/project_invitation.html.markdown new file mode 100644 index 0000000000..e380b60099 --- /dev/null +++ b/website/docs/d/project_invitation.html.markdown @@ -0,0 +1,52 @@ +--- +layout: "mongodbatlas" +page_title: "MongoDB Atlas: project_invitation" +sidebar_current: "docs-mongodbatlas-datasource-project-invitation" +description: |- + Provides an Atlas project invitation. +--- + +# mongodbatlas_project_invitation + +`mongodbatlas_project_invitation` describes an invitation to a user to join an Atlas project. + +-> **NOTE:** Groups and projects are synonymous terms. You may find GROUP-ID in the official documentation. + +## Example Usages + +```hcl +resource "mongodbatlas_project_invitation" "test" { + username = "test-acc-username" + project_id = "" + roles = [ "GROUP_DATA_ACCESS_READ_WRITE" ] +} + +data "mongodbatlas_project_invitation" "test" { + project_id = mongodbatlas_project_invitation.test.project_id + username = mongodbatlas_project_invitation.test.username +} +``` + +## Argument Reference + +* `project_id` - (Required) Unique 24-hexadecimal digit string that identifies the project to which you invited the user. +* `username` - (Required) Email address of the invited user. This is the address to which Atlas sends the invite. If the user accepts the invitation, they log in to Atlas with this username. +* `invitation_id` - (Required) Unique 24-hexadecimal digit string that identifies the invitation in Atlas. + +## Attributes Reference + +In addition to the arguments, this data source exports the following attributes: + +* `id` - Autogenerated unique string that identifies this data source. +* `created_at` - Timestamp in ISO 8601 date and time format in UTC when Atlas sent the invitation. +* `expires_at` - Timestamp in ISO 8601 date and time format in UTC when the invitation expires. Users have 30 days to accept an invitation. +* `inviter_username` - Atlas user who invited `username` to the project. +* `roles` - Atlas roles to assign to the invited user. If the user accepts the invitation, Atlas assigns these roles to them. The following options are available: + * GROUP_OWNER + * GROUP_CLUSTER_MANAGER + * GROUP_READ_ONLY + * GROUP_DATA_ACCESS_ADMIN + * GROUP_DATA_ACCESS_READ_WRITE + * GROUP_DATA_ACCESS_READ_ONLY + +See the [MongoDB Atlas Administration API](https://docs.atlas.mongodb.com/reference/api/project-get-one-invitation/) documentation for more information. \ No newline at end of file diff --git a/website/docs/r/org_invitation.html.markdown b/website/docs/r/org_invitation.html.markdown new file mode 100644 index 0000000000..3fbf9aef3a --- /dev/null +++ b/website/docs/r/org_invitation.html.markdown @@ -0,0 +1,78 @@ +--- +layout: "mongodbatlas" +page_title: "MongoDB Atlas: org_invitation" +sidebar_current: "docs-mongodbatlas-resource-organization-invitation" +description: |- + Provides an Atlas Organization Invitation resource. +--- + +# mongodbatlas_org_invitation + +`mongodbatlas_org_invitation` invites a user to join an Atlas organization. + +Each invitation for an Atlas user includes roles that Atlas grants the user when they accept the invitation. + +The [MongoDB Documentation](https://docs.atlas.mongodb.com/reference/user-roles/#organization-roles) describes the roles a user can have, which map to: + +* ORG_OWNER +* ORG_GROUP_CREATOR +* ORG_BILLING_ADMIN +* ORG_READ_ONLY +* ORG_MEMBER + +## Example Usages + +```hcl +resource "mongodbatlas_org_invitation" "test0" { + username = "test0-acc-username" + org_id = "" + roles = [ "ORG_OWNER" ] +} +``` + +```hcl +resource "mongodbatlas_org_invitation" "test0" { + username = "test0-acc-username" + org_id = "" + roles = [ "ORG_MEMBER", "ORG_BILLING_ADMIN" ] +} +``` + +```hcl +resource "mongodbatlas_org_invitation" "test1" { + username = "test1-acc-username" + org_id = "" + teams_ids = [ "", "" ] + roles = [ "ORG_MEMBER" ] +} +``` + +## Argument Reference + +* `org_id` - (Required) Unique 24-hexadecimal digit string that identifies the organization to which you want to invite a user. +* `username` - (Required) Email address of the invited user. This is the address to which Atlas sends the invite. If the user accepts the invitation, they log in to Atlas with this username. +* `teams_ids` - (Optional) An array of unique 24-hexadecimal digit strings that identify the teams that the user was invited to join. +* `roles` - (Required) Atlas roles to assign to the invited user. If the user accepts the invitation, Atlas assigns these roles to them. The following options are available: + * ORG_OWNER + * ORG_GROUP_CREATOR + * ORG_BILLING_ADMIN + * ORG_READ_ONLY + * ORG_MEMBER + +## Attributes Reference + +In addition to the arguments, this resource exports the following attributes: + +* `id` - Autogenerated unique string that identifies this resource. +* `created_at` - Timestamp in ISO 8601 date and time format in UTC when Atlas sent the invitation. +* `expires_at` - Timestamp in ISO 8601 date and time format in UTC when the invitation expires. Users have 30 days to accept an invitation. +* `invitation_id` - Unique 24-hexadecimal digit string that identifies the invitation in Atlas. +* `inviter_username` - Atlas user who invited `username` to the organization. + +## Import + +Import a user's invitation to an organization by separating the `org_id` and the `username` with a hyphen: + +``` +$ terraform import mongodbatlas_org_invitation.my_user 1112222b3bf99403840e8934-my_user@mongodb.com +``` diff --git a/website/docs/r/project_invitation.html.markdown b/website/docs/r/project_invitation.html.markdown new file mode 100644 index 0000000000..e30308f934 --- /dev/null +++ b/website/docs/r/project_invitation.html.markdown @@ -0,0 +1,72 @@ +--- +layout: "mongodbatlas" +page_title: "MongoDB Atlas: project_invitation" +sidebar_current: "docs-mongodbatlas-resource-project-invitation" +description: |- + Provides an Atlas Project Invitation resource. +--- + +# mongodbatlas_project_invitation + +`mongodbatlas_project_invitation` invites a user to join an Atlas project. + +Each invitation for an Atlas user includes roles that Atlas grants the user when they accept the invitation. + +The [MongoDB Documentation](https://docs.atlas.mongodb.com/reference/user-roles/#project-roles) describes the roles a user can have, which map to: + +* GROUP_OWNER +* GROUP_CLUSTER_MANAGER +* GROUP_READ_ONLY +* GROUP_DATA_ACCESS_ADMIN +* GROUP_DATA_ACCESS_READ_WRITE +* GROUP_DATA_ACCESS_READ_ONLY + +-> **NOTE:** Groups and projects are synonymous terms. You may find GROUP-ID in the official documentation. + +## Example Usages + +```hcl +resource "mongodbatlas_project_invitation" "test" { + username = "test-acc-username" + project_id = "" + roles = [ "GROUP_DATA_ACCESS_READ_WRITE" ] +} +``` + +```hcl +resource "mongodbatlas_project_invitation" "test" { + username = "test-acc-username" + project_id = "" + roles = [ "GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY" ] +} +``` + +## Argument Reference + +* `project_id` - (Required) Unique 24-hexadecimal digit string that identifies the project to which you want to invite a user. +* `username` - (Required) Email address to which Atlas sent the invitation. The user uses this email address as their Atlas username if they accept this invitation. +* `roles` - (Required) List of Atlas roles to assign to the invited user. If the user accepts the invitation, Atlas assigns these roles to them. Atlas accepts the following roles: + * GROUP_OWNER + * GROUP_CLUSTER_MANAGER + * GROUP_READ_ONLY + * GROUP_DATA_ACCESS_ADMIN + * GROUP_DATA_ACCESS_READ_WRITE + * GROUP_DATA_ACCESS_READ_ONLY + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - Autogenerated Unique ID for this resource. +* `created_at` - Timestamp in ISO 8601 date and time format in UTC when Atlas sent the invitation. +* `expires_at` - Timestamp in ISO 8601 date and time format in UTC when the invitation expires. Users have 30 days to accept an invitation. +* `invitation_id` - Unique 24-hexadecimal digit string that identifies the invitation in Atlas. +* `inviter_username` - Atlas user who invited `username` to the project. + +## Import + +Import a user's invitation to a project by separating the `project_id` and the `username` with a hyphen: + +``` +$ terraform import mongodbatlas_project_invitation.my_user 1112222b3bf99403840e8934-my_user@mongodb.com +```