diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 106ce575d..35c082c84 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -156,6 +156,7 @@ func Provider() terraform.ResourceProvider { "aws_alb_listener": resourceAwsAlbListener(), "aws_alb_listener_rule": resourceAwsAlbListenerRule(), "aws_alb_target_group": resourceAwsAlbTargetGroup(), + "aws_alb_target_group_attachment": resourceAwsAlbTargetGroupAttachment(), "aws_ami": resourceAwsAmi(), "aws_ami_copy": resourceAwsAmiCopy(), "aws_ami_from_instance": resourceAwsAmiFromInstance(), diff --git a/builtin/providers/aws/resource_aws_alb_target_group_attachment.go b/builtin/providers/aws/resource_aws_alb_target_group_attachment.go new file mode 100644 index 000000000..e6ba35b94 --- /dev/null +++ b/builtin/providers/aws/resource_aws_alb_target_group_attachment.go @@ -0,0 +1,131 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsAlbTargetGroupAttachment() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsAlbAttachmentCreate, + Read: resourceAwsAlbAttachmentRead, + Delete: resourceAwsAlbAttachmentDelete, + + Schema: map[string]*schema.Schema{ + "target_group_arn": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + + "target_id": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + + "port": { + Type: schema.TypeInt, + ForceNew: true, + Required: true, + }, + }, + } +} + +func resourceAwsAlbAttachmentCreate(d *schema.ResourceData, meta interface{}) error { + elbconn := meta.(*AWSClient).elbv2conn + + params := &elbv2.RegisterTargetsInput{ + TargetGroupArn: aws.String(d.Get("target_group_arn").(string)), + Targets: []*elbv2.TargetDescription{ + { + Id: aws.String(d.Get("target_id").(string)), + Port: aws.Int64(int64(d.Get("port").(int))), + }, + }, + } + + log.Printf("[INFO] Registering Target %s (%d) with Target Group %s", d.Get("target_id").(string), + d.Get("port").(int), d.Get("target_group_arn").(string)) + + _, err := elbconn.RegisterTargets(params) + if err != nil { + return errwrap.Wrapf("Error registering targets with target group: {{err}}", err) + } + + d.SetId(resource.PrefixedUniqueId(fmt.Sprintf("%s-", d.Get("target_group_arn")))) + + return nil +} + +func resourceAwsAlbAttachmentDelete(d *schema.ResourceData, meta interface{}) error { + elbconn := meta.(*AWSClient).elbv2conn + + params := &elbv2.DeregisterTargetsInput{ + TargetGroupArn: aws.String(d.Get("target_group_arn").(string)), + Targets: []*elbv2.TargetDescription{ + { + Id: aws.String(d.Get("target_id").(string)), + Port: aws.Int64(int64(d.Get("port").(int))), + }, + }, + } + + _, err := elbconn.DeregisterTargets(params) + if err != nil && !isTargetGroupNotFound(err) { + return errwrap.Wrapf("Error deregistering Targets: {{err}}", err) + } + + d.SetId("") + + return nil +} + +// resourceAwsAlbAttachmentRead requires all of the fields in order to describe the correct +// target, so there is no work to do beyond ensuring that the target and group still exist. +func resourceAwsAlbAttachmentRead(d *schema.ResourceData, meta interface{}) error { + elbconn := meta.(*AWSClient).elbv2conn + resp, err := elbconn.DescribeTargetHealth(&elbv2.DescribeTargetHealthInput{ + TargetGroupArn: aws.String(d.Get("target_group_arn").(string)), + Targets: []*elbv2.TargetDescription{ + { + Id: aws.String(d.Get("target_id").(string)), + Port: aws.Int64(int64(d.Get("port").(int))), + }, + }, + }) + if err != nil { + if isTargetGroupNotFound(err) { + log.Printf("[WARN] Target group does not exist, removing target attachment %s", d.Id()) + d.SetId("") + return nil + } + if isInvalidTarget(err) { + log.Printf("[WARN] Target does not exist, removing target attachment %s", d.Id()) + d.SetId("") + return nil + } + return errwrap.Wrapf("Error reading Target Health: {{err}}", err) + } + + if len(resp.TargetHealthDescriptions) != 1 { + log.Printf("[WARN] Target does not exist, removing target attachment %s", d.Id()) + d.SetId("") + return nil + } + + return nil +} + +func isInvalidTarget(err error) bool { + elberr, ok := err.(awserr.Error) + return ok && elberr.Code() == "InvalidTarget" +} diff --git a/builtin/providers/aws/resource_aws_alb_target_group_attachment_test.go b/builtin/providers/aws/resource_aws_alb_target_group_attachment_test.go new file mode 100644 index 000000000..bd063516d --- /dev/null +++ b/builtin/providers/aws/resource_aws_alb_target_group_attachment_test.go @@ -0,0 +1,154 @@ +package aws + +import ( + "errors" + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "strconv" + "testing" +) + +func TestAccAWSALBTargetGroupAttachment_basic(t *testing.T) { + targetGroupName := fmt.Sprintf("test-target-group-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_alb_target_group.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSALBTargetGroupAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSALBTargetGroupAttachmentConfig_basic(targetGroupName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSALBTargetGroupAttachmentExists("aws_alb_target_group_attachment.test"), + ), + }, + }, + }) +} + +func testAccCheckAWSALBTargetGroupAttachmentExists(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 errors.New("No Target Group Attachment ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).elbv2conn + + port, _ := strconv.Atoi(rs.Primary.Attributes["port"]) + describe, err := conn.DescribeTargetHealth(&elbv2.DescribeTargetHealthInput{ + TargetGroupArn: aws.String(rs.Primary.Attributes["target_group_arn"]), + Targets: []*elbv2.TargetDescription{ + { + Id: aws.String(rs.Primary.Attributes["target_id"]), + Port: aws.Int64(int64(port)), + }, + }, + }) + + if err != nil { + return err + } + + if len(describe.TargetHealthDescriptions) != 1 { + return errors.New("Target Group Attachment not found") + } + + return nil + } +} + +func testAccCheckAWSALBTargetGroupAttachmentDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).elbv2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_alb_target_group_attachment" { + continue + } + + port, _ := strconv.Atoi(rs.Primary.Attributes["port"]) + describe, err := conn.DescribeTargetHealth(&elbv2.DescribeTargetHealthInput{ + TargetGroupArn: aws.String(rs.Primary.Attributes["target_group_arn"]), + Targets: []*elbv2.TargetDescription{ + { + Id: aws.String(rs.Primary.Attributes["target_id"]), + Port: aws.Int64(int64(port)), + }, + }, + }) + if err == nil { + if len(describe.TargetHealthDescriptions) != 0 { + return fmt.Errorf("Target Group Attachment %q still exists", rs.Primary.ID) + } + } + + // Verify the error + if isTargetGroupNotFound(err) || isInvalidTarget(err) { + return nil + } else { + return errwrap.Wrapf("Unexpected error checking ALB destroyed: {{err}}", err) + } + } + + return nil +} + +func testAccAWSALBTargetGroupAttachmentConfig_basic(targetGroupName string) string { + return fmt.Sprintf(` +resource "aws_alb_target_group_attachment" "test" { + target_group_arn = "${aws_alb_target_group.test.arn}" + target_id = "${aws_instance.test.id}" + port = 80 +} + +resource "aws_instance" "test" { + ami = "ami-f701cb97" + instance_type = "t2.micro" + subnet_id = "${aws_subnet.subnet.id}" +} + +resource "aws_alb_target_group" "test" { + name = "%s" + port = 443 + protocol = "HTTPS" + vpc_id = "${aws_vpc.test.id}" + + deregistration_delay = 200 + + stickiness { + type = "lb_cookie" + cookie_duration = 10000 + } + + health_check { + path = "/health" + interval = 60 + port = 8081 + protocol = "HTTP" + timeout = 3 + healthy_threshold = 3 + unhealthy_threshold = 3 + matcher = "200-299" + } +} + +resource "aws_subnet" "subnet" { + cidr_block = "10.0.1.0/24" + vpc_id = "${aws_vpc.test.id}" + +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +}`, targetGroupName) +} diff --git a/website/source/docs/providers/aws/r/alb_target_group_attachment.html.markdown b/website/source/docs/providers/aws/r/alb_target_group_attachment.html.markdown new file mode 100644 index 000000000..d381d6932 --- /dev/null +++ b/website/source/docs/providers/aws/r/alb_target_group_attachment.html.markdown @@ -0,0 +1,50 @@ +--- +layout: "aws" +page_title: "AWS: aws_alb_target_group_attachment" +sidebar_current: "docs-aws-resource-alb-target-group-attachment" +description: |- + Provides the ability to register instances and containers with an ALB + target group +--- + +# aws\_alb\_target\_group\_attachment + +Provides the ability to register instances and containers with an ALB +target group + +## Example Usage + +``` +resource "aws_alb_target_group_attachment" "test" { + target_group_arn = "${aws_alb_target_group.test.arn}" + target_id = "${aws_instance.test.id}" + port = 80 +} + +resource "aws_alb_target_group" "test" { + // Other arguments +} + +resource "aws_instance" "test" { + // Other arguments +} +``` + +## Argument Reference + +The following arguments are supported: + +* `target_group_arn` - (Required) The ARN of the target group with which to register targets +* `target_id` (Required) The ID of the target. This is the Instance ID for an instance, or the container ID for an ECS container. +* `port` - (Required) The port on which targets receive traffic. + +## Attributes Reference + +The following attributes are exported in addition to the arguments listed above: + +* `id` - A unique identifier for the attachment + +## Import + +Target Group Attachments cannot be imported. + diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index b88b462b7..728d652ef 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -226,6 +226,10 @@ aws_alb_target_group + > + aws_alb_target_group_attachment + + > aws_ami