- 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.
11 KiB
Fase A — Rewrite in Python
Stato: piano esecutivo (parte committed della roadmap). Prerequisito di Fase B. Vedi overview. 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 (
vmruninvariato) - formato artifact e layout di storage
- workflow YAML esistenti (
gitea/workflows/*.yml) — cambia solo lashell: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*.ps1girano 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, packagesrc/ci_orchestrator/venvinF:\CI\python\venv\- moduli:
config.py(env + TOML),vmrun.py(wrapper),winrm.py(pypsrp),ssh.py(paramiko),credentials.py(keyring),backends/workstation.py - protocollo
VmBackenddefinito - 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-readyRemove-BuildVM→vm removeCleanup-OrphanedBuildVMs→vm cleanupWatch-DiskSpace→monitor diskWatch-RunnerHealth→monitor runnerGet-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 newInvoke-RemoteBuild→build runGet-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.mdaggiornato con sezione "Python development" (venv, stile, pytest, ruff)docs/ARCHITECTURE.mdaggiornato con il nuovo layout packageREADME.mdaggiornato 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.ps1non più presente nei workflow YAMLpytestverde con coverage minima 70% susrc/ci_orchestrator/ruff check+mypy --strictpuliti su tuttosrc/lint.ymlaggiornato: PSSA per file PS legacy + ruff/mypy per Python- Workflow
build-nsInnoUnp.ymlPASS su matrice Windows + Linux - Smoke test (
Test-Smoke.ps1o equivalente Python) PASS - Capacity burn-in 4 job concorrenti PASS
README.md,AGENTS.md,docs/ARCHITECTURE.md,docs/HOST-SETUP.mdaggiornati