github.com/mohanarpit/terraform@v0.6.16-0.20160909104007-291f29853544/builtin/providers/aws/resource_aws_elb.go (about)

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