repro-lambda 0.2.1__tar.gz → 0.2.3__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 (53) hide show
  1. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/.github/workflows/build.yml +24 -11
  2. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/CHANGELOG.md +21 -0
  3. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/PKG-INFO +1 -1
  4. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/pyproject.toml +1 -1
  5. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/src/repro_lambda/__init__.py +1 -1
  6. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/src/repro_lambda/cli.py +12 -0
  7. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/src/repro_lambda/docker_runner.py +1 -1
  8. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_build_integration.py +7 -7
  9. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_build_nodejs.py +5 -5
  10. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_cli_build.py +22 -2
  11. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_cli_smoke.py +2 -1
  12. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_s3_uploader.py +2 -2
  13. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/uv.lock +1 -1
  14. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/.github/workflows/ci.yml +0 -0
  15. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/.github/workflows/publish.yml +0 -0
  16. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/.gitignore +0 -0
  17. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/.pre-commit-config.yaml +0 -0
  18. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/LICENSE +0 -0
  19. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/README.md +0 -0
  20. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/SETUP.md +0 -0
  21. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/src/repro_lambda/__main__.py +0 -0
  22. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/src/repro_lambda/build.py +0 -0
  23. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/src/repro_lambda/catalog.py +0 -0
  24. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/src/repro_lambda/git_guard.py +0 -0
  25. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/src/repro_lambda/hasher.py +0 -0
  26. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/src/repro_lambda/manifest.py +0 -0
  27. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/src/repro_lambda/s3_uploader.py +0 -0
  28. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/src/repro_lambda/source_stager.py +0 -0
  29. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/src/repro_lambda/verify.py +0 -0
  30. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/src/repro_lambda/zip_packager.py +0 -0
  31. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/__init__.py +0 -0
  32. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/conftest.py +0 -0
  33. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/fixtures/sample_nodejs_lambda/handler/index.js +0 -0
  34. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/fixtures/sample_nodejs_lambda/handler/package-lock.json +0 -0
  35. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/fixtures/sample_nodejs_lambda/handler/package.json +0 -0
  36. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/fixtures/sample_nodejs_lambda/lambdas.toml +0 -0
  37. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/fixtures/sample_python_lambda/handler/app.py +0 -0
  38. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/fixtures/sample_python_lambda/handler/requirements.arm64.lock +0 -0
  39. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/fixtures/sample_python_lambda/handler/requirements.in +0 -0
  40. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/fixtures/sample_python_lambda/lambdas.toml +0 -0
  41. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_catalog.py +0 -0
  42. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_cli_lock.py +0 -0
  43. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_docker_runner.py +0 -0
  44. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_docker_runner_nodejs.py +0 -0
  45. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_e2e_nodejs_lambda.py +0 -0
  46. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_e2e_python_lambda.py +0 -0
  47. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_git_guard.py +0 -0
  48. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_hasher.py +0 -0
  49. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_manifest.py +0 -0
  50. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_python_byte_compat_regression.py +0 -0
  51. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_source_stager.py +0 -0
  52. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_verify.py +0 -0
  53. {repro_lambda-0.2.1 → repro_lambda-0.2.3}/tests/test_zip_packager.py +0 -0
@@ -9,13 +9,26 @@ on:
9
9
  description: Path to lambdas.toml in the caller repo.
10
10
  repro_lambda_version:
11
11
  type: string
12
- default: "0.2.1"
12
+ default: "0.2.3"
13
13
  description: Pinned repro-lambda PyPI version.
14
- secrets:
15
14
  aws-dev-role-arn:
15
+ type: string
16
16
  required: true
17
+ description: ARN of the dev OIDC role assumed for artifact upload. Not a secret (the security boundary is the OIDC trust policy + bucket immutability), so callers pass it as a plain input.
17
18
  aws-prod-role-arn:
19
+ type: string
20
+ required: false
21
+ default: ""
22
+ description: ARN of the prod OIDC role (master push only). Empty string disables the prod upload steps.
23
+ dev-bucket:
24
+ type: string
25
+ required: true
26
+ description: S3 bucket name for dev Lambda artifacts (set as REPRO_LAMBDA_BUCKET on the dev upload). Caller-supplied so the reusable workflow stays consumer-agnostic.
27
+ prod-bucket:
28
+ type: string
18
29
  required: false
30
+ default: ""
31
+ description: S3 bucket name for prod Lambda artifacts (master push only).
19
32
 
20
33
  jobs:
21
34
  detect-arches:
@@ -58,30 +71,30 @@ jobs:
58
71
  - name: Configure AWS credentials (dev)
59
72
  uses: aws-actions/configure-aws-credentials@v4
60
73
  with:
61
- role-to-assume: ${{ secrets.aws-dev-role-arn }}
74
+ role-to-assume: ${{ inputs.aws-dev-role-arn }}
62
75
  aws-region: eu-west-1
63
76
 
64
77
  - name: Build (dev bucket)
65
78
  env:
66
- REPRO_LAMBDA_BUCKET: dev-ctf-lambda-artifacts
67
- run: uvx --from "repro-lambda==${{ inputs.repro_lambda_version }}" repro-lambda build --manifest "${{ inputs.manifest_path }}"
79
+ REPRO_LAMBDA_BUCKET: ${{ inputs.dev-bucket }}
80
+ run: uvx --from "repro-lambda==${{ inputs.repro_lambda_version }}" repro-lambda build --manifest "${{ inputs.manifest_path }}" --arch "${{ matrix.arch }}"
68
81
 
69
82
  - name: Verify reproducible (PR only)
70
83
  if: github.event_name == 'pull_request'
71
- run: uvx --from "repro-lambda==${{ inputs.repro_lambda_version }}" repro-lambda build --manifest "${{ inputs.manifest_path }}" --verify --dry-run
84
+ run: uvx --from "repro-lambda==${{ inputs.repro_lambda_version }}" repro-lambda build --manifest "${{ inputs.manifest_path }}" --arch "${{ matrix.arch }}" --verify --dry-run
72
85
 
73
86
  - name: Configure AWS credentials (prod)
74
- if: github.ref == 'refs/heads/master' && secrets.aws-prod-role-arn != ''
87
+ if: github.ref == 'refs/heads/master' && inputs.aws-prod-role-arn != ''
75
88
  uses: aws-actions/configure-aws-credentials@v4
76
89
  with:
77
- role-to-assume: ${{ secrets.aws-prod-role-arn }}
90
+ role-to-assume: ${{ inputs.aws-prod-role-arn }}
78
91
  aws-region: eu-west-1
79
92
 
80
93
  - name: Build (prod bucket)
81
- if: github.ref == 'refs/heads/master' && secrets.aws-prod-role-arn != ''
94
+ if: github.ref == 'refs/heads/master' && inputs.aws-prod-role-arn != ''
82
95
  env:
83
- REPRO_LAMBDA_BUCKET: prod-ctf-lambda-artifacts
84
- run: uvx --from "repro-lambda==${{ inputs.repro_lambda_version }}" repro-lambda build --manifest "${{ inputs.manifest_path }}"
96
+ REPRO_LAMBDA_BUCKET: ${{ inputs.prod-bucket }}
97
+ run: uvx --from "repro-lambda==${{ inputs.repro_lambda_version }}" repro-lambda build --manifest "${{ inputs.manifest_path }}" --arch "${{ matrix.arch }}"
85
98
 
86
99
  - name: Commit catalog drift (master only, dev bot)
87
100
  if: github.ref == 'refs/heads/master'
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.2.3 - 2026-06-20
4
+
5
+ ### Fixed
6
+ - Python build container staged dependencies under `/build` (root-owned), which failed with `mkdir: Permission denied` when the container runs as a non-root `--user` (e.g. GitHub-hosted runners, uid 1001). Staging moved to `/tmp/build` (world-writable).
7
+
8
+ ### Added
9
+ - `build --arch <arm64|x86_64>` filters the manifest to lambdas of that arch, so a per-arch CI matrix builds each arch natively on its own runner. This avoids cross-arch `docker run` (which fails without emulation, and emulated builds would break byte-reproducibility). The reusable `build.yml` passes `--arch ${{ matrix.arch }}`.
10
+
11
+ ## v0.2.2 - 2026-06-20
12
+
13
+ ### Changed
14
+ - Reusable workflow `build.yml` now takes `aws-dev-role-arn` and `aws-prod-role-arn` as **inputs** instead of **secrets**. A role ARN is not sensitive (the security boundary is the OIDC trust policy plus the key-level bucket immutability policy), and typing it as a secret blocked callers from passing a derivable literal ARN, since secret inputs reject plain literal values. No package code change: PyPI 0.2.2 is behaviorally identical to 0.2.1.
15
+ - Artifact bucket names are now `dev-bucket` / `prod-bucket` **inputs** instead of hardcoded values, so the reusable workflow is consumer-agnostic and carries no environment-specific bucket names.
16
+
17
+ ### Consumer migration
18
+ - Bump the workflow ref to `uses: antonbabenko/repro-lambda/.github/workflows/build.yml@v0.2.2` and move `aws-dev-role-arn` / `aws-prod-role-arn` to the `with:` block, adding `dev-bucket` (and `prod-bucket` if you upload to prod). They are inputs now, so plain literals are valid:
19
+
20
+ with:
21
+ aws-dev-role-arn: arn:aws:iam::<account>:role/<role>
22
+ dev-bucket: <env>-my-lambda-artifacts
23
+
3
24
  ## v0.2.1 - 2026-05-27
4
25
 
5
26
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: repro-lambda
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Build reproducible AWS Lambda packages outside Terraform, optimized for terraform-aws-lambda by serverless.tf.
5
5
  Project-URL: Homepage, https://github.com/antonbabenko/repro-lambda
6
6
  Project-URL: Repository, https://github.com/antonbabenko/repro-lambda
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "repro-lambda"
3
- version = "0.2.1"
3
+ version = "0.2.3"
4
4
  description = "Build reproducible AWS Lambda packages outside Terraform, optimized for terraform-aws-lambda by serverless.tf."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """repro-lambda — reproducible AWS Lambda packaging outside Terraform."""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.2.3"
@@ -63,6 +63,12 @@ def build(
63
63
  verify: Annotated[bool, typer.Option("--verify")] = False,
64
64
  dry_run: Annotated[bool, typer.Option("--dry-run")] = False,
65
65
  allow_dirty: Annotated[bool, typer.Option("--allow-dirty")] = False,
66
+ arch: Annotated[
67
+ str,
68
+ typer.Option(
69
+ "--arch", help="Only build lambdas with this arch (e.g. arm64, x86_64). Empty = all."
70
+ ),
71
+ ] = "",
66
72
  ) -> None:
67
73
  """Build one lambda (or all) per manifest and upload to S3."""
68
74
  import json
@@ -84,6 +90,12 @@ def build(
84
90
  typer.echo(f"no lambda named {target!r} in {manifest}", err=True)
85
91
  raise typer.Exit(2)
86
92
 
93
+ if arch:
94
+ selected = [s for s in selected if s.arch == arch]
95
+ if not selected:
96
+ typer.echo(f"no lambdas with arch {arch!r} in {manifest}; nothing to build")
97
+ raise typer.Exit(0)
98
+
87
99
  if not dry_run and not bucket:
88
100
  typer.echo(
89
101
  "--bucket or REPRO_LAMBDA_BUCKET env var is required for non-dry-run",
@@ -37,7 +37,7 @@ class DockerRunError(RuntimeError):
37
37
 
38
38
  _PYTHON_INSTALL_SCRIPT = r"""
39
39
  set -euxo pipefail
40
- PKG=/build/pkg
40
+ PKG=/tmp/build/pkg
41
41
  mkdir -p "$PKG"
42
42
  cp -R /src/source/. "$PKG/"
43
43
 
@@ -61,12 +61,12 @@ def test_build_one_cache_hit_skips_docker_and_returns_existing_sha(
61
61
  with mock_aws():
62
62
  s3 = boto3.client("s3", region_name="eu-west-1")
63
63
  s3.create_bucket(
64
- Bucket="dev-ctf-lambda-artifacts",
64
+ Bucket="dev-test-lambda-artifacts",
65
65
  CreateBucketConfiguration={"LocationConstraint": "eu-west-1"},
66
66
  )
67
67
  sha = compute_sha_for(repo_root=git_repo_with_sample, spec=spec, builder=builder)
68
68
  s3.put_object(
69
- Bucket="dev-ctf-lambda-artifacts",
69
+ Bucket="dev-test-lambda-artifacts",
70
70
  Key=f"lambdas/app/{sha}.zip",
71
71
  Body=b"existing",
72
72
  )
@@ -75,7 +75,7 @@ def test_build_one_cache_hit_skips_docker_and_returns_existing_sha(
75
75
  repo_root=git_repo_with_sample,
76
76
  spec=spec,
77
77
  builder=builder,
78
- bucket="dev-ctf-lambda-artifacts",
78
+ bucket="dev-test-lambda-artifacts",
79
79
  catalog=catalog,
80
80
  source_commit="deadbeef",
81
81
  )
@@ -99,7 +99,7 @@ def test_build_one_cache_miss_runs_docker_uploads_and_records(git_repo_with_samp
99
99
  with mock_aws():
100
100
  s3 = boto3.client("s3", region_name="eu-west-1")
101
101
  s3.create_bucket(
102
- Bucket="dev-ctf-lambda-artifacts",
102
+ Bucket="dev-test-lambda-artifacts",
103
103
  CreateBucketConfiguration={"LocationConstraint": "eu-west-1"},
104
104
  )
105
105
 
@@ -107,14 +107,14 @@ def test_build_one_cache_miss_runs_docker_uploads_and_records(git_repo_with_samp
107
107
  repo_root=git_repo_with_sample,
108
108
  spec=spec,
109
109
  builder=builder,
110
- bucket="dev-ctf-lambda-artifacts",
110
+ bucket="dev-test-lambda-artifacts",
111
111
  catalog=catalog,
112
112
  source_commit="deadbeef",
113
113
  )
114
114
 
115
115
  assert outcome.outcome == BuildResult.BUILT_AND_UPLOADED
116
116
  s3.head_object(
117
- Bucket="dev-ctf-lambda-artifacts",
117
+ Bucket="dev-test-lambda-artifacts",
118
118
  Key=f"lambdas/app/{outcome.sha256}.zip",
119
119
  )
120
120
 
@@ -134,7 +134,7 @@ def test_build_one_dry_run_computes_hash_but_skips_upload(git_repo_with_sample:
134
134
  repo_root=git_repo_with_sample,
135
135
  spec=spec,
136
136
  builder=builder,
137
- bucket="dev-ctf-lambda-artifacts",
137
+ bucket="dev-test-lambda-artifacts",
138
138
  catalog=catalog,
139
139
  source_commit="deadbeef",
140
140
  dry_run=True,
@@ -60,12 +60,12 @@ def test_build_one_nodejs_routes_to_build_nodejs_lambda(git_repo_with_nodejs_sam
60
60
 
61
61
  with mock_aws():
62
62
  s3 = boto3.client("s3", region_name="us-east-1")
63
- s3.create_bucket(Bucket="dev-ctf-lambda-artifacts-us-east-1")
63
+ s3.create_bucket(Bucket="dev-test-lambda-artifacts-us-east-1")
64
64
  outcome = build_one(
65
65
  repo_root=git_repo_with_nodejs_sample,
66
66
  spec=_nodejs_spec(),
67
67
  builder=_nodejs_builder(),
68
- bucket="dev-ctf-lambda-artifacts",
68
+ bucket="dev-test-lambda-artifacts",
69
69
  catalog=Catalog(lambdas={}),
70
70
  source_commit="deadbeef",
71
71
  )
@@ -84,16 +84,16 @@ def test_build_one_lambda_at_edge_uses_us_east_1_bucket(git_repo_with_nodejs_sam
84
84
  )
85
85
  with mock_aws():
86
86
  s3 = boto3.client("s3", region_name="us-east-1")
87
- s3.create_bucket(Bucket="dev-ctf-lambda-artifacts-us-east-1")
87
+ s3.create_bucket(Bucket="dev-test-lambda-artifacts-us-east-1")
88
88
  outcome = build_one(
89
89
  repo_root=git_repo_with_nodejs_sample,
90
90
  spec=_nodejs_spec(),
91
91
  builder=_nodejs_builder(),
92
- bucket="dev-ctf-lambda-artifacts",
92
+ bucket="dev-test-lambda-artifacts",
93
93
  catalog=Catalog(lambdas={}),
94
94
  source_commit="deadbeef",
95
95
  )
96
96
  s3.head_object(
97
- Bucket="dev-ctf-lambda-artifacts-us-east-1",
97
+ Bucket="dev-test-lambda-artifacts-us-east-1",
98
98
  Key=f"lambdas/edge/{outcome.sha256}.zip",
99
99
  )
@@ -70,13 +70,13 @@ def test_cli_build_emits_catalog_on_success(consumer_repo: Path, mocker):
70
70
  )
71
71
  with mock_aws():
72
72
  boto3.client("s3", region_name="eu-west-1").create_bucket(
73
- Bucket="dev-ctf-lambda-artifacts",
73
+ Bucket="dev-test-lambda-artifacts",
74
74
  CreateBucketConfiguration={"LocationConstraint": "eu-west-1"},
75
75
  )
76
76
  result = runner.invoke(
77
77
  app,
78
78
  ["build", "app", "--manifest", str(consumer_repo / "lambdas.toml")],
79
- env={"REPRO_LAMBDA_BUCKET": "dev-ctf-lambda-artifacts"},
79
+ env={"REPRO_LAMBDA_BUCKET": "dev-test-lambda-artifacts"},
80
80
  )
81
81
  assert result.exit_code == 0, result.stdout
82
82
  catalog_path = consumer_repo / "builds" / "catalog.json"
@@ -109,3 +109,23 @@ def test_cli_build_allow_dirty_bypasses_guard(consumer_repo: Path, mocker):
109
109
  ],
110
110
  )
111
111
  assert result.exit_code == 0, result.stdout
112
+
113
+
114
+ def test_cli_build_arch_filter_skips_nonmatching(consumer_repo: Path, mocker):
115
+ mock_docker = mocker.patch("repro_lambda.build.build_python_lambda")
116
+ result = runner.invoke(
117
+ app,
118
+ [
119
+ "build",
120
+ "app",
121
+ "--manifest",
122
+ str(consumer_repo / "lambdas.toml"),
123
+ "--arch",
124
+ "x86_64",
125
+ "--dry-run",
126
+ ],
127
+ )
128
+ # consumer_repo's only lambda 'app' is arm64; an x86_64 filter -> nothing to build.
129
+ assert result.exit_code == 0, result.stdout
130
+ assert "nothing to build" in result.stdout
131
+ mock_docker.assert_not_called()
@@ -1,5 +1,6 @@
1
1
  from typer.testing import CliRunner
2
2
 
3
+ from repro_lambda import __version__
3
4
  from repro_lambda.cli import app
4
5
 
5
6
  runner = CliRunner()
@@ -8,7 +9,7 @@ runner = CliRunner()
8
9
  def test_cli_version_shows_package_version():
9
10
  result = runner.invoke(app, ["--version"])
10
11
  assert result.exit_code == 0
11
- assert "0.2.1" in result.stdout
12
+ assert __version__ in result.stdout
12
13
 
13
14
 
14
15
  def test_cli_build_subcommand_exists():
@@ -12,10 +12,10 @@ def bucket():
12
12
  with mock_aws():
13
13
  s3 = boto3.client("s3", region_name="eu-west-1")
14
14
  s3.create_bucket(
15
- Bucket="dev-ctf-lambda-artifacts",
15
+ Bucket="dev-test-lambda-artifacts",
16
16
  CreateBucketConfiguration={"LocationConstraint": "eu-west-1"},
17
17
  )
18
- yield "dev-ctf-lambda-artifacts"
18
+ yield "dev-test-lambda-artifacts"
19
19
 
20
20
 
21
21
  def _make_zip(tmp_path: Path, content: bytes = b"PK\x05\x06" + b"\x00" * 18) -> Path:
@@ -646,7 +646,7 @@ wheels = [
646
646
 
647
647
  [[package]]
648
648
  name = "repro-lambda"
649
- version = "0.2.1"
649
+ version = "0.2.3"
650
650
  source = { editable = "." }
651
651
  dependencies = [
652
652
  { name = "boto3" },
File without changes
File without changes
File without changes
File without changes