770ac1edf8
- Create a new document `plans/implementation-plan-A-B.md` that consolidates the implementation plans for the Python rewrite (Phase A) and the migration to Linux Mint (Phase B). - The document includes detailed sections such as executive summary, checklist, prerequisites, step-by-step activities, architectural hooks for future phases, risk matrix, and references. - Ensure the plan is self-contained, adhering to specified structure and style guidelines, and does not introduce new technologies or modify existing files.
209 lines
11 KiB
Markdown
209 lines
11 KiB
Markdown
# Fase A — Rewrite in Python
|
|
|
|
> **Stato**: piano esecutivo (parte committed della roadmap).
|
|
> Prerequisito di [Fase B](idea-2-linux-host.md). Vedi [overview](ideas-overview.md).
|
|
> Baseline: ~13.000 righe PowerShell 5.1 (41 file `.ps1` + 2 `.psm1`), 2 script
|
|
> bash, 5 workflow Gitea Actions.
|
|
|
|
---
|
|
|
|
## 1. Obiettivo
|
|
|
|
Riscrivere host orchestrator + script di gestione VM in **Python 3.11+**,
|
|
**con design cross-platform fin dall'inizio** (la Fase B trasferirà l'host
|
|
su Linux Mint senza riscrivere il core).
|
|
|
|
Vincoli mantenuti:
|
|
|
|
- runtime act_runner + Gitea (consuma comandi via shell, agnostico al linguaggio)
|
|
- template VM (Windows con WinRM, Linux con SSH)
|
|
- VMware Workstation Pro come hypervisor (`vmrun` invariato)
|
|
- formato artifact e layout di storage
|
|
- workflow YAML esistenti (`gitea/workflows/*.yml`) — cambia solo la `shell:`
|
|
e il comando invocato
|
|
|
|
Il punto di ingresso `Invoke-CIJob.ps1` viene sostituito da
|
|
`python -m ci_orchestrator job ...`, invocato da
|
|
`gitea/actions/local-ci-build/action.yml`.
|
|
|
|
---
|
|
|
|
## 2. Decisioni di design forzate dal target Linux futuro
|
|
|
|
Per evitare un doppio porting in Fase B, il codice Python deve rispettare
|
|
da subito queste regole:
|
|
|
|
| Regola | Implementazione |
|
|
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
| Nessun path hardcoded | Tutti i path via `pathlib.Path` + variabili ambiente (`CI_ROOT`, `CI_TEMPLATES`, `CI_BUILD_VMS`, `CI_ARTIFACTS`, `CI_KEYS`) con default in un file `config.toml`. |
|
|
| Path-binari astratti | `vmrun`, `ssh`, `scp` cercati via `shutil.which()` con fallback a env var (`VMRUN_PATH`); default `vmrun.exe` su Windows, `vmrun` su Linux. |
|
|
| Nessuna API Windows-only nel core | `keyring` (cross-platform), `paramiko` (no openssh client esterno), `pypsrp` (no `New-PSSession`). |
|
|
| Astrazione hypervisor | Interfaccia `VmBackend` (Protocol) con metodi `clone_linked`, `start`, `stop`, `delete`, `get_ip`. Implementazione iniziale `WorkstationVmrunBackend`. Hook per `EsxiBackend` futuro (Fase C). |
|
|
| Astrazione credential store | Interfaccia `CredentialStore` con backend `KeyringCredentialStore` (default) — funziona uguale su Win Credential Manager e Linux Secret Service. |
|
|
| Logging | `logging` con formatter strutturato; nessun colore ANSI obbligatorio (alcuni runner cattura non li gestiscono). |
|
|
| Path separator nei workflow YAML | Lasciare i workflow esistenti per ora; in Fase B si aggiorneranno i path (variabili env già pronte ad assorbire la modifica). |
|
|
|
|
---
|
|
|
|
## 3. Mappa di traduzione
|
|
|
|
| Componente PowerShell | Equivalente Python |
|
|
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
| `Invoke-Vmrun` (wrapper `vmrun.exe` su Win / `vmrun` su Linux) | `subprocess.run([vmrun, '-T', 'ws', op, *args], check=False, capture_output=True)` |
|
|
| `New-PSSession` + `Invoke-Command` (WinRM) | `pypsrp.client.Client(host, username, password, ssl=True, cert_validation=False)` |
|
|
| `Copy-Item -ToSession / -FromSession` | `pypsrp.client.Client.copy()` / `fetch()` |
|
|
| `_Transport.psm1` (`ssh.exe` / `scp.exe`) | `paramiko.SSHClient` / `paramiko.SFTPClient` |
|
|
| Credential Manager (`BuildVMGuest`, `GiteaPAT`) | `keyring.get_credential('BuildVMGuest', None)` |
|
|
| Pester | `pytest` + `pytest-mock` |
|
|
| PSScriptAnalyzer | `ruff` + `mypy --strict` |
|
|
| `Write-Host` | `logging.getLogger(__name__).info(...)` |
|
|
| `ConvertTo-Json` per stato job | `json.dumps()` su dataclass |
|
|
| Workflow YAML | invariati in Fase A |
|
|
| Script `.sh` in `gitea/burnin-dummy/` | invariati (girano nel guest Linux) |
|
|
|
|
---
|
|
|
|
## 4. Cosa NON viene portato in Python
|
|
|
|
Gli script `template/` (`Prepare-*.ps1`, `Deploy-*.ps1`,
|
|
`Install-CIToolchain-*.ps1`) restano in PowerShell:
|
|
|
|
- girano una tantum (ricostruzione template)
|
|
- sono fortemente legati a Windows (autounattend.xml, registry, sysprep, WinRM enable)
|
|
- gli script `Install-CIToolchain-WinBuild*.ps1` girano **dentro** il guest
|
|
Windows e PS è il linguaggio nativo lì
|
|
|
|
In Fase B questi script restano richiamabili da host Linux con `pwsh` 7
|
|
quando serve ricostruire un template Windows (operazione rara, anche manuale).
|
|
|
|
---
|
|
|
|
## 5. Strategia di migrazione: strangler fig in 4 step
|
|
|
|
Sequenza obbligata. Ad ogni step, gli script PS non ancora portati continuano
|
|
a funzionare; la coesistenza è garantita dalla stessa CLI `python -m ci_orchestrator`
|
|
chiamata internamente dagli script `.ps1` man mano che vengono "scavati".
|
|
|
|
### Step A1 — Bootstrap progetto + moduli core (`_Common`, `_Transport`)
|
|
|
|
Output:
|
|
- `pyproject.toml`, package `src/ci_orchestrator/`
|
|
- `venv` in `F:\CI\python\venv\`
|
|
- moduli: `config.py` (env + TOML), `vmrun.py` (wrapper), `winrm.py` (pypsrp),
|
|
`ssh.py` (paramiko), `credentials.py` (keyring), `backends/workstation.py`
|
|
- protocollo `VmBackend` definito
|
|
- test pytest per ognuno (mock `subprocess`, `paramiko`, `pypsrp`)
|
|
|
|
Validazione: `lint.yml` e `self-test.yml` continuano a passare; nuovi job
|
|
pytest aggiunti al workflow `lint.yml`.
|
|
|
|
### Step A2 — Script "foglia" (no state condiviso con orchestratore)
|
|
|
|
Portare in Python (CLI subcommands `python -m ci_orchestrator <cmd>`):
|
|
|
|
- `Wait-VMReady` → `wait-ready`
|
|
- `Remove-BuildVM` → `vm remove`
|
|
- `Cleanup-OrphanedBuildVMs` → `vm cleanup`
|
|
- `Watch-DiskSpace` → `monitor disk`
|
|
- `Watch-RunnerHealth` → `monitor runner`
|
|
- `Get-CIJobSummary` → `report job`
|
|
|
|
Per ognuno: il file `.ps1` viene sostituito da uno **shim** di 3 righe che
|
|
chiama `python -m ci_orchestrator <cmd> ...`, così i call site esterni
|
|
(scheduled tasks, ecc.) non si rompono.
|
|
|
|
### Step A3 — Pipeline di build
|
|
|
|
Portare in Python:
|
|
|
|
- `New-BuildVM` → `vm new`
|
|
- `Invoke-RemoteBuild` → `build run`
|
|
- `Get-BuildArtifacts` → `artifacts collect`
|
|
|
|
Stessa strategia shim per i `.ps1` esistenti.
|
|
|
|
### Step A4 — Orchestratore + switch workflow
|
|
|
|
Portare `Invoke-CIJob.ps1` → `python -m ci_orchestrator job`.
|
|
Aggiornare `gitea/actions/local-ci-build/action.yml` per chiamare Python
|
|
direttamente (non più via shim, per ridurre overhead startup).
|
|
|
|
A questo punto i `.ps1` shim restano come fallback; eliminarli al primo
|
|
ciclo di pulizia successivo.
|
|
|
|
### Step trasversale A5 — Test e documentazione
|
|
|
|
Parallelo agli step A1-A4:
|
|
- ogni feature portata ha test pytest che coprono almeno gli scenari
|
|
documentati negli "errori frequenti" di `AGENTS.md` (#9, #10, #11, #12)
|
|
- `AGENTS.md` aggiornato con sezione "Python development" (venv, stile,
|
|
pytest, ruff)
|
|
- `docs/ARCHITECTURE.md` aggiornato con il nuovo layout package
|
|
- `README.md` aggiornato con setup Python
|
|
|
|
---
|
|
|
|
## 6. Layout repository post-Fase A
|
|
|
|
```
|
|
src/ci_orchestrator/
|
|
__init__.py
|
|
__main__.py # entry point CLI (click)
|
|
config.py # env vars + config.toml loader
|
|
credentials.py # CredentialStore protocol + KeyringStore
|
|
backends/
|
|
__init__.py
|
|
protocol.py # VmBackend Protocol
|
|
workstation.py # WorkstationVmrunBackend
|
|
transport/
|
|
__init__.py
|
|
winrm.py # pypsrp client wrapper
|
|
ssh.py # paramiko client wrapper
|
|
commands/
|
|
job.py # ex Invoke-CIJob
|
|
vm.py # new / remove / cleanup
|
|
build.py # build run
|
|
artifacts.py # collect
|
|
wait.py # wait-ready
|
|
monitor.py # disk / runner
|
|
report.py # job summary
|
|
tests/
|
|
test_backends_workstation.py
|
|
test_transport_winrm.py
|
|
test_transport_ssh.py
|
|
test_commands_job.py
|
|
...
|
|
pyproject.toml
|
|
config.example.toml
|
|
scripts/ # PS legacy (shim sottili durante migrazione)
|
|
template/ # PS invariati (provisioning template)
|
|
gitea/ # workflow + actions invariati
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Rischi e mitigazioni
|
|
|
|
| Rischio | Severità | Mitigazione |
|
|
| ---------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
|
|
| `pypsrp` ha edge case con WinRM HTTPS self-signed | Media | PoC su `wait-ready` come primo deliverable di Step A1 prima di committare al resto. |
|
|
| `keyring` sotto SYSTEM account (act_runner service) può non vedere credenziali utente | Alta | Già un problema oggi con Credential Manager; documentare l'uso di credenziali a livello machine (DPAPI machine scope su Win, file vault con `age` su Linux). |
|
|
| Perdita di know-how degli "errori frequenti" `AGENTS.md` durante refactor | Media | Convertire ognuno (#9, #10, #11, #12) in un test pytest **prima** di rimuovere il `.ps1` corrispondente. |
|
|
| Doppio mantenimento durante la migrazione | Alta | Strategia shim minimizza la finestra; `lint.yml` deve passare per **entrambi** i mondi (PSSA + ruff). |
|
|
| Astrazione `VmBackend` over-engineered se Fase C non parte mai | Bassa | Una sola implementazione concreta + Protocol = ~50 righe extra; costo trascurabile. |
|
|
| act_runner cattura male l'output Python (encoding UTF-8 vs cp1252 su Win) | Media | Forzare `PYTHONIOENCODING=utf-8` in `runner/config.yaml` env. |
|
|
|
|
---
|
|
|
|
## 8. Definizione di "fatto" (Fase A)
|
|
|
|
- [ ] Tutti gli script in `scripts/` portati a Python o ridotti a shim
|
|
- [ ] `Invoke-CIJob.ps1` non più presente nei workflow YAML
|
|
- [ ] `pytest` verde con coverage minima 70% su `src/ci_orchestrator/`
|
|
- [ ] `ruff check` + `mypy --strict` puliti su tutto `src/`
|
|
- [ ] `lint.yml` aggiornato: PSSA per file PS legacy + ruff/mypy per Python
|
|
- [ ] Workflow `build-nsInnoUnp.yml` PASS su matrice Windows + Linux
|
|
- [ ] Smoke test (`Test-Smoke.ps1` o equivalente Python) PASS
|
|
- [ ] Capacity burn-in 4 job concorrenti PASS
|
|
- [ ] `README.md`, `AGENTS.md`, `docs/ARCHITECTURE.md`, `docs/HOST-SETUP.md` aggiornati
|