The release job stopped at the bind-source gate. RUNNER_TEMP is container-internal on this runner and no runner mount exposes it on the Docker host, so no sibling container could ever share it; only the workspace is host-mounted. Move every path shared between the runner's shell steps and its sibling containers under $GITHUB_WORKSPACE/.release-work: the release directory, the release builder's HOME and NuGet cache, both candidate image tars, and the container SPDX inventory. Resolution now maps the workspace alone to its host path, and each sibling binds that source at $GITHUB_WORKSPACE and works from there instead of /source, so a shared path is the same string on both sides of the boundary. publish-release.sh follows with a single bind and requires the release directory to sit inside the workspace. Ignore .release-work in Git so the tag gate's cleanliness check stays true while artifacts accumulate, and in Docker so artifacts written between the two candidate builds cannot alter the build context the byte-comparison gate depends on. Skip it in the dependency inventory as well, keeping the restored package cache out of the license policy scan.
257 lines
12 KiB
YAML
257 lines
12 KiB
YAML
name: immutable-release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*.*.*"
|
|
|
|
concurrency:
|
|
group: release-${{ gitea.ref_name }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
release:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
environment: production
|
|
steps:
|
|
- name: Check out immutable tag
|
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Resolve the host bind source 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. The workspace is the only runner mount
|
|
# backed by the host, so every path shared with a sibling lives under
|
|
# it and a single bind source is resolved once here. Siblings mount
|
|
# that source at $GITHUB_WORKSPACE, which keeps every shared path the
|
|
# same string on both sides of the boundary.
|
|
echo "RENDEZVOUS_WORK_DIR=$GITHUB_WORKSPACE/.release-work" >>"$GITHUB_ENV"
|
|
if ! mounts="$(docker inspect "$HOSTNAME" 2>/dev/null | jq -c '.[0].Mounts')"; then
|
|
echo "Runner is not containerized; using the workspace path as its own host path."
|
|
echo "RENDEZVOUS_WORKSPACE_SOURCE=$GITHUB_WORKSPACE" >>"$GITHUB_ENV"
|
|
exit 0
|
|
fi
|
|
workspace_source="$(jq -er --arg path "$GITHUB_WORKSPACE" '
|
|
[ .[]
|
|
| .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")"
|
|
echo "RENDEZVOUS_WORKSPACE_SOURCE=$workspace_source" >>"$GITHUB_ENV"
|
|
|
|
- name: Install pinned .NET SDKs
|
|
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
|
|
with:
|
|
dotnet-version: |
|
|
8.0.128
|
|
10.0.301
|
|
|
|
- name: Install pinned Buildx and BuildKit
|
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
|
with:
|
|
version: v0.35.0
|
|
install: true
|
|
driver-opts: image=moby/buildkit:v0.25.2@sha256:0f63d66f8d2de0bd16438284831a3e9ee6ca7cd57b6eb3ed6e38a7a456590fa7
|
|
|
|
- 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 "$unrelated_tag" ]]; then
|
|
echo "No prior release tag is an ancestor of $GITHUB_REF_NAME." >&2
|
|
exit 1
|
|
else
|
|
./scripts/check-compatibility.sh __initial_release_without_base__
|
|
fi
|
|
release_builder="rendezvous-release-builder:${GITHUB_SHA}"
|
|
docker buildx build \
|
|
--platform linux/amd64 \
|
|
--file eng/release-builder.Dockerfile \
|
|
--target release-builder \
|
|
--load \
|
|
--tag "$release_builder" .
|
|
release_dir="$RENDEZVOUS_WORK_DIR/release/$version"
|
|
mkdir -p "$RENDEZVOUS_WORK_DIR/release-home" "$RENDEZVOUS_WORK_DIR/nuget"
|
|
docker run --rm \
|
|
--user "$(id -u):$(id -g)" \
|
|
--env HOME="$RENDEZVOUS_WORK_DIR/release-home" \
|
|
--env NUGET_PACKAGES="$RENDEZVOUS_WORK_DIR/nuget" \
|
|
--volume "$RENDEZVOUS_WORKSPACE_SOURCE:$GITHUB_WORKSPACE" \
|
|
--workdir "$GITHUB_WORKSPACE" \
|
|
"$release_builder" \
|
|
./scripts/build-release.sh "$version" "$release_dir"
|
|
./scripts/verify-real-consumers.sh "$version" "$release_dir"
|
|
echo "RENDEZVOUS_VERSION=$version" >>"$GITHUB_ENV"
|
|
echo "RENDEZVOUS_RELEASE_DIR=$release_dir" >>"$GITHUB_ENV"
|
|
echo "RENDEZVOUS_RELEASE_BUILDER=$release_builder" >>"$GITHUB_ENV"
|
|
|
|
- name: Build exact container candidate
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
export SOURCE_DATE_EPOCH="$(git show -s --format=%ct HEAD)"
|
|
common=(
|
|
--no-cache
|
|
--pull=false
|
|
--provenance=false
|
|
--platform linux/amd64
|
|
--build-arg SOURCE_DATE_EPOCH="$SOURCE_DATE_EPOCH"
|
|
--build-arg SOURCE_REVISION_ID="$GITHUB_SHA"
|
|
)
|
|
release_tag="git.finalfactory.de/heikyu/rendezvous:${RENDEZVOUS_VERSION}"
|
|
# .release-work is excluded from the build context, so the tar written
|
|
# by the first build cannot change the context the second one sees.
|
|
image_one="$RENDEZVOUS_WORK_DIR/rendezvous-image-1.tar"
|
|
image_two="$RENDEZVOUS_WORK_DIR/rendezvous-image-2.tar"
|
|
docker buildx build "${common[@]}" --tag "$release_tag" \
|
|
--output "type=docker,dest=$image_one,rewrite-timestamp=true" .
|
|
docker buildx build "${common[@]}" --tag "$release_tag" \
|
|
--output "type=docker,dest=$image_two,rewrite-timestamp=true" .
|
|
cmp --silent "$image_one" "$image_two"
|
|
docker load --input "$image_one"
|
|
candidate_id="$(docker image inspect --format '{{.Id}}' "$release_tag")"
|
|
buildkit_version="$(docker buildx inspect --bootstrap | sed -n 's/.*BuildKit version: *//p' | sed -n '1p')"
|
|
docker run --rm \
|
|
--user "$(id -u):$(id -g)" \
|
|
--volume "$RENDEZVOUS_WORKSPACE_SOURCE:$GITHUB_WORKSPACE" \
|
|
--workdir "$GITHUB_WORKSPACE" \
|
|
"$RENDEZVOUS_RELEASE_BUILDER" \
|
|
python3 eng/release_artifacts.py record-container-build \
|
|
--provenance "$RENDEZVOUS_RELEASE_DIR/release-provenance.json" \
|
|
--buildx-version "$(docker buildx version)" \
|
|
--buildkit-version "$buildkit_version" \
|
|
--image-id "$candidate_id"
|
|
|
|
- name: Stage HTTP registration, browse, and authenticated UDP traversal
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
secret="deploy/compose/secrets/signing-key"
|
|
cleanup() {
|
|
RENDEZVOUS_UID=1654 RENDEZVOUS_GID=1654 RENDEZVOUS_IMAGE="git.finalfactory.de/heikyu/rendezvous:${RENDEZVOUS_VERSION}" \
|
|
docker compose -f deploy/compose/compose.yaml down --volumes >/dev/null 2>&1 || true
|
|
rm -f "$secret"
|
|
}
|
|
trap cleanup EXIT
|
|
umask 077
|
|
install -d -m 0700 deploy/compose/secrets
|
|
openssl rand -out "$secret" 32
|
|
chmod 0600 "$secret"
|
|
publisher_credential="$(RENDEZVOUS_SMOKE_LOCAL_KEY="$secret" \
|
|
./scripts/mint-local-publisher-credential.sh)"
|
|
export RENDEZVOUS_PUBLISHER_CREDENTIAL="$publisher_credential"
|
|
chmod 0444 "$secret"
|
|
export RENDEZVOUS_UID=1654
|
|
export RENDEZVOUS_GID=1654
|
|
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="${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}"
|
|
docker compose -f deploy/compose/compose.yaml up --detach --no-build
|
|
container_id="$(docker compose -f deploy/compose/compose.yaml ps -q rendezvous)"
|
|
test -n "$container_id"
|
|
docker run --rm \
|
|
--network "container:${container_id}" \
|
|
--user "$(id -u):$(id -g)" \
|
|
--env HOME=/tmp \
|
|
--env RENDEZVOUS_SMOKE_HTTP_URL=http://127.0.0.1:8080/ \
|
|
--env RENDEZVOUS_SMOKE_UDP_ENDPOINT=127.0.0.1:9050 \
|
|
--env RENDEZVOUS_PUBLISHER_CREDENTIAL \
|
|
--volume "$runner_workspace_source:$GITHUB_WORKSPACE:ro" \
|
|
--workdir "$GITHUB_WORKSPACE" \
|
|
"$RENDEZVOUS_RELEASE_BUILDER" \
|
|
bash -lc '
|
|
for attempt in {1..180}; do
|
|
curl --fail --silent "${RENDEZVOUS_SMOKE_HTTP_URL%/}/health/ready" >/dev/null 2>&1 && exec ./scripts/smoke-deployment.sh
|
|
sleep 1
|
|
done
|
|
exit 1
|
|
' || {
|
|
docker compose -f deploy/compose/compose.yaml logs rendezvous
|
|
exit 1
|
|
}
|
|
|
|
- name: Scan candidate for high and critical vulnerabilities
|
|
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0, post-incident safe SHA
|
|
with:
|
|
image-ref: git.finalfactory.de/heikyu/rendezvous:${{ env.RENDEZVOUS_VERSION }}
|
|
version: v0.69.3
|
|
format: table
|
|
exit-code: "1"
|
|
ignore-unfixed: false
|
|
severity: HIGH,CRITICAL
|
|
|
|
- name: Generate container SPDX inventory
|
|
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0, post-incident safe SHA
|
|
with:
|
|
image-ref: git.finalfactory.de/heikyu/rendezvous:${{ env.RENDEZVOUS_VERSION }}
|
|
version: v0.69.3
|
|
format: spdx-json
|
|
output: ${{ env.RENDEZVOUS_RELEASE_DIR }}/FinalFactory.Rendezvous.Container.${{ env.RENDEZVOUS_VERSION }}.spdx.json
|
|
|
|
- name: Finalize checksums over the publish-ready candidate
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
source_date_epoch="$(git show -s --format=%ct HEAD)"
|
|
docker run --rm \
|
|
--user "$(id -u):$(id -g)" \
|
|
--volume "$RENDEZVOUS_WORKSPACE_SOURCE:$GITHUB_WORKSPACE" \
|
|
--workdir "$GITHUB_WORKSPACE" \
|
|
"$RENDEZVOUS_RELEASE_BUILDER" \
|
|
bash -c 'python3 eng/release_artifacts.py normalize-container-sbom \
|
|
--file "$1/FinalFactory.Rendezvous.Container.$2.spdx.json" \
|
|
--version "$2" \
|
|
--commit "$3" \
|
|
--source-date-epoch "$4" \
|
|
&& ./scripts/finalize-release-candidate.sh "$2" "$1"' \
|
|
_ "$RENDEZVOUS_RELEASE_DIR" "$RENDEZVOUS_VERSION" "$GITHUB_SHA" "$source_date_epoch"
|
|
|
|
- name: Preserve verified candidate artifacts
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: rendezvous-${{ env.RENDEZVOUS_VERSION }}
|
|
path: ${{ env.RENDEZVOUS_RELEASE_DIR }}
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
|
|
- name: Install pinned signing client
|
|
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
|
with:
|
|
cosign-release: v3.0.6
|
|
|
|
- name: Publish once, sign, attest, and create release
|
|
shell: bash
|
|
env:
|
|
RENDEZVOUS_RELEASE_USERNAME: ${{ secrets.RELEASE_USERNAME }}
|
|
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 }}
|
|
run: ./scripts/publish-release.sh "$RENDEZVOUS_VERSION" "$RENDEZVOUS_RELEASE_DIR"
|