repro-lambda 0.6.1__tar.gz → 0.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. repro_lambda-0.7.1/.release-please-manifest.json +3 -0
  2. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/CHANGELOG.md +14 -0
  3. repro_lambda-0.7.1/LICENSE +21 -0
  4. repro_lambda-0.7.1/PKG-INFO +104 -0
  5. repro_lambda-0.7.1/README.md +71 -0
  6. repro_lambda-0.7.1/SETUP.md +262 -0
  7. repro_lambda-0.6.1/SETUP.md → repro_lambda-0.7.1/USAGE.md +27 -263
  8. repro_lambda-0.7.1/examples/complete/README.md +100 -0
  9. repro_lambda-0.7.1/examples/complete/builds/catalog.json +20 -0
  10. repro_lambda-0.7.1/examples/complete/lambdas.toml +26 -0
  11. repro_lambda-0.7.1/examples/complete/main.tf +25 -0
  12. repro_lambda-0.7.1/examples/complete/outputs.tf +9 -0
  13. repro_lambda-0.7.1/examples/complete/src/app/app.py +3 -0
  14. repro_lambda-0.7.1/examples/complete/src/app/requirements.arm64.lock +3 -0
  15. repro_lambda-0.7.1/examples/complete/variables.tf +11 -0
  16. repro_lambda-0.7.1/examples/complete/versions.tf +14 -0
  17. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/pyproject.toml +3 -3
  18. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/__init__.py +1 -1
  19. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/cli.py +1 -1
  20. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/manifest.py +1 -0
  21. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_manifest.py +18 -0
  22. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/uv.lock +1 -1
  23. repro_lambda-0.6.1/.release-please-manifest.json +0 -3
  24. repro_lambda-0.6.1/LICENSE +0 -177
  25. repro_lambda-0.6.1/PKG-INFO +0 -75
  26. repro_lambda-0.6.1/README.md +0 -42
  27. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/.github/workflows/build.yml +0 -0
  28. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/.github/workflows/ci.yml +0 -0
  29. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/.github/workflows/move-major-tag.yml +0 -0
  30. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/.github/workflows/promote.yml +0 -0
  31. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/.github/workflows/publish.yml +0 -0
  32. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/.github/workflows/release.yml +0 -0
  33. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/.gitignore +0 -0
  34. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/.pre-commit-config.yaml +0 -0
  35. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/release-please-config.json +0 -0
  36. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/__main__.py +0 -0
  37. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/build.py +0 -0
  38. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/catalog.py +0 -0
  39. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/docker_runner.py +0 -0
  40. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/git_guard.py +0 -0
  41. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/hasher.py +0 -0
  42. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/promote.py +0 -0
  43. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/s3_uploader.py +0 -0
  44. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/source_locker.py +0 -0
  45. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/source_stager.py +0 -0
  46. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/sources.py +0 -0
  47. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/verify.py +0 -0
  48. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/src/repro_lambda/zip_packager.py +0 -0
  49. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/__init__.py +0 -0
  50. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/conftest.py +0 -0
  51. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/fixtures/sample_nodejs_lambda/handler/index.js +0 -0
  52. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/fixtures/sample_nodejs_lambda/handler/package-lock.json +0 -0
  53. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/fixtures/sample_nodejs_lambda/handler/package.json +0 -0
  54. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/fixtures/sample_nodejs_lambda/lambdas.toml +0 -0
  55. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/fixtures/sample_python_lambda/handler/app.py +0 -0
  56. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/fixtures/sample_python_lambda/handler/requirements.arm64.lock +0 -0
  57. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/fixtures/sample_python_lambda/handler/requirements.in +0 -0
  58. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/fixtures/sample_python_lambda/lambdas.toml +0 -0
  59. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_build_integration.py +0 -0
  60. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_build_nodejs.py +0 -0
  61. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_catalog.py +0 -0
  62. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_cli_build.py +0 -0
  63. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_cli_lock.py +0 -0
  64. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_cli_smoke.py +0 -0
  65. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_docker_runner.py +0 -0
  66. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_docker_runner_nodejs.py +0 -0
  67. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_e2e_nodejs_lambda.py +0 -0
  68. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_e2e_python_lambda.py +0 -0
  69. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_extra_files.py +0 -0
  70. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_git_guard.py +0 -0
  71. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_hasher.py +0 -0
  72. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_per_lambda_builder.py +0 -0
  73. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_promote.py +0 -0
  74. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_python_byte_compat_regression.py +0 -0
  75. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_s3_uploader.py +0 -0
  76. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_source_locker.py +0 -0
  77. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_source_stager.py +0 -0
  78. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_sources.py +0 -0
  79. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_sources_hash.py +0 -0
  80. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_sources_schema.py +0 -0
  81. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_verify.py +0 -0
  82. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_zip_excludes.py +0 -0
  83. {repro_lambda-0.6.1 → repro_lambda-0.7.1}/tests/test_zip_packager.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.7.1"
3
+ }
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.1](https://github.com/antonbabenko/repro-lambda/compare/v0.7.0...v0.7.1) (2026-06-23)
4
+
5
+
6
+ ### Documentation
7
+
8
+ * split SETUP into SETUP+USAGE, add runnable terraform example, restructure README ([#11](https://github.com/antonbabenko/repro-lambda/issues/11)) ([768ebda](https://github.com/antonbabenko/repro-lambda/commit/768ebda07aefa51cc15c0a5b52adbd251b7891d2))
9
+
10
+ ## [0.7.0](https://github.com/antonbabenko/repro-lambda/compare/v0.6.1...v0.7.0) (2026-06-23)
11
+
12
+
13
+ ### Features
14
+
15
+ * support the python3.14 lambda runtime ([#9](https://github.com/antonbabenko/repro-lambda/issues/9)) ([c3fcd8c](https://github.com/antonbabenko/repro-lambda/commit/c3fcd8ceb400891e152293ba06ada747aaa21f9f))
16
+
3
17
  ## [0.6.1](https://github.com/antonbabenko/repro-lambda/compare/v0.6.0...v0.6.1) (2026-06-22)
4
18
 
5
19
 
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anton Babenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: repro-lambda
3
+ Version: 0.7.1
4
+ Summary: Build reproducible AWS Lambda packages outside Terraform, optimized for terraform-aws-lambda by serverless.tf.
5
+ Project-URL: Homepage, https://github.com/antonbabenko/repro-lambda
6
+ Project-URL: Repository, https://github.com/antonbabenko/repro-lambda
7
+ Project-URL: Issues, https://github.com/antonbabenko/repro-lambda/issues
8
+ Author-email: Anton Babenko <anton@antonbabenko.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: aws,lambda,reproducible-builds,serverless,terraform
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: MacOS :: MacOS X
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Build Tools
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: boto3>=1.34
22
+ Requires-Dist: repro-zipfile>=0.3.1
23
+ Requires-Dist: tomlkit>=0.13
24
+ Requires-Dist: typer>=0.12
25
+ Provides-Extra: dev
26
+ Requires-Dist: moto[s3]>=5; extra == 'dev'
27
+ Requires-Dist: pre-commit>=3.7; extra == 'dev'
28
+ Requires-Dist: pytest-mock>=3.12; extra == 'dev'
29
+ Requires-Dist: pytest>=8; extra == 'dev'
30
+ Requires-Dist: pyyaml>=6; extra == 'dev'
31
+ Requires-Dist: ruff>=0.5; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # repro-lambda
35
+
36
+ Build reproducible AWS Lambda packages outside Terraform, optimized for
37
+ [terraform-aws-lambda](https://registry.terraform.io/modules/terraform-aws-modules/lambda/aws/latest) by [serverless.tf](https://serverless.tf/).
38
+
39
+ Produces byte-identical zip files across local dev (macOS) and CI (Linux),
40
+ uploads to S3 by content-hash key, and lets Terraform read `s3_existing_package`
41
+ instead of building during `terraform plan`/`apply`.
42
+
43
+ ## Install
44
+
45
+ pip install repro-lambda
46
+
47
+ ## Quick start
48
+
49
+ repro-lambda lock # regenerate per-arch requirement + source locks
50
+ repro-lambda build --bucket <bucket> # build all lambdas in lambdas.toml, upload to S3
51
+ repro-lambda build --verify --dry-run # two-pass byte-reproducibility check (no upload)
52
+ repro-lambda promote \
53
+ --dev-bucket <dev> --prod-bucket <prod> # copy dev -> prod by content sha (no rebuild)
54
+
55
+ `--bucket` (or `REPRO_LAMBDA_BUCKET`) is required for a real build; add
56
+ `--dry-run` to build without uploading. There is no `init` command yet (the
57
+ subcommand is currently a stub).
58
+
59
+ ## Documentation
60
+
61
+ What each document covers, by section:
62
+
63
+ ### Setup - one-time AWS provisioning ([SETUP.md](./SETUP.md))
64
+
65
+ Provision the supporting infrastructure once per AWS account and environment.
66
+
67
+ - [Architecture](./SETUP.md#architecture) - artifact buckets, key-level immutability, the content-hash model
68
+ - [Terraform - per-account bootstrap](./SETUP.md#terraform---per-account-bootstrap) - the buckets, the GitHub OIDC builder role, and outputs
69
+ - [GitHub OIDC provider](./SETUP.md#github-oidc-provider) - declaring the shared per-account OIDC provider
70
+ - [Next steps](./SETUP.md#next-steps) - where to go after provisioning
71
+
72
+ ### Usage - day-to-day ([USAGE.md](./USAGE.md))
73
+
74
+ Using `repro-lambda` once the infrastructure exists.
75
+
76
+ - [Source-repo CI workflow](./USAGE.md#source-repo-ci-workflow) - wiring the reusable build workflow into CI/CD
77
+ - [Per-Lambda manifest](./USAGE.md#per-lambda-manifest) - the `lambdas.toml` fields
78
+ - [Per-lambda builder overrides](./USAGE.md#per-lambda-builder-overrides) - per-lambda base image and file filters
79
+ - [Declarative sources](./USAGE.md#declarative-sources---lambdasource) - pinned external artifacts via `[[lambda.source]]`
80
+ - [Terraform consumer (`s3_existing_package`)](./USAGE.md#terraform-consumer---s3_existing_package) - wiring `terraform-aws-modules/lambda/aws` to the built artifact
81
+ - [Smoke test](./USAGE.md#smoke-test) - first-build verification and the clean migration plan diff
82
+ - [Troubleshooting](./USAGE.md#troubleshooting) - upload 403s, `PreconditionFailed`, noisy plans
83
+ - [Node.js (npm) Lambdas](./USAGE.md#nodejs-npm-lambdas) - npm packaging support
84
+ - [Manifest fields for npm specs](./USAGE.md#manifest-fields-for-npm-specs)
85
+ - [Lockfile regeneration](./USAGE.md#lockfile-regeneration)
86
+ - [Lambda@Edge example](./USAGE.md#lambdaedge-example) - `us-east-1` artifacts for CloudFront
87
+ - [Caveats](./USAGE.md#caveats) - npm workspaces, native deps, symlinks
88
+
89
+ ### Example - runnable ([examples/complete/](./examples/complete/))
90
+
91
+ A self-contained consumer setup: manifest, catalog, and Terraform using
92
+ `terraform-aws-modules/lambda/aws`.
93
+
94
+ - [What this example shows](./examples/complete/README.md#what-this-example-shows) - files and layout
95
+ - [The build-outside-Terraform flow](./examples/complete/README.md#the-build-outside-terraform-flow) - build, inspect the catalog, apply
96
+ - [Expected plan diff](./examples/complete/README.md#expected-plan-diff) - the `s3_key`-only diff to expect
97
+
98
+ ## License
99
+
100
+ MIT - see [LICENSE](./LICENSE).
101
+
102
+ ## Author
103
+
104
+ Anton Babenko - [@antonbabenko](https://twitter.com/antonbabenko) on Twitter, [antonbabenko](https://www.linkedin.com/in/antonbabenko/) on LinkedIn.
@@ -0,0 +1,71 @@
1
+ # repro-lambda
2
+
3
+ Build reproducible AWS Lambda packages outside Terraform, optimized for
4
+ [terraform-aws-lambda](https://registry.terraform.io/modules/terraform-aws-modules/lambda/aws/latest) by [serverless.tf](https://serverless.tf/).
5
+
6
+ Produces byte-identical zip files across local dev (macOS) and CI (Linux),
7
+ uploads to S3 by content-hash key, and lets Terraform read `s3_existing_package`
8
+ instead of building during `terraform plan`/`apply`.
9
+
10
+ ## Install
11
+
12
+ pip install repro-lambda
13
+
14
+ ## Quick start
15
+
16
+ repro-lambda lock # regenerate per-arch requirement + source locks
17
+ repro-lambda build --bucket <bucket> # build all lambdas in lambdas.toml, upload to S3
18
+ repro-lambda build --verify --dry-run # two-pass byte-reproducibility check (no upload)
19
+ repro-lambda promote \
20
+ --dev-bucket <dev> --prod-bucket <prod> # copy dev -> prod by content sha (no rebuild)
21
+
22
+ `--bucket` (or `REPRO_LAMBDA_BUCKET`) is required for a real build; add
23
+ `--dry-run` to build without uploading. There is no `init` command yet (the
24
+ subcommand is currently a stub).
25
+
26
+ ## Documentation
27
+
28
+ What each document covers, by section:
29
+
30
+ ### Setup - one-time AWS provisioning ([SETUP.md](./SETUP.md))
31
+
32
+ Provision the supporting infrastructure once per AWS account and environment.
33
+
34
+ - [Architecture](./SETUP.md#architecture) - artifact buckets, key-level immutability, the content-hash model
35
+ - [Terraform - per-account bootstrap](./SETUP.md#terraform---per-account-bootstrap) - the buckets, the GitHub OIDC builder role, and outputs
36
+ - [GitHub OIDC provider](./SETUP.md#github-oidc-provider) - declaring the shared per-account OIDC provider
37
+ - [Next steps](./SETUP.md#next-steps) - where to go after provisioning
38
+
39
+ ### Usage - day-to-day ([USAGE.md](./USAGE.md))
40
+
41
+ Using `repro-lambda` once the infrastructure exists.
42
+
43
+ - [Source-repo CI workflow](./USAGE.md#source-repo-ci-workflow) - wiring the reusable build workflow into CI/CD
44
+ - [Per-Lambda manifest](./USAGE.md#per-lambda-manifest) - the `lambdas.toml` fields
45
+ - [Per-lambda builder overrides](./USAGE.md#per-lambda-builder-overrides) - per-lambda base image and file filters
46
+ - [Declarative sources](./USAGE.md#declarative-sources---lambdasource) - pinned external artifacts via `[[lambda.source]]`
47
+ - [Terraform consumer (`s3_existing_package`)](./USAGE.md#terraform-consumer---s3_existing_package) - wiring `terraform-aws-modules/lambda/aws` to the built artifact
48
+ - [Smoke test](./USAGE.md#smoke-test) - first-build verification and the clean migration plan diff
49
+ - [Troubleshooting](./USAGE.md#troubleshooting) - upload 403s, `PreconditionFailed`, noisy plans
50
+ - [Node.js (npm) Lambdas](./USAGE.md#nodejs-npm-lambdas) - npm packaging support
51
+ - [Manifest fields for npm specs](./USAGE.md#manifest-fields-for-npm-specs)
52
+ - [Lockfile regeneration](./USAGE.md#lockfile-regeneration)
53
+ - [Lambda@Edge example](./USAGE.md#lambdaedge-example) - `us-east-1` artifacts for CloudFront
54
+ - [Caveats](./USAGE.md#caveats) - npm workspaces, native deps, symlinks
55
+
56
+ ### Example - runnable ([examples/complete/](./examples/complete/))
57
+
58
+ A self-contained consumer setup: manifest, catalog, and Terraform using
59
+ `terraform-aws-modules/lambda/aws`.
60
+
61
+ - [What this example shows](./examples/complete/README.md#what-this-example-shows) - files and layout
62
+ - [The build-outside-Terraform flow](./examples/complete/README.md#the-build-outside-terraform-flow) - build, inspect the catalog, apply
63
+ - [Expected plan diff](./examples/complete/README.md#expected-plan-diff) - the `s3_key`-only diff to expect
64
+
65
+ ## License
66
+
67
+ MIT - see [LICENSE](./LICENSE).
68
+
69
+ ## Author
70
+
71
+ Anton Babenko - [@antonbabenko](https://twitter.com/antonbabenko) on Twitter, [antonbabenko](https://www.linkedin.com/in/antonbabenko/) on LinkedIn.
@@ -0,0 +1,262 @@
1
+ # Setting up `repro-lambda` for your project
2
+
3
+ `repro-lambda` builds and uploads Lambda artifacts to S3 outside of Terraform.
4
+ This guide covers the one-time job: provisioning the supporting AWS
5
+ infrastructure (the artifact buckets plus the GitHub OIDC builder role) once per
6
+ AWS account and region, so that Terraform can later read those artifacts via
7
+ `s3_existing_package`.
8
+
9
+ This is a copy-paste-able reference. Adapt the names and account IDs to your
10
+ environment. Once the infrastructure exists, see [USAGE.md](./USAGE.md) for
11
+ day-to-day use (the CI workflow, the per-Lambda manifest, and consuming
12
+ artifacts from Terraform).
13
+
14
+ ## Architecture
15
+
16
+ For each environment (e.g. `dev`, `prod`) you typically need:
17
+
18
+ ```
19
+ ${env}-my-lambda-artifacts S3 bucket, region = eu-west-1 (or your primary region)
20
+ ${env}-my-lambda-artifacts-us-east-1 S3 bucket, region = us-east-1 (for Lambda@Edge - optional)
21
+
22
+ gha-lambda-builder-${env} IAM role, assumed via GitHub OIDC by source-repo CI to upload
23
+ ```
24
+
25
+ Both buckets enforce **key-level immutability** via bucket policy:
26
+
27
+ - `s3:PutObject` denied unless `If-None-Match=*` is set (writes are first-write-wins)
28
+ - `s3:DeleteObject` and `s3:DeleteObjectVersion` denied
29
+ - The Lambda service is allowed `s3:GetObject` so functions can load their zip
30
+
31
+ Once an artifact with a content-hash key (`lambdas/<name>/<sha256>.zip`) is
32
+ uploaded, the key is permanently bound to those bytes. `repro-lambda` treats
33
+ HTTP 412 PreconditionFailed on a duplicate upload as success.
34
+
35
+ ## Terraform - per-account bootstrap
36
+
37
+ The Terraform below assumes you already have a configured AWS provider in the
38
+ target account. Drop it into your bootstrap directory (or anywhere you provision
39
+ account-wide infrastructure that has no dependencies on other Terraform state).
40
+
41
+ ```hcl
42
+ # us-east-1 is needed only for Lambda@Edge artifacts. Skip if you don't use L@E.
43
+ provider "aws" {
44
+ alias = "us_east_1"
45
+ region = "us-east-1"
46
+ }
47
+
48
+ locals {
49
+ env = "dev" # change per environment
50
+ lambda_artifacts_bucket_primary = "${local.env}-my-lambda-artifacts"
51
+ lambda_artifacts_bucket_us_east_1 = "${local.env}-my-lambda-artifacts-us-east-1"
52
+
53
+ # GitHub OIDC subjects allowed to assume the builder role.
54
+ # Pattern: repo:<owner>/<repo>:* - narrow to specific refs in production.
55
+ lambda_builder_oidc_subjects = [
56
+ "repo:my-org/my-source-repo:*",
57
+ ]
58
+ }
59
+
60
+ # Immutability policy applied to every artifact bucket.
61
+ data "aws_iam_policy_document" "lambda_artifacts_immutability" {
62
+ for_each = toset([
63
+ local.lambda_artifacts_bucket_primary,
64
+ local.lambda_artifacts_bucket_us_east_1,
65
+ ])
66
+
67
+ statement {
68
+ sid = "DenyOverwrites"
69
+ effect = "Deny"
70
+
71
+ principals {
72
+ type = "*"
73
+ identifiers = ["*"]
74
+ }
75
+
76
+ actions = ["s3:PutObject"]
77
+ resources = ["arn:aws:s3:::${each.value}/*"]
78
+
79
+ condition {
80
+ test = "StringNotEquals"
81
+ variable = "s3:If-None-Match"
82
+ values = ["*"]
83
+ }
84
+ }
85
+
86
+ statement {
87
+ sid = "DenyDelete"
88
+ effect = "Deny"
89
+
90
+ principals {
91
+ type = "*"
92
+ identifiers = ["*"]
93
+ }
94
+
95
+ actions = [
96
+ "s3:DeleteObject",
97
+ "s3:DeleteObjectVersion",
98
+ ]
99
+ resources = ["arn:aws:s3:::${each.value}/*"]
100
+ }
101
+
102
+ statement {
103
+ sid = "AllowLambdaServiceRead"
104
+ effect = "Allow"
105
+
106
+ principals {
107
+ type = "Service"
108
+ identifiers = ["lambda.amazonaws.com"]
109
+ }
110
+
111
+ actions = ["s3:GetObject"]
112
+ resources = ["arn:aws:s3:::${each.value}/*"]
113
+
114
+ condition {
115
+ test = "StringEquals"
116
+ variable = "aws:SourceAccount"
117
+ values = [data.aws_caller_identity.current.account_id]
118
+ }
119
+ }
120
+ }
121
+
122
+ data "aws_caller_identity" "current" {}
123
+
124
+ # Primary-region bucket
125
+ module "lambda_artifacts_bucket_primary" {
126
+ source = "terraform-aws-modules/s3-bucket/aws"
127
+ version = "~> 4.0"
128
+
129
+ bucket = local.lambda_artifacts_bucket_primary
130
+
131
+ versioning = {
132
+ enabled = false # sha-as-key IS the versioning
133
+ }
134
+
135
+ server_side_encryption_configuration = {
136
+ rule = {
137
+ apply_server_side_encryption_by_default = {
138
+ sse_algorithm = "AES256"
139
+ }
140
+ }
141
+ }
142
+
143
+ attach_policy = true
144
+ policy = data.aws_iam_policy_document.lambda_artifacts_immutability[local.lambda_artifacts_bucket_primary].json
145
+
146
+ block_public_acls = true
147
+ block_public_policy = true
148
+ ignore_public_acls = true
149
+ restrict_public_buckets = true
150
+
151
+ force_destroy = false
152
+ }
153
+
154
+ # us-east-1 bucket - only needed for Lambda@Edge. Remove if not using L@E.
155
+ module "lambda_artifacts_bucket_us_east_1" {
156
+ source = "terraform-aws-modules/s3-bucket/aws"
157
+ version = "~> 4.0"
158
+
159
+ providers = {
160
+ aws = aws.us_east_1
161
+ }
162
+
163
+ bucket = local.lambda_artifacts_bucket_us_east_1
164
+
165
+ versioning = {
166
+ enabled = false
167
+ }
168
+
169
+ server_side_encryption_configuration = {
170
+ rule = {
171
+ apply_server_side_encryption_by_default = {
172
+ sse_algorithm = "AES256"
173
+ }
174
+ }
175
+ }
176
+
177
+ attach_policy = true
178
+ policy = data.aws_iam_policy_document.lambda_artifacts_immutability[local.lambda_artifacts_bucket_us_east_1].json
179
+
180
+ block_public_acls = true
181
+ block_public_policy = true
182
+ ignore_public_acls = true
183
+ restrict_public_buckets = true
184
+
185
+ force_destroy = false
186
+ }
187
+
188
+ # OIDC role assumed by GitHub Actions in source repos to upload artifacts.
189
+ module "gha_lambda_builder_role" {
190
+ source = "terraform-aws-modules/iam/aws//modules/iam-role"
191
+ version = "~> 6.0"
192
+
193
+ name = "gha-lambda-builder-${local.env}"
194
+
195
+ enable_github_oidc = true
196
+ oidc_wildcard_subjects = local.lambda_builder_oidc_subjects
197
+
198
+ create_inline_policy = true
199
+ inline_policy_permissions = {
200
+ WriteLambdaArtifacts = {
201
+ actions = [
202
+ "s3:PutObject",
203
+ "s3:GetObject",
204
+ "s3:HeadObject",
205
+ "s3:ListBucket",
206
+ ]
207
+ resources = [
208
+ module.lambda_artifacts_bucket_primary.s3_bucket_arn,
209
+ "${module.lambda_artifacts_bucket_primary.s3_bucket_arn}/*",
210
+ module.lambda_artifacts_bucket_us_east_1.s3_bucket_arn,
211
+ "${module.lambda_artifacts_bucket_us_east_1.s3_bucket_arn}/*",
212
+ ]
213
+ }
214
+ }
215
+ }
216
+
217
+ output "lambda_artifacts_bucket_primary_arn" {
218
+ value = module.lambda_artifacts_bucket_primary.s3_bucket_arn
219
+ }
220
+
221
+ output "lambda_artifacts_bucket_us_east_1_arn" {
222
+ value = module.lambda_artifacts_bucket_us_east_1.s3_bucket_arn
223
+ }
224
+
225
+ output "gha_lambda_builder_role_arn" {
226
+ value = module.gha_lambda_builder_role.arn
227
+ }
228
+ ```
229
+
230
+ > Note: if you only use a single region (no Lambda@Edge), remove every
231
+ > us-east-1 reference together, or `terraform plan` fails on dangling
232
+ > references: the `lambda_artifacts_bucket_us_east_1` module, the `us_east_1`
233
+ > provider alias, the `local.lambda_artifacts_bucket_us_east_1` entry in the
234
+ > immutability policy `for_each`, and the two
235
+ > `module.lambda_artifacts_bucket_us_east_1` ARNs in the OIDC role's
236
+ > `WriteLambdaArtifacts` resources.
237
+
238
+ Apply the Terraform once per environment. The bucket names and role ARN are
239
+ referenced by your source repos' CI workflows in [USAGE.md](./USAGE.md).
240
+
241
+ ## GitHub OIDC provider
242
+
243
+ If your account doesn't already have the GitHub Actions OIDC provider, the
244
+ `terraform-aws-modules/iam` collection includes a module for it:
245
+
246
+ ```hcl
247
+ module "iam_github_oidc_provider" {
248
+ source = "terraform-aws-modules/iam/aws//modules/iam-oidc-provider"
249
+ version = "~> 6.0"
250
+ }
251
+ ```
252
+
253
+ The provider is shared per AWS account - declare it once, not per environment.
254
+
255
+ ## Next steps
256
+
257
+ The AWS infrastructure is now provisioned. To use it day-to-day:
258
+
259
+ - [USAGE.md](./USAGE.md) - the source-repo CI workflow, the per-Lambda manifest,
260
+ declarative sources, consuming artifacts from Terraform, and troubleshooting.
261
+ - [examples/complete/](./examples/complete/) - a runnable example that wires the
262
+ pieces together.