diff --git a/docs/data-sources/group.md b/docs/data-sources/group.md index 538b04ecb6..34e8b3a6e2 100644 --- a/docs/data-sources/group.md +++ b/docs/data-sources/group.md @@ -28,6 +28,7 @@ data "azuread_group" "example" { The following arguments are supported: * `display_name` - (Optional) The display name for the group. +* `include_transitive_members` - (Optional) Whether to include transitive members (a flat list of all nested members). Defaults to `false`. * `mail_nickname` - (Optional) The mail alias for the group, unique in the organisation. * `mail_enabled` - (Optional) Whether the group is mail-enabled. * `object_id` - (Optional) Specifies the object ID of the group. @@ -52,7 +53,7 @@ The following attributes are exported: * `mail` - The SMTP address for the group. * `mail_enabled` - Whether the group is mail-enabled. * `mail_nickname` - The mail alias for the group, unique in the organisation. -* `members` - List of object IDs of the group members. +* `members` - List of object IDs of the group members. When `include_transitive_members` is `true`, contains a list of object IDs of all transitive group members. * `onpremises_domain_name` - The on-premises FQDN, also called dnsDomainName, synchronised from the on-premises directory when Azure AD Connect is used. * `onpremises_group_type` - The on-premises group type that the AAD group will be written as, when writeback is enabled. Possible values are `UniversalDistributionGroup`, `UniversalMailEnabledSecurityGroup`, or `UniversalSecurityGroup`. * `onpremises_netbios_name` - The on-premises NetBIOS name, synchronised from the on-premises directory when Azure AD Connect is used. diff --git a/internal/services/groups/group_data_source.go b/internal/services/groups/group_data_source.go index e7eb5014f0..c36f6f7464 100644 --- a/internal/services/groups/group_data_source.go +++ b/internal/services/groups/group_data_source.go @@ -68,6 +68,13 @@ func groupDataSource() *pluginsdk.Resource { Computed: true, }, + "include_transitive_members": { + Description: "Specifies whether to include transitive members (a flat list of all nested members).", + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + "assignable_to_role": { Description: "Indicates whether this group can be assigned to an Azure Active Directory role", Type: pluginsdk.TypeBool, @@ -423,9 +430,19 @@ func groupDataSourceRead(ctx context.Context, d *pluginsdk.ResourceData, meta in tf.Set(d, "hide_from_address_lists", hideFromAddressLists) tf.Set(d, "hide_from_outlook_clients", hideFromOutlookClients) - members, _, err := client.ListMembers(ctx, d.Id()) - if err != nil { - return tf.ErrorDiagF(err, "Could not retrieve group members for group with object ID: %q", d.Id()) + includeTransitiveMembers := d.Get("include_transitive_members").(bool) + var members *[]string + var err error + if includeTransitiveMembers { + members, _, err = client.ListTransitiveMembers(ctx, d.Id()) + if err != nil { + return tf.ErrorDiagF(err, "Could not retrieve transitive group members for group with object ID: %q", d.Id()) + } + } else { + members, _, err = client.ListMembers(ctx, d.Id()) + if err != nil { + return tf.ErrorDiagF(err, "Could not retrieve group members for group with object ID: %q", d.Id()) + } } tf.Set(d, "members", members) diff --git a/internal/services/groups/group_data_source_test.go b/internal/services/groups/group_data_source_test.go index 33b36698b3..baa4ab76c1 100644 --- a/internal/services/groups/group_data_source_test.go +++ b/internal/services/groups/group_data_source_test.go @@ -159,6 +159,20 @@ func TestAccGroupDataSource_members(t *testing.T) { }) } +func TestAccGroupDataSource_transitiveMembers(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azuread_group", "test") + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: GroupDataSource{}.transitiveMembers(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("display_name").HasValue(fmt.Sprintf("acctestGroup-%d", data.RandomInteger)), + check.That(data.ResourceName).Key("members.#").HasValue("4"), + ), + }, + }) +} + func TestAccGroupDataSource_owners(t *testing.T) { data := acceptance.BuildTestData(t, "data.azuread_group", "test") @@ -314,6 +328,17 @@ data "azuread_group" "test" { `, GroupResource{}.withThreeMembers(data)) } +func (GroupDataSource) transitiveMembers(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +data "azuread_group" "test" { + object_id = azuread_group.test.object_id + include_transitive_members = true +} +`, GroupResource{}.withTransitiveMembers(data)) +} + func (GroupDataSource) dynamicMembership(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s diff --git a/internal/services/groups/group_resource_test.go b/internal/services/groups/group_resource_test.go index c2cebcf910..fb51147605 100644 --- a/internal/services/groups/group_resource_test.go +++ b/internal/services/groups/group_resource_test.go @@ -954,6 +954,30 @@ resource "azuread_group" "test" { `, r.templateThreeUsers(data), data.RandomInteger) } +func (r GroupResource) withTransitiveMembers(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +resource "azuread_group" "nested" { + display_name = "acctestGroup-%[2]d-Nested" + security_enabled = true + members = [ + azuread_user.test.object_id, + azuread_group.member.object_id, + azuread_service_principal.test.object_id + ] +} + +resource "azuread_group" "test" { + display_name = "acctestGroup-%[2]d" + security_enabled = true + members = [ + azuread_group.nested.object_id + ] +} +`, r.templateDiverseDirectoryObjects(data), data.RandomInteger) +} + func (r GroupResource) withOwnersAndMembers(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s