diff --git a/aws/resource_aws_lambda_function.go b/aws/resource_aws_lambda_function.go index 9d73e192b5a..6050df8d806 100644 --- a/aws/resource_aws_lambda_function.go +++ b/aws/resource_aws_lambda_function.go @@ -200,7 +200,21 @@ func resourceAwsLambdaFunction() *schema.Resource { "tags": tagsSchema(), }, + + CustomizeDiff: updateComputedAttributesOnPublish, + } +} + +func updateComputedAttributesOnPublish(d *schema.ResourceDiff, meta interface{}) error { + if needsFunctionCodeUpdate(d) { + d.SetNewComputed("last_modified") + publish := d.Get("publish").(bool) + if publish { + d.SetNewComputed("version") + d.SetNewComputed("qualified_arn") + } } + return nil } // resourceAwsLambdaFunction maps to: @@ -525,6 +539,14 @@ func resourceAwsLambdaFunctionDelete(d *schema.ResourceData, meta interface{}) e return nil } +type resourceDiffer interface { + HasChange(string) bool +} + +func needsFunctionCodeUpdate(d resourceDiffer) bool { + return d.HasChange("filename") || d.HasChange("source_code_hash") || d.HasChange("s3_bucket") || d.HasChange("s3_key") || d.HasChange("s3_object_version") +} + // resourceAwsLambdaFunctionUpdate maps to: // UpdateFunctionCode in the API / SDK func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) error { @@ -672,7 +694,7 @@ func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) e d.SetPartial("timeout") } - if d.HasChange("filename") || d.HasChange("source_code_hash") || d.HasChange("s3_bucket") || d.HasChange("s3_key") || d.HasChange("s3_object_version") { + if needsFunctionCodeUpdate(d) { codeReq := &lambda.UpdateFunctionCodeInput{ FunctionName: aws.String(d.Id()), Publish: aws.Bool(d.Get("publish").(bool)), diff --git a/aws/resource_aws_lambda_function_test.go b/aws/resource_aws_lambda_function_test.go index ac0052f2b40..29f2774089c 100644 --- a/aws/resource_aws_lambda_function_test.go +++ b/aws/resource_aws_lambda_function_test.go @@ -9,6 +9,7 @@ import ( "regexp" "strings" "testing" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/lambda" @@ -287,7 +288,7 @@ func TestAccAWSLambdaFunction_versioned(t *testing.T) { CheckDestroy: testAccCheckLambdaFunctionDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSLambdaConfigVersioned(funcName, policyName, roleName, sgName), + Config: testAccAWSLambdaConfigVersioned("test-fixtures/lambdatest.zip", funcName, policyName, roleName, sgName), Check: resource.ComposeTestCheckFunc( testAccCheckAwsLambdaFunctionExists("aws_lambda_function.lambda_function_test", funcName, &conf), testAccCheckAwsLambdaFunctionName(&conf, funcName), @@ -302,6 +303,58 @@ func TestAccAWSLambdaFunction_versioned(t *testing.T) { }) } +func TestAccAWSLambdaFunction_versionedUpdate(t *testing.T) { + var conf lambda.GetFunctionOutput + + path, zipFile, err := createTempFile("lambda_localUpdate") + if err != nil { + t.Fatal(err) + } + defer os.Remove(path) + + rString := acctest.RandString(8) + funcName := fmt.Sprintf("tf_acc_lambda_func_versioned_%s", rString) + policyName := fmt.Sprintf("tf_acc_policy_lambda_func_versioned_%s", rString) + roleName := fmt.Sprintf("tf_acc_role_lambda_func_versioned_%s", rString) + sgName := fmt.Sprintf("tf_acc_sg_lambda_func_versioned_%s", rString) + + var timeBeforeUpdate time.Time + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaConfigVersioned("test-fixtures/lambdatest.zip", funcName, policyName, roleName, sgName), + }, + { + PreConfig: func() { + testAccCreateZipFromFiles(map[string]string{"test-fixtures/lambda_func_modified.js": "lambda.js"}, zipFile) + timeBeforeUpdate = time.Now() + }, + Config: testAccAWSLambdaConfigVersioned(path, funcName, policyName, roleName, sgName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionExists("aws_lambda_function.lambda_function_test", funcName, &conf), + testAccCheckAwsLambdaFunctionName(&conf, funcName), + testAccCheckAwsLambdaFunctionArnHasSuffix(&conf, ":"+funcName), + resource.TestMatchResourceAttr("aws_lambda_function.lambda_function_test", "version", + regexp.MustCompile("^2$")), + resource.TestMatchResourceAttr("data.template_file.function_version", "rendered", + regexp.MustCompile("^2$")), + resource.TestMatchResourceAttr("aws_lambda_function.lambda_function_test", "qualified_arn", + regexp.MustCompile(":"+funcName+":[0-9]+$")), + resource.TestMatchResourceAttr("data.template_file.qualified_arn", "rendered", + regexp.MustCompile(fmt.Sprintf(":function:%s:2$", funcName))), + func(s *terraform.State) error { + return testAccCheckAttributeIsDateAfter(s, "data.template_file.last_modified", "rendered", timeBeforeUpdate) + }, + ), + }, + }, + }) +} + func TestAccAWSLambdaFunction_DeadLetterConfig(t *testing.T) { var conf lambda.GetFunctionOutput @@ -572,6 +625,8 @@ func TestAccAWSLambdaFunction_localUpdate(t *testing.T) { funcName := fmt.Sprintf("tf_acc_lambda_func_local_upd_%s", rString) roleName := fmt.Sprintf("tf_acc_role_lambda_func_local_upd_%s", rString) + var timeBeforeUpdate time.Time + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -592,6 +647,7 @@ func TestAccAWSLambdaFunction_localUpdate(t *testing.T) { { PreConfig: func() { testAccCreateZipFromFiles(map[string]string{"test-fixtures/lambda_func_modified.js": "lambda.js"}, zipFile) + timeBeforeUpdate = time.Now() }, Config: genAWSLambdaFunctionConfig_local(path, roleName, funcName), Check: resource.ComposeTestCheckFunc( @@ -599,6 +655,9 @@ func TestAccAWSLambdaFunction_localUpdate(t *testing.T) { testAccCheckAwsLambdaFunctionName(&conf, funcName), testAccCheckAwsLambdaFunctionArnHasSuffix(&conf, funcName), testAccCheckAwsLambdaSourceCodeHash(&conf, "0tdaP9H9hsk9c2CycSwOG/sa/x5JyAmSYunA/ce99Pg="), + func(s *terraform.State) error { + return testAccCheckAttributeIsDateAfter(s, "data.template_file.last_modified", "rendered", timeBeforeUpdate) + }, ), }, }, @@ -1050,6 +1109,30 @@ func testAccCheckAwsLambdaSourceCodeHash(function *lambda.GetFunctionOutput, exp } } +func testAccCheckAttributeIsDateAfter(s *terraform.State, name string, key string, before time.Time) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Resource %s not found", name) + } + + v, ok := rs.Primary.Attributes[key] + if !ok { + return fmt.Errorf("%s: Attribute '%s' not found", name, key) + } + + const ISO8601UTC = "2006-01-02T15:04:05Z0700" + timeValue, err := time.Parse(ISO8601UTC, v) + if err != nil { + return err + } + + if !before.Before(timeValue) { + return fmt.Errorf("Expected time attribute %s.%s with value %s was not before %s", name, key, v, before.Format(ISO8601UTC)) + } + + return nil +} + func testAccCreateZipFromFiles(files map[string]string, zipFile *os.File) error { zipFile.Truncate(0) zipFile.Seek(0, 0) @@ -1387,17 +1470,41 @@ resource "aws_lambda_function" "lambda_function_test" { `, keyDesc, funcName) } -func testAccAWSLambdaConfigVersioned(funcName, policyName, roleName, sgName string) string { +func testAccAWSLambdaConfigVersioned(fileName, funcName, policyName, roleName, sgName string) string { return fmt.Sprintf(baseAccAWSLambdaConfig(policyName, roleName, sgName)+` resource "aws_lambda_function" "lambda_function_test" { - filename = "test-fixtures/lambdatest.zip" + filename = "%s" function_name = "%s" publish = true role = "${aws_iam_role.iam_for_lambda.arn}" handler = "exports.example" runtime = "nodejs4.3" } -`, funcName) + +data "template_file" "function_version" { + template = "$${function_version}" + + vars { + function_version = "${aws_lambda_function.lambda_function_test.version}" + } +} + +data "template_file" "last_modified" { + template = "$${last_modified}" + + vars { + last_modified = "${aws_lambda_function.lambda_function_test.last_modified}" + } +} + +data "template_file" "qualified_arn" { + template = "$${qualified_arn}" + + vars { + qualified_arn = "${aws_lambda_function.lambda_function_test.qualified_arn}" + } +} +`, fileName, funcName) } func testAccAWSLambdaConfigWithTracingConfig(funcName, policyName, roleName, sgName string) string { @@ -1723,6 +1830,15 @@ resource "aws_lambda_function" "lambda_function_local" { handler = "exports.example" runtime = "nodejs4.3" } + +data "template_file" "last_modified" { + template = "$${last_modified}" + + vars { + last_modified = "${aws_lambda_function.lambda_function_local.last_modified}" + } +} + `, roleName, filePath, filePath, funcName) }