From db0842b3aa34e39e7063c33b0881ad6a42494c22 Mon Sep 17 00:00:00 2001 From: HyxiaoGe <18889592303@163.com> Date: Mon, 31 Aug 2026 14:51:20 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=A6=81=E6=AD=A2=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=90=AF=E5=8A=A8=20Docker=20Desktop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 背景:Windows 服务型 Runner 从 Session 0 启动并保留 Docker Desktop,会占用仅该会话可访问的后端管道并阻断桌面用户。 改动:发布前仅检查预配置 Docker Engine;不可用或无权限时立即失败,并新增契约测试禁止 GUI、服务启动、等待重试和 Runner 跟踪绕过。 验证:python3.11 -m pytest 关联配置测试通过 83 项及 14 个子测试;ruff 与 git diff --check 通过。 Co-Authored-By: Codex --- .github/workflows/deploy.yml | 68 +++----------------------- test/test_ci_cd_permission_boundary.py | 29 +++++------ 2 files changed, 23 insertions(+), 74 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 156d0733..df0a55d6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -142,69 +142,17 @@ jobs: - name: Verify Docker access shell: powershell -NoProfile -ExecutionPolicy Bypass -Command ". '{0}'" run: | - $ErrorActionPreference = "Continue" - $serviceNames = @('docker', 'com.docker.service') - $dockerDesktopStarted = $false - $desktopPaths = @( - "$env:ProgramFiles\Docker\Docker\Docker Desktop.exe", - "${env:ProgramFiles(x86)}\Docker\Docker\Docker Desktop.exe" - ) - - for ($attempt = 1; $attempt -le 6; $attempt++) { - Write-Host "Checking Docker access, attempt $attempt/6..." - - docker version - if ($LASTEXITCODE -eq 0) { - docker ps - if ($LASTEXITCODE -eq 0) { - Write-Host "Docker daemon ready" - exit 0 - } - } - - foreach ($serviceName in $serviceNames) { - $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue - if ($null -ne $service -and $service.Status -ne "Running") { - Write-Host "Starting Docker service: $serviceName" - try { - Start-Service -Name $serviceName -ErrorAction Stop - } catch { - Write-Host "Docker service start failed for ${serviceName}: $($_.Exception.Message)" - } - } elseif ($null -ne $service) { - Write-Host "Docker service ${serviceName} status: $($service.Status)" - } - } - - if (-not $dockerDesktopStarted) { - foreach ($desktopPath in $desktopPaths) { - if ($desktopPath -and (Test-Path $desktopPath)) { - Write-Host "Starting Docker Desktop: $desktopPath" - $runnerTrackingId = $env:RUNNER_TRACKING_ID - try { - # Keep Docker Desktop alive after the self-hosted runner cleans up this job. - $env:RUNNER_TRACKING_ID = "" - Start-Process -FilePath $desktopPath -WindowStyle Minimized - $dockerDesktopStarted = $true - break - } catch { - Write-Host "Docker Desktop start failed: $($_.Exception.Message)" - } finally { - $env:RUNNER_TRACKING_ID = $runnerTrackingId - } - } - } - } - - if ($attempt -lt 6) { - Start-Sleep -Seconds (10 * $attempt) - } + docker version + if ($LASTEXITCODE -ne 0) { + Write-Host "当前 Runner 未提供可用的 Docker Engine;请先完成 Runner 预配置" + exit $LASTEXITCODE } - Write-Host "Docker daemon did not become ready" - docker version docker ps - exit 1 + if ($LASTEXITCODE -ne 0) { + Write-Host "当前 Runner 无权访问 Docker Engine;请先修复 Runner 权限" + exit $LASTEXITCODE + } - name: Test and build Docker image timeout-minutes: 15 diff --git a/test/test_ci_cd_permission_boundary.py b/test/test_ci_cd_permission_boundary.py index a682378e..d5c74e80 100644 --- a/test/test_ci_cd_permission_boundary.py +++ b/test/test_ci_cd_permission_boundary.py @@ -365,7 +365,7 @@ def test_release_workflow_only_runs_for_master(self) -> None: self.assertEqual(publish_job.count("name: Publish master images on Windows runner"), 1) self.assertNotIn("name: Build on Windows runner", publish_job) - def test_windows_publish_recovers_docker_daemon_before_build(self) -> None: + def test_windows_publish_requires_preconfigured_docker_engine(self) -> None: publish_job = self.release_document["jobs"]["publish"] verify_step = workflow_step(publish_job, "Verify Docker access") script = verify_step["run"] @@ -374,19 +374,20 @@ def test_windows_publish_recovers_docker_daemon_before_build(self) -> None: verify_step["shell"], 'powershell -NoProfile -ExecutionPolicy Bypass -Command ". \'{0}\'"', ) - self.assertIn("for ($attempt = 1; $attempt -le 6; $attempt++)", script) - self.assertIn("$serviceNames = @('docker', 'com.docker.service')", script) - self.assertIn("Start-Service -Name $serviceName", script) - self.assertIn("Docker Desktop.exe", script) - self.assertIn("Start-Process -FilePath $desktopPath", script) - self.assertIn('$env:RUNNER_TRACKING_ID = ""', script) - self.assertLess( - script.index('$env:RUNNER_TRACKING_ID = ""'), - script.index("Start-Process -FilePath $desktopPath"), - ) - self.assertIn("Start-Sleep -Seconds (10 * $attempt)", script) - self.assertIn("Docker daemon ready", script) - self.assertIn("Docker daemon did not become ready", script) + self.assertEqual(script.count("docker version"), 1) + self.assertEqual(script.count("docker ps"), 1) + self.assertIn("if ($LASTEXITCODE -ne 0)", script) + self.assertIn("exit $LASTEXITCODE", script) + + for forbidden_command in ( + "Docker Desktop.exe", + "Start-Process", + "Start-Service", + "RUNNER_TRACKING_ID", + "Start-Sleep", + ): + with self.subTest(forbidden_command=forbidden_command): + self.assertNotIn(forbidden_command, script) def test_release_runs_are_never_cancelled_mid_deployment(self) -> None: self.assertRegex(