| name: windows-integration-flaky | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| on: | |
| workflow_dispatch: | |
| pull_request: | |
| env: | |
| GO_VERSION: "1.26.10" | |
| TEST_REPEAT: "10" | |
| WINDOWS_BASE_IMAGE: mcr.microsoft.com/windows/servercore | |
| WINDOWS_BASE_TAG_2025: ltsc2025 | |
| TEST_IMAGE_NAME: moby:test | |
| TEST_CTN_NAME: moby | |
| DOCKER_BUILDKIT: 0 | |
| jobs: | |
| detect-tests: | |
| if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }} | |
| # Test detection only needs git, so run it on Linux; it is cheaper and | |
| # avoids CRLF line endings that break sourcing the bash helpers, as the | |
| # Windows runners check out with core.autocrlf=true. | |
| runs-on: ubuntu-26.04 | |
| timeout-minutes: 10 | |
| outputs: | |
| has_tests: ${{ steps.detect-tests.outputs.has_tests }} | |
| test_flags: ${{ steps.detect-tests.outputs.test_flags }} | |
| steps: | |
| - | |
| name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - | |
| # Use the shared integration flaky-test selector. | |
| name: Detect new or modified integration tests | |
| id: detect-tests | |
| env: | |
| PR_BODY: ${{ github.event.pull_request.body }} | |
| VALIDATE_REPO: ${{ github.server_url }}/${{ github.repository }}.git | |
| VALIDATE_BRANCH: ${{ github.event.pull_request.base.ref || 'master' }} | |
| VALIDATE_ORIGIN_BRANCH: ${{ github.event.pull_request.base.sha || '' }} | |
| run: | | |
| source hack/make/.test-integration-flaky | |
| export FLAKY_EXTRA_TESTS | |
| FLAKY_EXTRA_TESTS=$(flaky_check_tests) | |
| source hack/validate/.validate | |
| test_filter=$(integration_flaky_test_filter) | |
| if [ -z "$test_filter" ]; then | |
| echo "No new or modified tests found in integration." | |
| echo "has_tests=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Found new or modified integration tests: $test_filter" | |
| echo "Running stress test for them." | |
| # Match the Linux flaky-test job: run each test 10 times per test | |
| # invocation, and invoke the tests TEST_REPEAT times. | |
| test_flags="-test.count $TEST_REPEAT -test.run $test_filter" | |
| echo "Using INTEGRATION_TESTFLAGS: $test_flags" | |
| echo "test_flags=$test_flags" >> "$GITHUB_OUTPUT" | |
| echo "has_tests=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| build: | |
| if: needs.detect-tests.outputs.has_tests == 'true' | |
| needs: | |
| - detect-tests | |
| runs-on: windows-2025 | |
| timeout-minutes: 120 # guardrails timeout for the whole job | |
| env: | |
| GOPATH: ${{ github.workspace }}\go | |
| GOBIN: ${{ github.workspace }}\go\bin | |
| BIN_OUT: ${{ github.workspace }}\out | |
| steps: | |
| - | |
| name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - | |
| name: Env | |
| run: | | |
| Get-ChildItem Env: | Out-String | |
| - | |
| name: Init | |
| run: | | |
| New-Item -ItemType "directory" -Path "${{ github.workspace }}\go-build" | |
| New-Item -ItemType "directory" -Path "${{ github.workspace }}\go\pkg\mod" | |
| echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2025 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append | |
| - | |
| name: Docker info | |
| run: | | |
| $tries = 30 | |
| Write-Host "Waiting for Docker daemon..." | |
| while ($true) { | |
| $ErrorActionPreference = "SilentlyContinue" | |
| docker version 2>&1 | Out-Null | |
| $ok = ($LastExitCode -eq 0) | |
| $ErrorActionPreference = "Stop" | |
| if ($ok) { break } | |
| # Check if Docker service exists and try to start it if stopped | |
| $svc = Get-Service docker -ErrorAction SilentlyContinue | |
| if ($svc) { | |
| if ($svc.Status -ne 'Running') { | |
| Write-Host "Docker service status: $($svc.Status) - attempting to start..." | |
| Start-Service docker -ErrorAction SilentlyContinue | |
| } | |
| } else { | |
| Write-Host "Docker service not found!" | |
| } | |
| $tries-- | |
| if ($tries -le 0) { throw "Docker daemon did not become ready" } | |
| Start-Sleep -Seconds 2 | |
| } | |
| docker info | |
| - | |
| name: Build base image | |
| run: | | |
| & docker build ` | |
| --build-arg WINDOWS_BASE_IMAGE ` | |
| --build-arg WINDOWS_BASE_IMAGE_TAG ` | |
| -t ${{ env.TEST_IMAGE_NAME }} ` | |
| -f Dockerfile.windows . | |
| - | |
| name: Build binaries | |
| run: | | |
| & docker run --name ${{ env.TEST_CTN_NAME }} -e "DOCKER_GITCOMMIT=${{ github.sha }}" ` | |
| ${{ env.TEST_IMAGE_NAME }} hack\make.ps1 -Daemon -Client | |
| - | |
| name: Copy artifacts | |
| run: | | |
| New-Item -ItemType "directory" -Path "${{ env.BIN_OUT }}" | |
| docker cp "${{ env.TEST_CTN_NAME }}`:c`:\usr\src\moby\bundles\docker.exe" ${{ env.BIN_OUT }}\ | |
| docker cp "${{ env.TEST_CTN_NAME }}`:c`:\usr\src\moby\bundles\dockerd.exe" ${{ env.BIN_OUT }}\ | |
| docker cp "${{ env.TEST_CTN_NAME }}`:c`:\usr\src\moby\bundles\docker-buildx.exe" ${{ env.BIN_OUT }}\ | |
| docker cp "${{ env.TEST_CTN_NAME }}`:c`:\gopath\bin\gotestsum.exe" ${{ env.BIN_OUT }}\ | |
| docker cp "${{ env.TEST_CTN_NAME }}`:c`:\containerd\bin\containerd.exe" ${{ env.BIN_OUT }}\ | |
| docker cp "${{ env.TEST_CTN_NAME }}`:c`:\containerd\bin\containerd-shim-runhcs-v1.exe" ${{ env.BIN_OUT }}\ | |
| Get-ChildItem -Path ${{ env.BIN_OUT }} | |
| - | |
| name: Upload artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: windows-integration-flaky-build | |
| path: ${{ env.BIN_OUT }}/* | |
| if-no-files-found: error | |
| retention-days: 2 | |
| integration-flaky: | |
| if: needs.detect-tests.outputs.has_tests == 'true' | |
| needs: | |
| - detect-tests | |
| - build | |
| runs-on: windows-2025 | |
| timeout-minutes: 120 # guardrails timeout for the whole job | |
| continue-on-error: ${{ github.event_name != 'pull_request' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| containerd: | |
| - external | |
| - embedded | |
| env: | |
| GOPATH: ${{ github.workspace }}\go | |
| GOBIN: ${{ github.workspace }}\go\bin | |
| BIN_OUT: ${{ github.workspace }}\out | |
| INTEGRATION_TESTFLAGS: ${{ needs.detect-tests.outputs.test_flags }} | |
| steps: | |
| - | |
| name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - | |
| name: Set up Go | |
| uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| cache: false | |
| - | |
| name: Set up OpenTelemetry Collector | |
| id: setup-otel | |
| run: | | |
| New-Item -ItemType Directory -Force -Path bundles -ErrorAction Continue | |
| Start-Process "msiexec" -ArgumentList "/i", | |
| "https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v$($Env:OTEL_COLLECTOR_VERSION)/otelcol_$($Env:OTEL_COLLECTOR_VERSION)_windows_x64.msi", | |
| "/qn", "/l*v", "$(Join-Path (Get-Location) "bundles/otelcol-install.log")", | |
| "COLLECTOR_SVC_ARGS=`"--config=`"`"file:$(Join-Path (Get-Location) "otelcol-ci-config.yml")`"`" --config=`"`"yaml:exporters::file::path: $(Join-Path (Get-Location) "bundles/otel-trace.jsonl")`"`"`"" ` | |
| -NoNewWindow -Wait | |
| @("OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318", "OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf") | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append | |
| shell: pwsh | |
| env: | |
| # Keep in sync with the version in .github/actions/setup-tracing/action.yml. | |
| OTEL_COLLECTOR_VERSION: "0.144.0" | |
| - | |
| name: Env | |
| run: | | |
| Get-ChildItem Env: | Out-String | |
| - | |
| name: Init | |
| run: | | |
| New-Item -ItemType "directory" -Path "${{ github.workspace }}\go-build" -ErrorAction SilentlyContinue | |
| New-Item -ItemType "directory" -Path "${{ github.workspace }}\go\pkg\mod" -ErrorAction SilentlyContinue | |
| New-Item -ItemType "directory" -Path "bundles" -ErrorAction SilentlyContinue | |
| New-Item -ItemType "directory" -Path "${env:ProgramData}\Docker" -ErrorAction SilentlyContinue | |
| New-Item -ItemType "directory" -Path "${env:ProgramData}\Docker\cli-plugins" -ErrorAction SilentlyContinue | |
| echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2025 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append | |
| Write-Output "${{ env.BIN_OUT }}" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| - | |
| name: Download artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: windows-integration-flaky-build | |
| path: ${{ env.BIN_OUT }} | |
| - | |
| name: Copy CLI plugin | |
| run: | | |
| Move-Item -Path "${{ env.BIN_OUT }}\docker-buildx.exe" -Destination "${env:ProgramData}\Docker\cli-plugins\docker-buildx.exe" -Force -ErrorAction Continue | |
| - | |
| name: Starting test daemon | |
| id: start-daemon | |
| uses: ./.github/actions/setup-windows-test-daemon | |
| with: | |
| bin_out: ${{ env.BIN_OUT }} | |
| containerd: ${{ matrix.containerd }} | |
| runtime: containerd | |
| storage: snapshotter | |
| - | |
| name: Docker info | |
| run: | | |
| & "${{ env.BIN_OUT }}\docker" info | |
| env: | |
| DOCKER_HOST: npipe:////./pipe/docker_engine | |
| - | |
| name: Building contrib/busybox | |
| run: | | |
| $maxRetries = 5 | |
| for ($i = 1; $i -le $maxRetries; $i++) { | |
| & "${{ env.BIN_OUT }}\docker" build -t busybox ` | |
| --build-arg WINDOWS_BASE_IMAGE ` | |
| --build-arg WINDOWS_BASE_IMAGE_TAG ` | |
| .\contrib\busybox\ | |
| if ($LASTEXITCODE -eq 0) { | |
| break | |
| } | |
| if ($i -eq $maxRetries) { | |
| Write-Error "Failed to build contrib/busybox after $maxRetries attempts" | |
| exit 1 | |
| } | |
| $delay = 15 * $i | |
| Write-Warning "Build attempt $i failed (exit code: $LASTEXITCODE). Retrying in $delay seconds..." | |
| Start-Sleep -Seconds $delay | |
| } | |
| env: | |
| DOCKER_HOST: npipe:////./pipe/docker_engine | |
| - | |
| name: Test integration (flaky) | |
| run: | | |
| for ($i = 1; $i -le $Env:TEST_REPEAT; $i++) { | |
| Write-Host "Running integration-test (iteration $i)" | |
| .\hack\make.ps1 -TestIntegration | |
| } | |
| env: | |
| DOCKER_HOST: npipe:////./pipe/docker_engine | |
| TEST_CLIENT_BINARY: ${{ env.BIN_OUT }}\docker | |
| - | |
| name: Docker info | |
| if: ${{ always() && steps.start-daemon.outcome != 'skipped' }} | |
| run: | | |
| & "${{ env.BIN_OUT }}\docker" info | |
| env: | |
| DOCKER_HOST: npipe:////./pipe/docker_engine | |
| - | |
| name: Stop daemon | |
| if: always() | |
| run: | | |
| $ErrorActionPreference = "SilentlyContinue" | |
| Stop-Service -Force -Name docker | |
| $ErrorActionPreference = "Stop" | |
| - | |
| name: Daemon event logs | |
| if: ${{ always() && steps.start-daemon.outcome != 'skipped' }} | |
| run: | | |
| New-Item -ItemType Directory -Force -Path ".\bundles" | Out-Null | |
| Get-WinEvent -ea SilentlyContinue ` | |
| -FilterHashtable @{ProviderName= "docker"; LogName = "application"} | | |
| Sort-Object @{Expression="TimeCreated";Descending=$false} | | |
| ForEach-Object {"$($_.TimeCreated.ToUniversalTime().ToString("o")) [$($_.LevelDisplayName)] $($_.Message)"} | | |
| Tee-Object -file ".\bundles\daemon.log" | |
| - | |
| name: Stop OpenTelemetry Collector | |
| if: ${{ always() && steps.setup-otel.outcome != 'skipped' }} | |
| run: | | |
| $svc = Get-Service -DisplayName "OpenTelemetry Collector" -ErrorAction SilentlyContinue | |
| if ($svc -and $svc.Status -ne "Stopped") { | |
| ($svc | Stop-Service -PassThru).WaitForStatus("Stopped", (New-TimeSpan -Seconds 30)) | |
| } | |
| - | |
| name: Upload reports | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: windows-integration-flaky-${{ matrix.containerd }}-reports | |
| path: bundles/* | |
| retention-days: 1 |