Casi d'uso#
I casi d'uso provano manent da capo a fondo, come gira in esercizio: server API, worker, client e target SSH in container separati, su Postgres. Si parte da un singolo deploy pianificato e si arriva ai rilasci a ondate su più region, poi ai container su VM e a Kubernetes, ai load balancer, ai rilasci per percentuale, agli step personalizzati, alle integrazioni e agli application server; per ogni caso si verificano anche fallimenti e rollback.
make test-usecases # tutti i casi (circa 30 minuti)
make test-usecases CASES="uc1 uc4" # solo alcuni
make test-usecases KEEP=1 # lascia il laboratorio attivo alla fine
Servono Docker con Compose v2, Go, jq e ssh-keygen. Lo script compila i binari linux sull'host, quindi non serve l'immagine golang. UC6 e UC7 usano container privilegiati (Docker in Docker e k3s) e circa 2 GB di memoria in più per Docker. UC8 usa l'immagine di Playwright (circa 2 GB su disco), UC10 HAProxy (circa 200 MB), UC14 WildFly (circa 800 MB) e UC15 Prometheus (circa 270 MB). UC11 scarica le CRD della Gateway API da GitHub.
Il laboratorio#
flowchart LR
C[client manent] --> A[API]
A --> P[(Postgres)]
W1[worker-1] --> P
W2[worker-2] --> P
subgraph eu-1
EA[eu1-a · eu-1a]
EB[eu1-b · eu-1b]
EC[eu1-c · eu-1c]
end
subgraph us-1
UA[us1-a · us-1a]
UB[us1-b · us-1b]
end
W1 --> eu-1
W1 --> us-1
W2 --> eu-1
W2 --> us-1
subgraph uc67[UC6 · UC7]
R[(registry)]
VA[vm-a · Docker]
VB[vm-b · Docker]
K[k3s]
end
W1 --> uc67
W2 --> uc67
- Target: container Debian con SSH (per il plugin
remote-copy, layoutreleases) e un server HTTP che espone la release attiva:http://<target>:8080/<app>/current/healthz. Una release senzahealthzsimula una versione difettosa. - Configurazione di ambiente: target, parametri e segreti arrivano dal provider
file(test/usecases/config/envconfig); i descrittori contengono solo l'artefatto. - Accesso: token statici con ruoli diversi (
cirilascia,cabapprova,adminpuò derogare ai freeze,viewerlegge soltanto), e un identity provider OIDC di prova (test/usecases/oidc) con gli stessi utenti per l'interfaccia web. - Guasti simulati: il
post_commandfallisce o attende se il caso crea un file sul target. Worker e target vengono fermati o terminati condocker compose. - Profilo region: per UC5 i worker generici sono sostituiti da un worker per region (
dbos.region,region_routing), ciascuno solo sulla rete della sua region. L'orchestratore non raggiunge nessun target. - Container e Kubernetes: per UC6 e UC7 il laboratorio avvia un registry delle immagini, due VM con Docker (
vm-a,vm-b) e un cluster k3s a un nodo. L'applicazione di prova è una piccola immagine con/healthz; la versione difettosa non ce l'ha, quindiHEALTHCHECKereadinessProberestano rossi. Questi servizi partono solo per il loro caso e vengono rimossi alla fine. - Load balancer: per UC10 un HAProxy vero con la Data Plane API v3 (
test/usecases/haproxy) davanti ai target dieu-1: il backenduc10-shopcon gli health check HTTP viene svuotato a lotti, il backenduc10-bluecontiene i due slot del rilascio per percentuale. Per UC15 e UC16 i backenduc15-webeuc16-webhanno i server chiamati come gli slot, e HAProxy esporta le sue metriche per server a un Prometheus vero (profilometrics, lettura ogni secondo). UC11 usa lo stesso cluster k3s con le CRD della Gateway API e unaHTTPRoutecreata in anticipo, come farebbe l'infrastruttura. - CI e Terraform: per UC18 un runner
alpinecon il client (profilotools) esegue lo script della GitHub Action e le righe del template GitLab come farebbero le due CI; per UC19 l'immaginehashicorp/terraform(profiloterraform) usa il provider compilato daintegrations/terraformcondev_overrides, senza registry. - Windows: per UC20 un host Windows emulato (
test/usecases/windows, profilowindows) con PowerShell 7 vero, OpenSSH, un endpoint WinRM su HTTPS che esegue i comandi con PowerShell, e moduli con i cmdlet di IIS e dei servizi usati dal plugin, su una configurazione in JSON da cui un server web serve le applicazioni IIS. Gli script del plugin girano come su Windows Server; IIS e il gestore dei servizi sono imitati. - Windows, secondo host: per UC21 un secondo host emulato (
win2) e, suwin1, i cmdlet delle attività pianificate e di NLB, unmsiexec.exee la parte ARR diMicrosoft.Web.Administration(compilata da C# nell'immagine, sotto$env:windircome su Windows), tutto su uno stato in JSON. Il PowerShell dei pool gira senza modifiche. - Azure: per UC22 il servizio
fakesserve anche, sulla porta 9400, Azure emulato a livello di protocollo (internal/azurefake, usato anche dai test dei plugin): token di Entra ID con il service principal del laboratorio, Resource Manager per App Service e Container Apps con le operazioni asincrone, Kudu, le web app e le container app stesse (servono la versione rilasciata) e l'API di query di Log Analytics. Le immagini di Container Apps vengono dal registry del laboratorio. - Servizi esterni: Slack, Teams, ServiceNow, Datadog, New Relic, Elastic e CloudWatch non hanno una versione locale (o non abbastanza leggera), quindi
test/usecases/fakesne emula le API (più un endpoint HTTP per gli step personalizzati; per i sistemi di metriche le serie da restituire si impostano conPOST /control/metrics, e CloudWatch parla il protocollo CBOR dell'SDK AWS con firma SigV4) e registra ogni richiesta, che i casi rileggono. UC14 usa un WildFly vero con l'API HTTP di management.
I casi#
| Caso | Scenario | Cosa verifica |
|---|---|---|
| UC1 | Deploy pianificato (schedule.at) su un target in una AZ, poi su tre AZ di una region |
Attesa fino all'orario, annullamento, idempotenza, release difettosa con rollback automatico, fallimento senza rollback e ripristino, rollback manuale, checksum errato, dry-run, target spento |
| UC2 | Configurazione di ambiente e promozione | Autenticazione e permessi, conflitti con la configurazione di ambiente, promozione test → prod con approvazione, divieto di auto-approvazione, rifiuto, freeze e deroga |
| UC3 | Più worker sullo stesso database | Un deploy alla volta per ambiente e ambienti in parallelo, worker terminato durante un deploy (recovery automatica dall'altro), arresto ordinato che attende il deploy in corso |
| UC4 | Rollout a ondate in una region | Canary e approvazione, un'AZ alla volta con bake e health gate, gate rosso sul canary, guasto nell'ultima AZ con rollback-all, rifiuto, onFailure: stop con drift nell'inventario |
| UC5 | Rollout a ondate multi-region | Passi e sonde dell'health gate eseguiti dai worker di region, region in parallelo, rollback-wave, worker di region spento (il passo lo attende), freeze di una sola region |
| UC6 | Container su VM con Docker (plugin container) | Immagine fissata al digest, promozione test → prod a ondate, dry-run, immagine unhealthy con rollback-all, rollback manuale, VM spenta, tag spostato nel registry (la promozione rifiuta l'immagine diversa) |
| UC7 | Manifest su Kubernetes (plugin kubernetes) | Dry-run lato server, stessi manifest in test e prod con configurazione di ambiente diversa, rollout bloccato e rollback, prune degli oggetti rimossi, storico delle release, immagine non fissata al digest, rollback manuale |
| UC8 | Interfaccia web | Intestazioni e configurazione, whoami, approvazioni, cursore e stream SSE dell'API; con Playwright: login OIDC, ogni vista verificata con axe in entrambi i temi, dettaglio con log in tempo reale, promozione dopo un dry-run, divieto di auto-approvazione, approvazione da un altro utente seguita in tempo reale, tastiera e italiano |
| UC9 | Catene di deploy | Deploy che attende un altro (after) senza toccare il proprio target, avvio dopo la fine della dipendenza, dipendenza fallita (DEPENDENCY_FAILED) e rifiuto all'invio, release composta in ordine topologico con reinvio idempotente, release fallita con onFailure: rollback (deploy riusciti riportati alla versione precedente) |
| UC10 | Load balancer e router su HAProxy | Ogni lotto svuotato dal backend durante il suo deploy e rientrato solo una volta sano, attese durevoli, controllo di capacità (min_healthy) che rimanda lo svuotamento mentre un altro server è guasto, nessuna richiesta fallita attraverso HAProxy durante il rilascio; router con due slot di target: primo rilascio con l'approvazione della governance spostata nel rollout (approvals.when: rollout), gradino del 50% con pesi uguali ed entrambe le versioni servite, release difettosa con i pesi tornati allo slot stabile |
| UC11 | Rilascio per percentuale su Kubernetes con un chart Helm | Candidata nello slot libero (uc11-web-a/-b) con l'etichetta dello slot, approvazione dopo i test a peso 0, regola dei tester dell'HTTPRoute, gradini sui pesi della regola principale, storico che indica lo slot, slot sostituito a zero repliche dopo scaleDownDelay, release difettosa che non diventa mai pronta con il traffico rimasto sullo slot stabile |
| UC12 | Step personalizzati | Piano con tipo e spec_hash ma senza comando né URL, dry-run con il solo comando e i soli segreti di dry_run, comando via SSH su un target con parametro, file del segreto e output dichiarato, segreto redatto nel log del deploy, richiesta HTTP con bearer, Idempotency-Key e corpo JSON costruito da manent, polling del job, exit 75 ritentato, errore permanente senza tentativi e target non toccati |
| UC13 | Chat e change management | Messaggio Slack all'avvio aggiornato con l'esito, richiesta di approvazione con i pulsanti su Slack, change aperto in ServiceNow con il deploy come correlation_id, clic di chi ha chiesto il deploy rifiutato (auto-approvazione), clic del CAB accettato (richiesta firmata come Slack), firma non valida rifiutata, deploy avviato solo dopo l'approvazione del change e change chiuso con l'esito, change respinto in ServiceNow, webhook di Teams rifiutato perché non https |
| UC14 | WildFly (plugin jboss) | Dry-run, un deployment per versione con lo stesso runtime-name, cambio con il server sospeso, war rifiutato da WildFly con la versione in esercizio intatta, post-test rosso con rollback automatico, rollback manuale senza ricaricare, parametro in conflitto con la configurazione di ambiente rifiutato, restart della JVM, keep_releases |
| UC15 | Analisi canary su Prometheus | Carico dall'host attraverso HAProxy, metriche di HAProxy per server (= slot) in un Prometheus vero, confronto di Mann-Whitney tra le serie di candidata e stabile a ogni lettura del gradino del 50%, primo rilascio senza confronti, release sana promossa con punteggio 100, release difettosa (una pagina mancante) respinta dalla metrica critica sulla quota di 4xx con il p-value nel gate e il traffico tornato allo slot stabile |
| UC16 | Analisi canary su Datadog, New Relic, Elastic e CloudWatch | Una metrica per sistema, ciascuna letta con la sua API e le sue credenziali (chiavi Datadog, chiave NerdGraph e NRQL TIMESERIES, ES|QL con API key, GetMetricData firmata con SigV4), serie equivalenti promosse con punteggio 100, due metriche non critiche peggiori: punteggio 50, sufficiente durante il bake (marginale) ma rosso alla fine, metrica critica di CloudWatch peggiore: rollout fermato prima della fine del bake, traffico tornato allo slot stabile |
| UC17 | Stato reale dei target | Riconciliazione ogni 10 secondi (reconcile nella configurazione del laboratorio): target letti uguali all'inventario con region e AZ, rollback a mano del symlink current su un target segnalato come drifted con la versione trovata, out_of_sync e manent inventory --out-of-sync, log del worker e metrica manent_targets, release rimossa (missing), target spento (unknown con l'errore), ritorno in linea senza un nuovo deploy, lettura del deploy precedente ignorata dopo un nuovo rilascio |
| UC18 | CI: GitHub Action e template GitLab | Script dell'action con le variabili di un runner (INPUT_*, GITHUB_OUTPUT, GITHUB_STEP_SUMMARY): deploy con versione e sorgente dalla pipeline, output e riepilogo del job, job rieseguito con lo stesso id senza nuove esecuzioni, dry-run, release difettosa con lo step fallito, state=ERROR e rollback nel riepilogo, validate, env-validate di una configurazione valida e di una con un campo sconosciuto; righe del job GitLab con gli input sostituiti e una variabile della pipeline nell'id, report dotenv |
| UC19 | Terraform (provider manent) | terraform apply della prima release e della successiva, plan vuoto con la stessa versione, rollback fatto con la CLI che compare nel plan e viene annullato dall'apply, target cambiato a mano rilasciato di nuovo con redeploy_out_of_sync, configurazione di ambiente candidata verificata nel plan (valida con i target, non valida con il plan fallito), release difettosa con l'apply fallito e il rollback automatico, destroy che lascia la release in esercizio |
| UC20 | Windows (plugin windows) | Applicazione IIS via WinRM (HTTPS, basic): dry-run, deploy con l'application pool fermato e riavviato, physicalPath sulla release e applicazione servita, release senza healthz con post-test rosso e rollback, physicalPath cambiato a mano segnalato dalla riconciliazione, rollback manuale senza nuova copia, keep_releases; servizio Windows via OpenSSH: binPath con gli argomenti sull'eseguibile della release, eseguibile che non parte con il deploy fallito e il servizio tornato sulla release precedente |
| UC21 | Windows: ARR, NLB, MSI, attività pianificate (plugin windows) | Applicazione IIS su due host dietro una web farm ARR: un server alla volta in Drain e di nuovo Available, step di svuotamento, deploy e rientro per lotto; pacchetto MSI sui nodi di un cluster NLB: un nodo alla volta svuotato e fermato, proprietà pubbliche a msiexec, MajorUpgrade, release letta dal prodotto installato, pacchetto fallito (1603) con l'host intatto, rollback manuale con downgrade dopo la disinstallazione; attività pianificata: azione sull'eseguibile della release con argomenti e cartella di lavoro, password memorizzata dell'account, run_now, istanza in esecuzione che blocca il deploy e stop_task |
| UC22 | Azure (appservice, containerapps, azure-monitor) |
App Service con slot di staging: pacchetto via Kudu, salute dello slot, swap, versione che passa con lo swap, release difettosa che non arriva mai in produzione, swap fatto a mano segnalato dalla riconciliazione, rollback manuale con un nuovo swap; senza slot: produzione e tag; rilascio per percentuale: 30% del traffico allo slot (rampUpRules) con il gate su Log Analytics (KQL), swap, tasso di errori oltre la soglia con la produzione intatta; Container Apps: revisione per deploy con l'immagine fissata al digest, revisione non sana con il traffico di nuovo sulla precedente |
| UC23 | Governance in Rego (Policy in Rego) | Laboratorio su governance.engine: opa (ogni caso gira sulla policy predefinita, che applica le regole di manent.yml); revisione caricata da un autore senza policy.approve, test eseguiti al caricamento, impatto sui deploy reali, auto-approvazione rifiutata (quattro occhi), approvazione da un admin senza riavvii e revisione nelle metriche; approvatori limitati al CAB con l'admin rifiutato, downgrade negato e mostrato da validate; freeze creato a runtime tra due ondate con il rollout in pausa e ripreso dopo l'eliminazione; freeze hard che blocca la deroga, eliminazione rifiutata senza freeze.write e break-glass con manent-server admin; ritorno alla policy predefinita |
| UC24 | Tenant | Team proprietari di applicazioni con il registro in tenants.yml; payments e retail hanno entrambi uc24-shop: senza tenant il deploy è rifiutato come ambiguo, con --tenant ogni release legge la configurazione del suo tenant ({tenant} nel template) e ha la sua voce di inventario; un'applicazione che nessun tenant elenca rilasciata come untenanted; la coda dedicata e le restrizioni di un tenant; ID dei deploy, after e release tra tenant rifiutati; il ruolo implicito dei membri, rifiutato sull'omonimo di retail indicato per errore e sulle applicazioni untenanted; i tag delle applicazioni nei selettori e un freeze a runtime su */uc24-*; admin move-application (dry-run); una policy Rego che legge i tag del tenant; ritorno alla policy predefinita |
| UC25 | Permessi come policy | Laboratorio su rbac.engine: opa (tutti i casi girano sulla policy predefinita, che applica i binding di manent.yml); utenti OIDC con il claim employee_type; una revisione che dà a un team la sua applicazione e le applicazioni dei tenant con il tag owner=<team>, turni di reperibilità con until e il divieto della produzione ai contractor, simulata con policy access (permessi guadagnati rispetto alla revisione attiva, divieto con il motivo, tenant propri e altrui) prima che una seconda persona la approvi; deploy reali consentiti e negati con motivo e revisione, audit con il permesso usato; un deploy pianificato annullato dall'identità di turno e non da quella con il turno scaduto; una revisione che toglie tutti i permessi corretta da chi ha policy.* statico; ritorno alla policy predefinita |
| UC26 | Export e import | Un deploy dell'infrastruttura il cui step personalizzato "crea" due VM ed esporta i loro host (format: targets) e la directory delle release; il deploy della configurazione li importa come target e dest_dir con after, con --import dal CLI e in una release composta che nomina il produttore; un export fuori dal suo pattern che fa fallire il produttore e lascia chi dipende in DEPENDENCY_FAILED; un export inesistente rifiutato all'invio; un ambiente che prende target e parametri dalla release in esercizio (targets_from, params_from) e rifiuta i target del descrittore; una promozione che toglie l'import del deploy di origine |
| UC27 | Repository Git | Un secondo server manent legge la configurazione di ambiente dal server Git del laboratorio (smart HTTP con un token, commit firmati con SSH): ogni deploy registra lo SHA del commit; un nuovo commit su main arriva al deploy successivo; un commit non firmato è rifiutato all'invio; --config-ref rilascia con la configurazione di un tag, un branch fuori da allowed_refs e uno SHA non raggiungibile sono rifiutati; il rollback manuale torna allo SHA registrato dal deploy che ripristina, non alla testa di main |
| UC28 | terraform | Il plugin Terraform esterno, con OpenTofu nell'immagine del worker, rilascia un modulo con risorse terraform_data in due stati (uno per region) del backend pg sul Postgres del laboratorio: il dry-run mostra le modifiche previste e non scrive stati; il deploy applica in entrambe le region ed esporta le VM come target, su cui uc28-app viene rilasciata dalla configurazione di ambiente (import da current + targets_from); la stessa versione di nuovo non modifica nulla; una versione che distrugge una VM viene rifiutata da max_destroy con un solo piano (errore permanente, nessun retry) e l'inventario resta sulla release precedente |
| UC29 | ansible | Una release composta concatena infrastruttura, configurazione e applicazione: il plugin Terraform esporta due VM con le loro etichette, il plugin Ansible le configura sulla sua coda, servita solo da worker-ansible (ansible-core di Debian; i worker distroless conoscono il plugin solo come stub), con un inventario raggruppato per etichetta, ed esporta la directory dell'applicazione con set_stats; remote-copy vi rilascia l'applicazione (targetsFrom + paramsFrom). Il marcatore della release viene scritto sui target, la coda del plugin compare con tipo plugin, il check mode non modifica nulla e un host che fallisce nel playbook fa fallire il deploy con il riepilogo per host |
| UC30 | Target in parallelo | Tre host SSH in un lotto con parallel: 100%, limitato a 2 da plugins.remote-copy.max_parallel: i log del post_command mostrano due host aggiornati insieme e il terzo che parte quando si libera un posto, lo step deploy riporta parallel 2 e parallel_requested 3. Senza parallel gli host vanno uno dopo l'altro; un descrittore può chiederlo entro descriptor_policy.max_parallel (4 nel laboratorio, 422 oltre) e non può cambiare quello dell'ambiente. Con un host guasto quello già avviato finisce e il terzo non parte mai. In un rilascio a ondate l'ondata canary resta a parallel: 1 e il lotto successivo di due host è aggiornato insieme |
| UC31 | Più provider | Tre provider file in envconfig.providers: uc31-legacy serve le applicazioni uc31-* con on_missing: next, quindi test viene letto lì (lo stesso ambiente nel provider default non viene mai letto) mentre prod, che non c'è più, ripiega su default; lo step config riporta provider e skipped. Il tenant uc31team indica uc31-team (tenant_only) nel registro: i suoi deploy leggono quel provider, e un ambiente che lì manca non ripiega sugli altri (422, nessun target) |
Ogni verifica stampa ✔ o ✘. Alla fine lo script riassume i fallimenti e salva i log dei container in test/usecases/.work/compose.log.
Aggiungere un caso#
Un caso è uno script in test/usecases/cases/ (ucN.sh), eseguito in ordine numerico sullo stesso laboratorio. Le funzioni di lib.sh creano artefatti e descrittori, eseguono il client con un ruolo (as cab manent approve <id>), attendono gli stati (wait_state, wait_rollout), leggono i target (current, healthy) e iniettano guasti (fault). Ogni caso usa una propria applicazione, così non dipende dagli altri.