From 03757c12b167250823af03ffe709180757596854 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Wed, 20 Jan 2021 09:11:23 -0500 Subject: [PATCH] resource/aws_lambda_function: Prevent crash with missing environment variable value (#17056) Previously if the environment variable value became `nil` (likely via AWS console update), the resource could panic: ``` { "Configuration": { ... "Environment": { "Variables": { "XXX": null, "YYY": "ZZZ" }, "Error": null }, ... }, ... } ``` ``` panic: runtime error: invalid memory address or nil pointer dereference 2020-12-10T19:59:19.412Z [DEBUG] plugin.terraform-provider-aws_v3.0.0_x5: [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x401dab7] 2020-12-10T19:59:19.413Z [DEBUG] plugin.terraform-provider-aws_v3.0.0_x5: 2020-12-10T19:59:19.413Z [DEBUG] plugin.terraform-provider-aws_v3.0.0_x5: goroutine 485 [running]: 2020-12-10T19:59:19.413Z [DEBUG] plugin.terraform-provider-aws_v3.0.0_x5: github.com/terraform-providers/terraform-provider-aws/aws.flattenLambdaEnvironment(0xc00162daa0, 0x57add72, 0xa, 0x44b4460) 2020-12-10T19:59:19.413Z [DEBUG] plugin.terraform-provider-aws_v3.0.0_x5: /opt/teamcity-agent/work/5d79fe75d4460a2f/src/github.com/terraform-providers/terraform-provider-aws/aws/structure.go:1594 +0xc7 2020-12-10T19:59:19.413Z [DEBUG] plugin.terraform-provider-aws_v3.0.0_x5: github.com/terraform-providers/terraform-provider-aws/aws.resourceAwsLambdaFunctionRead(0xc00016ee00, 0x4c948a0, 0xc000160a00, 0xc00016ee00, 0x0) 2020-12-10T19:59:19.413Z [DEBUG] plugin.terraform-provider-aws_v3.0.0_x5: /opt/teamcity-agent/work/5d79fe75d4460a2f/src/github.com/terraform-providers/terraform-provider-aws/aws/resource_aws_lambda_function.go:599 +0x107a ``` This replaces the unsafe dereferencing with the AWS Go SDK conversion function. Output from acceptance testing: ``` --- PASS: TestAccAWSLambdaFunction_basic (93.45s) --- PASS: TestAccAWSLambdaFunction_codeSigningConfig (143.84s) --- PASS: TestAccAWSLambdaFunction_concurrency (119.18s) --- PASS: TestAccAWSLambdaFunction_concurrencyCycle (80.17s) --- PASS: TestAccAWSLambdaFunction_DeadLetterConfig (130.84s) --- PASS: TestAccAWSLambdaFunction_DeadLetterConfigUpdated (139.36s) --- PASS: TestAccAWSLambdaFunction_disablePublish (290.84s) --- PASS: TestAccAWSLambdaFunction_disappears (76.76s) --- PASS: TestAccAWSLambdaFunction_EmptyVpcConfig (303.87s) --- PASS: TestAccAWSLambdaFunction_enablePublish (301.82s) --- PASS: TestAccAWSLambdaFunction_encryptedEnvVariables (152.33s) --- PASS: TestAccAWSLambdaFunction_Environment_Variables_NoValue (59.03s) --- PASS: TestAccAWSLambdaFunction_envVariables (135.67s) --- PASS: TestAccAWSLambdaFunction_expectFilenameAndS3Attributes (19.70s) --- PASS: TestAccAWSLambdaFunction_FileSystemConfig (2369.47s) --- PASS: TestAccAWSLambdaFunction_KmsKeyArn_NoEnvironmentVariables (1001.01s) --- PASS: TestAccAWSLambdaFunction_Layers (830.27s) --- PASS: TestAccAWSLambdaFunction_LayersUpdate (1218.31s) --- PASS: TestAccAWSLambdaFunction_localUpdate (1253.77s) --- PASS: TestAccAWSLambdaFunction_localUpdate_nameOnly (318.24s) --- PASS: TestAccAWSLambdaFunction_nilDeadLetterConfig (75.02s) --- PASS: TestAccAWSLambdaFunction_runtimes (434.13s) --- PASS: TestAccAWSLambdaFunction_s3 (39.17s) --- PASS: TestAccAWSLambdaFunction_s3Update_basic (65.26s) --- PASS: TestAccAWSLambdaFunction_s3Update_unversioned (66.94s) --- PASS: TestAccAWSLambdaFunction_tags (111.80s) --- PASS: TestAccAWSLambdaFunction_tracingConfig (1022.97s) --- PASS: TestAccAWSLambdaFunction_UnpublishedCodeUpdate (326.86s) --- PASS: TestAccAWSLambdaFunction_versioned (816.85s) --- PASS: TestAccAWSLambdaFunction_versionedUpdate (1283.13s) --- PASS: TestAccAWSLambdaFunction_VPC (810.42s) --- PASS: TestAccAWSLambdaFunction_VPC_withInvocation (1415.48s) --- PASS: TestAccAWSLambdaFunction_VpcConfig_ProperIamDependencies (346.28s) --- PASS: TestAccAWSLambdaFunction_VPCRemoval (2383.24s) --- PASS: TestAccAWSLambdaFunction_VPCUpdate (2409.76s) ``` --- aws/resource_aws_lambda_function.go | 14 +++++++ aws/resource_aws_lambda_function_test.go | 47 ++++++++++++++++++++++++ aws/structure.go | 18 --------- 3 files changed, 61 insertions(+), 18 deletions(-) diff --git a/aws/resource_aws_lambda_function.go b/aws/resource_aws_lambda_function.go index 7f987cc458f..1486b02f4f9 100644 --- a/aws/resource_aws_lambda_function.go +++ b/aws/resource_aws_lambda_function.go @@ -1274,6 +1274,20 @@ func waitForLambdaFunctionUpdate(conn *lambda.Lambda, functionName string, timeo return err } +func flattenLambdaEnvironment(apiObject *lambda.EnvironmentResponse) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Variables; v != nil { + tfMap["variables"] = aws.StringValueMap(v) + } + + return []interface{}{tfMap} +} + func flattenLambdaFileSystemConfigs(fscList []*lambda.FileSystemConfig) []map[string]interface{} { results := make([]map[string]interface{}, 0, len(fscList)) for _, fsc := range fscList { diff --git a/aws/resource_aws_lambda_function_test.go b/aws/resource_aws_lambda_function_test.go index a2c9e2c0936..262dce47fc6 100644 --- a/aws/resource_aws_lambda_function_test.go +++ b/aws/resource_aws_lambda_function_test.go @@ -416,6 +416,33 @@ func TestAccAWSLambdaFunction_envVariables(t *testing.T) { }) } +func TestAccAWSLambdaFunction_Environment_Variables_NoValue(t *testing.T) { + var conf lambda.GetFunctionOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lambda_function.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLambdaFunctionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLambdaConfigEnvironmentVariablesNoValue(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLambdaFunctionExists(resourceName, rName, &conf), + resource.TestCheckResourceAttr(resourceName, "environment.0.variables.key1", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"filename", "publish"}, + }, + }, + }) +} + func TestAccAWSLambdaFunction_encryptedEnvVariables(t *testing.T) { var conf lambda.GetFunctionOutput @@ -2241,6 +2268,26 @@ resource "aws_lambda_function" "test" { `, funcName) } +func testAccAWSLambdaConfigEnvironmentVariablesNoValue(rName string) string { + return composeConfig( + baseAccAWSLambdaConfig(rName, rName, rName), + fmt.Sprintf(` +resource "aws_lambda_function" "test" { + filename = "test-fixtures/lambdatest.zip" + function_name = %[1]q + handler = "exports.example" + role = aws_iam_role.iam_for_lambda.arn + runtime = "nodejs12.x" + + environment { + variables = { + key1 = "" + } + } +} +`, rName)) +} + func testAccAWSLambdaConfigEncryptedEnvVariables(keyDesc, funcName, policyName, roleName, sgName string) string { return fmt.Sprintf(baseAccAWSLambdaConfig(policyName, roleName, sgName)+` resource "aws_kms_key" "foo" { diff --git a/aws/structure.go b/aws/structure.go index a9f7f382b69..1781aa7bb81 100644 --- a/aws/structure.go +++ b/aws/structure.go @@ -1587,24 +1587,6 @@ func flattenDSVpcSettings( return []map[string]interface{}{settings} } -func flattenLambdaEnvironment(lambdaEnv *lambda.EnvironmentResponse) []interface{} { - envs := make(map[string]interface{}) - en := make(map[string]string) - - if lambdaEnv == nil { - return nil - } - - for k, v := range lambdaEnv.Variables { - en[k] = *v - } - if len(en) > 0 { - envs["variables"] = en - } - - return []interface{}{envs} -} - func expandLambdaEventSourceMappingDestinationConfig(vDest []interface{}) *lambda.DestinationConfig { if len(vDest) == 0 { return nil