github.com/ricardclau/terraform@v0.6.17-0.20160519222547-283e3ae6b5a9/builtin/providers/aws/resource_aws_elb.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go/aws"
    11  	"github.com/aws/aws-sdk-go/aws/awserr"
    12  	"github.com/aws/aws-sdk-go/service/ec2"
    13  	"github.com/aws/aws-sdk-go/service/elb"
    14  	"github.com/hashicorp/terraform/helper/hashcode"
    15  	"github.com/hashicorp/terraform/helper/resource"
    16  	"github.com/hashicorp/terraform/helper/schema"
    17  )
    18  
    19  func resourceAwsElb() *schema.Resource {
    20  	return &schema.Resource{
    21  		Create: resourceAwsElbCreate,
    22  		Read:   resourceAwsElbRead,
    23  		Update: resourceAwsElbUpdate,
    24  		Delete: resourceAwsElbDelete,
    25  		Importer: &schema.ResourceImporter{
    26  			State: schema.ImportStatePassthrough,
    27  		},
    28  
    29  		Schema: map[string]*schema.Schema{
    30  			"name": &schema.Schema{
    31  				Type:         schema.TypeString,
    32  				Optional:     true,
    33  				Computed:     true,
    34  				ForceNew:     true,
    35  				ValidateFunc: validateElbName,
    36  			},
    37  
    38  			"internal": &schema.Schema{
    39  				Type:     schema.TypeBool,
    40  				Optional: true,
    41  				ForceNew: true,
    42  				Computed: true,
    43  			},
    44  
    45  			"cross_zone_load_balancing": &schema.Schema{
    46  				Type:     schema.TypeBool,
    47  				Optional: true,
    48  			},
    49  
    50  			"availability_zones": &schema.Schema{
    51  				Type:     schema.TypeSet,
    52  				Elem:     &schema.Schema{Type: schema.TypeString},
    53  				Optional: true,
    54  				Computed: true,
    55  				Set:      schema.HashString,
    56  			},
    57  
    58  			"instances": &schema.Schema{
    59  				Type:     schema.TypeSet,
    60  				Elem:     &schema.Schema{Type: schema.TypeString},
    61  				Optional: true,
    62  				Computed: true,
    63  				Set:      schema.HashString,
    64  			},
    65  
    66  			"security_groups": &schema.Schema{
    67  				Type:     schema.TypeSet,
    68  				Elem:     &schema.Schema{Type: schema.TypeString},
    69  				Optional: true,
    70  				Computed: true,
    71  				Set:      schema.HashString,
    72  			},
    73  
    74  			"source_security_group": &schema.Schema{
    75  				Type:     schema.TypeString,
    76  				Optional: true,
    77  				Computed: true,
    78  			},
    79  
    80  			"source_security_group_id": &schema.Schema{
    81  				Type:     schema.TypeString,
    82  				Computed: true,
    83  			},
    84  
    85  			"subnets": &schema.Schema{
    86  				Type:     schema.TypeSet,
    87  				Elem:     &schema.Schema{Type: schema.TypeString},
    88  				Optional: true,
    89  				Computed: true,
    90  				Set:      schema.HashString,
    91  			},
    92  
    93  			"idle_timeout": &schema.Schema{
    94  				Type:     schema.TypeInt,
    95  				Optional: true,
    96  				Default:  60,
    97  			},
    98  
    99  			"connection_draining": &schema.Schema{
   100  				Type:     schema.TypeBool,
   101  				Optional: true,
   102  				Default:  false,
   103  			},
   104  
   105  			"connection_draining_timeout": &schema.Schema{
   106  				Type:     schema.TypeInt,
   107  				Optional: true,
   108  				Default:  300,
   109  			},
   110  
   111  			"access_logs": &schema.Schema{
   112  				Type:     schema.TypeList,
   113  				Optional: true,
   114  				Elem: &schema.Resource{
   115  					Schema: map[string]*schema.Schema{
   116  						"interval": &schema.Schema{
   117  							Type:     schema.TypeInt,
   118  							Optional: true,
   119  							Default:  60,
   120  						},
   121  						"bucket": &schema.Schema{
   122  							Type:     schema.TypeString,
   123  							Required: true,
   124  						},
   125  						"bucket_prefix": &schema.Schema{
   126  							Type:     schema.TypeString,
   127  							Optional: true,
   128  						},
   129  					},
   130  				},
   131  			},
   132  
   133  			"listener": &schema.Schema{
   134  				Type:     schema.TypeSet,
   135  				Required: true,
   136  				Elem: &schema.Resource{
   137  					Schema: map[string]*schema.Schema{
   138  						"instance_port": &schema.Schema{
   139  							Type:     schema.TypeInt,
   140  							Required: true,
   141  						},
   142  
   143  						"instance_protocol": &schema.Schema{
   144  							Type:     schema.TypeString,
   145  							Required: true,
   146  						},
   147  
   148  						"lb_port": &schema.Schema{
   149  							Type:     schema.TypeInt,
   150  							Required: true,
   151  						},
   152  
   153  						"lb_protocol": &schema.Schema{
   154  							Type:     schema.TypeString,
   155  							Required: true,
   156  						},
   157  
   158  						"ssl_certificate_id": &schema.Schema{
   159  							Type:     schema.TypeString,
   160  							Optional: true,
   161  						},
   162  					},
   163  				},
   164  				Set: resourceAwsElbListenerHash,
   165  			},
   166  
   167  			"health_check": &schema.Schema{
   168  				Type:     schema.TypeList,
   169  				Optional: true,
   170  				Computed: true,
   171  				Elem: &schema.Resource{
   172  					Schema: map[string]*schema.Schema{
   173  						"healthy_threshold": &schema.Schema{
   174  							Type:     schema.TypeInt,
   175  							Required: true,
   176  						},
   177  
   178  						"unhealthy_threshold": &schema.Schema{
   179  							Type:     schema.TypeInt,
   180  							Required: true,
   181  						},
   182  
   183  						"target": &schema.Schema{
   184  							Type:     schema.TypeString,
   185  							Required: true,
   186  						},
   187  
   188  						"interval": &schema.Schema{
   189  							Type:     schema.TypeInt,
   190  							Required: true,
   191  						},
   192  
   193  						"timeout": &schema.Schema{
   194  							Type:     schema.TypeInt,
   195  							Required: true,
   196  						},
   197  					},
   198  				},
   199  			},
   200  
   201  			"dns_name": &schema.Schema{
   202  				Type:     schema.TypeString,
   203  				Computed: true,
   204  			},
   205  
   206  			"zone_id": &schema.Schema{
   207  				Type:     schema.TypeString,
   208  				Computed: true,
   209  			},
   210  
   211  			"tags": tagsSchema(),
   212  		},
   213  	}
   214  }
   215  
   216  func resourceAwsElbCreate(d *schema.ResourceData, meta interface{}) error {
   217  	elbconn := meta.(*AWSClient).elbconn
   218  
   219  	// Expand the "listener" set to aws-sdk-go compat []*elb.Listener
   220  	listeners, err := expandListeners(d.Get("listener").(*schema.Set).List())
   221  	if err != nil {
   222  		return err
   223  	}
   224  
   225  	var elbName string
   226  	if v, ok := d.GetOk("name"); ok {
   227  		elbName = v.(string)
   228  	} else {
   229  		elbName = resource.PrefixedUniqueId("tf-lb-")
   230  		d.Set("name", elbName)
   231  	}
   232  
   233  	tags := tagsFromMapELB(d.Get("tags").(map[string]interface{}))
   234  	// Provision the elb
   235  	elbOpts := &elb.CreateLoadBalancerInput{
   236  		LoadBalancerName: aws.String(elbName),
   237  		Listeners:        listeners,
   238  		Tags:             tags,
   239  	}
   240  
   241  	if scheme, ok := d.GetOk("internal"); ok && scheme.(bool) {
   242  		elbOpts.Scheme = aws.String("internal")
   243  	}
   244  
   245  	if v, ok := d.GetOk("availability_zones"); ok {
   246  		elbOpts.AvailabilityZones = expandStringList(v.(*schema.Set).List())
   247  	}
   248  
   249  	if v, ok := d.GetOk("security_groups"); ok {
   250  		elbOpts.SecurityGroups = expandStringList(v.(*schema.Set).List())
   251  	}
   252  
   253  	if v, ok := d.GetOk("subnets"); ok {
   254  		elbOpts.Subnets = expandStringList(v.(*schema.Set).List())
   255  	}
   256  
   257  	log.Printf("[DEBUG] ELB create configuration: %#v", elbOpts)
   258  	err = resource.Retry(1*time.Minute, func() *resource.RetryError {
   259  		_, err := elbconn.CreateLoadBalancer(elbOpts)
   260  
   261  		if err != nil {
   262  			if awsErr, ok := err.(awserr.Error); ok {
   263  				// Check for IAM SSL Cert error, eventual consistancy issue
   264  				if awsErr.Code() == "CertificateNotFound" {
   265  					return resource.RetryableError(
   266  						fmt.Errorf("[WARN] Error creating ELB Listener with SSL Cert, retrying: %s", err))
   267  				}
   268  			}
   269  			return resource.NonRetryableError(err)
   270  		}
   271  		return nil
   272  	})
   273  
   274  	if err != nil {
   275  		return err
   276  	}
   277  
   278  	// Assign the elb's unique identifier for use later
   279  	d.SetId(elbName)
   280  	log.Printf("[INFO] ELB ID: %s", d.Id())
   281  
   282  	// Enable partial mode and record what we set
   283  	d.Partial(true)
   284  	d.SetPartial("name")
   285  	d.SetPartial("internal")
   286  	d.SetPartial("availability_zones")
   287  	d.SetPartial("listener")
   288  	d.SetPartial("security_groups")
   289  	d.SetPartial("subnets")
   290  
   291  	d.Set("tags", tagsToMapELB(tags))
   292  
   293  	return resourceAwsElbUpdate(d, meta)
   294  }
   295  
   296  func resourceAwsElbRead(d *schema.ResourceData, meta interface{}) error {
   297  	elbconn := meta.(*AWSClient).elbconn
   298  	elbName := d.Id()
   299  
   300  	// Retrieve the ELB properties for updating the state
   301  	describeElbOpts := &elb.DescribeLoadBalancersInput{
   302  		LoadBalancerNames: []*string{aws.String(elbName)},
   303  	}
   304  
   305  	describeResp, err := elbconn.DescribeLoadBalancers(describeElbOpts)
   306  	if err != nil {
   307  		if isLoadBalancerNotFound(err) {
   308  			// The ELB is gone now, so just remove it from the state
   309  			d.SetId("")
   310  			return nil
   311  		}
   312  
   313  		return fmt.Errorf("Error retrieving ELB: %s", err)
   314  	}
   315  	if len(describeResp.LoadBalancerDescriptions) != 1 {
   316  		return fmt.Errorf("Unable to find ELB: %#v", describeResp.LoadBalancerDescriptions)
   317  	}
   318  
   319  	describeAttrsOpts := &elb.DescribeLoadBalancerAttributesInput{
   320  		LoadBalancerName: aws.String(elbName),
   321  	}
   322  	describeAttrsResp, err := elbconn.DescribeLoadBalancerAttributes(describeAttrsOpts)
   323  	if err != nil {
   324  		if isLoadBalancerNotFound(err) {
   325  			// The ELB is gone now, so just remove it from the state
   326  			d.SetId("")
   327  			return nil
   328  		}
   329  
   330  		return fmt.Errorf("Error retrieving ELB: %s", err)
   331  	}
   332  
   333  	lbAttrs := describeAttrsResp.LoadBalancerAttributes
   334  
   335  	lb := describeResp.LoadBalancerDescriptions[0]
   336  
   337  	d.Set("name", *lb.LoadBalancerName)
   338  	d.Set("dns_name", *lb.DNSName)
   339  	d.Set("zone_id", *lb.CanonicalHostedZoneNameID)
   340  	d.Set("internal", *lb.Scheme == "internal")
   341  	d.Set("availability_zones", flattenStringList(lb.AvailabilityZones))
   342  	d.Set("instances", flattenInstances(lb.Instances))
   343  	d.Set("listener", flattenListeners(lb.ListenerDescriptions))
   344  	d.Set("security_groups", flattenStringList(lb.SecurityGroups))
   345  	if lb.SourceSecurityGroup != nil {
   346  		group := lb.SourceSecurityGroup.GroupName
   347  		if lb.SourceSecurityGroup.OwnerAlias != nil && *lb.SourceSecurityGroup.OwnerAlias != "" {
   348  			group = aws.String(*lb.SourceSecurityGroup.OwnerAlias + "/" + *lb.SourceSecurityGroup.GroupName)
   349  		}
   350  		d.Set("source_security_group", group)
   351  
   352  		// Manually look up the ELB Security Group ID, since it's not provided
   353  		var elbVpc string
   354  		if lb.VPCId != nil {
   355  			elbVpc = *lb.VPCId
   356  			sgId, err := sourceSGIdByName(meta, *lb.SourceSecurityGroup.GroupName, elbVpc)
   357  			if err != nil {
   358  				return fmt.Errorf("[WARN] Error looking up ELB Security Group ID: %s", err)
   359  			} else {
   360  				d.Set("source_security_group_id", sgId)
   361  			}
   362  		}
   363  	}
   364  	d.Set("subnets", flattenStringList(lb.Subnets))
   365  	d.Set("idle_timeout", lbAttrs.ConnectionSettings.IdleTimeout)
   366  	d.Set("connection_draining", lbAttrs.ConnectionDraining.Enabled)
   367  	d.Set("connection_draining_timeout", lbAttrs.ConnectionDraining.Timeout)
   368  	d.Set("cross_zone_load_balancing", lbAttrs.CrossZoneLoadBalancing.Enabled)
   369  	if lbAttrs.AccessLog != nil {
   370  		if err := d.Set("access_logs", flattenAccessLog(lbAttrs.AccessLog)); err != nil {
   371  			return err
   372  		}
   373  	}
   374  
   375  	resp, err := elbconn.DescribeTags(&elb.DescribeTagsInput{
   376  		LoadBalancerNames: []*string{lb.LoadBalancerName},
   377  	})
   378  
   379  	var et []*elb.Tag
   380  	if len(resp.TagDescriptions) > 0 {
   381  		et = resp.TagDescriptions[0].Tags
   382  	}
   383  	d.Set("tags", tagsToMapELB(et))
   384  
   385  	// There's only one health check, so save that to state as we
   386  	// currently can
   387  	if *lb.HealthCheck.Target != "" {
   388  		d.Set("health_check", flattenHealthCheck(lb.HealthCheck))
   389  	}
   390  
   391  	return nil
   392  }
   393  
   394  func resourceAwsElbUpdate(d *schema.ResourceData, meta interface{}) error {
   395  	elbconn := meta.(*AWSClient).elbconn
   396  
   397  	d.Partial(true)
   398  
   399  	if d.HasChange("listener") {
   400  		o, n := d.GetChange("listener")
   401  		os := o.(*schema.Set)
   402  		ns := n.(*schema.Set)
   403  
   404  		remove, _ := expandListeners(os.Difference(ns).List())
   405  		add, _ := expandListeners(ns.Difference(os).List())
   406  
   407  		if len(remove) > 0 {
   408  			ports := make([]*int64, 0, len(remove))
   409  			for _, listener := range remove {
   410  				ports = append(ports, listener.LoadBalancerPort)
   411  			}
   412  
   413  			deleteListenersOpts := &elb.DeleteLoadBalancerListenersInput{
   414  				LoadBalancerName:  aws.String(d.Id()),
   415  				LoadBalancerPorts: ports,
   416  			}
   417  
   418  			log.Printf("[DEBUG] ELB Delete Listeners opts: %s", deleteListenersOpts)
   419  			_, err := elbconn.DeleteLoadBalancerListeners(deleteListenersOpts)
   420  			if err != nil {
   421  				return fmt.Errorf("Failure removing outdated ELB listeners: %s", err)
   422  			}
   423  		}
   424  
   425  		if len(add) > 0 {
   426  			createListenersOpts := &elb.CreateLoadBalancerListenersInput{
   427  				LoadBalancerName: aws.String(d.Id()),
   428  				Listeners:        add,
   429  			}
   430  
   431  			// Occasionally AWS will error with a 'duplicate listener', without any
   432  			// other listeners on the ELB. Retry here to eliminate that.
   433  			err := resource.Retry(1*time.Minute, func() *resource.RetryError {
   434  				log.Printf("[DEBUG] ELB Create Listeners opts: %s", createListenersOpts)
   435  				if _, err := elbconn.CreateLoadBalancerListeners(createListenersOpts); err != nil {
   436  					if awsErr, ok := err.(awserr.Error); ok {
   437  						if awsErr.Code() == "DuplicateListener" {
   438  							log.Printf("[DEBUG] Duplicate listener found for ELB (%s), retrying", d.Id())
   439  							return resource.RetryableError(awsErr)
   440  						}
   441  						if awsErr.Code() == "CertificateNotFound" && strings.Contains(awsErr.Message(), "Server Certificate not found for the key: arn") {
   442  							log.Printf("[DEBUG] SSL Cert not found for given ARN, retrying")
   443  							return resource.RetryableError(awsErr)
   444  						}
   445  					}
   446  
   447  					// Didn't recognize the error, so shouldn't retry.
   448  					return resource.NonRetryableError(err)
   449  				}
   450  				// Successful creation
   451  				return nil
   452  			})
   453  			if err != nil {
   454  				return fmt.Errorf("Failure adding new or updated ELB listeners: %s", err)
   455  			}
   456  		}
   457  
   458  		d.SetPartial("listener")
   459  	}
   460  
   461  	// If we currently have instances, or did have instances,
   462  	// we want to figure out what to add and remove from the load
   463  	// balancer
   464  	if d.HasChange("instances") {
   465  		o, n := d.GetChange("instances")
   466  		os := o.(*schema.Set)
   467  		ns := n.(*schema.Set)
   468  		remove := expandInstanceString(os.Difference(ns).List())
   469  		add := expandInstanceString(ns.Difference(os).List())
   470  
   471  		if len(add) > 0 {
   472  			registerInstancesOpts := elb.RegisterInstancesWithLoadBalancerInput{
   473  				LoadBalancerName: aws.String(d.Id()),
   474  				Instances:        add,
   475  			}
   476  
   477  			_, err := elbconn.RegisterInstancesWithLoadBalancer(&registerInstancesOpts)
   478  			if err != nil {
   479  				return fmt.Errorf("Failure registering instances with ELB: %s", err)
   480  			}
   481  		}
   482  		if len(remove) > 0 {
   483  			deRegisterInstancesOpts := elb.DeregisterInstancesFromLoadBalancerInput{
   484  				LoadBalancerName: aws.String(d.Id()),
   485  				Instances:        remove,
   486  			}
   487  
   488  			_, err := elbconn.DeregisterInstancesFromLoadBalancer(&deRegisterInstancesOpts)
   489  			if err != nil {
   490  				return fmt.Errorf("Failure deregistering instances from ELB: %s", err)
   491  			}
   492  		}
   493  
   494  		d.SetPartial("instances")
   495  	}
   496  
   497  	if d.HasChange("cross_zone_load_balancing") || d.HasChange("idle_timeout") || d.HasChange("access_logs") {
   498  		attrs := elb.ModifyLoadBalancerAttributesInput{
   499  			LoadBalancerName: aws.String(d.Get("name").(string)),
   500  			LoadBalancerAttributes: &elb.LoadBalancerAttributes{
   501  				CrossZoneLoadBalancing: &elb.CrossZoneLoadBalancing{
   502  					Enabled: aws.Bool(d.Get("cross_zone_load_balancing").(bool)),
   503  				},
   504  				ConnectionSettings: &elb.ConnectionSettings{
   505  					IdleTimeout: aws.Int64(int64(d.Get("idle_timeout").(int))),
   506  				},
   507  			},
   508  		}
   509  
   510  		logs := d.Get("access_logs").([]interface{})
   511  		if len(logs) > 1 {
   512  			return fmt.Errorf("Only one access logs config per ELB is supported")
   513  		} else if len(logs) == 1 {
   514  			log := logs[0].(map[string]interface{})
   515  			accessLog := &elb.AccessLog{
   516  				Enabled:      aws.Bool(true),
   517  				EmitInterval: aws.Int64(int64(log["interval"].(int))),
   518  				S3BucketName: aws.String(log["bucket"].(string)),
   519  			}
   520  
   521  			if log["bucket_prefix"] != "" {
   522  				accessLog.S3BucketPrefix = aws.String(log["bucket_prefix"].(string))
   523  			}
   524  
   525  			attrs.LoadBalancerAttributes.AccessLog = accessLog
   526  		} else if len(logs) == 0 {
   527  			// disable access logs
   528  			attrs.LoadBalancerAttributes.AccessLog = &elb.AccessLog{
   529  				Enabled: aws.Bool(false),
   530  			}
   531  		}
   532  
   533  		log.Printf("[DEBUG] ELB Modify Load Balancer Attributes Request: %#v", attrs)
   534  		_, err := elbconn.ModifyLoadBalancerAttributes(&attrs)
   535  		if err != nil {
   536  			return fmt.Errorf("Failure configuring ELB attributes: %s", err)
   537  		}
   538  
   539  		d.SetPartial("cross_zone_load_balancing")
   540  		d.SetPartial("idle_timeout")
   541  		d.SetPartial("connection_draining_timeout")
   542  	}
   543  
   544  	// We have to do these changes separately from everything else since
   545  	// they have some weird undocumented rules. You can't set the timeout
   546  	// without having connection draining to true, so we set that to true,
   547  	// set the timeout, then reset it to false if requested.
   548  	if d.HasChange("connection_draining") || d.HasChange("connection_draining_timeout") {
   549  		// We do timeout changes first since they require us to set draining
   550  		// to true for a hot second.
   551  		if d.HasChange("connection_draining_timeout") {
   552  			attrs := elb.ModifyLoadBalancerAttributesInput{
   553  				LoadBalancerName: aws.String(d.Get("name").(string)),
   554  				LoadBalancerAttributes: &elb.LoadBalancerAttributes{
   555  					ConnectionDraining: &elb.ConnectionDraining{
   556  						Enabled: aws.Bool(true),
   557  						Timeout: aws.Int64(int64(d.Get("connection_draining_timeout").(int))),
   558  					},
   559  				},
   560  			}
   561  
   562  			_, err := elbconn.ModifyLoadBalancerAttributes(&attrs)
   563  			if err != nil {
   564  				return fmt.Errorf("Failure configuring ELB attributes: %s", err)
   565  			}
   566  
   567  			d.SetPartial("connection_draining_timeout")
   568  		}
   569  
   570  		// Then we always set connection draining even if there is no change.
   571  		// This lets us reset to "false" if requested even with a timeout
   572  		// change.
   573  		attrs := elb.ModifyLoadBalancerAttributesInput{
   574  			LoadBalancerName: aws.String(d.Get("name").(string)),
   575  			LoadBalancerAttributes: &elb.LoadBalancerAttributes{
   576  				ConnectionDraining: &elb.ConnectionDraining{
   577  					Enabled: aws.Bool(d.Get("connection_draining").(bool)),
   578  				},
   579  			},
   580  		}
   581  
   582  		_, err := elbconn.ModifyLoadBalancerAttributes(&attrs)
   583  		if err != nil {
   584  			return fmt.Errorf("Failure configuring ELB attributes: %s", err)
   585  		}
   586  
   587  		d.SetPartial("connection_draining")
   588  	}
   589  
   590  	if d.HasChange("health_check") {
   591  		hc := d.Get("health_check").([]interface{})
   592  		if len(hc) > 1 {
   593  			return fmt.Errorf("Only one health check per ELB is supported")
   594  		} else if len(hc) > 0 {
   595  			check := hc[0].(map[string]interface{})
   596  			configureHealthCheckOpts := elb.ConfigureHealthCheckInput{
   597  				LoadBalancerName: aws.String(d.Id()),
   598  				HealthCheck: &elb.HealthCheck{
   599  					HealthyThreshold:   aws.Int64(int64(check["healthy_threshold"].(int))),
   600  					UnhealthyThreshold: aws.Int64(int64(check["unhealthy_threshold"].(int))),
   601  					Interval:           aws.Int64(int64(check["interval"].(int))),
   602  					Target:             aws.String(check["target"].(string)),
   603  					Timeout:            aws.Int64(int64(check["timeout"].(int))),
   604  				},
   605  			}
   606  			_, err := elbconn.ConfigureHealthCheck(&configureHealthCheckOpts)
   607  			if err != nil {
   608  				return fmt.Errorf("Failure configuring health check for ELB: %s", err)
   609  			}
   610  			d.SetPartial("health_check")
   611  		}
   612  	}
   613  
   614  	if d.HasChange("security_groups") {
   615  		groups := d.Get("security_groups").(*schema.Set).List()
   616  
   617  		applySecurityGroupsOpts := elb.ApplySecurityGroupsToLoadBalancerInput{
   618  			LoadBalancerName: aws.String(d.Id()),
   619  			SecurityGroups:   expandStringList(groups),
   620  		}
   621  
   622  		_, err := elbconn.ApplySecurityGroupsToLoadBalancer(&applySecurityGroupsOpts)
   623  		if err != nil {
   624  			return fmt.Errorf("Failure applying security groups to ELB: %s", err)
   625  		}
   626  
   627  		d.SetPartial("security_groups")
   628  	}
   629  
   630  	if d.HasChange("availability_zones") {
   631  		o, n := d.GetChange("availability_zones")
   632  		os := o.(*schema.Set)
   633  		ns := n.(*schema.Set)
   634  
   635  		removed := expandStringList(os.Difference(ns).List())
   636  		added := expandStringList(ns.Difference(os).List())
   637  
   638  		if len(added) > 0 {
   639  			enableOpts := &elb.EnableAvailabilityZonesForLoadBalancerInput{
   640  				LoadBalancerName:  aws.String(d.Id()),
   641  				AvailabilityZones: added,
   642  			}
   643  
   644  			log.Printf("[DEBUG] ELB enable availability zones opts: %s", enableOpts)
   645  			_, err := elbconn.EnableAvailabilityZonesForLoadBalancer(enableOpts)
   646  			if err != nil {
   647  				return fmt.Errorf("Failure enabling ELB availability zones: %s", err)
   648  			}
   649  		}
   650  
   651  		if len(removed) > 0 {
   652  			disableOpts := &elb.DisableAvailabilityZonesForLoadBalancerInput{
   653  				LoadBalancerName:  aws.String(d.Id()),
   654  				AvailabilityZones: removed,
   655  			}
   656  
   657  			log.Printf("[DEBUG] ELB disable availability zones opts: %s", disableOpts)
   658  			_, err := elbconn.DisableAvailabilityZonesForLoadBalancer(disableOpts)
   659  			if err != nil {
   660  				return fmt.Errorf("Failure disabling ELB availability zones: %s", err)
   661  			}
   662  		}
   663  
   664  		d.SetPartial("availability_zones")
   665  	}
   666  
   667  	if d.HasChange("subnets") {
   668  		o, n := d.GetChange("subnets")
   669  		os := o.(*schema.Set)
   670  		ns := n.(*schema.Set)
   671  
   672  		removed := expandStringList(os.Difference(ns).List())
   673  		added := expandStringList(ns.Difference(os).List())
   674  
   675  		if len(added) > 0 {
   676  			attachOpts := &elb.AttachLoadBalancerToSubnetsInput{
   677  				LoadBalancerName: aws.String(d.Id()),
   678  				Subnets:          added,
   679  			}
   680  
   681  			log.Printf("[DEBUG] ELB attach subnets opts: %s", attachOpts)
   682  			_, err := elbconn.AttachLoadBalancerToSubnets(attachOpts)
   683  			if err != nil {
   684  				return fmt.Errorf("Failure adding ELB subnets: %s", err)
   685  			}
   686  		}
   687  
   688  		if len(removed) > 0 {
   689  			detachOpts := &elb.DetachLoadBalancerFromSubnetsInput{
   690  				LoadBalancerName: aws.String(d.Id()),
   691  				Subnets:          removed,
   692  			}
   693  
   694  			log.Printf("[DEBUG] ELB detach subnets opts: %s", detachOpts)
   695  			_, err := elbconn.DetachLoadBalancerFromSubnets(detachOpts)
   696  			if err != nil {
   697  				return fmt.Errorf("Failure removing ELB subnets: %s", err)
   698  			}
   699  		}
   700  
   701  		d.SetPartial("subnets")
   702  	}
   703  
   704  	if err := setTagsELB(elbconn, d); err != nil {
   705  		return err
   706  	}
   707  
   708  	d.SetPartial("tags")
   709  	d.Partial(false)
   710  
   711  	return resourceAwsElbRead(d, meta)
   712  }
   713  
   714  func resourceAwsElbDelete(d *schema.ResourceData, meta interface{}) error {
   715  	elbconn := meta.(*AWSClient).elbconn
   716  
   717  	log.Printf("[INFO] Deleting ELB: %s", d.Id())
   718  
   719  	// Destroy the load balancer
   720  	deleteElbOpts := elb.DeleteLoadBalancerInput{
   721  		LoadBalancerName: aws.String(d.Id()),
   722  	}
   723  	if _, err := elbconn.DeleteLoadBalancer(&deleteElbOpts); err != nil {
   724  		return fmt.Errorf("Error deleting ELB: %s", err)
   725  	}
   726  
   727  	return nil
   728  }
   729  
   730  func resourceAwsElbListenerHash(v interface{}) int {
   731  	var buf bytes.Buffer
   732  	m := v.(map[string]interface{})
   733  	buf.WriteString(fmt.Sprintf("%d-", m["instance_port"].(int)))
   734  	buf.WriteString(fmt.Sprintf("%s-",
   735  		strings.ToLower(m["instance_protocol"].(string))))
   736  	buf.WriteString(fmt.Sprintf("%d-", m["lb_port"].(int)))
   737  	buf.WriteString(fmt.Sprintf("%s-",
   738  		strings.ToLower(m["lb_protocol"].(string))))
   739  
   740  	if v, ok := m["ssl_certificate_id"]; ok {
   741  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   742  	}
   743  
   744  	return hashcode.String(buf.String())
   745  }
   746  
   747  func isLoadBalancerNotFound(err error) bool {
   748  	elberr, ok := err.(awserr.Error)
   749  	return ok && elberr.Code() == "LoadBalancerNotFound"
   750  }
   751  
   752  func sourceSGIdByName(meta interface{}, sg, vpcId string) (string, error) {
   753  	conn := meta.(*AWSClient).ec2conn
   754  	var filters []*ec2.Filter
   755  	var sgFilterName, sgFilterVPCID *ec2.Filter
   756  	sgFilterName = &ec2.Filter{
   757  		Name:   aws.String("group-name"),
   758  		Values: []*string{aws.String(sg)},
   759  	}
   760  
   761  	if vpcId != "" {
   762  		sgFilterVPCID = &ec2.Filter{
   763  			Name:   aws.String("vpc-id"),
   764  			Values: []*string{aws.String(vpcId)},
   765  		}
   766  	}
   767  
   768  	filters = append(filters, sgFilterName)
   769  
   770  	if sgFilterVPCID != nil {
   771  		filters = append(filters, sgFilterVPCID)
   772  	}
   773  
   774  	req := &ec2.DescribeSecurityGroupsInput{
   775  		Filters: filters,
   776  	}
   777  	resp, err := conn.DescribeSecurityGroups(req)
   778  	if err != nil {
   779  		if ec2err, ok := err.(awserr.Error); ok {
   780  			if ec2err.Code() == "InvalidSecurityGroupID.NotFound" ||
   781  				ec2err.Code() == "InvalidGroup.NotFound" {
   782  				resp = nil
   783  				err = nil
   784  			}
   785  		}
   786  
   787  		if err != nil {
   788  			log.Printf("Error on ELB SG look up: %s", err)
   789  			return "", err
   790  		}
   791  	}
   792  
   793  	if resp == nil || len(resp.SecurityGroups) == 0 {
   794  		return "", fmt.Errorf("No security groups found for name %s and vpc id %s", sg, vpcId)
   795  	}
   796  
   797  	group := resp.SecurityGroups[0]
   798  	return *group.GroupId, nil
   799  }