diff --git a/.changelog/26152.txt b/.changelog/26152.txt new file mode 100644 index 000000000000..e7c2d6c7a885 --- /dev/null +++ b/.changelog/26152.txt @@ -0,0 +1,3 @@ +```release-note:new-resource + aws_connect_instance_storage_config + ``` \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e2d2ef12dd7f..ba3bd20e2a9e 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1190,6 +1190,7 @@ func New(_ context.Context) (*schema.Provider, error) { "aws_connect_contact_flow": connect.ResourceContactFlow(), "aws_connect_contact_flow_module": connect.ResourceContactFlowModule(), "aws_connect_instance": connect.ResourceInstance(), + "aws_connect_instance_storage_config": connect.ResourceInstanceStorageConfig(), "aws_connect_hours_of_operation": connect.ResourceHoursOfOperation(), "aws_connect_lambda_function_association": connect.ResourceLambdaFunctionAssociation(), "aws_connect_queue": connect.ResourceQueue(), diff --git a/internal/service/connect/bot_association_data_source_test.go b/internal/service/connect/bot_association_data_source_test.go index 5a72d9bdb482..079bf6ed60b0 100644 --- a/internal/service/connect/bot_association_data_source_test.go +++ b/internal/service/connect/bot_association_data_source_test.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" ) -func TestAccConnectBotAssociationDataSource_basic(t *testing.T) { +func testAccBotAssociationDataSource_basic(t *testing.T) { rName := sdkacctest.RandStringFromCharSet(8, sdkacctest.CharSetAlpha) rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_connect_bot_association.test" diff --git a/internal/service/connect/bot_association_test.go b/internal/service/connect/bot_association_test.go index 949dfa4068c5..5f183f0b8a73 100644 --- a/internal/service/connect/bot_association_test.go +++ b/internal/service/connect/bot_association_test.go @@ -15,21 +15,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) -func TestAccConnectBotAssociation_serial(t *testing.T) { - testCases := map[string]func(t *testing.T){ - "basic": testAccBotAssociation_basic, - "disappears": testAccBotAssociation_disappears, - } - - for name, tc := range testCases { - tc := tc - t.Run(name, func(t *testing.T) { - tc(t) - }) - } -} - func testAccBotAssociation_basic(t *testing.T) { rName := sdkacctest.RandStringFromCharSet(8, sdkacctest.CharSetAlpha) rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) diff --git a/internal/service/connect/connect_test.go b/internal/service/connect/connect_test.go new file mode 100644 index 000000000000..f9bf7dd60b41 --- /dev/null +++ b/internal/service/connect/connect_test.go @@ -0,0 +1,139 @@ +package connect_test + +import "testing" + +// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) +func TestAccConnect_serial(t *testing.T) { + testCases := map[string]map[string]func(t *testing.T){ + "BotAssociation": { + "basic": testAccBotAssociation_basic, + "disappears": testAccBotAssociation_disappears, + "dataSource_basic": testAccBotAssociationDataSource_basic, + }, + "ContactFlow": { + "basic": testAccContactFlow_basic, + "disappears": testAccContactFlow_disappears, + "filename": testAccContactFlow_filename, + "dataSource_id": testAccContactFlowDataSource_contactFlowID, + "dataSource_name": testAccContactFlowDataSource_name, + }, + "ContactFlowModule": { + "basic": testAccContactFlowModule_basic, + "disappears": testAccContactFlowModule_disappears, + "filename": testAccContactFlowModule_filename, + "dataSource_id": testAccContactFlowModuleDataSource_contactFlowModuleID, + "dataSource_name": testAccContactFlowModuleDataSource_name, + }, + "HoursOfOperation": { + "basic": testAccHoursOfOperation_basic, + "disappears": testAccHoursOfOperation_disappears, + "tags": testAccHoursOfOperation_updateTags, + "config": testAccHoursOfOperation_updateConfig, + "dataSource_id": testAccHoursOfOperationDataSource_hoursOfOperationID, + "dataSource_name": testAccHoursOfOperationDataSource_name, + }, + "Instance": { + "basic": testAccInstance_basic, + "directory": testAccInstance_directory, + "saml": testAccInstance_saml, + "dataSource_basic": testAccInstanceDataSource_basic, + }, + "InstanceStorageConfig": { + "basic": testAccInstanceStorageConfig_basic, + "disappears": testAccInstanceStorageConfig_disappears, + "KinesisFirehoseConfig_FirehoseARN": testAccInstanceStorageConfig_KinesisFirehoseConfig_FirehoseARN, + "KinesisStreamConfig_StreamARN": testAccInstanceStorageConfig_KinesisStreamConfig_StreamARN, + "KinesisVideoStreamConfig_EncryptionConfig": testAccInstanceStorageConfig_KinesisVideoStreamConfig_EncryptionConfig, + "KinesisVideoStreamConfig_Prefix": testAccInstanceStorageConfig_KinesisVideoStreamConfig_Prefix, + "KinesisVideoStreamConfig_Retention": testAccInstanceStorageConfig_KinesisVideoStreamConfig_Retention, + "S3Config_BucketName": testAccInstanceStorageConfig_S3Config_BucketName, + "S3Config_BucketPrefix": testAccInstanceStorageConfig_S3Config_BucketPrefix, + "S3Config_EncryptionConfig": testAccInstanceStorageConfig_S3Config_EncryptionConfig, + }, + "LambdaFunctionAssociation": { + "basic": testAccLambdaFunctionAssociation_basic, + "disappears": testAccLambdaFunctionAssociation_disappears, + "dataSource_basic": testAccLambdaFunctionAssociationDataSource_basic, + }, + "Prompt": { + "dataSource_name": testAccPromptDataSource_name, + }, + "Queue": { + "basic": testAccQueue_basic, + "disappears": testAccQueue_disappears, + "tags": testAccQueue_updateTags, + "hoursOfOperationId": testAccQueue_updateHoursOfOperationId, + "maxContacts": testAccQueue_updateMaxContacts, + "outboundCallerConfig": testAccQueue_updateOutboundCallerConfig, + "status": testAccQueue_updateStatus, + "quickConnectIds": testAccQueue_updateQuickConnectIds, + "dataSource_id": testAccQueueDataSource_queueID, + "dataSource_name": testAccQueueDataSource_name, + }, + "QuickConnect": { + "basic": testAccQuickConnect_phoneNumber, + "disappears": testAccQuickConnect_disappears, + "tags": testAccQuickConnect_updateTags, + "dataSource_id": testAccQuickConnectDataSource_id, + "dataSource_name": testAccQuickConnectDataSource_name, + }, + "RoutingProfile": { + "basic": testAccRoutingProfile_basic, + "disappears": testAccRoutingProfile_disappears, + "tags": testAccRoutingProfile_updateTags, + "concurrency": testAccRoutingProfile_updateConcurrency, + "defaultOutboundQueue": testAccRoutingProfile_updateDefaultOutboundQueue, + "queues": testAccRoutingProfile_updateQueues, + "dataSource_id": testAccRoutingProfileDataSource_routingProfileID, + "dataSource_name": testAccRoutingProfileDataSource_name, + }, + "SecurityProfile": { + "basic": testAccSecurityProfile_basic, + "disappears": testAccSecurityProfile_disappears, + "tags": testAccSecurityProfile_updateTags, + "permissions": testAccSecurityProfile_updatePermissions, + "dataSource_id": testAccSecurityProfileDataSource_securityProfileID, + "dataSource_name": testAccSecurityProfileDataSource_name, + }, + "User": { + "basic": testAccUser_basic, + "disappears": testAccUser_disappears, + "tags": testAccUser_updateTags, + "hierarchyGroupId": testAccUser_updateHierarchyGroupId, + "identityInfo": testAccUser_updateIdentityInfo, + "phoneConfig": testAccUser_updatePhoneConfig, + "routingProfileId": testAccUser_updateRoutingProfileId, + "securityProfileIds": testAccUser_updateSecurityProfileIds, + }, + "UserHierarchyGroup": { + "basic": testAccUserHierarchyGroup_basic, + "disappears": testAccUserHierarchyGroup_disappears, + "updateTags": testAccUserHierarchyGroup_updateTags, + "parentGroupId": testAccUserHierarchyGroup_parentGroupId, + "dataSource_id": testAccUserHierarchyGroupDataSource_hierarchyGroupID, + "dataSource_name": testAccUserHierarchyGroupDataSource_name, + }, + "UserHierarchyStructure": { + "basic": testAccUserHierarchyStructure_basic, + "disappears": testAccUserHierarchyStructure_disappears, + "dataSource_id": testAccUserHierarchyStructureDataSource_instanceID, + }, + "Vocabulary": { + "basic": testAccVocabulary_basic, + "disappears": testAccVocabulary_disappears, + "tags": testAccVocabulary_updateTags, + }, + } + + for group, m := range testCases { + m := m + t.Run(group, func(t *testing.T) { + for name, tc := range m { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } + }) + } +} diff --git a/internal/service/connect/contact_flow_data_source_test.go b/internal/service/connect/contact_flow_data_source_test.go index dd68c972f878..b1402911b489 100644 --- a/internal/service/connect/contact_flow_data_source_test.go +++ b/internal/service/connect/contact_flow_data_source_test.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" ) -func TestAccConnectContactFlowDataSource_contactFlowID(t *testing.T) { +func testAccContactFlowDataSource_contactFlowID(t *testing.T) { rName := sdkacctest.RandomWithPrefix("resource-test-terraform") resourceName := "aws_connect_contact_flow.test" datasourceName := "data.aws_connect_contact_flow.test" @@ -38,7 +38,7 @@ func TestAccConnectContactFlowDataSource_contactFlowID(t *testing.T) { }) } -func TestAccConnectContactFlowDataSource_name(t *testing.T) { +func testAccContactFlowDataSource_name(t *testing.T) { rName := sdkacctest.RandomWithPrefix("resource-test-terraform") rName2 := sdkacctest.RandomWithPrefix("resource-test-terraform") resourceName := "aws_connect_contact_flow.test" diff --git a/internal/service/connect/contact_flow_module_data_source_test.go b/internal/service/connect/contact_flow_module_data_source_test.go index da85152eba72..4b9d75a3c9ef 100644 --- a/internal/service/connect/contact_flow_module_data_source_test.go +++ b/internal/service/connect/contact_flow_module_data_source_test.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" ) -func TestAccConnectContactFlowModuleDataSource_contactFlowModuleID(t *testing.T) { +func testAccContactFlowModuleDataSource_contactFlowModuleID(t *testing.T) { rName := sdkacctest.RandomWithPrefix("resource-test-terraform") resourceName := "aws_connect_contact_flow_module.test" datasourceName := "data.aws_connect_contact_flow_module.test" @@ -39,7 +39,7 @@ func TestAccConnectContactFlowModuleDataSource_contactFlowModuleID(t *testing.T) }) } -func TestAccConnectContactFlowModuleDataSource_name(t *testing.T) { +func testAccContactFlowModuleDataSource_name(t *testing.T) { rName := sdkacctest.RandomWithPrefix("resource-test-terraform") rName2 := sdkacctest.RandomWithPrefix("resource-test-terraform") resourceName := "aws_connect_contact_flow_module.test" diff --git a/internal/service/connect/contact_flow_module_test.go b/internal/service/connect/contact_flow_module_test.go index daa554fc0e65..5fa613dd9f09 100644 --- a/internal/service/connect/contact_flow_module_test.go +++ b/internal/service/connect/contact_flow_module_test.go @@ -15,22 +15,6 @@ import ( tfconnect "github.com/hashicorp/terraform-provider-aws/internal/service/connect" ) -// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) -func TestAccConnectContactFlowModule_serial(t *testing.T) { - testCases := map[string]func(t *testing.T){ - "basic": testAccContactFlowModule_basic, - "filename": testAccContactFlowModule_filename, - "disappears": testAccContactFlowModule_disappears, - } - - for name, tc := range testCases { - tc := tc - t.Run(name, func(t *testing.T) { - tc(t) - }) - } -} - func testAccContactFlowModule_basic(t *testing.T) { var v connect.DescribeContactFlowModuleOutput rName := sdkacctest.RandomWithPrefix("resource-test-terraform") diff --git a/internal/service/connect/contact_flow_test.go b/internal/service/connect/contact_flow_test.go index 7352af787e92..14549e03ef1a 100644 --- a/internal/service/connect/contact_flow_test.go +++ b/internal/service/connect/contact_flow_test.go @@ -15,22 +15,6 @@ import ( tfconnect "github.com/hashicorp/terraform-provider-aws/internal/service/connect" ) -// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) -func TestAccConnectContactFlow_serial(t *testing.T) { - testCases := map[string]func(t *testing.T){ - "basic": testAccContactFlow_basic, - "filename": testAccContactFlow_filename, - "disappears": testAccContactFlow_disappears, - } - - for name, tc := range testCases { - tc := tc - t.Run(name, func(t *testing.T) { - tc(t) - }) - } -} - func testAccContactFlow_basic(t *testing.T) { var v connect.DescribeContactFlowOutput rName := sdkacctest.RandomWithPrefix("resource-test-terraform") @@ -247,7 +231,7 @@ resource "aws_connect_contact_flow" "test" { { "Version": "2019-10-30", "StartAction": "12345678-1234-1234-1234-123456789012", - "Actions": [ + "Actions": [ { "Identifier": "12345678-1234-1234-1234-123456789012", "Type": "MessageParticipant", diff --git a/internal/service/connect/hours_of_operation_data_source_test.go b/internal/service/connect/hours_of_operation_data_source_test.go index 222133927fad..0853387a90d4 100644 --- a/internal/service/connect/hours_of_operation_data_source_test.go +++ b/internal/service/connect/hours_of_operation_data_source_test.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" ) -func TestAccConnectHoursOfOperationDataSource_hoursOfOperationID(t *testing.T) { +func testAccHoursOfOperationDataSource_hoursOfOperationID(t *testing.T) { rName := sdkacctest.RandomWithPrefix("resource-test-terraform") resourceName := "aws_connect_hours_of_operation.test" datasourceName := "data.aws_connect_hours_of_operation.test" @@ -38,7 +38,7 @@ func TestAccConnectHoursOfOperationDataSource_hoursOfOperationID(t *testing.T) { }) } -func TestAccConnectHoursOfOperationDataSource_name(t *testing.T) { +func testAccHoursOfOperationDataSource_name(t *testing.T) { rName := sdkacctest.RandomWithPrefix("resource-test-terraform") rName2 := sdkacctest.RandomWithPrefix("resource-test-terraform") resourceName := "aws_connect_hours_of_operation.test" diff --git a/internal/service/connect/hours_of_operation_test.go b/internal/service/connect/hours_of_operation_test.go index 4c1e5dfcb8a9..adcaeb8f7c8c 100644 --- a/internal/service/connect/hours_of_operation_test.go +++ b/internal/service/connect/hours_of_operation_test.go @@ -15,23 +15,6 @@ import ( tfconnect "github.com/hashicorp/terraform-provider-aws/internal/service/connect" ) -// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) -func TestAccConnectHoursOfOperation_serial(t *testing.T) { - testCases := map[string]func(t *testing.T){ - "basic": testAccHoursOfOperation_basic, - "disappears": testAccHoursOfOperation_disappears, - "updateConfig": testAccHoursOfOperation_updateConfig, - "updateTags": testAccHoursOfOperation_updateTags, - } - - for name, tc := range testCases { - tc := tc - t.Run(name, func(t *testing.T) { - tc(t) - }) - } -} - func testAccHoursOfOperation_basic(t *testing.T) { var v connect.DescribeHoursOfOperationOutput rName := sdkacctest.RandomWithPrefix("resource-test-terraform") diff --git a/internal/service/connect/instance_data_source_test.go b/internal/service/connect/instance_data_source_test.go index 80002964a8de..769d0ab59cac 100644 --- a/internal/service/connect/instance_data_source_test.go +++ b/internal/service/connect/instance_data_source_test.go @@ -11,11 +11,11 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" ) -func TestAccConnectInstanceDataSource_basic(t *testing.T) { +func testAccInstanceDataSource_basic(t *testing.T) { rName := sdkacctest.RandomWithPrefix("datasource-test-terraform") dataSourceName := "data.aws_connect_instance.test" resourceName := "aws_connect_instance.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, connect.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, diff --git a/internal/service/connect/instance_storage_config.go b/internal/service/connect/instance_storage_config.go new file mode 100644 index 000000000000..13f133bbef1f --- /dev/null +++ b/internal/service/connect/instance_storage_config.go @@ -0,0 +1,527 @@ +package connect + +import ( + "context" + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/connect" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "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/verify" +) + +func ResourceInstanceStorageConfig() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceInstanceStorageConfigCreate, + ReadContext: resourceInstanceStorageConfigRead, + UpdateContext: resourceInstanceStorageConfigUpdate, + DeleteContext: resourceInstanceStorageConfigDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "association_id": { + Type: schema.TypeString, + Computed: true, + }, + "instance_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 100), + }, + "resource_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(connect.InstanceStorageResourceType_Values(), false), + }, + "storage_config": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kinesis_firehose_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "firehose_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, + "kinesis_stream_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "stream_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, + "kinesis_video_stream_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "encryption_config": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "encryption_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(connect.EncryptionType_Values(), false), + }, + "key_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, + "prefix": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 128), + // API returns -connect--contact- + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // API returns -connect--contact- + // case 1: API appends to prefix. User-defined string (old) is prefix of API-returned string (new). Check non-empty old in resoure creation scenario + // case 2: after setting API-returned string. User-defined string (new) is prefix of API-returned string (old) + // case 3: update for other arguments that still require the prefix to be sent in the request + return (strings.HasPrefix(new, old) && old != "") || (strings.HasPrefix(old, new) && !d.HasChange("storage_config.0.kinesis_video_stream_config.0.encryption_config") && !d.HasChange("storage_config.0.kinesis_video_stream_config.0.retention_period_hours")) + }, + }, + "retention_period_hours": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(0, 87600), + }, + }, + }, + }, + "s3_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bucket_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 128), + }, + "bucket_prefix": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 128), + }, + "encryption_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "encryption_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(connect.EncryptionType_Values(), false), + }, + "key_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, + }, + }, + }, + "storage_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(connect.StorageType_Values(), false), + }, + }, + }, + }, + }, + } +} + +func resourceInstanceStorageConfigCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).ConnectConn + + instanceId := d.Get("instance_id").(string) + resourceType := d.Get("resource_type").(string) + + input := &connect.AssociateInstanceStorageConfigInput{ + InstanceId: aws.String(instanceId), + ResourceType: aws.String(resourceType), + StorageConfig: expandStorageConfig(d.Get("storage_config").([]interface{})), + } + + log.Printf("[DEBUG] Creating Connect Instance Storage Config %s", input) + output, err := conn.AssociateInstanceStorageConfigWithContext(ctx, input) + + if err != nil { + return diag.Errorf("creating Connect Instance Storage Config for Connect Instance (%s,%s): %s", instanceId, resourceType, err) + } + + if output == nil || output.AssociationId == nil { + return diag.Errorf("creating Connect Instance Storage Config for Connect Instance (%s,%s): empty output", instanceId, resourceType) + } + + d.SetId(fmt.Sprintf("%s:%s:%s", instanceId, aws.StringValue(output.AssociationId), resourceType)) + + return resourceInstanceStorageConfigRead(ctx, d, meta) +} + +func resourceInstanceStorageConfigRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).ConnectConn + + instanceId, associationId, resourceType, err := InstanceStorageConfigParseId(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + resp, err := conn.DescribeInstanceStorageConfigWithContext(ctx, &connect.DescribeInstanceStorageConfigInput{ + AssociationId: aws.String(associationId), + InstanceId: aws.String(instanceId), + ResourceType: aws.String(resourceType), + }) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, connect.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] Connect Instance Storage Config (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("getting Connect Instance Storage Config (%s): %s", d.Id(), err) + } + + if resp == nil || resp.StorageConfig == nil { + return diag.Errorf("getting Connect Instance Storage Config (%s): empty response", d.Id()) + } + + storageConfig := resp.StorageConfig + + d.Set("association_id", storageConfig.AssociationId) + d.Set("instance_id", instanceId) + d.Set("resource_type", resourceType) + + if err := d.Set("storage_config", flattenStorageConfig(storageConfig)); err != nil { + return diag.Errorf("setting storage_config: %s", err) + } + + return nil +} + +func resourceInstanceStorageConfigUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).ConnectConn + + instanceId, associationId, resourceType, err := InstanceStorageConfigParseId(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + input := &connect.UpdateInstanceStorageConfigInput{ + AssociationId: aws.String(associationId), + InstanceId: aws.String(instanceId), + ResourceType: aws.String(resourceType), + } + + if d.HasChange("storage_config") { + input.StorageConfig = expandStorageConfig(d.Get("storage_config").([]interface{})) + } + + _, err = conn.UpdateInstanceStorageConfigWithContext(ctx, input) + + if err != nil { + return diag.Errorf("updating Instance Storage Config (%s): %s", d.Id(), err) + } + + return resourceInstanceStorageConfigRead(ctx, d, meta) +} + +func resourceInstanceStorageConfigDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).ConnectConn + + instanceId, associationId, resourceType, err := InstanceStorageConfigParseId(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + _, err = conn.DisassociateInstanceStorageConfigWithContext(ctx, &connect.DisassociateInstanceStorageConfigInput{ + AssociationId: aws.String(associationId), + InstanceId: aws.String(instanceId), + ResourceType: aws.String(resourceType), + }) + + if err != nil { + return diag.Errorf("deleting InstanceStorageConfig (%s): %s", d.Id(), err) + } + + return nil +} + +func InstanceStorageConfigParseId(id string) (string, string, string, error) { + parts := strings.SplitN(id, ":", 3) + + if len(parts) < 3 || parts[0] == "" || parts[1] == "" || parts[2] == "" { + return "", "", "", fmt.Errorf("unexpected format of ID (%s), expected instanceId:associationId:resourceType", id) + } + + return parts[0], parts[1], parts[2], nil +} + +func expandStorageConfig(tfList []interface{}) *connect.InstanceStorageConfig { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + tfMap, ok := tfList[0].(map[string]interface{}) + if !ok { + return nil + } + + result := &connect.InstanceStorageConfig{ + StorageType: aws.String(tfMap["storage_type"].(string)), + } + + if v, ok := tfMap["kinesis_firehose_config"].([]interface{}); ok && len(v) > 0 { + result.KinesisFirehoseConfig = expandKinesisFirehoseConfig(v) + } + + if v, ok := tfMap["kinesis_stream_config"].([]interface{}); ok && len(v) > 0 { + result.KinesisStreamConfig = expandKinesisStreamConfig(v) + } + + if v, ok := tfMap["kinesis_video_stream_config"].([]interface{}); ok && len(v) > 0 { + result.KinesisVideoStreamConfig = expandKinesisVideoStreamConfig(v) + } + + if v, ok := tfMap["s3_config"].([]interface{}); ok && len(v) > 0 { + result.S3Config = exapandS3Config(v) + } + + return result +} + +func expandKinesisFirehoseConfig(tfList []interface{}) *connect.KinesisFirehoseConfig { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + tfMap, ok := tfList[0].(map[string]interface{}) + if !ok { + return nil + } + + result := &connect.KinesisFirehoseConfig{ + FirehoseArn: aws.String(tfMap["firehose_arn"].(string)), + } + + return result +} + +func expandKinesisStreamConfig(tfList []interface{}) *connect.KinesisStreamConfig { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + tfMap, ok := tfList[0].(map[string]interface{}) + if !ok { + return nil + } + + result := &connect.KinesisStreamConfig{ + StreamArn: aws.String(tfMap["stream_arn"].(string)), + } + + return result +} + +func expandKinesisVideoStreamConfig(tfList []interface{}) *connect.KinesisVideoStreamConfig { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + tfMap, ok := tfList[0].(map[string]interface{}) + if !ok { + return nil + } + + result := &connect.KinesisVideoStreamConfig{ + EncryptionConfig: expandEncryptionConfig(tfMap["encryption_config"].([]interface{})), + Prefix: aws.String(tfMap["prefix"].(string)), + RetentionPeriodHours: aws.Int64(int64(tfMap["retention_period_hours"].(int))), + } + + return result +} + +func exapandS3Config(tfList []interface{}) *connect.S3Config { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + tfMap, ok := tfList[0].(map[string]interface{}) + if !ok { + return nil + } + + result := &connect.S3Config{ + BucketName: aws.String(tfMap["bucket_name"].(string)), + BucketPrefix: aws.String(tfMap["bucket_prefix"].(string)), + } + + if v, ok := tfMap["encryption_config"].([]interface{}); ok && len(v) > 0 { + result.EncryptionConfig = expandEncryptionConfig(v) + } + + return result +} + +func expandEncryptionConfig(tfList []interface{}) *connect.EncryptionConfig { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + tfMap, ok := tfList[0].(map[string]interface{}) + if !ok { + return nil + } + + result := &connect.EncryptionConfig{ + EncryptionType: aws.String(tfMap["encryption_type"].(string)), + KeyId: aws.String(tfMap["key_id"].(string)), + } + + return result +} + +func flattenStorageConfig(apiObject *connect.InstanceStorageConfig) []interface{} { + if apiObject == nil { + return []interface{}{} + } + + values := map[string]interface{}{ + "storage_type": aws.StringValue(apiObject.StorageType), + } + + if v := apiObject.KinesisFirehoseConfig; v != nil { + values["kinesis_firehose_config"] = flattenKinesisFirehoseConfig(v) + } + + if v := apiObject.KinesisStreamConfig; v != nil { + values["kinesis_stream_config"] = flattenKinesisStreamConfig(v) + } + + if v := apiObject.KinesisVideoStreamConfig; v != nil { + values["kinesis_video_stream_config"] = flattenKinesisVideoStreamConfig(v) + } + + if v := apiObject.S3Config; v != nil { + values["s3_config"] = flattenS3Config(v) + } + + return []interface{}{values} +} + +func flattenKinesisFirehoseConfig(apiObject *connect.KinesisFirehoseConfig) []interface{} { + if apiObject == nil { + return []interface{}{} + } + + values := map[string]interface{}{ + "firehose_arn": aws.StringValue(apiObject.FirehoseArn), + } + + return []interface{}{values} +} + +func flattenKinesisStreamConfig(apiObject *connect.KinesisStreamConfig) []interface{} { + if apiObject == nil { + return []interface{}{} + } + + values := map[string]interface{}{ + "stream_arn": aws.StringValue(apiObject.StreamArn), + } + + return []interface{}{values} +} + +func flattenKinesisVideoStreamConfig(apiObject *connect.KinesisVideoStreamConfig) []interface{} { + if apiObject == nil { + return []interface{}{} + } + + values := map[string]interface{}{ + "encryption_config": flattenEncryptionConfig(apiObject.EncryptionConfig), + // API returns -connect--contact- + // DiffSuppressFunc used + "prefix": aws.StringValue(apiObject.Prefix), + "retention_period_hours": aws.Int64Value(apiObject.RetentionPeriodHours), + } + + return []interface{}{values} +} + +func flattenS3Config(apiObject *connect.S3Config) []interface{} { + if apiObject == nil { + return []interface{}{} + } + + values := map[string]interface{}{ + "bucket_name": aws.StringValue(apiObject.BucketName), + "bucket_prefix": aws.StringValue(apiObject.BucketPrefix), + } + + if v := apiObject.EncryptionConfig; v != nil { + values["encryption_config"] = flattenEncryptionConfig(v) + } + + return []interface{}{values} +} + +func flattenEncryptionConfig(apiObject *connect.EncryptionConfig) []interface{} { + if apiObject == nil { + return []interface{}{} + } + + values := map[string]interface{}{ + "encryption_type": aws.StringValue(apiObject.EncryptionType), + "key_id": aws.StringValue(apiObject.KeyId), + } + + return []interface{}{values} +} diff --git a/internal/service/connect/instance_storage_config_test.go b/internal/service/connect/instance_storage_config_test.go new file mode 100644 index 000000000000..20991339853b --- /dev/null +++ b/internal/service/connect/instance_storage_config_test.go @@ -0,0 +1,911 @@ +package connect_test + +import ( + "fmt" + "strconv" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/connect" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + 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" + tfconnect "github.com/hashicorp/terraform-provider-aws/internal/service/connect" +) + +func testAccInstanceStorageConfig_basic(t *testing.T) { + var v connect.DescribeInstanceStorageConfigOutput + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + rName2 := sdkacctest.RandomWithPrefix("resource-test-terraform") + resourceName := "aws_connect_instance_storage_config.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, connect.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckInstanceStorageConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceStorageConfigConfig_basic(rName, rName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, "association_id"), + resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), + resource.TestCheckResourceAttr(resourceName, "resource_type", connect.InstanceStorageResourceTypeChatTranscripts), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.s3_config.0.bucket_name", "aws_s3_bucket.test", "id"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.0.bucket_prefix", "tf-test-Chat-Transcripts"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeS3), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccInstanceStorageConfig_KinesisFirehoseConfig_FirehoseARN(t *testing.T) { + var v connect.DescribeInstanceStorageConfigOutput + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + rName2 := sdkacctest.RandomWithPrefix("resource-test-terraform") + rName3 := sdkacctest.RandomWithPrefix("resource-test-terraform") + rName4 := sdkacctest.RandomWithPrefix("resource-test-terraform") + resourceName := "aws_connect_instance_storage_config.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, connect.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckInstanceStorageConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceStorageConfigConfig_kinesisFirehoseConfig_firehoseARN(rName, rName2, rName3, rName4, "first"), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "resource_type", connect.InstanceStorageResourceTypeContactTraceRecords), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_firehose_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.kinesis_firehose_config.0.firehose_arn", "aws_kinesis_firehose_delivery_stream.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeKinesisFirehose), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceStorageConfigConfig_kinesisFirehoseConfig_firehoseARN(rName, rName2, rName3, rName4, "second"), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "resource_type", connect.InstanceStorageResourceTypeContactTraceRecords), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_firehose_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.kinesis_firehose_config.0.firehose_arn", "aws_kinesis_firehose_delivery_stream.test2", "arn"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeKinesisFirehose), + ), + }, + }, + }) +} + +func testAccInstanceStorageConfig_KinesisStreamConfig_StreamARN(t *testing.T) { + var v connect.DescribeInstanceStorageConfigOutput + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + rName2 := sdkacctest.RandomWithPrefix("resource-test-terraform") + rName3 := sdkacctest.RandomWithPrefix("resource-test-terraform") + resourceName := "aws_connect_instance_storage_config.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, connect.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckInstanceStorageConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceStorageConfigConfig_kinesisStreamConfig_streamARN(rName, rName2, rName3, "first"), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "resource_type", connect.InstanceStorageResourceTypeContactTraceRecords), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_stream_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.kinesis_stream_config.0.stream_arn", "aws_kinesis_stream.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeKinesisStream), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceStorageConfigConfig_kinesisStreamConfig_streamARN(rName, rName2, rName3, "second"), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "resource_type", connect.InstanceStorageResourceTypeContactTraceRecords), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_stream_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.kinesis_stream_config.0.stream_arn", "aws_kinesis_stream.test2", "arn"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeKinesisStream), + ), + }, + }, + }) +} + +func testAccInstanceStorageConfig_KinesisVideoStreamConfig_Prefix(t *testing.T) { + var v connect.DescribeInstanceStorageConfigOutput + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + resourceName := "aws_connect_instance_storage_config.test" + + originalPrefix := "originalPrefix" + updatedPrefix := "updatedPrefix" + + retention := 1 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, connect.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckInstanceStorageConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceStorageConfigConfig_kinesisVideoStreamConfig_prefixRetention(rName, originalPrefix, retention), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "resource_type", connect.InstanceStorageResourceTypeMediaStreams), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.0.encryption_type", connect.EncryptionTypeKms), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.0.key_id", "aws_kms_key.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.prefix", fmt.Sprintf("%s-connect-%s-contact-", originalPrefix, rName)), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.retention_period_hours", strconv.Itoa(retention)), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeKinesisVideoStream), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceStorageConfigConfig_kinesisVideoStreamConfig_prefixRetention(rName, updatedPrefix, retention), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "resource_type", connect.InstanceStorageResourceTypeMediaStreams), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.0.encryption_type", connect.EncryptionTypeKms), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.0.key_id", "aws_kms_key.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.prefix", fmt.Sprintf("%s-connect-%s-contact-", updatedPrefix, rName)), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.retention_period_hours", strconv.Itoa(retention)), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeKinesisVideoStream), + ), + }, + }, + }) +} + +func testAccInstanceStorageConfig_KinesisVideoStreamConfig_Retention(t *testing.T) { + var v connect.DescribeInstanceStorageConfigOutput + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + resourceName := "aws_connect_instance_storage_config.test" + + prefix := "examplePrefix" + + originalRetention := 0 + updatedRetention := 87600 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, connect.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckInstanceStorageConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceStorageConfigConfig_kinesisVideoStreamConfig_prefixRetention(rName, prefix, originalRetention), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "resource_type", connect.InstanceStorageResourceTypeMediaStreams), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.0.encryption_type", connect.EncryptionTypeKms), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.0.key_id", "aws_kms_key.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.prefix", fmt.Sprintf("%s-connect-%s-contact-", prefix, rName)), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.retention_period_hours", strconv.Itoa(originalRetention)), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeKinesisVideoStream), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceStorageConfigConfig_kinesisVideoStreamConfig_prefixRetention(rName, prefix, updatedRetention), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "resource_type", connect.InstanceStorageResourceTypeMediaStreams), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.0.encryption_type", connect.EncryptionTypeKms), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.0.key_id", "aws_kms_key.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.prefix", fmt.Sprintf("%s-connect-%s-contact-", prefix, rName)), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.retention_period_hours", strconv.Itoa(updatedRetention)), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeKinesisVideoStream), + ), + }, + }, + }) +} + +func testAccInstanceStorageConfig_KinesisVideoStreamConfig_EncryptionConfig(t *testing.T) { + var v connect.DescribeInstanceStorageConfigOutput + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + resourceName := "aws_connect_instance_storage_config.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, connect.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckInstanceStorageConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceStorageConfigConfig_kinesisVideoStreamConfig_encryptionConfig(rName, "first"), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "resource_type", connect.InstanceStorageResourceTypeMediaStreams), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.0.encryption_type", connect.EncryptionTypeKms), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.0.key_id", "aws_kms_key.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeKinesisVideoStream), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceStorageConfigConfig_kinesisVideoStreamConfig_encryptionConfig(rName, "second"), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "resource_type", connect.InstanceStorageResourceTypeMediaStreams), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.0.encryption_type", connect.EncryptionTypeKms), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.kinesis_video_stream_config.0.encryption_config.0.key_id", "aws_kms_key.test2", "arn"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeKinesisVideoStream), + ), + }, + }, + }) +} + +func testAccInstanceStorageConfig_S3Config_BucketName(t *testing.T) { + var v connect.DescribeInstanceStorageConfigOutput + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + rName2 := sdkacctest.RandomWithPrefix("resource-test-terraform") + rName3 := sdkacctest.RandomWithPrefix("resource-test-terraform") + resourceName := "aws_connect_instance_storage_config.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, connect.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckInstanceStorageConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceStorageConfigConfig_S3Config_bucketName(rName, rName2, rName3, "first"), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.s3_config.0.bucket_name", "aws_s3_bucket.test", "id"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.0.bucket_prefix", "tf-test-Chat-Transcripts"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeS3), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceStorageConfigConfig_S3Config_bucketName(rName, rName2, rName3, "second"), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.s3_config.0.bucket_name", "aws_s3_bucket.test2", "id"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.0.bucket_prefix", "tf-test-Chat-Transcripts"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeS3), + ), + }, + }, + }) +} + +func testAccInstanceStorageConfig_S3Config_BucketPrefix(t *testing.T) { + var v connect.DescribeInstanceStorageConfigOutput + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + rName2 := sdkacctest.RandomWithPrefix("resource-test-terraform") + resourceName := "aws_connect_instance_storage_config.test" + + originalBucketPrefix := "originalBucketPrefix" + updatedBucketPrefix := "updatedBucketPrefix" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, connect.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckInstanceStorageConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceStorageConfigConfig_S3Config_bucketPrefix(rName, rName2, originalBucketPrefix), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.s3_config.0.bucket_name", "aws_s3_bucket.test", "id"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.0.bucket_prefix", originalBucketPrefix), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeS3), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceStorageConfigConfig_S3Config_bucketPrefix(rName, rName2, updatedBucketPrefix), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.s3_config.0.bucket_name", "aws_s3_bucket.test", "id"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.0.bucket_prefix", updatedBucketPrefix), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeS3), + ), + }, + }, + }) +} + +func testAccInstanceStorageConfig_S3Config_EncryptionConfig(t *testing.T) { + var v connect.DescribeInstanceStorageConfigOutput + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + rName2 := sdkacctest.RandomWithPrefix("resource-test-terraform") + resourceName := "aws_connect_instance_storage_config.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, connect.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckInstanceStorageConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceStorageConfigConfig_S3Config_encryptionConfig(rName, rName2, "first"), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.s3_config.0.bucket_name", "aws_s3_bucket.test", "id"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.0.bucket_prefix", "tf-test-Chat-Transcripts"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.0.encryption_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.0.encryption_config.0.encryption_type", connect.EncryptionTypeKms), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.s3_config.0.encryption_config.0.key_id", "aws_kms_key.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeS3), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceStorageConfigConfig_S3Config_encryptionConfig(rName, rName2, "second"), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "storage_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.s3_config.0.bucket_name", "aws_s3_bucket.test", "id"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.0.bucket_prefix", "tf-test-Chat-Transcripts"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.0.encryption_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.s3_config.0.encryption_config.0.encryption_type", connect.EncryptionTypeKms), + resource.TestCheckResourceAttrPair(resourceName, "storage_config.0.s3_config.0.encryption_config.0.key_id", "aws_kms_key.test2", "arn"), + resource.TestCheckResourceAttr(resourceName, "storage_config.0.storage_type", connect.StorageTypeS3), + ), + }, + }, + }) +} + +func testAccInstanceStorageConfig_disappears(t *testing.T) { + var v connect.DescribeInstanceStorageConfigOutput + rName := sdkacctest.RandomWithPrefix("resource-test-terraform") + rName2 := sdkacctest.RandomWithPrefix("resource-test-terraform") + resourceName := "aws_connect_instance_storage_config.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, connect.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckInstanceStorageConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceStorageConfigConfig_basic(rName, rName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceStorageConfigExists(resourceName, &v), + acctest.CheckResourceDisappears(acctest.Provider, tfconnect.ResourceInstanceStorageConfig(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckInstanceStorageConfigExists(resourceName string, function *connect.DescribeInstanceStorageConfigOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Connect Instance Storage Config not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Connect Instance Storage Config ID not set") + } + instanceId, associationId, resourceType, err := tfconnect.InstanceStorageConfigParseId(rs.Primary.ID) + + if err != nil { + return err + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ConnectConn + + params := &connect.DescribeInstanceStorageConfigInput{ + AssociationId: aws.String(associationId), + InstanceId: aws.String(instanceId), + ResourceType: aws.String(resourceType), + } + + getFunction, err := conn.DescribeInstanceStorageConfig(params) + if err != nil { + return err + } + + *function = *getFunction + + return nil + } +} + +func testAccCheckInstanceStorageConfigDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_connect_instance_storage_config" { + continue + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ConnectConn + + instanceId, associationId, resourceType, err := tfconnect.InstanceStorageConfigParseId(rs.Primary.ID) + + if err != nil { + return err + } + + params := &connect.DescribeInstanceStorageConfigInput{ + AssociationId: aws.String(associationId), + InstanceId: aws.String(instanceId), + ResourceType: aws.String(resourceType), + } + + _, err = conn.DescribeInstanceStorageConfig(params) + + if tfawserr.ErrCodeEquals(err, connect.ErrCodeResourceNotFoundException) { + continue + } + + if err != nil { + return err + } + } + + return nil +} + +func testAccInstanceStorageConfigConfig_base(rName string) string { + return fmt.Sprintf(` +resource "aws_connect_instance" "test" { + identity_management_type = "CONNECT_MANAGED" + inbound_calls_enabled = true + instance_alias = %[1]q + outbound_calls_enabled = true +} +`, rName) +} + +func testAccInstanceStorageConfigConfig_basic(rName, rName2 string) string { + return acctest.ConfigCompose( + testAccInstanceStorageConfigConfig_base(rName), + fmt.Sprintf(` +resource "aws_s3_bucket" "test" { + bucket = %[1]q + force_destroy = true +} + +resource "aws_connect_instance_storage_config" "test" { + instance_id = aws_connect_instance.test.id + resource_type = "CHAT_TRANSCRIPTS" + + storage_config { + s3_config { + bucket_name = aws_s3_bucket.test.id + bucket_prefix = "tf-test-Chat-Transcripts" + } + storage_type = "S3" + } +} +`, rName2)) +} + +func testAccInstanceStorageDeliveryStreamConfig_Base(rName string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} +data "aws_partition" "current" {} + +resource "aws_iam_role" "firehose" { + name = %[1]q + + assume_role_policy = <-connect--contact-` since the API appends additional details to the `prefix`. +* `retention_period_hours` - (Required) The number of hours data is retained in the stream. Kinesis Video Streams retains the data in a data store that is associated with the stream. Minimum value of `0`. Maximum value of `87600`. A value of `0`, indicates that the stream does not persist data. + +#### `s3_config` + +The `s3_config` configuration block supports the following arguments: + +* `bucket_name` - (Required) The S3 bucket name. +* `bucket_prefix` - (Required) The S3 bucket prefix. +* `encryption_config` - (Optional) The encryption configuration. [Documented below](#encryption_config). + +#### `encryption_config` + +The `encryption_config` configuration block supports the following arguments: + +* `encryption_type` - (Required) The type of encryption. Valid Values: `KMS`. +* `key_id` - (Required) The full ARN of the encryption key. Be sure to provide the full ARN of the encryption key, not just the ID. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `association_id` - The existing association identifier that uniquely identifies the resource type and storage config for the given instance ID. +* `id` - The identifier of the hosting Amazon Connect Instance, `association_id`, and `resource_type` separated by a colon (`:`). + +## Import + +Amazon Connect Instance Storage Configs can be imported using the `instance_id`, `association_id`, and `resource_type` separated by a colon (`:`), e.g., + +``` +$ terraform import aws_connect_instance_storage_config.example f1288a1f-6193-445a-b47e-af739b2:c1d4e5f6-1b3c-1b3c-1b3c-c1d4e5f6c1d4e5:CHAT_TRANSCRIPTS +```