github.com/inge4pres/terraform@v0.7.5-0.20160930053151-bd083f84f376/builtin/providers/aws/resource_aws_alb.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"regexp"
     7  	"strconv"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/service/elbv2"
    11  	"github.com/hashicorp/errwrap"
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  )
    15  
    16  func resourceAwsAlb() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceAwsAlbCreate,
    19  		Read:   resourceAwsAlbRead,
    20  		Update: resourceAwsAlbUpdate,
    21  		Delete: resourceAwsAlbDelete,
    22  		Importer: &schema.ResourceImporter{
    23  			State: schema.ImportStatePassthrough,
    24  		},
    25  
    26  		Schema: map[string]*schema.Schema{
    27  			"arn": {
    28  				Type:     schema.TypeString,
    29  				Computed: true,
    30  			},
    31  
    32  			"arn_suffix": {
    33  				Type:     schema.TypeString,
    34  				Computed: true,
    35  			},
    36  
    37  			"name": {
    38  				Type:          schema.TypeString,
    39  				Optional:      true,
    40  				Computed:      true,
    41  				ForceNew:      true,
    42  				ConflictsWith: []string{"name_prefix"},
    43  				ValidateFunc:  validateElbName,
    44  			},
    45  
    46  			"name_prefix": {
    47  				Type:         schema.TypeString,
    48  				Optional:     true,
    49  				ForceNew:     true,
    50  				ValidateFunc: validateElbName,
    51  			},
    52  
    53  			"internal": {
    54  				Type:     schema.TypeBool,
    55  				Optional: true,
    56  				ForceNew: true,
    57  				Computed: true,
    58  			},
    59  
    60  			"security_groups": {
    61  				Type:     schema.TypeSet,
    62  				Elem:     &schema.Schema{Type: schema.TypeString},
    63  				Computed: true,
    64  				ForceNew: true,
    65  				Optional: true,
    66  				Set:      schema.HashString,
    67  			},
    68  
    69  			"subnets": {
    70  				Type:     schema.TypeSet,
    71  				Elem:     &schema.Schema{Type: schema.TypeString},
    72  				ForceNew: true,
    73  				Required: true,
    74  				Set:      schema.HashString,
    75  			},
    76  
    77  			"access_logs": {
    78  				Type:     schema.TypeList,
    79  				Optional: true,
    80  				MaxItems: 1,
    81  				Elem: &schema.Resource{
    82  					Schema: map[string]*schema.Schema{
    83  						"bucket": {
    84  							Type:     schema.TypeString,
    85  							Required: true,
    86  						},
    87  						"prefix": {
    88  							Type:     schema.TypeString,
    89  							Optional: true,
    90  						},
    91  					},
    92  				},
    93  			},
    94  
    95  			"enable_deletion_protection": {
    96  				Type:     schema.TypeBool,
    97  				Optional: true,
    98  				Default:  false,
    99  			},
   100  
   101  			"idle_timeout": {
   102  				Type:     schema.TypeInt,
   103  				Optional: true,
   104  				Default:  60,
   105  			},
   106  
   107  			"vpc_id": {
   108  				Type:     schema.TypeString,
   109  				Computed: true,
   110  			},
   111  
   112  			"zone_id": {
   113  				Type:     schema.TypeString,
   114  				Computed: true,
   115  			},
   116  
   117  			"dns_name": {
   118  				Type:     schema.TypeString,
   119  				Computed: true,
   120  			},
   121  
   122  			"tags": tagsSchema(),
   123  		},
   124  	}
   125  }
   126  
   127  func resourceAwsAlbCreate(d *schema.ResourceData, meta interface{}) error {
   128  	elbconn := meta.(*AWSClient).elbv2conn
   129  
   130  	var name string
   131  	if v, ok := d.GetOk("name"); ok {
   132  		name = v.(string)
   133  	} else if v, ok := d.GetOk("name_prefix"); ok {
   134  		name = resource.PrefixedUniqueId(v.(string))
   135  	} else {
   136  		name = resource.PrefixedUniqueId("tf-lb-")
   137  	}
   138  	d.Set("name", name)
   139  
   140  	elbOpts := &elbv2.CreateLoadBalancerInput{
   141  		Name: aws.String(name),
   142  		Tags: tagsFromMapELBv2(d.Get("tags").(map[string]interface{})),
   143  	}
   144  
   145  	if scheme, ok := d.GetOk("internal"); ok && scheme.(bool) {
   146  		elbOpts.Scheme = aws.String("internal")
   147  	}
   148  
   149  	if v, ok := d.GetOk("security_groups"); ok {
   150  		elbOpts.SecurityGroups = expandStringList(v.(*schema.Set).List())
   151  	}
   152  
   153  	if v, ok := d.GetOk("subnets"); ok {
   154  		elbOpts.Subnets = expandStringList(v.(*schema.Set).List())
   155  	}
   156  
   157  	log.Printf("[DEBUG] ALB create configuration: %#v", elbOpts)
   158  
   159  	resp, err := elbconn.CreateLoadBalancer(elbOpts)
   160  	if err != nil {
   161  		return errwrap.Wrapf("Error creating Application Load Balancer: {{err}}", err)
   162  	}
   163  
   164  	if len(resp.LoadBalancers) != 1 {
   165  		return fmt.Errorf("No load balancers returned following creation of %s", d.Get("name").(string))
   166  	}
   167  
   168  	d.SetId(*resp.LoadBalancers[0].LoadBalancerArn)
   169  	log.Printf("[INFO] ALB ID: %s", d.Id())
   170  
   171  	return resourceAwsAlbUpdate(d, meta)
   172  }
   173  
   174  func resourceAwsAlbRead(d *schema.ResourceData, meta interface{}) error {
   175  	elbconn := meta.(*AWSClient).elbv2conn
   176  	albArn := d.Id()
   177  
   178  	describeAlbOpts := &elbv2.DescribeLoadBalancersInput{
   179  		LoadBalancerArns: []*string{aws.String(albArn)},
   180  	}
   181  
   182  	describeResp, err := elbconn.DescribeLoadBalancers(describeAlbOpts)
   183  	if err != nil {
   184  		if isLoadBalancerNotFound(err) {
   185  			// The ALB is gone now, so just remove it from the state
   186  			log.Printf("[WARN] ALB %s not found in AWS, removing from state", d.Id())
   187  			d.SetId("")
   188  			return nil
   189  		}
   190  
   191  		return errwrap.Wrapf("Error retrieving ALB: {{err}}", err)
   192  	}
   193  	if len(describeResp.LoadBalancers) != 1 {
   194  		return fmt.Errorf("Unable to find ALB: %#v", describeResp.LoadBalancers)
   195  	}
   196  
   197  	alb := describeResp.LoadBalancers[0]
   198  
   199  	d.Set("arn", alb.LoadBalancerArn)
   200  	d.Set("arn_suffix", albSuffixFromARN(alb.LoadBalancerArn))
   201  	d.Set("name", alb.LoadBalancerName)
   202  	d.Set("internal", (alb.Scheme != nil && *alb.Scheme == "internal"))
   203  	d.Set("security_groups", flattenStringList(alb.SecurityGroups))
   204  	d.Set("subnets", flattenSubnetsFromAvailabilityZones(alb.AvailabilityZones))
   205  	d.Set("vpc_id", alb.VpcId)
   206  	d.Set("zone_id", alb.CanonicalHostedZoneId)
   207  	d.Set("dns_name", alb.DNSName)
   208  
   209  	respTags, err := elbconn.DescribeTags(&elbv2.DescribeTagsInput{
   210  		ResourceArns: []*string{alb.LoadBalancerArn},
   211  	})
   212  	if err != nil {
   213  		return errwrap.Wrapf("Error retrieving ALB Tags: {{err}}", err)
   214  	}
   215  
   216  	var et []*elbv2.Tag
   217  	if len(respTags.TagDescriptions) > 0 {
   218  		et = respTags.TagDescriptions[0].Tags
   219  	}
   220  	d.Set("tags", tagsToMapELBv2(et))
   221  
   222  	attributesResp, err := elbconn.DescribeLoadBalancerAttributes(&elbv2.DescribeLoadBalancerAttributesInput{
   223  		LoadBalancerArn: aws.String(d.Id()),
   224  	})
   225  	if err != nil {
   226  		return errwrap.Wrapf("Error retrieving ALB Attributes: {{err}}", err)
   227  	}
   228  
   229  	accessLogMap := map[string]interface{}{}
   230  	for _, attr := range attributesResp.Attributes {
   231  		switch *attr.Key {
   232  		case "access_logs.s3.bucket":
   233  			accessLogMap["bucket"] = *attr.Value
   234  		case "access_logs.s3.prefix":
   235  			accessLogMap["prefix"] = *attr.Value
   236  		case "idle_timeout.timeout_seconds":
   237  			timeout, err := strconv.Atoi(*attr.Value)
   238  			if err != nil {
   239  				return errwrap.Wrapf("Error parsing ALB timeout: {{err}}", err)
   240  			}
   241  			log.Printf("[DEBUG] Setting ALB Timeout Seconds: %d", timeout)
   242  			d.Set("idle_timeout", timeout)
   243  		case "deletion_protection.enabled":
   244  			protectionEnabled := (*attr.Value) == "true"
   245  			log.Printf("[DEBUG] Setting ALB Deletion Protection Enabled: %t", protectionEnabled)
   246  			d.Set("enable_deletion_protection", protectionEnabled)
   247  		}
   248  	}
   249  
   250  	log.Printf("[DEBUG] Setting ALB Access Logs: %#v", accessLogMap)
   251  	if accessLogMap["bucket"] != "" || accessLogMap["prefix"] != "" {
   252  		d.Set("access_logs", []interface{}{accessLogMap})
   253  	} else {
   254  		d.Set("access_logs", []interface{}{})
   255  	}
   256  
   257  	return nil
   258  }
   259  
   260  func resourceAwsAlbUpdate(d *schema.ResourceData, meta interface{}) error {
   261  	elbconn := meta.(*AWSClient).elbv2conn
   262  
   263  	if !d.IsNewResource() {
   264  		if err := setElbV2Tags(elbconn, d); err != nil {
   265  			return errwrap.Wrapf("Error Modifying Tags on ALB: {{err}}", err)
   266  		}
   267  	}
   268  
   269  	attributes := make([]*elbv2.LoadBalancerAttribute, 0)
   270  
   271  	if d.HasChange("access_logs") {
   272  		logs := d.Get("access_logs").([]interface{})
   273  		if len(logs) == 1 {
   274  			log := logs[0].(map[string]interface{})
   275  
   276  			attributes = append(attributes,
   277  				&elbv2.LoadBalancerAttribute{
   278  					Key:   aws.String("access_logs.s3.enabled"),
   279  					Value: aws.String("true"),
   280  				},
   281  				&elbv2.LoadBalancerAttribute{
   282  					Key:   aws.String("access_logs.s3.bucket"),
   283  					Value: aws.String(log["bucket"].(string)),
   284  				})
   285  
   286  			if prefix, ok := log["prefix"]; ok {
   287  				attributes = append(attributes, &elbv2.LoadBalancerAttribute{
   288  					Key:   aws.String("access_logs.s3.prefix"),
   289  					Value: aws.String(prefix.(string)),
   290  				})
   291  			}
   292  		} else if len(logs) == 0 {
   293  			attributes = append(attributes, &elbv2.LoadBalancerAttribute{
   294  				Key:   aws.String("access_logs.s3.enabled"),
   295  				Value: aws.String("false"),
   296  			})
   297  		}
   298  	}
   299  
   300  	if d.HasChange("enable_deletion_protection") {
   301  		attributes = append(attributes, &elbv2.LoadBalancerAttribute{
   302  			Key:   aws.String("deletion_protection.enabled"),
   303  			Value: aws.String(fmt.Sprintf("%t", d.Get("enable_deletion_protection").(bool))),
   304  		})
   305  	}
   306  
   307  	if d.HasChange("idle_timeout") {
   308  		attributes = append(attributes, &elbv2.LoadBalancerAttribute{
   309  			Key:   aws.String("idle_timeout.timeout_seconds"),
   310  			Value: aws.String(fmt.Sprintf("%d", d.Get("idle_timeout").(int))),
   311  		})
   312  	}
   313  
   314  	if len(attributes) != 0 {
   315  		input := &elbv2.ModifyLoadBalancerAttributesInput{
   316  			LoadBalancerArn: aws.String(d.Id()),
   317  			Attributes:      attributes,
   318  		}
   319  
   320  		log.Printf("[DEBUG] ALB Modify Load Balancer Attributes Request: %#v", input)
   321  		_, err := elbconn.ModifyLoadBalancerAttributes(input)
   322  		if err != nil {
   323  			return fmt.Errorf("Failure configuring ALB attributes: %s", err)
   324  		}
   325  	}
   326  
   327  	return resourceAwsAlbRead(d, meta)
   328  }
   329  
   330  func resourceAwsAlbDelete(d *schema.ResourceData, meta interface{}) error {
   331  	albconn := meta.(*AWSClient).elbv2conn
   332  
   333  	log.Printf("[INFO] Deleting ALB: %s", d.Id())
   334  
   335  	// Destroy the load balancer
   336  	deleteElbOpts := elbv2.DeleteLoadBalancerInput{
   337  		LoadBalancerArn: aws.String(d.Id()),
   338  	}
   339  	if _, err := albconn.DeleteLoadBalancer(&deleteElbOpts); err != nil {
   340  		return fmt.Errorf("Error deleting ALB: %s", err)
   341  	}
   342  
   343  	return nil
   344  }
   345  
   346  // flattenSubnetsFromAvailabilityZones creates a slice of strings containing the subnet IDs
   347  // for the ALB based on the AvailabilityZones structure returned by the API.
   348  func flattenSubnetsFromAvailabilityZones(availabilityZones []*elbv2.AvailabilityZone) []string {
   349  	var result []string
   350  	for _, az := range availabilityZones {
   351  		result = append(result, *az.SubnetId)
   352  	}
   353  	return result
   354  }
   355  
   356  func albSuffixFromARN(arn *string) string {
   357  	if arn == nil {
   358  		return ""
   359  	}
   360  
   361  	if arnComponents := regexp.MustCompile(`arn:.*:loadbalancer/(.*)`).FindAllStringSubmatch(*arn, -1); len(arnComponents) == 1 {
   362  		if len(arnComponents[0]) == 2 {
   363  			return arnComponents[0][1]
   364  		}
   365  	}
   366  
   367  	return ""
   368  }