fix(release): repair tag gate and sibling-container publication (#1)
The release workflow could not complete on the self-hosted sibling-container runner. Four defects are corrected together because three of them share the same release.yml plumbing. Exclude the tag under release from the prior-tag probe. check-release-tag.sh already requires the tag to point at HEAD, so the unfiltered listing always matched itself and rejected every first release before the initial baseline branch could run. Resolve host bind sources once per job. Steps run inside the runner container while docker run starts siblings on the host daemon, so GITHUB_WORKSPACE and RUNNER_TEMP are not usable as bind sources. A new step maps both to host paths through the runner's own mounts and exports them; the build, provenance, smoke, finalize, and publish steps reuse them, and publish-release.sh receives them by environment instead of repeating the inspection. Bind destinations stay at the container-internal paths so runner-side reads keep working unchanged. Prove the signing material before the first push. Gitea package versions are immutable, so a missing or undecryptable cosign key must abort ahead of the require_absent gate rather than after packages and the image are published. Authenticate the pinned consumer fetches. Both consumer repositories are private, so anonymous fetches fail; the release token is passed to the verification step and applied as a URL-scoped extra header through git's config environment, keeping it out of argv, remotes, and on-disk config. Anonymous fetch remains the fallback for local runs.
This commit is contained in:
@@ -20,6 +20,39 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Resolve host bind sources for sibling containers
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# This job's steps run inside the runner container while every
|
||||
# `docker run` starts a sibling container on the host daemon, so bind
|
||||
# sources must be host paths. Resolve them once from the runner's own
|
||||
# mounts and reuse them in every later step.
|
||||
if ! mounts="$(docker inspect "$HOSTNAME" 2>/dev/null | jq -c '.[0].Mounts')"; then
|
||||
echo "Runner is not containerized; using workspace and temp paths as host paths."
|
||||
echo "RENDEZVOUS_WORKSPACE_SOURCE=$GITHUB_WORKSPACE" >>"$GITHUB_ENV"
|
||||
echo "RENDEZVOUS_TEMP_SOURCE=$RUNNER_TEMP" >>"$GITHUB_ENV"
|
||||
exit 0
|
||||
fi
|
||||
resolve_host_path() {
|
||||
jq -er --arg path "$1" '
|
||||
[ .[]
|
||||
| .Destination as $destination
|
||||
| select($path == $destination
|
||||
or ($path | startswith($destination + "/"))) ]
|
||||
| if length == 0 then
|
||||
error("No runner mount exposes \($path) on the Docker host.")
|
||||
else
|
||||
sort_by(.Destination | length) | last
|
||||
end
|
||||
| (.Destination | length) as $prefix
|
||||
| .Source + $path[$prefix:]' <<<"$mounts"
|
||||
}
|
||||
workspace_source="$(resolve_host_path "$GITHUB_WORKSPACE")"
|
||||
temp_source="$(resolve_host_path "$RUNNER_TEMP")"
|
||||
echo "RENDEZVOUS_WORKSPACE_SOURCE=$workspace_source" >>"$GITHUB_ENV"
|
||||
echo "RENDEZVOUS_TEMP_SOURCE=$temp_source" >>"$GITHUB_ENV"
|
||||
|
||||
- name: Install pinned .NET SDKs
|
||||
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
|
||||
with:
|
||||
@@ -36,14 +69,19 @@ jobs:
|
||||
|
||||
- name: Validate tag and produce reproducible artifacts
|
||||
shell: bash
|
||||
env:
|
||||
RENDEZVOUS_RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="${GITHUB_REF_NAME#v}"
|
||||
./scripts/check-release-tag.sh "$GITHUB_REF_NAME"
|
||||
previous_tag="$(git tag --merged HEAD^ --list 'v*.*.*' --sort=-version:refname | sed -n '1p')"
|
||||
# check-release-tag.sh already required this tag to point at HEAD, so
|
||||
# it must be excluded before asking whether any earlier release exists.
|
||||
unrelated_tag="$(git tag --list 'v*.*.*' | grep -vFx "$GITHUB_REF_NAME" | sed -n '1p' || true)"
|
||||
if [[ -n "$previous_tag" ]]; then
|
||||
./scripts/check-compatibility.sh "$previous_tag"
|
||||
elif [[ -n "$(git tag --list 'v*.*.*' | sed -n '1p')" ]]; then
|
||||
elif [[ -n "$unrelated_tag" ]]; then
|
||||
echo "No prior release tag is an ancestor of $GITHUB_REF_NAME." >&2
|
||||
exit 1
|
||||
else
|
||||
@@ -61,14 +99,15 @@ jobs:
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--env HOME="${RUNNER_TEMP}/release-home" \
|
||||
--env NUGET_PACKAGES="${RUNNER_TEMP}/nuget" \
|
||||
--volume "$GITHUB_WORKSPACE:/source" \
|
||||
--volume "${RUNNER_TEMP}:${RUNNER_TEMP}" \
|
||||
--volume "$RENDEZVOUS_WORKSPACE_SOURCE:/source" \
|
||||
--volume "$RENDEZVOUS_TEMP_SOURCE:${RUNNER_TEMP}" \
|
||||
--workdir /source \
|
||||
"$release_builder" \
|
||||
./scripts/build-release.sh "$version" "${RUNNER_TEMP}/release/$version"
|
||||
./scripts/verify-real-consumers.sh "$version" "${RUNNER_TEMP}/release/$version"
|
||||
echo "RENDEZVOUS_VERSION=$version" >>"$GITHUB_ENV"
|
||||
echo "RENDEZVOUS_RELEASE_DIR=${RUNNER_TEMP}/release/$version" >>"$GITHUB_ENV"
|
||||
echo "RENDEZVOUS_RELEASE_DIR_SOURCE=${RENDEZVOUS_TEMP_SOURCE}/release/$version" >>"$GITHUB_ENV"
|
||||
echo "RENDEZVOUS_RELEASE_BUILDER=$release_builder" >>"$GITHUB_ENV"
|
||||
|
||||
- name: Build exact container candidate
|
||||
@@ -97,8 +136,8 @@ jobs:
|
||||
buildkit_version="$(docker buildx inspect --bootstrap | sed -n 's/.*BuildKit version: *//p' | sed -n '1p')"
|
||||
docker run --rm \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--volume "$GITHUB_WORKSPACE:/source" \
|
||||
--volume "${RUNNER_TEMP}:${RUNNER_TEMP}" \
|
||||
--volume "$RENDEZVOUS_WORKSPACE_SOURCE:/source" \
|
||||
--volume "$RENDEZVOUS_TEMP_SOURCE:${RUNNER_TEMP}" \
|
||||
--workdir /source \
|
||||
"$RENDEZVOUS_RELEASE_BUILDER" \
|
||||
python3 eng/release_artifacts.py record-container-build \
|
||||
@@ -131,9 +170,7 @@ jobs:
|
||||
port_suffix="$(( ${GITHUB_RUN_ID:-$$} % 10000 ))"
|
||||
export RENDEZVOUS_HTTP_HOST_PORT="$(( 20000 + port_suffix ))"
|
||||
export RENDEZVOUS_UDP_HOST_PORT="$(( 40000 + port_suffix ))"
|
||||
runner_workspace_source="$(docker inspect "$HOSTNAME" | jq -er \
|
||||
--arg destination "$GITHUB_WORKSPACE" \
|
||||
'.[0].Mounts[] | select(.Destination == $destination) | .Source')"
|
||||
runner_workspace_source="${RENDEZVOUS_WORKSPACE_SOURCE:?host workspace bind source was not resolved}"
|
||||
export RENDEZVOUS_CONFIG_SOURCE="$runner_workspace_source/deploy/compose/appsettings.Production.json"
|
||||
export RENDEZVOUS_SECRET_SOURCE="$runner_workspace_source/deploy/compose/secrets/signing-key"
|
||||
export RENDEZVOUS_IMAGE="git.finalfactory.de/heikyu/rendezvous:${RENDEZVOUS_VERSION}"
|
||||
@@ -186,8 +223,8 @@ jobs:
|
||||
source_date_epoch="$(git show -s --format=%ct HEAD)"
|
||||
docker run --rm \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--volume "$GITHUB_WORKSPACE:/source" \
|
||||
--volume "${RUNNER_TEMP}:${RUNNER_TEMP}" \
|
||||
--volume "$RENDEZVOUS_WORKSPACE_SOURCE:/source" \
|
||||
--volume "$RENDEZVOUS_TEMP_SOURCE:${RUNNER_TEMP}" \
|
||||
--workdir /source \
|
||||
"$RENDEZVOUS_RELEASE_BUILDER" \
|
||||
bash -c 'python3 eng/release_artifacts.py normalize-container-sbom \
|
||||
@@ -218,4 +255,6 @@ jobs:
|
||||
RENDEZVOUS_RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
||||
RENDEZVOUS_WORKSPACE_SOURCE: ${{ env.RENDEZVOUS_WORKSPACE_SOURCE }}
|
||||
RENDEZVOUS_RELEASE_DIR_SOURCE: ${{ env.RENDEZVOUS_RELEASE_DIR_SOURCE }}
|
||||
run: ./scripts/publish-release.sh "$RENDEZVOUS_VERSION" "$RENDEZVOUS_RELEASE_DIR"
|
||||
|
||||
@@ -10,6 +10,12 @@ image="$registry/heikyu/rendezvous:$version"
|
||||
token="${RENDEZVOUS_RELEASE_TOKEN:?RENDEZVOUS_RELEASE_TOKEN is required}"
|
||||
username="${RENDEZVOUS_RELEASE_USERNAME:?RENDEZVOUS_RELEASE_USERNAME is required}"
|
||||
release_builder="${RENDEZVOUS_RELEASE_BUILDER:?RENDEZVOUS_RELEASE_BUILDER is required}"
|
||||
# Sibling-container runners execute this script inside a container while
|
||||
# `docker run` starts containers on the host daemon, so bind sources must be
|
||||
# host paths. The workflow resolves them once and exports them; a direct host
|
||||
# run keeps the local paths.
|
||||
workspace_source="${RENDEZVOUS_WORKSPACE_SOURCE:-$root}"
|
||||
release_dir_source="${RENDEZVOUS_RELEASE_DIR_SOURCE:-$release_dir}"
|
||||
docker_config="$(mktemp -d)"
|
||||
curl_config="$(mktemp)"
|
||||
release_request=""
|
||||
@@ -33,11 +39,21 @@ for command in cosign curl docker dotnet jq; do
|
||||
}
|
||||
done
|
||||
|
||||
# Publication is a one-way gate: Gitea package versions are immutable, so the
|
||||
# signing material must be proven usable before anything is pushed. Never print
|
||||
# the key or the probe output.
|
||||
: "${COSIGN_PRIVATE_KEY:?COSIGN_PRIVATE_KEY is required}"
|
||||
: "${COSIGN_PASSWORD:?COSIGN_PASSWORD is required}"
|
||||
cosign public-key --key env://COSIGN_PRIVATE_KEY >/dev/null || {
|
||||
echo "Configured cosign key could not be loaded; refusing to publish." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
run_release_builder() {
|
||||
docker run --rm \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--volume "$root:/source:ro" \
|
||||
--volume "$release_dir:$release_dir" \
|
||||
--volume "$workspace_source:/source:ro" \
|
||||
--volume "$release_dir_source:$release_dir" \
|
||||
--workdir /source \
|
||||
"$release_builder" "$@"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
version="${1:?usage: verify-real-consumers.sh VERSION RELEASE_DIRECTORY}"
|
||||
release_dir="${2:?usage: verify-real-consumers.sh VERSION RELEASE_DIRECTORY}"
|
||||
manifest="$root/eng/consumer-revisions.json"
|
||||
# The pinned consumers live in private repositories, so release automation must
|
||||
# authenticate. Keep the token in the environment git reads config from: it must
|
||||
# never reach argv, the remote URL, or on-disk repository configuration.
|
||||
token="${RENDEZVOUS_CONSUMER_TOKEN:-${RENDEZVOUS_RELEASE_TOKEN:-}}"
|
||||
export GIT_TERMINAL_PROMPT=0
|
||||
work="$(mktemp -d "${TMPDIR:-/tmp}/rendezvous-consumers.XXXXXX")"
|
||||
cleanup() {
|
||||
rm -rf "$work"
|
||||
@@ -18,6 +23,20 @@ for command in dotnet git jq python3; do
|
||||
}
|
||||
done
|
||||
|
||||
fetch_consumer_revision() {
|
||||
local checkout="$1"
|
||||
local repository="$2"
|
||||
local revision="$3"
|
||||
if [[ -z "$token" ]]; then
|
||||
git -C "$checkout" fetch --quiet --depth 1 origin "$revision"
|
||||
return
|
||||
fi
|
||||
GIT_CONFIG_COUNT=1 \
|
||||
GIT_CONFIG_KEY_0="http.${repository}.extraHeader" \
|
||||
GIT_CONFIG_VALUE_0="Authorization: token ${token}" \
|
||||
git -C "$checkout" fetch --quiet --depth 1 origin "$revision"
|
||||
}
|
||||
|
||||
python3 "$root/eng/release_artifacts.py" consumer-config \
|
||||
--local-source "$release_dir" \
|
||||
--output "$work/NuGet.config"
|
||||
@@ -31,7 +50,7 @@ for ((index = 0; index < count; index++)); do
|
||||
checkout="$work/$name"
|
||||
git -c init.defaultBranch=main init --quiet "$checkout"
|
||||
git -C "$checkout" remote add origin "$repository"
|
||||
git -C "$checkout" fetch --quiet --depth 1 origin "$revision"
|
||||
fetch_consumer_revision "$checkout" "$repository" "$revision"
|
||||
GIT_LFS_SKIP_SMUDGE=1 git -C "$checkout" checkout --quiet --detach FETCH_HEAD
|
||||
[[ "$(git -C "$checkout" rev-parse HEAD)" == "$revision" ]] || {
|
||||
echo "$name did not resolve the pinned consumer revision." >&2
|
||||
|
||||
Reference in New Issue
Block a user