6db20d66bf
- Add plans/idea-3-powershell-removal.md: Phase C goal is to eliminate the pwsh dependency from the Linux host by porting the remaining PS scripts (bench run, validate host, smoke run) to Python sub-commands. - Update idea-3-esxi-support.md header: Fase C → Fase D. - Update ideas-overview.md: new four-phase table (A/B/C/D) with prerequisiti and criteri for Phase C. - Rename all "Hook futuri Fase C" (ESXi) → "Hook futuri Fase D" in implementation-plan-A-B.md; add Phase C/D entries to summary and references section. - Update PhaseB-user-checklist.md end note and CLAUDE.md to point Phase C → pwsh removal, Phase D → ESXi. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
172 lines
7.8 KiB
Markdown
172 lines
7.8 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with this repository.
|
||
|
||
---
|
||
|
||
## Project Overview
|
||
|
||
Self-hosted CI/CD system running on a Windows 11 host with VMware Workstation. Each build job runs inside an ephemeral VM (linked clone from a frozen template snapshot), communicates via WinRM (Windows guests) or SSH (Linux guests), and is destroyed after the job.
|
||
|
||
**Current state**: The Python orchestrator (`src/ci_orchestrator/`) is the production entry point. The PowerShell scripts in `scripts/` are 3-line shims that forward to the Python CLI. All new logic goes in Python.
|
||
|
||
---
|
||
|
||
## Development Commands
|
||
|
||
### Python (primary)
|
||
|
||
The production venv is at `/opt/ci/venv` (Linux host). To invoke the CLI as the service user:
|
||
|
||
```bash
|
||
sudo -u ci-runner /opt/ci/venv/bin/python -m ci_orchestrator --help
|
||
```
|
||
|
||
```bash
|
||
# Dev venv setup (one-time)
|
||
python -m venv .venv
|
||
.\.venv\Scripts\python.exe -m pip install -e ".[dev]"
|
||
|
||
# Run all tests with coverage (gate: ≥90%)
|
||
python -m pytest tests/python -q --cov=ci_orchestrator --cov-fail-under=90
|
||
|
||
# Run a single test file
|
||
python -m pytest tests/python/test_commands_job.py -q
|
||
|
||
# Lint
|
||
python -m ruff check src tests/python
|
||
|
||
# Type-check (strict)
|
||
python -m mypy --strict src
|
||
|
||
# CLI
|
||
python -m ci_orchestrator --help
|
||
```
|
||
|
||
**Important**: The production venv at `F:\CI\python\venv` must be installed **non-editable** (`pip install .`, not `-e`), because act_runner runs as LocalSystem and cannot resolve editable `.pth` files from a transient RunnerWork path. Re-run `pip install .` into the production venv after every code change. The dev `.venv` may use `-e`.
|
||
|
||
### PowerShell linting
|
||
|
||
```powershell
|
||
Invoke-ScriptAnalyzer -Path scripts\ -Recurse -Settings PSScriptAnalyzerSettings.psd1 -Severity Error,Warning
|
||
# Auto-fix formatting:
|
||
Invoke-ScriptAnalyzer -Path scripts\ -Recurse -Fix
|
||
```
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
### Pipeline flow
|
||
|
||
```
|
||
git push → Gitea Actions → act_runner (Windows service) → python -m ci_orchestrator job
|
||
→ vm new (linked clone)
|
||
→ wait-ready (WinRM poll or SSH poll)
|
||
→ build run (WinRM Invoke-Command or SSH+SFTP)
|
||
→ artifacts collect (WinRM Copy-Item or SCP)
|
||
→ vm remove (vmrun stop + deleteVM)
|
||
```
|
||
|
||
The host **never executes build tools directly** — it only orchestrates VM lifecycle.
|
||
|
||
### Python package layout (`src/ci_orchestrator/`)
|
||
|
||
| Module | Role |
|
||
|--------|------|
|
||
| `__main__.py` | click entry point; registers all sub-command groups |
|
||
| `config.py` | `Config` dataclass + TOML/env loader; resolution: OS defaults → `config.toml` → env vars |
|
||
| `credentials.py` | `CredentialStore` Protocol + `KeyringCredentialStore` |
|
||
| `backends/protocol.py` | `VmBackend` Protocol + `VmHandle` + `VmState` (hypervisor-agnostic) |
|
||
| `backends/workstation.py` | `WorkstationVmrunBackend` — wraps `vmrun.exe` subprocesses |
|
||
| `backends/__init__.py` | `load_backend(config)` factory — the only import application code should use |
|
||
| `transport/winrm.py` | WinRM transport via `pypsrp` |
|
||
| `transport/ssh.py` | SSH/SFTP transport via `paramiko` |
|
||
| `transport/errors.py` | `TransportConnectError`, `TransportCommandError` |
|
||
| `commands/job.py` | Full end-to-end orchestrator (auto-detects guest OS from VMX `guestOS` field) |
|
||
| `commands/vm.py` | `vm new / remove / cleanup` |
|
||
| `commands/build.py` | `build run` |
|
||
| `commands/artifacts.py` | `artifacts collect` |
|
||
| `commands/wait.py` | `wait-ready` |
|
||
| `commands/monitor.py` | `monitor disk / runner` |
|
||
| `commands/report.py` | `report job` |
|
||
|
||
**Key design rule**: Application code (especially `commands/job.py`) must import `backends.load_backend` only — never `WorkstationVmrunBackend` directly. This keeps the Phase D ESXi extension path open.
|
||
|
||
### Transport selection
|
||
|
||
`commands/job.py` reads `guestOS = "..."` from the cloned VMX. `ubuntu-*` → Linux/SSH (`transport/ssh.py`); anything else → Windows/WinRM (`transport/winrm.py`).
|
||
|
||
### VM templates and snapshots
|
||
|
||
| Template | VMX path | Snapshot | Transport |
|
||
|----------|----------|----------|-----------|
|
||
| WinBuild2025 | `F:\CI\Templates\WinBuild2025\WinBuild2025.vmx` | `BaseClean` | WinRM HTTPS/5986 |
|
||
| WinBuild2022 | `F:\CI\Templates\WinBuild2022\WinBuild2022.vmx` | `BaseClean` | WinRM HTTPS/5986 |
|
||
| LinuxBuild2404 | `F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx` | `BaseClean-Linux` | SSH/22 (`ci_build`, key `F:\CI\keys\ci_linux`) |
|
||
|
||
Snapshots **must be taken from powered-off state** — presence of `.vmem` in the template dir means the snapshot captured running memory and `vmrun clone ... linked` will fail.
|
||
|
||
### Configuration
|
||
|
||
The config file is auto-discovered at `$CI_ROOT/config.toml` (Linux default: `/var/lib/ci/config.toml`; Windows: `F:\CI\config.toml`). Set `$CI_CONFIG` to override. The file at `/var/lib/ci/config.toml` is the live config on the Linux host — keep its `[paths]` section pointing to `/var/lib/ci/...` (not the old Windows `F:/CI/...` paths). Environment variables `CI_ROOT`, `CI_TEMPLATES`, `CI_BUILD_VMS`, `CI_ARTIFACTS`, `CI_KEYS` override the file. `CI_PYTHON_LAUNCHER` in `runner/config.yaml` is the single source of truth for the system Python path used to bootstrap the production venv.
|
||
|
||
---
|
||
|
||
## PowerShell 5.1 Constraints
|
||
|
||
All `.ps1` / `.psm1` scripts run on **Windows PowerShell 5.1** (not Core/7). Every script must start with:
|
||
|
||
```powershell
|
||
#Requires -Version 5.1
|
||
Set-StrictMode -Version Latest
|
||
$ErrorActionPreference = 'Stop'
|
||
```
|
||
|
||
Forbidden PS 7+ syntax (use the PS 5.1 alternatives):
|
||
|
||
| Forbidden | Use instead |
|
||
|-----------|-------------|
|
||
| `$x ??= 'val'` | `if ($null -eq $x) { $x = 'val' }` |
|
||
| `$a ?? $b` | `if ($null -ne $a) { $a } else { $b }` |
|
||
| `cmd1 && cmd2` | `cmd1; if ($LASTEXITCODE -eq 0) { cmd2 }` |
|
||
| Ternary `$a ? $b : $c` | `if ($a) { $b } else { $c }` |
|
||
| `ForEach-Object -Parallel` | `foreach` or `Start-Job` |
|
||
|
||
Always check `$LASTEXITCODE` after `vmrun`, `ssh`, `scp` and `throw` if non-zero.
|
||
|
||
---
|
||
|
||
## Python Conventions
|
||
|
||
- `click.echo` everywhere — no bare `print()`.
|
||
- No `subprocess.run(..., shell=True)`. Use `paramiko` for SSH, `pypsrp` for WinRM, `keyring` for credentials.
|
||
- No global variables — state lives in `Config` and objects passed explicitly.
|
||
- All public functions and exported helpers must have type hints. Avoid `Any` except in pytest fixtures/mocks.
|
||
- `ruff` + `mypy --strict` are enforced in CI. The coverage gate is **90%**.
|
||
|
||
---
|
||
|
||
## Known Gotchas (from `AGENTS.md`)
|
||
|
||
- **`vmrun getGuestIPAddress`** can return exit code 0 with `"Error: The VMware Tools are not running..."` on stdout. Filter lines starting with `"Error:"`.
|
||
- **Linux machine-id**: Ubuntu cloud images ship with a fixed `/etc/machine-id`. VMware DHCP assigns IPs by machine-id — clones get the same IP and collide. The `BaseClean-Linux` snapshot must be taken after truncating machine-id.
|
||
- **`& nativecmd 2>$null` under `$ErrorActionPreference='Stop'`**: native stderr is converted to an ErrorRecord before `2>$null` discards it, causing a terminating error. Wrap with `$ErrorActionPreference = 'SilentlyContinue'` or `2>&1 | Out-Null`.
|
||
- **Snapshot with VM running**: never take a template snapshot while the VM is powered on.
|
||
- **WinRM vs SSH separation**: do not modify the Windows WinRM branch and the Linux SSH branch in the same commit/PR. Keep `if ($GuestOS -eq 'Linux')` guards clean.
|
||
|
||
---
|
||
|
||
## Key Files Reference
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `AGENTS.md` | Environment reference + error registry — read before writing any script |
|
||
| `docs/ARCHITECTURE.md` | Full system diagram and component descriptions |
|
||
| `docs/CI-FLOW.md` | Step-by-step pipeline description |
|
||
| `docs/RUNBOOK.md` | Common incident triage |
|
||
| `TODO.md` | Roadmap, audit trail, backlog |
|
||
| `plans/` | Phase closeout docs (A1–A5, B5) and implementation plans |
|
||
| `config.example.toml` | Annotated config template |
|
||
| `tests/python/test_agents_errors.py` | Regression tests for `AGENTS.md` errors #9–#12 — do not remove |
|