provider/aws: Added SQS FIFO queues (#10614)

This commit is contained in:
Ninir 2016-12-12 18:40:59 +01:00 committed by Paul Stack
parent 5eb614acee
commit fc5b05ebc1
6 changed files with 344 additions and 26 deletions

View File

@ -18,14 +18,40 @@ func TestAccAWSSQSQueue_importBasic(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSQSQueueDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccAWSSQSConfigWithDefaults(queueName),
},
resource.TestStep{
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("aws_sqs_queue.queue", "fifo_queue", "false"),
),
},
},
})
}
func TestAccAWSSQSQueue_importFifo(t *testing.T) {
resourceName := "aws_sqs_queue.queue"
queueName := fmt.Sprintf("sqs-queue-%s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSQSQueueDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSQSFifoConfigWithDefaults(queueName),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("aws_sqs_queue.queue", "fifo_queue", "true"),
),
},
},
})

View File

@ -16,14 +16,16 @@ import (
)
var AttributeMap = map[string]string{
"delay_seconds": "DelaySeconds",
"max_message_size": "MaximumMessageSize",
"message_retention_seconds": "MessageRetentionPeriod",
"receive_wait_time_seconds": "ReceiveMessageWaitTimeSeconds",
"visibility_timeout_seconds": "VisibilityTimeout",
"policy": "Policy",
"redrive_policy": "RedrivePolicy",
"arn": "QueueArn",
"delay_seconds": "DelaySeconds",
"max_message_size": "MaximumMessageSize",
"message_retention_seconds": "MessageRetentionPeriod",
"receive_wait_time_seconds": "ReceiveMessageWaitTimeSeconds",
"visibility_timeout_seconds": "VisibilityTimeout",
"policy": "Policy",
"redrive_policy": "RedrivePolicy",
"arn": "QueueArn",
"fifo_queue": "FifoQueue",
"content_based_deduplication": "ContentBasedDeduplication",
}
// A number of these are marked as computed because if you don't
@ -90,6 +92,17 @@ func resourceAwsSqsQueue() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"fifo_queue": {
Type: schema.TypeBool,
Default: false,
ForceNew: true,
Optional: true,
},
"content_based_deduplication": {
Type: schema.TypeBool,
Default: false,
Optional: true,
},
},
}
}
@ -98,6 +111,22 @@ func resourceAwsSqsQueueCreate(d *schema.ResourceData, meta interface{}) error {
sqsconn := meta.(*AWSClient).sqsconn
name := d.Get("name").(string)
fq := d.Get("fifo_queue").(bool)
cbd := d.Get("content_based_deduplication").(bool)
if fq {
if errors := validateSQSFifoQueueName(name, "name"); len(errors) > 0 {
return fmt.Errorf("Error validating the FIFO queue name: %v", errors)
}
} else {
if errors := validateSQSQueueName(name, "name"); len(errors) > 0 {
return fmt.Errorf("Error validating SQS queue name: %v", errors)
}
}
if !fq && cbd {
return fmt.Errorf("Content based deduplication can only be set with FIFO queues")
}
log.Printf("[DEBUG] SQS queue create: %s", name)
@ -112,9 +141,12 @@ func resourceAwsSqsQueueCreate(d *schema.ResourceData, meta interface{}) error {
for k, s := range resource.Schema {
if attrKey, ok := AttributeMap[k]; ok {
if value, ok := d.GetOk(k); ok {
if s.Type == schema.TypeInt {
switch s.Type {
case schema.TypeInt:
attributes[attrKey] = aws.String(strconv.Itoa(value.(int)))
} else {
case schema.TypeBool:
attributes[attrKey] = aws.String(strconv.FormatBool(value.(bool)))
default:
attributes[attrKey] = aws.String(value.(string))
}
}
@ -147,9 +179,12 @@ func resourceAwsSqsQueueUpdate(d *schema.ResourceData, meta interface{}) error {
if d.HasChange(k) {
log.Printf("[DEBUG] Updating %s", attrKey)
_, n := d.GetChange(k)
if s.Type == schema.TypeInt {
switch s.Type {
case schema.TypeInt:
attributes[attrKey] = aws.String(strconv.Itoa(n.(int)))
} else {
case schema.TypeBool:
attributes[attrKey] = aws.String(strconv.FormatBool(n.(bool)))
default:
attributes[attrKey] = aws.String(n.(string))
}
}
@ -201,14 +236,22 @@ func resourceAwsSqsQueueRead(d *schema.ResourceData, meta interface{}) error {
// iKey = internal struct key, oKey = AWS Attribute Map key
for iKey, oKey := range AttributeMap {
if attrmap[oKey] != nil {
if resource.Schema[iKey].Type == schema.TypeInt {
switch resource.Schema[iKey].Type {
case schema.TypeInt:
value, err := strconv.Atoi(*attrmap[oKey])
if err != nil {
return err
}
d.Set(iKey, value)
log.Printf("[DEBUG] Reading %s => %s -> %d", iKey, oKey, value)
} else {
case schema.TypeBool:
value, err := strconv.ParseBool(*attrmap[oKey])
if err != nil {
return err
}
d.Set(iKey, value)
log.Printf("[DEBUG] Reading %s => %s -> %t", iKey, oKey, value)
default:
log.Printf("[DEBUG] Reading %s => %s -> %s", iKey, oKey, *attrmap[oKey])
d.Set(iKey, *attrmap[oKey])
}
@ -216,6 +259,12 @@ func resourceAwsSqsQueueRead(d *schema.ResourceData, meta interface{}) error {
}
}
// Since AWS does not send the FifoQueue attribute back when the queue
// is a standard one (even to false), this enforces the queue to be set
// to the correct value.
d.Set("fifo_queue", d.Get("fifo_queue").(bool))
d.Set("content_based_deduplication", d.Get("content_based_deduplication").(bool))
return nil
}

View File

@ -11,10 +11,11 @@ import (
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/jen20/awspolicyequivalence"
"regexp"
)
func TestAccAWSSQSQueue_basic(t *testing.T) {
queueName := fmt.Sprintf("sqs-queue-%s", acctest.RandString(5))
queueName := fmt.Sprintf("sqs-queue-%s", acctest.RandString(10))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
@ -43,8 +44,8 @@ func TestAccAWSSQSQueue_basic(t *testing.T) {
}
func TestAccAWSSQSQueue_policy(t *testing.T) {
queueName := fmt.Sprintf("sqs-queue-%s", acctest.RandString(5))
topicName := fmt.Sprintf("sns-topic-%s", acctest.RandString(5))
queueName := fmt.Sprintf("sqs-queue-%s", acctest.RandString(10))
topicName := fmt.Sprintf("sns-topic-%s", acctest.RandString(10))
expectedPolicyText := fmt.Sprintf(
`{"Version": "2012-10-17","Id": "sqspolicy","Statement":[{"Sid": "Stmt1451501026839","Effect": "Allow","Principal":"*","Action":"sqs:SendMessage","Resource":"arn:aws:sqs:us-west-2:470663696735:%s","Condition":{"ArnEquals":{"aws:SourceArn":"arn:aws:sns:us-west-2:470663696735:%s"}}}]}`,
@ -72,7 +73,7 @@ func TestAccAWSSQSQueue_redrivePolicy(t *testing.T) {
CheckDestroy: testAccCheckAWSSQSQueueDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSQSConfigWithRedrive(acctest.RandStringFromCharSet(5, acctest.CharSetAlpha)),
Config: testAccAWSSQSConfigWithRedrive(acctest.RandString(10)),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSQSExistsWithDefaults("aws_sqs_queue.my_dead_letter_queue"),
),
@ -83,8 +84,8 @@ func TestAccAWSSQSQueue_redrivePolicy(t *testing.T) {
// Tests formatting and compacting of Policy, Redrive json
func TestAccAWSSQSQueue_Policybasic(t *testing.T) {
queueName := fmt.Sprintf("sqs-queue-%s", acctest.RandString(5))
topicName := fmt.Sprintf("sns-topic-%s", acctest.RandString(5))
queueName := fmt.Sprintf("sqs-queue-%s", acctest.RandString(10))
topicName := fmt.Sprintf("sns-topic-%s", acctest.RandString(10))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
@ -100,6 +101,70 @@ func TestAccAWSSQSQueue_Policybasic(t *testing.T) {
})
}
func TestAccAWSSQSQueue_FIFO(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSQSQueueDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSQSConfigWithFIFO(acctest.RandString(10)),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSQSExists("aws_sqs_queue.queue"),
resource.TestCheckResourceAttr("aws_sqs_queue.queue", "fifo_queue", "true"),
),
},
},
})
}
func TestAccAWSSQSQueue_FIFOExpectNameError(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSQSQueueDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSQSConfigWithFIFOExpectError(acctest.RandString(10)),
ExpectError: regexp.MustCompile(`Error validating the FIFO queue name`),
},
},
})
}
func TestAccAWSSQSQueue_FIFOWithContentBasedDeduplication(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSQSQueueDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSQSConfigWithFIFOContentBasedDeduplication(acctest.RandString(10)),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSQSExists("aws_sqs_queue.queue"),
resource.TestCheckResourceAttr("aws_sqs_queue.queue", "fifo_queue", "true"),
resource.TestCheckResourceAttr("aws_sqs_queue.queue", "content_based_deduplication", "true"),
),
},
},
})
}
func TestAccAWSSQSQueue_ExpectContentBasedDeduplicationError(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSQSQueueDestroy,
Steps: []resource.TestStep{
{
Config: testAccExpectContentBasedDeduplicationError(acctest.RandString(10)),
ExpectError: regexp.MustCompile(`Content based deduplication can only be set with FIFO queues`),
},
},
})
}
func testAccCheckAWSSQSQueueDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).sqsconn
@ -169,6 +234,21 @@ func testAccCheckAWSQSHasPolicy(n string, expectedPolicyText string) resource.Te
}
}
func testAccCheckAWSSQSExists(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No Queue URL specified!")
}
return nil
}
}
func testAccCheckAWSSQSExistsWithDefaults(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
@ -277,6 +357,15 @@ resource "aws_sqs_queue" "queue" {
`, r)
}
func testAccAWSSQSFifoConfigWithDefaults(r string) string {
return fmt.Sprintf(`
resource "aws_sqs_queue" "queue" {
name = "%s.fifo"
fifo_queue = true
}
`, r)
}
func testAccAWSSQSConfigWithOverrides(r string) string {
return fmt.Sprintf(`
resource "aws_sqs_queue" "queue" {
@ -362,3 +451,40 @@ resource "aws_sns_topic_subscription" "test_queue_target" {
}
`, topic, queue)
}
func testAccAWSSQSConfigWithFIFO(queue string) string {
return fmt.Sprintf(`
resource "aws_sqs_queue" "queue" {
name = "%s.fifo"
fifo_queue = true
}
`, queue)
}
func testAccAWSSQSConfigWithFIFOContentBasedDeduplication(queue string) string {
return fmt.Sprintf(`
resource "aws_sqs_queue" "queue" {
name = "%s.fifo"
fifo_queue = true
content_based_deduplication = true
}
`, queue)
}
func testAccAWSSQSConfigWithFIFOExpectError(queue string) string {
return fmt.Sprintf(`
resource "aws_sqs_queue" "queue" {
name = "%s"
fifo_queue = true
}
`, queue)
}
func testAccExpectContentBasedDeduplicationError(queue string) string {
return fmt.Sprintf(`
resource "aws_sqs_queue" "queue" {
name = "%s"
content_based_deduplication = true
}
`, queue)
}

View File

@ -539,3 +539,37 @@ func validateApiGatewayIntegrationType(v interface{}, k string) (ws []string, er
}
return
}
func validateSQSQueueName(v interface{}, k string) (errors []error) {
value := v.(string)
if len(value) > 80 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 80 characters", k))
}
if !regexp.MustCompile(`^[0-9A-Za-z-_]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf("only alphanumeric characters and hyphens allowed in %q", k))
}
return
}
func validateSQSFifoQueueName(v interface{}, k string) (errors []error) {
value := v.(string)
if len(value) > 80 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 80 characters", k))
}
if !regexp.MustCompile(`^[0-9A-Za-z-_.]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf("only alphanumeric characters and hyphens allowed in %q", k))
}
if regexp.MustCompile(`^[^a-zA-Z0-9-_]`).MatchString(value) {
errors = append(errors, fmt.Errorf("FIFO queue name must start with one of these characters [a-zA-Z0-9-_]: %v", value))
}
if !regexp.MustCompile(`\.fifo$`).MatchString(value) {
errors = append(errors, fmt.Errorf("FIFO queue name should ends with \".fifo\": %v", value))
}
return
}

View File

@ -1,6 +1,7 @@
package aws
import (
"fmt"
"strings"
"testing"
@ -794,3 +795,73 @@ func TestValidateApiGatewayIntegrationType(t *testing.T) {
}
}
}
func TestValidateSQSQueueName(t *testing.T) {
validNames := []string{
"valid-name",
"valid02-name",
"Valid-Name1",
"_",
"-",
strings.Repeat("W", 80),
}
for _, v := range validNames {
if errors := validateSQSQueueName(v, "name"); len(errors) > 0 {
t.Fatalf("%q should be a valid SQS queue Name", v)
}
}
invalidNames := []string{
"Here is a name with: colon",
"another * invalid name",
"also $ invalid",
"This . is also %% invalid@!)+(",
"*",
"",
" ",
".",
strings.Repeat("W", 81), // length > 80
}
for _, v := range invalidNames {
if errors := validateSQSQueueName(v, "name"); len(errors) == 0 {
t.Fatalf("%q should be an invalid SQS queue Name", v)
}
}
}
func TestValidateSQSFifoQueueName(t *testing.T) {
validNames := []string{
"valid-name.fifo",
"valid02-name.fifo",
"Valid-Name1.fifo",
"_.fifo",
"a.fifo",
"A.fifo",
"9.fifo",
"-.fifo",
fmt.Sprintf("%s.fifo", strings.Repeat("W", 75)),
}
for _, v := range validNames {
if errors := validateSQSFifoQueueName(v, "name"); len(errors) > 0 {
t.Fatalf("%q should be a valid SQS FIFO queue Name: %v", v, errors)
}
}
invalidNames := []string{
"Here is a name with: colon",
"another * invalid name",
"also $ invalid",
"This . is also %% invalid@!)+(",
".fifo",
"*",
"",
" ",
".",
strings.Repeat("W", 81), // length > 80
}
for _, v := range invalidNames {
if errors := validateSQSFifoQueueName(v, "name"); len(errors) == 0 {
t.Fatalf("%q should be an invalid SQS FIFO queue Name: %v", v, errors)
}
}
}

View File

@ -21,6 +21,16 @@ resource "aws_sqs_queue" "terraform_queue" {
}
```
## FIFO queue
```
resource "aws_sqs_queue" "terraform_queue" {
name = "terraform-example-queue.fifo"
fifo_queue = true
content_based_deduplication = true
}
```
## Argument Reference
The following arguments are supported:
@ -32,7 +42,9 @@ The following arguments are supported:
* `delay_seconds` - (Optional) The time in seconds that the delivery of all messages in the queue will be delayed. An integer from 0 to 900 (15 minutes). The default for this attribute is 0 seconds.
* `receive_wait_time_seconds` - (Optional) The time for which a ReceiveMessage call will wait for a message to arrive (long polling) before returning. An integer from 0 to 20 (seconds). The default for this attribute is 0, meaning that the call will return immediately.
* `policy` - (Optional) The JSON policy for the SQS queue
* `redrive_policy` - (Optional) The JSON policy to set up the Dead Letter Queue, see [AWS docs](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/SQSDeadLetterQueue.html). **Note:** when specifying `maxReceiveCount`, you must specify it as an integer (`5`), and not a string (`"5"`).
* `redrive_policy` - (Optional) The JSON policy to set up the Dead Letter Queue, see [AWS docs](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/SQSDeadLetterQueue.html). **Note:** when specifying `maxReceiveCount`, you must specify it as an integer (`5`), and not a string (`"5"`).
* `fifo_queue` - (Optional) Boolean designating a FIFO queue. If not set, it defaults to `false` making it standard.
* `content_based_deduplication` - (Optional) Enables content-based deduplication for FIFO queues. For more information, see the [related documentation](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html#FIFO-queues-exactly-once-processing)
## Attributes Reference
@ -43,8 +55,8 @@ The following attributes are exported:
## Import
SQS Queues can be imported using the `queue url`, e.g.
SQS Queues can be imported using the `queue url`, e.g.
```
$ terraform import aws_sqs_queue.public_queue https://queue.amazonaws.com/80398EXAMPLE/MyQueue
```
```