Skip to content

Commit aeff2a6

Browse files
authored
Pin each release's rust base image by digest (#20)
1 parent 486c354 commit aeff2a6

43 files changed

Lines changed: 813 additions & 938 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,31 @@ jobs:
2929
id: pair
3030
run: |
3131
cli="$(./scripts/newest_pair.py --stellar-cli-version)"
32-
rust="$(./scripts/newest_pair.py --rust-version)"
32+
# The newest pair is a fully-qualified pin: <label>@sha256:<digest>.
33+
pin="$(./scripts/newest_pair.py --rust-version)"
34+
rust="${pin%@*}"
35+
digest="${pin#*@}"
3336
tag="$(./scripts/tag_names.py \
3437
--stellar-cli-version "$cli" --rust-version "$rust")"
3538
{
3639
echo "cli=$cli"
3740
echo "rust=$rust"
41+
echo "digest=$digest"
3842
echo "image=stellar-cli:$tag"
3943
} >> "$GITHUB_OUTPUT"
4044
- name: build image
4145
run: |
4246
./scripts/build_image.py \
4347
--stellar-cli-version "${{ steps.pair.outputs.cli }}" \
44-
--rust-version "${{ steps.pair.outputs.rust }}"
48+
--rust-version "${{ steps.pair.outputs.rust }}" \
49+
--rust-image-digest "${{ steps.pair.outputs.digest }}"
4550
- name: smoke test
4651
run: |
4752
./scripts/smoke_test_image.py \
4853
--image "${{ steps.pair.outputs.image }}" \
4954
--stellar-cli-version "${{ steps.pair.outputs.cli }}" \
50-
--rust-version "${{ steps.pair.outputs.rust }}"
55+
--rust-version "${{ steps.pair.outputs.rust }}" \
56+
--rust-image-digest "${{ steps.pair.outputs.digest }}"
5157
- name: wasm reproducibility
5258
run: |
5359
./scripts/repro_test.py \

.github/workflows/publish.yml

Lines changed: 7 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ jobs:
8080
tag="$(./scripts/tag_names.py \
8181
--stellar-cli-version ${{ matrix.stellar_cli_version }} \
8282
--rust-version ${{ matrix.rust_base_key }} \
83-
--platform ${{ matrix.platform }} \
84-
--stellar-cli-ref ${{ matrix.stellar_cli_ref }})"
83+
--platform ${{ matrix.platform }})"
8584
echo "tag=$tag" >> "$GITHUB_OUTPUT"
8685
echo "image=$REGISTRY:$tag" >> "$GITHUB_OUTPUT"
8786
@@ -91,32 +90,13 @@ jobs:
9190
username: ${{ secrets.DOCKERHUB_USERNAME }}
9291
password: ${{ secrets.DOCKERHUB_TOKEN }}
9392

94-
- name: check whether already published
95-
id: skip
96-
run: |
97-
if docker buildx imagetools inspect "${{ steps.tag.outputs.image }}" >/dev/null 2>&1; then
98-
echo "::warning::${{ steps.tag.outputs.image }} already exists in the registry; skipping build (per-arch tags are immutable)"
99-
{
100-
echo "## ⚠️ Skipped — already published"
101-
echo ""
102-
echo "\`${{ steps.tag.outputs.image }}\` was already in the registry."
103-
echo ""
104-
echo "Per-arch tags are immutable, so no build or push happened for this row. If this is a corrupt-push recovery, delete the tag in Docker Hub by hand and re-run the workflow."
105-
} >> "$GITHUB_STEP_SUMMARY"
106-
echo "skipped=true" >> "$GITHUB_OUTPUT"
107-
else
108-
echo "skipped=false" >> "$GITHUB_OUTPUT"
109-
fi
110-
11193
- name: build metadata
11294
id: meta
113-
if: steps.skip.outputs.skipped != 'true'
11495
run: |
11596
echo "build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
11697
11798
- name: build and push
11899
id: build
119-
if: steps.skip.outputs.skipped != 'true'
120100
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
121101
with:
122102
context: .
@@ -135,18 +115,16 @@ jobs:
135115
SOURCE_REPO=${{ github.repository }}
136116
137117
- name: generate SBOM file
138-
if: steps.skip.outputs.skipped != 'true'
139118
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
140119
with:
141120
image: ${{ steps.tag.outputs.image }}@${{ steps.build.outputs.digest }}
142121
format: spdx-json
143-
output-file: sbom-${{ matrix.stellar_cli_version }}-rust${{ matrix.rust_base_key }}-${{ matrix.arch }}.spdx.json
122+
output-file: sbom-${{ matrix.stellar_cli_version }}-rust${{ matrix.rust_base_id }}-${{ matrix.arch }}.spdx.json
144123
upload-release-assets: false
145124
upload-artifact: false
146125

147126
- name: attest build provenance
148127
id: attest-prov
149-
if: steps.skip.outputs.skipped != 'true'
150128
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
151129
with:
152130
subject-name: ${{ env.REGISTRY }}
@@ -158,19 +136,17 @@ jobs:
158136

159137
- name: attest SBOM
160138
id: attest-sbom
161-
if: steps.skip.outputs.skipped != 'true'
162139
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
163140
with:
164141
subject-name: ${{ env.REGISTRY }}
165142
subject-digest: ${{ steps.build.outputs.digest }}
166-
sbom-path: sbom-${{ matrix.stellar_cli_version }}-rust${{ matrix.rust_base_key }}-${{ matrix.arch }}.spdx.json
143+
sbom-path: sbom-${{ matrix.stellar_cli_version }}-rust${{ matrix.rust_base_id }}-${{ matrix.arch }}.spdx.json
167144
push-to-registry: true
168145

169146
- name: write per-arch metadata
170-
if: steps.skip.outputs.skipped != 'true'
171147
run: |
172148
./scripts/write_metadata.py \
173-
--output "meta-${{ matrix.stellar_cli_version }}-rust${{ matrix.rust_base_key }}-${{ matrix.arch }}.json" \
149+
--output "meta-${{ matrix.stellar_cli_version }}-rust${{ matrix.rust_base_id }}-${{ matrix.arch }}.json" \
174150
--arch "${{ matrix.arch }}" \
175151
--stellar-cli-version "${{ matrix.stellar_cli_version }}" \
176152
--digest "${{ steps.build.outputs.digest }}" \
@@ -180,41 +156,20 @@ jobs:
180156
--tag "${{ steps.tag.outputs.tag }}"
181157
182158
- name: rename provenance bundle
183-
if: steps.skip.outputs.skipped != 'true'
184159
run: |
185160
cp "${{ steps.attest-prov.outputs.bundle-path }}" \
186-
"prov-${{ matrix.stellar_cli_version }}-rust${{ matrix.rust_base_key }}-${{ matrix.arch }}.intoto.jsonl"
187-
188-
# Skipped pairs still need metadata so the release-body composer can
189-
# show the full state of every declared pair, not just the freshly
190-
# built ones. Omitting --digest tells write_metadata to resolve it
191-
# from the existing tag in the registry; the SBOM/provenance files
192-
# are not regenerated (they stay attached to the previously-
193-
# published image's attestation store).
194-
- name: write per-arch metadata (skipped pair)
195-
if: steps.skip.outputs.skipped == 'true'
196-
run: |
197-
./scripts/write_metadata.py \
198-
--output "meta-${{ matrix.stellar_cli_version }}-rust${{ matrix.rust_base_key }}-${{ matrix.arch }}.json" \
199-
--arch "${{ matrix.arch }}" \
200-
--stellar-cli-version "${{ matrix.stellar_cli_version }}" \
201-
--image "${{ steps.tag.outputs.image }}" \
202-
--rust-base-key "${{ matrix.rust_base_key }}" \
203-
--rust-version "${{ matrix.rust_version }}" \
204-
--tag "${{ steps.tag.outputs.tag }}"
161+
"prov-${{ matrix.stellar_cli_version }}-rust${{ matrix.rust_base_id }}-${{ matrix.arch }}.intoto.jsonl"
205162
206163
- name: upload release artifacts
207164
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
208165
with:
209-
name: release-artifacts-${{ matrix.stellar_cli_version }}-rust${{ matrix.rust_base_key }}-${{ matrix.arch }}
166+
name: release-artifacts-${{ matrix.stellar_cli_version }}-rust${{ matrix.rust_base_id }}-${{ matrix.arch }}
210167
path: |
211168
sbom-*.spdx.json
212169
prov-*.intoto.jsonl
213170
meta-*.json
214171
retention-days: 7
215-
# warn (not error) because the skipped path only produces meta-*.json;
216-
# the sbom-*/prov-* globs find nothing in that case.
217-
if-no-files-found: warn
172+
if-no-files-found: error
218173

219174
manifest:
220175
name: assemble manifest lists

README.md

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,13 @@ docker run --rm \
6767
## Verifiable builds ([SEP-58](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0058.md))
6868

6969
For verifiable references, **always pin to a per-arch single-architecture
70-
digest**, never to a moving tag like `:latest` or `:<cli>`, and never to a
71-
multi-arch manifest list digest:
70+
digest (`@sha256:…`)** — it is the only stable reference. Never use a tag or a
71+
multi-arch manifest list digest in `bldimg`:
7272

7373
```sh
7474
# Find the per-arch digest for the architecture you used to build.
75-
# Pick any of the immutable manifest-list tags from the release notes,
76-
# e.g. :26.0.0-<ref>-rust1.94.0-slim-trixie, or the :26.0.0 alias:
75+
# Pick any of the manifest-list tags from the release notes,
76+
# e.g. :26.0.0-rust1.94.0-slim-trixie, or the :26.0.0 alias:
7777
docker buildx imagetools inspect docker.io/stellar/stellar-cli:26.0.0
7878
```
7979

@@ -83,36 +83,35 @@ compare the resulting WASM sha256.
8383

8484
## Repo layout
8585

86-
| Path | What |
87-
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
88-
| `Dockerfile` | Two-stage builder + runtime, args-driven. |
89-
| `builds.json` | Source of truth for which (stellar-cli, rust base key) pairs we publish. |
90-
| `builds.schema.json` | JSON Schema for `builds.json`. |
91-
| `scripts/build_image.py` | Local single-image build. |
92-
| `scripts/validate_json.py` | Validates every `*.json` for sorted keys and `builds.json` for schema + cross-field constraints. |
93-
| `scripts/refresh_rust_digests.py` | Fills blank `rust_image_digests` entries by inspecting `rust:<key>` upstream (where `<key>` is the composite `<rust>-<suffix>` form). Does not touch already-pinned digests unless asked per-key. |
94-
| `scripts/refresh_stellar_cli_digests.py` | Fills blank `stellar_cli_versions[].ref` entries by resolving the matching `v<version>` git tag in `stellar/stellar-cli`. Same per-target opt-in shape as the rust refresher. |
95-
| `scripts/verify_image.py` | Consumer-facing verifier. Wraps `gh attestation verify` for both the SLSA build provenance and the SPDX SBOM attestations against a per-arch image digest. |
96-
| `scripts/lib/` | Shared Python helpers imported by the other scripts (builds.json IO, semver/key parsing, subprocess + adapter wrappers). |
86+
| Path | What |
87+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
88+
| `Dockerfile` | Two-stage builder + runtime, args-driven. |
89+
| `builds.json` | Source of truth for which (stellar-cli, rust base key) pairs we publish. |
90+
| `builds.schema.json` | JSON Schema for `builds.json`. |
91+
| `scripts/build_image.py` | Local single-image build. |
92+
| `scripts/validate_json.py` | Validates every `*.json` for sorted keys and `builds.json` against the schema. |
93+
| `scripts/refresh.py` | For one `--stellar-cli-version`: picks the rust base labels, resolves the upstream cli ref and each base's index digest, and appends the fully-qualified pins `<label>@<digest>` (append-only; already-published pins are retained). |
94+
| `scripts/verify_image.py` | Consumer-facing verifier. Wraps `gh attestation verify` for both the SLSA build provenance and the SPDX SBOM attestations against a per-arch image digest. |
95+
| `scripts/lib/` | Shared Python helpers imported by the other scripts (builds.json IO, semver/key parsing, subprocess + adapter wrappers). |
9796

9897
## Local development
9998

10099
```sh
101100
# Validate builds.json.
102101
./scripts/validate_json.py
103102

104-
# Build a local image for a declared (cli, rust base) pair.
105-
./scripts/build_image.py --stellar-cli-version 26.0.0 --rust-version 1.94.0-slim-trixie
103+
# Build a local image for a declared (cli, rust base) pair. The rust base is
104+
# given as the label plus its pinned digest (copy the pin from builds.json).
105+
./scripts/build_image.py --stellar-cli-version 26.0.0 \
106+
--rust-version 1.94.0-slim-trixie \
107+
--rust-image-digest sha256:f7bf1c266d9e48c8d724733fd97ba60464c44b743eb4f46f935577d3242d81d0
106108

107109
# Smoke-test the built image.
108110
docker run --rm stellar-cli:26.0.0-rust1.94.0-slim-trixie --version
109111
docker run --rm stellar-cli:26.0.0-rust1.94.0-slim-trixie contract build --help
110112

111-
# Resolve blank rust base image digests (maintainer task).
112-
./scripts/refresh_rust_digests.py --dry-run
113-
114-
# Resolve blank stellar-cli refs from upstream git tags (maintainer task).
115-
./scripts/refresh_stellar_cli_digests.py --dry-run
113+
# Resolve + append rust base pins and the cli ref for a version (maintainer task).
114+
./scripts/refresh.py --stellar-cli-version 26.1.0 --dry-run
116115
```
117116

118117
Requirements: `docker` (with `buildx`) and [`uv`](https://docs.astral.sh/uv/).
@@ -121,8 +120,8 @@ Requirements: `docker` (with `buildx`) and [`uv`](https://docs.astral.sh/uv/).
121120

122121
Maintainers: see [`RELEASE.md`](./RELEASE.md) for the end-to-end release
123122
process — how `builds.json` works, the PR-driven release flow that fires
124-
the publish workflow when a GitHub Release is published, the tag-
125-
immutability guard, and how to verify a freshly published image.
123+
the publish workflow when a GitHub Release is published, the published tag
124+
scheme, and how to verify a freshly published image.
126125

127126
## License
128127

0 commit comments

Comments
 (0)