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