diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 1ef643553..b1aa9fb6b 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -176,6 +176,7 @@ func Provider() terraform.ResourceProvider { "aws_elastic_beanstalk_environment": resourceAwsElasticBeanstalkEnvironment(), "aws_elasticsearch_domain": resourceAwsElasticSearchDomain(), "aws_elb": resourceAwsElb(), + "aws_elb_attachment": resourceAwsElbAttachment(), "aws_flow_log": resourceAwsFlowLog(), "aws_glacier_vault": resourceAwsGlacierVault(), "aws_iam_access_key": resourceAwsIamAccessKey(), diff --git a/builtin/providers/aws/resource_aws_elb_attachment.go b/builtin/providers/aws/resource_aws_elb_attachment.go new file mode 100644 index 000000000..401544ad7 --- /dev/null +++ b/builtin/providers/aws/resource_aws_elb_attachment.go @@ -0,0 +1,121 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/elb" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsElbAttachment() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsElbAttachmentCreate, + Read: resourceAwsElbAttachmentRead, + Delete: resourceAwsElbAttachmentDelete, + + Schema: map[string]*schema.Schema{ + "elb": &schema.Schema{ + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + + "instance": &schema.Schema{ + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + }, + } +} + +func resourceAwsElbAttachmentCreate(d *schema.ResourceData, meta interface{}) error { + elbconn := meta.(*AWSClient).elbconn + elbName := d.Get("elb").(string) + + instance := d.Get("instance").(string) + + registerInstancesOpts := elb.RegisterInstancesWithLoadBalancerInput{ + LoadBalancerName: aws.String(elbName), + Instances: []*elb.Instance{{InstanceId: aws.String(instance)}}, + } + + log.Printf("[INFO] registering instance %s with ELB %s", instance, elbName) + + _, err := elbconn.RegisterInstancesWithLoadBalancer(®isterInstancesOpts) + if err != nil { + return fmt.Errorf("Failure registering instances with ELB: %s", err) + } + + d.SetId(resource.PrefixedUniqueId(fmt.Sprintf("%s-", elbName))) + + return nil +} + +func resourceAwsElbAttachmentRead(d *schema.ResourceData, meta interface{}) error { + elbconn := meta.(*AWSClient).elbconn + elbName := d.Get("elb").(string) + + // only add the instance that was previously defined for this resource + expected := d.Get("instance").(string) + + // Retrieve the ELB properties to get a list of attachments + describeElbOpts := &elb.DescribeLoadBalancersInput{ + LoadBalancerNames: []*string{aws.String(elbName)}, + } + + resp, err := elbconn.DescribeLoadBalancers(describeElbOpts) + if err != nil { + if isLoadBalancerNotFound(err) { + log.Printf("[ERROR] ELB %s not found", elbName) + d.SetId("") + return nil + } + return fmt.Errorf("Error retrieving ELB: %s", err) + } + if len(resp.LoadBalancerDescriptions) != 1 { + log.Printf("[ERROR] Unable to find ELB: %s", resp.LoadBalancerDescriptions) + d.SetId("") + return nil + } + + // only set the instance Id that this resource manages + found := false + for _, i := range resp.LoadBalancerDescriptions[0].Instances { + if expected == *i.InstanceId { + d.Set("instance", expected) + found = true + } + } + + if !found { + log.Printf("[WARN] instance %s not found in elb attachments", expected) + d.SetId("") + } + + return nil +} + +func resourceAwsElbAttachmentDelete(d *schema.ResourceData, meta interface{}) error { + elbconn := meta.(*AWSClient).elbconn + elbName := d.Get("elb").(string) + + instance := d.Get("instance").(string) + + log.Printf("[INFO] Deleting Attachment %s from: %s", instance, elbName) + + deRegisterInstancesOpts := elb.DeregisterInstancesFromLoadBalancerInput{ + LoadBalancerName: aws.String(elbName), + Instances: []*elb.Instance{{InstanceId: aws.String(instance)}}, + } + + _, err := elbconn.DeregisterInstancesFromLoadBalancer(&deRegisterInstancesOpts) + if err != nil { + return fmt.Errorf("Failure deregistering instances from ELB: %s", err) + } + + return nil +} diff --git a/builtin/providers/aws/resource_aws_elb_attachment_test.go b/builtin/providers/aws/resource_aws_elb_attachment_test.go new file mode 100644 index 000000000..84da24779 --- /dev/null +++ b/builtin/providers/aws/resource_aws_elb_attachment_test.go @@ -0,0 +1,232 @@ +package aws + +import ( + "fmt" + "log" + "testing" + + "github.com/aws/aws-sdk-go/service/elb" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSELBAttachment_basic(t *testing.T) { + var conf elb.LoadBalancerDescription + + testCheckInstanceAttached := func(count int) resource.TestCheckFunc { + return func(*terraform.State) error { + if len(conf.Instances) != count { + return fmt.Errorf("instance count does not match") + } + return nil + } + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_elb.bar", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSELBDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSELBAttachmentConfig1, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSELBExists("aws_elb.bar", &conf), + testCheckInstanceAttached(1), + ), + }, + + resource.TestStep{ + Config: testAccAWSELBAttachmentConfig2, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSELBExists("aws_elb.bar", &conf), + testCheckInstanceAttached(2), + ), + }, + + resource.TestStep{ + Config: testAccAWSELBAttachmentConfig3, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSELBExists("aws_elb.bar", &conf), + testCheckInstanceAttached(2), + ), + }, + + resource.TestStep{ + Config: testAccAWSELBAttachmentConfig4, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSELBExists("aws_elb.bar", &conf), + testCheckInstanceAttached(0), + ), + }, + }, + }) +} + +// remove and instance and check that it's correctly re-attached. +func TestAccAWSELBAttachment_drift(t *testing.T) { + var conf elb.LoadBalancerDescription + + deregInstance := func() { + conn := testAccProvider.Meta().(*AWSClient).elbconn + + deRegisterInstancesOpts := elb.DeregisterInstancesFromLoadBalancerInput{ + LoadBalancerName: conf.LoadBalancerName, + Instances: conf.Instances, + } + + log.Printf("[DEBUG] deregistering instance %s from ELB", conf.Instances[0].InstanceId) + + _, err := conn.DeregisterInstancesFromLoadBalancer(&deRegisterInstancesOpts) + if err != nil { + t.Fatalf("Failure deregistering instances from ELB: %s", err) + } + + } + + testCheckInstanceAttached := func(count int) resource.TestCheckFunc { + return func(*terraform.State) error { + if len(conf.Instances) != count { + return fmt.Errorf("instance count does not match") + } + return nil + } + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_elb.bar", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSELBDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSELBAttachmentConfig1, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSELBExists("aws_elb.bar", &conf), + testCheckInstanceAttached(1), + ), + }, + + // remove an instance from the ELB, and make sure it gets re-added + resource.TestStep{ + Config: testAccAWSELBAttachmentConfig1, + PreConfig: deregInstance, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSELBExists("aws_elb.bar", &conf), + testCheckInstanceAttached(1), + ), + }, + }, + }) +} + +// add one attachment +const testAccAWSELBAttachmentConfig1 = ` +resource "aws_elb" "bar" { + availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"] + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } +} + +resource "aws_instance" "foo1" { + # us-west-2 + ami = "ami-043a5034" + instance_type = "t1.micro" +} + +resource "aws_elb_attachment" "foo1" { + elb = "${aws_elb.bar.id}" + instance = "${aws_instance.foo1.id}" +} +` + +// add a second attachment +const testAccAWSELBAttachmentConfig2 = ` +resource "aws_elb" "bar" { + availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"] + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } +} + +resource "aws_instance" "foo1" { + # us-west-2 + ami = "ami-043a5034" + instance_type = "t1.micro" +} + +resource "aws_instance" "foo2" { + # us-west-2 + ami = "ami-043a5034" + instance_type = "t1.micro" +} + +resource "aws_elb_attachment" "foo1" { + elb = "${aws_elb.bar.id}" + instance = "${aws_instance.foo1.id}" +} + +resource "aws_elb_attachment" "foo2" { + elb = "${aws_elb.bar.id}" + instance = "${aws_instance.foo2.id}" +} +` + +// swap attachments between resources +const testAccAWSELBAttachmentConfig3 = ` +resource "aws_elb" "bar" { + availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"] + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } +} + +resource "aws_instance" "foo1" { + # us-west-2 + ami = "ami-043a5034" + instance_type = "t1.micro" +} + +resource "aws_instance" "foo2" { + # us-west-2 + ami = "ami-043a5034" + instance_type = "t1.micro" +} + +resource "aws_elb_attachment" "foo1" { + elb = "${aws_elb.bar.id}" + instance = "${aws_instance.foo2.id}" +} + +resource "aws_elb_attachment" "foo2" { + elb = "${aws_elb.bar.id}" + instance = "${aws_instance.foo1.id}" +} +` + +// destroy attachments +const testAccAWSELBAttachmentConfig4 = ` +resource "aws_elb" "bar" { + availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"] + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } +} +` diff --git a/website/source/docs/providers/aws/r/elb.html.markdown b/website/source/docs/providers/aws/r/elb.html.markdown index b8efdee52..6e0f73ea1 100644 --- a/website/source/docs/providers/aws/r/elb.html.markdown +++ b/website/source/docs/providers/aws/r/elb.html.markdown @@ -10,6 +10,12 @@ description: |- Provides an Elastic Load Balancer resource. +~> **NOTE on ELB Instances and ELB Attachments:** Terraform currently +provides both a standalone [ELB Attachment resource](elb_attachment.html) +(describing an instance attached to an ELB), and an ELB resource with +`instances` defined in-line. At this time you cannot use an ELB with in-line +instaces in conjunction with a ELB Attachment resources. Doing so will cause a +conflict and will overwrite attachments. ## Example Usage ``` diff --git a/website/source/docs/providers/aws/r/elb_attachment.html.markdown b/website/source/docs/providers/aws/r/elb_attachment.html.markdown new file mode 100644 index 000000000..ee28b243d --- /dev/null +++ b/website/source/docs/providers/aws/r/elb_attachment.html.markdown @@ -0,0 +1,34 @@ +--- +layout: "aws" +page_title: "AWS: aws_elb_attachment" +sidebar_current: "docs-aws-resource-elb-attachment" +description: |- + Provides an Elastic Load Balancer Attachment resource. +--- + +# aws\_elb\_attachment + +Provides an Elastic Load Balancer Attachment resource. + +~> **NOTE on ELB Instances and ELB Attachments:** Terraform currently provides +both a standalone ELB Attachment resource (describing an instance attached to +an ELB), and an [Elastic Load Balancer resource](elb.html) with +`instances` defined in-line. At this time you cannot use an ELB with in-line +instaces in conjunction with an ELB Attachment resource. Doing so will cause a +conflict and will overwrite attachments. +## Example Usage + +``` +# Create a new load balancer attachment +resource "aws_elb_attachment" "baz" { + elb = "${aws_elb.bar.id}" + instance = ["${aws_instance.foo.id}"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `elb` - (Required) The name of the ELB. +* `instance` - (Required) Instance ID to place in the ELB pool.