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