Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
fix: Make quiet_archive_local_exec properly suppress Poetry/pip/npm o…
…utput

- Pass quiet flag from Terraform to package.py via query data
- Add quiet flag to build_data dictionary in prepare_command
- Suppress stdout/stderr in Poetry, pip, and npm subprocess calls when quiet=true
- Add example demonstrating quiet packaging functionality
- Update documentation and apply formatting fixes

Fixes issue where quiet_archive_local_exec only affected Terraform's local-exec
output but not the actual Poetry/pip/npm subprocess calls within package.py.

Resolves #706
  • Loading branch information
Ryan Ragnell committed Oct 8, 2025
commit 3d1b3e3250bec9fa26512f2944537d97975d2aaa
8 changes: 8 additions & 0 deletions examples/build-package/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

Configuration in this directory creates deployment packages in a variety of combinations.

This example demonstrates various packaging scenarios including:
- Python packages with pip requirements
- Poetry-based Python packages
- Node.js packages with npm
- Docker-based builds
- **Quiet packaging** - suppressing Poetry/pip/npm output during builds using `quiet_archive_local_exec = true`

Look into [Runtimes Examples](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/runtimes) for more ways to build and deploy AWS Lambda Functions using supported runtimes (Rust, Go, Java).

## Usage
Expand Down Expand Up @@ -44,6 +51,7 @@ Note that this example may create resources which cost money. Run `terraform des
| <a name="module_package_dir_pip_dir"></a> [package\_dir\_pip\_dir](#module\_package\_dir\_pip\_dir) | ../../ | n/a |
| <a name="module_package_dir_poetry"></a> [package\_dir\_poetry](#module\_package\_dir\_poetry) | ../../ | n/a |
| <a name="module_package_dir_poetry_no_docker"></a> [package\_dir\_poetry\_no\_docker](#module\_package\_dir\_poetry\_no\_docker) | ../../ | n/a |
| <a name="module_package_dir_poetry_quiet"></a> [package\_dir\_poetry\_quiet](#module\_package\_dir\_poetry\_quiet) | ../../ | n/a |
| <a name="module_package_dir_with_npm_install"></a> [package\_dir\_with\_npm\_install](#module\_package\_dir\_with\_npm\_install) | ../../ | n/a |
| <a name="module_package_dir_with_npm_install_lock_file"></a> [package\_dir\_with\_npm\_install\_lock\_file](#module\_package\_dir\_with\_npm\_install\_lock\_file) | ../../ | n/a |
| <a name="module_package_dir_without_npm_install"></a> [package\_dir\_without\_npm\_install](#module\_package\_dir\_without\_npm\_install) | ../../ | n/a |
Expand Down
18 changes: 18 additions & 0 deletions examples/build-package/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,24 @@ module "package_dir_poetry_no_docker" {
artifacts_dir = "${path.root}/builds/package_dir_poetry/"
}

# Create zip-archive with Poetry dependencies and demonstrate quiet packaging output
module "package_dir_poetry_quiet" {
source = "../../"

create_function = false

runtime = "python3.12"

source_path = [
{
path = "${path.module}/../fixtures/python-app-poetry"
poetry_install = true
}
]
artifacts_dir = "${path.root}/builds/package_dir_poetry_quiet/"
quiet_archive_local_exec = true # Suppress Poetry/pip output during packaging
}

# Create zip-archive of a single directory without running "pip install" (which is default for python runtime)
module "package_dir_without_pip_install" {
source = "../../"
Expand Down
53 changes: 42 additions & 11 deletions package.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,11 @@ def __init__(
compress_type=zipfile.ZIP_DEFLATED,
compresslevel=None,
timestamp=None,
quiet=False,
):
self.timestamp = timestamp
self.filename = zip_filename
self.quiet = quiet

if not (self.filename and isinstance(self.filename, str)):
raise ValueError("Zip file path must be provided")
Expand All @@ -312,7 +314,8 @@ def open(self):
raise zipfile.BadZipFile("ZipStream object can't be reused")
self._ensure_base_path(self.filename)
self._tmp_filename = "{}.tmp".format(self.filename)
self._log.info("creating '%s' archive", self.filename)
if not self.quiet:
self._log.info("creating '%s' archive", self.filename)
self._zip = zipfile.ZipFile(self._tmp_filename, "w", self._compress_type)
return self

Expand Down Expand Up @@ -356,7 +359,8 @@ def write_dirs(self, *base_dirs, prefix=None, timestamp=None):
"""
self._ensure_open()
for base_dir in base_dirs:
self._log.info("adding content of directory: %s", base_dir)
if not self.quiet:
self._log.info("adding content of directory: %s", base_dir)
for path in emit_dir_content(base_dir):
arcname = os.path.relpath(path, base_dir)
self._write_file(path, prefix, arcname, timestamp)
Expand All @@ -382,10 +386,11 @@ def _write_file(self, file_path, prefix=None, name=None, timestamp=None):
if prefix:
arcname = os.path.join(prefix, arcname)
zinfo = self._make_zinfo_from_file(file_path, arcname)
if zinfo.is_dir():
self._log.info("adding: %s/", arcname)
else:
self._log.info("adding: %s", arcname)
if not self.quiet:
if zinfo.is_dir():
self._log.info("adding: %s/", arcname)
else:
self._log.info("adding: %s", arcname)
if timestamp is None:
timestamp = self.timestamp
date_time = self._timestamp_to_date_time(timestamp)
Expand Down Expand Up @@ -1170,7 +1175,15 @@ def install_pip_requirements(query, requirements_file, tmp_dir):
cmd_log.info(shlex_join(pip_command))
log_handler and log_handler.flush()
try:
check_call(pip_command, env=subproc_env)
if query.quiet:
check_call(
pip_command,
env=subproc_env,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
else:
check_call(pip_command, env=subproc_env)
except FileNotFoundError as e:
raise RuntimeError(
"Python interpreter version equal "
Expand Down Expand Up @@ -1346,7 +1359,15 @@ def copy_file_to_target(file, temp_dir):
cmd_log.info(poetry_commands)
log_handler and log_handler.flush()
for poetry_command in poetry_commands:
check_call(poetry_command, env=subproc_env)
if query.quiet:
check_call(
poetry_command,
env=subproc_env,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
else:
check_call(poetry_command, env=subproc_env)

os.remove(pyproject_target_file)
if poetry_lock_target_file:
Expand Down Expand Up @@ -1443,7 +1464,15 @@ def install_npm_requirements(query, requirements_file, tmp_dir):
cmd_log.info(shlex_join(npm_command))
log_handler and log_handler.flush()
try:
check_call(npm_command, env=subproc_env)
if query.quiet:
check_call(
npm_command,
env=subproc_env,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
else:
check_call(npm_command, env=subproc_env)
except FileNotFoundError as e:
raise RuntimeError(
"Nodejs interpreter version equal "
Expand Down Expand Up @@ -1719,6 +1748,7 @@ def prepare_command(args):
"runtime": runtime,
"artifacts_dir": artifacts_dir,
"build_plan": build_plan,
"quiet": query.quiet,
}
if docker:
build_data["docker"] = docker
Expand Down Expand Up @@ -1778,12 +1808,13 @@ def build_command(args):

# Zip up the build plan and write it to the target filename.
# This will be used by the Lambda function as the source code package.
with ZipWriteStream(filename) as zs:
with ZipWriteStream(filename, quiet=getattr(query, "quiet", False)) as zs:
bpm = BuildPlanManager(args, log=log)
bpm.execute(build_plan, zs, query)

os.utime(filename, ns=(timestamp, timestamp))
log.info("Created: %s", shlex.quote(filename))
if not getattr(query, "quiet", False):
log.info("Created: %s", shlex.quote(filename))
if log.isEnabledFor(logging.DEBUG):
with open(filename, "rb") as f:
log.info("Base64sha256: %s", source_code_hash(f.read()))
Expand Down
1 change: 1 addition & 0 deletions package.tf
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ data "external" "archive_prepare" {
)

recreate_missing_package = var.recreate_missing_package
quiet = var.quiet_archive_local_exec
}
}

Expand Down