github.com/econnell/terraform@v0.5.4-0.20150722160631-78eb236786a4/builtin/providers/aws/resource_aws_elb.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"regexp"
     8  	"strings"
     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/elb"
    13  	"github.com/hashicorp/terraform/helper/hashcode"
    14  	"github.com/hashicorp/terraform/helper/resource"
    15  	"github.com/hashicorp/terraform/helper/schema"
    16  )
    17  
    18  func resourceAwsElb() *schema.Resource {
    19  	return &schema.Resource{
    20  		Create: resourceAwsElbCreate,
    21  		Read:   resourceAwsElbRead,
    22  		Update: resourceAwsElbUpdate,
    23  		Delete: resourceAwsElbDelete,
    24  
    25  		Schema: map[string]*schema.Schema{
    26  			"name": &schema.Schema{
    27  				Type:     schema.TypeString,
    28  				Optional: true,
    29  				Computed: true,
    30  				ForceNew: true,
    31  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    32  					value := v.(string)
    33  					if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) {
    34  						errors = append(errors, fmt.Errorf(
    35  							"only alphanumeric characters and hyphens allowed in %q", k))
    36  					}
    37  					if len(value) > 32 {
    38  						errors = append(errors, fmt.Errorf(
    39  							"%q cannot be longer than 32 characters", k))
    40  					}
    41  					if regexp.MustCompile(`^-`).MatchString(value) {
    42  						errors = append(errors, fmt.Errorf(
    43  							"%q cannot begin with a hyphen", k))
    44  					}
    45  					if regexp.MustCompile(`-$`).MatchString(value) {
    46  						errors = append(errors, fmt.Errorf(
    47  							"%q cannot end with a hyphen", k))
    48  					}
    49  					return
    50  				},
    51  			},
    52  
    53  			"internal": &schema.Schema{
    54  				Type:     schema.TypeBool,
    55  				Optional: true,
    56  				ForceNew: true,
    57  				Computed: true,
    58  			},
    59  
    60  			"cross_zone_load_balancing": &schema.Schema{
    61  				Type:     schema.TypeBool,
    62  				Optional: true,
    63  			},
    64  
    65  			"availability_zones": &schema.Schema{
    66  				Type:     schema.TypeSet,
    67  				Elem:     &schema.Schema{Type: schema.TypeString},
    68  				Optional: true,
    69  				ForceNew: true,
    70  				Computed: true,
    71  				Set:      schema.HashString,
    72  			},
    73  
    74  			"instances": &schema.Schema{
    75  				Type:     schema.TypeSet,
    76  				Elem:     &schema.Schema{Type: schema.TypeString},
    77  				Optional: true,
    78  				Computed: true,
    79  				Set:      schema.HashString,
    80  			},
    81  
    82  			"security_groups": &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  			"source_security_group": &schema.Schema{
    91  				Type:     schema.TypeString,
    92  				Optional: true,
    93  				Computed: true,
    94  			},
    95  
    96  			"subnets": &schema.Schema{
    97  				Type:     schema.TypeSet,
    98  				Elem:     &schema.Schema{Type: schema.TypeString},
    99  				Optional: true,
   100  				ForceNew: true,
   101  				Computed: true,
   102  				Set:      schema.HashString,
   103  			},
   104  
   105  			"idle_timeout": &schema.Schema{
   106  				Type:     schema.TypeInt,
   107  				Optional: true,
   108  				Default:  60,
   109  			},
   110  
   111  			"connection_draining": &schema.Schema{
   112  				Type:     schema.TypeBool,
   113  				Optional: true,
   114  				Default:  false,
   115  			},
   116  
   117  			"connection_draining_timeout": &schema.Schema{
   118  				Type:     schema.TypeInt,
   119  				Optional: true,
   120  				Default:  300,
   121  			},
   122  
   123  			"listener": &schema.Schema{
   124  				Type:     schema.TypeSet,
   125  				Required: true,
   126  				Elem: &schema.Resource{
   127  					Schema: map[string]*schema.Schema{
   128  						"instance_port": &schema.Schema{
   129  							Type:     schema.TypeInt,
   130  							Required: true,
   131  						},
   132  
   133  						"instance_protocol": &schema.Schema{
   134  							Type:     schema.TypeString,
   135  							Required: true,
   136  						},
   137  
   138  						"lb_port": &schema.Schema{
   139  							Type:     schema.TypeInt,
   140  							Required: true,
   141  						},
   142  
   143  						"lb_protocol": &schema.Schema{
   144  							Type:     schema.TypeString,
   145  							Required: true,
   146  						},
   147  
   148  						"ssl_certificate_id": &schema.Schema{
   149  							Type:     schema.TypeString,
   150  							Optional: true,
   151  						},
   152  					},
   153  				},
   154  				Set: resourceAwsElbListenerHash,
   155  			},
   156  
   157  			"health_check": &schema.Schema{
   158  				Type:     schema.TypeSet,
   159  				Optional: true,
   160  				Computed: true,
   161  				Elem: &schema.Resource{
   162  					Schema: map[string]*schema.Schema{
   163  						"healthy_threshold": &schema.Schema{
   164  							Type:     schema.TypeInt,
   165  							Required: true,
   166  						},
   167  
   168  						"unhealthy_threshold": &schema.Schema{
   169  							Type:     schema.TypeInt,
   170  							Required: true,
   171  						},
   172  
   173  						"target": &schema.Schema{
   174  							Type:     schema.TypeString,
   175  							Required: true,
   176  						},
   177  
   178  						"interval": &schema.Schema{
   179  							Type:     schema.TypeInt,
   180  							Required: true,
   181  						},
   182  
   183  						"timeout": &schema.Schema{
   184  							Type:     schema.TypeInt,
   185  							Required: true,
   186  						},
   187  					},
   188  				},
   189  				Set: resourceAwsElbHealthCheckHash,
   190  			},
   191  
   192  			"dns_name": &schema.Schema{
   193  				Type:     schema.TypeString,
   194  				Computed: true,
   195  			},
   196  
   197  			"zone_id": &schema.Schema{
   198  				Type:     schema.TypeString,
   199  				Computed: true,
   200  			},
   201  
   202  			"tags": tagsSchema(),
   203  		},
   204  	}
   205  }
   206  
   207  func resourceAwsElbCreate(d *schema.ResourceData, meta interface{}) error {
   208  	elbconn := meta.(*AWSClient).elbconn
   209  
   210  	// Expand the "listener" set to aws-sdk-go compat []*elb.Listener
   211  	listeners, err := expandListeners(d.Get("listener").(*schema.Set).List())
   212  	if err != nil {
   213  		return err
   214  	}
   215  
   216  	var elbName string
   217  	if v, ok := d.GetOk("name"); ok {
   218  		elbName = v.(string)
   219  	} else {
   220  		elbName = resource.PrefixedUniqueId("tf-lb-")
   221  		d.Set("name", elbName)
   222  	}
   223  
   224  	tags := tagsFromMapELB(d.Get("tags").(map[string]interface{}))
   225  	// Provision the elb
   226  	elbOpts := &elb.CreateLoadBalancerInput{
   227  		LoadBalancerName: aws.String(elbName),
   228  		Listeners:        listeners,
   229  		Tags:             tags,
   230  	}
   231  
   232  	if scheme, ok := d.GetOk("internal"); ok && scheme.(bool) {
   233  		elbOpts.Scheme = aws.String("internal")
   234  	}
   235  
   236  	if v, ok := d.GetOk("availability_zones"); ok {
   237  		elbOpts.AvailabilityZones = expandStringList(v.(*schema.Set).List())
   238  	}
   239  
   240  	if v, ok := d.GetOk("security_groups"); ok {
   241  		elbOpts.SecurityGroups = expandStringList(v.(*schema.Set).List())
   242  	}
   243  
   244  	if v, ok := d.GetOk("subnets"); ok {
   245  		elbOpts.Subnets = expandStringList(v.(*schema.Set).List())
   246  	}
   247  
   248  	log.Printf("[DEBUG] ELB create configuration: %#v", elbOpts)
   249  	if _, err := elbconn.CreateLoadBalancer(elbOpts); err != nil {
   250  		return fmt.Errorf("Error creating ELB: %s", err)
   251  	}
   252  
   253  	// Assign the elb's unique identifier for use later
   254  	d.SetId(elbName)
   255  	log.Printf("[INFO] ELB ID: %s", d.Id())
   256  
   257  	// Enable partial mode and record what we set
   258  	d.Partial(true)
   259  	d.SetPartial("name")
   260  	d.SetPartial("internal")
   261  	d.SetPartial("availability_zones")
   262  	d.SetPartial("listener")
   263  	d.SetPartial("security_groups")
   264  	d.SetPartial("subnets")
   265  
   266  	d.Set("tags", tagsToMapELB(tags))
   267  
   268  	return resourceAwsElbUpdate(d, meta)
   269  }
   270  
   271  func resourceAwsElbRead(d *schema.ResourceData, meta interface{}) error {
   272  	elbconn := meta.(*AWSClient).elbconn
   273  	elbName := d.Id()
   274  
   275  	// Retrieve the ELB properties for updating the state
   276  	describeElbOpts := &elb.DescribeLoadBalancersInput{
   277  		LoadBalancerNames: []*string{aws.String(elbName)},
   278  	}
   279  
   280  	describeResp, err := elbconn.DescribeLoadBalancers(describeElbOpts)
   281  	if err != nil {
   282  		if isLoadBalancerNotFound(err) {
   283  			// The ELB is gone now, so just remove it from the state
   284  			d.SetId("")
   285  			return nil
   286  		}
   287  
   288  		return fmt.Errorf("Error retrieving ELB: %s", err)
   289  	}
   290  	if len(describeResp.LoadBalancerDescriptions) != 1 {
   291  		return fmt.Errorf("Unable to find ELB: %#v", describeResp.LoadBalancerDescriptions)
   292  	}
   293  
   294  	describeAttrsOpts := &elb.DescribeLoadBalancerAttributesInput{
   295  		LoadBalancerName: aws.String(elbName),
   296  	}
   297  	describeAttrsResp, err := elbconn.DescribeLoadBalancerAttributes(describeAttrsOpts)
   298  	if err != nil {
   299  		if isLoadBalancerNotFound(err) {
   300  			// The ELB is gone now, so just remove it from the state
   301  			d.SetId("")
   302  			return nil
   303  		}
   304  
   305  		return fmt.Errorf("Error retrieving ELB: %s", err)
   306  	}
   307  
   308  	lbAttrs := describeAttrsResp.LoadBalancerAttributes
   309  
   310  	lb := describeResp.LoadBalancerDescriptions[0]
   311  
   312  	d.Set("name", *lb.LoadBalancerName)
   313  	d.Set("dns_name", *lb.DNSName)
   314  	d.Set("zone_id", *lb.CanonicalHostedZoneNameID)
   315  	d.Set("internal", *lb.Scheme == "internal")
   316  	d.Set("availability_zones", lb.AvailabilityZones)
   317  	d.Set("instances", flattenInstances(lb.Instances))
   318  	d.Set("listener", flattenListeners(lb.ListenerDescriptions))
   319  	d.Set("security_groups", lb.SecurityGroups)
   320  	if lb.SourceSecurityGroup != nil {
   321  		d.Set("source_security_group", lb.SourceSecurityGroup.GroupName)
   322  	}
   323  	d.Set("subnets", lb.Subnets)
   324  	d.Set("idle_timeout", lbAttrs.ConnectionSettings.IdleTimeout)
   325  	d.Set("connection_draining", lbAttrs.ConnectionDraining.Enabled)
   326  	d.Set("connection_draining_timeout", lbAttrs.ConnectionDraining.Timeout)
   327  
   328  	resp, err := elbconn.DescribeTags(&elb.DescribeTagsInput{
   329  		LoadBalancerNames: []*string{lb.LoadBalancerName},
   330  	})
   331  
   332  	var et []*elb.Tag
   333  	if len(resp.TagDescriptions) > 0 {
   334  		et = resp.TagDescriptions[0].Tags
   335  	}
   336  	d.Set("tags", tagsToMapELB(et))
   337  	// There's only one health check, so save that to state as we
   338  	// currently can
   339  	if *lb.HealthCheck.Target != "" {
   340  		d.Set("health_check", flattenHealthCheck(lb.HealthCheck))
   341  	}
   342  
   343  	return nil
   344  }
   345  
   346  func resourceAwsElbUpdate(d *schema.ResourceData, meta interface{}) error {
   347  	elbconn := meta.(*AWSClient).elbconn
   348  
   349  	d.Partial(true)
   350  
   351  	if d.HasChange("listener") {
   352  		o, n := d.GetChange("listener")
   353  		os := o.(*schema.Set)
   354  		ns := n.(*schema.Set)
   355  
   356  		remove, _ := expandListeners(os.Difference(ns).List())
   357  		add, _ := expandListeners(ns.Difference(os).List())
   358  
   359  		if len(remove) > 0 {
   360  			ports := make([]*int64, 0, len(remove))
   361  			for _, listener := range remove {
   362  				ports = append(ports, listener.LoadBalancerPort)
   363  			}
   364  
   365  			deleteListenersOpts := &elb.DeleteLoadBalancerListenersInput{
   366  				LoadBalancerName:  aws.String(d.Id()),
   367  				LoadBalancerPorts: ports,
   368  			}
   369  
   370  			_, err := elbconn.DeleteLoadBalancerListeners(deleteListenersOpts)
   371  			if err != nil {
   372  				return fmt.Errorf("Failure removing outdated ELB listeners: %s", err)
   373  			}
   374  		}
   375  
   376  		if len(add) > 0 {
   377  			createListenersOpts := &elb.CreateLoadBalancerListenersInput{
   378  				LoadBalancerName: aws.String(d.Id()),
   379  				Listeners:        add,
   380  			}
   381  
   382  			_, err := elbconn.CreateLoadBalancerListeners(createListenersOpts)
   383  			if err != nil {
   384  				return fmt.Errorf("Failure adding new or updated ELB listeners: %s", err)
   385  			}
   386  		}
   387  
   388  		d.SetPartial("listener")
   389  	}
   390  
   391  	// If we currently have instances, or did have instances,
   392  	// we want to figure out what to add and remove from the load
   393  	// balancer
   394  	if d.HasChange("instances") {
   395  		o, n := d.GetChange("instances")
   396  		os := o.(*schema.Set)
   397  		ns := n.(*schema.Set)
   398  		remove := expandInstanceString(os.Difference(ns).List())
   399  		add := expandInstanceString(ns.Difference(os).List())
   400  
   401  		if len(add) > 0 {
   402  			registerInstancesOpts := elb.RegisterInstancesWithLoadBalancerInput{
   403  				LoadBalancerName: aws.String(d.Id()),
   404  				Instances:        add,
   405  			}
   406  
   407  			_, err := elbconn.RegisterInstancesWithLoadBalancer(&registerInstancesOpts)
   408  			if err != nil {
   409  				return fmt.Errorf("Failure registering instances with ELB: %s", err)
   410  			}
   411  		}
   412  		if len(remove) > 0 {
   413  			deRegisterInstancesOpts := elb.DeregisterInstancesFromLoadBalancerInput{
   414  				LoadBalancerName: aws.String(d.Id()),
   415  				Instances:        remove,
   416  			}
   417  
   418  			_, err := elbconn.DeregisterInstancesFromLoadBalancer(&deRegisterInstancesOpts)
   419  			if err != nil {
   420  				return fmt.Errorf("Failure deregistering instances from ELB: %s", err)
   421  			}
   422  		}
   423  
   424  		d.SetPartial("instances")
   425  	}
   426  
   427  	if d.HasChange("cross_zone_load_balancing") || d.HasChange("idle_timeout") {
   428  		attrs := elb.ModifyLoadBalancerAttributesInput{
   429  			LoadBalancerName: aws.String(d.Get("name").(string)),
   430  			LoadBalancerAttributes: &elb.LoadBalancerAttributes{
   431  				CrossZoneLoadBalancing: &elb.CrossZoneLoadBalancing{
   432  					Enabled: aws.Boolean(d.Get("cross_zone_load_balancing").(bool)),
   433  				},
   434  				ConnectionSettings: &elb.ConnectionSettings{
   435  					IdleTimeout: aws.Long(int64(d.Get("idle_timeout").(int))),
   436  				},
   437  			},
   438  		}
   439  
   440  		_, err := elbconn.ModifyLoadBalancerAttributes(&attrs)
   441  		if err != nil {
   442  			return fmt.Errorf("Failure configuring ELB attributes: %s", err)
   443  		}
   444  
   445  		d.SetPartial("cross_zone_load_balancing")
   446  		d.SetPartial("idle_timeout")
   447  		d.SetPartial("connection_draining_timeout")
   448  	}
   449  
   450  	// We have to do these changes separately from everything else since
   451  	// they have some weird undocumented rules. You can't set the timeout
   452  	// without having connection draining to true, so we set that to true,
   453  	// set the timeout, then reset it to false if requested.
   454  	if d.HasChange("connection_draining") || d.HasChange("connection_draining_timeout") {
   455  		// We do timeout changes first since they require us to set draining
   456  		// to true for a hot second.
   457  		if d.HasChange("connection_draining_timeout") {
   458  			attrs := elb.ModifyLoadBalancerAttributesInput{
   459  				LoadBalancerName: aws.String(d.Get("name").(string)),
   460  				LoadBalancerAttributes: &elb.LoadBalancerAttributes{
   461  					ConnectionDraining: &elb.ConnectionDraining{
   462  						Enabled: aws.Boolean(true),
   463  						Timeout: aws.Long(int64(d.Get("connection_draining_timeout").(int))),
   464  					},
   465  				},
   466  			}
   467  
   468  			_, err := elbconn.ModifyLoadBalancerAttributes(&attrs)
   469  			if err != nil {
   470  				return fmt.Errorf("Failure configuring ELB attributes: %s", err)
   471  			}
   472  
   473  			d.SetPartial("connection_draining_timeout")
   474  		}
   475  
   476  		// Then we always set connection draining even if there is no change.
   477  		// This lets us reset to "false" if requested even with a timeout
   478  		// change.
   479  		attrs := elb.ModifyLoadBalancerAttributesInput{
   480  			LoadBalancerName: aws.String(d.Get("name").(string)),
   481  			LoadBalancerAttributes: &elb.LoadBalancerAttributes{
   482  				ConnectionDraining: &elb.ConnectionDraining{
   483  					Enabled: aws.Boolean(d.Get("connection_draining").(bool)),
   484  				},
   485  			},
   486  		}
   487  
   488  		_, err := elbconn.ModifyLoadBalancerAttributes(&attrs)
   489  		if err != nil {
   490  			return fmt.Errorf("Failure configuring ELB attributes: %s", err)
   491  		}
   492  
   493  		d.SetPartial("connection_draining")
   494  	}
   495  
   496  	if d.HasChange("health_check") {
   497  		vs := d.Get("health_check").(*schema.Set).List()
   498  		if len(vs) > 0 {
   499  			check := vs[0].(map[string]interface{})
   500  			configureHealthCheckOpts := elb.ConfigureHealthCheckInput{
   501  				LoadBalancerName: aws.String(d.Id()),
   502  				HealthCheck: &elb.HealthCheck{
   503  					HealthyThreshold:   aws.Long(int64(check["healthy_threshold"].(int))),
   504  					UnhealthyThreshold: aws.Long(int64(check["unhealthy_threshold"].(int))),
   505  					Interval:           aws.Long(int64(check["interval"].(int))),
   506  					Target:             aws.String(check["target"].(string)),
   507  					Timeout:            aws.Long(int64(check["timeout"].(int))),
   508  				},
   509  			}
   510  			_, err := elbconn.ConfigureHealthCheck(&configureHealthCheckOpts)
   511  			if err != nil {
   512  				return fmt.Errorf("Failure configuring health check for ELB: %s", err)
   513  			}
   514  			d.SetPartial("health_check")
   515  		}
   516  	}
   517  
   518  	if d.HasChange("security_groups") {
   519  		groups := d.Get("security_groups").(*schema.Set).List()
   520  
   521  		applySecurityGroupsOpts := elb.ApplySecurityGroupsToLoadBalancerInput{
   522  			LoadBalancerName: aws.String(d.Id()),
   523  			SecurityGroups:   expandStringList(groups),
   524  		}
   525  
   526  		_, err := elbconn.ApplySecurityGroupsToLoadBalancer(&applySecurityGroupsOpts)
   527  		if err != nil {
   528  			return fmt.Errorf("Failure applying security groups to ELB: %s", err)
   529  		}
   530  
   531  		d.SetPartial("security_groups")
   532  	}
   533  
   534  	if err := setTagsELB(elbconn, d); err != nil {
   535  		return err
   536  	}
   537  
   538  	d.SetPartial("tags")
   539  	d.Partial(false)
   540  
   541  	return resourceAwsElbRead(d, meta)
   542  }
   543  
   544  func resourceAwsElbDelete(d *schema.ResourceData, meta interface{}) error {
   545  	elbconn := meta.(*AWSClient).elbconn
   546  
   547  	log.Printf("[INFO] Deleting ELB: %s", d.Id())
   548  
   549  	// Destroy the load balancer
   550  	deleteElbOpts := elb.DeleteLoadBalancerInput{
   551  		LoadBalancerName: aws.String(d.Id()),
   552  	}
   553  	if _, err := elbconn.DeleteLoadBalancer(&deleteElbOpts); err != nil {
   554  		return fmt.Errorf("Error deleting ELB: %s", err)
   555  	}
   556  
   557  	return nil
   558  }
   559  
   560  func resourceAwsElbHealthCheckHash(v interface{}) int {
   561  	var buf bytes.Buffer
   562  	m := v.(map[string]interface{})
   563  	buf.WriteString(fmt.Sprintf("%d-", m["healthy_threshold"].(int)))
   564  	buf.WriteString(fmt.Sprintf("%d-", m["unhealthy_threshold"].(int)))
   565  	buf.WriteString(fmt.Sprintf("%s-", m["target"].(string)))
   566  	buf.WriteString(fmt.Sprintf("%d-", m["interval"].(int)))
   567  	buf.WriteString(fmt.Sprintf("%d-", m["timeout"].(int)))
   568  
   569  	return hashcode.String(buf.String())
   570  }
   571  
   572  func resourceAwsElbListenerHash(v interface{}) int {
   573  	var buf bytes.Buffer
   574  	m := v.(map[string]interface{})
   575  	buf.WriteString(fmt.Sprintf("%d-", m["instance_port"].(int)))
   576  	buf.WriteString(fmt.Sprintf("%s-",
   577  		strings.ToLower(m["instance_protocol"].(string))))
   578  	buf.WriteString(fmt.Sprintf("%d-", m["lb_port"].(int)))
   579  	buf.WriteString(fmt.Sprintf("%s-",
   580  		strings.ToLower(m["lb_protocol"].(string))))
   581  
   582  	if v, ok := m["ssl_certificate_id"]; ok {
   583  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   584  	}
   585  
   586  	return hashcode.String(buf.String())
   587  }
   588  
   589  func isLoadBalancerNotFound(err error) bool {
   590  	elberr, ok := err.(awserr.Error)
   591  	return ok && elberr.Code() == "LoadBalancerNotFound"
   592  }