Promote automatic Docker Sandboxes prebuilt updates to main #137
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: Core runner verification | |
| on: | |
| pull_request: | |
| branches: | |
| - develop | |
| - main | |
| push: | |
| branches: | |
| - develop | |
| - main | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: epar-core-live | |
| cancel-in-progress: false | |
| jobs: | |
| controller: | |
| name: Core runner controller | |
| if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository | |
| runs-on: ubuntu-latest | |
| environment: epar-live-ci | |
| # Leaves time for a cold image build and bounded cleanup around the | |
| # controller's 40-minute canary deadline. | |
| timeout-minutes: 70 | |
| permissions: | |
| actions: write | |
| contents: read | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 | |
| - name: Set up Go | |
| uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - name: Build EPAR | |
| shell: bash | |
| run: go build -trimpath -o "${RUNNER_TEMP}/ephemeral-action-runner" ./cmd/ephemeral-action-runner | |
| - name: Orchestrate ephemeral canaries | |
| shell: bash | |
| env: | |
| EPAR_BINARY: ${{ runner.temp }}/ephemeral-action-runner | |
| EPAR_PROJECT_ROOT: ${{ github.workspace }} | |
| EPAR_APP_ID: ${{ vars.EPAR_GITHUB_APP_ID }} | |
| EPAR_ORGANIZATION: ${{ vars.EPAR_GITHUB_ORGANIZATION }} | |
| EPAR_APP_PRIVATE_KEY: ${{ secrets.EPAR_GITHUB_APP_PRIVATE_KEY }} | |
| CORE_POOL_PREFIX: epar-ci-core | |
| CORE_RUNNER_GROUP: epar-ci-canary | |
| CORE_CANARY_LABEL: epar-core-${{ github.run_id }}-${{ github.run_attempt }} | |
| CORE_MAX_WAIT_SECONDS: "2400" | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: bash scripts/ci/core-runner-controller.sh | |
| # This guard does not depend on checkout. If controller preparation fails | |
| # before the helper starts, cancel the canaries that are waiting for a | |
| # label which could never be provisioned. | |
| - name: Cancel workflow after controller setup failure | |
| if: ${{ failure() && !cancelled() }} | |
| shell: bash | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -u | |
| api_url="${GITHUB_API_URL%/}/repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" | |
| headers=( | |
| --header "Authorization: Bearer ${GITHUB_TOKEN}" | |
| --header "Accept: application/vnd.github+json" | |
| --header "X-GitHub-Api-Version: 2022-11-28" | |
| ) | |
| if ! curl --fail --silent --show-error --output /dev/null \ | |
| --request POST "${headers[@]}" "${api_url}/cancel"; then | |
| echo "Normal cancellation failed; attempting force cancellation." >&2 | |
| curl --fail --silent --show-error --output /dev/null \ | |
| --request POST "${headers[@]}" "${api_url}/force-cancel" | |
| fi | |
| canary-1: | |
| name: Core canary 1 | |
| if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository | |
| runs-on: | |
| group: epar-ci-canary | |
| labels: epar-core-${{ github.run_id }}-${{ github.run_attempt }} | |
| timeout-minutes: 15 | |
| permissions: | |
| actions: write | |
| contents: read | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 | |
| - name: Verify the core runner environment | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| [[ "${RUNNER_OS}" == "Linux" ]] | |
| [[ "${RUNNER_ARCH}" == "X64" ]] | |
| for command in bash curl docker git jq sudo; do | |
| command -v "${command}" >/dev/null | |
| done | |
| docker info >/dev/null | |
| - name: Record runner identity | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| identity_dir="${RUNNER_TEMP}/epar-core-identity" | |
| mkdir -p "${identity_dir}" | |
| printf '%s\n' "${RUNNER_NAME}" >"${identity_dir}/runner-name.txt" | |
| printf '%s-%s-%s-%s\n' \ | |
| "${GITHUB_RUN_ID}" "${GITHUB_RUN_ATTEMPT}" "${GITHUB_JOB}" "${RANDOM}" \ | |
| >"${identity_dir}/nonce.txt" | |
| - name: Upload runner identity | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: epar-core-identity-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: ${{ runner.temp }}/epar-core-identity | |
| if-no-files-found: error | |
| retention-days: 1 | |
| canary-2: | |
| name: Core canary 2 | |
| needs: canary-1 | |
| if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository | |
| runs-on: | |
| group: epar-ci-canary | |
| labels: epar-core-${{ github.run_id }}-${{ github.run_attempt }} | |
| timeout-minutes: 15 | |
| permissions: | |
| actions: read | |
| contents: read | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 | |
| - name: Download first runner identity | |
| uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 | |
| with: | |
| name: epar-core-identity-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: ${{ runner.temp }}/epar-core-identity | |
| - name: Prove ephemeral replacement | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| first_runner="$(<"${RUNNER_TEMP}/epar-core-identity/runner-name.txt")" | |
| nonce="$(<"${RUNNER_TEMP}/epar-core-identity/nonce.txt")" | |
| [[ -n "${first_runner}" && -n "${nonce}" ]] | |
| [[ "${first_runner}" == epar-ci-core-* ]] | |
| [[ "${RUNNER_NAME}" == epar-ci-core-* ]] | |
| if [[ "${first_runner}" == "${RUNNER_NAME}" ]]; then | |
| echo "Expected a replacement runner, but both jobs ran on ${RUNNER_NAME}" >&2 | |
| exit 1 | |
| fi | |
| echo "Runner ${first_runner} was replaced by ${RUNNER_NAME}." | |
| - name: Exercise Buildx and Compose | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| work_dir="${RUNNER_TEMP}/epar-core-workload" | |
| image="epar-core-workload:${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" | |
| export COMPOSE_PROJECT_NAME="eparcore${GITHUB_RUN_ID}${GITHUB_RUN_ATTEMPT}" | |
| mkdir -p "${work_dir}" | |
| cd "${work_dir}" | |
| cleanup() { | |
| docker compose down --volumes --remove-orphans >/dev/null 2>&1 || true | |
| docker image rm --force "${image}" >/dev/null 2>&1 || true | |
| } | |
| trap cleanup EXIT | |
| printf 'epar core runner healthy\n' >index.html | |
| cat >Dockerfile <<'DOCKERFILE' | |
| FROM busybox:1.37.0@sha256:9532d8c39891ca2ecde4d30d7710e01fb739c87a8b9299685c63704296b16028 | |
| COPY index.html /www/index.html | |
| HEALTHCHECK --interval=1s --timeout=2s --retries=30 CMD wget -q -O /dev/null http://127.0.0.1:8080/ || exit 1 | |
| CMD ["httpd", "-f", "-p", "8080", "-h", "/www"] | |
| DOCKERFILE | |
| docker buildx version | |
| docker buildx build --load --tag "${image}" . | |
| cat >compose.yml <<COMPOSE | |
| services: | |
| core: | |
| image: ${image} | |
| ports: | |
| - "127.0.0.1:18080:8080" | |
| COMPOSE | |
| docker compose version | |
| docker compose -f compose.yml up --detach --wait --wait-timeout 60 | |
| response="$(curl --fail --silent --show-error --retry 10 --retry-delay 1 http://127.0.0.1:18080/)" | |
| [[ "${response}" == "epar core runner healthy" ]] |