github.com/jrperritt/terraform@v0.1.1-0.20170525065507-96f391dafc38/builtin/providers/aws/resource_aws_alb.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"regexp"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go/aws"
    11  	"github.com/aws/aws-sdk-go/service/elbv2"
    12  	"github.com/hashicorp/errwrap"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsAlb() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsAlbCreate,
    20  		Read:   resourceAwsAlbRead,
    21  		Update: resourceAwsAlbUpdate,
    22  		Delete: resourceAwsAlbDelete,
    23  		Importer: &schema.ResourceImporter{
    24  			State: schema.ImportStatePassthrough,
    25  		},
    26  
    27  		Timeouts: &schema.ResourceTimeout{
    28  			Create: schema.DefaultTimeout(10 * time.Minute),
    29  			Update: schema.DefaultTimeout(10 * time.Minute),
    30  			Delete: schema.DefaultTimeout(10 * time.Minute),
    31  		},
    32  
    33  		Schema: map[string]*schema.Schema{
    34  			"arn": {
    35  				Type:     schema.TypeString,
    36  				Computed: true,
    37  			},
    38  
    39  			"arn_suffix": {
    40  				Type:     schema.TypeString,
    41  				Computed: true,
    42  			},
    43  
    44  			"name": {
    45  				Type:          schema.TypeString,
    46  				Optional:      true,
    47  				Computed:      true,
    48  				ForceNew:      true,
    49  				ConflictsWith: []string{"name_prefix"},
    50  				ValidateFunc:  validateElbName,
    51  			},
    52  
    53  			"name_prefix": {
    54  				Type:         schema.TypeString,
    55  				Optional:     true,
    56  				ForceNew:     true,
    57  				ValidateFunc: validateElbNamePrefix,
    58  			},
    59  
    60  			"internal": {
    61  				Type:     schema.TypeBool,
    62  				Optional: true,
    63  				ForceNew: true,
    64  				Computed: true,
    65  			},
    66  
    67  			"security_groups": {
    68  				Type:     schema.TypeSet,
    69  				Elem:     &schema.Schema{Type: schema.TypeString},
    70  				Computed: true,
    71  				Optional: true,
    72  				Set:      schema.HashString,
    73  			},
    74  
    75  			"subnets": {
    76  				Type:     schema.TypeSet,
    77  				Elem:     &schema.Schema{Type: schema.TypeString},
    78  				Required: true,
    79  				Set:      schema.HashString,
    80  			},
    81  
    82  			"access_logs": {
    83  				Type:     schema.TypeList,
    84  				Optional: true,
    85  				MaxItems: 1,
    86  				Elem: &schema.Resource{
    87  					Schema: map[string]*schema.Schema{
    88  						"bucket": {
    89  							Type:     schema.TypeString,
    90  							Required: true,
    91  						},
    92  						"prefix": {
    93  							Type:     schema.TypeString,
    94  							Optional: true,
    95  						},
    96  						"enabled": {
    97  							Type:     schema.TypeBool,
    98  							Optional: true,
    99  							Default:  true,
   100  						},
   101  					},
   102  				},
   103  			},
   104  
   105  			"enable_deletion_protection": {
   106  				Type:     schema.TypeBool,
   107  				Optional: true,
   108  				Default:  false,
   109  			},
   110  
   111  			"idle_timeout": {
   112  				Type:     schema.TypeInt,
   113  				Optional: true,
   114  				Default:  60,
   115  			},
   116  
   117  			"ip_address_type": {
   118  				Type:     schema.TypeString,
   119  				Computed: true,
   120  				Optional: true,
   121  			},
   122  
   123  			"vpc_id": {
   124  				Type:     schema.TypeString,
   125  				Computed: true,
   126  			},
   127  
   128  			"zone_id": {
   129  				Type:     schema.TypeString,
   130  				Computed: true,
   131  			},
   132  
   133  			"dns_name": {
   134  				Type:     schema.TypeString,
   135  				Computed: true,
   136  			},
   137  
   138  			"tags": tagsSchema(),
   139  		},
   140  	}
   141  }
   142  
   143  func resourceAwsAlbCreate(d *schema.ResourceData, meta interface{}) error {
   144  	elbconn := meta.(*AWSClient).elbv2conn
   145  
   146  	var name string
   147  	if v, ok := d.GetOk("name"); ok {
   148  		name = v.(string)
   149  	} else if v, ok := d.GetOk("name_prefix"); ok {
   150  		name = resource.PrefixedUniqueId(v.(string))
   151  	} else {
   152  		name = resource.PrefixedUniqueId("tf-lb-")
   153  	}
   154  	d.Set("name", name)
   155  
   156  	elbOpts := &elbv2.CreateLoadBalancerInput{
   157  		Name: aws.String(name),
   158  		Tags: tagsFromMapELBv2(d.Get("tags").(map[string]interface{})),
   159  	}
   160  
   161  	if scheme, ok := d.GetOk("internal"); ok && scheme.(bool) {
   162  		elbOpts.Scheme = aws.String("internal")
   163  	}
   164  
   165  	if v, ok := d.GetOk("security_groups"); ok {
   166  		elbOpts.SecurityGroups = expandStringList(v.(*schema.Set).List())
   167  	}
   168  
   169  	if v, ok := d.GetOk("subnets"); ok {
   170  		elbOpts.Subnets = expandStringList(v.(*schema.Set).List())
   171  	}
   172  
   173  	if v, ok := d.GetOk("ip_address_type"); ok {
   174  		elbOpts.IpAddressType = aws.String(v.(string))
   175  	}
   176  
   177  	log.Printf("[DEBUG] ALB create configuration: %#v", elbOpts)
   178  
   179  	resp, err := elbconn.CreateLoadBalancer(elbOpts)
   180  	if err != nil {
   181  		return errwrap.Wrapf("Error creating Application Load Balancer: {{err}}", err)
   182  	}
   183  
   184  	if len(resp.LoadBalancers) != 1 {
   185  		return fmt.Errorf("No load balancers returned following creation of %s", d.Get("name").(string))
   186  	}
   187  
   188  	lb := resp.LoadBalancers[0]
   189  	d.SetId(*lb.LoadBalancerArn)
   190  	log.Printf("[INFO] ALB ID: %s", d.Id())
   191  
   192  	stateConf := &resource.StateChangeConf{
   193  		Pending: []string{"provisioning", "failed"},
   194  		Target:  []string{"active"},
   195  		Refresh: func() (interface{}, string, error) {
   196  			describeResp, err := elbconn.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{
   197  				LoadBalancerArns: []*string{lb.LoadBalancerArn},
   198  			})
   199  			if err != nil {
   200  				return nil, "", err
   201  			}
   202  
   203  			if len(describeResp.LoadBalancers) != 1 {
   204  				return nil, "", fmt.Errorf("No load balancers returned for %s", *lb.LoadBalancerArn)
   205  			}
   206  			dLb := describeResp.LoadBalancers[0]
   207  
   208  			log.Printf("[INFO] ALB state: %s", *dLb.State.Code)
   209  
   210  			return describeResp, *dLb.State.Code, nil
   211  		},
   212  		Timeout:    d.Timeout(schema.TimeoutCreate),
   213  		MinTimeout: 10 * time.Second,
   214  		Delay:      30 * time.Second, // Wait 30 secs before starting
   215  	}
   216  	_, err = stateConf.WaitForState()
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	return resourceAwsAlbUpdate(d, meta)
   222  }
   223  
   224  func resourceAwsAlbRead(d *schema.ResourceData, meta interface{}) error {
   225  	elbconn := meta.(*AWSClient).elbv2conn
   226  	albArn := d.Id()
   227  
   228  	describeAlbOpts := &elbv2.DescribeLoadBalancersInput{
   229  		LoadBalancerArns: []*string{aws.String(albArn)},
   230  	}
   231  
   232  	describeResp, err := elbconn.DescribeLoadBalancers(describeAlbOpts)
   233  	if err != nil {
   234  		if isLoadBalancerNotFound(err) {
   235  			// The ALB is gone now, so just remove it from the state
   236  			log.Printf("[WARN] ALB %s not found in AWS, removing from state", d.Id())
   237  			d.SetId("")
   238  			return nil
   239  		}
   240  
   241  		return errwrap.Wrapf("Error retrieving ALB: {{err}}", err)
   242  	}
   243  	if len(describeResp.LoadBalancers) != 1 {
   244  		return fmt.Errorf("Unable to find ALB: %#v", describeResp.LoadBalancers)
   245  	}
   246  
   247  	return flattenAwsAlbResource(d, meta, describeResp.LoadBalancers[0])
   248  }
   249  
   250  func resourceAwsAlbUpdate(d *schema.ResourceData, meta interface{}) error {
   251  	elbconn := meta.(*AWSClient).elbv2conn
   252  
   253  	if !d.IsNewResource() {
   254  		if err := setElbV2Tags(elbconn, d); err != nil {
   255  			return errwrap.Wrapf("Error Modifying Tags on ALB: {{err}}", err)
   256  		}
   257  	}
   258  
   259  	attributes := make([]*elbv2.LoadBalancerAttribute, 0)
   260  
   261  	if d.HasChange("access_logs") {
   262  		logs := d.Get("access_logs").([]interface{})
   263  		if len(logs) == 1 {
   264  			log := logs[0].(map[string]interface{})
   265  
   266  			attributes = append(attributes,
   267  				&elbv2.LoadBalancerAttribute{
   268  					Key:   aws.String("access_logs.s3.enabled"),
   269  					Value: aws.String(strconv.FormatBool(log["enabled"].(bool))),
   270  				},
   271  				&elbv2.LoadBalancerAttribute{
   272  					Key:   aws.String("access_logs.s3.bucket"),
   273  					Value: aws.String(log["bucket"].(string)),
   274  				})
   275  
   276  			if prefix, ok := log["prefix"]; ok {
   277  				attributes = append(attributes, &elbv2.LoadBalancerAttribute{
   278  					Key:   aws.String("access_logs.s3.prefix"),
   279  					Value: aws.String(prefix.(string)),
   280  				})
   281  			}
   282  		} else if len(logs) == 0 {
   283  			attributes = append(attributes, &elbv2.LoadBalancerAttribute{
   284  				Key:   aws.String("access_logs.s3.enabled"),
   285  				Value: aws.String("false"),
   286  			})
   287  		}
   288  	}
   289  
   290  	if d.HasChange("enable_deletion_protection") {
   291  		attributes = append(attributes, &elbv2.LoadBalancerAttribute{
   292  			Key:   aws.String("deletion_protection.enabled"),
   293  			Value: aws.String(fmt.Sprintf("%t", d.Get("enable_deletion_protection").(bool))),
   294  		})
   295  	}
   296  
   297  	if d.HasChange("idle_timeout") {
   298  		attributes = append(attributes, &elbv2.LoadBalancerAttribute{
   299  			Key:   aws.String("idle_timeout.timeout_seconds"),
   300  			Value: aws.String(fmt.Sprintf("%d", d.Get("idle_timeout").(int))),
   301  		})
   302  	}
   303  
   304  	if len(attributes) != 0 {
   305  		input := &elbv2.ModifyLoadBalancerAttributesInput{
   306  			LoadBalancerArn: aws.String(d.Id()),
   307  			Attributes:      attributes,
   308  		}
   309  
   310  		log.Printf("[DEBUG] ALB Modify Load Balancer Attributes Request: %#v", input)
   311  		_, err := elbconn.ModifyLoadBalancerAttributes(input)
   312  		if err != nil {
   313  			return fmt.Errorf("Failure configuring ALB attributes: %s", err)
   314  		}
   315  	}
   316  
   317  	if d.HasChange("security_groups") {
   318  		sgs := expandStringList(d.Get("security_groups").(*schema.Set).List())
   319  
   320  		params := &elbv2.SetSecurityGroupsInput{
   321  			LoadBalancerArn: aws.String(d.Id()),
   322  			SecurityGroups:  sgs,
   323  		}
   324  		_, err := elbconn.SetSecurityGroups(params)
   325  		if err != nil {
   326  			return fmt.Errorf("Failure Setting ALB Security Groups: %s", err)
   327  		}
   328  
   329  	}
   330  
   331  	if d.HasChange("subnets") {
   332  		subnets := expandStringList(d.Get("subnets").(*schema.Set).List())
   333  
   334  		params := &elbv2.SetSubnetsInput{
   335  			LoadBalancerArn: aws.String(d.Id()),
   336  			Subnets:         subnets,
   337  		}
   338  
   339  		_, err := elbconn.SetSubnets(params)
   340  		if err != nil {
   341  			return fmt.Errorf("Failure Setting ALB Subnets: %s", err)
   342  		}
   343  	}
   344  
   345  	if d.HasChange("ip_address_type") {
   346  
   347  		params := &elbv2.SetIpAddressTypeInput{
   348  			LoadBalancerArn: aws.String(d.Id()),
   349  			IpAddressType:   aws.String(d.Get("ip_address_type").(string)),
   350  		}
   351  
   352  		_, err := elbconn.SetIpAddressType(params)
   353  		if err != nil {
   354  			return fmt.Errorf("Failure Setting ALB IP Address Type: %s", err)
   355  		}
   356  
   357  	}
   358  
   359  	stateConf := &resource.StateChangeConf{
   360  		Pending: []string{"active", "provisioning", "failed"},
   361  		Target:  []string{"active"},
   362  		Refresh: func() (interface{}, string, error) {
   363  			describeResp, err := elbconn.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{
   364  				LoadBalancerArns: []*string{aws.String(d.Id())},
   365  			})
   366  			if err != nil {
   367  				return nil, "", err
   368  			}
   369  
   370  			if len(describeResp.LoadBalancers) != 1 {
   371  				return nil, "", fmt.Errorf("No load balancers returned for %s", d.Id())
   372  			}
   373  			dLb := describeResp.LoadBalancers[0]
   374  
   375  			log.Printf("[INFO] ALB state: %s", *dLb.State.Code)
   376  
   377  			return describeResp, *dLb.State.Code, nil
   378  		},
   379  		Timeout:    d.Timeout(schema.TimeoutUpdate),
   380  		MinTimeout: 10 * time.Second,
   381  		Delay:      30 * time.Second, // Wait 30 secs before starting
   382  	}
   383  	_, err := stateConf.WaitForState()
   384  	if err != nil {
   385  		return err
   386  	}
   387  
   388  	return resourceAwsAlbRead(d, meta)
   389  }
   390  
   391  func resourceAwsAlbDelete(d *schema.ResourceData, meta interface{}) error {
   392  	albconn := meta.(*AWSClient).elbv2conn
   393  
   394  	log.Printf("[INFO] Deleting ALB: %s", d.Id())
   395  
   396  	// Destroy the load balancer
   397  	deleteElbOpts := elbv2.DeleteLoadBalancerInput{
   398  		LoadBalancerArn: aws.String(d.Id()),
   399  	}
   400  	if _, err := albconn.DeleteLoadBalancer(&deleteElbOpts); err != nil {
   401  		return fmt.Errorf("Error deleting ALB: %s", err)
   402  	}
   403  
   404  	return nil
   405  }
   406  
   407  // flattenSubnetsFromAvailabilityZones creates a slice of strings containing the subnet IDs
   408  // for the ALB based on the AvailabilityZones structure returned by the API.
   409  func flattenSubnetsFromAvailabilityZones(availabilityZones []*elbv2.AvailabilityZone) []string {
   410  	var result []string
   411  	for _, az := range availabilityZones {
   412  		result = append(result, *az.SubnetId)
   413  	}
   414  	return result
   415  }
   416  
   417  func albSuffixFromARN(arn *string) string {
   418  	if arn == nil {
   419  		return ""
   420  	}
   421  
   422  	if arnComponents := regexp.MustCompile(`arn:.*:loadbalancer/(.*)`).FindAllStringSubmatch(*arn, -1); len(arnComponents) == 1 {
   423  		if len(arnComponents[0]) == 2 {
   424  			return arnComponents[0][1]
   425  		}
   426  	}
   427  
   428  	return ""
   429  }
   430  
   431  // flattenAwsAlbResource takes a *elbv2.LoadBalancer and populates all respective resource fields.
   432  func flattenAwsAlbResource(d *schema.ResourceData, meta interface{}, alb *elbv2.LoadBalancer) error {
   433  	elbconn := meta.(*AWSClient).elbv2conn
   434  
   435  	d.Set("arn", alb.LoadBalancerArn)
   436  	d.Set("arn_suffix", albSuffixFromARN(alb.LoadBalancerArn))
   437  	d.Set("name", alb.LoadBalancerName)
   438  	d.Set("internal", (alb.Scheme != nil && *alb.Scheme == "internal"))
   439  	d.Set("security_groups", flattenStringList(alb.SecurityGroups))
   440  	d.Set("subnets", flattenSubnetsFromAvailabilityZones(alb.AvailabilityZones))
   441  	d.Set("vpc_id", alb.VpcId)
   442  	d.Set("zone_id", alb.CanonicalHostedZoneId)
   443  	d.Set("dns_name", alb.DNSName)
   444  	d.Set("ip_address_type", alb.IpAddressType)
   445  
   446  	respTags, err := elbconn.DescribeTags(&elbv2.DescribeTagsInput{
   447  		ResourceArns: []*string{alb.LoadBalancerArn},
   448  	})
   449  	if err != nil {
   450  		return errwrap.Wrapf("Error retrieving ALB Tags: {{err}}", err)
   451  	}
   452  
   453  	var et []*elbv2.Tag
   454  	if len(respTags.TagDescriptions) > 0 {
   455  		et = respTags.TagDescriptions[0].Tags
   456  	}
   457  	d.Set("tags", tagsToMapELBv2(et))
   458  
   459  	attributesResp, err := elbconn.DescribeLoadBalancerAttributes(&elbv2.DescribeLoadBalancerAttributesInput{
   460  		LoadBalancerArn: aws.String(d.Id()),
   461  	})
   462  	if err != nil {
   463  		return errwrap.Wrapf("Error retrieving ALB Attributes: {{err}}", err)
   464  	}
   465  
   466  	accessLogMap := map[string]interface{}{}
   467  	for _, attr := range attributesResp.Attributes {
   468  		switch *attr.Key {
   469  		case "access_logs.s3.enabled":
   470  			accessLogMap["enabled"] = *attr.Value
   471  		case "access_logs.s3.bucket":
   472  			accessLogMap["bucket"] = *attr.Value
   473  		case "access_logs.s3.prefix":
   474  			accessLogMap["prefix"] = *attr.Value
   475  		case "idle_timeout.timeout_seconds":
   476  			timeout, err := strconv.Atoi(*attr.Value)
   477  			if err != nil {
   478  				return errwrap.Wrapf("Error parsing ALB timeout: {{err}}", err)
   479  			}
   480  			log.Printf("[DEBUG] Setting ALB Timeout Seconds: %d", timeout)
   481  			d.Set("idle_timeout", timeout)
   482  		case "deletion_protection.enabled":
   483  			protectionEnabled := (*attr.Value) == "true"
   484  			log.Printf("[DEBUG] Setting ALB Deletion Protection Enabled: %t", protectionEnabled)
   485  			d.Set("enable_deletion_protection", protectionEnabled)
   486  		}
   487  	}
   488  
   489  	log.Printf("[DEBUG] Setting ALB Access Logs: %#v", accessLogMap)
   490  	if accessLogMap["bucket"] != "" || accessLogMap["prefix"] != "" {
   491  		d.Set("access_logs", []interface{}{accessLogMap})
   492  	} else {
   493  		d.Set("access_logs", []interface{}{})
   494  	}
   495  
   496  	return nil
   497  }