From 8dd479dbe0515825fdf5784f242d3e8c22beae44 Mon Sep 17 00:00:00 2001 From: John Ewart Date: Tue, 12 May 2015 14:34:10 -0700 Subject: [PATCH 1/3] Initial SQS support --- builtin/providers/aws/config.go | 5 + builtin/providers/aws/provider.go | 1 + .../providers/aws/resource_aws_sqs_queue.go | 188 ++++++++++++++++++ examples/aws-sqs/main.tf | 22 ++ examples/aws-sqs/variables.tf | 5 + 5 files changed, 221 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_sqs_queue.go create mode 100644 examples/aws-sqs/main.tf create mode 100644 examples/aws-sqs/variables.tf diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 552a0f446..bd03236ad 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -17,6 +17,7 @@ import ( "github.com/awslabs/aws-sdk-go/service/rds" "github.com/awslabs/aws-sdk-go/service/route53" "github.com/awslabs/aws-sdk-go/service/s3" + "github.com/awslabs/aws-sdk-go/service/sqs" ) type Config struct { @@ -35,6 +36,7 @@ type AWSClient struct { elbconn *elb.ELB autoscalingconn *autoscaling.AutoScaling s3conn *s3.S3 + sqsconn *sqs.SQS r53conn *route53.Route53 region string rdsconn *rds.RDS @@ -84,6 +86,9 @@ func (c *Config) Client() (interface{}, error) { log.Println("[INFO] Initializing S3 connection") client.s3conn = s3.New(awsConfig) + log.Println("[INFO] Initializing SQS connection") + client.sqsconn = sqs.New(awsConfig) + log.Println("[INFO] Initializing RDS Connection") client.rdsconn = rds.New(awsConfig) diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 095e392dd..b0af4e7be 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -121,6 +121,7 @@ func Provider() terraform.ResourceProvider { "aws_s3_bucket": resourceAwsS3Bucket(), "aws_security_group": resourceAwsSecurityGroup(), "aws_security_group_rule": resourceAwsSecurityGroupRule(), + "aws_sqs_queue": resourceAwsSqsQueue(), "aws_subnet": resourceAwsSubnet(), "aws_vpc_dhcp_options_association": resourceAwsVpcDhcpOptionsAssociation(), "aws_vpc_dhcp_options": resourceAwsVpcDhcpOptions(), diff --git a/builtin/providers/aws/resource_aws_sqs_queue.go b/builtin/providers/aws/resource_aws_sqs_queue.go new file mode 100644 index 000000000..affabfed2 --- /dev/null +++ b/builtin/providers/aws/resource_aws_sqs_queue.go @@ -0,0 +1,188 @@ +package aws + +import ( + "fmt" + "log" + "strconv" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/sqs" +) + +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", +} + + +func resourceAwsSqsQueue() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSqsQueueCreate, + Read: resourceAwsSqsQueueRead, + Update: resourceAwsSqsQueueUpdate, + Delete: resourceAwsSqsQueueDelete, + + Schema: map[string]*schema.Schema{ + "queue": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "delay_seconds": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "max_message_size": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "message_retention_seconds": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "receive_wait_time_seconds": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "visibility_timeout_seconds": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "policy": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "redrive_policy": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func resourceAwsSqsQueueCreate(d *schema.ResourceData, meta interface{}) error { + sqsconn := meta.(*AWSClient).sqsconn + + queue := d.Get("queue").(string) + + log.Printf("[DEBUG] SQS queue create: %s", queue) + + req := &sqs.CreateQueueInput{ + QueueName: aws.String(queue), + } + + attributes := make(map[string]*string) + + resource := *resourceAwsSqsQueue() + + for k, s := range resource.Schema { + if attrKey, ok := AttributeMap[k]; ok { + if value, ok := d.GetOk(k); ok { + if s.Type == schema.TypeInt { + attributes[attrKey] = aws.String(strconv.Itoa(value.(int))) + } else { + attributes[attrKey] = aws.String(value.(string)) + } + } + + } + } + + if len(attributes) > 0 { + req.Attributes = &attributes + } + + output, err := sqsconn.CreateQueue(req) + if err != nil { + return fmt.Errorf("Error creating SQS queue: %s", err) + } + + d.SetId(*output.QueueURL) + + return resourceAwsSqsQueueUpdate(d, meta) +} + +func resourceAwsSqsQueueUpdate(d *schema.ResourceData, meta interface{}) error { + sqsconn := meta.(*AWSClient).sqsconn + attributes := make(map[string]*string) + + resource := *resourceAwsSqsQueue() + + for k, s := range resource.Schema { + if attrKey, ok := AttributeMap[k]; ok { + if d.HasChange(k) { + log.Printf("[DEBUG] Updating %s", attrKey) + _, n := d.GetChange(k) + if s.Type == schema.TypeInt { + attributes[attrKey] = aws.String(strconv.Itoa(n.(int))) + } else { + attributes[attrKey] = aws.String(n.(string)) + } + } + } + } + + if len(attributes) > 0 { + req := &sqs.SetQueueAttributesInput{ + QueueURL: aws.String(d.Id()), + Attributes: &attributes, + } + sqsconn.SetQueueAttributes(req) + } + + return resourceAwsSqsQueueRead(d, meta) +} + +func resourceAwsSqsQueueRead(d *schema.ResourceData, meta interface{}) error { + sqsconn := meta.(*AWSClient).sqsconn + + attributeOutput, err := sqsconn.GetQueueAttributes(&sqs.GetQueueAttributesInput{ + QueueURL: aws.String(d.Id()), + }) + if err != nil { + return err + } + + if attributeOutput.Attributes != nil && len(*attributeOutput.Attributes) > 0 { + attrmap := *attributeOutput.Attributes + resource := *resourceAwsSqsQueue() + // 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 { + value, err := strconv.Atoi(*attrmap[oKey]) + if err != nil { + return err + } + d.Set(iKey, value) + } else { + d.Set(iKey, *attrmap[oKey]) + } + } + } + } + + return nil +} + +func resourceAwsSqsQueueDelete(d *schema.ResourceData, meta interface{}) error { + sqsconn := meta.(*AWSClient).sqsconn + + log.Printf("[DEBUG] SQS Delete Queue: %s", d.Id()) + _, err := sqsconn.DeleteQueue(&sqs.DeleteQueueInput{ + QueueURL: aws.String(d.Id()), + }) + if err != nil { + return err + } + return nil +} + + diff --git a/examples/aws-sqs/main.tf b/examples/aws-sqs/main.tf new file mode 100644 index 000000000..4e5979fd5 --- /dev/null +++ b/examples/aws-sqs/main.tf @@ -0,0 +1,22 @@ +# Specify the provider and access details +provider "aws" { + region = "${var.aws_region}" +} + +resource "aws_sqs_queue" "terraform_queue" { + queue = "terraform-example-renamed" +} + +resource "aws_sqs_queue" "terrform_queue_attr" { + queue = "terraform-example-attr" + delay_seconds = 90 + max_message_size = 2048 + message_retention_seconds = 86400 + receive_wait_time_seconds = 10 +} + +resource "aws_sqs_queue" "terraform_queue_too" { + queue = "terraform-queue-too" + delay_seconds = 120 + max_message_size = 4096 +} diff --git a/examples/aws-sqs/variables.tf b/examples/aws-sqs/variables.tf new file mode 100644 index 000000000..617a5719f --- /dev/null +++ b/examples/aws-sqs/variables.tf @@ -0,0 +1,5 @@ +variable "aws_region" { + description = "The AWS region to create things in." + default = "us-west-2" +} + From d538194f59b6c60c958292e3539ac710f525c41a Mon Sep 17 00:00:00 2001 From: John Ewart Date: Fri, 15 May 2015 13:09:20 -0700 Subject: [PATCH 2/3] Added docs, tests, and updated SQS code --- .../providers/aws/resource_aws_sqs_queue.go | 17 +- .../aws/resource_aws_sqs_queue_test.go | 179 ++++++++++++++++++ .../providers/aws/r/sqs_queue.html.markdown | 38 ++++ website/source/layouts/aws.erb | 10 +- 4 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 builtin/providers/aws/resource_aws_sqs_queue_test.go create mode 100644 website/source/docs/providers/aws/r/sqs_queue.html.markdown diff --git a/builtin/providers/aws/resource_aws_sqs_queue.go b/builtin/providers/aws/resource_aws_sqs_queue.go index affabfed2..6aa56a553 100644 --- a/builtin/providers/aws/resource_aws_sqs_queue.go +++ b/builtin/providers/aws/resource_aws_sqs_queue.go @@ -22,6 +22,9 @@ var AttributeMap = map[string]string{ } +// A number of these are marked as computed because if you don't +// provide a value, SQS will provide you with defaults (which are the +// default values specified below) func resourceAwsSqsQueue() *schema.Resource { return &schema.Resource{ Create: resourceAwsSqsQueueCreate, @@ -30,7 +33,7 @@ func resourceAwsSqsQueue() *schema.Resource { Delete: resourceAwsSqsQueueDelete, Schema: map[string]*schema.Schema{ - "queue": &schema.Schema{ + "name": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, @@ -38,22 +41,27 @@ func resourceAwsSqsQueue() *schema.Resource { "delay_seconds": &schema.Schema{ Type: schema.TypeInt, Optional: true, + Computed: true, }, "max_message_size": &schema.Schema{ Type: schema.TypeInt, Optional: true, + Computed: true, }, "message_retention_seconds": &schema.Schema{ Type: schema.TypeInt, Optional: true, + Computed: true, }, "receive_wait_time_seconds": &schema.Schema{ Type: schema.TypeInt, Optional: true, + Computed: true, }, "visibility_timeout_seconds": &schema.Schema{ Type: schema.TypeInt, Optional: true, + Computed: true, }, "policy": &schema.Schema{ Type: schema.TypeString, @@ -70,12 +78,12 @@ func resourceAwsSqsQueue() *schema.Resource { func resourceAwsSqsQueueCreate(d *schema.ResourceData, meta interface{}) error { sqsconn := meta.(*AWSClient).sqsconn - queue := d.Get("queue").(string) + name := d.Get("name").(string) - log.Printf("[DEBUG] SQS queue create: %s", queue) + log.Printf("[DEBUG] SQS queue create: %s", name) req := &sqs.CreateQueueInput{ - QueueName: aws.String(queue), + QueueName: aws.String(name), } attributes := make(map[string]*string) @@ -145,6 +153,7 @@ func resourceAwsSqsQueueRead(d *schema.ResourceData, meta interface{}) error { attributeOutput, err := sqsconn.GetQueueAttributes(&sqs.GetQueueAttributesInput{ QueueURL: aws.String(d.Id()), + AttributeNames: []*string{aws.String("All")}, }) if err != nil { return err diff --git a/builtin/providers/aws/resource_aws_sqs_queue_test.go b/builtin/providers/aws/resource_aws_sqs_queue_test.go new file mode 100644 index 000000000..5f99b6615 --- /dev/null +++ b/builtin/providers/aws/resource_aws_sqs_queue_test.go @@ -0,0 +1,179 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/sqs" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSSQSQueue(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSQSQueueDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSSQSConfigWithDefaults, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSQSExistsWithDefaults("aws_sqs_queue.queue-with-defaults"), + ), + }, + resource.TestStep{ + Config: testAccAWSSQSConfigWithOverrides, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSQSExistsWithOverrides("aws_sqs_queue.queue-with-overrides"), + ), + }, + }, + }) +} + + +func testAccCheckAWSSQSQueueDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).sqsconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_sqs_queue" { + continue + } + + // Check if queue exists by checking for its attributes + params := &sqs.GetQueueAttributesInput{ + QueueURL: aws.String(rs.Primary.ID), + } + _, err := conn.GetQueueAttributes(params) + if err == nil { + return fmt.Errorf("Queue %s still exists. Failing!", rs.Primary.ID) + } + + // Verify the error is what we want + _, ok := err.(aws.APIError) + if !ok { + return err + } + } + + return nil +} + + +func testAccCheckAWSSQSExistsWithDefaults(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!") + } + + conn := testAccProvider.Meta().(*AWSClient).sqsconn + + params := &sqs.GetQueueAttributesInput{ + QueueURL: aws.String(rs.Primary.ID), + AttributeNames: []*string{aws.String("All")}, + } + resp, err := conn.GetQueueAttributes(params) + + if err != nil { + return err + } + + // checking if attributes are defaults + for k, v := range *resp.Attributes { + if k == "VisibilityTimeout" && *v != "30" { + return fmt.Errorf("VisibilityTimeout (%s) was not set to 30", *v) + } + + if k == "MessageRetentionPeriod" && *v != "345600" { + return fmt.Errorf("MessageRetentionPeriod (%s) was not set to 345600", *v) + } + + if k == "MaximumMessageSize" && *v != "262144" { + return fmt.Errorf("MaximumMessageSize (%s) was not set to 262144", *v) + } + + if k == "DelaySeconds" && *v != "0" { + return fmt.Errorf("DelaySeconds (%s) was not set to 0", *v) + } + + if k == "ReceiveMessageWaitTimeSeconds" && *v != "0" { + return fmt.Errorf("ReceiveMessageWaitTimeSeconds (%s) was not set to 0", *v) + } + } + + return nil + } +} + +func testAccCheckAWSSQSExistsWithOverrides(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!") + } + + conn := testAccProvider.Meta().(*AWSClient).sqsconn + + params := &sqs.GetQueueAttributesInput{ + QueueURL: aws.String(rs.Primary.ID), + AttributeNames: []*string{aws.String("All")}, + } + resp, err := conn.GetQueueAttributes(params) + + if err != nil { + return err + } + + // checking if attributes match our overrides + for k, v := range *resp.Attributes { + if k == "VisibilityTimeout" && *v != "60" { + return fmt.Errorf("VisibilityTimeout (%s) was not set to 60", *v) + } + + if k == "MessageRetentionPeriod" && *v != "86400" { + return fmt.Errorf("MessageRetentionPeriod (%s) was not set to 86400", *v) + } + + if k == "MaximumMessageSize" && *v != "2048" { + return fmt.Errorf("MaximumMessageSize (%s) was not set to 2048", *v) + } + + if k == "DelaySeconds" && *v != "90" { + return fmt.Errorf("DelaySeconds (%s) was not set to 90", *v) + } + + if k == "ReceiveMessageWaitTimeSeconds" && *v != "10" { + return fmt.Errorf("ReceiveMessageWaitTimeSeconds (%s) was not set to 10", *v) + } + } + + return nil + } +} + +const testAccAWSSQSConfigWithDefaults = ` +resource "aws_sqs_queue" "queue-with-defaults" { + name = "test-sqs-queue-with-defaults" +} +` + +const testAccAWSSQSConfigWithOverrides = ` +resource "aws_sqs_queue" "queue-with-overrides" { + name = "test-sqs-queue-with-overrides" + delay_seconds = 90 + max_message_size = 2048 + message_retention_seconds = 86400 + receive_wait_time_seconds = 10 + visibility_timeout_seconds = 60 +} +` \ No newline at end of file diff --git a/website/source/docs/providers/aws/r/sqs_queue.html.markdown b/website/source/docs/providers/aws/r/sqs_queue.html.markdown new file mode 100644 index 000000000..e03c4a2f5 --- /dev/null +++ b/website/source/docs/providers/aws/r/sqs_queue.html.markdown @@ -0,0 +1,38 @@ +--- +layout: "aws" +page_title: "AWS: aws_sqs_queue" +sidebar_current: "docs-aws-resource-sqs" +description: |- + Provides a SQS resource. +--- + +# aws\_sqs\_queue + +## Example Usage + +``` +resource "aws_sqs_queue" "terrform_queue" { + name = "terraform-example-queue" + delay_seconds = 90 + max_message_size = 2048 + message_retention_seconds = 86400 + receive_wait_time_seconds = 10 +} +``` + +## Argument Reference + +The following arguments are supported: +* `name` - (Required) This is the human-readable name of the queue +* `visibility_timeout_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 30 seconds +* `message_retention_seconds` - (Optional) The number of seconds Amazon SQS retains a message. Integer representing seconds, from 60 (1 minute) to 1209600 (14 days). The default for this attribute is 345600 (4 days). +* `max_message_size` - (Optional) The limit of how many bytes a message can contain before Amazon SQS rejects it. An integer from 1024 bytes (1 KiB) up to 262144 bytes (256 KiB). The default for this attribute is 262144 (256 KiB). +* `delay_seconds` - (Optional) The visibility timeout for the queue. An integer from 0 to 43200 (12 hours). The default for this attribute is 30. For more information about visibility timeout. +* `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 + +## Attributes Reference + +The following attributes are exported: + +* `id` - The URL for the created Amazon SQS queue. \ No newline at end of file diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 528946ae9..7055332cf 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -152,9 +152,13 @@ aws_security_group - > - aws_security_group_rule - + > + aws_security_group_rule + + + > + aws_sqs_queue + > aws_subnet From cb05e5169a9f0baa7fd4990388b8392be8943ffa Mon Sep 17 00:00:00 2001 From: John Ewart Date: Fri, 15 May 2015 13:10:03 -0700 Subject: [PATCH 3/3] Removing examples in favor of docs --- examples/aws-sqs/main.tf | 22 ---------------------- examples/aws-sqs/variables.tf | 5 ----- 2 files changed, 27 deletions(-) delete mode 100644 examples/aws-sqs/main.tf delete mode 100644 examples/aws-sqs/variables.tf diff --git a/examples/aws-sqs/main.tf b/examples/aws-sqs/main.tf deleted file mode 100644 index 4e5979fd5..000000000 --- a/examples/aws-sqs/main.tf +++ /dev/null @@ -1,22 +0,0 @@ -# Specify the provider and access details -provider "aws" { - region = "${var.aws_region}" -} - -resource "aws_sqs_queue" "terraform_queue" { - queue = "terraform-example-renamed" -} - -resource "aws_sqs_queue" "terrform_queue_attr" { - queue = "terraform-example-attr" - delay_seconds = 90 - max_message_size = 2048 - message_retention_seconds = 86400 - receive_wait_time_seconds = 10 -} - -resource "aws_sqs_queue" "terraform_queue_too" { - queue = "terraform-queue-too" - delay_seconds = 120 - max_message_size = 4096 -} diff --git a/examples/aws-sqs/variables.tf b/examples/aws-sqs/variables.tf deleted file mode 100644 index 617a5719f..000000000 --- a/examples/aws-sqs/variables.tf +++ /dev/null @@ -1,5 +0,0 @@ -variable "aws_region" { - description = "The AWS region to create things in." - default = "us-west-2" -} -