repro-lambda 0.5.2__tar.gz → 0.6.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 (69) hide show
  1. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/.github/workflows/build.yml +93 -1
  2. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/.github/workflows/ci.yml +1 -1
  3. repro_lambda-0.6.1/.github/workflows/release.yml +85 -0
  4. repro_lambda-0.6.1/.release-please-manifest.json +3 -0
  5. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/CHANGELOG.md +7 -0
  6. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/PKG-INFO +1 -1
  7. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/pyproject.toml +1 -1
  8. repro_lambda-0.6.1/release-please-config.json +26 -0
  9. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/__init__.py +1 -1
  10. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/cli.py +33 -4
  11. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_promote.py +71 -0
  12. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/uv.lock +1 -1
  13. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/.github/workflows/move-major-tag.yml +0 -0
  14. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/.github/workflows/promote.yml +0 -0
  15. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/.github/workflows/publish.yml +0 -0
  16. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/.gitignore +0 -0
  17. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/.pre-commit-config.yaml +0 -0
  18. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/LICENSE +0 -0
  19. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/README.md +0 -0
  20. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/SETUP.md +0 -0
  21. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/__main__.py +0 -0
  22. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/build.py +0 -0
  23. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/catalog.py +0 -0
  24. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/docker_runner.py +0 -0
  25. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/git_guard.py +0 -0
  26. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/hasher.py +0 -0
  27. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/manifest.py +0 -0
  28. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/promote.py +0 -0
  29. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/s3_uploader.py +0 -0
  30. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/source_locker.py +0 -0
  31. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/source_stager.py +0 -0
  32. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/sources.py +0 -0
  33. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/verify.py +0 -0
  34. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/src/repro_lambda/zip_packager.py +0 -0
  35. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/__init__.py +0 -0
  36. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/conftest.py +0 -0
  37. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/fixtures/sample_nodejs_lambda/handler/index.js +0 -0
  38. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/fixtures/sample_nodejs_lambda/handler/package-lock.json +0 -0
  39. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/fixtures/sample_nodejs_lambda/handler/package.json +0 -0
  40. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/fixtures/sample_nodejs_lambda/lambdas.toml +0 -0
  41. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/fixtures/sample_python_lambda/handler/app.py +0 -0
  42. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/fixtures/sample_python_lambda/handler/requirements.arm64.lock +0 -0
  43. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/fixtures/sample_python_lambda/handler/requirements.in +0 -0
  44. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/fixtures/sample_python_lambda/lambdas.toml +0 -0
  45. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_build_integration.py +0 -0
  46. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_build_nodejs.py +0 -0
  47. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_catalog.py +0 -0
  48. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_cli_build.py +0 -0
  49. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_cli_lock.py +0 -0
  50. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_cli_smoke.py +0 -0
  51. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_docker_runner.py +0 -0
  52. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_docker_runner_nodejs.py +0 -0
  53. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_e2e_nodejs_lambda.py +0 -0
  54. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_e2e_python_lambda.py +0 -0
  55. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_extra_files.py +0 -0
  56. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_git_guard.py +0 -0
  57. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_hasher.py +0 -0
  58. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_manifest.py +0 -0
  59. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_per_lambda_builder.py +0 -0
  60. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_python_byte_compat_regression.py +0 -0
  61. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_s3_uploader.py +0 -0
  62. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_source_locker.py +0 -0
  63. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_source_stager.py +0 -0
  64. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_sources.py +0 -0
  65. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_sources_hash.py +0 -0
  66. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_sources_schema.py +0 -0
  67. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_verify.py +0 -0
  68. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_zip_excludes.py +0 -0
  69. {repro_lambda-0.5.2 → repro_lambda-0.6.1}/tests/test_zip_packager.py +0 -0
@@ -25,6 +25,27 @@ on:
25
25
  required: false
26
26
  default: ""
27
27
  description: S3 bucket name for prod Lambda artifacts (master push only).
28
+ promotion_repo:
29
+ type: string
30
+ required: false
31
+ default: ""
32
+ description: >-
33
+ owner/repo of the centralized promotion workflow. When set, the build Step
34
+ Summary renders a ready-to-run `gh workflow run` command per function. Generic
35
+ by design - the reusable workflow never hardcodes a promotion target; the caller
36
+ supplies it (keep this empty for public callers).
37
+ promotion_workflow:
38
+ type: string
39
+ required: false
40
+ default: promote-lambda.yml
41
+ description: Workflow file name dispatched by the promotion command in the Step Summary.
42
+ project:
43
+ type: string
44
+ required: false
45
+ default: ""
46
+ description: >-
47
+ Project key the promotion workflow expects (-f project=...). Defaults to the
48
+ caller repo name when empty.
28
49
  secrets:
29
50
  sources_token:
30
51
  required: false
@@ -92,7 +113,78 @@ jobs:
92
113
  env:
93
114
  REPRO_LAMBDA_BUCKET: ${{ inputs.dev_bucket }}
94
115
  REPRO_LAMBDA_SOURCES_TOKEN: ${{ secrets.sources_token }}
95
- run: uvx --from repro-lambda repro-lambda build --manifest "${{ inputs.manifest_path }}" --arch "${{ matrix.arch }}"
116
+ run: uvx --from repro-lambda repro-lambda build --manifest "${{ inputs.manifest_path }}" --arch "${{ matrix.arch }}" --json-out "$RUNNER_TEMP/build-summary.json"
117
+
118
+ # Render the dev build's content shas + a copy-paste promotion command per function
119
+ # into the run's Step Summary (the build JSON is otherwise buried in step logs).
120
+ # NB: the JSON lives in RUNNER_TEMP (outside the workspace), so hashFiles() can't guard
121
+ # it - run on success() and skip inside the script if the file is somehow absent.
122
+ - name: Build summary (content shas + promote commands)
123
+ if: success()
124
+ env:
125
+ SUMMARY_JSON: ${{ runner.temp }}/build-summary.json
126
+ PROMOTION_REPO: ${{ inputs.promotion_repo }}
127
+ PROMOTION_WORKFLOW: ${{ inputs.promotion_workflow }}
128
+ PROJECT: ${{ inputs.project != '' && inputs.project || github.event.repository.name }}
129
+ ARCH: ${{ matrix.arch }}
130
+ SRC_REPO: ${{ github.repository }}
131
+ SRC_COMMIT: ${{ github.sha }}
132
+ run: |
133
+ python3 - <<'PY'
134
+ import json, os, sys
135
+
136
+ path = os.environ["SUMMARY_JSON"]
137
+ if not os.path.exists(path):
138
+ print(f"::notice::no build summary JSON at {path}; skipping summary.")
139
+ sys.exit(0)
140
+ rows = json.load(open(path))
141
+ repo = os.environ["PROMOTION_REPO"]
142
+ wf = os.environ["PROMOTION_WORKFLOW"]
143
+ project = os.environ["PROJECT"]
144
+ arch = os.environ["ARCH"]
145
+ src = os.environ["SRC_REPO"]
146
+ commit = os.environ["SRC_COMMIT"]
147
+
148
+ out = [
149
+ f"## Lambda build ({arch})",
150
+ "",
151
+ f"Source: `{src}` @ `{commit[:12]}`",
152
+ "",
153
+ "| Function | Outcome | Content SHA | Dev key |",
154
+ "| --- | --- | --- | --- |",
155
+ ]
156
+ for r in rows:
157
+ out.append(
158
+ f"| `{r['logical_name']}` | {r['outcome']} | `{r['sha256']}` | `{r['bucket_key']}` |"
159
+ )
160
+ out += [
161
+ "",
162
+ "### Pipeline",
163
+ "",
164
+ "1. **Build** (this run) - artifacts uploaded to the dev bucket, keyed by content sha.",
165
+ "2. **Pin dev** - point the dev stack's lambda pin at the sha below, then apply dev and test.",
166
+ "3. **Promote to prod** - run the promote command below (copies dev -> prod by content"
167
+ " sha + opens the prod-pin PR), then apply prod.",
168
+ "",
169
+ "### Per-function",
170
+ "",
171
+ ]
172
+ for r in rows:
173
+ fn, sha = r["logical_name"], r["sha256"]
174
+ out += [f"**`{fn}`** - content sha `{sha}`", "", f"- dev pin: `{fn} = \"{sha}\"`"]
175
+ if repo:
176
+ out += [
177
+ "- promote to prod:",
178
+ "```bash",
179
+ f"gh workflow run {wf} --repo {repo} "
180
+ f"-f project={project} -f function={fn} -f sha={sha}",
181
+ "```",
182
+ ]
183
+ out += [""]
184
+
185
+ with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as f:
186
+ f.write("\n".join(out) + "\n")
187
+ PY
96
188
 
97
189
  - name: Verify reproducible (PR only)
98
190
  if: github.event_name == 'pull_request'
@@ -3,7 +3,7 @@ name: ci
3
3
  on:
4
4
  pull_request:
5
5
  push:
6
- branches: [main]
6
+ branches: [master]
7
7
 
8
8
  jobs:
9
9
  lint-and-test:
@@ -0,0 +1,85 @@
1
+ name: release
2
+
3
+ # Automated releases via release-please. On every push to master, release-please
4
+ # maintains a "release PR" that bumps the version (pyproject.toml + __init__.py) and
5
+ # the CHANGELOG. Merging that PR creates the vX.Y.Z tag + GitHub Release, then this
6
+ # same run publishes to PyPI (OIDC) and force-moves the sliding v0 major tag.
7
+ #
8
+ # Why publish here instead of relying on publish.yml's tag trigger: release-please
9
+ # creates the tag via the GitHub API using GITHUB_TOKEN, and a GITHUB_TOKEN-created
10
+ # tag does NOT trigger other workflows. So the publish + major-tag steps run as
11
+ # release_created-gated jobs in this workflow. publish.yml / move-major-tag.yml stay
12
+ # as the manual-tag fallback (a human can still `git tag vX.Y.Z && git push`).
13
+
14
+ on:
15
+ push:
16
+ branches: [master]
17
+ workflow_dispatch:
18
+
19
+ permissions: {}
20
+
21
+ jobs:
22
+ release-please:
23
+ runs-on: ubuntu-24.04
24
+ permissions:
25
+ contents: write # create the release commit, tag, and GitHub Release
26
+ pull-requests: write # open/update the release PR
27
+ issues: write # manage the autorelease:* labels
28
+ outputs:
29
+ release_created: ${{ steps.release.outputs.release_created }}
30
+ tag_name: ${{ steps.release.outputs.tag_name }}
31
+ steps:
32
+ - uses: googleapis/release-please-action@v5
33
+ id: release
34
+ with:
35
+ token: ${{ secrets.GITHUB_TOKEN }}
36
+ target-branch: master
37
+ config-file: release-please-config.json
38
+ manifest-file: .release-please-manifest.json
39
+
40
+ publish:
41
+ needs: release-please
42
+ if: ${{ needs.release-please.outputs.release_created == 'true' }}
43
+ runs-on: ubuntu-24.04
44
+ permissions:
45
+ id-token: write # PyPI OIDC Trusted Publishing
46
+ contents: read
47
+ steps:
48
+ - uses: actions/checkout@v7
49
+ - uses: astral-sh/setup-uv@v8.2.0
50
+ - run: uv sync --all-extras
51
+ - name: Verify tag matches package version
52
+ env:
53
+ TAG_NAME: ${{ needs.release-please.outputs.tag_name }}
54
+ run: |
55
+ TAG="${TAG_NAME#v}"
56
+ PKG=$(uv run python -c "import repro_lambda; print(repro_lambda.__version__)")
57
+ if [[ "$TAG" != "$PKG" ]]; then
58
+ echo "tag $TAG != package version $PKG" >&2
59
+ exit 1
60
+ fi
61
+ - name: Build sdist + wheel
62
+ run: uv build
63
+ - name: Publish to PyPI
64
+ uses: pypa/gh-action-pypi-publish@release/v1
65
+ with:
66
+ packages-dir: dist/
67
+
68
+ major-tag:
69
+ needs: release-please
70
+ if: ${{ needs.release-please.outputs.release_created == 'true' }}
71
+ runs-on: ubuntu-24.04
72
+ permissions:
73
+ contents: write
74
+ steps:
75
+ - uses: actions/checkout@v7
76
+ with:
77
+ fetch-depth: 0
78
+ fetch-tags: true
79
+ - name: Force-move sliding major tag
80
+ env:
81
+ TAG: ${{ needs.release-please.outputs.tag_name }}
82
+ run: |
83
+ MAJOR="${TAG%%.*}" # v0.4.1 -> v0
84
+ git tag -f "$MAJOR" "$TAG"
85
+ git push -f origin "refs/tags/$MAJOR"
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.6.1"
3
+ }
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.1](https://github.com/antonbabenko/repro-lambda/compare/v0.6.0...v0.6.1) (2026-06-22)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **ci:** render the build Step Summary (hashFiles cannot guard a RUNNER_TEMP file) ([#5](https://github.com/antonbabenko/repro-lambda/issues/5)) ([4fadb30](https://github.com/antonbabenko/repro-lambda/commit/4fadb307136f4dffd8c7e6bf5303d79f1a641f5a))
9
+
3
10
  ## v0.5.2 - 2026-06-21
4
11
 
5
12
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: repro-lambda
3
- Version: 0.5.2
3
+ Version: 0.6.1
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.5.2"
3
+ version = "0.6.1"
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"
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3
+ "packages": {
4
+ ".": {
5
+ "release-type": "python",
6
+ "package-name": "repro-lambda",
7
+ "include-component-in-tag": false,
8
+ "bump-minor-pre-major": true,
9
+ "bump-patch-for-minor-pre-major": false,
10
+ "changelog-path": "CHANGELOG.md",
11
+ "extra-files": [
12
+ "src/repro_lambda/__init__.py"
13
+ ],
14
+ "changelog-sections": [
15
+ { "type": "feat", "section": "Features" },
16
+ { "type": "fix", "section": "Bug Fixes" },
17
+ { "type": "perf", "section": "Performance Improvements" },
18
+ { "type": "refactor", "section": "Code Refactoring" },
19
+ { "type": "docs", "section": "Documentation" },
20
+ { "type": "test", "section": "Tests", "hidden": true },
21
+ { "type": "chore", "section": "Miscellaneous", "hidden": true },
22
+ { "type": "ci", "section": "Continuous Integration", "hidden": true }
23
+ ]
24
+ }
25
+ }
26
+ }
@@ -1,3 +1,3 @@
1
1
  """repro-lambda — reproducible AWS Lambda packaging outside Terraform."""
2
2
 
3
- __version__ = "0.5.2"
3
+ __version__ = "0.6.1" # x-release-please-version
@@ -70,6 +70,13 @@ def build(
70
70
  "--arch", help="Only build lambdas with this arch (e.g. arm64, x86_64). Empty = all."
71
71
  ),
72
72
  ] = "",
73
+ json_out: Annotated[
74
+ Path | None,
75
+ typer.Option(
76
+ "--json-out",
77
+ help="Also write the build summary JSON array to this file (for CI Step Summary).",
78
+ ),
79
+ ] = None,
73
80
  ) -> None:
74
81
  """Build one lambda (or all) per manifest and upload to S3."""
75
82
  import json
@@ -170,6 +177,9 @@ def build(
170
177
  if not dry_run:
171
178
  catalog.save(catalog_path)
172
179
 
180
+ if json_out is not None:
181
+ json_out.write_text(json.dumps(summary, indent=2))
182
+
173
183
  typer.echo(json.dumps(summary, indent=2))
174
184
 
175
185
 
@@ -196,14 +206,25 @@ def promote(
196
206
  help="Destination (prod) base bucket; us-east-1 variant auto-derived.",
197
207
  ),
198
208
  ] = "",
209
+ sha: Annotated[
210
+ str,
211
+ typer.Option(
212
+ "--sha",
213
+ help="Promote this exact 64-hex content sha instead of the catalog's current. "
214
+ "Requires a single lambda target (not 'all').",
215
+ ),
216
+ ] = "",
199
217
  dry_run: Annotated[bool, typer.Option("--dry-run")] = False,
200
218
  ) -> None:
201
219
  """Copy built artifacts dev -> prod by content hash (no rebuild).
202
220
 
203
- The sha per lambda is read from builds/catalog.json, so the artifact promoted
204
- is exactly the one the source commit built and tested in dev.
221
+ By default the sha per lambda is read from builds/catalog.json, so the artifact
222
+ promoted is exactly the one the source commit built and tested in dev. Pass
223
+ ``--sha`` to promote one explicit content sha (e.g. copy-pasted from a build run's
224
+ Step Summary), bypassing the catalog.
205
225
  """
206
226
  import json
227
+ import re
207
228
 
208
229
  from repro_lambda.manifest import load_manifest
209
230
  from repro_lambda.promote import (
@@ -225,6 +246,14 @@ def promote(
225
246
  typer.echo(f"no lambda named {target!r} in {manifest}", err=True)
226
247
  raise typer.Exit(2)
227
248
 
249
+ if sha:
250
+ if target == "all" or len(selected) != 1:
251
+ typer.echo("--sha requires a single lambda target (not 'all')", err=True)
252
+ raise typer.Exit(2)
253
+ if not re.fullmatch(r"[a-f0-9]{64}", sha):
254
+ typer.echo(f"--sha must be 64 lowercase hex chars, got {sha!r}", err=True)
255
+ raise typer.Exit(2)
256
+
228
257
  if not dry_run and (not dev_bucket or not prod_bucket):
229
258
  typer.echo(
230
259
  "--dev-bucket and --prod-bucket (or REPRO_LAMBDA_DEV_BUCKET / "
@@ -237,10 +266,10 @@ def promote(
237
266
  summary = []
238
267
  for spec in selected:
239
268
  try:
240
- sha = sha_from_catalog(catalog_path, spec.logical_name)
269
+ resolved_sha = sha or sha_from_catalog(catalog_path, spec.logical_name)
241
270
  outcome = promote_one(
242
271
  spec=spec,
243
- sha=sha,
272
+ sha=resolved_sha,
244
273
  dev_bucket=dev_bucket,
245
274
  prod_bucket=prod_bucket,
246
275
  dry_run=dry_run,
@@ -226,3 +226,74 @@ def test_cli_promote_unknown_target_exits_2(tmp_path: Path):
226
226
  ],
227
227
  )
228
228
  assert result.exit_code == 2
229
+
230
+
231
+ _EXPLICIT_SHA = "a" * 64 # 64-hex, deliberately NOT the catalog's "cafef00d"
232
+
233
+
234
+ def test_cli_promote_explicit_sha_bypasses_catalog(tmp_path: Path):
235
+ repo = _consumer(tmp_path)
236
+ with mock_aws():
237
+ c = boto3.client("s3", region_name="eu-west-1")
238
+ _make_bucket(c, DEV, "eu-west-1")
239
+ _make_bucket(c, PROD, "eu-west-1")
240
+ # Only the explicit-sha object exists; the catalog's current sha does not.
241
+ c.put_object(Bucket=DEV, Key=f"lambdas/app/{_EXPLICIT_SHA}.zip", Body=b"z")
242
+
243
+ result = runner.invoke(
244
+ app,
245
+ [
246
+ "promote",
247
+ "app",
248
+ "--manifest",
249
+ str(repo / "lambdas.toml"),
250
+ "--dev-bucket",
251
+ DEV,
252
+ "--prod-bucket",
253
+ PROD,
254
+ "--sha",
255
+ _EXPLICIT_SHA,
256
+ ],
257
+ )
258
+ assert result.exit_code == 0, result.stdout
259
+ assert c.head_object(Bucket=PROD, Key=f"lambdas/app/{_EXPLICIT_SHA}.zip")
260
+
261
+
262
+ def test_cli_promote_sha_rejects_all_target(tmp_path: Path):
263
+ repo = _consumer(tmp_path)
264
+ result = runner.invoke(
265
+ app,
266
+ [
267
+ "promote",
268
+ "all",
269
+ "--manifest",
270
+ str(repo / "lambdas.toml"),
271
+ "--dev-bucket",
272
+ DEV,
273
+ "--prod-bucket",
274
+ PROD,
275
+ "--sha",
276
+ _EXPLICIT_SHA,
277
+ ],
278
+ )
279
+ assert result.exit_code == 2 # --sha forbidden with 'all'
280
+
281
+
282
+ def test_cli_promote_sha_invalid_hex_exits_2(tmp_path: Path):
283
+ repo = _consumer(tmp_path)
284
+ result = runner.invoke(
285
+ app,
286
+ [
287
+ "promote",
288
+ "app",
289
+ "--manifest",
290
+ str(repo / "lambdas.toml"),
291
+ "--dev-bucket",
292
+ DEV,
293
+ "--prod-bucket",
294
+ PROD,
295
+ "--sha",
296
+ "not-a-valid-sha",
297
+ ],
298
+ )
299
+ assert result.exit_code == 2 # not 64-hex
@@ -646,7 +646,7 @@ wheels = [
646
646
 
647
647
  [[package]]
648
648
  name = "repro-lambda"
649
- version = "0.5.2"
649
+ version = "0.6.1"
650
650
  source = { editable = "." }
651
651
  dependencies = [
652
652
  { name = "boto3" },
File without changes
File without changes
File without changes
File without changes