terraform-aws-modules/terraform-aws-lambda

Lambda layer build with pip requirements.txt works for local execution but fails using docker

caioquirino opened this issue · 14 comments

Description

Please provide a clear and concise description of the issue you are encountering, and a reproduction of your configuration (see the examples/* directory for references that you can copy+paste and tailor to match your configs if you are unable to copy your exact configuration). The reproduction MUST be executable by running terraform init && terraform apply without any further changes.

If your request is for a new feature, please use the Feature request template.

  • ✋ I have searched the open/closed issues and my issue is not listed.

⚠️ Note

Before you submit an issue, please perform the following first:

  1. Remove the local .terraform directory (! ONLY if state is stored remotely, which hopefully you are following that best practice!): rm -rf .terraform/
  2. Re-initialize the project root to pull down modules: terraform init
  3. Re-attempt your terraform plan or apply and check if the issue still persists

Versions

  • Module version [Required]: 4.17.0

  • Terraform version: 1.4.2

  • Provider version(s): 4.65.0

Reproduction Code [Required]

Code:

locals {
  build_in_docker = true
}
module "lambda_layer" {
  source  = "terraform-aws-modules/lambda/aws"
  version = "~> 4.17.0"

  create_layer        = true
  layer_name          = "layer"
  compatible_runtimes = ["python3.9"]
  architectures       = ["x86_64"]

  store_on_s3   = true
  s3_bucket     = "bucket"
  s3_prefix     = "layers/"

  source_path = [
    "${path.root}/../requirements.txt",
    {
      pip_requirements = "${path.root}/../requirements.txt"
    },
  ]

  build_in_docker           = local.build_in_docker
  docker_pip_cache          = local.build_in_docker
  docker_additional_options = local.build_in_docker ? ["--platform", "linux/amd64"] : null
  runtime                   = "python3.9"
}

Files:

requirements.txt
localstack/
            main.tf
            ....tf

Execution: cd localstack && terraform apply -auto-approve

Steps to reproduce the behavior:

When I run the code above following the folder structure above, it works normally when building locally, but it builds a package with macos binaries. In order to build it for linux, I have to use the docker setup. But when I try it, it doesn't work. It throws an error instead, saying that there's no requirements.txt file.

Not using workspaces, I have deleted the terraform state and .terraform dirs entirely, as well as recreated the localstack instance.

Expected behavior

The lambda layer's artifact should be built properly from the requirements.txt file

Actual behavior

It failed complaining that the requirements.txt file does not exist

Terminal Output Screenshot(s)

image

Additional context

│ Error: local-exec provisioner error
│ 
│   with module.lambda_layer_poetry.null_resource.archive[0],
│   on .terraform/modules/lambda_layer_poetry/package.tf line 67, in resource "null_resource" "archive":
│   67:   provisioner "local-exec" {
│ 
│ Error running command 'builds/be89b46f2726a9d44b2733a4b9c40a5d1717ab38c2c299851d2ad7dee97ac085.plan.json': exit status 1. Output: zip: creating 'builds/be89b46f2726a9d44b2733a4b9c40a5d1717ab38c2c299851d2ad7dee97ac085.zip'
│ archive
│ zip: adding: requirements.txt
│ Installing python requirements: ./../requirements.txt
│ > mktemp -d terraform-aws-lambda-XXXXXXXX # /var/folders/sb/hzwg60vj2l391dqngh0l0swm0000gp/T/terraform-aws-lambda-5pyvm69o
│ > cd /var/folders/sb/hzwg60vj2l391dqngh0l0swm0000gp/T/terraform-aws-lambda-5pyvm69o
│ > docker run --rm -w /var/task -v /private/var/folders/sb/hzwg60vj2l391dqngh0l0swm0000gp/T/terraform-aws-lambda-5pyvm69o:/var/task:z -v /Users/caio.quirinodasilva@TMNL.nl/.ssh/known_hosts:/root/.ssh/known_hosts:z --platform
│ linux/amd64 -v /Users/caio.quirinodasilva@TMNL.nl/github/tmnlorg/digital-channel-data-ingestion/localstack/builds/cache/pip:/root/.cache/pip:z --entrypoint '' public.ecr.aws/sam/build-python3.9 /bin/sh -c 'python3.9 -m pip
│ install --no-compile --prefix= --target=. --requirement=requirements.txt && chown -R 502:20 .'
│ ERROR: Could not open requirements file: [Errno 2] No such file or directory: 'requirements.txt'
│ WARNING: You are using pip version 22.0.4; however, version 23.1.2 is available.
│ You should consider upgrading via the '/var/lang/bin/python3.9 -m pip install --upgrade pip' command.
│ zip: Error during zip archive creation
│ Traceback (most recent call last):
│   File "/Users/caio.quirinodasilva@TMNL.nl/github/tmnlorg/digital-channel-data-ingestion/localstack/.terraform/modules/lambda_layer_poetry/package.py", line 1516, in build_command
│     bpm.execute(build_plan, zs, query)
│   File "/Users/caio.quirinodasilva@TMNL.nl/github/tmnlorg/digital-channel-data-ingestion/localstack/.terraform/modules/lambda_layer_poetry/package.py", line 859, in execute
│     with install_pip_requirements(query, pip_requirements, tmp_dir) as rd:
│   File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/contextlib.py", line 117, in __enter__
│     return next(self.gen)
│   File "/Users/caio.quirinodasilva@TMNL.nl/github/tmnlorg/digital-channel-data-ingestion/localstack/.terraform/modules/lambda_layer_poetry/package.py", line 1004, in install_pip_requirements
│     check_call(docker_run_command(
│   File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/subprocess.py", line 373, in check_call
│     raise CalledProcessError(retcode, cmd)
│ subprocess.CalledProcessError: Command '['docker', 'run', '--rm', '-w', '/var/task', '-v', '/private/var/folders/sb/hzwg60vj2l391dqngh0l0swm0000gp/T/terraform-aws-lambda-5pyvm69o:/var/task:z', '-v',
│ '/Users/caio.quirinodasilva@TMNL.nl/.ssh/known_hosts:/root/.ssh/known_hosts:z', '--platform', 'linux/amd64', '-v',
│ '/Users/caio.quirinodasilva@TMNL.nl/github/tmnlorg/digital-channel-data-ingestion/localstack/builds/cache/pip:/root/.cache/pip:z', '--entrypoint', '', 'public.ecr.aws/sam/build-python3.9', '/bin/sh', '-c', 'python3.9 -m pip
│ install --no-compile --prefix= --target=. --requirement=requirements.txt && chown -R 502:20 .']' returned non-zero exit status 1.

Hi, today I encountered the exact same error. I use a MacBook Pro with an Apple M1 Pro chip and macOS Ventura 13.4. I'm using Rancher Desktop, and even though I prefer containerd+nerdctl as the container engine, I have configured dockerd (moby) for the comment below. Also, I haven't delved into this module's specifics, so my analysis may be incorrect and potentially misleading.

Terraform:

module "audit_exporter" {
  source  = "terraform-aws-modules/lambda/aws"
  version = "~> 5.0.0"
 
  function_name = "${local.project}-eks-audit-exporter"
  description   = "Export EKS audit logs from CloudWatch and uploads them to S3"
  handler       = "app.lambda_handler"
  runtime       = "python3.8"
  architectures = ["x86_64"]
 
  source_path = [
    {
      path             = "${path.root}/assets/audit_exporter_lambda"
      pip_requirements = true
      patterns         = ["!.venv/.*"]
    }
  ]
 
  build_in_docker   = true
  docker_additional_options = ["--platform", "linux/amd64"]
 
  store_on_s3 = true
  s3_bucket   = module.audit_exporter_artifacts.s3_bucket_id
 
  environment_variables = {
    BUCKET_NAME = "mybucket"
  }
}

Output:

module.audit_exporter.null_resource.archive[0]: Provisioning with 'local-exec'...
module.audit_exporter.null_resource.archive[0] (local-exec): Executing: ["python3" ".terraform/modules/audit_exporter/package.py" "build" "--timestamp" "1686251027549448000" "builds/bcd093d772e6d337d36210bded0729f72b6a3e1a74bd3167093b73f0de4ad58b.plan.json"]
module.audit_exporter.null_resource.archive[0] (local-exec): zip: creating 'builds/bcd093d772e6d337d36210bded0729f72b6a3e1a74bd3167093b73f0de4ad58b.zip' archive
module.audit_exporter.null_resource.archive[0] (local-exec): Installing python requirements: ./assets/audit_exporter_lambda/requirements.txt
module.audit_exporter.null_resource.archive[0] (local-exec): > mktemp -d terraform-aws-lambda-XXXXXXXX # /var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T/terraform-aws-lambda-qfm73ip_
module.audit_exporter.null_resource.archive[0] (local-exec): > cd /var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T/terraform-aws-lambda-qfm73ip_
module.audit_exporter.null_resource.archive[0] (local-exec): > docker run --rm -w /var/task -v /private/var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T/terraform-aws-lambda-qfm73ip_:/var/task:z -v /Users/xxxxxxxxxxxxxx/.ssh/known_hosts:/root/.ssh/known_hosts:z --platform linux/amd64 --entrypoint '' public.ecr.aws/sam/build-python3.8 /bin/sh -c 'python3.8 -m pip install --no-compile --prefix= --target=. --requirement=requirements.txt && chown -R 501:20 .'
module.audit_exporter.null_resource.archive[0] (local-exec): ERROR: Could not open requirements file: [Errno 2] No such file or directory: 'requirements.txt'
module.audit_exporter.null_resource.archive[0] (local-exec): WARNING: You are using pip version 22.0.4; however, version 23.1.2 is available.
module.audit_exporter.null_resource.archive[0] (local-exec): You should consider upgrading via the '/var/lang/bin/python3.8 -m pip install --upgrade pip' command.
module.audit_exporter.null_resource.archive[0] (local-exec): zip: Error during zip archive creation
module.audit_exporter.null_resource.archive[0] (local-exec): Traceback (most recent call last):
module.audit_exporter.null_resource.archive[0] (local-exec):   File ".terraform/modules/audit_exporter/package.py", line 1516, in build_command
module.audit_exporter.null_resource.archive[0] (local-exec):     bpm.execute(build_plan, zs, query)
module.audit_exporter.null_resource.archive[0] (local-exec):   File ".terraform/modules/audit_exporter/package.py", line 859, in execute
module.audit_exporter.null_resource.archive[0] (local-exec):     with install_pip_requirements(query, pip_requirements, tmp_dir) as rd:
module.audit_exporter.null_resource.archive[0] (local-exec):   File "/Users/xxxxxxxxxxxxxx/.asdf/installs/python/3.8.16/lib/python3.8/contextlib.py", line 113, in __enter__
module.audit_exporter.null_resource.archive[0] (local-exec):     return next(self.gen)
module.audit_exporter.null_resource.archive[0] (local-exec):   File ".terraform/modules/audit_exporter/package.py", line 1004, in install_pip_requirements
module.audit_exporter.null_resource.archive[0] (local-exec):     check_call(docker_run_command(
module.audit_exporter.null_resource.archive[0] (local-exec):   File "/Users/xxxxxxxxxxxxxx/.asdf/installs/python/3.8.16/lib/python3.8/subprocess.py", line 364, in check_call
module.audit_exporter.null_resource.archive[0] (local-exec):     raise CalledProcessError(retcode, cmd)
module.audit_exporter.null_resource.archive[0] (local-exec): subprocess.CalledProcessError: Command '['docker', 'run', '--rm', '-w', '/var/task', '-v', '/private/var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T/terraform-aws-lambda-qfm73ip_:/var/task:z', '-v', '/Users/xxxxxxxxxxxxxx/.ssh/known_hosts:/root/.ssh/known_hosts:z', '--platform', 'linux/amd64', '--entrypoint', '', 'public.ecr.aws/sam/build-python3.8', '/bin/sh', '-c', 'python3.8 -m pip install --no-compile --prefix= --target=. --requirement=requirements.txt && chown -R 501:20 .']' returned non-zero exit status 1.

Error:

Error: local-exec provisioner error
 
  with module.audit_exporter.null_resource.archive[0],
  on .terraform/modules/audit_exporter/package.tf line 67, in resource "null_resource" "archive":
  67:   provisioner "local-exec" {
 
Error running command 'builds/bcd093d772e6d337d36210bded0729f72b6a3e1a74bd3167093b73f0de4ad58b.plan.json': exit status 1. Output: zip: creating
'builds/bcd093d772e6d337d36210bded0729f72b6a3e1a74bd3167093b73f0de4ad58b.zip' archive
Installing python requirements: ./assets/audit_exporter_lambda/requirements.txt
> mktemp -d terraform-aws-lambda-XXXXXXXX # /var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T/terraform-aws-lambda-qfm73ip_
> cd /var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T/terraform-aws-lambda-qfm73ip_
> docker run --rm -w /var/task -v /private/var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T/terraform-aws-lambda-qfm73ip_:/var/task:z -v
/Users/xxxxxxxxxxxxxx/.ssh/known_hosts:/root/.ssh/known_hosts:z --platform linux/amd64 --entrypoint '' public.ecr.aws/sam/build-python3.8 /bin/sh
-c 'python3.8 -m pip install --no-compile --prefix= --target=. --requirement=requirements.txt && chown -R 501:20 .'
ERROR: Could not open requirements file: [Errno 2] No such file or directory: 'requirements.txt'
WARNING: You are using pip version 22.0.4; however, version 23.1.2 is available.
You should consider upgrading via the '/var/lang/bin/python3.8 -m pip install --upgrade pip' command.
zip: Error during zip archive creation
Traceback (most recent call last):
  File ".terraform/modules/audit_exporter/package.py", line 1516, in build_command
    bpm.execute(build_plan, zs, query)
  File ".terraform/modules/audit_exporter/package.py", line 859, in execute
    with install_pip_requirements(query, pip_requirements, tmp_dir) as rd:
  File "/Users/xxxxxxxxxxxxxx/.asdf/installs/python/3.8.16/lib/python3.8/contextlib.py", line 113, in __enter__
    return next(self.gen)
  File ".terraform/modules/audit_exporter/package.py", line 1004, in install_pip_requirements
    check_call(docker_run_command(
  File "/Users/xxxxxxxxxxxxxx/.asdf/installs/python/3.8.16/lib/python3.8/subprocess.py", line 364, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['docker', 'run', '--rm', '-w', '/var/task', '-v',
'/private/var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T/terraform-aws-lambda-qfm73ip_:/var/task:z', '-v',
'/Users/xxxxxxxxxxxxxx/.ssh/known_hosts:/root/.ssh/known_hosts:z', '--platform', 'linux/amd64', '--entrypoint', '',
'public.ecr.aws/sam/build-python3.8', '/bin/sh', '-c', 'python3.8 -m pip install --no-compile --prefix= --target=. --requirement=requirements.txt
&& chown -R 501:20 .']' returned non-zero exit status 1.

By locally modifying package.py (which is neither a solution nor a workaround but useful for debugging purposes), I ensured that the requirements.txt file was correctly copied to the temporary folder:

    log.info('Installing python requirements: %s', requirements_file)
    with tempdir(tmp_dir) as temp_dir:
        requirements_filename = os.path.basename(requirements_file)
        target_file = os.path.join(temp_dir, requirements_filename)
        shutil.copyfile(requirements_file, target_file)
+       log.info(os.listdir(temp_dir))
 
        python_exec = runtime
        subproc_env = None
 
        if not docker:
            if WINDOWS:
                python_exec = 'python.exe'
            elif OSX:
                # Workaround for OSX when XCode command line tools'
                # python becomes the main system python interpreter
                os_path = '{}:/Library/Developer/CommandLineTools' \
                          '/usr/bin'.format(os.environ['PATH'])
                subproc_env = os.environ.copy()
                subproc_env['PATH'] = os_path
 
        # Install dependencies into the temporary directory.
        with cd(temp_dir):
+           log.info(os.listdir('.'))
            pip_command = [
                python_exec, '-m', 'pip',
                'install', '--no-compile',
                '--prefix=', '--target=.',
                '--requirement={}'.format(requirements_filename),
            ]

For both functions, I received module.audit_exporter.null_resource.archive[0] (local-exec): ['requirements.txt'] as the output.

I noticed that the bind mount is being performed on a formally different folder than the one created:

module.audit_exporter.null_resource.archive[0] (local-exec): > mktemp -d terraform-aws-lambda-XXXXXXXX # /var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T/terraform-aws-lambda-qfm73ip_
module.audit_exporter.null_resource.archive[0] (local-exec): > cd /var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T/terraform-aws-lambda-qfm73ip_
module.audit_exporter.null_resource.archive[0] (local-exec): > docker run --rm -w /var/task -v /private/var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T/terraform-aws-lambda-qfm73ip_:/var/task:z -v /Users/matteocellucci/.ssh/known_hosts:/root/.ssh/known_hosts:z --platform linux/amd64 --entrypoint '' public.ecr.aws/sam/build-python3.8 /bin/sh -c 'python3.8 -m pip install --no-compile --prefix= --target=. --requirement=requirements.txt && chown -R 501:20 .'

Upon examining the folder, I noticed that it is actually a symlink:

$ ls -l /
total 10
[…]
drwxr-xr-x   6 root  wheel   192 May 31 09:27 private
[…]
lrwxr-xr-x@  1 root  wheel    11 May 13 00:29 var -> private/var

Although a symlink shouldn't cause any issues while researching online, I came across this article (I couldn't find more official sources): https://medium.com/effy-tech/fixing-the-var-folders-error-in-docker-for-mac-v2-2-3-2a40e776132d.

By still modifying the local package.py file, I managed to make the module work by changing the mounted path in the container:

        with cd(temp_dir):
            pip_command = [
                python_exec, '-m', 'pip',
                'install', '--no-compile',
                '--prefix=', '--target=.',
                '--requirement={}'.format(requirements_filename),
            ]
            if docker:
                with_ssh_agent = docker.with_ssh_agent
                pip_cache_dir = docker.docker_pip_cache
                if pip_cache_dir:
                    if isinstance(pip_cache_dir, str):
                        pip_cache_dir = os.path.abspath(
                            os.path.join(working_dir, pip_cache_dir))
                    else:
                        pip_cache_dir = os.path.abspath(os.path.join(
                            working_dir, artifacts_dir, 'cache/pip'))
 
                chown_mask = '{}:{}'.format(os.getuid(), os.getgid())
                shell_command = [shlex_join(pip_command), '&&',
                                 shlex_join(['chown', '-R',
                                             chown_mask, '.'])]
                shell_command = [' '.join(shell_command)]
                check_call(docker_run_command(
-                   '.', shell_command, runtime,
+                   temp_dir, shell_command, runtime,
                    image=docker_image_tag_id,
                    shell=True, ssh_agent=with_ssh_agent,
                    pip_cache_dir=pip_cache_dir, docker=docker,
                ))

Additionally, while entering the Python REPL, I conducted the following tests:

$ python
Python 3.8.16 (default, Jun  1 2023, 14:39:18)
[Clang 14.0.3 (clang-1403.0.22.14.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.chdir("/var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T/")
>>> os.getcwd()
'/private/var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T'
>>> os.path.abspath('.')
'/private/var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T'
>>> os.system("pwd")
/private/var/folders/nr/n2kh28194_31djxs4n734hwr0000gn/T

I also tried enabling Administrative Access to Rancher Desktop without success.

EDIT#1: This problem may be related to number #137, albeit in different environments.

EDIT#2: Maybe I found a viable workaround. Setting pip_tmp_dir to a folder that does not suffer from the described issue like pip_tmp_dir = "${path.module}/.build". Unfortunately, I encountered another error. I'm investigating it and will let you know.

Based on the analysis of the previous comment, I made it work by allowing the mount of /private/var/folders/. Following this comment on GitHub (rancher-sandbox/rancher-desktop#2875 (comment)), I created the file ~/Library/Application\ Support/rancher-desktop/lima/_config/override.yaml and added the following content:

mounts:
  - location: /private/var/folders
    writable: true

After restarting Rancher Desktop, it is working correctly now. I don't know if this is a permanent solution or a workaround. Although I think this module should handle the default configuration of Rancher Desktop, I'm unsure of the best way to do it.

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

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

Same issue here, does not work on Windows.
Any suggestion I could find for the poor guy on Windows was to use Unix based system 🤷‍♂️

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

Unstale...

same here

Please consider running examples/build-package - there are multiple similar samples which can help you to find out the right set of arguments.

PS: I can't reproduce the error with the provided code snippet.

@antonbabenko Is there any chance that you try using Apple Silicon?

My scenario was exactly:

  • Compiling on Apple Silicon (macos-darwin) to run on Localstack (linux-x86-64)

@caioquirino I was able to run the code provided by you (with valid path to requirements.txt) using tflocal apply (and verify with awslocal lambda list-layers).

module "package_file_with_pip_requirements" {
  source = "../../"   # <- using latest version

  create_function = false

  runtime = "python3.8"

  source_path = [
    "${path.module}/../fixtures/python3.8-app1/requirements.txt",
    {
      pip_requirements = "${path.module}/../fixtures/python3.8-app1/requirements.txt"
    },
  ]

  create_layer = true
  layer_name   = "layer2435"
  architectures    = ["x86_64"]
  build_in_docker  = true
  docker_pip_cache = true
}

It just works for me. :)

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

This issue was automatically closed because of stale in 10 days

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.