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