Merge pull request #11291 from hashicorp/pr-10985

provider/aws: implement CloudFront Lambda Function Associations (supersedes #10985)
This commit is contained in:
Clint 2017-01-20 16:34:03 -06:00 committed by GitHub
commit 7c5b3a5012
4 changed files with 240 additions and 17 deletions

View File

@ -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)),

View File

@ -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)

View File

@ -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,

View File

@ -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