Skip to content

Commit 8170bc3

Browse files
committed
fix(ci): isolate immutable packages from acceptance dependencies
1 parent 96c303d commit 8170bc3

6 files changed

Lines changed: 124 additions & 24 deletions

File tree

‎.github/workflows/acceptance.yml‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ permissions:
1010

1111
jobs:
1212
build:
13+
outputs:
14+
package-artifact-id: ${{ steps.upload-package.outputs.artifact-id }}
1315
runs-on: ubuntu-latest
1416
timeout-minutes: 20
1517
steps:
@@ -24,14 +26,21 @@ jobs:
2426
- run: uv run python -m build --wheel --outdir candidate
2527
- run: bash scripts/build-acceptance.sh candidate/*.whl candidate
2628
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
29+
id: upload-package
2730
with:
2831
name: client-acceptance
2932
path: candidate/*
3033
if-no-files-found: error
3134

35+
acceptance-check:
36+
needs: build
37+
uses: ./.github/workflows/verify-acceptance.yml
38+
with:
39+
package-artifact-id: ${{ needs.build.outputs.package-artifact-id }}
40+
3241
attest:
3342
if: github.event_name != 'pull_request'
34-
needs: build
43+
needs: [build, acceptance-check]
3544
runs-on: ubuntu-latest
3645
timeout-minutes: 5
3746
permissions:
@@ -41,7 +50,8 @@ jobs:
4150
steps:
4251
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
4352
with:
44-
name: client-acceptance
53+
artifact-ids: ${{ needs.build.outputs.package-artifact-id }}
54+
merge-multiple: true
4555
path: candidate
4656
- uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4
4757
with:

‎.github/workflows/publish.yml‎

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ jobs:
4141
uses: ./.github/workflows/ci.yml
4242

4343
build:
44+
outputs:
45+
package-artifact-id: ${{ steps.upload-package.outputs.artifact-id }}
46+
bundle-artifact-id: ${{ steps.upload-acceptance.outputs.artifact-id }}
4447
needs: [validate, check]
4548
runs-on: ubuntu-latest
4649
timeout-minutes: 15
@@ -58,20 +61,31 @@ jobs:
5861
- name: Check package identity and release version
5962
env:
6063
RELEASE_TAG: ${{ github.event.release.tag_name }}
61-
run: bash scripts/check_package.sh "${RELEASE_TAG#v}"
64+
run: |
65+
python -c 'import os,tomllib; p=tomllib.load(open("pyproject.toml","rb"))["project"]; assert p["name"]=="volcano-sdk-python" and "v"+p["version"]==os.environ["RELEASE_TAG"]'
6266
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
67+
id: upload-package
6368
with:
6469
name: release-package
6570
path: dist/*
6671
if-no-files-found: error
6772
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
73+
id: upload-acceptance
6874
with:
6975
name: acceptance-tests
7076
path: acceptance-output/sdk-acceptance.tar.gz
7177
if-no-files-found: error
7278

73-
attest:
79+
acceptance-check:
7480
needs: build
81+
uses: ./.github/workflows/verify-acceptance.yml
82+
with:
83+
package-artifact-id: ${{ needs.build.outputs.package-artifact-id }}
84+
bundle-artifact-id: ${{ needs.build.outputs.bundle-artifact-id }}
85+
release-version: ${{ github.event.release.tag_name }}
86+
87+
attest:
88+
needs: [build, acceptance-check]
7589
runs-on: ubuntu-latest
7690
timeout-minutes: 5
7791
permissions:
@@ -81,11 +95,13 @@ jobs:
8195
steps:
8296
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
8397
with:
84-
name: release-package
98+
artifact-ids: ${{ needs.build.outputs.package-artifact-id }}
99+
merge-multiple: true
85100
path: release
86101
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
87102
with:
88-
name: acceptance-tests
103+
artifact-ids: ${{ needs.build.outputs.bundle-artifact-id }}
104+
merge-multiple: true
89105
path: release
90106
- uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4
91107
with:
@@ -121,21 +137,23 @@ jobs:
121137
# Publishing receives checked artifacts without executing SDK source.
122138
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
123139
with:
124-
name: release-package
140+
artifact-ids: ${{ needs.build.outputs.package-artifact-id }}
141+
merge-multiple: true
125142
path: dist
126143
- name: Publish distributions to PyPI
127144
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
128145

129146
registry-smoke:
130-
needs: publish
147+
needs: [publish, build]
131148
runs-on: ubuntu-latest
132149
timeout-minutes: 15
133150
permissions:
134151
contents: read
135152
steps:
136153
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
137154
with:
138-
name: acceptance-tests
155+
artifact-ids: ${{ needs.build.outputs.bundle-artifact-id }}
156+
merge-multiple: true
139157
path: acceptance-output
140158
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
141159
with:
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Verify immutable acceptance artifacts
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
package-artifact-id:
7+
required: true
8+
type: string
9+
bundle-artifact-id:
10+
default: ''
11+
type: string
12+
release-version:
13+
default: ''
14+
type: string
15+
16+
permissions:
17+
contents: read
18+
19+
jobs:
20+
verify:
21+
runs-on: ubuntu-latest
22+
timeout-minutes: 20
23+
steps:
24+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
25+
with:
26+
persist-credentials: false
27+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
28+
with:
29+
python-version: '3.14'
30+
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e
31+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
32+
with:
33+
artifact-ids: ${{ inputs.package-artifact-id }}
34+
merge-multiple: true
35+
path: package
36+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
37+
if: inputs.bundle-artifact-id != ''
38+
with:
39+
artifact-ids: ${{ inputs.bundle-artifact-id }}
40+
merge-multiple: true
41+
path: acceptance
42+
# Fresh dependencies can modify these copies, never the uploaded artifacts.
43+
- name: Verify the installed package and fresh consumer
44+
env:
45+
ACCEPTANCE_BUNDLE: ${{ inputs.bundle-artifact-id != '' && 'acceptance/sdk-acceptance.tar.gz' || 'package/sdk-acceptance.tar.gz' }}
46+
run: bash scripts/test-acceptance.sh package/*.whl "$ACCEPTANCE_BUNDLE"
47+
- name: Check wheel, source distribution and installed types
48+
if: inputs.release-version != ''
49+
env:
50+
RELEASE_TAG: ${{ inputs.release-version }}
51+
run: |
52+
uv sync --frozen
53+
bash scripts/check_package.sh "${RELEASE_TAG#v}" package

‎maintainers/acceptance.md‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ description: Build and publish the locked consumer project used by Hosting.
44
---
55

66
Run `bash scripts/build-acceptance.sh <package-file> <output-directory>` after
7-
building the SDK. The command creates a standalone consumer, installs the exact
8-
package with the language package manager, checks package loading and the public
9-
quickstart, and exports `sdk-acceptance.tar.gz` without runtime source or SDK bytes.
10-
The bundle contains the existing contract bindings and a native dependency lock.
7+
building the SDK. The command creates a standalone consumer and native dependency
8+
lock without installing or executing newly resolved dependencies. It exports
9+
`sdk-acceptance.tar.gz` with the existing contract bindings, without SDK runtime
10+
source or package bytes.
11+
12+
CI uploads the package and bundle before running
13+
`bash scripts/test-acceptance.sh <package-file> <bundle-file>` in a separate job.
14+
That job checks frozen installation, package loading, and the public quickstart.
15+
Signing and publishing download the original immutable artifact IDs after those
16+
checks pass; dependencies cannot modify the originals.
1117

1218
Hosting authenticates the bundle and customer-registry package, restores that
1319
package at the filename recorded in `acceptance.json`, then performs a frozen

‎scripts/build-acceptance.sh‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,17 @@ cp "$repo_dir/tests/package/quickstart.py" "$work/tests/package/"
1515
cp "$repo_dir/docs/README.md" "$work/docs/"
1616
cp "$repo_dir/scripts/smoke-wheel.sh" "$work/scripts/"
1717
cd "$work"
18-
uv add --no-sync "./$(basename "$artifact")"
19-
uv sync --frozen
20-
uv run --no-sync python -I - "$(basename "$artifact")" <<'PYTHON'
21-
import hashlib, json, sys
22-
from importlib.metadata import distribution
18+
uv add --no-sync --no-build "./$(basename "$artifact")"
19+
python3 - "$(basename "$artifact")" <<'PYTHON'
20+
import email, hashlib, json, sys, zipfile
2321
from pathlib import Path
24-
import volcano_sdk
25-
package = distribution("volcano-sdk-python")
26-
assert Path(volcano_sdk.__file__).resolve().is_relative_to(Path.cwd() / ".venv")
2722
artifact = Path(sys.argv[1])
28-
Path("acceptance.json").write_text(json.dumps({"schema": 1, "language": "python", "package": "volcano-sdk-python", "version": package.version, "filename": artifact.name, "sha256": hashlib.sha256(artifact.read_bytes()).hexdigest()}, indent=2))
23+
with zipfile.ZipFile(artifact) as wheel:
24+
names = [name for name in wheel.namelist() if name.endswith(".dist-info/METADATA")]
25+
assert len(names) == 1
26+
metadata = email.message_from_bytes(wheel.read(names[0]))
27+
assert metadata["Name"] == "volcano-sdk-python"
28+
Path("acceptance.json").write_text(json.dumps({"schema": 1, "language": "python", "package": metadata["Name"], "version": metadata["Version"], "filename": artifact.name, "sha256": hashlib.sha256(artifact.read_bytes()).hexdigest()}, indent=2))
2929
PYTHON
30-
bash scripts/smoke-wheel.sh "$(basename "$artifact")"
31-
rm -rf .venv "$(basename "$artifact")"
30+
rm "$(basename "$artifact")"
3231
tar -czf "$output/sdk-acceptance.tar.gz" .

‎scripts/test-acceptance.sh‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
if [ "$#" -ne 2 ]; then echo 'usage: test-acceptance.sh package-file sdk-acceptance.tar.gz' >&2; exit 1; fi
4+
artifact="$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
5+
bundle="$(cd "$(dirname "$2")" && pwd)/$(basename "$2")"
6+
work="$(mktemp -d)"
7+
trap 'rm -rf "$work"' EXIT
8+
tar -xzf "$bundle" -C "$work"
9+
cd "$work"
10+
cp "$artifact" .
11+
python3 -c 'import hashlib,json,pathlib; a=json.loads(pathlib.Path("acceptance.json").read_text()); assert hashlib.sha256(pathlib.Path(a["filename"]).read_bytes()).hexdigest()==a["sha256"], "Package differs from acceptance bundle"'
12+
uv sync --frozen --no-build
13+
uv run --no-sync python -I -c 'from pathlib import Path; import volcano_sdk; assert Path(volcano_sdk.__file__).resolve().is_relative_to(Path.cwd()/".venv")'
14+
bash scripts/smoke-wheel.sh "$(basename "$artifact")"

0 commit comments

Comments
 (0)