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