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