github.com/armen/terraform@v0.5.2-0.20150529052519-caa8117a08f1/builtin/providers/aws/resource_aws_elb.go (about)

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