Files
local-ci-cd-system/plans/idea-1-python-rewrite.md
T
Simone 97ec820ebb ci: rename build-nsInnoUnp.yml -> build-ns7zip.yml; update doc refs
Workflow now targets Simone/nsis-plugin-ns7zip. Renamed the file and
the workflow `name:`, and updated all references in active docs
(plans/archived and docs/archived left as frozen history).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 20:12:25 +02:00

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-ns7zip.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