Skip to content

[Bug fix] aws_lambda_function: Suppress false persistent diffs on log levels when logging_format = "JSON" and publish = true #42660

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/42660.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_lambda_function: Fixed an issue where persistent diffs appeared in log level arguments when `log_format` in `logging_config` was set to `JSON` and `publish = true`
```
21 changes: 19 additions & 2 deletions internal/service/lambda/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -1360,12 +1360,25 @@ func needsFunctionCodeUpdate(d sdkv2.ResourceDiffer) bool {
d.HasChange("architectures")
}

func hasChangeForLoggingConfigLogLevel(d sdkv2.ResourceDiffer, key string, logFormatHasChange bool) bool {
if d.HasChange(key) {
oldValue, newValue := d.GetChange(key)
suppressed := suppressLoggingConfigUnspecifiedLogLevelsPrimitive(key, oldValue.(string), newValue.(string), logFormatHasChange)
return !suppressed
} else {
return false
}
}

func needsFunctionConfigUpdate(d sdkv2.ResourceDiffer) bool {
return d.HasChange(names.AttrDescription) ||
d.HasChange("handler") ||
d.HasChange("file_system_config") ||
d.HasChange("image_config") ||
d.HasChange("logging_config") ||
d.HasChange("logging_config.0.log_format") ||
d.HasChange("logging_config.0.log_group") ||
hasChangeForLoggingConfigLogLevel(d, "logging_config.0.application_log_level", d.HasChange("logging_config.0.log_format")) ||
hasChangeForLoggingConfigLogLevel(d, "logging_config.0.system_log_level", d.HasChange("logging_config.0.log_format")) ||
d.HasChange("memory_size") ||
d.HasChange(names.AttrRole) ||
d.HasChange(names.AttrTimeout) ||
Expand Down Expand Up @@ -1544,7 +1557,11 @@ func flattenLoggingConfig(apiObject *awstypes.LoggingConfig) []map[string]any {

// Suppress diff if log levels have not been specified, unless log_format has changed
func suppressLoggingConfigUnspecifiedLogLevels(k, old, new string, d *schema.ResourceData) bool {
if d.HasChanges("logging_config.0.log_format") {
return suppressLoggingConfigUnspecifiedLogLevelsPrimitive(k, old, new, d.HasChanges("logging_config.0.log_format"))
}

func suppressLoggingConfigUnspecifiedLogLevelsPrimitive(k, old, new string, logFormatHasChanges bool) bool { //nolint:unparam
if logFormatHasChanges {
return false
}
if old != "" && new == "" {
Expand Down
165 changes: 161 additions & 4 deletions internal/service/lambda/function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -635,12 +635,12 @@ func TestAccLambdaFunction_versionedUpdate(t *testing.T) {
PreConfig: func() {
timeBeforeUpdate = time.Now()
},
Config: testAccFunctionConfig_versionedNodeJs20xRuntime(path, rName),
Config: testAccFunctionConfig_versionedNodeJs22xRuntime(path, rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, names.AttrVersion, versionUpdated),
acctest.CheckResourceAttrRegionalARN(ctx, resourceName, "qualified_arn", "lambda", fmt.Sprintf("function:%s:%s", rName, versionUpdated)),
resource.TestCheckResourceAttr(resourceName, "runtime", string(awstypes.RuntimeNodejs20x)),
resource.TestCheckResourceAttr(resourceName, "runtime", string(awstypes.RuntimeNodejs22x)),
func(s *terraform.State) error {
return testAccCheckAttributeIsDateAfter(s, resourceName, "last_modified", timeBeforeUpdate)
},
Expand Down Expand Up @@ -1251,6 +1251,104 @@ func TestAccLambdaFunction_loggingConfig(t *testing.T) {
})
}

func TestAccLambdaFunction_loggingConfigWithPublish(t *testing.T) {
ctx := acctest.Context(t)
var conf lambda.GetFunctionOutput
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_lambda_function.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.LambdaServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckFunctionDestroy(ctx),

Steps: []resource.TestStep{
{
Config: testAccFunctionConfig_loggingConfigWithPublish(rName, "Text"),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "publish", acctest.CtTrue),
resource.TestCheckResourceAttr(resourceName, names.AttrVersion, "1"),
resource.TestCheckResourceAttr(resourceName, "logging_config.#", "1"),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.application_log_level", ""),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.log_format", "Text"),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.system_log_level", ""),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"filename", "publish"},
},
{
Config: testAccFunctionConfig_loggingConfigWithPublish(rName, "JSON"),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "publish", acctest.CtTrue),
resource.TestCheckResourceAttr(resourceName, names.AttrVersion, "2"),
resource.TestCheckResourceAttr(resourceName, "logging_config.#", "1"),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.application_log_level", "INFO"),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.log_format", "JSON"),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.system_log_level", "INFO"),
),
},
{
Config: testAccFunctionConfig_loggingConfigWithPublish(rName, "JSON"),
ExpectNonEmptyPlan: false,
PlanOnly: true,
},
{
Config: testAccFunctionConfig_loggingConfigWithPublishUpdated1(rName, "JSON", "DEBUG"),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "publish", acctest.CtTrue),
resource.TestCheckResourceAttr(resourceName, names.AttrVersion, "3"),
resource.TestCheckResourceAttr(resourceName, "logging_config.#", "1"),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.application_log_level", "DEBUG"),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.log_format", "JSON"),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.system_log_level", "INFO"),
),
},
{
Config: testAccFunctionConfig_loggingConfigWithPublishUpdated2(rName, "JSON", "WARN"),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "publish", acctest.CtTrue),
resource.TestCheckResourceAttr(resourceName, names.AttrVersion, "4"),
resource.TestCheckResourceAttr(resourceName, "logging_config.#", "1"),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.application_log_level", "DEBUG"),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.log_format", "JSON"),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.system_log_level", "WARN"),
),
},
{
Config: testAccFunctionConfig_loggingConfigWithPublish(rName, "JSON"),
ExpectNonEmptyPlan: false,
PlanOnly: true,
},
{
Config: testAccFunctionConfig_loggingConfigWithPublish(rName, "Text"),
Check: resource.ComposeTestCheckFunc(
testAccCheckFunctionExists(ctx, resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "publish", acctest.CtTrue),
resource.TestCheckResourceAttr(resourceName, names.AttrVersion, "5"),
resource.TestCheckResourceAttr(resourceName, "logging_config.#", "1"),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.application_log_level", ""),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.log_format", "Text"),
resource.TestCheckResourceAttr(resourceName, "logging_config.0.system_log_level", ""),
),
},
{
Config: testAccFunctionConfig_loggingConfigWithPublish(rName, "Text"),
ExpectNonEmptyPlan: false,
PlanOnly: true,
},
},
})
}

func TestAccLambdaFunction_tracing(t *testing.T) {
ctx := acctest.Context(t)
if testing.Short() {
Expand Down Expand Up @@ -2973,7 +3071,7 @@ resource "aws_lambda_function" "test" {
`, fileName, rName, publish))
}

func testAccFunctionConfig_versionedNodeJs20xRuntime(fileName, rName string) string {
func testAccFunctionConfig_versionedNodeJs22xRuntime(fileName, rName string) string {
return acctest.ConfigCompose(
acctest.ConfigLambdaBase(rName, rName, rName),
fmt.Sprintf(`
Expand All @@ -2983,7 +3081,7 @@ resource "aws_lambda_function" "test" {
publish = true
role = aws_iam_role.iam_for_lambda.arn
handler = "exports.example"
runtime = "nodejs20.x"
runtime = "nodejs22.x"
}
`, fileName, rName))
}
Expand Down Expand Up @@ -3385,6 +3483,65 @@ resource "aws_lambda_function" "test" {
`, rName))
}

func testAccFunctionConfig_loggingConfigWithPublish(rName, logFormat string) string {
return acctest.ConfigCompose(
acctest.ConfigLambdaBase(rName, rName, rName),
fmt.Sprintf(`
resource "aws_lambda_function" "test" {
filename = "test-fixtures/lambdatest.zip"
function_name = %[1]q
role = aws_iam_role.iam_for_lambda.arn
handler = "exports.example"
runtime = "nodejs20.x"
publish = true

logging_config {
log_format = %[2]q
}
}
`, rName, logFormat))
}

func testAccFunctionConfig_loggingConfigWithPublishUpdated1(rName, logFormat, appLogLevel string) string {
return acctest.ConfigCompose(
acctest.ConfigLambdaBase(rName, rName, rName),
fmt.Sprintf(`
resource "aws_lambda_function" "test" {
filename = "test-fixtures/lambdatest.zip"
function_name = %[1]q
role = aws_iam_role.iam_for_lambda.arn
handler = "exports.example"
runtime = "nodejs20.x"
publish = true

logging_config {
log_format = %[2]q
application_log_level = %[3]q
}
}
`, rName, logFormat, appLogLevel))
}

func testAccFunctionConfig_loggingConfigWithPublishUpdated2(rName, logFormat, sysLogLevel string) string {
return acctest.ConfigCompose(
acctest.ConfigLambdaBase(rName, rName, rName),
fmt.Sprintf(`
resource "aws_lambda_function" "test" {
filename = "test-fixtures/lambdatest.zip"
function_name = %[1]q
role = aws_iam_role.iam_for_lambda.arn
handler = "exports.example"
runtime = "nodejs20.x"
publish = true

logging_config {
log_format = %[2]q
system_log_level = %[3]q
}
}
`, rName, logFormat, sysLogLevel))
}

func testAccFunctionConfig_tracing(rName string) string {
return acctest.ConfigCompose(
acctest.ConfigLambdaBase(rName, rName, rName),
Expand Down
Loading