feat(template): cleanup-after-update + ruff fixes

Move disk cleanup out of the Windows Update loop:
- PS scripts: extract Invoke-DiskCleanup function, add -CleanupOnly switch
  for standalone post-update cleanup pass.
- Python command: always pass -SkipCleanup:$true during WU loop; after
  loop exits 0, run a separate -CleanupOnly invocation (skipped if
  --skip-cleanup). Fails fast if cleanup-only returns nonzero.
- Tests: 3 new tests for cleanup-runs-after-loop, skip-cleanup suppresses
  it, and cleanup-failure propagates as ClickException.
- ruff --fix: remove unused shutil import, sort imports, fix UP037/UP035.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 02:41:15 +02:00
parent d5f362b8dc
commit f96b9953c5
6 changed files with 356 additions and 225 deletions
+30 -5
View File
@@ -57,7 +57,6 @@ from __future__ import annotations
import fnmatch
import json
import shutil
import socket
import subprocess
import sys
@@ -68,8 +67,8 @@ from pathlib import Path
import click
from ci_orchestrator.backends.workstation import WorkstationVmrunBackend
from ci_orchestrator.backends.protocol import VmHandle
from ci_orchestrator.backends.workstation import WorkstationVmrunBackend
from ci_orchestrator.config import load_config
from ci_orchestrator.transport.errors import TransportError
from ci_orchestrator.transport.winrm import WinRmResult, WinRmTransport
@@ -324,7 +323,7 @@ def _run_with_heartbeat(
def _worker() -> None:
try:
result_holder.append(transport.run(script, check=check))
except BaseException as exc: # noqa: BLE001
except BaseException as exc:
exc_holder.append(exc)
finally:
stop_event.set()
@@ -507,8 +506,7 @@ def template_prepare_win(
args_parts.append("-SkipWindowsUpdate:$true")
if skip_tier2:
args_parts.append("-SkipTier2:$true")
if skip_cleanup:
args_parts.append("-SkipCleanup:$true")
args_parts.append("-SkipCleanup:$true") # always skip during WU loop; cleanup runs separately after
args_str = " ".join(args_parts)
# Run toolchain script — loop handles Windows Update reboot (exit 3010)
@@ -569,6 +567,33 @@ def template_prepare_win(
_wait_winrm(transport, winrm_timeout)
click.echo("[prepare-win] WinRM ready after reboot.")
# Post-update disk cleanup — runs once after all WU reboots complete
if not skip_cleanup:
click.echo("[prepare-win] Running post-update disk cleanup (-CleanupOnly) ...")
cleanup_invoke = (
"Set-ExecutionPolicy Bypass -Scope Process -Force\n"
"$ec = 0\n"
"try {\n"
f" & '{e(guest_script)}' -BuildPassword '{e(build_password)}'"
f" -BuildUsername '{e(build_username)}'"
f" -AdminPassword '{e(admin_password)}' -CleanupOnly:$true\n"
" $ec = if ($null -eq $LASTEXITCODE) { 0 } else { $LASTEXITCODE }\n"
"} catch {\n"
" Write-Error $_\n"
" $ec = 1\n"
"}\n"
'"CI_EXITCODE:$ec"\n'
)
cleanup_result = _run_with_heartbeat(transport, cleanup_invoke, check=False)
cleanup_ec = _parse_exit_marker(cleanup_result.stdout)
if cleanup_result.stdout.strip():
click.echo(cleanup_result.stdout.strip())
if cleanup_result.stderr.strip():
click.echo(cleanup_result.stderr.strip(), err=True)
if cleanup_ec != 0:
raise click.ClickException(f"Disk cleanup failed (exit {cleanup_ec}).")
click.echo("[prepare-win] Post-update cleanup complete.")
# Install CI-StaticIp scheduled task
if static_ip_path is not None:
click.echo("[prepare-win] Installing CI-StaticIp scheduled task ...")
+1 -1
View File
@@ -39,9 +39,9 @@ from __future__ import annotations
import json
import time
from collections.abc import Generator
from contextlib import contextmanager
from pathlib import Path
from typing import Generator
class IpSlotPool: