diff --git a/.changelog/34787.txt b/.changelog/34787.txt new file mode 100644 index 000000000000..36bbd4e1bae7 --- /dev/null +++ b/.changelog/34787.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +provider: Add `sqs_wait_times` configuration block to customize SQS queue operation wait times for local development environments +``` diff --git a/docs/retries-and-waiters.md b/docs/retries-and-waiters.md index 8943b5744338..b3248d7ebfc4 100644 --- a/docs/retries-and-waiters.md +++ b/docs/retries-and-waiters.md @@ -601,3 +601,69 @@ func waitThingDeleted(ctx context.Context, conn *example.Example, id string, tim ``` Typically, the AWS Go SDK should include constants for various status field values (e.g., `StatusCreating` for `CREATING`). If not, create them in a file named `internal/service/{SERVICE}/consts.go`. + +## Configurable Wait Times + +Some AWS services require configurable wait times to accommodate different environments and use cases. For example, SQS queue operations have different propagation characteristics in local development environments (like LocalStack) compared to production AWS environments. + +### SQS Wait Times Configuration + +The SQS service supports configurable wait times through the provider configuration to optimize performance for different environments: + +```hcl +provider "aws" { + # ... other provider configuration ... + + sqs_wait_times { + create_continuous_target_occurrence = 1 # Default: 6 (30 seconds) + delete_continuous_target_occurrence = 2 # Default: 15 (45 seconds) + } +} +``` + +#### Configuration Options + +- **`create_continuous_target_occurrence`** (Optional, Number): Number of consecutive successful checks required during SQS queue creation. Default is 6, which results in approximately 30 seconds of wait time (6 checks × 5 seconds). Lower values speed up local development environments. + +- **`delete_continuous_target_occurrence`** (Optional, Number): Number of consecutive successful checks required during SQS queue deletion. Default is 15, which results in approximately 45 seconds of wait time (15 checks × 3 seconds). Lower values speed up local development environments. + +#### Use Cases + +**Local Development (LocalStack):** +```hcl +provider "aws" { + # LocalStack configuration + access_key = "test" + secret_key = "test" + region = "us-east-1" + skip_credentials_validation = true + skip_metadata_api_check = true + skip_region_validation = true + skip_requesting_account_id = true + + # Fast wait times for local development + sqs_wait_times { + create_continuous_target_occurrence = 1 # ~5 seconds + delete_continuous_target_occurrence = 2 # ~6 seconds + } + + endpoints { + sqs = "http://localhost:4566" + } +} +``` + +**Production AWS:** +```hcl +provider "aws" { + # Production configuration uses default wait times + # (no sqs_wait_times block needed) +} +``` + +#### Implementation Notes + +- Default values are designed to accommodate various AWS regions including GovCloud, commercial, and China regions +- Lower values should only be used in controlled environments where eventual consistency is not a concern +- The configuration affects both `aws_sqs_queue` resource creation and deletion operations +- Changes to wait times do not affect existing resources, only new operations diff --git a/internal/conns/awsclient.go b/internal/conns/awsclient.go index 93aca9b3ee53..64f710e27dff 100644 --- a/internal/conns/awsclient.go +++ b/internal/conns/awsclient.go @@ -39,10 +39,11 @@ type AWSClient struct { partition endpoints.Partition servicePackages map[string]ServicePackage s3ExpressClient *s3.Client - s3UsePathStyle bool // From provider configuration. - s3USEast1RegionalEndpoint string // From provider configuration. - stsRegion string // From provider configuration. - terraformVersion string // From provider configuration. + s3UsePathStyle bool // From provider configuration. + s3USEast1RegionalEndpoint string // From provider configuration. + sqsWaitTimes *SQSWaitTimesConfig // From provider configuration. + stsRegion string // From provider configuration. + terraformVersion string // From provider configuration. } func (c *AWSClient) SetServicePackages(_ context.Context, servicePackages map[string]ServicePackage) { @@ -412,3 +413,8 @@ func client[T any](ctx context.Context, c *AWSClient, servicePackageName string, return client, nil } + +// SQSWaitTimes returns the SQS wait times configuration +func (c *AWSClient) SQSWaitTimes() *SQSWaitTimesConfig { + return c.sqsWaitTimes +} diff --git a/internal/conns/config.go b/internal/conns/config.go index 6c47241c1150..e87bf9829694 100644 --- a/internal/conns/config.go +++ b/internal/conns/config.go @@ -61,6 +61,12 @@ type Config struct { TokenBucketRateLimiterCapacity int UseDualStackEndpoint bool UseFIPSEndpoint bool + SQSWaitTimes *SQSWaitTimesConfig +} + +type SQSWaitTimesConfig struct { + CreateContinuousTargetOccurrence int + DeleteContinuousTargetOccurrence int } // ConfigureProvider configures the provided provider Meta (instance data). @@ -203,6 +209,7 @@ func (c *Config) ConfigureProvider(ctx context.Context, client *AWSClient) (*AWS client.logger = logger client.s3UsePathStyle = c.S3UsePathStyle client.s3USEast1RegionalEndpoint = c.S3USEast1RegionalEndpoint + client.sqsWaitTimes = c.SQSWaitTimes client.stsRegion = c.STSRegion return client, diags diff --git a/internal/provider/sdkv2/provider.go b/internal/provider/sdkv2/provider.go index f6b44b7c2bc1..08425433660f 100644 --- a/internal/provider/sdkv2/provider.go +++ b/internal/provider/sdkv2/provider.go @@ -243,6 +243,30 @@ func NewProvider(ctx context.Context) (*schema.Provider, error) { Description: "Skip requesting the account ID. " + "Used for AWS API implementations that do not have IAM/STS API and/or metadata API.", }, + "sqs_wait_times": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "Configuration block for SQS wait times. Useful for local development environments like LocalStack.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "create_continuous_target_occurrence": { + Type: schema.TypeInt, + Optional: true, + Default: 6, + ValidateFunc: validation.IntAtLeast(1), + Description: "Number of consecutive successful checks required during SQS queue creation. Default is 6 (30 seconds). Lower values speed up local development.", + }, + "delete_continuous_target_occurrence": { + Type: schema.TypeInt, + Optional: true, + Default: 15, + ValidateFunc: validation.IntAtLeast(1), + Description: "Number of consecutive successful checks required during SQS queue deletion. Default is 15 (45 seconds). Lower values speed up local development.", + }, + }, + }, + }, "sts_region": { Type: schema.TypeString, Optional: true, @@ -485,6 +509,14 @@ func (p *sdkProvider) configure(ctx context.Context, d *schema.ResourceData) (an } } + if v, ok := d.GetOk("sqs_wait_times"); ok && len(v.([]any)) > 0 && v.([]any)[0] != nil { + sqsWaitTimes := v.([]any)[0].(map[string]any) + config.SQSWaitTimes = &conns.SQSWaitTimesConfig{ + CreateContinuousTargetOccurrence: sqsWaitTimes["create_continuous_target_occurrence"].(int), + DeleteContinuousTargetOccurrence: sqsWaitTimes["delete_continuous_target_occurrence"].(int), + } + } + var c *conns.AWSClient if v, ok := p.provider.Meta().(*conns.AWSClient); ok { c = v diff --git a/internal/service/sqs/attribute_funcs.go b/internal/service/sqs/attribute_funcs.go index db799f2fd136..f9a6e987b591 100644 --- a/internal/service/sqs/attribute_funcs.go +++ b/internal/service/sqs/attribute_funcs.go @@ -59,7 +59,7 @@ func (h *queueAttributeHandler) Upsert(ctx context.Context, d *schema.ResourceDa d.SetId(url) - if err := waitQueueAttributesPropagated(ctx, conn, d.Id(), attributes, deadline.Remaining()); err != nil { + if err := waitQueueAttributesPropagated(ctx, conn, d.Id(), attributes, deadline.Remaining(), meta); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for SQS Queue (%s) attribute (%s) create: %s", d.Id(), h.AttributeName, err) } @@ -123,7 +123,7 @@ func (h *queueAttributeHandler) Delete(ctx context.Context, d *schema.ResourceDa return sdkdiag.AppendErrorf(diags, "deleting SQS Queue (%s) attribute (%s): %s", d.Id(), h.AttributeName, err) } - if err := waitQueueAttributesPropagated(ctx, conn, d.Id(), attributes, d.Timeout(schema.TimeoutDelete)); err != nil { + if err := waitQueueAttributesPropagated(ctx, conn, d.Id(), attributes, d.Timeout(schema.TimeoutDelete), meta); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for SQS Queue (%s) attribute (%s) delete: %s", d.Id(), h.AttributeName, err) } diff --git a/internal/service/sqs/queue.go b/internal/service/sqs/queue.go index 1491c8c5330b..b4ceca0607fa 100644 --- a/internal/service/sqs/queue.go +++ b/internal/service/sqs/queue.go @@ -256,7 +256,7 @@ func resourceQueueCreate(ctx context.Context, d *schema.ResourceData, meta any) d.SetId(aws.ToString(outputRaw.(*sqs.CreateQueueOutput).QueueUrl)) - if err := waitQueueAttributesPropagated(ctx, conn, d.Id(), attributes, deadline.Remaining()); err != nil { + if err := waitQueueAttributesPropagated(ctx, conn, d.Id(), attributes, deadline.Remaining(), meta); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for SQS Queue (%s) attributes create: %s", d.Id(), err) } @@ -342,7 +342,7 @@ func resourceQueueUpdate(ctx context.Context, d *schema.ResourceData, meta any) return sdkdiag.AppendErrorf(diags, "updating SQS Queue (%s) attributes: %s", d.Id(), err) } - if err := waitQueueAttributesPropagated(ctx, conn, d.Id(), attributes, d.Timeout(schema.TimeoutUpdate)); err != nil { + if err := waitQueueAttributesPropagated(ctx, conn, d.Id(), attributes, d.Timeout(schema.TimeoutUpdate), meta); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for SQS Queue (%s) attributes update: %s", d.Id(), err) } } @@ -367,7 +367,7 @@ func resourceQueueDelete(ctx context.Context, d *schema.ResourceData, meta any) return sdkdiag.AppendErrorf(diags, "deleting SQS Queue (%s): %s", d.Id(), err) } - if err := waitQueueDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + if err := waitQueueDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete), meta); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for SQS Queue (%s) delete: %s", d.Id(), err) } @@ -600,13 +600,18 @@ func statusQueueAttributeState(ctx context.Context, conn *sqs.Client, url string } } -func waitQueueAttributesPropagated(ctx context.Context, conn *sqs.Client, url string, expected map[types.QueueAttributeName]string, timeout time.Duration) error { +func waitQueueAttributesPropagated(ctx context.Context, conn *sqs.Client, url string, expected map[types.QueueAttributeName]string, timeout time.Duration, meta any) error { + continuousTargetOccurrence := 6 + if awsClient, ok := meta.(*conns.AWSClient); ok && awsClient.SQSWaitTimes() != nil { + continuousTargetOccurrence = awsClient.SQSWaitTimes().CreateContinuousTargetOccurrence + } + stateConf := &retry.StateChangeConf{ Pending: []string{queueAttributeStateNotEqual}, Target: []string{queueAttributeStateEqual}, Refresh: statusQueueAttributeState(ctx, conn, url, expected), Timeout: timeout, - ContinuousTargetOccurence: 6, // set to accommodate GovCloud, commercial, China, etc. - avoid lowering + ContinuousTargetOccurence: continuousTargetOccurrence, MinTimeout: 5 * time.Second, // set to accommodate GovCloud, commercial, China, etc. - avoid lowering NotFoundChecks: 10, // set to accommodate GovCloud, commercial, China, etc. - avoid lowering } @@ -616,13 +621,18 @@ func waitQueueAttributesPropagated(ctx context.Context, conn *sqs.Client, url st return err } -func waitQueueDeleted(ctx context.Context, conn *sqs.Client, url string, timeout time.Duration) error { +func waitQueueDeleted(ctx context.Context, conn *sqs.Client, url string, timeout time.Duration, meta any) error { + continuousTargetOccurrence := 15 + if awsClient, ok := meta.(*conns.AWSClient); ok && awsClient.SQSWaitTimes() != nil { + continuousTargetOccurrence = awsClient.SQSWaitTimes().DeleteContinuousTargetOccurrence + } + stateConf := &retry.StateChangeConf{ Pending: []string{queueStateExists}, Target: []string{}, Refresh: statusQueueState(ctx, conn, url), Timeout: timeout, - ContinuousTargetOccurence: 15, // set to accommodate GovCloud, commercial, China, etc. - avoid lowering + ContinuousTargetOccurence: continuousTargetOccurrence, MinTimeout: 3 * time.Second, // set to accommodate GovCloud, commercial, China, etc. - avoid lowering NotFoundChecks: 5, // set to accommodate GovCloud, commercial, China, etc. - avoid lowering }