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