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