Release VM Assets #34
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release VM Assets | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| channel: | |
| description: Asset channel to publish. | |
| required: true | |
| type: choice | |
| options: | |
| - stable | |
| - nightly | |
| profile: | |
| description: Profile id that owns the VM image build. | |
| required: true | |
| dry_run: | |
| description: Build and validate without deploying release.capsem.org. | |
| required: true | |
| type: boolean | |
| default: true | |
| concurrency: | |
| group: capsem-release-${{ inputs.channel }} | |
| cancel-in-progress: false | |
| permissions: | |
| actions: read | |
| attestations: write | |
| contents: write | |
| deployments: write | |
| id-token: write | |
| env: | |
| ASSET_MANIFEST_URL: https://release.capsem.org/assets/${{ inputs.channel }}/manifest.json | |
| jobs: | |
| runtime-preflight: | |
| uses: ./.github/workflows/release-runtime-preflight.yaml | |
| with: | |
| channel: ${{ inputs.channel }} | |
| bootstrap_missing_first_party: true | |
| fast-gate: | |
| uses: ./.github/workflows/fast-gate.yaml | |
| resolve-current-binary: | |
| needs: runtime-preflight | |
| runs-on: ubuntu-latest | |
| name: Resolve current channel binary | |
| outputs: | |
| functional_ready: ${{ steps.functional-cohort.outputs.functional-ready }} | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c | |
| with: | |
| toolchain: 1.97.1 | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 | |
| with: | |
| key: profile-release-selection | |
| - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 | |
| - run: uv sync | |
| - name: Validate selected channel profile through capsem-admin | |
| run: | | |
| mkdir -p target | |
| cargo run -p capsem-admin -- validate \ | |
| --channel "${{ inputs.channel }}" \ | |
| --profile "${{ inputs.profile }}" \ | |
| --json > target/profile-release-selection.json | |
| - name: Fetch latest selected channel source manifest | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| python3 scripts/fetch-channel-source-manifest.py \ | |
| --channel "${{ inputs.channel }}" \ | |
| --profile "${{ inputs.profile }}" \ | |
| --bootstrap-missing-first-party \ | |
| --output target/channel-source/manifest.json | |
| - name: Fetch exact deployed public-before package | |
| uses: ./.github/actions/fetch-release-inputs | |
| with: | |
| manifest-url: ${{ env.ASSET_MANIFEST_URL }} | |
| bootstrap-manifest-url: file://${{ github.workspace }}/target/channel-source/manifest.json | |
| kind: packages | |
| output: target/profile-public-before/packages | |
| - name: Check pulled package functional binary cohort | |
| id: functional-cohort | |
| run: | | |
| uv run python scripts/stage-release-test-inputs.py \ | |
| --input-dir target/profile-public-before/packages \ | |
| --check-functional-cohort \ | |
| --github-output "$GITHUB_OUTPUT" | |
| - name: Fetch exact deployed public-before profiles | |
| uses: ./.github/actions/fetch-release-inputs | |
| with: | |
| manifest-url: ${{ env.ASSET_MANIFEST_URL }} | |
| bootstrap-manifest-url: file://${{ github.workspace }}/target/channel-source/manifest.json | |
| allow-empty-profiles: "true" | |
| kind: profiles | |
| architecture: x86_64 | |
| output: target/profile-public-before/profiles | |
| - name: Prove public-before cohorts use one manifest | |
| run: | | |
| uv run python scripts/verify-release-inputs.py \ | |
| --input-dir target/profile-public-before/profiles | |
| cmp \ | |
| target/profile-public-before/packages/manifest.json \ | |
| target/profile-public-before/profiles/manifest.json | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: profile-public-before-packages | |
| path: target/profile-public-before/packages/ | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: profile-public-before-profiles | |
| path: target/profile-public-before/profiles/ | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: profile-channel-source | |
| path: target/channel-source/manifest.json | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: profile-release-selection | |
| path: target/profile-release-selection.json | |
| cloudflare-release-site-preflight: | |
| runs-on: ubuntu-latest | |
| name: Cloudflare release site preflight | |
| env: | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| DRY_RUN: ${{ inputs.dry_run }} | |
| RELEASE_CHANNEL_PROJECT: release | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| - name: Verify Cloudflare Pages project for live publish | |
| run: | | |
| set -euo pipefail | |
| if [[ "$DRY_RUN" == "true" ]]; then | |
| echo "Dry run: skipping Cloudflare Pages project preflight." | |
| exit 0 | |
| fi | |
| python scripts/check-cloudflare-pages-project.py \ | |
| --project "$RELEASE_CHANNEL_PROJECT" | |
| resolve-profile-assets: | |
| needs: resolve-current-binary | |
| runs-on: ubuntu-latest | |
| name: Resolve reusable profile assets | |
| outputs: | |
| reuse_run_id: ${{ steps.resolve.outputs.run_id }} | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: profile-release-selection | |
| path: target/ | |
| - name: Resolve exact prior profile asset cohort | |
| id: resolve | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| python3 scripts/resolve-reusable-profile-assets.py \ | |
| --repository "${{ github.repository }}" \ | |
| --workflow release-assets.yaml \ | |
| --current-run-id "${{ github.run_id }}" \ | |
| --selection target/profile-release-selection.json \ | |
| --github-output "$GITHUB_OUTPUT" | |
| build-assets: | |
| needs: [cloudflare-release-site-preflight, resolve-current-binary, fast-gate, resolve-profile-assets] | |
| if: ${{ needs.resolve-profile-assets.outputs.reuse_run_id == '' }} | |
| strategy: | |
| matrix: | |
| include: | |
| - arch: arm64 | |
| runner: ubuntu-24.04-arm | |
| rust-target: aarch64-unknown-linux-musl | |
| - arch: x86_64 | |
| runner: ubuntu-24.04 | |
| rust-target: x86_64-unknown-linux-musl | |
| runs-on: ${{ matrix.runner }} | |
| name: Build VM assets (${{ matrix.arch }}) | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c | |
| - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 | |
| - run: uv sync | |
| - uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b | |
| - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 | |
| with: | |
| version: 10 | |
| - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| cache-dependency-path: frontend/pnpm-lock.yaml | |
| - name: Install musl C toolchain | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends musl-tools | |
| - name: Install OBOM generator | |
| run: | | |
| npm install -g @cyclonedx/cdxgen@12.7.0 | |
| cdxgen --version | |
| - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c | |
| with: | |
| toolchain: 1.97.1 | |
| targets: ${{ matrix.rust-target }} | |
| - name: Build VM assets (kernel + rootfs) | |
| env: | |
| CAPSEM_CDXGEN_CMD: cdxgen | |
| CC_aarch64_unknown_linux_musl: musl-gcc | |
| CC_x86_64_unknown_linux_musl: musl-gcc | |
| CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: musl-gcc | |
| CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: musl-gcc | |
| run: | | |
| just _build-kernel ${{ matrix.arch }} "${{ inputs.profile }}" | |
| just _build-rootfs ${{ matrix.arch }} "${{ inputs.profile }}" | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: vm-assets-${{ matrix.arch }} | |
| path: assets/${{ matrix.arch }}/ | |
| reuse-assets: | |
| needs: [cloudflare-release-site-preflight, fast-gate, resolve-profile-assets] | |
| if: ${{ needs.resolve-profile-assets.outputs.reuse_run_id != '' }} | |
| strategy: | |
| matrix: | |
| arch: [arm64, x86_64] | |
| runs-on: ubuntu-latest | |
| name: Reuse VM assets (${{ matrix.arch }}) | |
| steps: | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: vm-assets-${{ matrix.arch }} | |
| path: assets/${{ matrix.arch }}/ | |
| repository: ${{ github.repository }} | |
| run-id: ${{ needs.resolve-profile-assets.outputs.reuse_run_id }} | |
| github-token: ${{ github.token }} | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: vm-assets-${{ matrix.arch }} | |
| path: assets/${{ matrix.arch }}/ | |
| test-profile-pairing: | |
| needs: [author-profile-release, resolve-current-binary] | |
| if: ${{ always() && needs.author-profile-release.result == 'success' && needs.resolve-current-binary.result == 'success' && needs.author-profile-release.outputs.profile_changed == 'true' }} | |
| runs-on: ubuntu-24.04 | |
| name: Complete profile pairing gate | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 | |
| - run: uv sync | |
| - uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b | |
| - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 | |
| with: | |
| version: 10 | |
| - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| cache-dependency-path: frontend/pnpm-lock.yaml | |
| - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c | |
| with: | |
| toolchain: 1.97.1 | |
| components: llvm-tools | |
| targets: aarch64-unknown-linux-musl,x86_64-unknown-linux-musl | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: profile-public-before-packages | |
| path: target/profile-public-before/packages/ | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: profile-public-before-profiles | |
| path: target/profile-public-before/profiles/ | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: authored-profile-channel-source | |
| path: target/source-channel/ | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: authored-profile-publication | |
| path: target/asset-release/ | |
| - name: Enable KVM for complete functional proof | |
| run: | | |
| set -euo pipefail | |
| sudo modprobe kvm | |
| sudo chmod 0666 /dev/kvm | |
| sudo modprobe vhost_vsock | |
| sudo chmod 0666 /dev/vhost-vsock | |
| test -r /dev/kvm -a -w /dev/kvm | |
| test -r /dev/vhost-vsock -a -w /dev/vhost-vsock | |
| - name: Prepare exact profile and pulled binary pairing | |
| run: | | |
| set -euo pipefail | |
| uv run python scripts/verify-release-inputs.py \ | |
| --input-dir target/profile-public-before/packages | |
| uv run python scripts/verify-release-inputs.py \ | |
| --input-dir target/profile-public-before/profiles | |
| cmp \ | |
| target/profile-public-before/packages/manifest.json \ | |
| target/profile-public-before/profiles/manifest.json | |
| PUBLICATION_IDENTITY="${{ needs.author-profile-release.outputs.publication_identity }}" | |
| PUBLICATION_BASE="https://github.com/${{ github.repository }}/releases/download/$PUBLICATION_IDENTITY" | |
| PUBLICATION_DIR="target/asset-release/$PUBLICATION_IDENTITY" | |
| uv run python scripts/fetch-release-artifacts.py \ | |
| --manifest-url "file://$PWD/target/source-channel/manifest.json" \ | |
| --kind profiles \ | |
| --output target/candidate-profile-inputs \ | |
| --local-publication-base "$PUBLICATION_BASE" \ | |
| --local-publication-dir "$PUBLICATION_DIR" | |
| uv run python scripts/verify-release-inputs.py \ | |
| --input-dir target/candidate-profile-inputs | |
| uv run python scripts/stage-release-test-inputs.py \ | |
| --input-dir target/profile-public-before/packages \ | |
| --binary-dir target/debug | |
| uv run python scripts/stage-release-test-inputs.py \ | |
| --input-dir target/candidate-profile-inputs \ | |
| --assets-dir assets \ | |
| --config-root target/release-config \ | |
| --shared-config-root config | |
| CAPSEM_ASSET_MANIFEST="file://$PWD/assets/manifest.json" \ | |
| CAPSEM_CONFIG_ROOT="$PWD/target/release-config" \ | |
| CAPSEM_CONFIG_OUTPUT_ROOT="$PWD/target/config" \ | |
| bash scripts/materialize-config.sh | |
| package=$(uv run python scripts/stage-release-test-inputs.py \ | |
| --input-dir target/profile-public-before/packages \ | |
| --print-package-path) | |
| test -n "$package" | |
| uv run python scripts/install-deb-runtime-dependencies.py "$package" | |
| { | |
| echo "CAPSEM_RELEASE_PACKAGE=$PWD/$package" | |
| echo "CAPSEM_RELEASE_BIN_DIR=$PWD/target/debug" | |
| echo "CAPSEM_RELEASE_INPUT_DIR=$PWD/target/candidate-profile-inputs" | |
| echo "CAPSEM_RELEASE_CHANNEL=${{ inputs.channel }}" | |
| echo "CAPSEM_RELEASE_TRANSITION=profile_only" | |
| echo "CAPSEM_RELEASE_BEFORE_MANIFEST=$PWD/target/profile-public-before/profiles/manifest.json" | |
| echo "CAPSEM_RELEASE_AFTER_MANIFEST=$PWD/target/source-channel/manifest.json" | |
| echo "CAPSEM_RELEASE_BEFORE_PACKAGE=$PWD/$package" | |
| echo "CAPSEM_RELEASE_BEFORE_PROFILE_INPUTS=$PWD/target/profile-public-before/profiles" | |
| echo "CAPSEM_RELEASE_AFTER_PROFILE_INPUTS=$PWD/target/candidate-profile-inputs" | |
| echo "CAPSEM_RELEASE_PROFILE=${{ inputs.profile }}" | |
| echo "CAPSEM_RELEASE_CANDIDATE_PROFILE_PUBLICATION=$PWD/$PUBLICATION_DIR" | |
| echo "CAPSEM_RELEASE_PUBLICATION_BASE=$PUBLICATION_BASE" | |
| echo "CAPSEM_TEST_BINARY=$PWD/target/debug/capsem" | |
| echo "CAPSEM_TEST_ASSETS_DIR=$PWD/assets" | |
| echo "CAPSEM_TEST_CONFIG_ROOT=$PWD/target/config" | |
| } >> "$GITHUB_ENV" | |
| - name: Run shared artifact module | |
| run: just _test-artifacts | |
| - name: Run shared complete functional module | |
| if: ${{ needs.author-profile-release.outputs.compatible == 'true' }} | |
| run: just _test-functional | |
| - name: Run shared native and update glow-up module | |
| if: ${{ needs.author-profile-release.outputs.compatible == 'true' }} | |
| run: just _test-glowup | |
| - name: Run shared release contracts | |
| run: just _test-release-contracts | |
| author-profile-release: | |
| needs: [build-assets, reuse-assets, resolve-current-binary] | |
| if: ${{ always() && needs.resolve-current-binary.result == 'success' && ((needs.build-assets.result == 'success' && needs.reuse-assets.result == 'skipped') || (needs.build-assets.result == 'skipped' && needs.reuse-assets.result == 'success')) }} | |
| runs-on: ubuntu-latest | |
| name: Author selected profile release | |
| outputs: | |
| profile_changed: ${{ steps.profile-delta.outputs.changed }} | |
| compatible: ${{ steps.author-release.outputs.compatible }} | |
| publication_identity: ${{ steps.author-release.outputs.publication_identity }} | |
| env: | |
| CHANNEL: ${{ inputs.channel }} | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 | |
| with: | |
| version: 10 | |
| - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| cache-dependency-path: release-site/pnpm-lock.yaml | |
| - name: Install release site dependencies | |
| run: cd release-site && pnpm install --frozen-lockfile | |
| - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 | |
| - run: uv sync | |
| - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c | |
| with: | |
| toolchain: 1.97.1 | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: vm-assets-arm64 | |
| path: assets/arm64/ | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: vm-assets-x86_64 | |
| path: assets/x86_64/ | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: profile-channel-source | |
| path: target/source-channel/ | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: profile-release-selection | |
| path: target/ | |
| - name: Generate asset manifest | |
| run: | | |
| cargo run -p capsem-admin -- manifest generate assets | |
| - name: Verify profile release lane policy | |
| run: | | |
| uv run pytest \ | |
| tests/capsem-release/test_profile_lane_gate.py \ | |
| tests/capsem-release/test_release_lane_diff_policy.py -q | |
| - name: Build complete asset channel preview | |
| run: | | |
| PROFILE_IDENTITY=$(python3 -c 'import json; print(json.load(open("target/profile-release-selection.json"))["publication_identity"])') | |
| ASSET_BASE="https://github.com/${{ github.repository }}/releases/download/$PROFILE_IDENTITY" | |
| MANIFEST_VERSION=$(python3 -c 'import json; print(json.load(open("target/source-channel/manifest.json"))["version"])') | |
| uv run python scripts/build-complete-release-channel.py \ | |
| --channel-source "$CHANNEL=file://$PWD/assets/manifest.json" \ | |
| --primary-channel "$CHANNEL" \ | |
| --assets-dir assets \ | |
| --asset-source-base "$ASSET_BASE" \ | |
| --manifest-version "$MANIFEST_VERSION" \ | |
| --out-dir target/profile-candidate \ | |
| --allow-mirror-missing | |
| - name: Check selected channel profile delta | |
| id: profile-delta | |
| run: | | |
| uv run python scripts/check-profile-release-delta.py \ | |
| --source-manifest target/source-channel/manifest.json \ | |
| --candidate-manifest "target/profile-candidate/assets/$CHANNEL/manifest.json" \ | |
| --channel "$CHANNEL" \ | |
| --profile "${{ inputs.profile }}" \ | |
| --json-output target/profile-release-delta/delta.json | |
| - name: Author selected channel profile in the source manifest | |
| id: author-release | |
| if: ${{ steps.profile-delta.outputs.changed == 'true' }} | |
| run: | | |
| set -euo pipefail | |
| MANIFEST_VERSION=$(python3 -c 'import json; print(json.load(open("target/source-channel/manifest.json"))["version"])') | |
| PUBLICATION_IDENTITY=$(python3 -c 'import json; print(json.load(open("target/profile-release-selection.json"))["publication_identity"])') | |
| PUBLICATION_BASE="https://github.com/${{ github.repository }}/releases/download/$PUBLICATION_IDENTITY" | |
| PROFILE_VERSION=$(python3 - <<'PY' | |
| import json | |
| channel = "${{ inputs.channel }}" | |
| profile = "${{ inputs.profile }}" | |
| manifest = json.load(open(f"target/profile-candidate/assets/{channel}/manifest.json")) | |
| print(manifest["profiles"][profile]["revision"]) | |
| PY | |
| ) | |
| cargo run -p capsem-admin -- release \ | |
| --channel "$CHANNEL" \ | |
| --profile "${{ inputs.profile }}" \ | |
| --manifest-path target/source-channel/manifest.json \ | |
| --candidate-manifest "target/profile-candidate/assets/$CHANNEL/manifest.json" \ | |
| --publication-base "$PUBLICATION_BASE" \ | |
| --manifest-version "$MANIFEST_VERSION" \ | |
| --profile-version "$PROFILE_VERSION" \ | |
| --json > target/profile-release.json | |
| test "$(python3 -c 'import json; print(json.load(open("target/profile-release.json"))["publication_identity"])')" = "$PUBLICATION_IDENTITY" | |
| PRODUCT_COMPATIBLE=$(python3 -c 'import json; print(str(json.load(open("target/profile-release.json"))["compatible_with_current_binary"]).lower())') | |
| FUNCTIONAL_READY="${{ needs.resolve-current-binary.outputs.functional_ready }}" | |
| if [[ "$PRODUCT_COMPATIBLE" == "true" && "$FUNCTIONAL_READY" == "true" ]]; then | |
| COMPATIBLE=true | |
| else | |
| COMPATIBLE=false | |
| fi | |
| echo "compatible=$COMPATIBLE" >> "$GITHUB_OUTPUT" | |
| echo "publication_identity=$PUBLICATION_IDENTITY" >> "$GITHUB_OUTPUT" | |
| - name: Stage and verify immutable profile publication once | |
| if: ${{ steps.profile-delta.outputs.changed == 'true' }} | |
| env: | |
| PUBLICATION_IDENTITY: ${{ steps.author-release.outputs.publication_identity }} | |
| run: | | |
| set -euo pipefail | |
| PUBLICATION_BASE="https://github.com/${{ github.repository }}/releases/download/$PUBLICATION_IDENTITY" | |
| RELEASE_DIR="target/asset-release/$PUBLICATION_IDENTITY" | |
| uv run python scripts/stage-profile-publication.py \ | |
| --manifest target/source-channel/manifest.json \ | |
| --profile "${{ inputs.profile }}" \ | |
| --assets-dir assets \ | |
| --config-root config \ | |
| --release-dir "$RELEASE_DIR" | |
| uv run python scripts/verify-profile-publication.py \ | |
| --manifest "$RELEASE_DIR/channel-source-$CHANNEL.json" \ | |
| --profile "${{ inputs.profile }}" \ | |
| --publication-base "$PUBLICATION_BASE" \ | |
| --release-dir "$RELEASE_DIR" | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: authored-profile-channel-source | |
| path: target/source-channel/manifest.json | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: authored-profile-candidate | |
| path: target/profile-candidate/ | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| if: ${{ steps.profile-delta.outputs.changed == 'true' }} | |
| with: | |
| name: authored-profile-publication | |
| path: target/asset-release/ | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: profile-release-delta | |
| path: target/profile-release-delta/ | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| if: ${{ steps.profile-delta.outputs.changed == 'true' }} | |
| with: | |
| name: authored-profile-release-report | |
| path: target/profile-release.json | |
| publish-profile-release: | |
| needs: [author-profile-release, test-profile-pairing] | |
| if: ${{ always() && needs.author-profile-release.outputs.profile_changed == 'true' && needs.test-profile-pairing.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| name: Publish tested profile release | |
| outputs: | |
| profile_changed: ${{ needs.author-profile-release.outputs.profile_changed }} | |
| compatible: ${{ needs.author-profile-release.outputs.compatible }} | |
| env: | |
| CHANNEL: ${{ inputs.channel }} | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd | |
| - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 | |
| with: | |
| version: 10 | |
| - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| cache-dependency-path: release-site/pnpm-lock.yaml | |
| - name: Install release site dependencies | |
| run: cd release-site && pnpm install --frozen-lockfile | |
| - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 | |
| - run: uv sync | |
| - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c | |
| with: | |
| toolchain: 1.97.1 | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: vm-assets-arm64 | |
| path: assets/arm64/ | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: vm-assets-x86_64 | |
| path: assets/x86_64/ | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: authored-profile-channel-source | |
| path: target/source-channel/ | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: authored-profile-candidate | |
| path: target/profile-candidate/ | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: authored-profile-publication | |
| path: target/asset-release/ | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: profile-release-selection | |
| path: target/ | |
| - name: Build deployable channel from authored source manifest | |
| if: ${{ needs.author-profile-release.outputs.compatible == 'true' }} | |
| run: | | |
| uv run python scripts/build-complete-release-channel.py \ | |
| --channel-source "$CHANNEL=file://$PWD/target/source-channel/manifest.json" \ | |
| --primary-channel "$CHANNEL" \ | |
| --assets-dir assets \ | |
| --manifest-version "$(python3 -c 'import json; print(json.load(open("target/source-channel/manifest.json"))["version"])')" \ | |
| --profile-source-root target/profile-candidate \ | |
| --out-dir target/release-channel | |
| - name: Publish immutable GitHub profile release | |
| env: | |
| DRY_RUN: ${{ inputs.dry_run }} | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| TAG="${{ needs.author-profile-release.outputs.publication_identity }}" | |
| PROFILE_REVISION=$(python3 -c 'import json; print(json.load(open("target/profile-release-selection.json"))["profile_revision"])') | |
| RELEASE_DIR="target/asset-release/$TAG" | |
| NOTES_FILE="target/asset-release/profile-release-notes.md" | |
| PUBLICATION_BASE="https://github.com/${{ github.repository }}/releases/download/$TAG" | |
| uv run python scripts/verify-profile-publication.py \ | |
| --manifest "$RELEASE_DIR/channel-source-$CHANNEL.json" \ | |
| --profile "${{ inputs.profile }}" \ | |
| --publication-base "$PUBLICATION_BASE" \ | |
| --release-dir "$RELEASE_DIR" | |
| files=("$RELEASE_DIR"/*) | |
| test "${#files[@]}" -gt 0 | |
| printf '%s\n' \ | |
| "Immutable Capsem profile release for $CHANNEL/${{ inputs.profile }}." \ | |
| > "$NOTES_FILE" | |
| { | |
| echo "## Immutable VM profile release" | |
| echo | |
| echo "- Tag: \`$TAG\`" | |
| echo "- Dry run: \`$DRY_RUN\`" | |
| echo "- Files:" | |
| # shellcheck disable=SC2016 | |
| printf ' - `%s`\n' "${files[@]}" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| if [[ "$DRY_RUN" == "true" ]]; then | |
| echo "Dry run: would publish immutable VM profile release $TAG" | |
| else | |
| CAPSEM_RELEASE_CREATE_TITLE="Capsem $CHANNEL/${{ inputs.profile }} $PROFILE_REVISION" \ | |
| CAPSEM_RELEASE_CREATE_NOTES_FILE="$NOTES_FILE" \ | |
| CAPSEM_RELEASE_CREATE_TARGET="$GITHUB_SHA" \ | |
| scripts/publish-immutable-release-assets.sh \ | |
| "$TAG" "$RELEASE_DIR" | |
| fi | |
| - name: Attest VM asset provenance | |
| if: ${{ inputs.dry_run == false }} | |
| uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 | |
| with: | |
| subject-path: target/asset-release/profile-*/* | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: asset-channel-source | |
| path: target/source-channel/manifest.json | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| if: ${{ inputs.dry_run == true }} | |
| with: | |
| name: asset-release-plan | |
| path: target/asset-release/ | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| if: ${{ needs.author-profile-release.outputs.compatible == 'true' }} | |
| with: | |
| name: asset-channel-preview | |
| path: target/release-channel/ | |
| deploy-channel: | |
| needs: publish-profile-release | |
| if: ${{ inputs.dry_run == false && needs.publish-profile-release.outputs.profile_changed == 'true' && needs.publish-profile-release.outputs.compatible == 'true' }} | |
| uses: ./.github/workflows/release-channel.yaml | |
| with: | |
| channel: ${{ inputs.channel }} | |
| dist_artifact: asset-channel-preview | |
| secrets: inherit |