diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 7d83fd2f..6efa75e2 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -27,9 +27,42 @@ permissions:
actions: read # Required for workflow access
jobs:
+ # Resolve component versions once: pinned in versions.env, else upstream latest
+ resolve-tags:
+ name: resolve component tags
+ runs-on: ubuntu-latest
+ outputs:
+ snphost: ${{ steps.resolve.outputs.snphost }}
+ snpguest: ${{ steps.resolve.outputs.snpguest }}
+ beacon: ${{ steps.resolve.outputs.beacon }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Resolve tags (pin from versions.env, fallback to latest)
+ id: resolve
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ set -euo pipefail
+ . versions.env
+ resolve() { # $1=repo $2=pinned tag
+ if [ -n "$2" ]; then
+ echo "$2"
+ else
+ gh release view --repo "$1" --json tagName --jq '.tagName'
+ fi
+ }
+ {
+ echo "snphost=$(resolve virtee/snphost "${SNPHOST_TAG:-}")"
+ echo "snpguest=$(resolve virtee/snpguest "${SNPGUEST_TAG:-}")"
+ echo "beacon=$(resolve AMDEPYC/beacon "${BEACON_TAG:-}")"
+ } >> "$GITHUB_OUTPUT"
+
# Development release for tag or main pushes
create-release:
name: create new release for tagged or latest assets
+ needs: resolve-tags
runs-on: ubuntu-latest
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.replace_release == 'true')
steps:
@@ -63,6 +96,7 @@ jobs:
build:
name: ${{ matrix.distro }}-${{ matrix.release }}
+ needs: resolve-tags
runs-on: ubuntu-24.04
strategy:
fail-fast: false
@@ -99,17 +133,15 @@ jobs:
sudo apt update
sudo apt install -y mkosi
- - name: Fetch latest release tags
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Apply resolved release tags
run: |
set -euo pipefail
-
- # Fetch latest tags using gh CLI (automatically authenticated)
- SNPGUEST_TAG=$(gh release view --repo virtee/snpguest --json tagName --jq '.tagName')
- BEACON_TAG=$(gh release view --repo AMDEPYC/beacon --json tagName --jq '.tagName')
- SNPHOST_TAG=$(gh release view --repo virtee/snphost --json tagName --jq '.tagName')
-
+
+ # Tags resolved once by the resolve-tags job (pinned in versions.env, else upstream latest)
+ SNPGUEST_TAG="${{ needs.resolve-tags.outputs.snpguest }}"
+ BEACON_TAG="${{ needs.resolve-tags.outputs.beacon }}"
+ SNPHOST_TAG="${{ needs.resolve-tags.outputs.snphost }}"
+
# Inject environment variables into mkosi.conf files
sed -i "/^\[Build\]/a\Environment=SNPGUEST_TAG=$SNPGUEST_TAG" modules/build/common/snpguest/mkosi.conf
sed -i "/^\[Build\]/a\Environment=BEACON_TAG=$BEACON_TAG" modules/build/host/beacon/mkosi.conf
diff --git a/README.md b/README.md
index 527003e3..73127380 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,8 @@ Each certification run automatically creates a GitHub Issue containing the resul
Host and Guest images are constructed in GitHub Workflows via [`mkosi`](https://github.com/systemd/mkosi). Host images are designed to be booted on a SEV-enabled EPYC server, and are configured with a series of tests in the form of custom systemd services that will run on an embedded guest image. The resulting host and guest images are available in GitHub releases.
+The upstream release versions of the components baked into the images — [`snphost`](https://github.com/virtee/snphost), [`snpguest`](https://github.com/virtee/snpguest), and [`beacon`](https://github.com/AMDEPYC/beacon) — are pinned in [`versions.env`](./versions.env) at the repository root. Bumps are made deliberately via a pull request so that any breakage in a new upstream release is caught at review time; the `build-and-release` workflow resolves the pinned tags (falling back to the upstream latest for any empty entry) and reports the resolved `repository → version` pairs in the `resolve-tags` job summary. Leave a value empty in `versions.env` to track the upstream latest release at build time.
+
[cert-3.0]: ./docs/certifications.md#amd-epyc-7003-milan
[cert-3.1]: ./docs/certifications.md#amd-epyc-9004-genoa
[cert-4.0]: ./docs/certifications.md#amd-epyc-7004-bergamo
diff --git a/docs/how-to-add-new-os-images.md b/docs/how-to-add-new-os-images.md
index b06fbd9e..0cec7ee7 100644
--- a/docs/how-to-add-new-os-images.md
+++ b/docs/how-to-add-new-os-images.md
@@ -53,6 +53,8 @@ You can create and publish new host or guest images in `sev-certify` with `mkosi
Then, verify for successful execution with the built new host and guest OS images under `devel` tag.
+ The `build-and-release` workflow resolves the `snphost`, `snpguest` and `beacon` release tags in its `resolve-tags` job before building. Versions are pinned in [`versions.env`](https://github.com/AMDEPYC/sev-certify/blob/main/versions.env) at the repository root (an empty entry falls back to the upstream latest release). The resolved repository/version pairs are shown as a table in the `resolve-tags` job summary.
+
If the `build-and-release` workflow fails, you need to troubleshoot for the error in the workflow log, implement changes, and retest until successful execution.
- **Test the new OS Host/Guest Images:**
@@ -183,6 +185,8 @@ make GENERATE_MANIFEST=yes guest--
The `GENERATE_MANIFEST` variable defaults to empty in the Makefile. When set to a truthy value (`y`, `yes`, or `1`), mkosi is invoked with the environment variable to generate manifest files.
+The `make tags` target resolves the `snphost`, `snpguest` and `beacon` versions from [`versions.env`](https://github.com/AMDEPYC/sev-certify/blob/main/versions.env); pinned entries are used as-is and empty entries fall back to fetching the upstream latest release.
+
**For local builds using mkosi directly**, pass the environment variable:
```bash
diff --git a/images/Makefile b/images/Makefile
index a00878d7..65bf1e5b 100644
--- a/images/Makefile
+++ b/images/Makefile
@@ -5,22 +5,28 @@ CLEAN_IMAGES := $(addprefix clean-,$(IMAGES))
GH_AUTH := $(if $(GITHUB_TOKEN),-H "Authorization: Bearer $(GITHUB_TOKEN)")
GH_LATEST = curl -sf $(GH_AUTH) https://api.github.com/repos/$(1)/releases/latest | jq -r '.tag_name'
+# Pinned component versions (override on the command line with make VAR=x)
+-include ../versions.env
+
.PHONY: all clean list status tags $(IMAGES) $(CLEAN_IMAGES)
+# Pinned versions from ../versions.env are used as-is; empty entries fall back
+# to fetching the latest upstream release.
tags:
@set -e; \
trap 'rm -f .tags.tmp' EXIT; \
fetch() { \
- tag=$$($(call GH_LATEST,$$1)); \
+ tag=$$3; \
+ if [ -z "$$tag" ]; then tag=$$($(call GH_LATEST,$$1)); fi; \
if [ -z "$$tag" ] || [ "$$tag" = "null" ]; then \
- echo "Failed to fetch tag for $$1. Try: export GITHUB_TOKEN=\$$(gh auth token)" >&2; \
+ echo "Failed to resolve tag for $$1. Try: export GITHUB_TOKEN=\$$(gh auth token)" >&2; \
exit 1; \
fi; \
echo "$$2=$$tag"; \
}; \
- { fetch virtee/snpguest SNPGUEST_TAG; \
- fetch AMDEPYC/beacon BEACON_TAG; \
- fetch virtee/snphost SNPHOST_TAG; } > .tags.tmp; \
+ { fetch virtee/snpguest SNPGUEST_TAG "$(SNPGUEST_TAG)"; \
+ fetch AMDEPYC/beacon BEACON_TAG "$(BEACON_TAG)"; \
+ fetch virtee/snphost SNPHOST_TAG "$(SNPHOST_TAG)"; } > .tags.tmp; \
mv .tags.tmp .tags
@cat .tags
diff --git a/versions.env b/versions.env
new file mode 100644
index 00000000..c113f54f
--- /dev/null
+++ b/versions.env
@@ -0,0 +1,6 @@
+# Upstream release versions used to build the host/guest images.
+# Bump deliberately via a PR so any breakage in a new release is caught at review time.
+# Leave a value empty to fall back to the upstream latest release at build time.
+SNPHOST_TAG=v0.9.0
+SNPGUEST_TAG=v0.10.0
+BEACON_TAG=v1.1.0