Add ovirt-engine-health codebundle #609
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: Build And Push | |
| # Builds the rw-cli-codecollection image and pushes a multi-arch manifest | |
| # to GHCR with the tag schema the cc-registry-v2 image catalog expects: | |
| # | |
| # <sanitized-ref>-<cc_sha7>-<rt_sha7> | |
| # | |
| # Where: | |
| # sanitized-ref = github.ref_name with '/' -> '-' (e.g. "main", "feature-foo", "pr-42") | |
| # cc_sha7 = first 7 chars of github.sha (this repo's commit) | |
| # rt_sha7 = first 7 chars of rw-base-runtime at `runtime_ref` | |
| # (https://github.com/runwhen-contrib/rw-base-runtime) | |
| # | |
| # Architecture | |
| # ------------ | |
| # The build is split across three jobs so each platform is built natively | |
| # rather than under QEMU emulation (mirrors the rw-base-runtime workflow): | |
| # | |
| # prepare -- compute tag set + push flag + build args ONCE, | |
| # share with downstream jobs so they can't drift | |
| # build (matrix) -- one job per platform, each on its own native runner: | |
| # linux/amd64 -> ubuntu-latest | |
| # linux/arm64 -> ubuntu-24.04-arm | |
| # each job: | |
| # 1. builds the image natively (no QEMU) | |
| # 2. loads it into the local docker daemon | |
| # 3. runs the smoke test against the native arch | |
| # 4. on push, builds again push-by-digest (no tags) | |
| # and exports the per-platform digest as an artifact | |
| # merge -- pulls the per-platform digests and uses | |
| # `docker buildx imagetools create` to stitch them | |
| # into a single multi-arch manifest under every tag | |
| # prepare computed. Pure registry-side metadata op. | |
| # | |
| # Build triggers: | |
| # - push to ANY branch -> produces a CodeCollectionVersion image for that branch | |
| # - pull_request to ANY base branch -> produces a "pr-<n>" preview image | |
| # - workflow_dispatch -> manual build (e.g. to validate against a BYO base) | |
| # | |
| # We build off non-main branches on purpose: each branch is a candidate CCV | |
| # the catalog can pin to. Path filters below skip rebuilds for pure | |
| # docs / config-only diffs. | |
| # | |
| # The base image is overridable so we can: | |
| # - Build against rw-base-runtime:latest (default, production target) | |
| # - Pin to a specific rw-base-runtime sha7 (reproducible builds) | |
| # - Build against a customer-supplied BYO base (validation runs) | |
| on: | |
| push: | |
| branches: | |
| - '**' # all branches; tag pushes (refs/tags/*) are excluded | |
| paths-ignore: | |
| - '**/*.md' | |
| - '**/*.html' | |
| - 'LICENSE' | |
| - '.gitignore' | |
| - '.gitbook.yaml' | |
| - '.devcontainer.json' | |
| - 'CHANGELOG.md' | |
| - 'CONTRIBUTING.md' | |
| - 'README.md' | |
| - 'SUMMARY.md' | |
| - 'Introduction.md' | |
| - 'docs/**' | |
| pull_request: | |
| # No branches: restriction -> triggers on PRs targeting any base branch | |
| paths-ignore: | |
| - '**/*.md' | |
| - '**/*.html' | |
| - 'LICENSE' | |
| - '.gitignore' | |
| - '.gitbook.yaml' | |
| - '.devcontainer.json' | |
| - 'CHANGELOG.md' | |
| - 'CONTRIBUTING.md' | |
| - 'README.md' | |
| - 'SUMMARY.md' | |
| - 'Introduction.md' | |
| - 'docs/**' | |
| workflow_dispatch: | |
| inputs: | |
| base_image: | |
| description: "Base image (FROM ref) to build against. Leave blank to use the Dockerfile default." | |
| required: false | |
| default: "" | |
| type: string | |
| runtime_ref: | |
| description: "rw-base-runtime ref to embed in the tag suffix (branch / tag / sha). Default: main." | |
| required: false | |
| default: "main" | |
| type: string | |
| push: | |
| description: "Push the resulting image to GHCR." | |
| required: false | |
| default: true | |
| type: boolean | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE: ${{ github.repository }} | |
| # The repo we resolve the rt_sha tag suffix from. This MUST match the | |
| # base image we FROM in Dockerfile (currently rw-base-runtime). | |
| RUNTIME_REPO: runwhen-contrib/rw-base-runtime | |
| permissions: | |
| contents: read | |
| packages: write | |
| jobs: | |
| # =========================================================================== | |
| # prepare — single source of truth for the tag set, push flag and build | |
| # args so the matrix builds and the final merge job don't drift. | |
| # =========================================================================== | |
| prepare: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_push: ${{ steps.push_flag.outputs.should_push }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| canonical_tag: ${{ steps.meta.outputs.canonical_tag }} | |
| repo_lc: ${{ steps.meta.outputs.repo_lc }} | |
| sanitized_ref: ${{ steps.meta.outputs.sanitized_ref }} | |
| cc_sha: ${{ steps.meta.outputs.cc_sha }} | |
| rt_sha: ${{ steps.meta.outputs.rt_sha }} | |
| runtime_ref: ${{ steps.meta.outputs.runtime_ref }} | |
| base_image_arg: ${{ steps.args.outputs.base_image_arg }} | |
| steps: | |
| - name: Determine push flag | |
| id: push_flag | |
| run: | | |
| case "${{ github.event_name }}" in | |
| push|pull_request) | |
| echo "should_push=true" >> "$GITHUB_OUTPUT" ;; | |
| workflow_dispatch) | |
| echo "should_push=${{ inputs.push }}" >> "$GITHUB_OUTPUT" ;; | |
| *) | |
| echo "should_push=false" >> "$GITHUB_OUTPUT" ;; | |
| esac | |
| - name: Compute tag set | |
| id: meta | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| RUNTIME_REF_INPUT: ${{ inputs.runtime_ref }} | |
| run: | | |
| set -euo pipefail | |
| # --- this repo's sha --- | |
| cc_sha="${{ github.sha }}" | |
| cc_sha7="${cc_sha:0:7}" | |
| # --- ref name (PRs collapse to "pr-<n>" since github.ref_name on | |
| # pull_request is "<n>/merge", which is useless as an OCI tag) --- | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| ref_name="pr-${{ github.event.pull_request.number }}" | |
| else | |
| ref_name="${{ github.ref_name }}" | |
| fi | |
| # OCI tags must match [A-Za-z0-9_.-]{1,128}. We replace '/' with '-' | |
| # so refs like "release/1.2" survive intact. | |
| sanitized_ref="$(echo "${ref_name}" | tr '/' '-' | tr -c 'A-Za-z0-9_.-' '-' | sed 's/^-*//;s/-*$//')" | |
| # --- runtime sha (defaults to rw-base-runtime@main) --- | |
| runtime_ref="${RUNTIME_REF_INPUT:-main}" | |
| rt_sha="$(gh api "repos/${RUNTIME_REPO}/commits/${runtime_ref}" --jq '.sha')" | |
| rt_sha7="${rt_sha:0:7}" | |
| # --- repo path (GHCR rejects uppercase) --- | |
| repo_lc="$(echo "${{ env.REGISTRY }}/${{ env.IMAGE }}" | tr '[:upper:]' '[:lower:]')" | |
| # --- canonical immutable tag the catalog uses for discovery --- | |
| canonical_tag="${sanitized_ref}-${cc_sha7}-${rt_sha7}" | |
| # --- moving-pointer aliases (these get re-pointed on every build) --- | |
| tags=( "${repo_lc}:${canonical_tag}" ) | |
| case "${{ github.event_name }}" in | |
| push) | |
| # Always alias to the (sanitized) branch name. For main this is | |
| # ":main"; for "feature/foo" it's ":feature-foo". | |
| tags+=( "${repo_lc}:${sanitized_ref}" ) | |
| # Only the main branch gets the global ":latest" alias. | |
| if [ "${{ github.ref_name }}" = "main" ]; then | |
| tags+=( "${repo_lc}:latest" ) | |
| fi | |
| ;; | |
| pull_request) | |
| tags+=( "${repo_lc}:pr-${{ github.event.pull_request.number }}" ) | |
| ;; | |
| workflow_dispatch) | |
| # For manual dispatches, alias to the source ref name so | |
| # operators can pull ":${{ github.ref_name }}". | |
| tags+=( "${repo_lc}:${sanitized_ref}" ) | |
| ;; | |
| esac | |
| { | |
| echo "cc_sha=${cc_sha}" | |
| echo "rt_sha=${rt_sha}" | |
| echo "runtime_ref=${runtime_ref}" | |
| echo "sanitized_ref=${sanitized_ref}" | |
| echo "repo_lc=${repo_lc}" | |
| echo "canonical_tag=${canonical_tag}" | |
| printf 'tags=%s\n' "$(IFS=,; echo "${tags[*]}")" | |
| } >> "$GITHUB_OUTPUT" | |
| { | |
| echo "## Will publish" | |
| echo | |
| for t in "${tags[@]}"; do echo "- \`${t}\`"; done | |
| echo | |
| echo "- Codecollection sha: \`${cc_sha}\`" | |
| echo "- Runtime ref: \`${runtime_ref}\`" | |
| echo "- Runtime sha: \`${rt_sha}\`" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Resolve build args | |
| id: args | |
| env: | |
| INPUT_BASE_IMAGE: ${{ inputs.base_image }} | |
| run: | | |
| set -euo pipefail | |
| if [ -n "${INPUT_BASE_IMAGE:-}" ]; then | |
| echo "base_image_arg=BASE_IMAGE=${INPUT_BASE_IMAGE}" >> "$GITHUB_OUTPUT" | |
| else | |
| # Empty -> docker/build-push-action ignores blank build-args lines | |
| echo "base_image_arg=" >> "$GITHUB_OUTPUT" | |
| fi | |
| # =========================================================================== | |
| # build — parallel native builds. Each matrix leg runs on a runner whose | |
| # architecture matches the platform it is building, so there's no QEMU | |
| # emulation cost. The smoke test consequently runs on real hardware for | |
| # each arch (the previous single-runner workflow only ever exercised | |
| # amd64 even though we shipped arm64 manifests). | |
| # =========================================================================== | |
| build: | |
| needs: prepare | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux/amd64 | |
| arch: amd64 | |
| runner: ubuntu-latest | |
| - platform: linux/arm64 | |
| arch: arm64 | |
| runner: ubuntu-24.04-arm | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| if: needs.prepare.outputs.should_push == 'true' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # ---------------------------------------------------------------- | |
| # Phase 1: build natively + load locally so we can exec the smoke | |
| # test. Per-arch GHA cache scope -- the two matrix legs run in | |
| # parallel on different runners, so they MUST NOT share a scope. | |
| # ---------------------------------------------------------------- | |
| - name: Build (native, load) | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: Dockerfile | |
| platforms: ${{ matrix.platform }} | |
| load: true | |
| tags: rw-cli-codecollection:smoke | |
| cache-from: type=gha,scope=rw-cli-codecollection-${{ matrix.arch }} | |
| cache-to: type=gha,mode=max,scope=rw-cli-codecollection-${{ matrix.arch }} | |
| build-args: | | |
| ${{ needs.prepare.outputs.base_image_arg }} | |
| - name: Smoke test image | |
| run: | | |
| set -euo pipefail | |
| # Native-arch exec of the image -- verifies rw-core-keywords (provided | |
| # by the base image), the codecollection layout, and the worker binary. | |
| docker run --rm --entrypoint /bin/bash rw-cli-codecollection:smoke -c ' | |
| set -eux | |
| python3 -c "import RW.Core, RW.platform, RW.fetchsecrets, robot" | |
| robot --version || true | |
| test -d /home/runwhen/collection/codebundles | |
| test -f /home/runwhen/collection/requirements.txt | |
| test -x /home/runwhen/worker | |
| python3 --version | |
| ' | |
| # ---------------------------------------------------------------- | |
| # Phase 2: only on push -- rebuild push-by-digest. This writes the | |
| # platform-specific manifest into the registry WITHOUT any | |
| # human-readable tags. The merge job assembles the multi-arch | |
| # manifest from these digests under the canonical tags. | |
| # | |
| # The build is cheap here: every layer is already in the local | |
| # buildx cache from Phase 1. | |
| # ---------------------------------------------------------------- | |
| - name: Build & push by digest | |
| id: digest_push | |
| if: needs.prepare.outputs.should_push == 'true' | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: Dockerfile | |
| platforms: ${{ matrix.platform }} | |
| outputs: type=image,name=${{ needs.prepare.outputs.repo_lc }},push-by-digest=true,name-canonical=true,push=true | |
| labels: | | |
| org.opencontainers.image.source=https://github.com/${{ github.repository }} | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| io.runwhen.codecollection.commit=${{ github.sha }} | |
| io.runwhen.runtime.commit=${{ needs.prepare.outputs.rt_sha }} | |
| io.runwhen.runtime.ref=${{ needs.prepare.outputs.runtime_ref }} | |
| cache-from: type=gha,scope=rw-cli-codecollection-${{ matrix.arch }} | |
| cache-to: type=gha,mode=max,scope=rw-cli-codecollection-${{ matrix.arch }} | |
| build-args: | | |
| ${{ needs.prepare.outputs.base_image_arg }} | |
| - name: Stage digest for merge job | |
| if: needs.prepare.outputs.should_push == 'true' | |
| run: | | |
| set -euo pipefail | |
| mkdir -p /tmp/digests | |
| digest="${{ steps.digest_push.outputs.digest }}" | |
| # The merge job enumerates files in this dir; the filename is | |
| # the digest minus the "sha256:" prefix. | |
| touch "/tmp/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| if: needs.prepare.outputs.should_push == 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: digests-${{ matrix.arch }} | |
| path: /tmp/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| # =========================================================================== | |
| # merge — combine the per-arch digests into the final multi-arch manifest | |
| # under every tag prepare computed. `buildx imagetools create` is a | |
| # registry-side metadata operation -- no image rebuild. | |
| # =========================================================================== | |
| merge: | |
| needs: [prepare, build] | |
| if: needs.prepare.outputs.should_push == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download digests | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: /tmp/digests | |
| pattern: digests-* | |
| merge-multiple: true | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create multi-arch manifest | |
| env: | |
| REPO_LC: ${{ needs.prepare.outputs.repo_lc }} | |
| TAG_CSV: ${{ needs.prepare.outputs.tags }} | |
| run: | | |
| set -euo pipefail | |
| # Build the -t <tag> argv from the comma-separated tag list. | |
| tag_args=() | |
| IFS=',' read -ra TAGS <<< "${TAG_CSV}" | |
| for t in "${TAGS[@]}"; do tag_args+=(-t "$t"); done | |
| # Build the @<digest> argv from the staged digest files. | |
| digest_args=() | |
| for f in /tmp/digests/*; do | |
| d="$(basename "$f")" | |
| digest_args+=("${REPO_LC}@sha256:${d}") | |
| done | |
| echo "Tags: ${TAGS[*]}" | |
| echo "Digests: ${digest_args[*]}" | |
| docker buildx imagetools create "${tag_args[@]}" "${digest_args[@]}" | |
| - name: Inspect resulting manifest | |
| env: | |
| TAG_CSV: ${{ needs.prepare.outputs.tags }} | |
| run: | | |
| set -euo pipefail | |
| IFS=',' read -ra TAGS <<< "${TAG_CSV}" | |
| docker buildx imagetools inspect "${TAGS[0]}" | |
| - name: Summary | |
| env: | |
| REPO_LC: ${{ needs.prepare.outputs.repo_lc }} | |
| CANONICAL_TAG: ${{ needs.prepare.outputs.canonical_tag }} | |
| TAG_CSV: ${{ needs.prepare.outputs.tags }} | |
| run: | | |
| { | |
| echo "## rw-cli-codecollection build" | |
| echo | |
| echo "- Event: \`${{ github.event_name }}\`" | |
| echo "- Ref: \`${{ github.ref }}\`" | |
| echo "- Codecollection sha: \`${{ github.sha }}\`" | |
| echo "- Runtime ref: \`${{ needs.prepare.outputs.runtime_ref }}\`" | |
| echo "- Runtime sha: \`${{ needs.prepare.outputs.rt_sha }}\`" | |
| echo "- Canonical tag: \`${REPO_LC}:${CANONICAL_TAG}\`" | |
| echo "- Pushed: \`true\`" | |
| echo | |
| echo "### Published manifest" | |
| IFS=',' read -ra TAGS <<< "${TAG_CSV}" | |
| for t in "${TAGS[@]}"; do echo "- \`${t}\`"; done | |
| } >> "$GITHUB_STEP_SUMMARY" |