Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

d/aws_identitystore_group: expose all group attributes #27762

Merged
Merged
156 changes: 96 additions & 60 deletions internal/service/identitystore/group_data_source.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ package identitystore

import (
"context"
"errors"
"regexp"

"github.com/aws/aws-sdk-go-v2/aws"
@@ -12,6 +13,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/create"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/names"
)

@@ -20,14 +22,63 @@ func DataSourceGroup() *schema.Resource {
ReadContext: dataSourceGroupRead,

Schema: map[string]*schema.Schema{
"alternate_identifier": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
ConflictsWith: []string{"filter", "group_id"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"external_id": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
ExactlyOneOf: []string{"alternate_identifier.0.external_id", "alternate_identifier.0.unique_attribute"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Required: true,
},
"issuer": {
Type: schema.TypeString,
Required: true,
},
},
},
},
"unique_attribute": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
ExactlyOneOf: []string{"alternate_identifier.0.external_id", "alternate_identifier.0.unique_attribute"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"attribute_path": {
Type: schema.TypeString,
Required: true,
},
"attribute_value": {
Type: schema.TypeString,
Required: true,
},
},
},
},
},
},
},
"display_name": {
Type: schema.TypeString,
Computed: true,
},

"filter": {
Type: schema.TypeSet,
Required: true,
Deprecated: "Use the alternate_identifier attribute instead.",
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
AtLeastOneOf: []string{"alternate_identifier", "filter", "group_id"},
ConflictsWith: []string{"alternate_identifier"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"attribute_path": {
@@ -41,17 +92,17 @@ func DataSourceGroup() *schema.Resource {
},
},
},

"group_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Type: schema.TypeString,
Optional: true,
Computed: true,
AtLeastOneOf: []string{"alternate_identifier", "filter", "group_id"},
ConflictsWith: []string{"alternate_identifier"},
ValidateFunc: validation.All(
validation.StringLenBetween(1, 47),
validation.StringMatch(regexp.MustCompile(`^([0-9a-f]{10}-|)[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$`), "must match ([0-9a-f]{10}-|)[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"),
),
},

"identity_store_id": {
Type: schema.TypeString,
Required: true,
@@ -73,76 +124,61 @@ func dataSourceGroupRead(ctx context.Context, d *schema.ResourceData, meta inter

identityStoreId := d.Get("identity_store_id").(string)

// Filters has been marked as deprecated in favour of GetGroupId, which
// allows only a single filter. Keep using it to maintain backwards
// compatibility of the data source.
var getGroupIdInput *identitystore.GetGroupIdInput

input := &identitystore.ListGroupsInput{
IdentityStoreId: aws.String(identityStoreId),
Filters: expandFilters(d.Get("filter").(*schema.Set).List()),
if v, ok := d.GetOk("alternate_identifier"); ok && len(v.([]interface{})) > 0 {
getGroupIdInput = &identitystore.GetGroupIdInput{
AlternateIdentifier: expandAlternateIdentifier(v.([]interface{})[0].(map[string]interface{})),
IdentityStoreId: aws.String(identityStoreId),
}
} else if v, ok := d.GetOk("filter"); ok && len(v.([]interface{})) > 0 {
getGroupIdInput = &identitystore.GetGroupIdInput{
AlternateIdentifier: &types.AlternateIdentifierMemberUniqueAttribute{
Value: *expandUniqueAttribute(v.([]interface{})[0].(map[string]interface{})),
},
IdentityStoreId: aws.String(identityStoreId),
}
}

var results []types.Group

paginator := identitystore.NewListGroupsPaginator(conn, input)
var groupId string

for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if getGroupIdInput != nil {
output, err := conn.GetGroupId(ctx, getGroupIdInput)

if err != nil {
return create.DiagError(names.IdentityStore, create.ErrActionReading, DSNameGroup, identityStoreId, err)
var e *types.ResourceNotFoundException
if errors.As(err, &e) {
return diag.Errorf("no Identity Store Group found matching criteria; try different search")
} else {
return create.DiagError(names.IdentityStore, create.ErrActionReading, DSNameGroup, identityStoreId, err)
}
}

for _, group := range page.Groups {
if v, ok := d.GetOk("group_id"); ok && v.(string) != aws.ToString(group.GroupId) {
continue
}
groupId = aws.ToString(output.GroupId)
}

results = append(results, group)
if v, ok := d.GetOk("group_id"); ok && v.(string) != "" {
if groupId != "" && groupId != v.(string) {
// We were given a filter, and it found a group different to this one.
return diag.Errorf("no Identity Store Group found matching criteria; try different search")
}
}

if len(results) == 0 {
return diag.Errorf("no Identity Store Group found matching criteria\n%v; try different search", input.Filters)
groupId = v.(string)
}

if len(results) > 1 {
return diag.Errorf("multiple Identity Store Groups found matching criteria\n%v; try different search", input.Filters)
}
group, err := findGroupByID(ctx, conn, identityStoreId, groupId)

if err != nil {
if tfresource.NotFound(err) {
return diag.Errorf("no Identity Store Group found matching criteria; try different search")
}

group := results[0]
return create.DiagError(names.IdentityStore, create.ErrActionReading, DSNameGroup, identityStoreId, err)
}

d.SetId(aws.ToString(group.GroupId))
d.Set("display_name", group.DisplayName)
d.Set("group_id", group.GroupId)

return nil
}

func expandFilters(l []interface{}) []types.Filter {
if len(l) == 0 || l[0] == nil {
return nil
}

filters := make([]types.Filter, 0, len(l))
for _, v := range l {
tfMap, ok := v.(map[string]interface{})
if !ok {
continue
}

filter := types.Filter{}

if v, ok := tfMap["attribute_path"].(string); ok && v != "" {
filter.AttributePath = aws.String(v)
}

if v, ok := tfMap["attribute_value"].(string); ok && v != "" {
filter.AttributeValue = aws.String(v)
}

filters = append(filters, filter)
}

return filters
}
46 changes: 40 additions & 6 deletions website/docs/d/identitystore_group.html.markdown
Original file line number Diff line number Diff line change
@@ -18,9 +18,11 @@ data "aws_ssoadmin_instances" "example" {}
data "aws_identitystore_group" "example" {
identity_store_id = tolist(data.aws_ssoadmin_instances.example.identity_store_ids)[0]

filter {
attribute_path = "DisplayName"
attribute_value = "ExampleGroup"
alternate_identifier {
unique_attribute {
attribute_path = "DisplayName"
attribute_value = "ExampleGroup"
}
}
}

@@ -31,19 +33,51 @@ output "group_id" {

## Argument Reference

The following arguments are supported:

* `filter` - (Required) Configuration block(s) for filtering. Currently, the AWS Identity Store API supports only 1 filter. Detailed below.
* `group_id` - (Optional) The identifier for a group in the Identity Store.
The following arguments are required:

* `identity_store_id` - (Required) Identity Store ID associated with the Single Sign-On Instance.

The following arguments are optional:

* `alternate_identifier` (Optional) A unique identifier for the group that is not the primary identifier. Conflicts with `group_id` and `filter`. Detailed below.
* `filter` - (Optional, **Deprecated** use the `alternate_identifier` attribute instead) Configuration block for filtering by a unique attribute of the group. Detailed below.
* `group_id` - (Optional) The identifier for a group in the Identity Store.

-> Exactly one of the above arguments must be provided. Passing both `filter` and `group_id` is allowed for backwards compatibility.

### `alternate_identifier` Configuration Block

The following arguments are supported by the `alternate_identifier` configuration block:

* `external_id` - (Optional) Configuration block for filtering by the identifier issued by an external identity provider. Detailed below.
* `unique_attribute` - (Optional) An entity attribute that's unique to a specific entity. Detailed below.

-> Exactly one of the above arguments must be provided.

### `external_id` Configuration Block

The following arguments are supported by the `external_id` configuration block:

* `id` - (Required) The identifier issued to this resource by an external identity provider.
* `issuer` - (Required) The issuer for an external identifier.

### `filter` Configuration Block

~> The `filter` configuration block has been deprecated. Use `alternate_identifier` instead.

The following arguments are supported by the `filter` configuration block:

* `attribute_path` - (Required) Attribute path that is used to specify which attribute name to search. Currently, `DisplayName` is the only valid attribute path.
* `attribute_value` - (Required) Value for an attribute.

### `unique_attribute` Configuration Block

The following arguments are supported by the `unique_attribute` configuration block:

* `attribute_path` - (Required) Attribute path that is used to specify which attribute name to search. For example: `DisplayName`. Refer to the [Group data type](https://docs.aws.amazon.com/singlesignon/latest/IdentityStoreAPIReference/API_Group.html).
* `attribute_value` - (Required) Value for an attribute.

## Attributes Reference

In addition to all arguments above, the following attributes are exported: