diff --git a/.dockerignore b/.dockerignore index 4cc783c..f6a697e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,6 +6,7 @@ .agents **/bin **/obj +.release-work TestResults deploy/compose/secrets deploy/compose/.smoke.env diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index e172ced..5cc4708 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -20,38 +20,36 @@ jobs: with: fetch-depth: 0 - - name: Resolve host bind sources for sibling containers + - 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. Resolve them once from the runner's own - # mounts and reuse them in every later step. + # 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 workspace and temp paths as host paths." + echo "Runner is not containerized; using the workspace path as its own host path." 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")" + 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" - echo "RENDEZVOUS_TEMP_SOURCE=$temp_source" >>"$GITHUB_ENV" - name: Install pinned .NET SDKs uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 @@ -94,20 +92,19 @@ jobs: --target release-builder \ --load \ --tag "$release_builder" . - mkdir -p "${RUNNER_TEMP}/release-home" "${RUNNER_TEMP}/nuget" + 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="${RUNNER_TEMP}/release-home" \ - --env NUGET_PACKAGES="${RUNNER_TEMP}/nuget" \ - --volume "$RENDEZVOUS_WORKSPACE_SOURCE:/source" \ - --volume "$RENDEZVOUS_TEMP_SOURCE:${RUNNER_TEMP}" \ - --workdir /source \ + --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" "${RUNNER_TEMP}/release/$version" - ./scripts/verify-real-consumers.sh "$version" "${RUNNER_TEMP}/release/$version" + ./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=${RUNNER_TEMP}/release/$version" >>"$GITHUB_ENV" - echo "RENDEZVOUS_RELEASE_DIR_SOURCE=${RENDEZVOUS_TEMP_SOURCE}/release/$version" >>"$GITHUB_ENV" + echo "RENDEZVOUS_RELEASE_DIR=$release_dir" >>"$GITHUB_ENV" echo "RENDEZVOUS_RELEASE_BUILDER=$release_builder" >>"$GITHUB_ENV" - name: Build exact container candidate @@ -124,8 +121,10 @@ jobs: --build-arg SOURCE_REVISION_ID="$GITHUB_SHA" ) release_tag="git.finalfactory.de/heikyu/rendezvous:${RENDEZVOUS_VERSION}" - image_one="${RUNNER_TEMP}/rendezvous-image-1.tar" - image_two="${RUNNER_TEMP}/rendezvous-image-2.tar" + # .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" \ @@ -136,9 +135,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 "$RENDEZVOUS_WORKSPACE_SOURCE:/source" \ - --volume "$RENDEZVOUS_TEMP_SOURCE:${RUNNER_TEMP}" \ - --workdir /source \ + --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" \ @@ -184,8 +182,8 @@ jobs: --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:/source:ro" \ - --workdir /source \ + --volume "$runner_workspace_source:$GITHUB_WORKSPACE:ro" \ + --workdir "$GITHUB_WORKSPACE" \ "$RENDEZVOUS_RELEASE_BUILDER" \ bash -lc ' for attempt in {1..180}; do @@ -223,9 +221,8 @@ jobs: source_date_epoch="$(git show -s --format=%ct HEAD)" docker run --rm \ --user "$(id -u):$(id -g)" \ - --volume "$RENDEZVOUS_WORKSPACE_SOURCE:/source" \ - --volume "$RENDEZVOUS_TEMP_SOURCE:${RUNNER_TEMP}" \ - --workdir /source \ + --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" \ @@ -256,5 +253,4 @@ jobs: 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" diff --git a/.gitignore b/.gitignore index 7bc481d..317d7a6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ TestResults/ *.userosscache deploy/compose/.smoke.env artifacts/ +.release-work/ __pycache__/ *.pyc deploy/compose/secrets/* diff --git a/eng/release_artifacts.py b/eng/release_artifacts.py index d53252a..fc2bf33 100644 --- a/eng/release_artifacts.py +++ b/eng/release_artifacts.py @@ -30,7 +30,10 @@ def load_json(path: pathlib.Path): def dependency_inventory(root: pathlib.Path): dependencies = {} for lock_path in sorted(root.glob("**/packages.lock.json")): - if any(part in {"bin", "obj", "artifacts"} for part in lock_path.parts): + if any( + part in {"bin", "obj", "artifacts", ".release-work"} + for part in lock_path.parts + ): continue lock = load_json(lock_path) for framework in lock.get("dependencies", {}).values(): diff --git a/scripts/publish-release.sh b/scripts/publish-release.sh index 25367ef..ced2c3e 100755 --- a/scripts/publish-release.sh +++ b/scripts/publish-release.sh @@ -11,11 +11,21 @@ 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. +# `docker run` starts containers on the host daemon, so the bind source must be +# a host path. The workflow resolves the workspace once and exports it; a direct +# host run keeps the local path. Only the workspace is bound, so the release +# directory has to live inside it, and binding it back onto its own path keeps +# every shared path identical on both sides of the boundary. workspace_source="${RENDEZVOUS_WORKSPACE_SOURCE:-$root}" -release_dir_source="${RENDEZVOUS_RELEASE_DIR_SOURCE:-$release_dir}" +[[ -d "$release_dir" ]] || { + echo "Release directory does not exist: $release_dir" >&2 + exit 1 +} +release_dir="$(cd "$release_dir" && pwd)" +if [[ "$release_dir" != "$root"/* ]]; then + echo "Release directory must live inside the workspace: $release_dir" >&2 + exit 1 +fi docker_config="$(mktemp -d)" curl_config="$(mktemp)" release_request="" @@ -52,9 +62,8 @@ cosign public-key --key env://COSIGN_PRIVATE_KEY >/dev/null || { run_release_builder() { docker run --rm \ --user "$(id -u):$(id -g)" \ - --volume "$workspace_source:/source:ro" \ - --volume "$release_dir_source:$release_dir" \ - --workdir /source \ + --volume "$workspace_source:$root" \ + --workdir "$root" \ "$release_builder" "$@" }