Skip to content

Commit 5a75bcc

Browse files
csanxCristina Sánchez Sánchezlantoli
authored
feat: Adds users attribute to mongodbatlas_organization(s) singular and plural data source. (#3468)
* Added `users` attribute, modified test and docs. * Updated doc * Fixed test * Added changelog * Fixed SDK version * Update docs/data-sources/organization.md Co-authored-by: Leo Antoli <[email protected]> * Removed FlattenUsers and related functions from common utility * Change 3 functions to private visibility * Added some value checks * Fixed doc (users attributes to be the same as in the schema) * Minor changes * Updated note in docs * Changed attributes names for consistency and type to TypeSet * Fix * Removed TODOs --------- Co-authored-by: Cristina Sánchez Sánchez <[email protected]> Co-authored-by: Leo Antoli <[email protected]>
1 parent 61237c8 commit 5a75bcc

File tree

6 files changed

+260
-2
lines changed

6 files changed

+260
-2
lines changed

.changelog/3468.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
```release-note:enhancement
2+
data-source/mongodbatlas_organization: Adds `users` attribute
3+
```
4+
5+
```release-note:enhancement
6+
data-source/mongodbatlas_organizations: Adds `users` attribute
7+
```

docs/data-sources/organization.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ In addition to all arguments above, the following attributes are exported:
2323
* `name` - Human-readable label that identifies the organization.
2424
* `id` - Unique 24-hexadecimal digit string that identifies the organization.
2525
* `is_deleted` - Flag that indicates whether this organization has been deleted.
26+
* `users`- Returns a list of all pending and active MongoDB Cloud users associated with the specified organization.
2627
* `api_access_list_required` - (Optional) Flag that indicates whether to require API operations to originate from an IP Address added to the API access list for the specified organization.
2728
* `multi_factor_auth_required` - (Optional) Flag that indicates whether to require users to set up Multi-Factor Authentication (MFA) before accessing the specified organization. To learn more, see: https://www.mongodb.com/docs/atlas/security-multi-factor-authentication/.
2829
* `restrict_employee_access` - (Optional) Flag that indicates whether to block MongoDB Support from accessing Atlas infrastructure for any deployment in the specified organization without explicit permission. Once this setting is turned on, you can grant MongoDB Support a 24-hour bypass access to the Atlas deployment to resolve support issues. To learn more, see: https://www.mongodb.com/docs/atlas/security-restrict-support-access/.
@@ -31,6 +32,28 @@ In addition to all arguments above, the following attributes are exported:
3132
* `skip_default_alerts_settings` - (Optional) Flag that indicates whether to prevent Atlas from automatically creating organization-level alerts not explicitly managed through Terraform. Defaults to `true`.
3233

3334

35+
### Users
36+
* `id` - Unique 24-hexadecimal digit string that identifies the MongoDB Cloud user.
37+
* `org_membership_status` - String enum that indicates whether the MongoDB Cloud user has a pending invitation to join the organization or they are already active in the organization.
38+
* `roles` - Organization- and project-level roles assigned to one MongoDB Cloud user within one organization.
39+
* `team_ids` - List of unique 24-hexadecimal digit strings that identifies the teams to which this MongoDB Cloud user belongs.
40+
* `username` - Email address that represents the username of the MongoDB Cloud user.
41+
* `country` - Two-character alphabetical string that identifies the MongoDB Cloud user's geographic location. This parameter uses the ISO 3166-1a2 code format.
42+
* `invitation_created_at` - Date and time when MongoDB Cloud sent the invitation. MongoDB Cloud represents this timestamp in ISO 8601 format in UTC.
43+
* `invitation_expires_at` - Date and time when the invitation from MongoDB Cloud expires. MongoDB Cloud represents this timestamp in ISO 8601 format in UTC.
44+
* `inviter_username` - Username of the MongoDB Cloud user who sent the invitation to join the organization.
45+
* `created_at` - Date and time when MongoDB Cloud created the current account. This value is in the ISO 8601 timestamp format in UTC.
46+
* `first_name` - First or given name that belongs to the MongoDB Cloud user.
47+
* `last_auth` - Date and time when the current account last authenticated. This value is in the ISO 8601 timestamp format in UTC.
48+
* `last_name` - Last name, family name, or surname that belongs to the MongoDB Cloud user.
49+
* `mobile_number` - Mobile phone number that belongs to the MongoDB Cloud user.
50+
51+
52+
~> **NOTE:** - Users with pending invitations created using [`mongodbatlas_project_invitation`](../resources/project_invitation.md) resource or via the deprecated [Invite One MongoDB Cloud User to Join One Project](https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2/operation/operation-createprojectinvitation) endpoint are excluded (or cannot be managed) with this resource. See [MongoDB Atlas API]<link-to-resource-API> for details.
53+
To manage these users with this resource/data source, refer to our [migration guide]<link-to-migration-guide>.
54+
55+
56+
3457
~> **NOTE:** - If you create an organization with our Terraform provider version >=1.30.0, this field is set to `true` by default.<br> - If you have an existing organization created with our Terraform provider version <1.30.0, this field might be `false`, which is the [API default value](https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2/operation/operation-createorganization). To prevent the creation of future default alerts, set this explicitly to `true` using the [`mongodbatlas_organization`](../resources/organization.md) resource.
3558

3659

docs/data-sources/organizations.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,36 @@ data "mongodbatlas_organizations" "test" {
2828
* `name` - Human-readable label that identifies the organization.
2929
* `id` - Unique 24-hexadecimal digit string that identifies the organization.
3030
* `is_deleted` - Flag that indicates whether this organization has been deleted.
31+
* `users` - Returns list of all pending and active MongoDB Cloud users associated with the specified organization.
3132
* `api_access_list_required` - (Optional) Flag that indicates whether to require API operations to originate from an IP Address added to the API access list for the specified organization.
3233
* `multi_factor_auth_required` - (Optional) Flag that indicates whether to require users to set up Multi-Factor Authentication (MFA) before accessing the specified organization. To learn more, see: https://www.mongodb.com/docs/atlas/security-multi-factor-authentication/.
3334
* `restrict_employee_access` - (Optional) Flag that indicates whether to block MongoDB Support from accessing Atlas infrastructure for any deployment in the specified organization without explicit permission. Once this setting is turned on, you can grant MongoDB Support a 24-hour bypass access to the Atlas deployment to resolve support issues. To learn more, see: https://www.mongodb.com/docs/atlas/security-restrict-support-access/.
3435
* `gen_ai_features_enabled` - (Optional) Flag that indicates whether this organization has access to generative AI features. This setting only applies to Atlas Commercial and defaults to `true`. With this setting on, Project Owners may be able to enable or disable individual AI features at the project level. To learn more, see https://www.mongodb.com/docs/generative-ai-faq/.
3536
* `security_contact` - (Optional) String that specifies a single email address for the specified organization to receive security-related notifications. Specifying a security contact does not grant them authorization or access to Atlas for security decisions or approvals.
3637
* `skip_default_alerts_settings` - (Optional) Flag that indicates whether to prevent Atlas from automatically creating organization-level alerts not explicitly managed through Terraform. Defaults to `true`.
3738

39+
40+
### Users
41+
* `id` - Unique 24-hexadecimal digit string that identifies the MongoDB Cloud user.
42+
* `org_membership_status` - String enum that indicates whether the MongoDB Cloud user has a pending invitation to join the organization or they are already active in the organization.
43+
* `roles` - Organization- and project-level roles assigned to one MongoDB Cloud user within one organization.
44+
* `teamIds` - List of unique 24-hexadecimal digit strings that identifies the teams to which this MongoDB Cloud user belongs.
45+
* `username` - Email address that represents the username of the MongoDB Cloud user.
46+
* `country` - Two-character alphabetical string that identifies the MongoDB Cloud user's geographic location. This parameter uses the ISO 3166-1a2 code format.
47+
* `invitation_created_at` - Date and time when MongoDB Cloud sent the invitation. MongoDB Cloud represents this timestamp in ISO 8601 format in UTC.
48+
* `invitation_expires_at` - Date and time when the invitation from MongoDB Cloud expires. MongoDB Cloud represents this timestamp in ISO 8601 format in UTC.
49+
* `inviter_username` - Username of the MongoDB Cloud user who sent the invitation to join the organization.
50+
* `created_at` - Date and time when MongoDB Cloud created the current account. This value is in the ISO 8601 timestamp format in UTC.
51+
* `first_name` - First or given name that belongs to the MongoDB Cloud user.
52+
* `last_auth` - Date and time when the current account last authenticated. This value is in the ISO 8601 timestamp format in UTC.
53+
* `last_name` - Last name, family name, or surname that belongs to the MongoDB Cloud user.
54+
* `mobile_number` - Mobile phone number that belongs to the MongoDB Cloud user.
55+
56+
57+
~> **NOTE:** - Users with pending invitations created using [`mongodbatlas_project_invitation`](../resources/project_invitation.md) resource or via the deprecated [Invite One MongoDB Cloud User to Join One Project](https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2/operation/operation-createprojectinvitation) endpoint are excluded (or cannot be managed) with this resource. See [MongoDB Atlas API]<link-to-resource-API> for details.
58+
To manage these users with this resource/data source, refer to our [migration guide]<link-to-migration-guide>.
59+
60+
3861
~> **NOTE:** - If you create an organization with our Terraform provider version >=1.30.0, this field is set to `true` by default.<br> - If you have an existing organization created with our Terraform provider version <1.30.0, this field might be `false`, which is the [API default value](https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2/operation/operation-createorganization). To prevent the creation of future default alerts, set this explicitly to `true` using the [`mongodbatlas_organization`](../resources/organization.md) resource.
3962

4063

internal/service/organization/data_source_organization.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,112 @@ package organization
33
import (
44
"context"
55
"fmt"
6+
"net/http"
7+
"time"
68

79
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
810
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
11+
"go.mongodb.org/atlas-sdk/v20250312005/admin"
912

1013
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
14+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/dsschema"
1115
"github.com/mongodb/terraform-provider-mongodbatlas/internal/config"
1216
)
1317

18+
var (
19+
DSOrgUsersSchema = schema.Schema{
20+
Type: schema.TypeSet,
21+
Computed: true,
22+
Elem: &schema.Resource{
23+
Schema: map[string]*schema.Schema{
24+
"id": {
25+
Type: schema.TypeString,
26+
Computed: true,
27+
},
28+
"org_membership_status": {
29+
Type: schema.TypeString,
30+
Computed: true,
31+
},
32+
"roles": {
33+
Type: schema.TypeList,
34+
Computed: true,
35+
Elem: &schema.Resource{
36+
Schema: map[string]*schema.Schema{
37+
"org_roles": {
38+
Type: schema.TypeSet,
39+
Computed: true,
40+
Elem: &schema.Schema{Type: schema.TypeString},
41+
},
42+
"project_roles_assignments": {
43+
Type: schema.TypeSet,
44+
Computed: true,
45+
Elem: &schema.Resource{
46+
Schema: map[string]*schema.Schema{
47+
"project_id": {
48+
Type: schema.TypeString,
49+
Computed: true,
50+
},
51+
"project_roles": {
52+
Type: schema.TypeSet,
53+
Computed: true,
54+
Elem: &schema.Schema{Type: schema.TypeString},
55+
},
56+
},
57+
},
58+
},
59+
},
60+
},
61+
},
62+
"team_ids": {
63+
Type: schema.TypeList,
64+
Computed: true,
65+
Elem: &schema.Schema{Type: schema.TypeString},
66+
},
67+
"username": {
68+
Type: schema.TypeString,
69+
Computed: true,
70+
},
71+
"invitation_created_at": {
72+
Type: schema.TypeString,
73+
Computed: true,
74+
},
75+
"invitation_expires_at": {
76+
Type: schema.TypeString,
77+
Computed: true,
78+
},
79+
"inviter_username": {
80+
Type: schema.TypeString,
81+
Computed: true,
82+
},
83+
"country": {
84+
Type: schema.TypeString,
85+
Computed: true,
86+
},
87+
"created_at": {
88+
Type: schema.TypeString,
89+
Computed: true,
90+
},
91+
"first_name": {
92+
Type: schema.TypeString,
93+
Computed: true,
94+
},
95+
"last_auth": {
96+
Type: schema.TypeString,
97+
Computed: true,
98+
},
99+
"last_name": {
100+
Type: schema.TypeString,
101+
Computed: true,
102+
},
103+
"mobile_number": {
104+
Type: schema.TypeString,
105+
Computed: true,
106+
},
107+
},
108+
},
109+
}
110+
)
111+
14112
func DataSource() *schema.Resource {
15113
return &schema.Resource{
16114
ReadContext: dataSourceRead,
@@ -43,6 +141,7 @@ func DataSource() *schema.Resource {
43141
},
44142
},
45143
},
144+
"users": &DSOrgUsersSchema,
46145
"api_access_list_required": {
47146
Type: schema.TypeBool,
48147
Computed: true,
@@ -71,6 +170,57 @@ func DataSource() *schema.Resource {
71170
}
72171
}
73172

173+
func flattenUsers(users []admin.OrgUserResponse) []map[string]any {
174+
ret := make([]map[string]any, len(users))
175+
for i := range users {
176+
user := &users[i]
177+
ret[i] = map[string]any{
178+
"id": user.GetId(),
179+
"org_membership_status": user.GetOrgMembershipStatus(),
180+
"roles": flattenUserRoles(user.GetRoles()),
181+
"team_ids": user.GetTeamIds(),
182+
"username": user.GetUsername(),
183+
"invitation_created_at": user.GetInvitationCreatedAt().Format(time.RFC3339),
184+
"invitation_expires_at": user.GetInvitationExpiresAt().Format(time.RFC3339),
185+
"inviter_username": user.GetInviterUsername(),
186+
"country": user.GetCountry(),
187+
"created_at": user.GetCreatedAt().Format(time.RFC3339),
188+
"first_name": user.GetFirstName(),
189+
"last_auth": user.GetLastAuth().Format(time.RFC3339),
190+
"last_name": user.GetLastName(),
191+
"mobile_number": user.GetMobileNumber(),
192+
}
193+
}
194+
return ret
195+
}
196+
197+
func flattenUserRoles(roles admin.OrgUserRolesResponse) []map[string]any {
198+
ret := make([]map[string]any, 0)
199+
roleMap := map[string]any{
200+
"org_roles": []string{},
201+
"project_roles_assignments": []map[string]any{},
202+
}
203+
if roles.HasOrgRoles() {
204+
roleMap["org_roles"] = roles.GetOrgRoles()
205+
}
206+
if roles.HasGroupRoleAssignments() {
207+
roleMap["project_roles_assignments"] = flattenProjectRolesAssignments(roles.GetGroupRoleAssignments())
208+
}
209+
ret = append(ret, roleMap)
210+
return ret
211+
}
212+
213+
func flattenProjectRolesAssignments(assignments []admin.GroupRoleAssignment) []map[string]any {
214+
ret := make([]map[string]any, 0, len(assignments))
215+
for _, assignment := range assignments {
216+
ret = append(ret, map[string]any{
217+
"project_id": assignment.GetGroupId(),
218+
"project_roles": assignment.GetGroupRoles(),
219+
})
220+
}
221+
return ret
222+
}
223+
74224
func dataSourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
75225
conn := meta.(*config.MongoDBClient).AtlasV2
76226
orgID := d.Get("org_id").(string)
@@ -97,6 +247,14 @@ func dataSourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.
97247
return diag.FromErr(fmt.Errorf("error setting `is_deleted`: %s", err))
98248
}
99249

250+
users, err := listAllOrganizationUsers(ctx, orgID, conn)
251+
if err != nil {
252+
return diag.FromErr(fmt.Errorf("error getting organization users: %s", err))
253+
}
254+
if err := d.Set("users", flattenUsers(users)); err != nil {
255+
return diag.FromErr(fmt.Errorf("error setting `users`: %s", err))
256+
}
257+
100258
settings, _, err := conn.OrganizationsApi.GetOrganizationSettings(ctx, orgID).Execute()
101259
if err != nil {
102260
return diag.FromErr(fmt.Errorf("error getting organization settings: %s", err))
@@ -121,3 +279,11 @@ func dataSourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.
121279

122280
return nil
123281
}
282+
283+
func listAllOrganizationUsers(ctx context.Context, orgID string, conn *admin.APIClient) ([]admin.OrgUserResponse, error) {
284+
return dsschema.AllPages(ctx, func(ctx context.Context, pageNum int) (dsschema.PaginateResponse[admin.OrgUserResponse], *http.Response, error) {
285+
request := conn.MongoDBCloudUsersApi.ListOrganizationUsers(ctx, orgID)
286+
request = request.PageNum(pageNum)
287+
return request.Execute()
288+
})
289+
}

internal/service/organization/data_source_organizations.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ func PluralDataSource() *schema.Resource {
6363
},
6464
},
6565
},
66+
"users": &DSOrgUsersSchema,
6667
"api_access_list_required": {
6768
Type: schema.TypeBool,
6869
Computed: true,
@@ -138,16 +139,21 @@ func flattenOrganizations(ctx context.Context, conn *admin.APIClient, organizati
138139
results = make([]map[string]any, len(organizations))
139140

140141
for k, organization := range organizations {
142+
users, err := listAllOrganizationUsers(ctx, *organization.Id, conn)
143+
if err != nil {
144+
return nil, fmt.Errorf("error getting organization users (orgID: %s, name: %s): %s", organization.GetId(), organization.GetName(), err)
145+
}
141146
settings, _, err := conn.OrganizationsApi.GetOrganizationSettings(ctx, *organization.Id).Execute()
142147
if err != nil {
143-
return nil, fmt.Errorf("error getting organization settings (orgID: %s, org Name: %s): %s", organization.GetId(), organization.GetName(), err)
148+
return nil, fmt.Errorf("error getting organization settings (orgID: %s, name: %s): %s", organization.GetId(), organization.GetName(), err)
144149
}
145150
results[k] = map[string]any{
146151
"id": organization.Id,
147152
"name": organization.Name,
148153
"skip_default_alerts_settings": organization.SkipDefaultAlertsSettings,
149154
"is_deleted": organization.IsDeleted,
150155
"links": conversion.FlattenLinks(organization.GetLinks()),
156+
"users": flattenUsers(users),
151157
"api_access_list_required": settings.ApiAccessListRequired,
152158
"multi_factor_auth_required": settings.MultiFactorAuthRequired,
153159
"restrict_employee_access": settings.RestrictEmployeeAccess,

internal/service/organization/resource_organization_test.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,40 @@ func TestAccConfigDSOrganization_basic(t *testing.T) {
184184
{
185185
Config: configWithPluralDS(orgID),
186186
Check: checkAggrDS(resource.TestCheckResourceAttr(datasourceName, "gen_ai_features_enabled", "true"),
187-
resource.TestCheckResourceAttr(pluralDSName, "results.0.gen_ai_features_enabled", "true")),
187+
resource.TestCheckResourceAttr(pluralDSName, "results.0.gen_ai_features_enabled", "true"),
188+
resource.TestCheckResourceAttrSet(datasourceName, "users.#"),
189+
resource.TestCheckResourceAttrSet(datasourceName, "users.0.id")),
190+
},
191+
},
192+
})
193+
}
194+
195+
func TestAccConfigDSOrganization_users(t *testing.T) {
196+
var (
197+
orgID = os.Getenv("MONGODB_ATLAS_ORG_ID")
198+
)
199+
200+
resource.ParallelTest(t, resource.TestCase{
201+
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
202+
Steps: []resource.TestStep{
203+
{
204+
Config: configWithPluralDS(orgID),
205+
Check: checkAggrDS(
206+
resource.TestCheckResourceAttrWith(datasourceName, "users.#", acc.IntGreatThan(0)),
207+
resource.TestCheckResourceAttrSet(datasourceName, "users.0.id"),
208+
resource.TestCheckResourceAttrSet(datasourceName, "users.0.roles.0.org_roles.#"),
209+
resource.TestCheckResourceAttrSet(datasourceName, "users.0.roles.0.project_roles_assignments.#"),
210+
resource.TestMatchResourceAttr(datasourceName, "users.0.username", regexp.MustCompile(`.*@mongodb\.com$`)),
211+
resource.TestMatchResourceAttr(datasourceName, "users.0.last_auth", regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$`)), // Follows RFC3339 timestamp
212+
213+
resource.TestCheckResourceAttrWith(pluralDSName, "results.0.users.#", acc.IntGreatThan(0)),
214+
resource.TestCheckResourceAttrSet(pluralDSName, "results.0.users.0.id"),
215+
resource.TestCheckResourceAttrSet(pluralDSName, "results.0.users.0.roles.0.org_roles.#"),
216+
resource.TestCheckResourceAttrSet(pluralDSName, "results.0.users.0.roles.0.project_roles_assignments.#"),
217+
resource.TestMatchResourceAttr(pluralDSName, "results.0.users.0.username", regexp.MustCompile(`.*@mongodb\.com$`)),
218+
resource.TestMatchResourceAttr(pluralDSName, "results.0.users.0.last_auth", regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$`)), // Follows RFC3339 timestamp
219+
220+
),
188221
},
189222
},
190223
})

0 commit comments

Comments
 (0)