diff --git a/.changelog/22821.txt b/.changelog/22821.txt new file mode 100644 index 00000000000..a96bdb04b59 --- /dev/null +++ b/.changelog/22821.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_connect_queue: The `quick_connect_ids` argument can now be updated in-place +``` \ No newline at end of file diff --git a/internal/service/connect/enum.go b/internal/service/connect/enum.go index 1e763276c3e..57fb7a0dadb 100644 --- a/internal/service/connect/enum.go +++ b/internal/service/connect/enum.go @@ -24,6 +24,9 @@ const ( // MaxResults Valid Range: Minimum value of 1. Maximum value of 1000 // https://docs.aws.amazon.com/connect/latest/APIReference/API_ListPrompts.html ListPromptsMaxResults = 60 + // ListQueueQuickConnectsMaxResults Valid Range: Minimum value of 1. Maximum value of 100. + // https://docs.aws.amazon.com/connect/latest/APIReference/API_ListQueueQuickConnects.html + ListQueueQuickConnectsMaxResults = 60 // ListQueuesMaxResults Valid Range: Minimum value of 1. Maximum value of 1000. // https://docs.aws.amazon.com/connect/latest/APIReference/API_ListQueues.html ListQueuesMaxResults = 60 diff --git a/internal/service/connect/queue.go b/internal/service/connect/queue.go index 838f179c552..5530e4a4766 100644 --- a/internal/service/connect/queue.go +++ b/internal/service/connect/queue.go @@ -87,7 +87,13 @@ func ResourceQueue() *schema.Resource { "quick_connect_ids": { Type: schema.TypeSet, Optional: true, - ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "quick_connect_ids_associated": { + Type: schema.TypeSet, + Computed: true, Elem: &schema.Schema{ Type: schema.TypeString, }, @@ -200,6 +206,16 @@ func resourceQueueRead(ctx context.Context, d *schema.ResourceData, meta interfa d.Set("queue_id", resp.Queue.QueueId) d.Set("status", resp.Queue.Status) + // reading quick_connect_ids requires a separate API call + quickConnectIds, err := getConnectQueueQuickConnectIds(ctx, conn, instanceID, queueID) + + if err != nil { + return diag.FromErr(fmt.Errorf("error finding Connect Queue Quick Connect ID for Queue (%s): %w", queueID, err)) + } + + d.Set("quick_connect_ids", flex.FlattenStringSet(quickConnectIds)) + d.Set("quick_connect_ids_associated", flex.FlattenStringSet(quickConnectIds)) + tags := KeyValueTags(resp.Queue.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 @@ -223,12 +239,13 @@ func resourceQueueUpdate(ctx context.Context, d *schema.ResourceData, meta inter return diag.FromErr(err) } - // Queue has 5 update APIs + // Queue has 6 update APIs // UpdateQueueHoursOfOperationWithContext: Updates the hours_of_operation_id of a queue. // UpdateQueueMaxContactsWithContext: Updates the max_contacts of a queue. // UpdateQueueNameWithContext: Updates the name and description of a queue. // UpdateQueueOutboundCallerConfigWithContext: Updates the outbound_caller_config of a queue. // UpdateQueueStatusWithContext: Updates the status of a queue. Valid Values: ENABLED | DISABLED + // AssociateQueueQuickConnectsWithContext: Associates a set of quick connects with a queue. There is also DisassociateQueueQuickConnectsWithContext // updates to hours_of_operation_id if d.HasChange("hours_of_operation_id") { @@ -301,6 +318,35 @@ func resourceQueueUpdate(ctx context.Context, d *schema.ResourceData, meta inter } } + // updates to quick_connect_ids + if d.HasChange("quick_connect_ids") { + // first disassociate all existing quick connects + if v, ok := d.GetOk("quick_connect_ids_associated"); ok && v.(*schema.Set).Len() > 0 { + input := &connect.DisassociateQueueQuickConnectsInput{ + InstanceId: aws.String(instanceID), + QueueId: aws.String(queueID), + } + input.QuickConnectIds = flex.ExpandStringSet(v.(*schema.Set)) + _, err = conn.DisassociateQueueQuickConnectsWithContext(ctx, input) + if err != nil { + return diag.FromErr(fmt.Errorf("[ERROR] Error updating Queues Quick Connect IDs, specifically disassociating quick connects from queue (%s): %w", d.Id(), err)) + } + } + + // re-associate the quick connects + if v, ok := d.GetOk("quick_connect_ids"); ok && v.(*schema.Set).Len() > 0 { + input := &connect.AssociateQueueQuickConnectsInput{ + InstanceId: aws.String(instanceID), + QueueId: aws.String(queueID), + } + input.QuickConnectIds = flex.ExpandStringSet(v.(*schema.Set)) + _, err = conn.AssociateQueueQuickConnectsWithContext(ctx, input) + if err != nil { + return diag.FromErr(fmt.Errorf("[ERROR] Error updating Queues Quick Connect IDs, specifically associating quick connects to queue (%s): %w", d.Id(), err)) + } + } + } + // updates to tags if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") @@ -363,6 +409,38 @@ func flattenOutboundCallerConfig(outboundCallerConfig *connect.OutboundCallerCon return []interface{}{values} } +func getConnectQueueQuickConnectIds(ctx context.Context, conn *connect.Connect, instanceID, queueID string) ([]*string, error) { + var result []*string + + input := &connect.ListQueueQuickConnectsInput{ + InstanceId: aws.String(instanceID), + MaxResults: aws.Int64(ListQueueQuickConnectsMaxResults), + QueueId: aws.String(queueID), + } + + err := conn.ListQueueQuickConnectsPagesWithContext(ctx, input, func(page *connect.ListQueueQuickConnectsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, qc := range page.QuickConnectSummaryList { + if qc == nil { + continue + } + + result = append(result, qc.Id) + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return result, nil +} + func QueueParseID(id string) (string, string, error) { parts := strings.SplitN(id, ":", 2) diff --git a/internal/service/connect/queue_test.go b/internal/service/connect/queue_test.go index 1d0685ac8fb..bc4a0a34ceb 100644 --- a/internal/service/connect/queue_test.go +++ b/internal/service/connect/queue_test.go @@ -24,6 +24,7 @@ func TestAccConnectQueue_serial(t *testing.T) { "update_max_contacts": testAccQueue_updateMaxContacts, "update_outbound_caller_config": testAccQueue_updateOutboundCallerConfig, "update_status": testAccQueue_updateStatus, + "update_quick_connect_ids": testAccQueue_updateQuickConnectIds, } for name, tc := range testCases { @@ -58,6 +59,7 @@ func testAccQueue_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", rName2), resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "0"), resource.TestCheckResourceAttr(resourceName, "status", connect.QueueStatusEnabled), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), ), @@ -77,6 +79,7 @@ func testAccQueue_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", rName2), resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "0"), resource.TestCheckResourceAttr(resourceName, "status", connect.QueueStatusEnabled), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), ), @@ -132,6 +135,7 @@ func testAccQueue_updateHoursOfOperationId(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", rName2), resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "0"), resource.TestCheckResourceAttr(resourceName, "status", connect.QueueStatusEnabled), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), ), @@ -151,6 +155,7 @@ func testAccQueue_updateHoursOfOperationId(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", rName2), resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "0"), resource.TestCheckResourceAttr(resourceName, "status", connect.QueueStatusEnabled), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), ), @@ -170,6 +175,7 @@ func testAccQueue_updateHoursOfOperationId(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", rName2), resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "0"), resource.TestCheckResourceAttr(resourceName, "status", connect.QueueStatusEnabled), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), ), @@ -204,6 +210,7 @@ func testAccQueue_updateMaxContacts(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "max_contacts", originalMaxContacts), resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "0"), resource.TestCheckResourceAttr(resourceName, "status", connect.QueueStatusEnabled), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), ), @@ -224,6 +231,7 @@ func testAccQueue_updateMaxContacts(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "max_contacts", updatedMaxContacts), resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "0"), resource.TestCheckResourceAttr(resourceName, "status", connect.QueueStatusEnabled), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), ), @@ -258,6 +266,7 @@ func testAccQueue_updateOutboundCallerConfig(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "outbound_caller_config.0.outbound_caller_id_name", originalOutboundCallerIdName), resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "0"), resource.TestCheckResourceAttr(resourceName, "status", connect.QueueStatusEnabled), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), ), @@ -279,6 +288,7 @@ func testAccQueue_updateOutboundCallerConfig(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "outbound_caller_config.0.outbound_caller_id_name", updatedOutboundCallerIdName), resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "0"), resource.TestCheckResourceAttr(resourceName, "status", connect.QueueStatusEnabled), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), ), @@ -311,6 +321,7 @@ func testAccQueue_updateStatus(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", rName2), resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "0"), resource.TestCheckResourceAttr(resourceName, "status", originalStatus), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), ), @@ -330,6 +341,7 @@ func testAccQueue_updateStatus(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", rName2), resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "0"), resource.TestCheckResourceAttr(resourceName, "status", updatedStatus), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), ), @@ -338,6 +350,106 @@ func testAccQueue_updateStatus(t *testing.T) { }) } +func testAccQueue_updateQuickConnectIds(t *testing.T) { + var v connect.DescribeQueueOutput + 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_queue.test" + description := "test queue integrations with quick connects" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, connect.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckQueueDestroy, + Steps: []resource.TestStep{ + { + // start with no quick connects associated with the queue + Config: testAccQueueBasicConfig(rName, rName4, description), + Check: resource.ComposeTestCheckFunc( + testAccCheckQueueExists(resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestCheckResourceAttrPair(resourceName, "hours_of_operation_id", "data.aws_connect_hours_of_operation.test", "hours_of_operation_id"), + resource.TestCheckResourceAttr(resourceName, "name", rName4), + resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), + resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "0"), + resource.TestCheckResourceAttr(resourceName, "status", connect.QueueStatusEnabled), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // associate one quick connect to the queue + Config: testAccQueueQuickConnectConfig1(rName, rName2, rName3, rName4, description), + Check: resource.ComposeTestCheckFunc( + testAccCheckQueueExists(resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttrSet(resourceName, "description"), + resource.TestCheckResourceAttrPair(resourceName, "hours_of_operation_id", "data.aws_connect_hours_of_operation.test", "hours_of_operation_id"), + resource.TestCheckResourceAttr(resourceName, "name", rName4), + resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), + resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "quick_connect_ids.0", "aws_connect_quick_connect.test1", "quick_connect_id"), + resource.TestCheckResourceAttr(resourceName, "status", connect.QueueStatusEnabled), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // associate two quick connects to the queue + Config: testAccQueueQuickConnectConfig2(rName, rName2, rName3, rName4, description), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckQueueExists(resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttrSet(resourceName, "description"), + resource.TestCheckResourceAttrPair(resourceName, "hours_of_operation_id", "data.aws_connect_hours_of_operation.test", "hours_of_operation_id"), + resource.TestCheckResourceAttr(resourceName, "name", rName4), + resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), + resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "2"), + resource.TestCheckResourceAttr(resourceName, "status", connect.QueueStatusEnabled), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // remove one quick connect + Config: testAccQueueQuickConnectConfig1(rName, rName2, rName3, rName4, description), + Check: resource.ComposeTestCheckFunc( + testAccCheckQueueExists(resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttrSet(resourceName, "description"), + resource.TestCheckResourceAttrPair(resourceName, "hours_of_operation_id", "data.aws_connect_hours_of_operation.test", "hours_of_operation_id"), + resource.TestCheckResourceAttr(resourceName, "name", rName4), + resource.TestCheckResourceAttrPair(resourceName, "instance_id", "aws_connect_instance.test", "id"), + resource.TestCheckResourceAttrSet(resourceName, "queue_id"), + resource.TestCheckResourceAttr(resourceName, "quick_connect_ids.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "quick_connect_ids.0", "aws_connect_quick_connect.test1", "quick_connect_id"), + resource.TestCheckResourceAttr(resourceName, "status", connect.QueueStatusEnabled), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + ), + }, + }, + }) +} + func testAccCheckQueueExists(resourceName string, function *connect.DescribeQueueOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] @@ -542,3 +654,88 @@ resource "aws_connect_queue" "test" { } `, rName2, status)) } + +func testAccQueueQuickConnectBaseConfig(rName, rName2 string) string { + return fmt.Sprintf(` +resource "aws_connect_quick_connect" "test1" { + instance_id = aws_connect_instance.test.id + name = %[1]q + description = "Test Quick Connect 1" + + quick_connect_config { + quick_connect_type = "PHONE_NUMBER" + + phone_config { + phone_number = "+12345678912" + } + } + + tags = { + "Name" = "Test Quick Connect 1" + } +} + +resource "aws_connect_quick_connect" "test2" { + instance_id = aws_connect_instance.test.id + name = %[2]q + description = "Test Quick Connect 2" + + quick_connect_config { + quick_connect_type = "PHONE_NUMBER" + + phone_config { + phone_number = "+12345678913" + } + } + + tags = { + "Name" = "Test Quick Connect 2" + } +} +`, rName, rName2) +} + +func testAccQueueQuickConnectConfig1(rName, rName2, rName3, rName4, label string) string { + return acctest.ConfigCompose( + testAccQueueBaseConfig(rName), + testAccQueueQuickConnectBaseConfig(rName2, rName3), + fmt.Sprintf(` +resource "aws_connect_queue" "test" { + instance_id = aws_connect_instance.test.id + name = %[1]q + description = %[2]q + hours_of_operation_id = data.aws_connect_hours_of_operation.test.hours_of_operation_id + + quick_connect_ids = [ + aws_connect_quick_connect.test1.quick_connect_id, + ] + + tags = { + "Name" = "Test Queue", + } +} +`, rName4, label)) +} + +func testAccQueueQuickConnectConfig2(rName, rName2, rName3, rName4, label string) string { + return acctest.ConfigCompose( + testAccQueueBaseConfig(rName), + testAccQueueQuickConnectBaseConfig(rName2, rName3), + fmt.Sprintf(` +resource "aws_connect_queue" "test" { + instance_id = aws_connect_instance.test.id + name = %[1]q + description = %[2]q + hours_of_operation_id = data.aws_connect_hours_of_operation.test.hours_of_operation_id + + quick_connect_ids = [ + aws_connect_quick_connect.test1.quick_connect_id, + aws_connect_quick_connect.test2.quick_connect_id, + ] + + tags = { + "Name" = "Test Queue", + } +} +`, rName4, label)) +} diff --git a/website/docs/r/kms_grant.html.markdown b/website/docs/r/kms_grant.html.markdown index e92bea2b6a8..080e4327aec 100644 --- a/website/docs/r/kms_grant.html.markdown +++ b/website/docs/r/kms_grant.html.markdown @@ -80,5 +80,5 @@ In addition to all arguments above, the following attributes are exported: KMS Grants can be imported using the Key ID and Grant ID separated by a colon (`:`), e.g., ``` -$ terraform import aws_kms_grant.test 1234abcd-12ab-34cd-56ef-1234567890ab: abcde1237f76e4ba7987489ac329fbfba6ad343d6f7075dbd1ef191f0120514 +$ terraform import aws_kms_grant.test 1234abcd-12ab-34cd-56ef-1234567890ab:abcde1237f76e4ba7987489ac329fbfba6ad343d6f7075dbd1ef191f0120514 ```