
# Applica un piano di configurazione

`POST /v1/config-plans/{plan_id}:apply` · autorizzazione `config:write`, le autorizzazioni di lettura del piano e l'autorizzazione di scrittura di ogni tipo che modifica

Applica un [piano](create.md) salvato all'ambiente. Ogni modifica viene scritta in un'unica transazione: se una fallisce, non viene mantenuta nessuna. Un piano può essere applicato una sola volta.

## Richiesta

| Parametro | Descrizione |
|---|---|
| `plan_id` | L'id del piano, `cfgp_…`. |

Il body è facoltativo.

| Campo | Descrizione |
|---|---|
| `resolved_secrets` | Oggetto di `secret_ref` nome → valore per ogni modifica con `requires_secret: true` (ad esempio `KEENAUTH_CONNECTION_GITHUB_CLIENT_SECRET`). I valori sono al massimo 8 KiB. Non vengono mai salvati dal piano, registrati nel log né restituiti. |
| `confirm_deletions` | Elenco di eliminazioni `<kind>/<key>` che confermi, ad esempio `connection/legacy`. Gli avvisi `deletion_requires_confirmation` del piano elencano le eliminazioni che lo richiedono. |
| `sp_host` | L'host del service provider dell'ambiente, ad esempio `login.example.com`. Richiesto solo quando il piano crea il materiale OIDC di una connessione enterprise (il callback `https://<sp_host>/sso/<connection id>/callback` viene fissato a partire da esso); una richiesta che non lo richiede non deve inviarlo. |

- Le autorizzazioni di scrittura sono `environment-settings:write`, `identity-settings:write`, `attack-protection:write`, `branding:write` (per `branding` e `application_branding`), `connections:write`, `apis:write`, `roles:write` e `applications:write`, solo per i tipi con una modifica diversa da `no_op`.
- Prima di scrivere, il target viene letto di nuovo. Se la versione di una risorsa pianificata o il contenuto dei tipi del piano sono cambiati dopo la creazione del piano, non viene scritto nulla, il piano diventa `conflicted` e la richiesta fallisce con `409 config_conflict`. Crea un nuovo piano. Con `prune`, vengono confrontati anche i tipi che fanno riferimento alle risorse pianificate e i riferimenti tra risorse vengono controllati di nuovo; anche un riferimento interrotto rende il piano `conflicted`.
- L'eliminazione di una connessione, o di un'applicazione che ha ricevuto un token negli ultimi 30 giorni, deve essere ripetuta in `confirm_deletions`. Altrimenti la richiesta fallisce con `409 deletion_not_confirmed` e il piano resta `planned`.
- Il materiale OIDC di una connessione enterprise (`spec.oidc`) e il suo client secret vengono scritti nel servizio di federazione dopo il record della connessione. Se questa seconda scrittura viene rifiutata, il piano resta `planned`, il record della connessione potrebbe già esistere e un nuovo piano scrive solo il materiale mancante. Un `spec.oidc` di `null` lascia il materiale invariato. Il client secret di una connessione social non può essere ancora scritto: un piano con una modifica `requires_secret` di questo tipo restituisce `501 not_implemented` dopo il controllo dei segreti. Non viene scritto nulla.
- `Idempotency-Key` è supportato. Quando possibile, invialo senza segreti nel body: il record della chiave conserva un hash del body.

## Risposta

`200 OK` con `Cache-Control: no-store`: il piano nella forma di [Ottieni un piano di configurazione](get.md) con `status: applied` e `applied_at`.

La configurazione delle risorse interessate prima della modifica viene conservata come revisione (le 50 più recenti per ambiente, per al massimo 90 giorni). L'applicazione scrive una voce `config.applied` nel log di gestione dell'ambiente con i conteggi di risorse create, aggiornate, eliminate e invariate; ogni risorsa modificata scrive anche la propria voce.

## Errori

| HTTP | codice | Quando |
|---|---|---|
| 400 | `malformed_request`, `unknown_field` | Il body non è JSON oppure ha un campo diverso da `resolved_secrets`, `confirm_deletions` e `sp_host`. |
| 401 | `unauthenticated` | La credenziale è mancante, non valida o revocata. |
| 403 | `insufficient_permissions` | La credenziale non ha un'autorizzazione richiesta; `required_permissions` le elenca tutte. |
| 404 | `not_found` | Il piano non esiste, appartiene a un altro ambiente o è stato eliminato. |
| 409 | `config_conflict` | Il target è cambiato dopo la creazione del piano; il piano ora è `conflicted`. |
| 409 | `config_plan_not_applicable` | Il piano era già stato applicato oppure è `conflicted`, oppure un'altra richiesta lo sta applicando ora (`plan_status: applying`); `plan_status` ne indica lo stato. |
| 409 | `deletion_not_confirmed` | Manca un'eliminazione protetta da `confirm_deletions`; `unconfirmed_deletions` le elenca. |
| 409 | `conflict` | Un'altra risorsa usa già un valore univoco di una modifica (`errors[].pointer` è `/changes/<i>/spec/<field>`, codice `duplicate`); il piano resta `planned`. |
| 409 | `connection_in_use` | La connessione da eliminare è ancora in uso; il piano resta `planned`. |
| 409 | `connection_test_required` | Una modifica crea una connessione `enterprise` `active` o ne attiva una senza un test di accesso riuscito (`errors[].pointer` è `/changes/<i>/spec`, codice `test_required`); il piano resta `planned`. |
| 410 | `config_plan_expired` | L'ora del piano è scaduta; crea un nuovo piano. |
| 413 | `config_document_too_large` | Le modifiche, o la revisione conservata per esse (al massimo 10 MiB), sono troppo grandi per essere applicate in una sola richiesta; pianifica meno tipi. |
| 422 | `validation_failed` | Vedi sotto. |
| 422 | `policy_conflict` | La modifica è in conflitto con una policy dell'ambiente; il piano resta `planned`. |
| 429 | `rate_limited` | È stato superato il limite di richieste `config_plan` (condiviso con la creazione dei piani), oppure troppi piani vengono calcolati o applicati contemporaneamente per questo ambiente; il piano resta `planned`; riprova dopo il periodo `Retry-After`. |
| 501 | `not_implemented` | Il piano richiede il client secret di una connessione social, che non può ancora essere scritto. |
| 503 | `service_unavailable` | Una dipendenza (la directory o, per il materiale OIDC enterprise, il servizio di federazione) è temporaneamente non disponibile oppure la capacità per i piani è temporaneamente esaurita; non è stato scritto nulla oppure, per il servizio di federazione, è stato scritto solo il record della connessione. Riprova dopo il periodo `Retry-After`. |

| `errors[].pointer` | `errors[].code` | Significato |
|---|---|---|
| `/resolved_secrets/<NAME>` | `secret_missing` | Il piano richiede questo segreto. |
| `/resolved_secrets/<NAME>` | `secret_unused` | Il piano non richiede questo segreto. |
| `/resolved_secrets/<NAME>` | `secret_invalid` | Il valore è vuoto, più lungo di 8 KiB o non è testo. |
| `/resolved_secrets/<NAME>` | `invalid` | Il nome non ha la forma `KEENAUTH_…` (`A-Z`, `0-9`, `_`). |
| `/sp_host` | `sp_host_required`, `sp_host_unused`, `invalid` | Il piano crea materiale OIDC e non è stato fornito alcun host, non serve alcun host, oppure l'host non è un nome DNS in minuscolo. |
| `/changes/<i>/spec/oidc/...` | come nell'API OIDC della connessione | Il servizio di federazione ha rifiutato il materiale in questa posizione di `changes` del piano. |
| `/confirm_deletions/<i>` | `duplicate`, `not_a_deletion` | La voce è ripetuta o non è un'eliminazione nel piano. |
| `/confirm_deletions/<i>` | `invalid` | La voce non ha la forma `<kind>/<key>`. |
| `/resolved_secrets`, `/confirm_deletions` | `too_many` | Più di 5.000 voci. |
| `/changes/<i>/spec/...` | come nell'API della risorsa | L'ambiente ha rifiutato la modifica in questa posizione di `changes` del piano. |

## Esempio

```bash
curl -X POST "https://api.eu.keenauth.com/v1/config-plans/cfgp_01m4qpqtywfs7930tb6kqn5rc5:apply" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"confirm_deletions": ["connection/legacy"]}'

keenauth config apply cfgp_01m4qpqtywfs7930tb6kqn5rc5 --confirm-deletion connection/legacy
```

`keenauth config apply --secrets-from-env` legge ogni segreto richiesto dal piano dalla variabile d'ambiente con lo stesso nome e non lo stampa mai.
