From 96e14fe6c4a6fc3be8bf76c4b3f6178d9290029e Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 6 Jun 2024 01:36:54 +0100 Subject: [PATCH] data.azuread_users: support for the `mails` property --- docs/data-sources/users.md | 8 ++- internal/services/users/user_resource_test.go | 2 + internal/services/users/users_data_source.go | 52 +++++++++++++++-- .../services/users/users_data_source_test.go | 58 +++++++++++++++++++ 4 files changed, 112 insertions(+), 8 deletions(-) diff --git a/docs/data-sources/users.md b/docs/data-sources/users.md index c0974fe77b..0f6f83c774 100644 --- a/docs/data-sources/users.md +++ b/docs/data-sources/users.md @@ -29,11 +29,12 @@ The following arguments are supported: * `employee_ids` - (Optional) The employee identifiers assigned to the users by the organisation. * `ignore_missing` - (Optional) Ignore missing users and return users that were found. The data source will still fail if no users are found. Cannot be specified with `return_all`. Defaults to `false`. * `mail_nicknames` - (Optional) The email aliases of the users. +* `mails` - (Optional) The SMTP email addresses of the users. * `object_ids` - (Optional) The object IDs of the users. * `return_all` - (Optional) When `true`, the data source will return all users. Cannot be used with `ignore_missing`. Defaults to `false`. * `user_principal_names` - (Optional) The user principal names (UPNs) of the users. -~> Either `return_all`, or one of `user_principal_names`, `object_ids`, `mail_nicknames` or `employee_ids` must be specified. These _may_ be specified as an empty list, in which case no results will be returned. +~> Either `return_all`, or one of `user_principal_names`, `object_ids`, `mail_nicknames`, `mails`, or `employee_ids` must be specified. These _may_ be specified as an empty list, in which case no results will be returned. ## Attributes Reference @@ -41,6 +42,7 @@ The following attributes are exported: * `employee_ids` - The employee identifiers assigned to the users by the organisation. * `mail_nicknames` - The email aliases of the users. +* `mails` - The SMTP email addresses of the users. * `object_ids` - The object IDs of the users. * `user_principal_names` - The user principal names (UPNs) of the users. * `users` - A list of users. Each `user` object provides the attributes documented below. @@ -49,11 +51,11 @@ The following attributes are exported: `user` object exports the following: -* `account_enabled` - Whether or not the account is enabled. +* `account_enabled` - Whether the account is enabled. * `display_name` - The display name of the user. * `employee_id` - The employee identifier assigned to the user by the organisation. * `mail_nickname` - The email alias of the user. -* `mail` - The primary email address of the user. +* `mail` - The SMTP email address of the user. * `object_id` - The object ID of the user. * `onpremises_immutable_id` - The value used to associate an on-premises Active Directory user account with their Azure AD user object. * `onpremises_sam_account_name` - The on-premise SAM account name of the user. diff --git a/internal/services/users/user_resource_test.go b/internal/services/users/user_resource_test.go index c6834a5f10..e797a24bb3 100644 --- a/internal/services/users/user_resource_test.go +++ b/internal/services/users/user_resource_test.go @@ -228,6 +228,7 @@ resource "azuread_user" "testA" { user_principal_name = "acctestUser'%[1]d.A@${data.azuread_domains.test.domains.0.domain_name}" display_name = "acctestUser-%[1]d-A" employee_id = "A%[3]s%[3]s" + mail = "acctestUser-%[1]d-A@${data.azuread_domains.test.domains.0.domain_name}" password = "%[2]s" } @@ -235,6 +236,7 @@ resource "azuread_user" "testB" { user_principal_name = "acctestUser.%[1]d.B@${data.azuread_domains.test.domains.0.domain_name}" display_name = "acctestUser-%[1]d-B" mail_nickname = "acctestUser-%[1]d-B" + mail = "acctestUser-%[1]d-B@${data.azuread_domains.test.domains.0.domain_name}" employee_id = "B%[3]s%[3]s" password = "%[2]s" } diff --git a/internal/services/users/users_data_source.go b/internal/services/users/users_data_source.go index 3fc8848390..e2624bc2c7 100644 --- a/internal/services/users/users_data_source.go +++ b/internal/services/users/users_data_source.go @@ -35,7 +35,7 @@ func usersData() *pluginsdk.Resource { Type: pluginsdk.TypeList, Optional: true, Computed: true, - ExactlyOneOf: []string{"object_ids", "user_principal_names", "mail_nicknames", "employee_ids", "return_all"}, + ExactlyOneOf: []string{"object_ids", "user_principal_names", "mail_nicknames", "mails", "employee_ids", "return_all"}, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), @@ -47,7 +47,19 @@ func usersData() *pluginsdk.Resource { Type: pluginsdk.TypeList, Optional: true, Computed: true, - ExactlyOneOf: []string{"object_ids", "user_principal_names", "mail_nicknames", "employee_ids", "return_all"}, + ExactlyOneOf: []string{"object_ids", "user_principal_names", "mail_nicknames", "mails", "employee_ids", "return_all"}, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + }, + + "mails": { + Description: "The SMTP address of the users", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + ExactlyOneOf: []string{"object_ids", "user_principal_names", "mail_nicknames", "mails", "employee_ids", "return_all"}, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), @@ -59,7 +71,7 @@ func usersData() *pluginsdk.Resource { Type: pluginsdk.TypeList, Optional: true, Computed: true, - ExactlyOneOf: []string{"object_ids", "user_principal_names", "mail_nicknames", "employee_ids", "return_all"}, + ExactlyOneOf: []string{"object_ids", "user_principal_names", "mail_nicknames", "mails", "employee_ids", "return_all"}, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), @@ -71,7 +83,7 @@ func usersData() *pluginsdk.Resource { Type: pluginsdk.TypeList, Optional: true, Computed: true, - ExactlyOneOf: []string{"object_ids", "user_principal_names", "mail_nicknames", "employee_ids", "return_all"}, + ExactlyOneOf: []string{"object_ids", "user_principal_names", "mail_nicknames", "mails", "employee_ids", "return_all"}, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), @@ -92,7 +104,7 @@ func usersData() *pluginsdk.Resource { Optional: true, Default: false, ConflictsWith: []string{"ignore_missing"}, - ExactlyOneOf: []string{"object_ids", "user_principal_names", "mail_nicknames", "employee_ids", "return_all"}, + ExactlyOneOf: []string{"object_ids", "user_principal_names", "mail_nicknames", "mails", "employee_ids", "return_all"}, }, "users": { @@ -263,6 +275,31 @@ func usersDataSourceRead(ctx context.Context, d *pluginsdk.ResourceData, meta in } users = append(users, (*result)[0]) } + } else if mails, ok := d.Get("mails").([]interface{}); ok && len(mails) > 0 { + expectedCount = len(mails) + for _, v := range mails { + query := odata.Query{ + Filter: fmt.Sprintf("mail eq '%s'", odata.EscapeSingleQuote(v.(string))), + } + result, _, err := client.List(ctx, query) + if err != nil { + return tf.ErrorDiagF(err, "Finding user with mail address: %q", v) + } + if result == nil { + return tf.ErrorDiagF(errors.New("API returned nil result"), "Bad API Response") + } + + count := len(*result) + if count > 1 { + return tf.ErrorDiagPathF(nil, "mails", "More than one user found with mail address: %q", v) + } else if count == 0 { + if ignoreMissing { + continue + } + return tf.ErrorDiagPathF(err, "mails", "User not found with mail address: %q", v) + } + users = append(users, (*result)[0]) + } } else if employeeIds, ok := d.Get("employee_ids").([]interface{}); ok && len(employeeIds) > 0 { expectedCount = len(employeeIds) for _, v := range employeeIds { @@ -299,6 +336,7 @@ func usersDataSourceRead(ctx context.Context, d *pluginsdk.ResourceData, meta in upns := make([]string, 0) objectIds := make([]string, 0) mailNicknames := make([]string, 0) + mails := make([]msgraph.StringNullWhenEmpty, 0) employeeIds := make([]msgraph.StringNullWhenEmpty, 0) userList := make([]map[string]interface{}, 0) for _, u := range users { @@ -311,6 +349,9 @@ func usersDataSourceRead(ctx context.Context, d *pluginsdk.ResourceData, meta in if u.MailNickname != nil { mailNicknames = append(mailNicknames, *u.MailNickname) } + if u.Mail != nil { + mails = append(mails, *u.Mail) + } if u.EmployeeId != nil { employeeIds = append(employeeIds, *u.EmployeeId) } @@ -339,6 +380,7 @@ func usersDataSourceRead(ctx context.Context, d *pluginsdk.ResourceData, meta in d.SetId("users#" + base64.URLEncoding.EncodeToString(h.Sum(nil))) tf.Set(d, "employee_ids", employeeIds) tf.Set(d, "mail_nicknames", mailNicknames) + tf.Set(d, "mails", mails) tf.Set(d, "object_ids", objectIds) tf.Set(d, "user_principal_names", upns) tf.Set(d, "users", userList) diff --git a/internal/services/users/users_data_source_test.go b/internal/services/users/users_data_source_test.go index 1520f40e8a..aab5f136d7 100644 --- a/internal/services/users/users_data_source_test.go +++ b/internal/services/users/users_data_source_test.go @@ -103,6 +103,38 @@ func TestAccUsersDataSource_byMailNicknamesIgnoreMissing(t *testing.T) { }}) } +func TestAccUsersDataSource_byMails(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azuread_users", "test") + + data.DataSourceTest(t, []acceptance.TestStep{{ + Config: UsersDataSource{}.byMails(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("user_principal_names.#").HasValue("2"), + check.That(data.ResourceName).Key("object_ids.#").HasValue("2"), + check.That(data.ResourceName).Key("mail_nicknames.#").HasValue("2"), + check.That(data.ResourceName).Key("mails.#").HasValue("2"), + check.That(data.ResourceName).Key("employee_ids.#").HasValue("2"), + check.That(data.ResourceName).Key("users.#").HasValue("2"), + ), + }}) +} + +func TestAccUsersDataSource_byMailsIgnoreMissing(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azuread_users", "test") + + data.DataSourceTest(t, []acceptance.TestStep{{ + Config: UsersDataSource{}.byMailsIgnoreMissing(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("user_principal_names.#").HasValue("2"), + check.That(data.ResourceName).Key("object_ids.#").HasValue("2"), + check.That(data.ResourceName).Key("mail_nicknames.#").HasValue("2"), + check.That(data.ResourceName).Key("mails.#").HasValue("2"), + check.That(data.ResourceName).Key("employee_ids.#").HasValue("2"), + check.That(data.ResourceName).Key("users.#").HasValue("2"), + ), + }}) +} + func TestAccUsersDataSource_byEmployeeIds(t *testing.T) { data := acceptance.BuildTestData(t, "data.azuread_users", "test") @@ -242,6 +274,32 @@ data "azuread_users" "test" { `, UserResource{}.threeUsersABC(data), data.RandomInteger) } +func (UsersDataSource) byMails(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +data "azuread_users" "test" { + mails = [azuread_user.testA.mail, azuread_user.testB.mail] +} +`, UserResource{}.threeUsersABC(data)) +} + +func (UsersDataSource) byMailsIgnoreMissing(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +data "azuread_users" "test" { + ignore_missing = true + + mails = [ + azuread_user.testA.mail, + "not-a-real-user-%[2]d${data.azuread_domains.test.domains.0.domain_name}", + azuread_user.testB.mail, + ] +} +`, UserResource{}.threeUsersABC(data), data.RandomInteger) +} + func (UsersDataSource) byEmployeeIds(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s