diff --git a/.changelog/26948.txt b/.changelog/26948.txt new file mode 100644 index 00000000000..0171e4c6fa9 --- /dev/null +++ b/.changelog/26948.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_identitystore_user +``` diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 5bf75c2a60f..5e18e322039 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1602,6 +1602,8 @@ func New(_ context.Context) (*schema.Provider, error) { "aws_iam_user_ssh_key": iam.ResourceUserSSHKey(), "aws_iam_virtual_mfa_device": iam.ResourceVirtualMFADevice(), + "aws_identitystore_user": identitystore.ResourceUser(), + "aws_imagebuilder_component": imagebuilder.ResourceComponent(), "aws_imagebuilder_container_recipe": imagebuilder.ResourceContainerRecipe(), "aws_imagebuilder_distribution_configuration": imagebuilder.ResourceDistributionConfiguration(), diff --git a/internal/service/identitystore/user.go b/internal/service/identitystore/user.go new file mode 100644 index 00000000000..d08fc49ebeb --- /dev/null +++ b/internal/service/identitystore/user.go @@ -0,0 +1,1037 @@ +package identitystore + +import ( + "context" + "errors" + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/identitystore" + "github.com/aws/aws-sdk-go-v2/service/identitystore/document" + "github.com/aws/aws-sdk-go-v2/service/identitystore/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "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" +) + +func ResourceUser() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceUserCreate, + ReadWithoutTimeout: resourceUserRead, + UpdateWithoutTimeout: resourceUserUpdate, + DeleteWithoutTimeout: resourceUserDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "addresses": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "country": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "formatted": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "locality": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "postal_code": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "primary": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "region": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "street_address": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "type": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + }, + }, + }, + "display_name": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "emails": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "primary": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "type": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "value": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + }, + }, + }, + "external_ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeBool, + Computed: true, + }, + "issuer": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "identity_store_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "locale": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "name": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "family_name": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "formatted": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "given_name": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "honorific_prefix": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "honorific_suffix": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "middle_name": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + }, + }, + }, + "nickname": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "phone_numbers": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "primary": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "type": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "value": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + }, + }, + }, + "preferred_language": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "profile_url": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "timezone": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "title": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + "user_id": { + Type: schema.TypeString, + Computed: true, + }, + "user_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 128)), + }, + "user_type": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(1, 1024)), + }, + }, + } +} + +const ( + ResNameUser = "User" +) + +func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).IdentityStoreConn + + in := &identitystore.CreateUserInput{ + DisplayName: aws.String(d.Get("display_name").(string)), + IdentityStoreId: aws.String(d.Get("identity_store_id").(string)), + UserName: aws.String(d.Get("user_name").(string)), + } + + if v, ok := d.GetOk("addresses"); ok && len(v.([]interface{})) > 0 { + in.Addresses = expandAddresses(v.([]interface{})) + } + + if v, ok := d.GetOk("emails"); ok && len(v.([]interface{})) > 0 { + in.Emails = expandEmails(v.([]interface{})) + } + + if v, ok := d.GetOk("locale"); ok { + in.Locale = aws.String(v.(string)) + } + + if v, ok := d.GetOk("name"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + in.Name = expandName(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("phone_numbers"); ok && len(v.([]interface{})) > 0 { + in.PhoneNumbers = expandPhoneNumbers(v.([]interface{})) + } + + if v, ok := d.GetOk("nickname"); ok { + in.NickName = aws.String(v.(string)) + } + + if v, ok := d.GetOk("preferred_language"); ok { + in.PreferredLanguage = aws.String(v.(string)) + } + + if v, ok := d.GetOk("profile_url"); ok { + in.ProfileUrl = aws.String(v.(string)) + } + + if v, ok := d.GetOk("timezone"); ok { + in.Timezone = aws.String(v.(string)) + } + + if v, ok := d.GetOk("title"); ok { + in.Title = aws.String(v.(string)) + } + + if v, ok := d.GetOk("user_type"); ok { + in.UserType = aws.String(v.(string)) + } + + out, err := conn.CreateUser(ctx, in) + if err != nil { + return create.DiagError(names.IdentityStore, create.ErrActionCreating, ResNameUser, d.Get("identity_store_id").(string), err) + } + + if out == nil || out.UserId == nil { + return create.DiagError(names.IdentityStore, create.ErrActionCreating, ResNameUser, d.Get("identity_store_id").(string), errors.New("empty output")) + } + + d.SetId(fmt.Sprintf("%s/%s", aws.ToString(out.IdentityStoreId), aws.ToString(out.UserId))) + + return resourceUserRead(ctx, d, meta) +} + +func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).IdentityStoreConn + + identityStoreId, userId, err := resourceUserParseID(d.Id()) + + if err != nil { + return create.DiagError(names.IdentityStore, create.ErrActionReading, ResNameUser, d.Id(), err) + } + + out, err := findUserByID(ctx, conn, identityStoreId, userId) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] IdentityStore User (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return create.DiagError(names.IdentityStore, create.ErrActionReading, ResNameUser, d.Id(), err) + } + + d.Set("display_name", out.DisplayName) + d.Set("identity_store_id", out.IdentityStoreId) + d.Set("locale", out.Locale) + d.Set("nickname", out.NickName) + d.Set("preferred_language", out.PreferredLanguage) + d.Set("profile_url", out.ProfileUrl) + d.Set("timezone", out.Timezone) + d.Set("title", out.Title) + d.Set("user_id", out.UserId) + d.Set("user_name", out.UserName) + d.Set("user_type", out.UserType) + + if err := d.Set("addresses", flattenAddresses(out.Addresses)); err != nil { + return create.DiagError(names.IdentityStore, create.ErrActionSetting, ResNameUser, d.Id(), err) + } + + if err := d.Set("emails", flattenEmails(out.Emails)); err != nil { + return create.DiagError(names.IdentityStore, create.ErrActionSetting, ResNameUser, d.Id(), err) + } + + if err := d.Set("external_ids", flattenExternalIds(out.ExternalIds)); err != nil { + return create.DiagError(names.IdentityStore, create.ErrActionSetting, ResNameUser, d.Id(), err) + } + + if err := d.Set("name", []interface{}{flattenName(out.Name)}); err != nil { + return create.DiagError(names.IdentityStore, create.ErrActionSetting, ResNameUser, d.Id(), err) + } + + if err := d.Set("phone_numbers", flattenPhoneNumbers(out.PhoneNumbers)); err != nil { + return create.DiagError(names.IdentityStore, create.ErrActionSetting, ResNameUser, d.Id(), err) + } + + return nil +} + +func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).IdentityStoreConn + + in := &identitystore.UpdateUserInput{ + IdentityStoreId: aws.String(d.Get("identity_store_id").(string)), + UserId: aws.String(d.Get("user_id").(string)), + Operations: nil, + } + + // IMPLEMENTATION NOTE. + // + // Complex types, such as the `emails` field, don't allow field by field + // updates, and require that the entire sub-object is modified. + // + // In those sub-objects, to remove a field, it must not be present at all + // in the updated attribute value. + // + // However, structs such as types.Email don't specify omitempty in their + // struct tags, so the document.NewLazyDocument marshaller will write out + // nulls. + // + // This is why, for those complex fields, a custom Expand function is + // provided that converts the Go SDK type (e.g. types.Email) into a field + // by field representation of what the API would expect. + + fieldsToUpdate := []struct { + // Attribute corresponds to the provider schema. + Attribute string + + // Field corresponds to the AWS API schema. + Field string + + // Expand, when not nil, is used to transform the value of the field + // given in Attribute before it's passed to the UpdateOperation. + Expand func(interface{}) interface{} + }{ + { + Attribute: "display_name", + Field: "displayName", + }, + { + Attribute: "locale", + Field: "locale", + }, + { + Attribute: "name.0.family_name", + Field: "name.familyName", + }, + { + Attribute: "name.0.formatted", + Field: "name.formatted", + }, + { + Attribute: "name.0.given_name", + Field: "name.givenName", + }, + { + Attribute: "name.0.honorific_prefix", + Field: "name.honorificPrefix", + }, + { + Attribute: "name.0.honorific_suffix", + Field: "name.honorificSuffix", + }, + { + Attribute: "name.0.middle_name", + Field: "name.middleName", + }, + { + Attribute: "nickname", + Field: "nickName", + }, + { + Attribute: "preferred_language", + Field: "preferredLanguage", + }, + { + Attribute: "profile_url", + Field: "profileUrl", + }, + { + Attribute: "timezone", + Field: "timezone", + }, + { + Attribute: "title", + Field: "title", + }, + { + Attribute: "user_type", + Field: "userType", + }, + { + Attribute: "addresses", + Field: "addresses", + Expand: func(value interface{}) interface{} { + addresses := expandAddresses(value.([]interface{})) + + var result []interface{} + + // The API requires a null to unset the list, so in the case + // of no addresses, a nil result is preferable. + for _, address := range addresses { + m := map[string]interface{}{} + + if v := address.Country; v != nil { + m["country"] = v + } + + if v := address.Formatted; v != nil { + m["formatted"] = v + } + + if v := address.Locality; v != nil { + m["locality"] = v + } + + if v := address.PostalCode; v != nil { + m["postalCode"] = v + } + + m["primary"] = address.Primary + + if v := address.Region; v != nil { + m["region"] = v + } + + if v := address.StreetAddress; v != nil { + m["streetAddress"] = v + } + + if v := address.Type; v != nil { + m["type"] = v + } + + result = append(result, m) + } + + return result + }, + }, + { + Attribute: "emails", + Field: "emails", + Expand: func(value interface{}) interface{} { + emails := expandEmails(value.([]interface{})) + + var result []interface{} + + // The API requires a null to unset the list, so in the case + // of no emails, a nil result is preferable. + for _, email := range emails { + m := map[string]interface{}{} + + m["primary"] = email.Primary + + if v := email.Type; v != nil { + m["type"] = v + } + + if v := email.Value; v != nil { + m["value"] = v + } + + result = append(result, m) + } + + return result + }, + }, + { + Attribute: "phone_numbers", + Field: "phoneNumbers", + Expand: func(value interface{}) interface{} { + emails := expandPhoneNumbers(value.([]interface{})) + + var result []interface{} + + // The API requires a null to unset the list, so in the case + // of no emails, a nil result is preferable. + for _, email := range emails { + m := map[string]interface{}{} + + m["primary"] = email.Primary + + if v := email.Type; v != nil { + m["type"] = v + } + + if v := email.Value; v != nil { + m["value"] = v + } + + result = append(result, m) + } + + return result + }, + }, + } + + for _, fieldToUpdate := range fieldsToUpdate { + if d.HasChange(fieldToUpdate.Attribute) { + value := d.Get(fieldToUpdate.Attribute) + + if expand := fieldToUpdate.Expand; expand != nil { + value = expand(value) + } + + // The API doesn't allow empty attribute values. To unset an + // attribute, set it to null. + if value == "" { + value = nil + } + + in.Operations = append(in.Operations, types.AttributeOperation{ + AttributePath: aws.String(fieldToUpdate.Field), + AttributeValue: document.NewLazyDocument(value), + }) + } + } + + if len(in.Operations) > 0 { + log.Printf("[DEBUG] Updating IdentityStore User (%s): %#v", d.Id(), in) + _, err := conn.UpdateUser(ctx, in) + if err != nil { + return create.DiagError(names.IdentityStore, create.ErrActionUpdating, ResNameUser, d.Id(), err) + } + } + + return resourceUserRead(ctx, d, meta) +} + +func resourceUserDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).IdentityStoreConn + + log.Printf("[INFO] Deleting IdentityStore User %s", d.Id()) + + _, err := conn.DeleteUser(ctx, &identitystore.DeleteUserInput{ + IdentityStoreId: aws.String(d.Get("identity_store_id").(string)), + UserId: aws.String(d.Get("user_id").(string)), + }) + + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + + return create.DiagError(names.IdentityStore, create.ErrActionDeleting, ResNameUser, d.Id(), err) + } + + return nil +} + +func findUserByID(ctx context.Context, conn *identitystore.Client, identityStoreId, userId string) (*identitystore.DescribeUserOutput, error) { + in := &identitystore.DescribeUserInput{ + IdentityStoreId: aws.String(identityStoreId), + UserId: aws.String(userId), + } + + out, err := conn.DescribeUser(ctx, in) + + if err != nil { + var e *types.ResourceNotFoundException + if errors.As(err, &e) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: in, + } + } else { + return nil, err + } + } + + if out == nil || out.UserId == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} + +func flattenAddress(apiObject *types.Address) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.Country; v != nil { + m["country"] = aws.ToString(v) + } + + if v := apiObject.Formatted; v != nil { + m["formatted"] = aws.ToString(v) + } + + if v := apiObject.Locality; v != nil { + m["locality"] = aws.ToString(v) + } + + if v := apiObject.PostalCode; v != nil { + m["postal_code"] = aws.ToString(v) + } + + m["primary"] = apiObject.Primary + + if v := apiObject.Region; v != nil { + m["region"] = aws.ToString(v) + } + + if v := apiObject.StreetAddress; v != nil { + m["street_address"] = aws.ToString(v) + } + + if v := apiObject.Type; v != nil { + m["type"] = aws.ToString(v) + } + + return m +} + +func expandAddress(tfMap map[string]interface{}) *types.Address { + if tfMap == nil { + return nil + } + + a := &types.Address{} + + if v, ok := tfMap["country"].(string); ok && v != "" { + a.Country = aws.String(v) + } + + if v, ok := tfMap["formatted"].(string); ok && v != "" { + a.Formatted = aws.String(v) + } + + if v, ok := tfMap["locality"].(string); ok && v != "" { + a.Locality = aws.String(v) + } + + if v, ok := tfMap["postal_code"].(string); ok && v != "" { + a.PostalCode = aws.String(v) + } + + a.Primary = tfMap["primary"].(bool) + + if v, ok := tfMap["region"].(string); ok && v != "" { + a.Region = aws.String(v) + } + + if v, ok := tfMap["street_address"].(string); ok && v != "" { + a.StreetAddress = aws.String(v) + } + + if v, ok := tfMap["type"].(string); ok && v != "" { + a.Type = aws.String(v) + } + + return a +} + +func flattenAddresses(apiObjects []types.Address) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var l []interface{} + + for _, apiObject := range apiObjects { + apiObject := apiObject + l = append(l, flattenAddress(&apiObject)) + } + + return l +} + +func expandAddresses(tfList []interface{}) []types.Address { + s := make([]types.Address, 0, len(tfList)) + + for _, r := range tfList { + m, ok := r.(map[string]interface{}) + + if !ok { + continue + } + + a := expandAddress(m) + + if a == nil { + continue + } + + s = append(s, *a) + } + + return s +} + +func flattenName(apiObject *types.Name) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.FamilyName; v != nil { + m["family_name"] = aws.ToString(v) + } + + if v := apiObject.Formatted; v != nil { + m["formatted"] = aws.ToString(v) + } + + if v := apiObject.GivenName; v != nil { + m["given_name"] = aws.ToString(v) + } + + if v := apiObject.HonorificPrefix; v != nil { + m["honorific_prefix"] = aws.ToString(v) + } + + if v := apiObject.HonorificSuffix; v != nil { + m["honorific_suffix"] = aws.ToString(v) + } + + if v := apiObject.MiddleName; v != nil { + m["middle_name"] = aws.ToString(v) + } + + return m +} + +func expandName(tfMap map[string]interface{}) *types.Name { + if tfMap == nil { + return nil + } + + a := &types.Name{} + + if v, ok := tfMap["family_name"].(string); ok && v != "" { + a.FamilyName = aws.String(v) + } + + if v, ok := tfMap["formatted"].(string); ok && v != "" { + a.Formatted = aws.String(v) + } + + if v, ok := tfMap["given_name"].(string); ok && v != "" { + a.GivenName = aws.String(v) + } + + if v, ok := tfMap["honorific_prefix"].(string); ok && v != "" { + a.HonorificPrefix = aws.String(v) + } + + if v, ok := tfMap["honorific_suffix"].(string); ok && v != "" { + a.HonorificSuffix = aws.String(v) + } + + if v, ok := tfMap["middle_name"].(string); ok && v != "" { + a.MiddleName = aws.String(v) + } + + return a +} + +func flattenEmail(apiObject *types.Email) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + m["primary"] = apiObject.Primary + + if v := apiObject.Type; v != nil { + m["type"] = aws.ToString(v) + } + + if v := apiObject.Value; v != nil { + m["value"] = aws.ToString(v) + } + + return m +} + +func expandEmail(tfMap map[string]interface{}) *types.Email { + if tfMap == nil { + return nil + } + + a := &types.Email{} + + a.Primary = tfMap["primary"].(bool) + + if v, ok := tfMap["type"].(string); ok && v != "" { + a.Type = aws.String(v) + } + + if v, ok := tfMap["value"].(string); ok && v != "" { + a.Value = aws.String(v) + } + + return a +} + +func flattenEmails(apiObjects []types.Email) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var l []interface{} + + for _, apiObject := range apiObjects { + apiObject := apiObject + l = append(l, flattenEmail(&apiObject)) + } + + return l +} + +func expandEmails(tfList []interface{}) []types.Email { + s := make([]types.Email, 0, len(tfList)) + + for _, r := range tfList { + m, ok := r.(map[string]interface{}) + + if !ok { + continue + } + + a := expandEmail(m) + + if a == nil { + continue + } + + s = append(s, *a) + } + + return s +} + +func flattenExternalId(apiObject *types.ExternalId) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.Id; v != nil { + m["id"] = aws.ToString(v) + } + + if v := apiObject.Issuer; v != nil { + m["issuer"] = aws.ToString(v) + } + + return m +} + +func flattenExternalIds(apiObjects []types.ExternalId) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var l []interface{} + + for _, apiObject := range apiObjects { + apiObject := apiObject + l = append(l, flattenExternalId(&apiObject)) + } + + return l +} + +func flattenPhoneNumber(apiObject *types.PhoneNumber) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + m["primary"] = apiObject.Primary + + if v := apiObject.Type; v != nil { + m["type"] = aws.ToString(v) + } + + if v := apiObject.Value; v != nil { + m["value"] = aws.ToString(v) + } + + return m +} + +func expandPhoneNumber(tfMap map[string]interface{}) *types.PhoneNumber { + if tfMap == nil { + return nil + } + + a := &types.PhoneNumber{} + + a.Primary = tfMap["primary"].(bool) + + if v, ok := tfMap["type"].(string); ok && v != "" { + a.Type = aws.String(v) + } + + if v, ok := tfMap["value"].(string); ok && v != "" { + a.Value = aws.String(v) + } + + return a +} + +func flattenPhoneNumbers(apiObjects []types.PhoneNumber) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var l []interface{} + + for _, apiObject := range apiObjects { + apiObject := apiObject + l = append(l, flattenPhoneNumber(&apiObject)) + } + + return l +} + +func expandPhoneNumbers(tfList []interface{}) []types.PhoneNumber { + s := make([]types.PhoneNumber, 0, len(tfList)) + + for _, r := range tfList { + m, ok := r.(map[string]interface{}) + + if !ok { + continue + } + + a := expandPhoneNumber(m) + + if a == nil { + continue + } + + s = append(s, *a) + } + + return s +} + +func resourceUserParseID(id string) (identityStoreId, userId string, err error) { + parts := strings.Split(id, "/") + + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + err = errors.New("expected a resource id in the form: identity-store-id/user-id") + return + } + + return parts[0], parts[1], nil +} diff --git a/internal/service/identitystore/user_test.go b/internal/service/identitystore/user_test.go new file mode 100644 index 00000000000..af06b02827c --- /dev/null +++ b/internal/service/identitystore/user_test.go @@ -0,0 +1,1534 @@ +package identitystore_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/identitystore" + "github.com/aws/aws-sdk-go-v2/service/identitystore/types" + "github.com/aws/aws-sdk-go/service/ssoadmin" + sdkacctest "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" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tfidentitystore "github.com/hashicorp/terraform-provider-aws/internal/service/identitystore" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccIdentityStoreUser_basic(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "addresses.#", "0"), + resource.TestCheckResourceAttr(resourceName, "display_name", "Acceptance Test"), + resource.TestCheckResourceAttr(resourceName, "emails.#", "0"), + resource.TestCheckResourceAttr(resourceName, "external_ids.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "identity_store_id"), + resource.TestCheckResourceAttr(resourceName, "locale", ""), + resource.TestCheckResourceAttr(resourceName, "name.0.family_name", "Doe"), + resource.TestCheckResourceAttr(resourceName, "name.0.formatted", ""), + resource.TestCheckResourceAttr(resourceName, "name.0.given_name", "John"), + resource.TestCheckResourceAttr(resourceName, "name.0.honorific_prefix", ""), + resource.TestCheckResourceAttr(resourceName, "name.0.honorific_suffix", ""), + resource.TestCheckResourceAttr(resourceName, "name.0.middle_name", ""), + resource.TestCheckResourceAttr(resourceName, "nickname", ""), + resource.TestCheckResourceAttr(resourceName, "phone_numbers.#", "0"), + resource.TestCheckResourceAttr(resourceName, "preferred_language", ""), + resource.TestCheckResourceAttr(resourceName, "profile_url", ""), + resource.TestCheckResourceAttr(resourceName, "timezone", ""), + resource.TestCheckResourceAttr(resourceName, "title", ""), + resource.TestCheckResourceAttrSet(resourceName, "user_id"), + resource.TestCheckResourceAttr(resourceName, "user_name", rName), + resource.TestCheckResourceAttr(resourceName, "user_type", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccIdentityStoreUser_disappears(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheckSSOAdminInstances(t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + acctest.CheckResourceDisappears(acctest.Provider, tfidentitystore.ResourceUser(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccIdentityStoreUser_Addresses(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_addresses2(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "addresses.#", "1"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.country", "US"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.formatted", "Formatted Address 1"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.locality", "The Locality 1"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.postal_code", "AAA BBB 1"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.primary", "true"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.region", "The Region 1"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.street_address", "The Street Address 1"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.type", "The Type 1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_addresses3(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "addresses.#", "1"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.country", "GB"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.formatted", "Formatted Address 2"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.locality", "The Locality 2"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.postal_code", "AAA BBB 2"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.primary", "false"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.region", "The Region 2"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.street_address", "The Street Address 2"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.type", "The Type 2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_addresses1(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "addresses.#", "1"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.country", "US"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.formatted", ""), + resource.TestCheckResourceAttr(resourceName, "addresses.0.locality", ""), + resource.TestCheckResourceAttr(resourceName, "addresses.0.postal_code", ""), + resource.TestCheckResourceAttr(resourceName, "addresses.0.primary", "false"), + resource.TestCheckResourceAttr(resourceName, "addresses.0.region", ""), + resource.TestCheckResourceAttr(resourceName, "addresses.0.street_address", ""), + resource.TestCheckResourceAttr(resourceName, "addresses.0.type", "Home"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "addresses.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccIdentityStoreUser_Emails(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + email1 := acctest.RandomEmailAddress(acctest.RandomDomainName()) + email2 := acctest.RandomEmailAddress(acctest.RandomDomainName()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_emails1(rName, email1), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "emails.#", "1"), + resource.TestCheckResourceAttr(resourceName, "emails.0.primary", "true"), + resource.TestCheckResourceAttr(resourceName, "emails.0.type", "The Type 1"), + resource.TestCheckResourceAttr(resourceName, "emails.0.value", email1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_emails2(rName, email2), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "emails.#", "1"), + resource.TestCheckResourceAttr(resourceName, "emails.0.primary", "false"), + resource.TestCheckResourceAttr(resourceName, "emails.0.type", "The Type 2"), + resource.TestCheckResourceAttr(resourceName, "emails.0.value", email2), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_emails3(rName, email2), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "emails.#", "1"), + resource.TestCheckResourceAttr(resourceName, "emails.0.primary", "false"), + resource.TestCheckResourceAttr(resourceName, "emails.0.type", ""), + resource.TestCheckResourceAttr(resourceName, "emails.0.value", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "emails.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccIdentityStoreUser_Locale(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_locale(rName, "en-US"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "locale", "en-US"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_locale(rName, "en-GB"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "locale", "en-GB"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "locale", ""), + ), + }, + }, + }) +} + +func TestAccIdentityStoreUser_NameFamilyName(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_nameFamilyName(rName, "Doe"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.family_name", "Doe"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_nameFamilyName(rName, "Deer"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.family_name", "Deer"), + ), + }, + }, + }) +} + +func TestAccIdentityStoreUser_NameFormatted(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_nameFormatted(rName, "JD1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.formatted", "JD1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_nameFormatted(rName, "JD2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.formatted", "JD2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.formatted", ""), + ), + }, + }, + }) +} + +func TestAccIdentityStoreUser_NameGivenName(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_nameGivenName(rName, "John"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.given_name", "John"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_nameGivenName(rName, "Jane"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.given_name", "Jane"), + ), + }, + }, + }) +} + +func TestAccIdentityStoreUser_NameHonorificPrefix(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_nameHonorificPrefix(rName, "Dr."), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.honorific_prefix", "Dr."), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_nameHonorificPrefix(rName, "Mr."), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.honorific_prefix", "Mr."), + ), + }, + }, + }) +} + +func TestAccIdentityStoreUser_NameHonorificSuffix(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_nameHonorificSuffix(rName, "M.D."), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.honorific_suffix", "M.D."), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_nameHonorificSuffix(rName, "MSc"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.honorific_suffix", "MSc"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.honorific_suffix", ""), + ), + }, + }, + }) +} + +func TestAccIdentityStoreUser_NameMiddleName(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_nameMiddleName(rName, "Howard"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.middle_name", "Howard"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_nameMiddleName(rName, "Ben"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.middle_name", "Ben"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "name.0.middle_name", ""), + ), + }, + }, + }) +} + +func TestAccIdentityStoreUser_NickName(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_nickName(rName, "JD"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "nickname", "JD"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_nickName(rName, "Johnny"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "nickname", "Johnny"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "nickname", ""), + ), + }, + }, + }) +} + +func TestAccIdentityStoreUser_PhoneNumbers(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_phoneNumbers1(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "phone_numbers.#", "1"), + resource.TestCheckResourceAttr(resourceName, "phone_numbers.0.primary", "true"), + resource.TestCheckResourceAttr(resourceName, "phone_numbers.0.type", "The Type 1"), + resource.TestCheckResourceAttr(resourceName, "phone_numbers.0.value", "111111"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_phoneNumbers2(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "phone_numbers.#", "1"), + resource.TestCheckResourceAttr(resourceName, "phone_numbers.0.primary", "false"), + resource.TestCheckResourceAttr(resourceName, "phone_numbers.0.type", "The Type 2"), + resource.TestCheckResourceAttr(resourceName, "phone_numbers.0.value", "2222222"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_phoneNumbers3(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "phone_numbers.#", "1"), + resource.TestCheckResourceAttr(resourceName, "phone_numbers.0.primary", "false"), + resource.TestCheckResourceAttr(resourceName, "phone_numbers.0.type", ""), + resource.TestCheckResourceAttr(resourceName, "phone_numbers.0.value", "2222222"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "addresses.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccIdentityStoreUser_PreferredLanguage(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_preferredLanguage(rName, "EN"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "preferred_language", "EN"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_preferredLanguage(rName, "ET"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "preferred_language", "ET"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "preferred_language", ""), + ), + }, + }, + }) +} + +func TestAccIdentityStoreUser_ProfileURL(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_profileURL(rName, "http://example.com/1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "profile_url", "http://example.com/1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_profileURL(rName, "http://example.com/2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "profile_url", "http://example.com/2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "profile_url", ""), + ), + }, + }, + }) +} + +func TestAccIdentityStoreUser_Timezone(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_timezone(rName, "UTC"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_timezone(rName, "Europe/London"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "timezone", "Europe/London"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "timezone", ""), + ), + }, + }, + }) +} + +func TestAccIdentityStoreUser_Title(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_title(rName, "Mr"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "title", "Mr"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_title(rName, "Ms"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "title", "Ms"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "title", ""), + ), + }, + }, + }) +} + +func TestAccIdentityStoreUser_UserType(t *testing.T) { + var user identitystore.DescribeUserOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_identitystore_user.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.IdentityStoreEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IdentityStoreEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_userType(rName, "Member"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "user_type", "Member"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_userType(rName, "Admin"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "user_type", "Admin"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "user_type", ""), + ), + }, + }, + }) +} + +func testAccCheckUserDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).IdentityStoreConn + ctx := context.Background() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_identitystore_user" { + continue + } + + _, err := conn.DescribeUser(ctx, &identitystore.DescribeUserInput{ + IdentityStoreId: aws.String(rs.Primary.Attributes["identity_store_id"]), + UserId: aws.String(rs.Primary.Attributes["user_id"]), + }) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + return err + } + + return create.Error(names.IdentityStore, create.ErrActionCheckingDestroyed, tfidentitystore.ResNameUser, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil +} + +func testAccCheckUserExists(name string, user *identitystore.DescribeUserOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.IdentityStore, create.ErrActionCheckingExistence, tfidentitystore.ResNameUser, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.IdentityStore, create.ErrActionCheckingExistence, tfidentitystore.ResNameUser, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).IdentityStoreConn + ctx := context.Background() + resp, err := conn.DescribeUser(ctx, &identitystore.DescribeUserInput{ + IdentityStoreId: aws.String(rs.Primary.Attributes["identity_store_id"]), + UserId: aws.String(rs.Primary.Attributes["user_id"]), + }) + + if err != nil { + return create.Error(names.IdentityStore, create.ErrActionCheckingExistence, tfidentitystore.ResNameUser, rs.Primary.ID, err) + } + + *user = *resp + + return nil + } +} + +func testAccPreCheck(t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).IdentityStoreConn + ssoadminConn := acctest.Provider.Meta().(*conns.AWSClient).SSOAdminConn + ctx := context.Background() + + instances, err := ssoadminConn.ListInstances(&ssoadmin.ListInstancesInput{MaxResults: aws.Int64(1)}) + + if err != nil { + t.Fatalf("failed to list SSO instances: %s", err) + } + + if len(instances.Instances) != 1 { + t.Fatalf("expected to find at least one SSO instance") + } + + input := &identitystore.ListUsersInput{ + IdentityStoreId: instances.Instances[0].IdentityStoreId, + } + + _, err = conn.ListUsers(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccUserConfig_basic(rName string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "Doe" + given_name = "John" + } +} +`, rName) +} + +func testAccUserConfig_addresses1(rName string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "Doe" + given_name = "John" + } + + addresses { + country = "US" + type = "Home" + } +} +`, rName) +} + +func testAccUserConfig_addresses2(rName string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "Doe" + given_name = "John" + } + + addresses { + country = "US" + formatted = "Formatted Address 1" + locality = "The Locality 1" + postal_code = "AAA BBB 1" + primary = true + region = "The Region 1" + street_address = "The Street Address 1" + type = "The Type 1" + } +} +`, rName) +} + +func testAccUserConfig_addresses3(rName string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "Doe" + given_name = "John" + } + + addresses { + country = "GB" + formatted = "Formatted Address 2" + locality = "The Locality 2" + postal_code = "AAA BBB 2" + primary = false + region = "The Region 2" + street_address = "The Street Address 2" + type = "The Type 2" + } +} +`, rName) +} + +func testAccUserConfig_emails1(rName, email string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "John" + given_name = "Doe" + } + + emails { + primary = true + type = "The Type 1" + value = %[2]q + } +} +`, rName, email) +} + +func testAccUserConfig_emails2(rName, email string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "John" + given_name = "Doe" + } + + emails { + primary = false + type = "The Type 2" + value = %[2]q + } +} +`, rName, email) +} + +func testAccUserConfig_emails3(rName, email string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "John" + given_name = "Doe" + } + + emails {} +} +`, rName, email) +} + +func testAccUserConfig_locale(rName, locale string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + locale = %[2]q + + name { + family_name = "Doe" + given_name = "John" + } +} +`, rName, locale) +} + +func testAccUserConfig_nameFamilyName(rName, familyName string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = %[2]q + given_name = "John" + } +} +`, rName, familyName) +} + +func testAccUserConfig_nameFormatted(rName, formatted string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "Doe" + formatted = %[2]q + given_name = "John" + } +} +`, rName, formatted) +} + +func testAccUserConfig_nameGivenName(rName, givenName string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "Doe" + given_name = %[2]q + } +} +`, rName, givenName) +} + +func testAccUserConfig_nameHonorificPrefix(rName, honorificPrefix string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "Doe" + given_name = "John" + honorific_prefix = %[2]q + } +} +`, rName, honorificPrefix) +} + +func testAccUserConfig_nameHonorificSuffix(rName, honorificSuffix string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "Doe" + given_name = "John" + honorific_suffix = %[2]q + } +} +`, rName, honorificSuffix) +} + +func testAccUserConfig_nameMiddleName(rName, middleName string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "Doe" + given_name = "John" + middle_name = %[2]q + } +} +`, rName, middleName) +} + +func testAccUserConfig_nickName(rName, nickName string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + nickname = %[2]q + + name { + family_name = "Doe" + given_name = "John" + } +} +`, rName, nickName) +} + +func testAccUserConfig_phoneNumbers1(rName string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "John" + given_name = "Doe" + } + + phone_numbers { + primary = true + type = "The Type 1" + value = "111111" + } +} +`, rName) +} + +func testAccUserConfig_phoneNumbers2(rName string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "John" + given_name = "Doe" + } + + phone_numbers { + primary = false + type = "The Type 2" + value = "2222222" + } +} +`, rName) +} + +func testAccUserConfig_phoneNumbers3(rName string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + name { + family_name = "John" + given_name = "Doe" + } + + phone_numbers { + value = "2222222" + } +} +`, rName) +} + +func testAccUserConfig_preferredLanguage(rName, preferredLanguage string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + preferred_language = %[2]q + + name { + family_name = "Doe" + given_name = "John" + } +} +`, rName, preferredLanguage) +} + +func testAccUserConfig_profileURL(rName, profileUrl string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + profile_url = %[2]q + + name { + family_name = "Doe" + given_name = "John" + } +} +`, rName, profileUrl) +} + +func testAccUserConfig_timezone(rName, timezone string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + timezone = %[2]q + + name { + family_name = "Doe" + given_name = "John" + } +} +`, rName, timezone) +} + +func testAccUserConfig_title(rName, title string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + title = %[2]q + + name { + family_name = "Doe" + given_name = "John" + } +} +`, rName, title) +} + +func testAccUserConfig_userType(rName, userType string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_identitystore_user" "test" { + identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0] + + display_name = "Acceptance Test" + user_name = %[1]q + + user_type = %[2]q + + name { + family_name = "Doe" + given_name = "John" + } +} +`, rName, userType) +} diff --git a/website/docs/r/identitystore_user.html.markdown b/website/docs/r/identitystore_user.html.markdown new file mode 100644 index 00000000000..2599b08fce7 --- /dev/null +++ b/website/docs/r/identitystore_user.html.markdown @@ -0,0 +1,116 @@ +--- +subcategory: "SSO Identity Store" +layout: "aws" +page_title: "AWS: aws_identitystore_user" +description: |- + Terraform resource for managing an AWS Identity Store User. +--- + +# Resource: aws_identitystore_user + +This resource manages a User resource within an Identity Store. + +-> **Note:** If you use an external identity provider or Active Directory as your identity source, +use this resource with caution. IAM Identity Center does not support outbound synchronization, +so your identity source does not automatically update with the changes that you make to +users using this resource. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_identitystore_user" "example" { + identity_store_id = tolist(data.aws_ssoadmin_instances.example.identity_store_ids)[0] + + display_name = "John Doe" + user_name = "johndoe" + + name { + given_name = "John" + family_name = "Doe" + } + + emails { + value = "john@example.com" + } +} +``` + +## Argument Reference + +-> Unless specified otherwise, all fields can contain up to 1024 characters of free-form text. + +The following arguments are required: + +* `display_name` - (Required) The name that is typically displayed when the user is referenced. +* `identity_store_id` - (Required, Forces new resource) The globally unique identifier for the identity store that this user is in. +* `name` - (Required) Details about the user's full name. Detailed below. +* `user_name` - (Required, Forces new resource) A unique string used to identify the user. This value can consist of letters, accented characters, symbols, numbers, and punctuation. This value is specified at the time the user is created and stored as an attribute of the user object in the identity store. The limit is 128 characters. + +The following arguments are optional: + +* `addresses` - (Optional) Details about the user's address. At most 1 address is allowed. Detailed below. +* `emails` - (Optional) Details about the user's email. At most 1 email is allowed. Detailed below. +* `locale` - (Optional) The user's geographical region or location. +* `nickname` - (Optional) An alternate name for the user. +* `phone_numbers` - (Optional) Details about the user's phone number. At most 1 phone number is allowed. Detailed below. +* `preferred_language` - (Optional) The preferred language of the user. +* `profile_url` - (Optional) An URL that may be associated with the user. +* `timezone` - (Optional) The user's time zone. +* `title` - (Optional) The user's title. +* `user_type` - (Optional) The user type. + +### addresses Configuration Block + +* `country` - (Optional) The country that this address is in. +* `formatted` - (Optional) The name that is typically displayed when the address is shown for display. +* `locality` - (Optional) The address locality. +* `postal_code` - (Optional) The postal code of the address. +* `primary` - (Optional) When `true`, this is the primary address associated with the user. +* `region` - (Optional) The region of the address. +* `street_address` - (Optional) The street of the address. +* `type` - (Optional) The type of address. + +### emails Configuration Block + +* `primary` - (Optional) When `true`, this is the primary email associated with the user. +* `type` - (Optional) The type of email. +* `value` - (Optional) The email address. This value must be unique across the identity store. + +### emails Configuration Block + +The following arguments are required: + +* `family_name` - (Required) The family name of the user. +* `given_name` - (Required) The given name of the user. + +The following arguments are optional: + +* `formatted` - (Optional) The name that is typically displayed when the name is shown for display. +* `honorific_prefix` - (Optional) The honorific prefix of the user. +* `honorific_suffix` - (Optional) The honorific suffix of the user. +* `middle_name` - (Optional) The middle name of the user. + +### phone_numbers Configuration Block + +* `primary` - (Optional) When `true`, this is the primary phone number associated with the user. +* `type` - (Optional) The type of phone number. +* `value` - (Optional) The user's phone number. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `external_ids` - A list of identifiers issued to this resource by an external identity provider. + * `id` - The identifier issued to this resource by an external identity provider. + * `issuer` - The issuer for an external identifier. +* `user_id` - The identifier for this user in the identity store. + +## Import + +An Identity Store User can be imported using the combination `identity_store_id/user_id`. For example: + +``` +$ terraform import aws_identitystore_user.example d-9c6705e95c/065212b4-9061-703b-5876-13a517ae2a7c +```