diff --git a/builtin/providers/aws/cloudfront_distribution_configuration_structure.go b/builtin/providers/aws/cloudfront_distribution_configuration_structure.go index b891bd26b..ccac0d9c5 100644 --- a/builtin/providers/aws/cloudfront_distribution_configuration_structure.go +++ b/builtin/providers/aws/cloudfront_distribution_configuration_structure.go @@ -228,6 +228,18 @@ func defaultCacheBehaviorHash(v interface{}) int { buf.WriteString(fmt.Sprintf("%s-", e.(string))) } } + if d, ok := m["lambda_function_association"]; ok { + var associations []interface{} + switch d.(type) { + case *schema.Set: + associations = d.(*schema.Set).List() + default: + associations = d.([]interface{}) + } + for _, lfa := range associations { + buf.WriteString(fmt.Sprintf("%d-", lambdaFunctionAssociationHash(lfa.(map[string]interface{})))) + } + } return hashcode.String(buf.String()) } @@ -267,6 +279,11 @@ func expandCacheBehavior(m map[string]interface{}) *cloudfront.CacheBehavior { } else { cb.TrustedSigners = expandTrustedSigners([]interface{}{}) } + + if v, ok := m["lambda_function_association"]; ok { + cb.LambdaFunctionAssociations = expandLambdaFunctionAssociations(v.(*schema.Set).List()) + } + if v, ok := m["smooth_streaming"]; ok { cb.SmoothStreaming = aws.Bool(v.(bool)) } @@ -294,6 +311,9 @@ func flattenCacheBehavior(cb *cloudfront.CacheBehavior) map[string]interface{} { if len(cb.TrustedSigners.Items) > 0 { m["trusted_signers"] = flattenTrustedSigners(cb.TrustedSigners) } + if len(cb.LambdaFunctionAssociations.Items) > 0 { + m["lambda_function_association"] = flattenLambdaFunctionAssociations(cb.LambdaFunctionAssociations) + } if cb.MaxTTL != nil { m["max_ttl"] = int(*cb.MaxTTL) } @@ -352,6 +372,18 @@ func cacheBehaviorHash(v interface{}) int { if d, ok := m["path_pattern"]; ok { buf.WriteString(fmt.Sprintf("%s-", d)) } + if d, ok := m["lambda_function_association"]; ok { + var associations []interface{} + switch d.(type) { + case *schema.Set: + associations = d.(*schema.Set).List() + default: + associations = d.([]interface{}) + } + for _, lfa := range associations { + buf.WriteString(fmt.Sprintf("%d-", lambdaFunctionAssociationHash(lfa.(map[string]interface{})))) + } + } return hashcode.String(buf.String()) } @@ -375,6 +407,59 @@ func flattenTrustedSigners(ts *cloudfront.TrustedSigners) []interface{} { return []interface{}{} } +func lambdaFunctionAssociationHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["event_type"].(string))) + buf.WriteString(fmt.Sprintf("%s", m["lambda_arn"].(string))) + return hashcode.String(buf.String()) +} + +func expandLambdaFunctionAssociations(v interface{}) *cloudfront.LambdaFunctionAssociations { + if v == nil { + return &cloudfront.LambdaFunctionAssociations{ + Quantity: aws.Int64(0), + } + } + + s := v.([]interface{}) + var lfa cloudfront.LambdaFunctionAssociations + lfa.Quantity = aws.Int64(int64(len(s))) + lfa.Items = make([]*cloudfront.LambdaFunctionAssociation, len(s)) + for i, lf := range s { + lfa.Items[i] = expandLambdaFunctionAssociation(lf.(map[string]interface{})) + } + return &lfa +} + +func expandLambdaFunctionAssociation(lf map[string]interface{}) *cloudfront.LambdaFunctionAssociation { + var lfa cloudfront.LambdaFunctionAssociation + if v, ok := lf["event_type"]; ok { + lfa.EventType = aws.String(v.(string)) + } + if v, ok := lf["lambda_arn"]; ok { + lfa.LambdaFunctionARN = aws.String(v.(string)) + } + return &lfa +} + +func flattenLambdaFunctionAssociations(lfa *cloudfront.LambdaFunctionAssociations) []interface{} { + s := make([]interface{}, len(lfa.Items)) + for i, v := range lfa.Items { + s[i] = flattenLambdaFunctionAssociation(v) + } + return s +} + +func flattenLambdaFunctionAssociation(lfa *cloudfront.LambdaFunctionAssociation) map[string]interface{} { + m := map[string]interface{}{} + if lfa != nil { + m["event_type"] = *lfa.EventType + m["lambda_arn"] = *lfa.LambdaFunctionARN + } + return m +} + func expandForwardedValues(m map[string]interface{}) *cloudfront.ForwardedValues { fv := &cloudfront.ForwardedValues{ QueryString: aws.Bool(m["query_string"].(bool)), diff --git a/builtin/providers/aws/cloudfront_distribution_configuration_structure_test.go b/builtin/providers/aws/cloudfront_distribution_configuration_structure_test.go index 24bb45849..14cdad322 100644 --- a/builtin/providers/aws/cloudfront_distribution_configuration_structure_test.go +++ b/builtin/providers/aws/cloudfront_distribution_configuration_structure_test.go @@ -5,22 +5,24 @@ import ( "testing" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudfront" "github.com/hashicorp/terraform/helper/schema" ) func defaultCacheBehaviorConf() map[string]interface{} { return map[string]interface{}{ - "viewer_protocol_policy": "allow-all", - "target_origin_id": "myS3Origin", - "forwarded_values": schema.NewSet(forwardedValuesHash, []interface{}{forwardedValuesConf()}), - "min_ttl": 86400, - "trusted_signers": trustedSignersConf(), - "max_ttl": 365000000, - "smooth_streaming": false, - "default_ttl": 86400, - "allowed_methods": allowedMethodsConf(), - "cached_methods": cachedMethodsConf(), - "compress": true, + "viewer_protocol_policy": "allow-all", + "target_origin_id": "myS3Origin", + "forwarded_values": schema.NewSet(forwardedValuesHash, []interface{}{forwardedValuesConf()}), + "min_ttl": 86400, + "trusted_signers": trustedSignersConf(), + "lambda_function_association": lambdaFunctionAssociationsConf(), + "max_ttl": 365000000, + "smooth_streaming": false, + "default_ttl": 86400, + "allowed_methods": allowedMethodsConf(), + "cached_methods": cachedMethodsConf(), + "compress": true, } } @@ -44,6 +46,21 @@ func trustedSignersConf() []interface{} { return []interface{}{"1234567890EX", "1234567891EX"} } +func lambdaFunctionAssociationsConf() *schema.Set { + x := []interface{}{ + map[string]interface{}{ + "event_type": "viewer-request", + "lambda_arn": "arn:aws:lambda:us-east-1:999999999:function1:alias", + }, + map[string]interface{}{ + "event_type": "origin-response", + "lambda_arn": "arn:aws:lambda:us-east-1:999999999:function2:alias", + }, + } + + return schema.NewSet(lambdaFunctionAssociationHash, x) +} + func forwardedValuesConf() map[string]interface{} { return map[string]interface{}{ "query_string": true, @@ -236,6 +253,9 @@ func viewerCertificateConfSetACM() map[string]interface{} { func TestCloudFrontStructure_expandDefaultCacheBehavior(t *testing.T) { data := defaultCacheBehaviorConf() dcb := expandDefaultCacheBehavior(data) + if dcb == nil { + t.Fatalf("ExpandDefaultCacheBehavior returned nil") + } if *dcb.Compress != true { t.Fatalf("Expected Compress to be true, got %v", *dcb.Compress) } @@ -263,6 +283,9 @@ func TestCloudFrontStructure_expandDefaultCacheBehavior(t *testing.T) { if *dcb.DefaultTTL != 86400 { t.Fatalf("Expected DefaultTTL to be 86400, got %v", *dcb.DefaultTTL) } + if *dcb.LambdaFunctionAssociations.Quantity != 2 { + t.Fatalf("Expected LambdaFunctionAssociations to be 2, got %v", *dcb.LambdaFunctionAssociations.Quantity) + } if reflect.DeepEqual(dcb.AllowedMethods.Items, expandStringList(allowedMethodsConf())) != true { t.Fatalf("Expected TrustedSigners.Items to be %v, got %v", allowedMethodsConf(), dcb.AllowedMethods.Items) } @@ -312,6 +335,9 @@ func TestCloudFrontStructure_expandCacheBehavior(t *testing.T) { if *cb.DefaultTTL != 86400 { t.Fatalf("Expected DefaultTTL to be 86400, got %v", *cb.DefaultTTL) } + if *cb.LambdaFunctionAssociations.Quantity != 2 { + t.Fatalf("Expected LambdaFunctionAssociations to be 2, got %v", *cb.LambdaFunctionAssociations.Quantity) + } if reflect.DeepEqual(cb.AllowedMethods.Items, expandStringList(allowedMethodsConf())) != true { t.Fatalf("Expected AllowedMethods.Items to be %v, got %v", allowedMethodsConf(), cb.AllowedMethods.Items) } @@ -337,6 +363,27 @@ func TestCloudFrontStructure_flattenCacheBehavior(t *testing.T) { if out["target_origin_id"] != "myS3Origin" { t.Fatalf("Expected out[target_origin_id] to be myS3Origin, got %v", out["target_origin_id"]) } + + // the flattened lambda function associations are a slice of maps, + // where as the default cache behavior LFAs are a set. Here we double check + // that and conver the slice to a set, and use Set's Equal() method to check + // equality + var outSet *schema.Set + if outSlice, ok := out["lambda_function_association"].([]interface{}); ok { + outSet = schema.NewSet(lambdaFunctionAssociationHash, outSlice) + } else { + t.Fatalf("out['lambda_function_association'] is not a slice as expected: %#v", out["lambda_function_association"]) + } + + inSet, ok := in["lambda_function_association"].(*schema.Set) + if !ok { + t.Fatalf("in['lambda_function_association'] is not a set as expected: %#v", in["lambda_function_association"]) + } + + if !inSet.Equal(outSet) { + t.Fatalf("in / out sets are not equal, in: \n%#v\n\nout: \n%#v\n", inSet, outSet) + } + diff = out["forwarded_values"].(*schema.Set).Difference(in["forwarded_values"].(*schema.Set)) if len(diff.List()) > 0 { t.Fatalf("Expected out[forwarded_values] to be %v, got %v, diff: %v", out["forwarded_values"], in["forwarded_values"], diff) @@ -427,6 +474,47 @@ func TestCloudFrontStructure_expandTrustedSigners_empty(t *testing.T) { } } +func TestCloudFrontStructure_expandLambdaFunctionAssociations(t *testing.T) { + data := lambdaFunctionAssociationsConf() + lfa := expandLambdaFunctionAssociations(data.List()) + if *lfa.Quantity != 2 { + t.Fatalf("Expected Quantity to be 2, got %v", *lfa.Quantity) + } + if len(lfa.Items) != 2 { + t.Fatalf("Expected Items to be len 2, got %v", len(lfa.Items)) + } + if et := "viewer-request"; *lfa.Items[0].EventType != et { + t.Fatalf("Expected first Item's EventType to be %q, got %q", et, *lfa.Items[0].EventType) + } + if et := "origin-response"; *lfa.Items[1].EventType != et { + t.Fatalf("Expected second Item's EventType to be %q, got %q", et, *lfa.Items[1].EventType) + } +} + +func TestCloudFrontStructure_flattenlambdaFunctionAssociations(t *testing.T) { + in := lambdaFunctionAssociationsConf() + lfa := expandLambdaFunctionAssociations(in.List()) + out := flattenLambdaFunctionAssociations(lfa) + + if reflect.DeepEqual(in.List(), out) != true { + t.Fatalf("Expected out to be %v, got %v", in, out) + } +} + +func TestCloudFrontStructure_expandlambdaFunctionAssociations_empty(t *testing.T) { + data := new(schema.Set) + lfa := expandLambdaFunctionAssociations(data.List()) + if *lfa.Quantity != 0 { + t.Fatalf("Expected Quantity to be 0, got %v", *lfa.Quantity) + } + if len(lfa.Items) != 0 { + t.Fatalf("Expected Items to be len 0, got %v", len(lfa.Items)) + } + if reflect.DeepEqual(lfa.Items, []*cloudfront.LambdaFunctionAssociation{}) != true { + t.Fatalf("Expected Items to be empty, got %v", lfa.Items) + } +} + func TestCloudFrontStructure_expandForwardedValues(t *testing.T) { data := forwardedValuesConf() fv := expandForwardedValues(data) diff --git a/builtin/providers/aws/resource_aws_cloudfront_distribution.go b/builtin/providers/aws/resource_aws_cloudfront_distribution.go index 10372e655..480c02093 100644 --- a/builtin/providers/aws/resource_aws_cloudfront_distribution.go +++ b/builtin/providers/aws/resource_aws_cloudfront_distribution.go @@ -102,6 +102,24 @@ func resourceAwsCloudFrontDistribution() *schema.Resource { }, }, }, + "lambda_function_association": { + Type: schema.TypeSet, + Optional: true, + MaxItems: 4, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "event_type": { + Type: schema.TypeString, + Required: true, + }, + "lambda_arn": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + Set: lambdaFunctionAssociationHash, + }, "max_ttl": { Type: schema.TypeInt, Required: true, @@ -232,6 +250,24 @@ func resourceAwsCloudFrontDistribution() *schema.Resource { }, }, }, + "lambda_function_association": { + Type: schema.TypeSet, + Optional: true, + MaxItems: 4, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "event_type": { + Type: schema.TypeString, + Required: true, + }, + "lambda_arn": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + Set: lambdaFunctionAssociationHash, + }, "max_ttl": { Type: schema.TypeInt, Required: true, diff --git a/website/source/docs/providers/aws/r/cloudfront_distribution.html.markdown b/website/source/docs/providers/aws/r/cloudfront_distribution.html.markdown index d310fa309..98a6fc749 100644 --- a/website/source/docs/providers/aws/r/cloudfront_distribution.html.markdown +++ b/website/source/docs/providers/aws/r/cloudfront_distribution.html.markdown @@ -111,11 +111,9 @@ of several sub-resources - these resources are laid out below. * `comment` (Optional) - Any comments you want to include about the distribution. - * `custom_error_response` (Optional) - One or more [custom error - response](#custom-error-response-arguments) elements (multiples allowed). + * `custom_error_response` (Optional) - One or more [custom error response](#custom-error-response-arguments) elements (multiples allowed). - * `default_cache_behavior` (Required) - The [default cache - behavior](#default-cache-behavior-arguments) for this distribution (maximum + * `default_cache_behavior` (Required) - The [default cache behavior](#default-cache-behavior-arguments) for this distribution (maximum one). * `default_root_object` (Optional) - The object that you want CloudFront to @@ -173,10 +171,13 @@ of several sub-resources - these resources are laid out below. object is in a CloudFront cache before CloudFront forwards another request in the absence of an `Cache-Control max-age` or `Expires` header. - * `forwarded_values` (Required) - The [forwarded values - configuration](#forwarded-values-arguments) that specifies how CloudFront + * `forwarded_values` (Required) - The [forwarded values configuration](#forwarded-values-arguments) that specifies how CloudFront handles query strings, cookies and headers (maximum one). + * `lambda_function_association` (Optional) - A config block that triggers a lambda function with + specific actions. Defined below, maximum 4. **Lambda@Edge is in technical + Preview, and must be enabled on your AWS account to be used** + * `max_ttl` (Required) - The maximum amount of time (in seconds) that an object is in a CloudFront cache before CloudFront forwards another request to your origin to determine whether the object has been updated. Only @@ -222,6 +223,19 @@ of several sub-resources - these resources are laid out below. `true` for `query_string`, all query strings are forwarded, however only the query string keys listed in this argument are cached. When omitted with a value of `true` for `query_string`, all query string keys are cached. + +##### Lambda Function Association + +Lambda@Edge allows you to associate an AWS Lambda Function with a predefined +event. You can associate a single function per event type. See [What is +Lambda@Edge](http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/what-is-lambda-at-edge.html) +for more information + + * `event_type` (Required) - The specific event to trigger this function. + Valid values: `viewer-request`, `origin-request`, `viewer-response`, + `origin-response` + + * `lambda_function_arn` (Required) - ARN of the Lambda function. ##### Cookies Arguments