Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions docs/resources/mnq_sqs_queue.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,44 @@ resource "scaleway_mnq_sqs_queue" "main" {
}
```

### With Dead Letter Queue

```terraform
resource "scaleway_mnq_sqs" "main" {}
resource "scaleway_mnq_sqs_credentials" "main" {
project_id = scaleway_mnq_sqs.main.project_id
name = "sqs-credentials"
permissions {
can_manage = true
can_receive = false
can_publish = false
}
}
resource "scaleway_mnq_sqs_queue" "dead_letter" {
project_id = scaleway_mnq_sqs.main.project_id
name = "dead-letter-queue"
sqs_endpoint = scaleway_mnq_sqs.main.endpoint
access_key = scaleway_mnq_sqs_credentials.main.access_key
secret_key = scaleway_mnq_sqs_credentials.main.secret_key
}
resource "scaleway_mnq_sqs_queue" "main" {
project_id = scaleway_mnq_sqs.main.project_id
name = "my-queue"
sqs_endpoint = scaleway_mnq_sqs.main.endpoint
access_key = scaleway_mnq_sqs_credentials.main.access_key
secret_key = scaleway_mnq_sqs_credentials.main.secret_key
dead_letter_queue {
id = scaleway_mnq_sqs_queue.dead_letter.id
max_receive_count = 3
}
}
```

## Argument Reference

The following arguments are supported:
Expand All @@ -62,10 +100,20 @@ The following arguments are supported:

- `message_max_size` - (Optional) The maximum size of a message. Should be in bytes. Must be between 1024 and 262_144. Defaults to 262_144.

- `dead_letter_queue` - (Optional) Configuration for the dead letter queue. See [Dead Letter Queue](#dead-letter-queue) below for details.

- `region` - (Defaults to [provider](../index.md#region) `region`). The [region](../guides/regions_and_zones.md#regions) in which SQS is enabled.

- `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the Project in which SQS is enabled.

## Dead Letter Queue

The `dead_letter_queue` block supports the following:

- `id` - (Required) The ID of the dead letter queue. Can be either in the format `{region}/{project-id}/{queue-name}` or `arn:scw:sqs:{region}:project-{project-id}:{queue-name}`.

- `max_receive_count` - (Required) The number of times a message is delivered to the source queue before being moved to the dead letter queue. Must be between 1 and 1000.


## Attributes Reference

Expand Down
2,251 changes: 1,346 additions & 905 deletions internal/services/container/testdata/trigger-sqs.cassette.yaml

Large diffs are not rendered by default.

1,838 changes: 1,213 additions & 625 deletions internal/services/function/testdata/function-trigger-sqs.cassette.yaml

Large diffs are not rendered by default.

87 changes: 84 additions & 3 deletions internal/services/mnq/helpers_mnq.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package mnq

import (
"encoding/json"
"errors"
"fmt"
"strconv"
Expand All @@ -17,6 +18,12 @@ import (
const (
AWSErrQueueDeletedRecently = "AWS.SimpleQueueService.QueueDeletedRecently"
AWSErrNonExistentQueue = "AWS.SimpleQueueService.NonExistentQueue"

// SQS ARN prefix
SQSPrefix = "arn:scw:sqs:"

// Dead letter queue resource path
DeadLetterQueuePath = "dead_letter_queue"
)

func newMNQNatsAPI(d *schema.ResourceData, m any) (*mnq.NatsAPI, scw.Region, error) {
Expand Down Expand Up @@ -172,11 +179,9 @@ func ComposeSNSARN(region scw.Region, projectID string, resourceName string) str
return composeARN("sns", region, projectID, resourceName)
}

// Set the value inside values at the resource path (e.g. a.0.b sets b's value)
func setResourceValue(values map[string]any, resourcePath string, value any, resourceSchemas map[string]*schema.Schema) {
parts := strings.Split(resourcePath, ".")
if len(parts) > 1 {
// Terraform's nested objects are represented as slices of maps
if _, ok := values[parts[0]]; !ok {
values[parts[0]] = []any{make(map[string]any)}
}
Expand Down Expand Up @@ -224,6 +229,49 @@ func awsResourceDataToAttribute(awsAttributes map[string]string, awsAttribute st
s = strconv.Itoa(resourceValue.(int))
case schema.TypeString:
s = resourceValue.(string)
case schema.TypeList:
if resourcePath == DeadLetterQueuePath {
deadLetterConfig := resourceValue.([]any)
if len(deadLetterConfig) > 0 {
config := deadLetterConfig[0].(map[string]any)
queueID := config["id"].(string)
maxReceiveCount := config["max_receive_count"].(int)

var scwARN string

switch {
case strings.HasPrefix(queueID, SQSPrefix):
scwARN = queueID
case strings.Contains(queueID, "/"):
parts := strings.Split(queueID, "/")
if len(parts) == 3 {
region := parts[0]
projectID := parts[1]
queueName := parts[2]

scwARN = fmt.Sprintf("arn:scw:sqs:%s:project-%s:%s", region, projectID, queueName)
} else {
return fmt.Errorf("invalid queue ID format for dead-letter queue: %s (expected region/project-id/queue-name or arn:scw:sqs:region:project-id:queue-name)", queueID)
}
default:
scwARN = queueID
}

redrivePolicy := map[string]any{
"deadLetterTargetArn": scwARN,
"maxReceiveCount": maxReceiveCount,
}

jsonData, err := json.Marshal(redrivePolicy)
if err != nil {
return fmt.Errorf("failed to marshal redrive policy: %w", err)
}

s = string(jsonData)
}
} else {
return fmt.Errorf("unsupported list type for %s", resourcePath)
}
default:
return fmt.Errorf("unsupported type %s for %s", resourceSchema.Type, resourcePath)
}
Expand Down Expand Up @@ -265,14 +313,47 @@ func awsAttributeToResourceData(values map[string]any, value string, resourcePat
setResourceValue(values, resourcePath, i, resourceSchemas)
case schema.TypeString:
setResourceValue(values, resourcePath, value, resourceSchemas)
case schema.TypeList:
if resourcePath == DeadLetterQueuePath && value != "" {
var redrivePolicy map[string]any
if err := json.Unmarshal([]byte(value), &redrivePolicy); err != nil {
return fmt.Errorf("failed to unmarshal redrive policy: %w", err)
}

deadLetterTargetArn := redrivePolicy["deadLetterTargetArn"].(string)

var terraformID string

if strings.HasPrefix(deadLetterTargetArn, SQSPrefix) {
parts := strings.Split(deadLetterTargetArn, ":")
if len(parts) >= 6 {
region := parts[3]
projectID := strings.TrimPrefix(parts[4], "project-")
queueName := parts[5]
terraformID = fmt.Sprintf("%s/%s/%s", region, projectID, queueName)
} else {
terraformID = deadLetterTargetArn
}
} else {
terraformID = deadLetterTargetArn
}

deadLetterConfig := map[string]any{
"id": terraformID,
"max_receive_count": int(redrivePolicy["maxReceiveCount"].(float64)),
}

setResourceValue(values, resourcePath, []any{deadLetterConfig}, resourceSchemas)
} else {
return fmt.Errorf("unsupported list type for %s", resourcePath)
}
default:
return fmt.Errorf("unsupported type %s for %s", resourceSchema.Type, resourcePath)
}

return nil
}

// awsAttributesToResourceData returns a map of valid values for a terraform schema from an attributes map and a conversion map
func awsAttributesToResourceData(attributes map[string]string, resourceSchemas map[string]*schema.Schema, attributesToResourceMap map[string]string) (map[string]any, error) {
values := make(map[string]any)

Expand Down
24 changes: 24 additions & 0 deletions internal/services/mnq/helpers_mnq_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ var SQSAttributesToResourceMap = map[string]string{
string(awstype.QueueAttributeNameContentBasedDeduplication): "content_based_deduplication",
string(awstype.QueueAttributeNameReceiveMessageWaitTimeSeconds): "receive_wait_time_seconds",
string(awstype.QueueAttributeNameVisibilityTimeout): "visibility_timeout_seconds",
string(awstype.QueueAttributeNameRedrivePolicy): "dead_letter_queue",
string(awstype.QueueAttributeNameQueueArn): "arn",
}

// Returns all managed SQS attribute names
Expand Down Expand Up @@ -208,6 +210,28 @@ func resourceMNQQueueCustomizeDiff(_ context.Context, d *schema.ResourceDiff, _
return errors.New("content-based deduplication can only be set for FIFO queue")
}

// Validate dead-letter queue configuration
if deadLetterConfig, ok := d.GetOk("dead_letter_queue"); ok {
deadLetterList := deadLetterConfig.([]any)
if len(deadLetterList) > 0 {
config := deadLetterList[0].(map[string]any)
queueID := config["id"].(string)
maxReceiveCount := config["max_receive_count"].(int)

if queueID == "" || strings.Contains(queueID, "scaleway_mnq_sqs_queue") {
return nil
}

if queueID == "" {
return errors.New("dead-letter queue ID cannot be empty")
}

if maxReceiveCount < 1 || maxReceiveCount > 1000 {
return errors.New("max_receive_count must be between 1 and 1,000")
}
}
}

if !nameRegex.MatchString(name) {
return fmt.Errorf("invalid queue name: %s (format is %s)", name, nameRegex.String())
}
Expand Down
26 changes: 26 additions & 0 deletions internal/services/mnq/sqs_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,27 @@ func ResourceSQSQueue() *schema.Resource {
ValidateFunc: validation.IntBetween(1024, 262_144),
Description: "The maximum size of a message. Should be in bytes.",
},
"dead_letter_queue": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: "Configuration for the dead-letter queue",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Required: true,
Description: "The ID or ARN of the dead-letter queue where messages are sent after the maximum receive count is exceeded.",
},
"max_receive_count": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(1, 1000),
Description: "The number of times a message is delivered to the source queue before being sent to the dead-letter queue. Must be between 1 and 1,000.",
},
},
},
},
"region": regional.Schema(),
"project_id": account.ProjectIDSchema(),

Expand All @@ -116,6 +137,11 @@ func ResourceSQSQueue() *schema.Resource {
Computed: true,
Description: "The URL of the queue",
},
"arn": {
Type: schema.TypeString,
Computed: true,
Description: "The ARN of the queue",
},
},
CustomizeDiff: resourceMNQQueueCustomizeDiff,
StateUpgraders: []schema.StateUpgrader{
Expand Down
Loading
Loading