Add unified implementation plan for Phase A and Phase B in Markdown format
- 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.
This commit is contained in:
+178
-148
@@ -1,178 +1,208 @@
|
||||
# Idea 1 — Conversione del sistema in Python
|
||||
# Fase A — Rewrite in Python
|
||||
|
||||
> Stato: **proposta / valutazione**. Documento esplorativo, non piano esecutivo.
|
||||
> Baseline: ~13.000 righe PowerShell 5.1 (41 file `.ps1` + 2 `.psm1`), 2 script bash,
|
||||
> 5 workflow Gitea Actions, orchestratore `Invoke-CIJob.ps1` + moduli
|
||||
> `_Common.psm1` / `_Transport.psm1`.
|
||||
> **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 l'intero sistema CI/CD locale (host orchestrator + script di
|
||||
provisioning template) in **Python 3.11+**, mantenendo invariati:
|
||||
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)
|
||||
- formato artifact e layout `F:\CI\`
|
||||
- compatibilità con i workflow YAML esistenti (`gitea/workflows/*.yml`)
|
||||
- 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 `invoke_ci_job.py`
|
||||
(o `python -m ci_job ...`), invocato da `gitea/actions/local-ci-build/action.yml`.
|
||||
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. Fattibilità
|
||||
## 2. Decisioni di design forzate dal target Linux futuro
|
||||
|
||||
### 2.1 Mappa di traduzione dei componenti
|
||||
Per evitare un doppio porting in Fase B, il codice Python deve rispettare
|
||||
da subito queste regole:
|
||||
|
||||
| Componente PowerShell | Equivalente Python | Note |
|
||||
| ----------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------- |
|
||||
| `Invoke-Vmrun` (wrapper `vmrun.exe` su Win / `vmrun` su Linux) | `subprocess.run([vmrun, '-T', 'ws', ...])` | Identico, gestione `returncode` esplicita. Path binario configurabile via env var. |
|
||||
| `New-PSSession` + `Invoke-Command` (WinRM) | `pypsrp` (`pypsrp.client.Client`) o `pywinrm` | **Rischio basso**. `pypsrp` supporta HTTPS self-signed e PSRP nativo. |
|
||||
| `Copy-Item -ToSession / -FromSession` | `pypsrp.client.Client.copy()` / `fetch()` | Throughput simile a WinRM nativo. |
|
||||
| `_Transport.psm1` (ssh.exe / scp.exe) | `paramiko` o `fabric`, oppure subprocess di `ssh`/`scp` | `paramiko` elimina dipendenza da OpenSSH client. |
|
||||
| Credential Manager (`BuildVMGuest`) | `keyring` (backend `Windows Credential Manager`) | API cross-platform; stesso store sotto Windows. |
|
||||
| `Get-StoredCredential` | `keyring.get_credential(target, None)` | Modulo PyPI `keyring`. |
|
||||
| `Pester` test | `pytest` + `pytest-mock` | Riscrittura test 1:1 fattibile. |
|
||||
| `PSScriptAnalyzer` | `ruff` + `mypy` + `pylint` | Profilo lint più rigoroso (tipi statici). |
|
||||
| Logging `Write-Host` | `logging` (stdout handler) | act_runner cattura stdout normalmente. |
|
||||
| `ConvertTo-Json` per stato | `json` stdlib | |
|
||||
| Workflow YAML | **Invariati** | Cambia solo lo `shell:` / il comando invocato. |
|
||||
| Script `.sh` in `gitea/burnin-dummy/` | Invariati | Girano nel guest Linux. |
|
||||
| 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). |
|
||||
|
||||
### 2.2 Aree senza equivalenti dirette / da progettare
|
||||
---
|
||||
|
||||
1. **`Set-StrictMode -Version Latest`** → in Python si ottiene con `mypy --strict`
|
||||
+ `from __future__ import annotations`. Cambia il modello mentale (non è
|
||||
runtime ma static check).
|
||||
2. **`ShouldProcess` / `-WhatIf`** → richiede un flag `--dry-run` esplicito
|
||||
implementato manualmente in ogni comando state-changing.
|
||||
3. **PSSessionOption con `-SkipCACheck`** → `pypsrp` accetta
|
||||
`cert_validation=False` direttamente.
|
||||
4. **Pipeline objects** (es. `Get-ChildItem | Where-Object …`) → diventano
|
||||
list comprehension / generator. Refactor logico, non meccanico.
|
||||
5. **PowerShell auto-loading di moduli** → in Python serve `pyproject.toml` +
|
||||
package layout (`src/ci_orchestrator/...`) e installazione (`pip install -e .`).
|
||||
## 3. Mappa di traduzione
|
||||
|
||||
### 2.3 Dipendenze runtime aggiuntive sull'host
|
||||
| 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) |
|
||||
|
||||
- Python 3.11+ (Microsoft Store o python.org installer)
|
||||
- pacchetti: `pypsrp[credssp]`, `paramiko`, `keyring`, `pyyaml`, `click`
|
||||
(CLI), `rich` (output colorato), `pytest`, `ruff`, `mypy`
|
||||
- Tutti installabili in un `venv` isolato (`F:\CI\python\venv\`) →
|
||||
zero impatto sul Python di sistema.
|
||||
---
|
||||
|
||||
### 2.4 Compatibilità con act_runner
|
||||
## 4. Cosa NON viene portato in Python
|
||||
|
||||
act_runner invoca uno step `run:` con `shell: powershell` oggi. Cambia in:
|
||||
Gli script `template/` (`Prepare-*.ps1`, `Deploy-*.ps1`,
|
||||
`Install-CIToolchain-*.ps1`) restano in PowerShell:
|
||||
|
||||
```yaml
|
||||
- shell: cmd
|
||||
run: F:\CI\python\venv\Scripts\python.exe -m ci_orchestrator job ...
|
||||
- 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
|
||||
```
|
||||
|
||||
oppure si lascia un wrapper `.ps1` minimale che chiama Python (più graduale).
|
||||
---
|
||||
|
||||
### 2.5 Rischi specifici
|
||||
## 7. Rischi e mitigazioni
|
||||
|
||||
| Rischio | Severità | Mitigazione |
|
||||
| -------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------- |
|
||||
| `pypsrp` ha edge case con prompt UAC / loopback HTTPS | Media | PoC isolato su `Wait-VMReady` equivalente prima di committare alla rewrite. |
|
||||
| `keyring` sotto SYSTEM account (act_runner service) può non vedere credenziali utente | Alta | Già un problema oggi con Credential Manager; stessa mitigazione (DPAPI machine scope o vault file). |
|
||||
| Perdita di know-how dei `#11`, `#12` di `AGENTS.md` durante refactor | Media | Riportare ogni "errore frequente" come **test pytest** prima di portare il codice. |
|
||||
| Doppio mantenimento durante la migrazione | Alta | Migrazione "big bang" per branch sconsigliata; usare strategia 2.6. |
|
||||
|
||||
### 2.6 Strategia di migrazione consigliata
|
||||
|
||||
**Strangler fig**, in 4 fasi:
|
||||
|
||||
1. Estrarre `_Common.psm1` + `_Transport.psm1` in `ci_orchestrator/transport.py`
|
||||
e `ci_orchestrator/vmrun.py`; mantenere gli script `.ps1` esistenti che
|
||||
chiamano Python via `python -c` per le primitive critiche. Validazione:
|
||||
workflow `lint.yml` e `self-test.yml` continuano a passare.
|
||||
2. Portare gli script "foglia" senza state condiviso: `Wait-VMReady`,
|
||||
`Remove-BuildVM`, `Cleanup-OrphanedBuildVMs`, `Watch-DiskSpace`,
|
||||
`Watch-RunnerHealth`. Ognuno con i suoi test pytest.
|
||||
3. Portare `New-BuildVM` + `Get-BuildArtifacts` + `Invoke-RemoteBuild`.
|
||||
4. Portare `Invoke-CIJob` (orchestratore) per ultimo, switchando il workflow
|
||||
YAML solo a quel punto.
|
||||
|
||||
Gli script di **template setup** (`template/Prepare-*.ps1`,
|
||||
`Deploy-*.ps1`, `Install-CIToolchain-*.ps1`) restano in PowerShell perché
|
||||
girano una tantum e sono fortemente legati a Windows
|
||||
(autounattend.xml, registry, sysprep). Convertirli ha ROI basso.
|
||||
| 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. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Stima volumi / sforzo (relativa)
|
||||
## 8. Definizione di "fatto" (Fase A)
|
||||
|
||||
| Fase | Indicatori dimensione | Complessità |
|
||||
| ----------------------------------------------------- | ------------------------------------ | ----------- |
|
||||
| F1 — Moduli `_Common` + `_Transport` | ~540 righe PS → ~600 righe Py + test | Media |
|
||||
| F2 — Script foglia (6 file) | ~1.500 righe PS | Bassa |
|
||||
| F3 — Build pipeline (3 file core) | ~2.000 righe PS | Media |
|
||||
| F4 — Orchestrator + switch workflow | ~800 righe PS | Alta |
|
||||
| F5 — Riscrittura test Pester → pytest | 4 file test esistenti + nuovi | Media |
|
||||
| F6 — Documentazione (AGENTS.md, README, docs/) | 24 file md | Bassa |
|
||||
|
||||
Le fasi F1→F4 devono essere sequenziali; F5 si fa in parallelo a ciascuna fase
|
||||
(test della fase prima del merge). F6 si fa a fine F4.
|
||||
|
||||
---
|
||||
|
||||
## 4. Pro / Contro
|
||||
|
||||
### Pro
|
||||
|
||||
- **Cross-platform di base**: rimuove il vincolo PS 5.1 (vedi Idea 2).
|
||||
- Ecosistema test/typing/lint più ricco (`pytest`, `mypy`, `ruff`).
|
||||
- Type hints + dataclass riducono la classe di bug `Set-StrictMode` non cattura.
|
||||
- Libreria `pypsrp` è attivamente manutenuta; `paramiko` standard de facto.
|
||||
- Più sviluppatori conoscono Python che PS 5.1.
|
||||
|
||||
### Contro
|
||||
|
||||
- **Costo migrazione alto** rispetto al beneficio se il sistema resta
|
||||
Windows-only e funziona già (oggi è in production-ready).
|
||||
- Doppio mantenimento per ~tutta la durata della migrazione.
|
||||
- `pypsrp` su Windows Server target richiede tuning (CredSSP, message
|
||||
encryption) — possibili sorprese non visibili oggi con WinRM nativo PS.
|
||||
- Gli `Install-CIToolchain-WinBuild2025.ps1` restano comunque in PS
|
||||
(girano nel guest Windows) → la codebase non sarà mai 100% Python.
|
||||
- Documentazione (`AGENTS.md`, `docs/BEST-PRACTICES.md`) tutta da riscrivere.
|
||||
|
||||
---
|
||||
|
||||
## 5. Decision checklist
|
||||
|
||||
Procedere **solo se** almeno 2 dei seguenti sono veri:
|
||||
|
||||
- [ ] È prevista l'Idea 2 (host Linux) o l'Idea 3 (ESXi) entro 6 mesi.
|
||||
- [ ] Si vogliono assumere/coinvolgere sviluppatori non-Windows.
|
||||
- [ ] Si pianifica integrazione con tool Python (es. Ansible per
|
||||
provisioning, librerie pyVmomi per ESXi).
|
||||
- [ ] Il debito tecnico di PS 5.1 (no async, no ternari, dipendenza da
|
||||
Credential Manager) sta bloccando feature concrete.
|
||||
|
||||
Se nessuna è vera, **non vale la pena**: investire piuttosto in test e
|
||||
osservabilità sul codice PS attuale.
|
||||
|
||||
---
|
||||
|
||||
## 6. Sinergia con Idee 2 e 3
|
||||
|
||||
- **Idea 2 (host Linux)**: la riscrittura Python è **utile ma non strettamente
|
||||
obbligata**. VMware Workstation Pro esiste nativamente per Linux con lo
|
||||
stesso `vmrun`, quindi il transport può restare identico (PS 7 su Linux
|
||||
può continuare a chiamare `vmrun`). Python rende comunque il porting più
|
||||
semplice se si valuta di passare a un hypervisor diverso (es. KVM con
|
||||
`libvirt`).
|
||||
- **Idea 3 (ESXi)**: `pyVmomi` (SDK ufficiale VMware per ESXi/vCenter) è
|
||||
**solo Python**. Equivalenti PowerShell sono PowerCLI (richiede
|
||||
PS 7+ moduli VMware.PowerCLI installabili anche su PS 5.1, ma con limiti).
|
||||
Andare su Python apre la strada al supporto ESXi in modo idiomatico.
|
||||
|
||||
**Raccomandazione**: se si vuole davvero perseguire Idee 2 o 3, la riscrittura
|
||||
Python diventa quasi obbligata. Se invece il sistema resta su Windows + VMware
|
||||
Workstation, la conversione è **opzionale** e non urgente.
|
||||
- [ ] 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
|
||||
|
||||
+177
-179
@@ -1,216 +1,214 @@
|
||||
# Idea 2 — Adattare il sistema a girare su host Linux (Mint)
|
||||
# Fase B — Migrazione host su Linux Mint
|
||||
|
||||
> Stato: **proposta / valutazione**.
|
||||
> Target host: Linux Mint (kernel ≥ 6.x, su hardware compatibile con
|
||||
> virtualizzazione hardware; KVM/Intel VT-x presenti su i9-10900X).
|
||||
> **Stato**: piano esecutivo (parte committed della roadmap).
|
||||
> Prerequisito: [Fase A](idea-1-python-rewrite.md) completata.
|
||||
> Vedi [overview](ideas-overview.md).
|
||||
> Hypervisor scelto: **VMware Workstation Pro Linux** (zero conversione template).
|
||||
|
||||
---
|
||||
|
||||
## 1. Obiettivo
|
||||
|
||||
Spostare l'host CI/CD da Windows 11 + VMware Workstation Pro a Linux Mint,
|
||||
mantenendo:
|
||||
Spostare l'host CI/CD da Windows 11 + Workstation Pro a **Linux Mint** +
|
||||
**Workstation Pro Linux**, mantenendo:
|
||||
|
||||
- act_runner / Gitea (identici, già cross-platform — `act_runner` ha build Linux)
|
||||
- workflow YAML invariati
|
||||
- template VM Windows e Linux operative come build target
|
||||
- artifact layout su filesystem locale
|
||||
- act_runner / Gitea (binario Linux ufficiale)
|
||||
- workflow YAML invariati (path generalizzati via env vars introdotte in Fase A)
|
||||
- template VM Windows e Linux **identici** (stessi `.vmx`, `.vmdk`, snapshot)
|
||||
- artifact layout (cambia solo la radice: `F:\CI\` → `/var/lib/ci/`)
|
||||
- transport WinRM/SSH ai guest (pypsrp + paramiko già cross-platform da Fase A)
|
||||
|
||||
Cambia: hypervisor, transport layer di management, percorsi, eseguibili
|
||||
amministrativi, gestione credenziali.
|
||||
Cambia: OS host, service manager, credential store, percorsi, ownership filesystem.
|
||||
|
||||
---
|
||||
|
||||
## 2. Fattibilità
|
||||
## 2. Decisione hypervisor: perché Workstation Pro Linux
|
||||
|
||||
### 2.1 Bivio strategico: quale hypervisor su Linux?
|
||||
Confronto sintetico (decisione presa, non più aperta):
|
||||
|
||||
| Opzione | Pro | Contro | Verdetto |
|
||||
| ----------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |
|
||||
| **A. VMware Workstation Pro Linux** | Comando `vmrun` identico (binario `/usr/bin/vmrun`, **senza estensione `.exe`**); VMX dei template riutilizzabili 1:1 (zero conversione); guest tools invariati; dal 2024 free per uso personale e commerciale (Broadcom). | Roadmap di lungo periodo incerta dopo acquisizione Broadcom; dipendenza da binari proprietari su un OS open source. Richiede aggiornare i default hardcoded del wrapper `Resolve-VmrunPath` (`C:\Program Files (x86)\...\vmrun.exe` → `/usr/bin/vmrun`). | **Path a basso attrito** se si vuole minimizzare il rischio di porting (zero conversione template, zero rewrite di `_Common.psm1` oltre al path). |
|
||||
| **B. KVM + libvirt + QEMU** | Open source, supportato nativamente da Linux Mint; tooling solido (`virsh`, `virt-install`, `virt-clone`); cloud-init friendly. | Conversione VMDK→qcow2 obbligatoria (`qemu-img convert`); riconfigurare guest Windows (driver virtio); riscrivere completamente lo strato vmrun. | **Raccomandata** per il lungo periodo. |
|
||||
| C. VirtualBox | Cross-platform, simile a Workstation. | Performance inferiore a KVM; snapshot meno robusti; meno usato in CI. | Sconsigliata. |
|
||||
| D. Proxmox VE | UI di management, API REST. | È un OS dedicato, non gira "sopra" Linux Mint. | Vedi Idea 3 (è simile a ESXi). |
|
||||
|
||||
**Le sezioni seguenti assumono Opzione B (KVM)** salvo dove indicato — è
|
||||
lo scenario di porting più impegnativo e quindi quello da pianificare in
|
||||
dettaglio. Se si sceglie A (Workstation Pro su Linux), la maggior parte del
|
||||
tooling resta invariato: cambiano solo path (`F:\CI\` → `/var/lib/ci/`),
|
||||
service manager (Windows Service → systemd), credential store e shell
|
||||
(PS 5.1 → `pwsh` 7 o Python — vedi Idea 1).
|
||||
|
||||
### 2.2 Mappa di adattamento (assumendo Idea 1 già completata o in corso)
|
||||
|
||||
| Componente attuale | Equivalente Linux + KVM |
|
||||
| ------------------------------------------------------ | ------------------------------------------------------------------ |
|
||||
| `vmrun clone -snapshot=BaseClean ... linked` | `virsh snapshot-create` + `virt-clone --reflink` (qcow2 CoW) |
|
||||
| `vmrun start <vmx>` | `virsh start <domain>` |
|
||||
| `vmrun stop <vmx> hard` | `virsh destroy <domain>` |
|
||||
| `vmrun deleteVM` | `virsh undefine <domain> --remove-all-storage` |
|
||||
| File `.vmx` | Domain XML libvirt (definizione VM) |
|
||||
| `WinBuild2025.vmdk` | `winbuild2025.qcow2` (base) + overlay qcow2 per ogni clone |
|
||||
| `F:\CI\BuildVMs\` | `/var/lib/ci/build-vms/` (o pool storage libvirt dedicato) |
|
||||
| `F:\CI\Artifacts\` | `/var/lib/ci/artifacts/` |
|
||||
| `F:\CI\Templates\` | `/var/lib/ci/templates/` |
|
||||
| `F:\CI\keys\ci_linux` | `/etc/ci/keys/ci_linux` (perms `600`, owner `ci-runner`) |
|
||||
| Credential Manager `BuildVMGuest` | `secret-tool` (libsecret/GNOME Keyring) o file `.env` cifrato (`age`/`sops`) |
|
||||
| WinRM HTTPS verso guest Windows | Identico (lato guest non cambia nulla) |
|
||||
| SSH verso guest Linux | Identico |
|
||||
| act_runner come Windows Service | `systemd unit` `act-runner.service` |
|
||||
| Task Scheduler (`Register-CIScheduledTasks.ps1`) | `systemd timers` (`*.timer` + `*.service`) |
|
||||
| PowerShell 5.1 | PowerShell 7 (`pwsh`) o **Python** (vedi Idea 1) |
|
||||
| `vmnet8` (NAT 192.168.79.0/24) | `default` libvirt network (NAT 192.168.122.0/24) o bridge dedicato |
|
||||
|
||||
### 2.3 Conversione dei template VM (lavoro una-tantum)
|
||||
|
||||
#### Template Windows (WinBuild2025/2022)
|
||||
1. Spegnere VM su Workstation, esportare `.vmx` + `.vmdk`.
|
||||
2. Convertire: `qemu-img convert -O qcow2 WinBuild2025.vmdk winbuild2025.qcow2`.
|
||||
3. Iniettare driver **virtio** (network, storage) nel guest Windows **prima**
|
||||
della conversione (offline injection via `virt-v2v` oppure boot manuale
|
||||
con ISO `virtio-win.iso` e installazione driver).
|
||||
4. Importare con `virt-install --import --disk path=winbuild2025.qcow2,bus=virtio --os-variant=win2k25`.
|
||||
5. Validare WinRM accessibile da host Linux (`Test-WSMan` equivalente con
|
||||
`pwsh` o `pypsrp`).
|
||||
6. Spegnere, creare snapshot di base: `virsh snapshot-create-as winbuild2025-template BaseClean`.
|
||||
|
||||
#### Template Linux (LinuxBuild2404)
|
||||
1. Esportare e convertire VMDK → qcow2 (più semplice, driver virtio già presenti).
|
||||
2. Importare con `virt-install --import ...`.
|
||||
3. Già usa SSH + chiave: nessun cambio lato transport.
|
||||
4. Snapshot `BaseClean-Linux`.
|
||||
|
||||
#### Linked clone su KVM
|
||||
- `qemu-img create -f qcow2 -b /var/lib/ci/templates/winbuild2025.qcow2 -F qcow2 /var/lib/ci/build-vms/<jobid>.qcow2`
|
||||
- Crea overlay CoW (equivalente di linked clone Workstation).
|
||||
- Performance migliore se il filesystem è XFS con `reflink=1` o BTRFS.
|
||||
|
||||
### 2.4 Permessi e utenti
|
||||
|
||||
- Utente di servizio `ci-runner` (uid dedicato), membro di `libvirt` e `kvm`.
|
||||
- act_runner gira come `ci-runner` via systemd (`User=ci-runner`).
|
||||
- Sudoers limitati per operazioni privilegiate (es. `virsh net-start`).
|
||||
- Storage pool libvirt con ownership corretta (`/var/lib/ci`).
|
||||
|
||||
### 2.5 Networking
|
||||
|
||||
- Bridge `br0` se serve raggiungibilità diretta dei guest dal host Gitea
|
||||
remoto, oppure NAT (default libvirt) + DHCP statico via libvirt
|
||||
dnsmasq se Gitea sta sulla stessa LAN.
|
||||
- Replicare la convenzione "1 IP per job concorrente" già in uso.
|
||||
|
||||
### 2.6 Rischi specifici
|
||||
|
||||
| Rischio | Severità | Mitigazione |
|
||||
| --------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------- |
|
||||
| Guest Windows non parte dopo conversione (driver virtio mancanti) | Alta | Iniezione driver prima della conversione, oppure boot in safe mode + install. |
|
||||
| Performance I/O dei clone qcow2 inferiore a VMware linked clone in alcuni scenari | Media | Filesystem CoW (BTRFS / XFS reflink) + cache=none + aio=native su disco. |
|
||||
| `vmrun` workflow / convenzioni codificate in `AGENTS.md` errori #9 #10 non più applicabili | Bassa | Documentare nuovo set di "errori frequenti KVM" (es. virtio drivers, SELinux). |
|
||||
| Linux Mint kernel non sempre allineato al supporto VMware Workstation se si sceglie A | Media | Solo se si va opzione A: vincolarsi a kernel LTS testato. |
|
||||
| Burn-in concorrente saturazione I/O diversa su qcow2 vs VMDK | Media | Ripetere `Test-CapacityBurnIn` su nuovo host prima del go-live. |
|
||||
| WinRM HTTPS da host Linux verso guest Windows ha cipher mismatch occasionali | Bassa | Usare `pypsrp` con `auth='credssp'` o `auth='negotiate'`; testato in lab. |
|
||||
| Conversione VMDK + sysprep del Windows template può rompere attivazione / licenza | Media | Validare licenza Windows e tooling unattended con la nuova HW signature. |
|
||||
|
||||
### 2.7 Vincoli che spariscono / che restano
|
||||
|
||||
**Spariscono**:
|
||||
- Limitazioni PS 5.1 (`AGENTS.md` tabella PS7+) — su Linux si usa `pwsh` 7 o Python.
|
||||
- Necessità di Credential Manager con bug noti sotto SYSTEM account.
|
||||
- `autounattend.xml` solo per refresh del template Windows (operazione rara).
|
||||
- Errori #9 (snapshot con `.vmem`) e #10 (`getGuestIPAddress` bloccante)
|
||||
sono specifici di vmrun — non più applicabili.
|
||||
|
||||
**Restano / si trasformano**:
|
||||
- Necessità di gestire snapshot in stato "powered off".
|
||||
- IP collision per machine-id identico nei clone Linux (#11): identico
|
||||
comportamento sotto KVM con dnsmasq DHCP.
|
||||
- Errore #12 (stderr nativa + `$ErrorActionPreference='Stop'`) sparisce
|
||||
se si esce da PowerShell, altrimenti resta.
|
||||
| Opzione | Verdetto |
|
||||
| ---------------------------------- | -------------------------------------------------------------------- |
|
||||
| **VMware Workstation Pro Linux** | **SCELTA.** `vmrun` identico, VMX template riutilizzabili 1:1, free per uso personale e commerciale dal 2024. Plumbing-only port. |
|
||||
| KVM + libvirt | Rimandato. Richiederebbe conversione VMDK→qcow2, virtio injection nei guest Windows, rewrite backend hypervisor. Resta come piano B se Broadcom dismette Workstation. |
|
||||
| VirtualBox | Scartato. Performance/snapshot inferiori. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Stima volumi / sforzo (relativa)
|
||||
## 3. Mappa di adattamento
|
||||
|
||||
| Area | Indicatori dimensione | Complessità |
|
||||
| ------------------------------------------------------------------- | ------------------------------- | ----------- |
|
||||
| Conversione template Windows → qcow2 + virtio | 2 VM (2025, 2022) | Alta |
|
||||
| Conversione template Linux → qcow2 | 1 VM | Bassa |
|
||||
| Riscrittura `_Common.psm1` → wrapper `virsh`/`libvirt` | ~300 righe | Media |
|
||||
| Riscrittura `_Transport.psm1` (WinRM) | ~240 righe | Bassa |
|
||||
| Adattamento `New-BuildVM` / `Remove-BuildVM` / `Invoke-CIJob` | ~2.000 righe | Media |
|
||||
| Riscrittura `Setup-Host.ps1` → script bash + systemd | ~400 righe | Media |
|
||||
| `Register-CIScheduledTasks.ps1` → systemd `.timer` units | 6 task → 6 timer | Bassa |
|
||||
| Adattamento workflow YAML (path Windows → POSIX) | 5 file | Bassa |
|
||||
| Documentazione (HOST-SETUP.md, AGENTS.md, BEST-PRACTICES.md) | ~5 file md | Media |
|
||||
| Validazione e2e: smoke + section 3.3 + capacity burn-in | piano test esistente | Alta |
|
||||
|
||||
Sequenza obbligata:
|
||||
1. Decidere hypervisor (A vs B).
|
||||
2. Convertire template Linux (più semplice, fa da PoC).
|
||||
3. Convertire un template Windows e validare WinRM da Linux.
|
||||
4. Riscrittura tooling (idealmente in Python — vedi Idea 1).
|
||||
5. Migrazione storage `F:\CI\` → `/var/lib/ci/`.
|
||||
6. Switch act_runner.
|
||||
| Componente attuale | Equivalente Linux + Workstation Pro |
|
||||
| --------------------------------------------------- | ------------------------------------------------------------ |
|
||||
| `vmrun.exe` (`C:\Program Files (x86)\...`) | `/usr/bin/vmrun` (binario senza `.exe`, installato dal package Workstation) |
|
||||
| File `.vmx` / `.vmdk` | **identici, copiati così come sono** |
|
||||
| Snapshot `BaseClean` / `BaseClean-Linux` | preservati nella copia |
|
||||
| `F:\CI\BuildVMs\` | `/var/lib/ci/build-vms/` |
|
||||
| `F:\CI\Artifacts\` | `/var/lib/ci/artifacts/` |
|
||||
| `F:\CI\Templates\` | `/var/lib/ci/templates/` |
|
||||
| `F:\CI\keys\ci_linux` | `/etc/ci/keys/ci_linux` (perms `600`, owner `ci-runner`) |
|
||||
| Credential Manager (`BuildVMGuest`, `GiteaPAT`) | `secret-tool` / GNOME Keyring / KWallet (backend `keyring`) |
|
||||
| WinRM HTTPS verso guest Windows | identico (pypsrp parla con WinRM da Linux senza modifiche) |
|
||||
| SSH verso guest Linux | identico (paramiko) |
|
||||
| act_runner come Windows Service | systemd unit `act-runner.service` |
|
||||
| Task Scheduler (`Register-CIScheduledTasks.ps1`) | systemd `*.timer` + `*.service` units |
|
||||
| PowerShell 5.1 | `pwsh` 7 (solo per ricostruire template, raro) |
|
||||
| `vmnet8` NAT (`192.168.79.0/24`) | `vmnet8` NAT su Workstation Pro Linux (stesso concetto, IP range configurabile) |
|
||||
|
||||
---
|
||||
|
||||
## 4. Pro / Contro
|
||||
## 4. Strategia di migrazione
|
||||
|
||||
### Pro
|
||||
### Step B1 — Setup host Linux Mint in parallelo (no impatto produzione)
|
||||
|
||||
- Elimina i vincoli PS 5.1 (tabella in `AGENTS.md`).
|
||||
- Stack open source completo (no licenze VMware Workstation se si va KVM).
|
||||
- `systemd` + `journalctl` per service management e log centralizzati.
|
||||
- Filesystem CoW (BTRFS/XFS reflink) → clone disco ~istantanei, equivalente
|
||||
o migliore di linked clone Workstation.
|
||||
- Linux Mint ha buon supporto desktop per debug interattivo del runner.
|
||||
- Disaccoppia dal futuro incerto di VMware Workstation (Broadcom).
|
||||
- Installazione Linux Mint LTS + aggiornamenti
|
||||
- Installazione VMware Workstation Pro per Linux (bundle ufficiale Broadcom)
|
||||
- Verifica `vmrun` con una VM di test (clone, start, stop)
|
||||
- Configurazione `vmnet8` NAT range coerente o equivalente
|
||||
- Creazione utente di servizio `ci-runner` (uid dedicato)
|
||||
- Creazione storage layout `/var/lib/ci/{build-vms,artifacts,templates,keys}`
|
||||
con ownership `ci-runner:ci-runner` (mode 750)
|
||||
- Installazione Python 3.11+ + venv in `/opt/ci/venv/` con `pip install -e .`
|
||||
del package `ci_orchestrator` portato in Fase A
|
||||
|
||||
### Contro
|
||||
Validazione: `python -m ci_orchestrator --help` funziona da utente `ci-runner`.
|
||||
|
||||
- Migrazione "tutta o niente" del host: non si può avere CI/CD attivo su
|
||||
Win e su Linux in parallelo se il template/storage layout è condiviso.
|
||||
- Conversione template Windows è il passaggio più rischioso (virtio,
|
||||
attivazione, eventuale re-provisioning toolchain).
|
||||
- Curva di apprendimento libvirt / virsh / domain XML.
|
||||
- Tutta la documentazione e `AGENTS.md` da riscrivere.
|
||||
- Test plan completo da rieseguire (smoke, e2e, burn-in).
|
||||
### Step B2 — Trasferimento template VM
|
||||
|
||||
- Spegnere i template Workstation sull'host Windows (devono essere
|
||||
fully powered-off — vedi `AGENTS.md` errore #9)
|
||||
- Copia bit-by-bit di `F:\CI\Templates\` → `/var/lib/ci/templates/`
|
||||
(`scp -r` o `rsync`)
|
||||
- Aprire i `.vmx` su Workstation Linux per registrarli (`vmrun start ... nogui`
|
||||
con stop immediato, oppure `vmrun -T ws snapshot list`) — risolverà
|
||||
eventuali path interni se Workstation li chiede
|
||||
- Verifica snapshot presenti: `vmrun listSnapshots <vmx>` deve mostrare
|
||||
`BaseClean` / `BaseClean-Linux`
|
||||
- Smoke test manuale: clone linked + start + WinRM/SSH dal host Linux
|
||||
|
||||
### Step B3 — Trasferimento credenziali e chiavi
|
||||
|
||||
- Copia chiave SSH guest Linux: `F:\CI\keys\ci_linux*` → `/etc/ci/keys/`
|
||||
(mode 600, owner `ci-runner`)
|
||||
- Re-store delle credenziali guest Windows nel keyring Linux:
|
||||
`secret-tool store --label='BuildVMGuest' service ci target BuildVMGuest`
|
||||
- Re-store Gitea PAT: `secret-tool store --label='GiteaPAT' service ci target GiteaPAT`
|
||||
- Verifica via Python: `keyring.get_credential('BuildVMGuest', None)` non None
|
||||
|
||||
### Step B4 — Setup act_runner come systemd service
|
||||
|
||||
- Download act_runner Linux binary (versione allineata a v1.0.2 o successiva)
|
||||
- Registrazione runner verso Gitea con label `windows-build:host` e
|
||||
`linux-build:host` (stesse label di oggi → workflow non cambiano)
|
||||
- File unit `/etc/systemd/system/act-runner.service`:
|
||||
- `User=ci-runner`
|
||||
- `WorkingDirectory=/var/lib/ci/runner`
|
||||
- `Environment=PYTHONIOENCODING=utf-8`
|
||||
- env vars `CI_*` per il config Python
|
||||
- `systemctl enable --now act-runner.service`
|
||||
- Validazione: `journalctl -u act-runner -f` mostra connessione a Gitea OK
|
||||
|
||||
### Step B5 — Conversione scheduled tasks → systemd timers
|
||||
|
||||
`Register-CIScheduledTasks.ps1` definisce N task periodici. Per ognuno
|
||||
creare:
|
||||
- `/etc/systemd/system/ci-<task>.service` (oneshot, chiama
|
||||
`python -m ci_orchestrator <cmd>`)
|
||||
- `/etc/systemd/system/ci-<task>.timer` (`OnCalendar=...` equivalente)
|
||||
- `systemctl enable --now ci-<task>.timer`
|
||||
|
||||
Task tipici da convertire:
|
||||
- `cleanup-orphaned-vms` (ogni ora)
|
||||
- `retention-policy` (giornaliero)
|
||||
- `watch-disk-space` (ogni 15 min)
|
||||
- `watch-runner-health` (ogni 5 min)
|
||||
- `backup-template` (settimanale)
|
||||
|
||||
### Step B6 — Cutover
|
||||
|
||||
Finestra di manutenzione concordata:
|
||||
|
||||
1. Stop act_runner sull'host Windows (`Stop-Service actions-runner`)
|
||||
2. Rsync incrementale finale degli artifact recenti:
|
||||
`F:\CI\Artifacts\` → `/var/lib/ci/artifacts/`
|
||||
3. Verificare nessun job in coda lato Gitea
|
||||
4. Avviare act_runner Linux (`systemctl start act-runner`)
|
||||
5. Trigger manuale di un workflow di smoke test
|
||||
6. Trigger del workflow matrix `build-nsInnoUnp.yml` (Win + Linux build)
|
||||
|
||||
Se PASS → cutover confermato. Se FAIL → rollback (riavvio runner Windows,
|
||||
nessun dato è stato distrutto).
|
||||
|
||||
### Step B7 — Capacity burn-in sul nuovo host
|
||||
|
||||
Eseguire `Test-CapacityBurnIn` (versione Python) con N=4 concorrenti × R=10
|
||||
round, sia per template Windows che Linux. Confronto tempi vs baseline
|
||||
Windows host pre-migrazione (aspettativa: paragonabili, eventuali differenze
|
||||
vanno indagate ma non sono blocker se entro ±20%).
|
||||
|
||||
### Step B8 — Decommissioning host Windows (opzionale)
|
||||
|
||||
Dopo 1-2 settimane di stabilità sul nuovo host:
|
||||
- Backup finale di `F:\CI\` su archivio
|
||||
- Spegnimento runner Windows (lasciato installato per un mese come
|
||||
rollback option)
|
||||
- Riallocazione hardware
|
||||
|
||||
---
|
||||
|
||||
## 5. Sinergia con altre idee
|
||||
## 5. Adattamenti al codice Python (residui dopo Fase A)
|
||||
|
||||
- **Idea 1 (Python)**: forte prerequisito. PS 7 su Linux è possibile, ma
|
||||
Python è più idiomatico per `libvirt` (binding `libvirt-python`) e
|
||||
per riusare `paramiko` / `pypsrp` cross-platform.
|
||||
- **Idea 3 (ESXi)**: parzialmente alternativa. Se si va su ESXi, l'host CI
|
||||
resta comunque una macchina di management (può essere Linux Mint con
|
||||
Python + `pyVmomi`, oppure restare Windows). Il "dove gira l'host" e
|
||||
"dove girano le VM" si disaccoppiano.
|
||||
Se Fase A ha rispettato le regole di §2 di [idea-1-python-rewrite.md](idea-1-python-rewrite.md),
|
||||
il codice **non cambia**. Modifiche residue attese:
|
||||
|
||||
- `config.example.toml`: aggiungere sezione `[paths.linux]` con default
|
||||
`/var/lib/ci/...` (la sezione `[paths.windows]` resta per chi ricostruisce
|
||||
template da Win)
|
||||
- `runner/config.yaml`: path env vars aggiornati a POSIX
|
||||
- Eventuali test pytest che assumevano `Path('F:\\CI')` → usare fixture
|
||||
parametrizzata
|
||||
- Workflow `lint.yml`: rimuovere o relegare a job opzionale la parte PSSA
|
||||
(utile solo se si tocca ancora codice PS dei template)
|
||||
|
||||
---
|
||||
|
||||
## 6. Decision checklist
|
||||
## 6. Rischi specifici Fase B
|
||||
|
||||
Procedere **solo se** almeno 2 dei seguenti sono veri:
|
||||
|
||||
- [ ] Il vincolo Windows host è un problema concreto (licenze, policy aziendale, preferenza team).
|
||||
- [ ] Si è disposti a investire una settimana piena di test di conversione VMDK→qcow2.
|
||||
- [ ] Si è già completata (o si è disposti a fare in parallelo) l'Idea 1.
|
||||
- [ ] Si vuole rimuovere la dipendenza commerciale da VMware Workstation.
|
||||
| Rischio | Severità | Mitigazione |
|
||||
| -------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------- |
|
||||
| `vmrun` su Workstation Linux ha differenze sottili nel parsing output `list` / `getGuestIPAddress` | Media | Test del modulo `vmrun.py` su Linux durante Step B1; eventuali fix dietro feature flag. |
|
||||
| Performance I/O linked clone su filesystem ext4 vs NTFS | Bassa | Misurare con burn-in (Step B7). Eventualmente XFS o BTRFS con CoW per il dataset template. |
|
||||
| `vmnet8` NAT range configurato diversamente su Linux: IP collision con altri host LAN | Media | Allineare il range a quello attuale Windows (`192.168.79.0/24`) in `vmware-vmnet.conf`. |
|
||||
| WinRM HTTPS self-signed: pypsrp da Linux può negoziare TLS 1.2 con cipher non supportati dal Windows guest | Media | Già testato in Fase A (lo stesso codice gira anche da Windows host). Validare con smoke test prima del cutover. |
|
||||
| Permessi su `/var/lib/ci/build-vms/`: vmrun gira come `ci-runner` ma deve scrivere VMDK CoW | Media | ACL POSIX: `setfacl -d -m u:ci-runner:rwX` sulla directory. Test in Step B1. |
|
||||
| `secret-tool` headless: act_runner come systemd service non ha sessione D-Bus per accedere al keyring | Alta | Usare backend `keyring` file-based + `age`/`sops` (vault file) oppure `keyring.backends.SecretService` con D-Bus user@.service. Decidere in Step B3 con PoC. |
|
||||
| Errore #11 `AGENTS.md` (machine-id Linux clone) | Bassa | Identico comportamento, fix identico (reset machine-id pre-snapshot). Già documentato. |
|
||||
| Errore #12 `AGENTS.md` (stderr nativa + `'Stop'`) | N/A | Sparisce: il codice non è più PowerShell. |
|
||||
|
||||
---
|
||||
|
||||
## 7. Path alternativo "minimo": dual-host
|
||||
## 7. Path alternativo "dual-host" (se la Fase B si blocca)
|
||||
|
||||
Se non si vuole migrare ma solo "supportare anche Linux Mint", una via meno
|
||||
invasiva:
|
||||
- Mantenere host Windows come primario.
|
||||
- Aggiungere un **secondo runner** Linux Mint che usa solo `linux-build` jobs
|
||||
(KVM o Workstation Linux), label `linux-build:host-mint`.
|
||||
- Workflow esistenti restano invariati; nuovi workflow possono selezionare il
|
||||
runner secondario per test sperimentali.
|
||||
- Stima: ~1/3 dello sforzo della migrazione completa, ma codebase divisa
|
||||
in due (PS Win + qualche script Linux ad hoc) — debito tecnico crescente.
|
||||
Se durante la migrazione si scopre un blocker grave (es. WinRM da Linux
|
||||
non funzionante affidabilmente verso il template Windows specifico), si
|
||||
può ripiegare su:
|
||||
|
||||
Questa via ha senso solo come **PoC temporaneo** prima di una migrazione completa.
|
||||
- mantenere host Windows per i job `windows-build:host`
|
||||
- usare host Linux Mint solo per `linux-build:host` (label dedicata)
|
||||
- runner doppio registrato verso Gitea
|
||||
|
||||
Costo: codebase divisa in due ambienti, maggiore overhead operativo.
|
||||
**Soluzione di emergenza, non target finale.**
|
||||
|
||||
---
|
||||
|
||||
## 8. Definizione di "fatto" (Fase B)
|
||||
|
||||
- [ ] act_runner gira come systemd service `act-runner.service` su Linux Mint
|
||||
- [ ] Tutti i template VM (`WinBuild2025`, `WinBuild2022`, `LinuxBuild2404`)
|
||||
operativi dal nuovo host con snapshot integri
|
||||
- [ ] Workflow `build-nsInnoUnp.yml` (matrix Win+Linux) PASS dal nuovo host
|
||||
- [ ] Burn-in capacity 4 job concorrenti PASS, tempi entro ±20% del baseline
|
||||
- [ ] Storage migrato a `/var/lib/ci/`, host Windows in stand-by come rollback
|
||||
- [ ] Tutti i timer systemd attivi e schedulati
|
||||
- [ ] Credenziali guest accessibili da `ci-runner` headless
|
||||
- [ ] `README.md`, `AGENTS.md`, `docs/HOST-SETUP.md` aggiornati per host Linux
|
||||
- [ ] Almeno 1 settimana di esercizio in produzione senza incidenti
|
||||
|
||||
+136
-161
@@ -1,204 +1,179 @@
|
||||
# Idea 3 — Supporto ESXi per le Build VM
|
||||
# Fase C — Supporto ESXi per le Build VM (futuro)
|
||||
|
||||
> Stato: **proposta / valutazione**.
|
||||
> Modello: host CI (Windows o Linux) continua a fare orchestrazione ma le
|
||||
> build VM girano su un **server ESXi remoto** invece che in VMware
|
||||
> Workstation locale.
|
||||
> **Stato**: **piano differito**, da rivalutare dopo che Fase A
|
||||
> ([rewrite Python](idea-1-python-rewrite.md)) e Fase B
|
||||
> ([host Linux](idea-2-linux-host.md)) sono stabili in produzione.
|
||||
> Vedi [overview](ideas-overview.md).
|
||||
>
|
||||
> Questo documento esiste per:
|
||||
> 1. Garantire che il design di Fase A abbia gli hook corretti
|
||||
> (astrazione `VmBackend`).
|
||||
> 2. Avere un piano pronto se/quando si decide di scalare oltre i limiti
|
||||
> dell'host singolo Workstation Pro.
|
||||
|
||||
---
|
||||
|
||||
## 1. Obiettivo
|
||||
## 1. Quando attivare Fase C
|
||||
|
||||
Permettere a `Invoke-CIJob` di usare un host **ESXi** (o cluster vCenter)
|
||||
come backend di virtualizzazione, mantenendo:
|
||||
Fase C ha senso **solo se** si verifica almeno uno di:
|
||||
|
||||
- workflow YAML invariati (eventualmente con nuovo label `windows-build:esxi`)
|
||||
- transport WinRM/SSH verso i guest invariato
|
||||
- formato artifact e flusso `clone → start → wait → build → collect → destroy` identico
|
||||
- Saturazione della concorrenza locale (>4 job paralleli regolarmente)
|
||||
- Necessità di disaccoppiare hardware CI control plane (ufficio) dal hardware
|
||||
build (rack/datacenter)
|
||||
- Disponibilità di hardware ESXi + licenza vSphere Essentials o superiore
|
||||
- Richiesta di QoS / resource pool per i job CI vs altri carichi
|
||||
|
||||
Cambia: come si fa clone/start/stop/delete della VM, dove vivono i dischi,
|
||||
come si determina l'IP del guest, come si autenticano i comandi di management.
|
||||
Se nessuno è vero a 6-12 mesi dal completamento Fase B, **non aprire Fase C**:
|
||||
Workstation Pro su Linux copre il caso d'uso e l'overhead operativo di un
|
||||
secondo backend non è giustificato.
|
||||
|
||||
---
|
||||
|
||||
## 2. Fattibilità
|
||||
|
||||
### 2.1 API di management ESXi
|
||||
|
||||
| Opzione | Pro | Contro | Verdetto |
|
||||
| ---------------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------- |
|
||||
| **A. pyVmomi** (Python SDK ufficiale) | API completa (cloni, snapshot, power ops, GuestOps); supportata; va anche contro singolo host ESXi senza vCenter. | Solo Python. Curva di apprendimento (managed object reference, task tracking). | **Raccomandata se si va Python (Idea 1).** |
|
||||
| **B. PowerCLI** (`VMware.PowerCLI` PSGallery) | Idiomatica per PowerShell; comandi `New-VM`, `Get-Snapshot`, ecc. molto leggibili. | Modulo grosso (~2 GB), Windows-friendly ma installazione richiede `Install-Module`. Performance avvio cmdlet non sempre rapida. | Buona se si **resta in PowerShell**. |
|
||||
| C. `ovftool` + REST API ESXi (vSphere API REST) | Standard moderno; copre la maggior parte delle operazioni in vSphere 7+. | API REST single-host ha gap rispetto a SOAP/pyVmomi; complicata gestione task. | Sconsigliata come strato unico. |
|
||||
| D. `govc` (VMware/govmomi CLI) | Binario Go portabile, scriptabile da shell qualsiasi. | Meno espressivo per logiche complesse (loop, error handling); diventa un "second-class" tooling. | Buona per script ad-hoc, non per orchestratore. |
|
||||
|
||||
Le due strade realistiche sono **A (pyVmomi)** o **B (PowerCLI)**, in funzione
|
||||
dell'esito dell'Idea 1.
|
||||
|
||||
### 2.2 Mappa di adattamento
|
||||
|
||||
| Operazione | Workstation oggi | ESXi domani (pyVmomi) |
|
||||
| ----------------------------------------- | --------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| Linked clone | `vmrun clone ... linked -snapshot=BaseClean` | `vim.VirtualMachineCloneSpec` con `linkedClone=True` e snapshot ref |
|
||||
| Start VM | `vmrun start <vmx>` | `VirtualMachine.PowerOn()` (Task) |
|
||||
| Stop VM | `vmrun stop <vmx> hard` | `VirtualMachine.PowerOff()` (Task) |
|
||||
| Delete VM | `vmrun deleteVM <vmx>` | `VirtualMachine.Destroy_Task()` |
|
||||
| List snapshots | `vmrun listSnapshots` | `VirtualMachine.snapshot.rootSnapshotList` (tree) |
|
||||
| Get guest IP | `vmrun getGuestIPAddress` (errore #10) | `VirtualMachine.guest.ipAddress` (richiede VMware Tools attivi) |
|
||||
| Test running | `vmrun list` + path search | `VirtualMachine.runtime.powerState == 'poweredOn'` |
|
||||
| Trasferimento file pre-WinRM/SSH | non usato | GuestOperationsManager (FileManager + ProcessManager) — opzionale, oggi non serve |
|
||||
|
||||
Il transport `_Transport.psm1` (SSH) e le sessioni WinRM **non cambiano**:
|
||||
una volta che la VM è up con IP raggiungibile, l'host CI parla direttamente
|
||||
col guest come oggi.
|
||||
|
||||
### 2.3 Architettura proposta
|
||||
## 2. Architettura prevista
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌─────────────────────────────┐
|
||||
│ Gitea │ │ ESXi host(s) │
|
||||
│ act_runner │ │ ├─ Template Windows (snap BaseClean) │
|
||||
│ │ │ ├─ Template Linux (snap BaseClean-Linux) │
|
||||
└──────┬───────┘ │ └─ Datastore: ci-builds │
|
||||
│ └──────────▲──────────────────┘
|
||||
│ orchestrate │ vSphere API (443)
|
||||
▼ │
|
||||
┌──────────────────┐ │
|
||||
│ CI orchestrator │──────────────┘
|
||||
│ (host Win o Lin)│
|
||||
│ act_runner │ │ ├─ Template Windows (snap BaseClean) │
|
||||
│ (Linux Mint) │ │ ├─ Template Linux (snap BaseClean-Linux) │
|
||||
│ │ │ └─ Datastore: ci-builds │
|
||||
└──────┬───────┘ └──────────▲──────────────────┘
|
||||
│ │ vSphere API (443)
|
||||
▼ │
|
||||
┌──────────────────┐ │
|
||||
│ ci_orchestrator │───────────┘
|
||||
│ (Python, Linux) │
|
||||
│ ↳ EsxiBackend │
|
||||
└─────┬────────────┘
|
||||
│ WinRM/SSH (network L3 ammessa)
|
||||
│ WinRM/SSH
|
||||
▼
|
||||
Build VM efimera su ESXi
|
||||
```
|
||||
|
||||
L'host CI continua a esistere come "control plane". Può essere lo stesso
|
||||
hardware di oggi (Windows 11) oppure migrato a Linux (Idea 2). Le build VM
|
||||
non girano più localmente.
|
||||
|
||||
### 2.4 Nuove configurazioni richieste
|
||||
|
||||
- **ESXi host** raggiungibile da host CI sulla 443/TCP.
|
||||
- Account di servizio ESXi (`ci-runner@vsphere.local` o `local user` con
|
||||
ruolo custom): permessi minimi su datastore, folder, snapshot, power ops.
|
||||
- Datastore dedicato (SSD/NVMe) per cloni e template; quota / monitoring.
|
||||
- Resource pool dedicato `ci-builds` con CPU/memory limits.
|
||||
- Portgroup di rete con DHCP raggiungibile (o assegnazione IP statica via
|
||||
cloud-init / Sysprep).
|
||||
- Snapshot dei template **identico** al setup attuale (`BaseClean` /
|
||||
`BaseClean-Linux`) — convertire VMX→VMX-su-ESXi via OVF (`ovftool`).
|
||||
|
||||
### 2.5 Rischi specifici
|
||||
|
||||
| Rischio | Severità | Mitigazione |
|
||||
| --------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------- |
|
||||
| Linked clone su ESXi standalone (senza vCenter) ha limitazioni rispetto a Workstation | Alta | Validare in PoC: alcune versioni ESXi free non supportano `CloneVM_Task` via API senza license. |
|
||||
| Licenza ESXi: la versione free **non espone le API write** (solo read-only) — bug noto vSphere | **Critica** | Serve almeno **vSphere Essentials** o licenza valutativa. Verificare PRIMA di pianificare. |
|
||||
| Throughput linked clone su NFS/iSCSI datastore inferiore a SSD locale Workstation | Media | Datastore VMFS su NVMe locale o vSAN; benchmark con `Test-CapacityBurnIn`. |
|
||||
| Tempo medio "clone → IP raggiungibile" più alto rispetto a Workstation locale | Media | Misurare con `Measure-CIBenchmark`; ottimizzare warm pool template (`Backup-CITemplate` non più utile, sostituire con template VM in folder dedicata). |
|
||||
| Gestione concorrente cloni: race su nome VM o snapshot revert | Media | Lock per `JobId` lato orchestrator; oppure naming con suffisso UUID. |
|
||||
| Determinazione IP: VMware Tools deve essere attivo e DHCP funzionante | Bassa | Già un prerequisito attuale (`Wait-VMReady`). Documentare in `AGENTS.md` nuova versione. |
|
||||
| Errore #11 (machine-id identico sui clone Linux) si ripropone identico su ESXi | Bassa | Stessa fix: reset machine-id pre-snapshot. Documentato. |
|
||||
| Sicurezza: credenziali vCenter/ESXi nello stesso store delle credenziali guest | Media | Target Credential Manager separato (`ESXiServiceAccount`) o keyring + ACL. |
|
||||
| Cleanup VM orfane più costoso (richiede chiamata API per ogni VM in folder) | Bassa | Riscrivere `Cleanup-OrphanedBuildVMs` per usare folder-scoped query. |
|
||||
|
||||
### 2.6 Sequenza di implementazione consigliata
|
||||
|
||||
1. **PoC manuale**: clonare manualmente un template su ESXi via UI/PowerCLI,
|
||||
avviare, fare WinRM da host CI. Misurare tempi.
|
||||
2. **PoC scriptato**: una versione minimale di `New-BuildVM-ESXi.ps1` (o
|
||||
`new_build_vm_esxi.py`) che fa clone+start. Validare race conditions.
|
||||
3. **Astrazione `IVmBackend`**: introdurre nei moduli `_Common` /
|
||||
`vmrun.py` un'interfaccia a 2 implementazioni (`Workstation`, `ESXi`),
|
||||
selezionata via env var (`GITEA_CI_VM_BACKEND=workstation|esxi`).
|
||||
In questo modo si può fare canary deploy job-per-job.
|
||||
4. Migrazione template (export OVF da Workstation, import su ESXi datastore).
|
||||
5. Workflow YAML aggiornati con label `windows-build:esxi` accanto a
|
||||
`windows-build:host`.
|
||||
6. Capacity burn-in concorrente sull'host ESXi.
|
||||
L'host CI Linux (Fase B) continua a esistere come control plane.
|
||||
Le build VM girano su ESXi remoto. Il transport WinRM/SSH al guest non
|
||||
cambia.
|
||||
|
||||
---
|
||||
|
||||
## 3. Stima volumi / sforzo (relativa)
|
||||
## 3. Implementazione: nuovo backend pyVmomi
|
||||
|
||||
| Area | Indicatori dimensione | Complessità |
|
||||
| -------------------------------------------------------------------------- | ---------------------------------------- | ----------- |
|
||||
| Setup hardware/licenze ESXi | 1 host fisico + licenza Essentials | Variabile (acquisto) |
|
||||
| PoC clone/start/stop via pyVmomi o PowerCLI | ~200 righe | Media |
|
||||
| Astrazione backend (`IVmBackend` con 2 impl) | refactor di `_Common`, `New-BuildVM`, `Remove-BuildVM`, `Wait-VMReady` | Media |
|
||||
| Export OVF dei template + import ESXi + ri-validazione snapshot | 3 template | Media |
|
||||
| Riconfigurazione network/DHCP per build VM su ESXi | 1 portgroup + DHCP scope | Media |
|
||||
| Adattamento `Cleanup-OrphanedBuildVMs`, `Watch-DiskSpace` (datastore-aware) | ~300 righe | Bassa |
|
||||
| Documentazione (`HOST-SETUP.md` nuovo capitolo ESXi, `OPTIMIZATION.md`) | 2-3 file md | Bassa |
|
||||
| Validazione e2e + benchmark vs Workstation | piano test esistente esteso | Alta |
|
||||
Sfruttando l'astrazione `VmBackend` introdotta in Fase A:
|
||||
|
||||
```python
|
||||
# src/ci_orchestrator/backends/esxi.py
|
||||
class EsxiBackend(VmBackend):
|
||||
def __init__(self, host: str, user: str, password: str, datastore: str, folder: str): ...
|
||||
def clone_linked(self, template: str, snapshot: str, name: str) -> VmHandle: ...
|
||||
def start(self, vm: VmHandle) -> None: ...
|
||||
def stop(self, vm: VmHandle, hard: bool = True) -> None: ...
|
||||
def delete(self, vm: VmHandle) -> None: ...
|
||||
def get_ip(self, vm: VmHandle, timeout: float) -> str: ...
|
||||
```
|
||||
|
||||
Selezione backend via env / config:
|
||||
|
||||
```toml
|
||||
[backend]
|
||||
type = "esxi" # oppure "workstation"
|
||||
|
||||
[backend.esxi]
|
||||
host = "esxi.lab.local"
|
||||
user = "ci-runner@vsphere.local"
|
||||
datastore = "ci-builds"
|
||||
folder = "ci"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Pro / Contro
|
||||
## 4. Mappa operazioni Workstation → ESXi
|
||||
|
||||
### Pro
|
||||
|
||||
- **Scalabilità**: un ESXi con CPU/RAM adeguate ospita facilmente 8-16 job concorrenti
|
||||
vs ~4 con Workstation locale (limite testato in `Test-CapacityBurnIn`).
|
||||
- Disaccoppia host CI (control plane) da hardware di build (data plane).
|
||||
- Resource pool / shares / reservation: QoS per i job CI.
|
||||
- Backup template / DR via snapshot ESXi nativi o appliance Veeam.
|
||||
- Permette di avere host CI Linux (Mint) senza dover usare KVM (Idea 2 + Idea 3 sinergiche).
|
||||
- Multi-runner futuro più semplice (più host CI puntano allo stesso ESXi).
|
||||
|
||||
### Contro
|
||||
|
||||
- **Costo licenza** vSphere (la free non basta per le API write).
|
||||
- Hardware aggiuntivo dedicato (o ri-uso di un server esistente).
|
||||
- Latenza di rete in più rispetto a in-process locale.
|
||||
- Throughput clone dipende da datastore; può essere peggio se non si ha SSD locale ESXi.
|
||||
- Backup VMDK template (`Backup-CITemplate.ps1`) va riprogettato (usare
|
||||
snapshot ESXi nativi o export OVF schedulato).
|
||||
- Disaster scenario: se ESXi è giù, **tutte** le pipeline si fermano. Con
|
||||
Workstation locale il guasto era confinato all'host CI.
|
||||
| Operazione | Workstation (oggi) | ESXi (Fase C, pyVmomi) |
|
||||
| ----------------------------- | --------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| Linked clone | `vmrun clone ... linked -snapshot=BaseClean` | `vim.VirtualMachineCloneSpec(linkedClone=True, snapshot=ref)` |
|
||||
| Start VM | `vmrun start <vmx>` | `VirtualMachine.PowerOn()` (Task) |
|
||||
| Stop VM | `vmrun stop <vmx> hard` | `VirtualMachine.PowerOff()` (Task) |
|
||||
| Delete VM | `vmrun deleteVM <vmx>` | `VirtualMachine.Destroy_Task()` |
|
||||
| List snapshots | `vmrun listSnapshots` | `VirtualMachine.snapshot.rootSnapshotList` |
|
||||
| Get guest IP | `vmrun getGuestIPAddress` | `VirtualMachine.guest.ipAddress` (richiede VMware Tools nei guest) |
|
||||
| Test running | `vmrun list` | `VirtualMachine.runtime.powerState == 'poweredOn'` |
|
||||
|
||||
---
|
||||
|
||||
## 5. Sinergia con altre idee
|
||||
## 5. Setup ESXi richiesto (Step C0)
|
||||
|
||||
- **Idea 1 (Python)**: forte sinergia. `pyVmomi` è Python-only; restando in
|
||||
PowerShell si è costretti a `VMware.PowerCLI`, che ha overhead di
|
||||
caricamento e dimensioni notevoli.
|
||||
- **Idea 2 (host Linux)**: complementare. Idea 3 sposta il "dove girano le VM"
|
||||
ma non vincola il "dove gira l'orchestrator". Si può:
|
||||
- **Win host + ESXi backend**: minima migrazione lato CI script (resta PS).
|
||||
- **Linux host + ESXi backend**: massima modernizzazione, ma somma i rischi
|
||||
delle due migrazioni. Solo dopo Idee 1+2 stabilizzate.
|
||||
- Con Idea 3 implementata, l'utilità di Idea 2 (host Linux con KVM locale)
|
||||
cala: la VM locale serve solo per debug, non più per CI in production.
|
||||
Prerequisiti hardware/licenza da verificare PRIMA di pianificare:
|
||||
|
||||
- **Licenza**: ESXi free **non basta** (le API write sono bloccate dal 2020).
|
||||
Serve almeno **vSphere Essentials** o licenza valutativa rinnovabile.
|
||||
- **Hardware**: server con CPU che supporti la versione ESXi target,
|
||||
RAM ≥64 GB, NVMe locale per datastore.
|
||||
- **Network**: portgroup dedicato per le build VM, DHCP raggiungibile.
|
||||
- **Account**: user dedicato con ruolo custom (datastore, folder, snapshot,
|
||||
power, network — minimo set richiesto da `clone_linked`/`destroy`).
|
||||
|
||||
---
|
||||
|
||||
## 6. Decision checklist
|
||||
## 6. Sequenza implementativa
|
||||
|
||||
Procedere **solo se** almeno 3 dei seguenti sono veri:
|
||||
### C1 — Validazione ambiente
|
||||
- Setup hardware ESXi + licenza
|
||||
- Account vSphere dedicato
|
||||
- Test manuale clone+start+stop via UI vSphere
|
||||
- Misurazione tempi di clone (baseline)
|
||||
|
||||
- [ ] È disponibile (o pianificato) hardware ESXi + licenza Essentials.
|
||||
- [ ] Si prevede di superare i 4 job concorrenti regolarmente.
|
||||
- [ ] Si vuole disaccoppiare hardware CI da hardware build (es. CI in ufficio,
|
||||
build in rack).
|
||||
- [ ] Si è pianificata l'Idea 1 (Python) o si è disposti a usare PowerCLI.
|
||||
- [ ] Disaster recovery / backup datastore è un requisito esplicito.
|
||||
### C2 — Implementazione `EsxiBackend`
|
||||
- `pip install pyvmomi` aggiunto al `pyproject.toml`
|
||||
- `src/ci_orchestrator/backends/esxi.py` implementato
|
||||
- Test pytest con `pyvmomi` mock + un test di integrazione
|
||||
opzionale che gira solo se env `CI_TEST_ESXI_HOST` è settata
|
||||
|
||||
Se la concorrenza richiesta è ≤4 job e l'hardware attuale (i9 + 64GB) regge,
|
||||
**l'investimento ESXi non si ripaga** rispetto a Workstation locale.
|
||||
### C3 — Migrazione template
|
||||
- Export OVF dei template Workstation: `ovftool ... template.vmx template.ovf`
|
||||
- Import su datastore ESXi: `ovftool template.ovf vi://...`
|
||||
- Verifica WinRM/SSH raggiungibili dal control plane verso le VM ESXi
|
||||
- Snapshot `BaseClean` / `BaseClean-Linux` ricreati lato ESXi
|
||||
|
||||
### C4 — Canary deploy
|
||||
- Workflow di test dedicato con label `windows-build:esxi` o
|
||||
config override `backend=esxi`
|
||||
- Eseguito quotidianamente per ≥1 settimana
|
||||
- Confronto metriche (tempo totale, success rate) vs Workstation backend
|
||||
|
||||
### C5 — Rollout selettivo
|
||||
- Workflow di produzione spostati uno a uno su backend ESXi
|
||||
- Monitoring tempi via `Measure-CIBenchmark` equivalente Python
|
||||
- Workstation backend resta disponibile come fallback (basta cambiare config)
|
||||
|
||||
### C6 — Adattamenti tooling secondari
|
||||
- `Cleanup-OrphanedBuildVMs` esteso per query ESXi folder-scoped
|
||||
- `Watch-DiskSpace` esteso a query datastore via API
|
||||
- `Backup-CITemplate` sostituito da snapshot ESXi nativi o `ovftool` schedulato
|
||||
|
||||
---
|
||||
|
||||
## 7. Path incrementale "minimo": ESXi opzionale
|
||||
## 7. Rischi specifici Fase C
|
||||
|
||||
Anziché migrazione totale, supportare **entrambi** i backend in parallelo:
|
||||
| Rischio | Severità | Mitigazione |
|
||||
| -------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------- |
|
||||
| Licenza ESXi free blocca API write (`CloneVM_Task` non autorizzata) | **Critica** | Verificare licenza in C1 prima di qualsiasi sviluppo. Procurare vSphere Essentials. |
|
||||
| Linked clone su ESXi standalone (no vCenter) ha gap funzionali | Alta | PoC manuale in C1; se gap rilevante, valutare full clone vs linked. |
|
||||
| Tempo clone+poweron+IP > Workstation locale per latenza rete + overhead vSphere | Media | Misurare in C1; possibile mitigazione: pool di VM "warm" pre-clonate. |
|
||||
| VMware Tools nel guest non sempre rapide a riportare IP via API | Bassa | Stesso problema oggi (`AGENTS.md` errore #10). Fallback: parse DHCP lease su ESXi via API. |
|
||||
| Concorrenza: race condition su naming VM o folder | Media | Lock per JobId lato orchestrator; suffisso UUID nel nome VM. |
|
||||
| Cleanup VM orfane più costoso (API call per ogni VM in folder) | Bassa | Riscrivere cleanup come query folder-scoped, non scan globale. |
|
||||
| Single point of failure: ESXi giù = tutta la CI giù | Media | Documentare procedura fallback su backend Workstation locale (basta cambio config). |
|
||||
| Errore #11 `AGENTS.md` (machine-id identico clone Linux) si ripropone | Bassa | Stessa fix lato template, già documentata. |
|
||||
|
||||
- Lasciare default `windows-build:host` (Workstation locale, situazione attuale).
|
||||
- Aggiungere label `windows-build:esxi` per job specifici (es. burn-in
|
||||
pesanti, regression suite notturna).
|
||||
- Naming dei job e folder ESXi prefissati con `ci-` per filtering.
|
||||
- Eventuale rollback semplice (basta disabilitare il label).
|
||||
---
|
||||
|
||||
Sforzo ~50% rispetto a migrazione totale, debito tecnico controllato perché
|
||||
l'astrazione `IVmBackend` è già lì per design.
|
||||
## 8. Definizione di "fatto" (Fase C)
|
||||
|
||||
- [ ] Hardware ESXi + licenza Essentials operativi
|
||||
- [ ] `EsxiBackend` implementato, testato (unit + integration test)
|
||||
- [ ] Template Windows e Linux importati e funzionanti su ESXi con snapshot
|
||||
- [ ] Almeno 1 workflow di produzione in canary su backend ESXi per ≥1 settimana
|
||||
senza regressioni
|
||||
- [ ] Documentazione (`docs/HOST-SETUP.md`, `docs/ARCHITECTURE.md`,
|
||||
`AGENTS.md`) aggiornata con istruzioni multi-backend
|
||||
- [ ] Procedura di rollback su backend Workstation testata e documentata
|
||||
|
||||
+63
-34
@@ -1,50 +1,79 @@
|
||||
# Overview delle tre idee evolutive
|
||||
# Roadmap evolutiva — overview
|
||||
|
||||
Documento di sintesi che mette in relazione i tre piani esplorativi:
|
||||
Decisione presa: implementare **prima Idea 1 (Python) + Idea 2 (host Linux)**
|
||||
in sequenza, e **solo dopo** valutare Idea 3 (ESXi).
|
||||
|
||||
- [idea-1-python-rewrite.md](idea-1-python-rewrite.md) — Conversione del sistema in Python
|
||||
- [idea-2-linux-host.md](idea-2-linux-host.md) — Host CI su Linux Mint
|
||||
- [idea-3-esxi-support.md](idea-3-esxi-support.md) — Build VM su ESXi remoto
|
||||
| Fase | Cosa | File di dettaglio |
|
||||
| ---- | ------------------------------------------------------------ | ---------------------------------------------------------- |
|
||||
| A | Rewrite in Python sull'host Windows attuale (codebase cross-platform-ready) | [idea-1-python-rewrite.md](idea-1-python-rewrite.md) |
|
||||
| B | Migrazione host: Windows 11 + Workstation Pro → Linux Mint + Workstation Pro Linux | [idea-2-linux-host.md](idea-2-linux-host.md) |
|
||||
| C | (Futuro, condizionato a A+B stabili) Aggiunta backend ESXi | [idea-3-esxi-support.md](idea-3-esxi-support.md) |
|
||||
|
||||
---
|
||||
|
||||
## Matrice di sinergia
|
||||
## 1. Razionale dell'ordine
|
||||
|
||||
| Combinazione | Sinergia | Note |
|
||||
| --------------------------------------- | -------- | -------------------------------------------------------------------------------- |
|
||||
| Solo Idea 1 (Python) | Neutra | Refactor "interno", nessun beneficio funzionale immediato. Utile se prerequisito a 2/3. |
|
||||
| Solo Idea 2 (Linux host + KVM) | Alta | Cambio piattaforma; PS 7 possibile ma Python (Idea 1) consigliato. |
|
||||
| Solo Idea 3 (ESXi backend) | Alta | Compatibile con stato attuale; PowerCLI o pyVmomi. |
|
||||
| Idea 1 + Idea 2 | **Molto alta** | La rewrite Python rimuove l'attrito cross-platform. |
|
||||
| Idea 1 + Idea 3 | **Molto alta** | `pyVmomi` è Python-only. |
|
||||
| Idea 2 + Idea 3 | Media | Host Linux + build ESXi: massimo decoupling, ma rischi sommati. |
|
||||
| Idee 1 + 2 + 3 | Alta | Massima modernizzazione. Da scaglionare in 3 milestone separate. |
|
||||
1. **Idea 1 prima di Idea 2**: scrivere Python *sapendo già* che girerà anche
|
||||
su Linux evita un doppio porting. Tutto il codice nuovo nasce
|
||||
cross-platform (no path hardcoded `F:\`, no `Get-StoredCredential`,
|
||||
no `New-PSSession` come unica via). Idea 2 diventa un'operazione di
|
||||
"swap della base OS" con codice già pronto.
|
||||
2. **Workstation Pro su entrambi gli OS**: scegliendo Workstation Pro anche
|
||||
su Linux (binario `/usr/bin/vmrun`, senza `.exe`) si mantiene **lo stesso
|
||||
comando `vmrun` e gli stessi VMX template** — zero conversione VMDK→qcow2,
|
||||
zero rewrite del backend hypervisor. Idea 2 diventa un porting di
|
||||
plumbing (path, service manager, credential store) e non di logica.
|
||||
3. **ESXi dopo**: una volta stabilizzato Python + Linux, l'introduzione di
|
||||
ESXi è un nuovo backend dietro un'astrazione `VmBackend` (introdotta
|
||||
già in fase A come hook di design); il core non cambia.
|
||||
|
||||
---
|
||||
|
||||
## Ordine consigliato (se si vogliono perseguire tutte)
|
||||
## 2. Sinergie sfruttate
|
||||
|
||||
1. **Idea 1 prima fase** (estrarre `_Common` + `_Transport` in Python con
|
||||
strangler fig) — riduce rischio futuro senza rompere niente.
|
||||
2. **Idea 3 con backend astratto** (sui job sperimentali, label dedicata) —
|
||||
sblocca scalabilità.
|
||||
3. **Idea 1 fasi 2-4** (completamento rewrite Python).
|
||||
4. **Idea 2** (migrazione host Linux) — solo dopo 1 e 3 stabili.
|
||||
|
||||
Andare nell'ordine inverso (prima Linux, poi ESXi, infine Python) raddoppia
|
||||
il debito tecnico nei periodi intermedi.
|
||||
- Python (Idea 1) abilita `pypsrp` (WinRM cross-platform), `paramiko` (SSH
|
||||
cross-platform), `keyring` (Credential Manager su Win, Secret Service su
|
||||
Linux): un unico tooling che funziona su entrambi gli host.
|
||||
- Tenere VMware Workstation Pro su Linux (Idea 2) elimina la necessità di
|
||||
imparare libvirt/KVM ora; resta come opzione futura se Broadcom dismette
|
||||
Workstation Pro.
|
||||
- L'astrazione `VmBackend` introdotta come "design hook" già in Idea 1
|
||||
riduce a costo marginale l'aggiunta del backend ESXi (Idea 3) tramite
|
||||
`pyVmomi`.
|
||||
|
||||
---
|
||||
|
||||
## Quando NON fare nulla
|
||||
## 3. Riepilogo prerequisiti per fase
|
||||
|
||||
Il sistema attuale è documentato come `production-ready`. Astenersi se:
|
||||
| Fase | Prerequisiti host | Prerequisiti competenze |
|
||||
| ---- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
|
||||
| A | Python 3.11+ su Windows; ambiente venv in `F:\CI\python\venv\` | Python, pytest, pypsrp/paramiko |
|
||||
| B | Hardware Linux Mint operativo; VMware Workstation Pro Linux installata; storage analogo a `F:\CI\` | systemd, bash, secret-tool, gestione utente di servizio |
|
||||
| C | Hardware ESXi + licenza Essentials o superiore; account vSphere dedicato | pyVmomi, datastore/portgroup management |
|
||||
|
||||
- La concorrenza richiesta è ≤4 job e l'hardware regge.
|
||||
- Non c'è pressione team su preferenze cross-platform.
|
||||
- Non si pianifica espansione hardware nei prossimi 6 mesi.
|
||||
- Il budget tempo è meglio speso in nuovi workflow CI per altri repo.
|
||||
---
|
||||
|
||||
In quel caso, l'investimento migliore è **estendere i test** (`tests/` Pester
|
||||
attuali), aggiungere osservabilità (`Get-CIJobSummary`, metriche) e
|
||||
documentare meglio l'esistente — non riscrivere.
|
||||
## 4. Criteri di "fase completata"
|
||||
|
||||
- **A done**: `Invoke-CIJob.ps1` retired; tutti i job CI passano via
|
||||
`python -m ci_orchestrator job ...`; test pytest verdi; capacity burn-in
|
||||
4 job concorrenti PASS sull'host Windows attuale.
|
||||
- **B done**: act_runner gira come systemd service su Linux Mint; tutti i
|
||||
workflow esistenti (Windows + Linux build) PASS dal nuovo host; storage
|
||||
migrato a `/var/lib/ci/`; capacity burn-in PASS.
|
||||
- **C done** (se attivata): label `windows-build:esxi` operativa con backend
|
||||
pyVmomi; canary su almeno 1 workflow di produzione per ≥1 settimana
|
||||
senza regressioni.
|
||||
|
||||
---
|
||||
|
||||
## 5. Cosa fare se A o B falliscono / si bloccano
|
||||
|
||||
- **Stop in A**: il sistema PowerShell attuale resta in produzione (rollback
|
||||
banale, niente è stato distrutto). La parte Python già scritta resta come
|
||||
modulo "side-by-side" non utilizzato.
|
||||
- **Stop in B**: l'host Windows resta primario; eventuale runner Linux
|
||||
secondario può comunque essere utile per workload sperimentali (path
|
||||
"dual-host" descritto in [idea-2-linux-host.md](idea-2-linux-host.md) §7).
|
||||
- **Stop pianificato in C**: nessun impatto — C è opzionale per definizione,
|
||||
Workstation Pro su Linux copre già il caso d'uso primario.
|
||||
|
||||
@@ -0,0 +1,953 @@
|
||||
# Piano implementativo unificato — Fase A (Python) + Fase B (host Linux)
|
||||
|
||||
## Summary esecutivo
|
||||
|
||||
Obiettivo combinato: riscrivere l'orchestratore CI/CD in Python 3.11+
|
||||
cross-platform (Fase A) e poi spostare l'host da Windows 11 a Linux Mint
|
||||
mantenendo VMware Workstation Pro come hypervisor (Fase B). Il risultato
|
||||
è un unico stack Python in produzione su Linux, con i template VM, i
|
||||
workflow Gitea Actions e act_runner invariati nella sostanza.
|
||||
|
||||
| Fase | Obiettivo | Output verificabile | Stato |
|
||||
| ---- | --------- | ------------------- | ----- |
|
||||
| A | Rewrite Python dell'orchestratore (host Windows attuale) | `python -m ci_orchestrator job ...` sostituisce `Invoke-CIJob.ps1`; `pytest` ≥70% coverage; workflow `build-nsInnoUnp.yml` PASS | da iniziare |
|
||||
| B | Migrazione host a Linux Mint + Workstation Pro Linux | act_runner come `act-runner.service` systemd; storage `/var/lib/ci/`; burn-in 4 job concorrenti PASS | da iniziare (gating: A done) |
|
||||
| C | (Hook only, non implementata) backend ESXi via `pyVmomi` | Protocol `VmBackend` rispettato; `[backend]` selector in `config.toml` | non in scope |
|
||||
|
||||
Decisioni architetturali già prese e non rinegoziabili:
|
||||
|
||||
- Linguaggio: Python 3.11+, packaging `pyproject.toml`, CLI via `click`
|
||||
- Transport WinRM: `pypsrp` (no dipendenza da `New-PSSession`)
|
||||
- Transport SSH: `paramiko` (no dipendenza da `ssh.exe` / `scp.exe` esterni)
|
||||
- Credential store: `keyring` (Credential Manager su Win, Secret Service su Linux)
|
||||
- Hypervisor host Linux: VMware Workstation Pro Linux (binario `/usr/bin/vmrun`)
|
||||
- Astrazione hypervisor: Protocol `VmBackend` con prima implementazione `WorkstationVmrunBackend`
|
||||
- Lint dual-stack: `ruff` + `mypy --strict` per Python, PSScriptAnalyzer per i PS legacy residui
|
||||
|
||||
Vincoli che NON cambiano: act_runner v1.0.2+ come consumer agnostico di
|
||||
shell, Gitea `http://10.10.20.11:3100` / `https://gitea.emulab.it`,
|
||||
template VM (`WinBuild2025`, `WinBuild2022`, `LinuxBuild2404`) e relativi
|
||||
snapshot `BaseClean` / `BaseClean-Linux`, workflow YAML in
|
||||
`gitea/workflows/` (cambia solo la `shell:` e il comando invocato in Fase
|
||||
A4; cambiano solo path/env vars in Fase B).
|
||||
|
||||
## Checklist riassuntiva master
|
||||
|
||||
- [ ] [A1] Creare `pyproject.toml` + package `src/ci_orchestrator/` + venv `F:\CI\python\venv\`
|
||||
- [ ] [A1] Implementare `config.py` (env vars + `config.toml`) con default Windows e hook path Linux
|
||||
- [ ] [A1] Implementare `backends/protocol.py` con Protocol `VmBackend` (firma neutra, no `vmrun_*` nei nomi pubblici)
|
||||
- [ ] [A1] Implementare `backends/workstation.py` (`WorkstationVmrunBackend`) usando `subprocess` + `shutil.which('vmrun')`
|
||||
- [ ] [A1] Implementare `transport/winrm.py` con wrapper `pypsrp.client.Client`
|
||||
- [ ] [A1] Implementare `transport/ssh.py` con wrapper `paramiko.SSHClient` / `SFTPClient`
|
||||
- [ ] [A1] Implementare `credentials.py` con Protocol `CredentialStore` + `KeyringCredentialStore`
|
||||
- [ ] [A1] PoC `wait-ready` end-to-end via `pypsrp` contro un guest Windows reale
|
||||
- [ ] [A1] Test pytest unitari per `vmrun.py`, `winrm.py`, `ssh.py`, `credentials.py` con mock
|
||||
- [ ] [A1] Aggiungere job pytest a `gitea/workflows/lint.yml`
|
||||
- [ ] [A2] Portare `Wait-VMReady.ps1` → `python -m ci_orchestrator wait-ready`
|
||||
- [ ] [A2] Portare `Remove-BuildVM.ps1` → `vm remove`
|
||||
- [ ] [A2] Portare `Cleanup-OrphanedBuildVMs.ps1` → `vm cleanup`
|
||||
- [ ] [A2] Portare `Watch-DiskSpace.ps1` → `monitor disk`
|
||||
- [ ] [A2] Portare `Watch-RunnerHealth.ps1` → `monitor runner`
|
||||
- [ ] [A2] Portare `Get-CIJobSummary.ps1` → `report job`
|
||||
- [ ] [A2] Sostituire ognuno dei `.ps1` portati con shim a 3 righe verso la CLI Python
|
||||
- [ ] [A3] Portare `New-BuildVM.ps1` → `vm new`
|
||||
- [ ] [A3] Portare `Invoke-RemoteBuild.ps1` → `build run`
|
||||
- [ ] [A3] Portare `Get-BuildArtifacts.ps1` → `artifacts collect`
|
||||
- [ ] [A3] Convertire test Pester `New-BuildVM.Tests.ps1`, `Wait-VMReady.Tests.ps1`, `Remove-BuildVM.Tests.ps1` in pytest
|
||||
- [ ] [A4] Portare `Invoke-CIJob.ps1` → `python -m ci_orchestrator job`
|
||||
- [ ] [A4] Aggiornare `gitea/actions/local-ci-build/action.yml` per invocare la CLI Python direttamente
|
||||
- [ ] [A4] Forzare `PYTHONIOENCODING=utf-8` in `runner/config.yaml`
|
||||
- [ ] [A4] Eseguire workflow `build-nsInnoUnp.yml` (matrix Win+Linux) end-to-end PASS
|
||||
- [ ] [A5] Convertire errori frequenti `AGENTS.md` #9, #10, #11, #12 in test pytest dedicati
|
||||
- [ ] [A5] Aggiornare `AGENTS.md` con sezione "Python development" (venv, ruff, mypy, pytest)
|
||||
- [ ] [A5] Aggiornare `docs/ARCHITECTURE.md` con nuovo layout package
|
||||
- [ ] [A5] Aggiornare `README.md` con setup Python
|
||||
- [ ] [A5] Eseguire `Test-CapacityBurnIn` (versione Python) 4 job concorrenti PASS
|
||||
- [ ] [B1] Installare Linux Mint LTS + aggiornamenti su hardware target
|
||||
- [ ] [B1] Installare VMware Workstation Pro Linux (bundle ufficiale Broadcom) e validare `vmrun` su VM di test
|
||||
- [ ] [B1] Configurare `vmnet8` NAT range `192.168.79.0/24` (allineato all'host Windows)
|
||||
- [ ] [B1] Creare utente `ci-runner` (uid dedicato) e storage `/var/lib/ci/{build-vms,artifacts,templates,keys}` con ownership `ci-runner:ci-runner` mode 750
|
||||
- [ ] [B1] Installare Python 3.11+ e creare venv in `/opt/ci/venv/` con `pip install -e .` del package portato in Fase A
|
||||
- [ ] [B1] Validare `python -m ci_orchestrator --help` da utente `ci-runner`
|
||||
- [ ] [B2] Spegnere fully powered-off i template VM sull'host Windows
|
||||
- [ ] [B2] Trasferire `F:\CI\Templates\` → `/var/lib/ci/templates/` con `rsync` (preservare snapshot `BaseClean` / `BaseClean-Linux`)
|
||||
- [ ] [B2] Registrare i `.vmx` su Workstation Linux e validare snapshot via `vmrun listSnapshots`
|
||||
- [ ] [B2] Smoke test manuale: `vm new` + `wait-ready` + `vm remove` da host Linux
|
||||
- [ ] [B3] Copiare `F:\CI\keys\ci_linux*` → `/etc/ci/keys/` con perms 600 owner `ci-runner`
|
||||
- [ ] [B3] Re-store credenziali `BuildVMGuest` e `GiteaPAT` con `secret-tool` nel keyring Linux
|
||||
- [ ] [B3] PoC accesso headless al keyring sotto systemd (file vault `age`/`sops` come fallback)
|
||||
- [ ] [B4] Scaricare act_runner Linux ≥ v1.0.2 e registrarlo verso Gitea con label `windows-build:host` e `linux-build:host`
|
||||
- [ ] [B4] Creare unit `/etc/systemd/system/act-runner.service` con `User=ci-runner`, env `CI_*`, `PYTHONIOENCODING=utf-8`
|
||||
- [ ] [B4] `systemctl enable --now act-runner.service` e validare via `journalctl -u act-runner -f`
|
||||
- [ ] [B5] Convertire `Register-CIScheduledTasks.ps1` in coppie `*.service` + `*.timer` (`cleanup-orphaned-vms`, `retention-policy`, `watch-disk-space`, `watch-runner-health`, `backup-template`)
|
||||
- [ ] [B5] Abilitare tutti i timer con `systemctl enable --now` e validare `systemctl list-timers`
|
||||
- [ ] [B6] Stop act_runner sull'host Windows e verifica coda Gitea vuota
|
||||
- [ ] [B6] Rsync incrementale finale `F:\CI\Artifacts\` → `/var/lib/ci/artifacts/`
|
||||
- [ ] [B6] Trigger smoke workflow + `build-nsInnoUnp.yml` matrix da host Linux PASS
|
||||
- [ ] [B7] Eseguire burn-in 4 job concorrenti × 10 round su template Windows e Linux con tempi entro ±20% baseline
|
||||
- [ ] [B8] Backup finale di `F:\CI\` su archivio
|
||||
- [ ] [B8] Lasciare host Windows spento ma reinstallato per ≥1 mese come rollback
|
||||
- [ ] [X1] Aggiornare `lint.yml` per girare ruff + mypy + pytest oltre a PSSA
|
||||
- [ ] [X2] Aggiungere observability: log strutturato `logging` + journald handler in Fase B
|
||||
- [ ] [X3] Documentare gestione credenziali headless (DPAPI machine scope su Win, vault `age` su Linux)
|
||||
- [ ] [X4] Aggiornare `docs/RUNBOOK.md`, `docs/HOST-SETUP.md`, `docs/CI-FLOW.md` end-of-Fase-B
|
||||
|
||||
## 0. Prerequisiti e gating
|
||||
|
||||
Da soddisfare prima di iniziare A1:
|
||||
|
||||
- [ ] Repo `local-ci-cd-system` clonato e workflow `lint.yml` + `self-test.yml` verdi sull'host Windows attuale
|
||||
- [ ] Backup integro di `F:\CI\Templates\` (snapshot `BaseClean` validato con `vmrun listSnapshots`)
|
||||
- [ ] Python 3.11+ installato sull'host Windows e disponibile come `python` nel PATH dell'utente che gira act_runner
|
||||
- [ ] Accesso amministrativo a Gitea per registrare/de-registrare runner durante A4 e B4
|
||||
- [ ] Credenziali `BuildVMGuest` e `GiteaPAT` esistenti nel Credential Manager Windows e leggibili dall'utente di servizio
|
||||
- [ ] `AGENTS.md` letto integralmente da chi esegue il piano (errori #1–#12 sono test case obbligatori)
|
||||
- [ ] Hardware target Linux Mint disponibile fisicamente (può restare spento fino a B1)
|
||||
|
||||
## 1. Fase A — Rewrite Python (host Windows attuale)
|
||||
|
||||
### A1 — Bootstrap progetto + moduli core
|
||||
|
||||
**Obiettivo**: creare scheletro package Python con `VmBackend`, transport
|
||||
WinRM/SSH e credential store funzionanti contro l'ambiente reale via PoC
|
||||
`wait-ready`.
|
||||
|
||||
**Input / dipendenze**: prerequisiti §0; `scripts/_Common.psm1` e
|
||||
`scripts/_Transport.psm1` come riferimento di comportamento atteso;
|
||||
`AGENTS.md` errori #9–#12 da preservare semanticamente.
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Creare `pyproject.toml` (build backend `setuptools` o `hatchling`) con dipendenze `pypsrp`, `paramiko`, `keyring`, `click`, `tomli` (se Python <3.11), `rich` opzionale per logging
|
||||
- [ ] Creare layout `src/ci_orchestrator/` con `__init__.py`, `__main__.py` (entry point `click`)
|
||||
- [ ] Creare venv in `F:\CI\python\venv\` e installare il package in editable (`pip install -e .[dev]`)
|
||||
- [ ] Implementare `config.py`: caricamento env vars (`CI_ROOT`, `CI_TEMPLATES`, `CI_BUILD_VMS`, `CI_ARTIFACTS`, `CI_KEYS`) con merge da `config.toml` opzionale; default OS-aware (Windows → `F:\CI\...`, Linux → `/var/lib/ci/...`)
|
||||
- [ ] Implementare `backends/protocol.py` con Protocol `VmBackend` (metodi `clone_linked`, `start`, `stop`, `delete`, `get_ip`, `list_snapshots`) e dataclass `VmHandle` (path/identificatore opaco)
|
||||
- [ ] Implementare `backends/workstation.py`: `WorkstationVmrunBackend` che usa `subprocess.run([vmrun, '-T', 'ws', op, *args], check=False, capture_output=True, text=True, encoding='utf-8')` con check esplicito su `returncode`
|
||||
- [ ] Implementare `transport/winrm.py`: wrapper su `pypsrp.client.Client(host, username, password, ssl=True, cert_validation=False)` con metodi `run`, `copy`, `fetch`
|
||||
- [ ] Implementare `transport/ssh.py`: wrapper su `paramiko.SSHClient` con `set_missing_host_key_policy(AutoAddPolicy)`, `known_hosts` configurabile (default `None` per non interferire con clone ephemeri — vedi `AGENTS.md` errore #12)
|
||||
- [ ] Implementare `credentials.py`: Protocol `CredentialStore` + `KeyringCredentialStore` che usa `keyring.get_credential(target, None)` e ritorna oggetto `Credential(username, password)`
|
||||
- [ ] PoC end-to-end: comando `python -m ci_orchestrator wait-ready --vmx <path> --timeout 120` su un clone reale del template `WinBuild2025` (validare WinRM HTTPS self-signed)
|
||||
- [ ] Setup `pytest`, `pytest-mock`, `ruff`, `mypy` come dev dependencies; configurare `pyproject.toml` con `[tool.ruff]` e `[tool.mypy]` strict
|
||||
- [ ] Test pytest unitari: `test_vmrun.py` (mock subprocess, casi `vmrun list` con e senza VMX target — copre errore #10), `test_winrm.py` (mock `pypsrp`), `test_ssh.py` (mock `paramiko`, copre errore #12), `test_credentials.py` (mock `keyring`)
|
||||
- [ ] Aggiungere job `python` a `gitea/workflows/lint.yml`: setup venv → `ruff check src/ tests/` → `mypy --strict src/` → `pytest --cov=ci_orchestrator --cov-fail-under=70`
|
||||
|
||||
**Hook futuri Fase C**: il Protocol `VmBackend` deve usare nomi neutri
|
||||
(`clone_linked`, non `vmrun_clone`), accettare `template`/`snapshot`/`name`
|
||||
come stringhe opache (non path Windows), separare `clone_linked` da
|
||||
`start` (ESXi richiede `PowerOn` come task distinto). Niente assunzioni
|
||||
"backend == stesso host del control plane" — `WorkstationVmrunBackend`
|
||||
deve esporre la stessa firma anche se di fatto è locale.
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- `pytest -q tests/` PASS
|
||||
- `pytest --cov=ci_orchestrator --cov-fail-under=70` PASS
|
||||
- `ruff check src/ tests/` 0 errori
|
||||
- `mypy --strict src/` 0 errori
|
||||
- PoC `wait-ready` ritorna exit 0 entro 120s contro clone reale di `WinBuild2025`
|
||||
- `gitea/workflows/lint.yml` PASS in CI
|
||||
|
||||
**Rollback**: rimuovere cartella `src/`, `tests/`, `pyproject.toml`,
|
||||
venv `F:\CI\python\venv\`; ripristinare `lint.yml` precedente da git
|
||||
(`git checkout HEAD~1 -- gitea/workflows/lint.yml`). Nessun PS legacy
|
||||
modificato in A1.
|
||||
|
||||
**Definizione di fatto step A1**:
|
||||
|
||||
- [ ] PoC `wait-ready` PASS contro VM reale
|
||||
- [ ] Coverage pytest ≥70% sui moduli core
|
||||
- [ ] `lint.yml` aggiornato e verde
|
||||
- [ ] Protocol `VmBackend` reviewato e congelato
|
||||
- [ ] Documentato setup venv in `README.md` (sezione minima)
|
||||
|
||||
### A2 — Script "foglia" (no state condiviso)
|
||||
|
||||
**Obiettivo**: portare in Python tutti gli script PS che non condividono
|
||||
stato con l'orchestratore, sostituendoli con shim minimi.
|
||||
|
||||
**Input / dipendenze**: A1 done. File: `Wait-VMReady.ps1`,
|
||||
`Remove-BuildVM.ps1`, `Cleanup-OrphanedBuildVMs.ps1`,
|
||||
`Watch-DiskSpace.ps1`, `Watch-RunnerHealth.ps1`, `Get-CIJobSummary.ps1`.
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Implementare `commands/wait.py` con sottocomando `wait-ready` (parametri `--vmx`, `--timeout`, `--guest-os`)
|
||||
- [ ] Implementare `commands/vm.py` con sottocomando `vm remove` (parametri `--vmx`, `--force`)
|
||||
- [ ] Implementare `vm cleanup` (scan `CI_BUILD_VMS`, riconoscimento clone orfani per pattern naming + età)
|
||||
- [ ] Implementare `commands/monitor.py` con `monitor disk` (soglie configurabili, output JSON o human)
|
||||
- [ ] Implementare `monitor runner` (controllo processo act_runner attivo + ultimo heartbeat)
|
||||
- [ ] Implementare `commands/report.py` con `report job` (read-only su artifact dir + log job)
|
||||
- [ ] Sostituire `scripts/Wait-VMReady.ps1` con shim PS 5.1 di 3 righe: `& 'F:\CI\python\venv\Scripts\python.exe' -m ci_orchestrator wait-ready @args; exit $LASTEXITCODE`
|
||||
- [ ] Idem per `Remove-BuildVM.ps1`, `Cleanup-OrphanedBuildVMs.ps1`, `Watch-DiskSpace.ps1`, `Watch-RunnerHealth.ps1`, `Get-CIJobSummary.ps1`
|
||||
- [ ] Aggiungere test pytest per ogni comando (mock backend + mock filesystem via `tmp_path`)
|
||||
- [ ] Convertire `tests/Wait-VMReady.Tests.ps1` in `tests/test_commands_wait.py` (preservando i casi negativi)
|
||||
- [ ] Convertire `tests/Remove-BuildVM.Tests.ps1` in `tests/test_commands_vm_remove.py`
|
||||
- [ ] Verificare che gli scheduled task esistenti (`Register-CIScheduledTasks.ps1`) continuino a funzionare invocando gli shim PS
|
||||
|
||||
**Hook futuri Fase C**: `vm cleanup` non deve assumere scan locale del
|
||||
filesystem — la firma deve accettare un `VmBackend` come dipendenza,
|
||||
così che in Fase C la stessa logica interroghi l'API ESXi (folder-scoped
|
||||
list).
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- `pytest tests/test_commands_*.py` PASS
|
||||
- Coverage globale rimane ≥70%
|
||||
- Trigger manuale di `Watch-DiskSpace` via shim PS produce stesso output
|
||||
semantico della versione PS originale (json comparabile)
|
||||
- Smoke: clone manuale di una VM → `wait-ready` → `vm remove` end-to-end
|
||||
|
||||
**Rollback**: `git checkout HEAD~1 -- scripts/Wait-VMReady.ps1 ...`
|
||||
(ripristino .ps1 originali); rimozione `commands/*.py` portati;
|
||||
scheduled tasks restano collegati ai .ps1 ripristinati.
|
||||
|
||||
**Definizione di fatto step A2**:
|
||||
|
||||
- [ ] Tutti gli script "foglia" hanno shim PS che chiama Python
|
||||
- [ ] Test pytest sostituiscono i Pester corrispondenti
|
||||
- [ ] Scheduled task continuano a funzionare via shim
|
||||
- [ ] Nessuna regressione su `self-test.yml`
|
||||
|
||||
### A3 — Pipeline di build
|
||||
|
||||
**Obiettivo**: portare la pipeline core (clone → build → collect) in
|
||||
Python preservando il comportamento dei `.ps1` esistenti.
|
||||
|
||||
**Input / dipendenze**: A2 done. File: `New-BuildVM.ps1`,
|
||||
`Invoke-RemoteBuild.ps1`, `Get-BuildArtifacts.ps1`.
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Implementare `vm new` in `commands/vm.py` (parametri `--template`, `--snapshot`, `--name`, `--guest-os`)
|
||||
- [ ] Integrare `WorkstationVmrunBackend.clone_linked` + `start` + attesa IP via `get_ip`
|
||||
- [ ] Implementare `commands/build.py` con `build run` (parametri `--vmx`, `--script`, `--workdir`, `--guest-os`); usa `transport.winrm` per Windows, `transport.ssh` per Linux
|
||||
- [ ] Gestione streaming output build verso stdout (act_runner cattura stdout — preservare comportamento `Write-Host`)
|
||||
- [ ] Implementare `commands/artifacts.py` con `artifacts collect` (parametri `--vmx`, `--remote-path`, `--local-dir`)
|
||||
- [ ] Sostituire `scripts/New-BuildVM.ps1`, `Invoke-RemoteBuild.ps1`, `Get-BuildArtifacts.ps1` con shim
|
||||
- [ ] Convertire `tests/New-BuildVM.Tests.ps1` in `tests/test_commands_vm_new.py`
|
||||
- [ ] Aggiungere test pytest per `build run` (mock transport + capture stdout)
|
||||
- [ ] Aggiungere test pytest per `artifacts collect` (mock SFTP/WinRM file copy + tmp_path)
|
||||
- [ ] Validare end-to-end: clone WinBuild2025 → build script PowerShell trivial → collect artifact ZIP
|
||||
|
||||
**Hook futuri Fase C**: `build run` deve ricevere un `VmHandle` opaco,
|
||||
non un path VMX (ESXi non ha path locale al control plane). Adattare le
|
||||
CLI a accettare entrambe (`--vmx` per workstation, `--vm-id` futuro per
|
||||
ESXi) con dispatcher nel layer `commands/`.
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- `pytest tests/test_commands_vm_new.py tests/test_commands_build.py tests/test_commands_artifacts.py` PASS
|
||||
- Coverage globale ≥70%
|
||||
- Smoke: workflow CI manuale end-to-end usando esclusivamente gli shim
|
||||
- `Test-NsinnounpBuild.ps1` continua a passare (usa shim sotto)
|
||||
|
||||
**Rollback**: ripristino `.ps1` originali da git; rimozione comandi
|
||||
Python; nessuna modifica ai workflow YAML in A3 (solo shim).
|
||||
|
||||
**Definizione di fatto step A3**:
|
||||
|
||||
- [ ] Pipeline build completa accessibile via Python CLI
|
||||
- [ ] Shim PS preservano l'API per i caller esistenti
|
||||
- [ ] Smoke build PASS contro VM reale Windows e Linux
|
||||
- [ ] Pester `New-BuildVM.Tests.ps1` rimosso e sostituito
|
||||
|
||||
### A4 — Orchestratore + switch workflow
|
||||
|
||||
**Obiettivo**: portare `Invoke-CIJob.ps1` e fare switch dei workflow
|
||||
Gitea per chiamare la CLI Python direttamente.
|
||||
|
||||
**Input / dipendenze**: A3 done. File: `Invoke-CIJob.ps1`,
|
||||
`gitea/actions/local-ci-build/action.yml`, `runner/config.yaml`.
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Implementare `commands/job.py` con sottocomando `job` (entry point completo: parsing parametri job, clone, wait, build, collect, cleanup)
|
||||
- [ ] Gestione errori e cleanup garantito (try/finally con `vm remove` anche su failure)
|
||||
- [ ] Aggiornare `gitea/actions/local-ci-build/action.yml`: cambiare `shell: powershell` → `shell: cmd` o invocazione diretta `python -m ci_orchestrator job ...`
|
||||
- [ ] Aggiungere `PYTHONIOENCODING=utf-8` in `runner/config.yaml` env
|
||||
- [ ] Lasciare `Invoke-CIJob.ps1` come shim per scheduled task / chiamate manuali esterne (rimosso in A5)
|
||||
- [ ] Test pytest end-to-end con mock backend + transport: `test_commands_job.py`
|
||||
- [ ] Eseguire workflow `build-nsInnoUnp.yml` matrix (Win + Linux) PASS
|
||||
- [ ] Eseguire workflow `self-test.yml` PASS
|
||||
- [ ] Eseguire workflow `lint.yml` PASS
|
||||
|
||||
**Hook futuri Fase C**: la selezione del backend deve avvenire in
|
||||
`job.py` leggendo `config.toml` `[backend].type` (default `workstation`).
|
||||
Niente import diretto di `WorkstationVmrunBackend` in `job.py` — usare
|
||||
factory `backends.load_backend(config)`.
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- `pytest tests/test_commands_job.py` PASS con coverage ≥80% sul comando `job`
|
||||
- Workflow `build-nsInnoUnp.yml` end-to-end PASS dal nuovo entry point
|
||||
- Output catturato da act_runner leggibile (no encoding broken, no ANSI rotti)
|
||||
- Tempi job entro ±10% rispetto al baseline PS (misurati con `Measure-CIBenchmark` ancora in PS, lecito)
|
||||
|
||||
**Rollback**: `git checkout HEAD~1 -- gitea/actions/local-ci-build/action.yml runner/config.yaml`;
|
||||
`Invoke-CIJob.ps1` originale è ancora in git history e referenziabile.
|
||||
|
||||
**Definizione di fatto step A4**:
|
||||
|
||||
- [ ] `action.yml` chiama Python direttamente
|
||||
- [ ] Workflow matrix Win+Linux PASS
|
||||
- [ ] `runner/config.yaml` ha `PYTHONIOENCODING=utf-8`
|
||||
- [ ] Coverage `job.py` ≥80%
|
||||
- [ ] Misurazione tempo entro ±10% baseline
|
||||
|
||||
### A5 — Test, documentazione, cleanup
|
||||
|
||||
**Obiettivo**: chiudere debito tecnico, rimuovere shim ridondanti,
|
||||
aggiornare documentazione, eseguire burn-in finale.
|
||||
|
||||
**Input / dipendenze**: A4 done.
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Implementare test pytest dedicati per ognuno degli errori frequenti `AGENTS.md` #9 (snapshot powered-off), #10 (`vmrun list` vs `getGuestIPAddress`), #11 (machine-id reset), #12 (stderr handling — N/A per Python ma documentare)
|
||||
- [ ] Rimuovere shim PS che non hanno call site esterni (verificare con `grep_search` che nessun task / doc li referenzia)
|
||||
- [ ] Lasciare `Install-CIToolchain-*.ps1`, `Prepare-*.ps1`, `Deploy-*.ps1` in PowerShell (girano dentro guest o per ricostruzione template — vedi §4 di [idea-1](idea-1-python-rewrite.md))
|
||||
- [ ] Aggiornare `AGENTS.md`: aggiungere sezione "Python development" (venv path, ruff, mypy strict, pytest, encoding utf-8)
|
||||
- [ ] Aggiornare `docs/ARCHITECTURE.md` con nuovo layout `src/ci_orchestrator/`
|
||||
- [ ] Aggiornare `README.md` con setup Python e quick-start CLI
|
||||
- [ ] Portare `Test-CapacityBurnIn.ps1` in `commands/burnin.py` (o lasciare in PS se ancora utile come driver esterno)
|
||||
- [ ] Eseguire burn-in 4 job concorrenti × 10 round PASS sull'host Windows
|
||||
- [ ] Aggiornare `docs/RUNBOOK.md` con sezione "Operare da Python CLI"
|
||||
|
||||
**Hook futuri Fase C**: documentare in `docs/ARCHITECTURE.md` il
|
||||
contratto `VmBackend` come "extension point" e il selettore `[backend]`
|
||||
in `config.toml` come unica via per aggiungere ESXi.
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- `pytest -q` PASS, coverage globale ≥70%
|
||||
- `ruff check` + `mypy --strict` puliti
|
||||
- `lint.yml` PASS (PSSA su PS residui + ruff/mypy/pytest su Python)
|
||||
- Burn-in 4×10 PASS senza VM orfane
|
||||
- `grep_search Invoke-CIJob` nei workflow YAML ritorna 0 hit
|
||||
|
||||
**Rollback**: ripristino doc da git; ri-aggiungere shim rimossi se
|
||||
qualche caller esterno non ancora migrato emerge.
|
||||
|
||||
**Definizione di fatto step A5**:
|
||||
|
||||
- [ ] Errori #9–#12 coperti da test pytest
|
||||
- [ ] Documentazione aggiornata
|
||||
- [ ] Shim non referenziati rimossi
|
||||
- [ ] Burn-in PASS
|
||||
- [ ] **Fase A done**: tutti i criteri di "fatto" §7 soddisfatti per la parte A
|
||||
|
||||
## 2. Fase B — Migrazione host Linux Mint
|
||||
|
||||
> **Parallelizzazione**: B1 può iniziare durante A3/A4 (setup hardware
|
||||
> indipendente dal codice). B2, B3 possono iniziare appena B1 è done e
|
||||
> A5 è done. B4–B6 sono **strettamente sequenziali** dopo "A done".
|
||||
|
||||
### B1 — Setup host Linux Mint in parallelo
|
||||
|
||||
**Obiettivo**: preparare hardware Linux Mint con Workstation Pro Linux,
|
||||
storage layout e venv Python pronti, senza impatto produzione.
|
||||
|
||||
**Input / dipendenze**: hardware target disponibile. Può girare in
|
||||
parallelo a A3/A4. Riferimento `idea-2-linux-host.md` §4.
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Installare Linux Mint LTS sull'hardware target + aggiornamenti (`apt full-upgrade`)
|
||||
- [ ] Scaricare e installare VMware Workstation Pro Linux (bundle `.bundle` da Broadcom)
|
||||
- [ ] Validare `vmrun -T ws --version` ritorna versione coerente
|
||||
- [ ] Smoke test Workstation: creare VM trivia, clone, start, stop, delete via UI e via `vmrun`
|
||||
- [ ] Configurare `vmnet8` NAT range `192.168.79.0/24` (allineato all'host Windows) editando `/etc/vmware/vmnet8/dhcpd/dhcpd.conf` o via `vmware-netcfg`
|
||||
- [ ] Creare utente di servizio `ci-runner` (`useradd -r -m -s /bin/bash ci-runner`) con uid/gid dedicati
|
||||
- [ ] Creare layout `/var/lib/ci/{build-vms,artifacts,templates,keys}` con ownership `ci-runner:ci-runner` mode 750
|
||||
- [ ] Applicare ACL POSIX: `setfacl -d -m u:ci-runner:rwX /var/lib/ci/build-vms`
|
||||
- [ ] Installare Python 3.11+ (`apt install python3.11 python3.11-venv`)
|
||||
- [ ] Creare venv `/opt/ci/venv/` con `python3.11 -m venv` e installare il package: `/opt/ci/venv/bin/pip install -e /path/to/local-ci-cd-system`
|
||||
- [ ] Validare `sudo -u ci-runner /opt/ci/venv/bin/python -m ci_orchestrator --help`
|
||||
- [ ] Aggiungere `[paths.linux]` a `config.example.toml` con i path POSIX
|
||||
|
||||
**Hook futuri Fase C**: lo stesso host Linux farà da control plane se
|
||||
si attiverà ESXi. Non installare nulla che assuma "VM girano in locale"
|
||||
(es. condivisioni `vmware-shared-folders` host-only).
|
||||
|
||||
**Rischi specifici** (da `idea-2-linux-host.md` §6):
|
||||
|
||||
- `vmrun` su Linux ha differenze sottili nel parsing output → mitigazione: test del modulo `vmrun.py` su Linux subito in B1, confronto output con Windows
|
||||
- Permessi `/var/lib/ci/build-vms/`: `vmrun` come `ci-runner` deve scrivere VMDK CoW → mitigazione: ACL POSIX + test scrittura
|
||||
- `vmnet8` NAT range diverso → mitigazione: allineamento range a `192.168.79.0/24`
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- `vmrun -T ws list` ritorna lista vuota o coerente
|
||||
- `python -m ci_orchestrator --help` PASS da `ci-runner`
|
||||
- ACL POSIX verificata con `getfacl /var/lib/ci/build-vms`
|
||||
- `pytest` (subset cross-platform) PASS sul venv Linux
|
||||
|
||||
**Rollback**: il host Linux è fisicamente separato. Spegnerlo. Nessun
|
||||
impatto su produzione Windows.
|
||||
|
||||
**Definizione di fatto step B1**:
|
||||
|
||||
- [ ] Workstation Pro Linux operativa
|
||||
- [ ] Storage layout creato con ACL
|
||||
- [ ] Python venv pronto e package installato
|
||||
- [ ] `vmnet8` configurato
|
||||
|
||||
### B2 — Trasferimento template VM
|
||||
|
||||
**Obiettivo**: copiare i template VMware dall'host Windows a Linux
|
||||
mantenendo snapshot integri.
|
||||
|
||||
**Input / dipendenze**: B1 done. Può girare durante A4/A5. Riferimento
|
||||
`AGENTS.md` errore #9 (snapshot solo da powered-off).
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Verificare assenza di `*.vmem` / `*.vmsn` di runtime in `F:\CI\Templates\<name>\` (template fully powered-off)
|
||||
- [ ] Eseguire `rsync -av --progress /cygdrive/f/CI/Templates/ ci-runner@<linux-host>:/var/lib/ci/templates/` (o `scp -r` se rsync non disponibile)
|
||||
- [ ] Verificare integrità: `find /var/lib/ci/templates -name '*.vmx' -exec vmrun -T ws listSnapshots {} \;`
|
||||
- [ ] Aprire i `.vmx` su Workstation Linux per registrarli (eventuale prompt "I moved it / I copied it" → "I copied it")
|
||||
- [ ] Validare snapshot `BaseClean` su `WinBuild2025.vmx`, `WinBuild2022.vmx`
|
||||
- [ ] Validare snapshot `BaseClean-Linux` su `LinuxBuild2404.vmx`
|
||||
- [ ] Smoke test: `python -m ci_orchestrator vm new --template /var/lib/ci/templates/WinBuild2025/WinBuild2025.vmx --snapshot BaseClean --name smoke1` + `wait-ready` + `vm remove`
|
||||
|
||||
**Hook futuri Fase C**: i template restano in formato VMX. Per ESXi
|
||||
serviranno OVF (`ovftool`) — non fare nulla in B2, ma documentare che
|
||||
il dataset attuale è la base per export futuro.
|
||||
|
||||
**Rischi specifici**:
|
||||
|
||||
- Performance I/O linked clone su ext4 vs NTFS → mitigazione: misurare in B7, eventualmente XFS/BTRFS
|
||||
- Snapshot corrotto in transit → mitigazione: checksum (`sha256sum`) prima e dopo rsync
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- `vmrun listSnapshots` ritorna gli snapshot attesi su tutti i template
|
||||
- Smoke `vm new` + `wait-ready` PASS con WinRM da Linux verso guest Windows
|
||||
- Smoke `vm new` + `wait-ready` PASS con SSH da Linux verso guest Linux
|
||||
|
||||
**Rollback**: i template originali su `F:\CI\Templates\` sono intatti.
|
||||
Spegnere host Linux. Eventualmente eliminare `/var/lib/ci/templates/`.
|
||||
|
||||
**Definizione di fatto step B2**:
|
||||
|
||||
- [ ] Tutti i template registrati su Workstation Linux
|
||||
- [ ] Snapshot integri verificati
|
||||
- [ ] Smoke `vm new` PASS per Win e Linux
|
||||
|
||||
### B3 — Trasferimento credenziali e chiavi
|
||||
|
||||
**Obiettivo**: rendere disponibili sull'host Linux le credenziali
|
||||
guest e i token Gitea, accessibili da `ci-runner` headless.
|
||||
|
||||
**Input / dipendenze**: B1 done. Può girare durante A5. Riferimento
|
||||
`idea-2-linux-host.md` §6 (rischio Alta su keyring headless).
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Copiare `F:\CI\keys\ci_linux` e `ci_linux.pub` → `/etc/ci/keys/` con `chmod 600` e `chown ci-runner:ci-runner`
|
||||
- [ ] Re-store credenziale guest Windows: `sudo -u ci-runner secret-tool store --label='BuildVMGuest' service ci target BuildVMGuest`
|
||||
- [ ] Re-store Gitea PAT: `sudo -u ci-runner secret-tool store --label='GiteaPAT' service ci target GiteaPAT`
|
||||
- [ ] Validare lettura: `sudo -u ci-runner /opt/ci/venv/bin/python -c "import keyring; print(keyring.get_credential('BuildVMGuest', None))"`
|
||||
- [ ] PoC headless: testare lettura keyring da contesto systemd (no sessione D-Bus utente). Se fallisce, implementare backend `keyring` file-based con vault `age` o `sops` come fallback documentato
|
||||
- [ ] Documentare la scelta finale in `docs/HOST-SETUP.md`
|
||||
|
||||
**Hook futuri Fase C**: anche ESXi userà credenziali da `keyring` (user
|
||||
vSphere). Lo stesso `KeyringCredentialStore` deve poter caricare `EsxiHost`
|
||||
target — niente hard-coding di nomi credenziali.
|
||||
|
||||
**Rischi specifici**:
|
||||
|
||||
- `secret-tool` headless: act_runner systemd non ha D-Bus user session → mitigazione: backend file-based con `age` (decisione PoC in B3)
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- Lettura keyring PASS da utente interattivo
|
||||
- Lettura keyring PASS da contesto systemd (`systemd-run --uid=ci-runner ...` come simulazione)
|
||||
- Pytest `test_credentials.py` con backend Linux PASS
|
||||
|
||||
**Rollback**: rimuovere `/etc/ci/keys/`, `secret-tool clear` per ogni
|
||||
target. Credenziali su host Windows intatte.
|
||||
|
||||
**Definizione di fatto step B3**:
|
||||
|
||||
- [ ] Chiavi SSH copiate con perms corretti
|
||||
- [ ] Credenziali nel keyring leggibili headless
|
||||
- [ ] Strategia documentata in `docs/HOST-SETUP.md`
|
||||
|
||||
### B4 — Setup act_runner come systemd service
|
||||
|
||||
**Obiettivo**: act_runner Linux registrato verso Gitea e gestito da
|
||||
systemd, equivalente al servizio Windows attuale.
|
||||
|
||||
**Input / dipendenze**: B1, B2, B3 done. Sequenziale dopo "A done"
|
||||
(serve la CLI Python stabile per i comandi invocati dai workflow).
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Scaricare binario act_runner Linux ≥ v1.0.2 in `/opt/ci/act_runner/`
|
||||
- [ ] Generare token registrazione su Gitea per il nuovo runner
|
||||
- [ ] Registrare runner: `act_runner register --no-interactive --instance <gitea-url> --token <t> --name ci-linux --labels windows-build:host,linux-build:host`
|
||||
- [ ] Creare `/etc/systemd/system/act-runner.service`:
|
||||
- `[Service]` `User=ci-runner`, `WorkingDirectory=/var/lib/ci/runner`, `ExecStart=/opt/ci/act_runner/act_runner daemon`
|
||||
- `Environment="PYTHONIOENCODING=utf-8"`, `Environment="CI_ROOT=/var/lib/ci"`, `Environment="CI_TEMPLATES=/var/lib/ci/templates"`, `Environment="CI_BUILD_VMS=/var/lib/ci/build-vms"`, `Environment="CI_ARTIFACTS=/var/lib/ci/artifacts"`, `Environment="CI_KEYS=/etc/ci/keys"`
|
||||
- `Restart=on-failure`, `RestartSec=10`
|
||||
- [ ] `systemctl daemon-reload && systemctl enable --now act-runner.service`
|
||||
- [ ] Validare `journalctl -u act-runner -f` mostra connessione a Gitea OK
|
||||
- [ ] **NON** avviare ancora workflow di produzione — il runner Windows è ancora primario
|
||||
|
||||
**Hook futuri Fase C**: env vars `CI_*` restano valide; per ESXi si
|
||||
aggiungeranno solo `CI_BACKEND=esxi` + sezione `[backend.esxi]` in
|
||||
`config.toml`. `act-runner.service` non cambia.
|
||||
|
||||
**Rischi specifici**:
|
||||
|
||||
- Runner Linux in idle che intercetta job destinati a Windows → mitigazione: in B4 il runner viene registrato ma in stato `paused` lato Gitea, oppure label distinte temporanee fino a B6
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- `systemctl status act-runner` → `active (running)`
|
||||
- Runner visibile in Gitea admin UI come "online"
|
||||
- Job di test manuale (workflow `self-test.yml`) PASS sul runner Linux
|
||||
- `journalctl -u act-runner --since "5min ago"` senza errori critici
|
||||
|
||||
**Rollback**: `systemctl disable --now act-runner`, de-registrare
|
||||
runner da Gitea. Runner Windows resta primario.
|
||||
|
||||
**Definizione di fatto step B4**:
|
||||
|
||||
- [ ] act-runner.service attivo e abilitato
|
||||
- [ ] Runner online in Gitea
|
||||
- [ ] `self-test.yml` PASS dal nuovo runner
|
||||
- [ ] Logging via journald validato
|
||||
|
||||
### B5 — Conversione scheduled tasks → systemd timers
|
||||
|
||||
**Obiettivo**: tutti i task periodici del runner Windows sono
|
||||
replicati come coppie `*.service` + `*.timer` su Linux.
|
||||
|
||||
**Input / dipendenze**: B4 done. Riferimento `Register-CIScheduledTasks.ps1`
|
||||
per l'inventario dei task.
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Inventariare i task in `Register-CIScheduledTasks.ps1` (identificare cadenza e comando per ognuno)
|
||||
- [ ] Per `cleanup-orphaned-vms`: creare `ci-cleanup-orphaned-vms.service` (oneshot, `ExecStart=/opt/ci/venv/bin/python -m ci_orchestrator vm cleanup`) + `.timer` (`OnCalendar=hourly`)
|
||||
- [ ] Per `retention-policy`: creare `ci-retention-policy.service` + `.timer` (`OnCalendar=daily`)
|
||||
- [ ] Per `watch-disk-space`: `ci-watch-disk-space.service` + `.timer` (`OnCalendar=*:0/15`)
|
||||
- [ ] Per `watch-runner-health`: `ci-watch-runner-health.service` + `.timer` (`OnCalendar=*:0/5`)
|
||||
- [ ] Per `backup-template`: `ci-backup-template.service` + `.timer` (`OnCalendar=weekly`)
|
||||
- [ ] `systemctl daemon-reload` e `systemctl enable --now <ognuno>.timer`
|
||||
- [ ] Validare `systemctl list-timers` mostra tutti i timer schedulati
|
||||
- [ ] Documentare il mapping in `docs/HOST-SETUP.md`
|
||||
|
||||
**Hook futuri Fase C**: `vm cleanup` con backend ESXi userà la stessa
|
||||
unit — il selettore backend è in `config.toml`, non nel comando.
|
||||
|
||||
**Rischi specifici**:
|
||||
|
||||
- Cadenza non perfettamente equivalente tra Task Scheduler e systemd timers → mitigazione: usare `OnCalendar` esplicito + `Persistent=true` per non perdere esecuzioni durante reboot
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- `systemctl list-timers --all` mostra tutti i timer attesi
|
||||
- Trigger manuale `systemctl start ci-cleanup-orphaned-vms.service` esegue senza errori
|
||||
- `journalctl -u ci-*` mostra esecuzioni riuscite
|
||||
|
||||
**Rollback**: `systemctl disable --now <name>.timer` per ognuno;
|
||||
rimuovere file unit. Task Scheduler Windows ancora attivo.
|
||||
|
||||
**Definizione di fatto step B5**:
|
||||
|
||||
- [ ] Tutti i task periodici come timer systemd attivi
|
||||
- [ ] Trigger manuale di ognuno PASS
|
||||
- [ ] Mapping documentato
|
||||
|
||||
### B6 — Cutover
|
||||
|
||||
**Obiettivo**: transizione produzione dall'host Windows al Linux in
|
||||
una finestra di manutenzione concordata.
|
||||
|
||||
**Input / dipendenze**: A done, B1–B5 done. Strettamente sequenziale.
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Annunciare finestra di manutenzione
|
||||
- [ ] Verificare nessun job in coda lato Gitea
|
||||
- [ ] Stop act_runner sull'host Windows: `Stop-Service actions-runner` (PS 5.1, `$LASTEXITCODE` controllato)
|
||||
- [ ] Disabilitare scheduled task Windows: `Unregister-ScheduledTask -TaskName 'CI-*' -Confirm:$false`
|
||||
- [ ] Rsync incrementale finale `F:\CI\Artifacts\` → `/var/lib/ci/artifacts/` (preserva permessi)
|
||||
- [ ] Verificare che il runner Linux sia "online" e non "paused" su Gitea
|
||||
- [ ] Trigger manuale di workflow smoke (`self-test.yml`) PASS
|
||||
- [ ] Trigger manuale `build-nsInnoUnp.yml` matrix Win + Linux PASS
|
||||
- [ ] Monitorare `journalctl -u act-runner -f` per ≥30 min sotto carico reale
|
||||
|
||||
**Hook futuri Fase C**: nessuno specifico in B6. Cutover non tocca
|
||||
backend.
|
||||
|
||||
**Rischi specifici**:
|
||||
|
||||
- Gap di tempo tra stop runner Windows e start runner Linux durante il quale i job vanno in errore → mitigazione: cutover in finestra a zero traffic + Gitea UI "paused" preventivo
|
||||
- WinRM da Linux verso guest Windows fallisce su edge case TLS → mitigazione: test in B2 + B4, fallback documentato in `idea-2-linux-host.md` §7 (dual-host)
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- `self-test.yml` PASS dal runner Linux
|
||||
- `build-nsInnoUnp.yml` matrix Win+Linux PASS dal runner Linux
|
||||
- Nessun errore in `journalctl -u act-runner --since "1h ago"` di severità critica
|
||||
- Artifact correttamente in `/var/lib/ci/artifacts/`
|
||||
|
||||
**Rollback** (entro 30 min se PASS critico fallisce):
|
||||
|
||||
- `systemctl stop act-runner` su Linux
|
||||
- `Start-Service actions-runner` su Windows
|
||||
- `Register-ScheduledTask` per riabilitare task Windows
|
||||
- Documentare incident e tornare a B1–B5 per fix
|
||||
|
||||
**Definizione di fatto step B6**:
|
||||
|
||||
- [ ] Runner Linux primario, runner Windows fermo
|
||||
- [ ] Workflow matrix PASS dal nuovo host
|
||||
- [ ] Artifact migrati
|
||||
- [ ] 30 min di esercizio senza incidenti
|
||||
|
||||
### B7 — Capacity burn-in sul nuovo host
|
||||
|
||||
**Obiettivo**: validare che il nuovo host regga il carico target con
|
||||
tempi paragonabili.
|
||||
|
||||
**Input / dipendenze**: B6 done.
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Eseguire burn-in 4 job concorrenti × 10 round su template `WinBuild2025`
|
||||
- [ ] Eseguire burn-in 4 job concorrenti × 10 round su template `LinuxBuild2404`
|
||||
- [ ] Confrontare tempi medi vs baseline pre-migrazione (registrato in A5)
|
||||
- [ ] Verificare success rate 100% (no VM orfane, no failure transienti)
|
||||
- [ ] Misurare uso disco `/var/lib/ci/build-vms/` durante e dopo burn-in (cleanup automatico)
|
||||
- [ ] Documentare risultati in `docs/RUNBOOK.md`
|
||||
|
||||
**Hook futuri Fase C**: i risultati di B7 sono il baseline contro cui
|
||||
confrontare un eventuale backend ESXi (criterio di "C done" in
|
||||
`idea-3-esxi-support.md` §8).
|
||||
|
||||
**Rischi specifici**:
|
||||
|
||||
- Performance I/O ext4 inferiore → mitigazione: se delta >20%, valutare XFS/BTRFS
|
||||
- VM orfane non pulite → mitigazione: timer `ci-cleanup-orphaned-vms` deve essere già attivo (B5)
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- Burn-in PASS con success rate 100%
|
||||
- Tempo medio entro ±20% baseline Windows
|
||||
- Nessuna VM orfana al termine
|
||||
- Uso disco torna al baseline post-cleanup
|
||||
|
||||
**Rollback**: non applicabile (B7 è validazione, non modifica). Se
|
||||
fallisce, rollback a Windows (vedi B6 rollback).
|
||||
|
||||
**Definizione di fatto step B7**:
|
||||
|
||||
- [ ] Burn-in PASS
|
||||
- [ ] Tempi documentati
|
||||
- [ ] Nessuna VM orfana
|
||||
|
||||
### B8 — Decommissioning host Windows (opzionale)
|
||||
|
||||
**Obiettivo**: dopo ≥1 settimana di stabilità, dismettere host Windows.
|
||||
|
||||
**Input / dipendenze**: B7 done + ≥1 settimana di esercizio Linux
|
||||
senza incidenti.
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Backup finale di `F:\CI\` su archivio esterno (`tar` + checksum)
|
||||
- [ ] Spegnimento runner Windows (`Stop-Service actions-runner`, runner già fermo da B6)
|
||||
- [ ] Lasciare host Windows installato ma spento per ≥1 mese come rollback
|
||||
- [ ] Documentare procedura di riaccensione in `docs/RUNBOOK.md`
|
||||
- [ ] Dopo 1 mese: riallocare hardware se non ci sono stati rollback
|
||||
|
||||
**Hook futuri Fase C**: nessuno.
|
||||
|
||||
**Rischi specifici**:
|
||||
|
||||
- Decommissioning prematuro elimina rollback → mitigazione: gating ≥1 settimana + ≥1 mese spento prima di riallocare
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- Backup verificato (estrazione di prova in tmp)
|
||||
- Host Windows spento, hardware integro
|
||||
- Runbook aggiornato
|
||||
|
||||
**Rollback**: riaccendere host Windows, ripristinare scheduled task,
|
||||
re-registrare runner Windows verso Gitea.
|
||||
|
||||
**Definizione di fatto step B8**:
|
||||
|
||||
- [ ] Backup `F:\CI\` archiviato e validato
|
||||
- [ ] Host Windows spento ma intatto
|
||||
- [ ] Runbook rollback documentato
|
||||
- [ ] **Fase B done**: tutti i criteri di "fatto" §7 soddisfatti
|
||||
|
||||
## 3. Step trasversali (X)
|
||||
|
||||
### X1 — Lint dual-stack (PSSA + ruff/mypy)
|
||||
|
||||
**Obiettivo**: pipeline di lint unica che copre Python e PowerShell residuo.
|
||||
|
||||
**Input / dipendenze**: A1 (per la parte Python). Aggiornata in A5 e B8.
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Aggiornare `gitea/workflows/lint.yml` con job `python-lint` (ruff + mypy + pytest) e job `pwsh-lint` (PSSA su `scripts/`, `template/`, `tests/`)
|
||||
- [ ] In B8: rimuovere o relegare a job opzionale la parte PSSA (resta utile solo per `template/` Windows)
|
||||
- [ ] Configurare `PSScriptAnalyzerSettings.psd1` per coprire `scripts/`, `template/`, `tests/` (già presente in repo)
|
||||
- [ ] Configurare `pyproject.toml` con `[tool.ruff]`, `[tool.mypy]`, `[tool.pytest.ini_options]`
|
||||
|
||||
**Hook futuri Fase C**: nessuno specifico.
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- `lint.yml` PASS con entrambi i job
|
||||
- 0 errori PSSA, 0 errori ruff, 0 errori mypy strict
|
||||
|
||||
**Rollback**: ripristino `lint.yml` da git.
|
||||
|
||||
**Definizione di fatto step X1**:
|
||||
|
||||
- [ ] `lint.yml` con job dual-stack
|
||||
- [ ] 0 warning bloccanti
|
||||
- [ ] Documentato in `AGENTS.md`
|
||||
|
||||
### X2 — Observability
|
||||
|
||||
**Obiettivo**: log strutturati + integrazione journald in Fase B.
|
||||
|
||||
**Input / dipendenze**: A1 (logger setup); B4 (systemd journald).
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Configurare `logging` Python con formatter strutturato (JSON o key=value), nessun colore ANSI obbligatorio (act_runner non li gestisce sempre)
|
||||
- [ ] In B4: configurare service unit per inviare stdout/stderr a journald (default systemd) e validare `journalctl -u act-runner -o json`
|
||||
- [ ] Documentare query journald utili in `docs/RUNBOOK.md`
|
||||
|
||||
**Hook futuri Fase C**: log strutturati facilitano correlazione job ↔ backend.
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- `journalctl -u act-runner -o json` ritorna eventi parsabili
|
||||
- Log Python catturato correttamente da act_runner stdout
|
||||
|
||||
**Rollback**: revert formatter logging.
|
||||
|
||||
**Definizione di fatto step X2**:
|
||||
|
||||
- [ ] Logging strutturato
|
||||
- [ ] journald query documentate
|
||||
|
||||
### X3 — Gestione credenziali headless
|
||||
|
||||
**Obiettivo**: credenziali accessibili da contesto SYSTEM (Win) e
|
||||
systemd (Linux) senza sessione utente interattiva.
|
||||
|
||||
**Input / dipendenze**: A1 (credential store); B3 (PoC headless Linux).
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Documentare in `docs/HOST-SETUP.md` il problema noto: keyring Win sotto SYSTEM e keyring Linux sotto systemd richiedono soluzioni dedicate
|
||||
- [ ] In Fase A: documentare l'uso corrente (Credential Manager con utente di servizio + DPAPI machine scope)
|
||||
- [ ] In B3: PoC + decisione finale (Secret Service via D-Bus user@.service, oppure file vault `age`/`sops`)
|
||||
- [ ] Implementare backend `keyring` file-based come opzione `KeyringFileBackend(vault_path, age_key_path)` se serve
|
||||
- [ ] Test pytest che simulano contesto headless (mock `keyring.get_credential` ritorna `None` → fallback a file vault)
|
||||
|
||||
**Hook futuri Fase C**: stesso `CredentialStore` userà credenziali
|
||||
vSphere — mantenere API generica.
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- Lettura credenziale da contesto headless PASS in entrambi gli OS
|
||||
- Test pytest fallback PASS
|
||||
|
||||
**Rollback**: usare credenziali in file plaintext non accettabile —
|
||||
nessun rollback se PoC fallisce, va risolto come blocker.
|
||||
|
||||
**Definizione di fatto step X3**:
|
||||
|
||||
- [ ] Strategia documentata
|
||||
- [ ] Test pytest fallback verde
|
||||
- [ ] PoC Linux PASS
|
||||
|
||||
### X4 — Documentazione consolidata
|
||||
|
||||
**Obiettivo**: documentazione `docs/` allineata allo stato finale (host
|
||||
Linux + Python).
|
||||
|
||||
**Input / dipendenze**: A5 e B8.
|
||||
|
||||
**Attività**:
|
||||
|
||||
- [ ] Aggiornare `docs/ARCHITECTURE.md`: layout `src/ci_orchestrator/`, Protocol `VmBackend`, hook ESXi
|
||||
- [ ] Aggiornare `docs/HOST-SETUP.md`: setup Linux Mint, Workstation Pro Linux, ACL, keyring headless
|
||||
- [ ] Aggiornare `docs/CI-FLOW.md`: nuovo entry point Python, env vars
|
||||
- [ ] Aggiornare `docs/RUNBOOK.md`: comandi systemd, journalctl, troubleshooting
|
||||
- [ ] Aggiornare `docs/BEST-PRACTICES.md`: convenzioni Python (no path hardcoded, ruff, mypy strict)
|
||||
- [ ] Aggiornare `AGENTS.md`: sezione "Python development" + nota errore #12 N/A in Python
|
||||
- [ ] Aggiornare `README.md`: quick-start Python + Linux
|
||||
|
||||
**Hook futuri Fase C**: lasciare placeholder "Backend ESXi (futuro)" in
|
||||
`docs/ARCHITECTURE.md` e `docs/HOST-SETUP.md`.
|
||||
|
||||
**Test / validazione**:
|
||||
|
||||
- Lettura manuale: un nuovo operatore deve poter setuppare host Linux + lanciare un job seguendo solo `docs/`
|
||||
- Link interni risolvono (`grep_search` su `\[.*\]\(.*\.md\)`)
|
||||
|
||||
**Rollback**: revert doc da git.
|
||||
|
||||
**Definizione di fatto step X4**:
|
||||
|
||||
- [ ] Docs aggiornate
|
||||
- [ ] Link interni validi
|
||||
- [ ] Quick-start testato da operatore esterno
|
||||
|
||||
## 4. Hook architetturali per Fase C
|
||||
|
||||
Punti di design da rispettare durante A e B per non bloccare un futuro
|
||||
backend ESXi senza implementarlo:
|
||||
|
||||
- **Protocol `VmBackend`** in `src/ci_orchestrator/backends/protocol.py`:
|
||||
metodi `clone_linked(template, snapshot, name) -> VmHandle`, `start(vm)`,
|
||||
`stop(vm, hard=True)`, `delete(vm)`, `get_ip(vm, timeout) -> str`,
|
||||
`list_snapshots(template) -> list[str]`. Firma neutra, nessun riferimento
|
||||
a `vmrun`, `vmx`, `esxi` nei nomi pubblici.
|
||||
- **`VmHandle` dataclass**: campo `id` opaco (string). Per Workstation
|
||||
contiene il path VMX, per ESXi conterrà il MoRef. Chi consuma `VmHandle`
|
||||
non può fare assunzioni sul contenuto.
|
||||
- **Selezione backend via config**: `config.toml` ha sezione `[backend]`
|
||||
con `type = "workstation"` (default) o `"esxi"` (futuro). Factory
|
||||
`backends.load_backend(config)` istanzia il backend corretto. Niente
|
||||
import diretto di `WorkstationVmrunBackend` nei comandi.
|
||||
- **Separazione control plane / compute**: il control plane (host con
|
||||
act_runner + Python) **non** deve assumere che le VM girino in locale.
|
||||
Le funzioni `vm new` / `vm cleanup` ricevono il backend come parametro,
|
||||
non leggono filesystem locale (eccetto per `WorkstationVmrunBackend`
|
||||
internamente).
|
||||
- **Naming neutro nelle API pubbliche**: i sottocomandi CLI sono
|
||||
`vm new`, `vm remove`, `vm cleanup` (non `vmrun-clone`). Le env vars
|
||||
sono `CI_BUILD_VMS`, `CI_TEMPLATES` (non `VMRUN_*`).
|
||||
- **Credenziali**: `CredentialStore` accetta target name arbitrario.
|
||||
Per ESXi si aggiungerà target `EsxiHost` senza modifiche al codice.
|
||||
- **Logging strutturato**: include sempre `backend=<type>` e
|
||||
`vm_id=<handle.id>` nei log per tracciabilità multi-backend.
|
||||
|
||||
Riferimento esplicito: [idea-3-esxi-support.md](idea-3-esxi-support.md)
|
||||
§3 (interfaccia `EsxiBackend`) e §4 (mappa operazioni).
|
||||
|
||||
## 5. Cronoprogramma e dipendenze
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
P0[0. Prerequisiti] --> A1
|
||||
A1[A1 Bootstrap + core] --> A2[A2 Script foglia]
|
||||
A2 --> A3[A3 Pipeline build]
|
||||
A3 --> A4[A4 Orchestratore + workflow]
|
||||
A4 --> A5[A5 Test + docs + cleanup]
|
||||
A1 --> X1[X1 Lint dual-stack]
|
||||
A1 --> X2[X2 Observability]
|
||||
A1 --> X3[X3 Credenziali headless]
|
||||
A3 -.parallelo.-> B1[B1 Setup host Linux]
|
||||
A4 -.parallelo.-> B1
|
||||
B1 --> B2[B2 Trasferimento template]
|
||||
B1 --> B3[B3 Credenziali Linux]
|
||||
A5 --> B4
|
||||
B2 --> B4[B4 act_runner systemd]
|
||||
B3 --> B4
|
||||
B4 --> B5[B5 Timer systemd]
|
||||
B5 --> B6[B6 Cutover]
|
||||
B6 --> B7[B7 Burn-in Linux]
|
||||
B7 --> B8[B8 Decommissioning Win]
|
||||
A5 --> X4[X4 Docs consolidate]
|
||||
B8 --> X4
|
||||
X3 --> B3
|
||||
X1 --> A5
|
||||
X2 --> B4
|
||||
```
|
||||
|
||||
## 6. Matrice rischi consolidata
|
||||
|
||||
| Rischio | Fase | Severità | Mitigazione | Owner action item |
|
||||
| ------- | ---- | -------- | ----------- | ----------------- |
|
||||
| `pypsrp` edge case con WinRM HTTPS self-signed | A1 | Media | PoC `wait-ready` come primo deliverable A1 prima di committare al resto | A1 attività #10 |
|
||||
| `keyring` sotto SYSTEM (act_runner service) non vede credenziali utente | A1, B3 | Alta | DPAPI machine scope su Win; PoC vault file `age`/`sops` su Linux in B3 | X3 attività complete |
|
||||
| Perdita di know-how errori `AGENTS.md` #9–#12 durante refactor | A5 | Media | Ognuno convertito in test pytest **prima** di rimuovere il `.ps1` corrispondente | A5 attività #1 |
|
||||
| Doppio mantenimento PS+Python durante migrazione | A2–A4 | Alta | Strategia shim minimizza la finestra; `lint.yml` dual-stack | X1 attività complete |
|
||||
| Astrazione `VmBackend` over-engineered se Fase C non parte mai | A1 | Bassa | 1 implementazione concreta + Protocol = ~50 LOC extra; costo trascurabile | accettato |
|
||||
| act_runner cattura male output Python (encoding cp1252) | A4 | Media | `PYTHONIOENCODING=utf-8` in `runner/config.yaml` | A4 attività #4 |
|
||||
| `vmrun` su Linux ha differenze parsing output `list` / `getGuestIPAddress` | B1 | Media | Test modulo `vmrun.py` su Linux subito in B1 | B1 attività #3 |
|
||||
| Performance I/O linked clone su ext4 vs NTFS | B7 | Bassa | Misurare con burn-in B7; eventualmente XFS/BTRFS | B7 attività #5 |
|
||||
| `vmnet8` NAT range diverso → IP collision LAN | B1 | Media | Allineare `192.168.79.0/24` in B1 | B1 attività #5 |
|
||||
| WinRM HTTPS da Linux: TLS 1.2 cipher non supportati dal Windows guest | B2 | Media | Smoke test prima del cutover; stesso codice già testato in A1 da Win host | B2 attività #7 |
|
||||
| Permessi `/var/lib/ci/build-vms/` per `ci-runner` | B1 | Media | ACL POSIX con `setfacl -d -m` | B1 attività #8 |
|
||||
| `secret-tool` headless senza D-Bus session | B3 | Alta | PoC + fallback file vault `age` | B3 attività #5 |
|
||||
| Errore `AGENTS.md` #11 (machine-id Linux) si ripropone | B2 | Bassa | Già documentato; fix nel template prima dello snapshot | preservato |
|
||||
| Errore `AGENTS.md` #12 (stderr nativa + `'Stop'`) | A | N/A | Sparisce in Python | dichiarato in A5 |
|
||||
| Cutover gap window (job in errore tra stop Win e start Linux) | B6 | Media | Finestra a zero traffic + Gitea "paused" preventivo | B6 attività #2 |
|
||||
| Decommissioning prematuro elimina rollback | B8 | Media | Gating ≥1 settimana + ≥1 mese spento prima di riallocare | B8 attività #3 |
|
||||
| **(integrazione A↔B)** Shim PS che assume path Windows si rompe in Fase B | A2, B6 | Media | Shim deve invocare Python via path da env (`%CI_PYTHON_VENV%`), non hardcoded `F:\` | A2 attività #7 |
|
||||
| **(integrazione A↔B)** `config.toml` con default Win-only blocca B1 | A1, B1 | Bassa | A1 deve già includere `[paths.linux]` con default POSIX | A1 attività #4 |
|
||||
| **(integrazione A↔B)** Test pytest assumono `Path('F:\\CI')` e falliscono su Linux | A1, B1 | Media | Fixture parametrizzata `tmp_path` in tutti i test, no path hardcoded | A1 attività #11 |
|
||||
| Licenza Workstation Pro Linux cambia post-Broadcom | B1 | Bassa | Free per uso personale e commerciale dal 2024; monitorare; piano B = KVM (rimandato) | B1 monitoring continuo |
|
||||
|
||||
## 7. Definizione di "fatto" globale (A+B)
|
||||
|
||||
- [ ] Tutti gli script `scripts/*.ps1` portati a Python o ridotti a shim (eccetto `template/*.ps1` e `Install-CIToolchain-*.ps1` che restano PS by design)
|
||||
- [ ] `Invoke-CIJob.ps1` non più referenziato nei workflow YAML
|
||||
- [ ] `pytest` verde con coverage ≥70% su `src/ci_orchestrator/`
|
||||
- [ ] `ruff check` + `mypy --strict` puliti su tutto `src/`
|
||||
- [ ] `lint.yml` dual-stack (PSSA per PS legacy + ruff/mypy/pytest per Python) PASS
|
||||
- [ ] act_runner gira come `act-runner.service` systemd su Linux Mint
|
||||
- [ ] Tutti i template VM (`WinBuild2025`, `WinBuild2022`, `LinuxBuild2404`) operativi dal nuovo host con snapshot integri
|
||||
- [ ] Workflow `build-nsInnoUnp.yml` matrix Win+Linux PASS dal nuovo host
|
||||
- [ ] Burn-in capacity 4 job concorrenti × 10 round PASS, tempi entro ±20% baseline Windows
|
||||
- [ ] Storage migrato a `/var/lib/ci/`, host Windows spento come rollback
|
||||
- [ ] Tutti i timer systemd attivi e schedulati equivalenti ai Task Scheduler Windows
|
||||
- [ ] Credenziali guest accessibili da `ci-runner` headless (strategia documentata)
|
||||
- [ ] `README.md`, `AGENTS.md`, `docs/ARCHITECTURE.md`, `docs/HOST-SETUP.md`, `docs/CI-FLOW.md`, `docs/RUNBOOK.md` aggiornati allo stato finale
|
||||
- [ ] ≥1 settimana di esercizio in produzione su host Linux senza incidenti
|
||||
- [ ] Backup `F:\CI\` archiviato e validato
|
||||
- [ ] Hook §4 (Protocol `VmBackend`, factory backend, naming neutro) presenti nel codice e referenziati in `docs/ARCHITECTURE.md`
|
||||
|
||||
## 7.1 Auto-check
|
||||
|
||||
Domande di auto-verifica a cui il documento risponde "sì":
|
||||
|
||||
- **Ogni voce della checklist master compare almeno una volta come `- [ ]` dentro lo step corrispondente?** Sì: voci `[A1]…[A5]` mappate alle attività in §1, `[B1]…[B8]` in §2, `[X1]…[X4]` in §3.
|
||||
- **Ogni step `A<n>`/`B<n>`/`X<n>` ha tutte le sottosezioni richieste?** Sì: Obiettivo, Input/dipendenze, Attività, Hook futuri Fase C, Test/validazione, Rollback, Definizione di fatto presenti per ogni step (B*: in più rischi specifici).
|
||||
- **I 12 errori frequenti di `AGENTS.md` sono stati indirizzati o dichiarati non applicabili?**
|
||||
- #1 (sintassi PS 7+): si applica solo agli shim PS residui in A2/A3 — vincolo PS 5.1 mantenuto
|
||||
- #2 (`$LASTEXITCODE` non controllato): N/A in Python (`subprocess.returncode` esplicito)
|
||||
- #3 (path hardcoded): risolto da A1 attività #4 (env vars + `config.toml`)
|
||||
- #4 (mix branch WinRM/SSH): risolto dalla separazione `transport/winrm.py` vs `transport/ssh.py`
|
||||
- #5 (WinRM verso Linux): impossibile per design — backend selezionato per `guest_os`
|
||||
- #6 (seed ISO Linux): preservato nei template, non toccato in A/B
|
||||
- #7 (`[ordered]@{}` PS 2): N/A
|
||||
- #8 (`ForEach-Object -Parallel`): N/A in Python (concorrenza via `concurrent.futures` o `asyncio` se serve)
|
||||
- #9 (snapshot powered-off): coperto da test pytest in A5 + procedura B2 attività #1
|
||||
- #10 (`vmrun getGuestIPAddress`): coperto da test pytest in A1 attività #11
|
||||
- #11 (machine-id Linux): preservato nel template, citato in B2 e in §6 rischi
|
||||
- #12 (stderr nativa + `'Stop'`): N/A in Python, dichiarato in A5 e §6
|
||||
- **Gli hook §4 sono coerenti con `idea-3-esxi-support.md` §3?** Sì: Protocol `VmBackend` ha esattamente i metodi `clone_linked`, `start`, `stop`, `delete`, `get_ip` previsti dalla firma `EsxiBackend`; selettore `[backend]` in `config.toml` come da §3 di idea-3.
|
||||
|
||||
## 8. Riferimenti
|
||||
|
||||
- [AGENTS.md](../AGENTS.md) — vincoli host, PowerShell 5.1, errori frequenti #1–#12
|
||||
- [plans/ideas-overview.md](ideas-overview.md) — razionale ordine fasi, criteri "fase completata", rollback
|
||||
- [plans/idea-1-python-rewrite.md](idea-1-python-rewrite.md) — Fase A: design, mappa traduzione, step A1–A5, layout repo, rischi
|
||||
- [plans/idea-2-linux-host.md](idea-2-linux-host.md) — Fase B: hypervisor, mappa adattamento, step B1–B8, rischi
|
||||
- [plans/idea-3-esxi-support.md](idea-3-esxi-support.md) — Fase C: §2 architettura, §3 backend pyVmomi (solo per hook)
|
||||
- [docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md) — layout attuale del sistema
|
||||
- [docs/CI-FLOW.md](../docs/CI-FLOW.md) — flusso job CI corrente
|
||||
- [docs/HOST-SETUP.md](../docs/HOST-SETUP.md) — setup host Windows attuale
|
||||
- [docs/RUNBOOK.md](../docs/RUNBOOK.md) — operatività corrente
|
||||
- [scripts/](../scripts/) — inventario PowerShell da portare/preservare
|
||||
- [template/](../template/) — script provisioning template (preservati in PS)
|
||||
@@ -0,0 +1,176 @@
|
||||
# Prompt — Generazione piano di implementazione unificato Fase A + Fase B
|
||||
|
||||
> Copia-incolla il blocco sotto come messaggio iniziale all'IA che dovrà
|
||||
> produrre il piano. L'IA deve avere accesso in lettura al repository
|
||||
> `local-ci-cd-system` (in particolare ai file citati nei riferimenti).
|
||||
|
||||
---
|
||||
|
||||
## Prompt da fornire all'IA
|
||||
|
||||
Sei un senior platform/DevOps engineer assegnato al progetto
|
||||
**local-ci-cd-system** (CI/CD on-prem basato su Gitea Actions + act_runner +
|
||||
VMware Workstation Pro + VM ephemere Win/Linux). Il tuo compito è produrre
|
||||
**un singolo documento di piano implementativo dettagliato** che fonde
|
||||
**Fase A** (rewrite Python dell'orchestratore) e **Fase B** (migrazione
|
||||
host da Windows 11 a Linux Mint) in un'unica roadmap eseguibile, mantenendo
|
||||
**hook architetturali compatibili con Fase C** (backend ESXi futuro) senza
|
||||
implementarla.
|
||||
|
||||
### Contesto obbligatorio da leggere PRIMA di scrivere
|
||||
|
||||
Prima di produrre output, leggi e tieni come riferimento:
|
||||
|
||||
1. [AGENTS.md](../AGENTS.md) — vincoli host, PowerShell 5.1, errori frequenti
|
||||
(#1–#12). Tutti i punti devono restare validi o essere esplicitamente
|
||||
superati dal piano.
|
||||
2. [plans/ideas-overview.md](ideas-overview.md) — razionale, ordine, criteri
|
||||
di "fase completata", strategia di rollback.
|
||||
3. [plans/idea-1-python-rewrite.md](idea-1-python-rewrite.md) — Fase A
|
||||
(decisioni di design, mappa di traduzione, step A1–A5, layout repo,
|
||||
rischi, definizione di fatto).
|
||||
4. [plans/idea-2-linux-host.md](idea-2-linux-host.md) — Fase B (decisione
|
||||
hypervisor, mappa di adattamento, step B1–B8, rischi, fatto).
|
||||
5. [plans/idea-3-esxi-support.md](idea-3-esxi-support.md) — Fase C: leggi
|
||||
solo §2 (architettura) e §3 (backend pyVmomi) per sapere quali hook
|
||||
lasciare aperti nel design.
|
||||
6. [docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md), [docs/CI-FLOW.md](../docs/CI-FLOW.md),
|
||||
[docs/HOST-SETUP.md](../docs/HOST-SETUP.md), [docs/RUNBOOK.md](../docs/RUNBOOK.md)
|
||||
per capire layout e operatività attuali.
|
||||
7. La cartella [scripts/](../scripts/) e [template/](../template/) per
|
||||
conoscere l'inventario reale degli script da portare/tenere.
|
||||
|
||||
### Cosa devi produrre
|
||||
|
||||
Un unico file Markdown chiamato
|
||||
`plans/implementation-plan-A-B.md` con la struttura sotto. Il documento
|
||||
deve essere **autocontenuto** (chi lo legge non deve aprire altri 6 file
|
||||
per capire cosa fare oggi), ma deve **citare** i piani originali quando
|
||||
rimanda a sezioni di dettaglio.
|
||||
|
||||
### Struttura obbligatoria del documento
|
||||
|
||||
Usa esattamente questa scaletta e gli heading indicati:
|
||||
|
||||
#### `# Piano implementativo unificato — Fase A (Python) + Fase B (host Linux)`
|
||||
|
||||
#### `## Summary esecutivo`
|
||||
Massimo 25 righe. Includi:
|
||||
- Obiettivo combinato in 2 frasi.
|
||||
- Tabella `Fase | Obiettivo | Output verificabile | Stato`.
|
||||
- Decisioni architetturali chiave già prese (Python 3.11+, pypsrp,
|
||||
paramiko, keyring, Workstation Pro Linux, astrazione `VmBackend`).
|
||||
- Vincoli che NON cambiano (act_runner, Gitea, template VM, workflow YAML
|
||||
invariati a meno di env vars / shell).
|
||||
|
||||
#### `## Checklist riassuntiva master`
|
||||
Una sola checklist `- [ ]` piatta che enumera **tutti** i deliverable
|
||||
ordinati cronologicamente, con prefisso del macro-step (`[A1]`, `[A2]`,
|
||||
…, `[B1]`, …, `[X]` per gli step trasversali). Deve essere copiabile in
|
||||
un issue tracker. Ogni voce: ≤ 1 riga, verbo all'infinito, deliverable
|
||||
osservabile (file, comando, test che passa). Niente prosa qui.
|
||||
|
||||
#### `## 0. Prerequisiti e gating`
|
||||
Cosa deve essere vero PRIMA di iniziare A1. Checklist.
|
||||
|
||||
#### `## 1. Fase A — Rewrite Python (host Windows attuale)`
|
||||
Per ogni step da A1 ad A5:
|
||||
- `### A<n> — <titolo>`
|
||||
- `**Obiettivo**:` 1 frase.
|
||||
- `**Input / dipendenze**:` step precedenti, file, env vars.
|
||||
- `**Attività**:` checklist `- [ ]` granulare (5–15 voci tipiche),
|
||||
ognuna con deliverable osservabile.
|
||||
- `**Hook futuri Fase C**:` cosa deve essere progettato adesso per non
|
||||
bloccare ESXi (es. firma del Protocol `VmBackend`, separazione
|
||||
`clone_linked` da `start`, niente assunzione "stesso host").
|
||||
- `**Test / validazione**:` comandi e criteri pass/fail concreti
|
||||
(pytest target, coverage minima, smoke workflow Gitea da triggerare).
|
||||
- `**Rollback**:` come tornare allo stato precedente lo step.
|
||||
- `**Definizione di fatto step A<n>**:` checklist `- [ ]` di chiusura
|
||||
(max 5 voci).
|
||||
|
||||
#### `## 2. Fase B — Migrazione host Linux Mint`
|
||||
Stessa struttura per gli step da B1 a B8. In più:
|
||||
- Marca esplicitamente quali step sono **eseguibili in parallelo** ad A
|
||||
(B1 setup hardware Linux può iniziare durante A3/A4) e quali sono
|
||||
**strettamente sequenziali** (B6 cutover dopo "A done").
|
||||
- Per ogni step indica **rischi specifici** presi da
|
||||
`idea-2-linux-host.md` §6 e mitigazioni.
|
||||
|
||||
#### `## 3. Step trasversali (X)`
|
||||
Step che attraversano A e B (test, docs, observability, sicurezza,
|
||||
gestione credenziali headless, lint dual-stack PSSA+ruff).
|
||||
Stessa struttura per ogni X<n>.
|
||||
|
||||
#### `## 4. Hook architetturali per Fase C`
|
||||
Lista esplicita dei punti del codice/config che restano "aperti" per
|
||||
ESXi: Protocol `VmBackend`, selezione backend via `config.toml`,
|
||||
separazione control plane / compute, naming neutro (no `vmrun_*` nei
|
||||
nomi pubblici delle API). **Niente implementazione** — solo contratto
|
||||
di design da non violare.
|
||||
|
||||
#### `## 5. Cronoprogramma e dipendenze`
|
||||
Diagramma Mermaid `gantt` o `flowchart` con le dipendenze tra step
|
||||
A1…A5, B1…B8, X1…Xn. **Niente date assolute, niente stime di durata
|
||||
in giorni**: usa solo numerazione di sequenza e dipendenze logiche.
|
||||
|
||||
#### `## 6. Matrice rischi consolidata`
|
||||
Tabella `Rischio | Fase | Severità | Mitigazione | Owner action item`.
|
||||
Unisci §7 di idea-1 e §6 di idea-2, aggiungi rischi di integrazione
|
||||
A↔B che emergono solo nel piano fuso (es. shim PowerShell che assume
|
||||
path Windows e si rompe in Fase B).
|
||||
|
||||
#### `## 7. Definizione di "fatto" globale (A+B)`
|
||||
Una checklist finale `- [ ]` che combina §8 di idea-1 + §8 di idea-2,
|
||||
deduplicata e coerente con la checklist master in testa.
|
||||
|
||||
#### `## 8. Riferimenti`
|
||||
Link relativi ai file originali letti.
|
||||
|
||||
### Regole stilistiche e di contenuto
|
||||
|
||||
1. **Lingua**: italiano tecnico, coerente con i piani esistenti.
|
||||
2. **Tono**: imperativo / dichiarativo, niente "potremmo", "si potrebbe".
|
||||
Ogni attività è un'azione concreta con deliverable.
|
||||
3. **Niente stime di tempo / date / sprint**. Solo dipendenze logiche.
|
||||
4. **Niente nuovi vincoli inventati**: tutto quello che dichiari deve
|
||||
essere derivabile dai 6 file di riferimento o dallo stato attuale del
|
||||
repo. Se proponi qualcosa di nuovo, marcalo con `> NOTA NUOVA:` e
|
||||
spiega perché.
|
||||
5. **Coerenza con `AGENTS.md`**: ogni step PowerShell residuo deve
|
||||
rispettare PS 5.1 (no `??`, no `&&`, ecc.); ogni nuova decisione che
|
||||
supera un errore frequente (#1–#12) deve dichiararlo esplicitamente.
|
||||
6. **Checklist machine-friendly**: usa esclusivamente `- [ ]` (con spazio,
|
||||
non `- [x]`). Una voce per riga. Niente sub-checklist annidate oltre
|
||||
il primo livello dentro la stessa sezione.
|
||||
7. **Tabelle**: usa pipe-tables Markdown standard. Nessun HTML.
|
||||
8. **Mermaid**: un solo diagramma in §5, sintassi valida.
|
||||
9. **Lunghezza target**: 600–1100 righe Markdown. Sotto le 500 è troppo
|
||||
sintetico, sopra le 1300 stai duplicando i piani originali.
|
||||
10. **Auto-verifica finale**: alla fine del documento, prima di §8,
|
||||
aggiungi una sezione `## 7.1 Auto-check` con queste domande a cui
|
||||
il documento stesso deve poter rispondere "sì" leggendolo:
|
||||
- Ogni voce della checklist master compare almeno una volta come
|
||||
`- [ ]` dentro lo step corrispondente?
|
||||
- Ogni step A<n>/B<n>/X<n> ha tutte le sottosezioni richieste?
|
||||
- I 12 errori frequenti di `AGENTS.md` sono stati indirizzati o
|
||||
dichiarati non applicabili?
|
||||
- Gli hook §4 sono coerenti con `idea-3-esxi-support.md` §3?
|
||||
|
||||
### Cosa NON devi fare
|
||||
|
||||
- Non implementare codice. Solo piano.
|
||||
- Non duplicare integralmente i contenuti di idea-1/idea-2: cita e
|
||||
riassumi.
|
||||
- Non introdurre tecnologie nuove non già menzionate (no Ansible,
|
||||
no Terraform, no Docker host-side, no K8s) salvo `> NOTA NUOVA:`
|
||||
motivata.
|
||||
- Non modificare altri file del repo. Solo creazione di
|
||||
`plans/implementation-plan-A-B.md`.
|
||||
- Non includere date, settimane, story point, t-shirt sizing.
|
||||
|
||||
### Output atteso
|
||||
|
||||
Un solo file: `plans/implementation-plan-A-B.md`, conforme alla
|
||||
struttura sopra, pronto per essere committato. Nessun testo
|
||||
conversazionale fuori dal file.
|
||||
Reference in New Issue
Block a user