|
| 1 | +name: Hosted runner Docker architecture proof |
| 2 | + |
| 3 | +on: |
| 4 | + pull_request: |
| 5 | + branches: |
| 6 | + - develop |
| 7 | + - main |
| 8 | + paths: |
| 9 | + - .github/workflows/hosted-runner-docker-architecture.yml |
| 10 | + workflow_dispatch: |
| 11 | + |
| 12 | +permissions: |
| 13 | + contents: read |
| 14 | + |
| 15 | +concurrency: |
| 16 | + group: hosted-docker-architecture-${{ github.workflow }}-${{ github.ref }} |
| 17 | + cancel-in-progress: true |
| 18 | + |
| 19 | +jobs: |
| 20 | + capability: |
| 21 | + name: Capability (${{ matrix.runner }}) |
| 22 | + runs-on: ${{ matrix.runner }} |
| 23 | + timeout-minutes: 10 |
| 24 | + strategy: |
| 25 | + fail-fast: false |
| 26 | + matrix: |
| 27 | + runner: |
| 28 | + - ubuntu-latest |
| 29 | + - ubuntu-24.04-arm |
| 30 | + - windows-latest |
| 31 | + - windows-11-arm |
| 32 | + - macos-latest |
| 33 | + - macos-15-intel |
| 34 | + |
| 35 | + steps: |
| 36 | + - name: Record host and Docker capability |
| 37 | + shell: pwsh |
| 38 | + run: | |
| 39 | + $ErrorActionPreference = 'Continue' |
| 40 | + $summary = [System.Collections.Generic.List[string]]::new() |
| 41 | + $summary.Add("## $env:RUNNER_OS/$env:RUNNER_ARCH on ``${{ matrix.runner }}``") |
| 42 | + $summary.Add('') |
| 43 | + $summary.Add("- Runner OS: ``$env:RUNNER_OS``") |
| 44 | + $summary.Add("- Runner architecture: ``$env:RUNNER_ARCH``") |
| 45 | +
|
| 46 | + $dockerCommand = Get-Command docker -ErrorAction SilentlyContinue |
| 47 | + if (-not $dockerCommand) { |
| 48 | + $summary.Add('- Docker CLI: not installed') |
| 49 | + $summary.Add('- Linux container test: unavailable') |
| 50 | + } else { |
| 51 | + $summary.Add("- Docker CLI: ``$($dockerCommand.Source)``") |
| 52 | + $versionOutput = (& docker version 2>&1 | Out-String).Trim() |
| 53 | + $versionStatus = $LASTEXITCODE |
| 54 | + $summary.Add("- Docker daemon reachable: ``$($versionStatus -eq 0)``") |
| 55 | +
|
| 56 | + if ($versionStatus -eq 0) { |
| 57 | + $serverOS = (& docker info --format '{{.OSType}}' 2>&1 | Out-String).Trim() |
| 58 | + $serverArchitecture = (& docker info --format '{{.Architecture}}' 2>&1 | Out-String).Trim() |
| 59 | + $summary.Add("- Docker server: ``$serverOS/$serverArchitecture``") |
| 60 | + if ($serverOS -eq 'linux') { |
| 61 | + $summary.Add('- Linux container test: available') |
| 62 | + } else { |
| 63 | + $summary.Add('- Linux container test: unavailable because the reachable daemon is not a Linux daemon') |
| 64 | + } |
| 65 | + } else { |
| 66 | + $summary.Add('- Linux container test: unavailable because no Docker daemon is reachable') |
| 67 | + $summary.Add('') |
| 68 | + $summary.Add('<details><summary>docker version diagnostic</summary>') |
| 69 | + $summary.Add('') |
| 70 | + $summary.Add('```text') |
| 71 | + $summary.Add($versionOutput) |
| 72 | + $summary.Add('```') |
| 73 | + $summary.Add('</details>') |
| 74 | + } |
| 75 | + } |
| 76 | +
|
| 77 | + $summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append |
| 78 | + $summary | ForEach-Object { Write-Host $_ } |
| 79 | +
|
| 80 | + linux-cross-architecture: |
| 81 | + name: Linux ${{ matrix.native }} runs ${{ matrix.foreign }} with explicit QEMU |
| 82 | + runs-on: ${{ matrix.runner }} |
| 83 | + timeout-minutes: 15 |
| 84 | + strategy: |
| 85 | + fail-fast: false |
| 86 | + matrix: |
| 87 | + include: |
| 88 | + - runner: ubuntu-latest |
| 89 | + native: amd64 |
| 90 | + native_platform: linux/amd64 |
| 91 | + native_output: x86_64 |
| 92 | + foreign: arm64 |
| 93 | + foreign_platform: linux/arm64 |
| 94 | + foreign_output: aarch64 |
| 95 | + - runner: ubuntu-24.04-arm |
| 96 | + native: arm64 |
| 97 | + native_platform: linux/arm64 |
| 98 | + native_output: aarch64 |
| 99 | + foreign: amd64 |
| 100 | + foreign_platform: linux/amd64 |
| 101 | + foreign_output: x86_64 |
| 102 | + |
| 103 | + steps: |
| 104 | + - name: Verify native container execution |
| 105 | + shell: bash |
| 106 | + run: | |
| 107 | + set -euo pipefail |
| 108 | + docker info --format 'Docker server: {{.OSType}}/{{.Architecture}}' |
| 109 | + native_architecture="$(docker run --rm --platform '${{ matrix.native_platform }}' alpine:3.22 uname -m)" |
| 110 | + echo "Native container reported: ${native_architecture}" |
| 111 | + [[ "${native_architecture}" == '${{ matrix.native_output }}' ]] |
| 112 | +
|
| 113 | + - name: Observe foreign-architecture behavior before explicit QEMU setup |
| 114 | + id: baseline |
| 115 | + shell: bash |
| 116 | + run: | |
| 117 | + set -euo pipefail |
| 118 | + set +e |
| 119 | + baseline_output="$(docker run --rm --platform '${{ matrix.foreign_platform }}' alpine:3.22 uname -m 2>&1)" |
| 120 | + baseline_status=$? |
| 121 | + set -e |
| 122 | +
|
| 123 | + printf '%s\n' "${baseline_output}" |
| 124 | + { |
| 125 | + echo '## ${{ matrix.foreign }} container before explicit QEMU setup' |
| 126 | + echo |
| 127 | + echo "- Exit status: \`${baseline_status}\`" |
| 128 | + echo |
| 129 | + echo '```text' |
| 130 | + printf '%s\n' "${baseline_output}" |
| 131 | + echo '```' |
| 132 | + } >>"${GITHUB_STEP_SUMMARY}" |
| 133 | +
|
| 134 | + if [[ ${baseline_status} -eq 0 ]]; then |
| 135 | + [[ "${baseline_output}" == *'${{ matrix.foreign_output }}'* ]] |
| 136 | + echo 'This hosted image already had a compatible foreign-architecture execution path before the workflow configured QEMU.' >>"${GITHUB_STEP_SUMMARY}" |
| 137 | + elif [[ "${baseline_output}" == *"exec format error"* ]]; then |
| 138 | + echo 'The baseline failed with the expected missing-emulation error.' >>"${GITHUB_STEP_SUMMARY}" |
| 139 | + else |
| 140 | + echo 'The baseline failed for a reason other than a recognized architecture mismatch.' >&2 |
| 141 | + exit 1 |
| 142 | + fi |
| 143 | +
|
| 144 | + - name: Set up foreign-architecture container emulation |
| 145 | + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4 |
| 146 | + with: |
| 147 | + image: docker.io/tonistiigi/binfmt@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0 |
| 148 | + platforms: ${{ matrix.foreign }} |
| 149 | + |
| 150 | + - name: Verify foreign-architecture container execution after QEMU setup |
| 151 | + shell: bash |
| 152 | + run: | |
| 153 | + set -euo pipefail |
| 154 | + foreign_architecture="$(docker run --rm --platform '${{ matrix.foreign_platform }}' alpine:3.22 uname -m)" |
| 155 | + echo "Emulated container reported: ${foreign_architecture}" |
| 156 | + [[ "${foreign_architecture}" == '${{ matrix.foreign_output }}' ]] |
| 157 | + { |
| 158 | + echo '## ${{ matrix.foreign }} container after explicit QEMU setup' |
| 159 | + echo |
| 160 | + echo "- Reported architecture: \`${foreign_architecture}\`" |
| 161 | + echo '- Result: success' |
| 162 | + } >>"${GITHUB_STEP_SUMMARY}" |
0 commit comments