terraform-aws-lambda icon indicating copy to clipboard operation
terraform-aws-lambda copied to clipboard

Building function package separately fails with Terraform v1.13

Open richardowen opened this issue 5 months ago β€’ 10 comments

Description

When we deploy multiple functions with the same source code, we use a separate module call for the function packaging before deploying the functions from the generated package zip (see example code below). This worked fine previously, with Terraform v1.12.2, but no longer works with Terraform v1.13 (just released).

This appears to be because Terraform now checks whether the zip file exists during the plan step (it doesn't yet), and then expects the same result during the apply step (but the file does exist at that stage). This validation has been added in Terraform v1.13 - see hashicorp/terraform#37001.

Is this an intended way to use this module, or should we have each function module call do its own packaging of the same code? I seem to remember that this may have caused a problem in the past with parallel builds overwriting each other (as they were building from exactly the same source directory with the same hash).

  • [x] βœ‹ I have searched the open/closed issues and my issue is not listed.

Versions

  • Module version [Required]: v8.1.0

  • Terraform version: v1.13.0

  • Provider version(s):

provider registry.terraform.io/hashicorp/aws v6.10.0 provider registry.terraform.io/hashicorp/external v2.3.5 provider registry.terraform.io/hashicorp/local v2.5.3 provider registry.terraform.io/hashicorp/null v3.2.4

Reproduction Code

module "lambda_package" {
  source = "terraform-aws-modules/lambda/aws"

  create_function = false

  source_path = "src"
}

module "lambda_function_1" {
  source = "terraform-aws-modules/lambda/aws"

  create_package = false

  function_name = "function_1"
  handler       = "index.handler_1"
  runtime       = "python3.13"

  local_existing_package = module.lambda_package.local_filename
}

module "lambda_function_2" {
  source = "terraform-aws-modules/lambda/aws"

  create_package = false

  function_name = "function_2"
  handler       = "index.handler_2"
  runtime       = "python3.13"

  local_existing_package = module.lambda_package.local_filename
}

Steps to reproduce the behavior:

  • terraform init
  • terraform apply

Expected behavior

  • terraform apply completes successfully

Actual behavior

...
Plan: 10 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

module.lambda_package.local_file.archive_plan[0]: Creating...
module.lambda_package.local_file.archive_plan[0]: Creation complete after 0s [id=f1bd1c8263437ce6362e0088149d446474d01b4b]
module.lambda_package.null_resource.archive[0]: Creating...
module.lambda_package.null_resource.archive[0]: Provisioning with 'local-exec'...
module.lambda_package.null_resource.archive[0] (local-exec): local-exec: Executing: Suppressed by quiet=true
module.lambda_package.null_resource.archive[0] (local-exec): zip: creating 'builds/44141140bf260a18b95b6fa11487ff73afdc0d31a7b36c4149fb66efdb35d648.zip' archive
module.lambda_package.null_resource.archive[0] (local-exec): zip: adding content of directory: /Users/richard.owen/Desktop/lambda_test/src
module.lambda_package.null_resource.archive[0] (local-exec): zip: adding: index.py
module.lambda_package.null_resource.archive[0] (local-exec): Created: builds/44141140bf260a18b95b6fa11487ff73afdc0d31a7b36c4149fb66efdb35d648.zip
module.lambda_package.null_resource.archive[0]: Creation complete after 0s [id=475881427651737073]
module.lambda_function_1.aws_cloudwatch_log_group.lambda[0]: Creating...
module.lambda_function_2.aws_cloudwatch_log_group.lambda[0]: Creating...
module.lambda_function_2.aws_iam_role.lambda[0]: Creating...
module.lambda_function_1.aws_iam_role.lambda[0]: Creating...
module.lambda_function_1.aws_cloudwatch_log_group.lambda[0]: Creation complete after 0s [id=/aws/lambda/function_1]
module.lambda_function_2.aws_cloudwatch_log_group.lambda[0]: Creation complete after 0s [id=/aws/lambda/function_2]
module.lambda_function_1.data.aws_iam_policy_document.logs[0]: Reading...
module.lambda_function_2.data.aws_iam_policy_document.logs[0]: Reading...
module.lambda_function_2.data.aws_iam_policy_document.logs[0]: Read complete after 0s [id=932321896]
module.lambda_function_1.data.aws_iam_policy_document.logs[0]: Read complete after 0s [id=4263703753]
module.lambda_function_2.aws_iam_role.lambda[0]: Creation complete after 1s [id=function_2]
module.lambda_function_2.aws_iam_role_policy.logs[0]: Creating...
module.lambda_function_1.aws_iam_role.lambda[0]: Creation complete after 1s [id=function_1]
module.lambda_function_1.aws_iam_role_policy.logs[0]: Creating...
module.lambda_function_2.aws_iam_role_policy.logs[0]: Creation complete after 0s [id=function_2:function_2-logs]
module.lambda_function_1.aws_iam_role_policy.logs[0]: Creation complete after 0s [id=function_1:function_1-logs]
β•·
β”‚ Error: Error in function call
β”‚ 
β”‚   on .terraform/modules/lambda_function_1/main.tf line 15, in locals:
β”‚   15:   was_missing = var.local_existing_package != null ? !fileexists(var.local_existing_package) : local.archive_was_missing
β”‚     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     β”‚ while calling fileexists(path)
β”‚     β”‚ var.local_existing_package is "builds/44141140bf260a18b95b6fa11487ff73afdc0d31a7b36c4149fb66efdb35d648.zip"
β”‚ 
β”‚ Call to function "fileexists" failed: function returned an inconsistent result.
β•΅
β•·
β”‚ Error: Error in function call
β”‚ 
β”‚   on .terraform/modules/lambda_function_2/main.tf line 15, in locals:
β”‚   15:   was_missing = var.local_existing_package != null ? !fileexists(var.local_existing_package) : local.archive_was_missing
β”‚     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     β”‚ while calling fileexists(path)
β”‚     β”‚ var.local_existing_package is "builds/44141140bf260a18b95b6fa11487ff73afdc0d31a7b36c4149fb66efdb35d648.zip"
β”‚ 
β”‚ Call to function "fileexists" failed: function returned an inconsistent result.
β•΅

richardowen avatar Aug 22 '25 12:08 richardowen

Interesting... I don't have a specific fix in mind for this issue, but I think you are right that Terraform 1.13 suddenly became more strict with filesystem functions.

As a workaround, you can try to split the runs (terraform apply -target=module.lambda_package) to get the package before using it in other modules.

antonbabenko avatar Aug 22 '25 12:08 antonbabenko

This issue has been automatically marked as stale because it has been open 30 days with no activity. Remove stale label or comment or this issue will be closed in 10 days

github-actions[bot] avatar Sep 22 '25 00:09 github-actions[bot]

Not stale

richardowen avatar Sep 22 '25 06:09 richardowen

This issue has been automatically marked as stale because it has been open 30 days with no activity. Remove stale label or comment or this issue will be closed in 10 days

github-actions[bot] avatar Oct 23 '25 00:10 github-actions[bot]

Not stale

richardowen avatar Oct 23 '25 11:10 richardowen

Came here to report this also. Thanks for beating me to it @richardowen !

lorengordon avatar Oct 24 '25 22:10 lorengordon

I traced through the commit history to try and better understand the use case for local.was_missing, but it goes all the way back to the very first commit! 😭

If we look at how it is used, the only reference is to the source_code_hash. But I think that's a "nice to have". We could instead impose on the user the requirement to change some other attribute to update the function. Like changing the filename when the package changes. Which the module already does, by writing the hash as the filename. Code changes, new filename, new package, lambda is updated.

Basically, perhaps when the user provides var.local_existing_package, we just short-circuit local.was_missing to avoid the call to fileexists() and also force source_code_hash = null?

lorengordon avatar Oct 24 '25 23:10 lorengordon

@antonbabenko Do you think this proposal would be acceptable?

I traced through the commit history to try and better understand the use case for local.was_missing, but it goes all the way back to the very first commit! 😭

If we look at how it is used, the only reference is to the source_code_hash. But I think that's a "nice to have". We could instead impose on the user the requirement to change some other attribute to update the function. Like changing the filename when the package changes. Which the module already does, by writing the hash as the filename. Code changes, new filename, new package, lambda is updated.

Basically, perhaps when the user provides var.local_existing_package, we just short-circuit local.was_missing to avoid the call to fileexists() and also force source_code_hash = null?

lorengordon avatar Oct 28 '25 15:10 lorengordon

This issue has been automatically marked as stale because it has been open 30 days with no activity. Remove stale label or comment or this issue will be closed in 10 days

github-actions[bot] avatar Nov 28 '25 00:11 github-actions[bot]

Not stale

lorengordon avatar Nov 28 '25 00:11 lorengordon