github.com/hobbeswalsh/terraform@v0.3.7-0.20150619183303-ad17cf55a0fa/builtin/providers/aws/resource_aws_elb.go (about)

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