# Change-Anbindung für Bau-Pipelines

Diese Vorlagen legen aus Ihrer Pipeline einen Change in knooing an und melden danach das Ergebnis des Rollouts. Sie nutzen die Schnittstelle `POST /api/pipeline/changes` und `POST /api/pipeline/changes/:id/status`.

| Datei | Zweck |
|---|---|
| `gitlab-ci-template.yml` | Include-fähige Vorlage mit den Jobs `knooing-change-open` und `knooing-change-status` (optional `knooing-change-status-failed`) |
| `github-action/action.yml` | GitHub Composite Action mit `mode: open` und `mode: status` |

## Voraussetzungen

1. Ein API-Schlüssel mit dem Scope `changes:write`. Sie legen ihn in knooing unter den Schnittstellen-Schlüsseln an.
2. Die ID des Services, für den der Change gilt.
3. Der Schlüssel wird nur als geheime Variable hinterlegt:
   - GitLab: Einstellungen, CI/CD, Variablen, `KNOOING_API_KEY`, mit "Mask variable" und "Protect variable".
   - GitHub: Repository-Secret `KNOOING_API_KEY`.
   Tragen Sie den Schlüssel niemals in eine Datei im Repository ein.

## GitLab CI

```yaml
include:
  - local: gitlab-ci-template.yml

variables:
  KNOOING_SERVICE_ID: "<Service-ID>"
```

Die Jobs im Überblick:

| Job | Stufe | Läuft, wenn | Meldet |
|---|---|---|---|
| `knooing-change-open` | `.pre` (vor allen anderen Jobs) | Standard-Branch oder geschützter Branch/Tag | legt den Change an, gibt die Change-ID als `dotenv`-Artefakt weiter |
| `knooing-change-status` | `.post` (nach allen anderen Jobs) | alle früheren Jobs waren erfolgreich | `successful` |
| `knooing-change-status-failed` | `.post` | mindestens ein früherer Job ist fehlgeschlagen | `failed` |

Alle drei Jobs laufen nur, wenn `$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH` oder `$CI_COMMIT_REF_PROTECTED == "true"` gilt. Feature-Branches legen also keine Changes an. Wollen Sie das ändern, überschreiben Sie `rules:` im jeweiligen Job in Ihrer `.gitlab-ci.yml`.

**Warum Stufe `.post`?** Ein Job ohne `needs` startet erst, wenn alle Jobs der früheren Stufen beendet sind, und `.post` ist immer die letzte Stufe. Der Statusjob meldet deshalb erst nach Ihrem Deployment. Mit `when: on_failure` läuft ein Job laut GitLab-Dokumentation nur, wenn mindestens ein Job einer früheren Stufe fehlgeschlagen ist, mit `on_success` (Standard) nur, wenn alle früheren Jobs erfolgreich waren. Ist schon `knooing-change-open` fehlgeschlagen, gibt es keine Change-ID, und beide Statusjobs beenden sich ohne Meldung.

**Grenzen der Standardvorlage.** Die Statusjobs werten alle früheren Jobs aus, nicht nur Ihren Deploy-Job:

- Schlägt ein Job nach dem Deployment fehl (zum Beispiel ein späterer Test oder Cleanup), meldet `knooing-change-status-failed` den Change als `failed`, obwohl der Rollout selbst gelungen sein kann.
- Ist der Deploy-Job manuell (`when: manual`) oder mit `allow_failure: true` konfiguriert, zählt GitLab ihn bei einem Fehlschlag oder einer Auslassung nicht als Fehler. `knooing-change-status` meldet dann gegebenenfalls fälschlich `successful`, und ein Fehlschlag löst kein `failed` aus. Verwenden Sie in diesem Fall die `needs`-Variante unten.

**Statusmeldung direkt am Deploy-Job (optional).** Wenn Sie die Meldung an einen bestimmten Deploy-Job binden wollen, tragen Sie diesen in `needs` ein. Der Open-Job muss dann ebenfalls in `needs` stehen, damit die Change-ID ankommt:

```yaml
knooing-change-status:
  needs: [knooing-change-open, deploy-production]

knooing-change-status-failed:
  needs:
    - job: knooing-change-open
      optional: true
    - deploy-production
```

Bei Jobs mit `needs` hängt der Start allein von den dort genannten Jobs ab. Die genaue Auswertung von `when: on_failure` zusammen mit `needs` beschreibt die GitLab-Dokumentation nicht eindeutig. Prüfen Sie diese Variante deshalb einmal in Ihrer Instanz, indem Sie den Deploy-Job absichtlich fehlschlagen lassen. Die Standardvorlage ohne `needs` stützt sich nur auf die dokumentierte Stufen-Semantik.

## GitHub Actions

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - id: change
        uses: ./.github/actions/knooing-change
        with:
          mode: open
          api-key: ${{ secrets.KNOOING_API_KEY }}
          service-id: "<Service-ID>"
      # ... Ihr Deployment ...
      - if: always()
        uses: ./.github/actions/knooing-change
        with:
          mode: status
          api-key: ${{ secrets.KNOOING_API_KEY }}
          change-id: ${{ steps.change.outputs.change-id }}
          outcome: ${{ job.status == 'success' && 'successful' || 'failed' }}
```

Auf den Runnern müssen `curl` und `jq` vorhanden sein, bei GitHub-gehosteten Runnern sind sie es.

## Felder

Anlage (`mode: open`, Job `knooing-change-open`):

| Feld in der API | GitLab-Variable | GitHub-Eingabe | Pflicht | Standard |
|---|---|---|---|---|
| `serviceId` | `KNOOING_SERVICE_ID` | `service-id` | ja | keiner |
| `title` | `KNOOING_CHANGE_TITLE` | `title` | ja | Projekt und Commit |
| `description` | `KNOOING_CHANGE_DESCRIPTION` | `description` | ja | Verweis auf den Pipeline-Lauf |
| `impact` | `KNOOING_IMPACT` | `impact` | ja, 1 bis 5 | 2 |
| `urgency` | `KNOOING_URGENCY` | `urgency` | ja, 1 bis 5 | 2 |
| `changeClass` | `KNOOING_CHANGE_CLASS` | `change-class` | ja: `standard`, `normal`, `emergency` | `standard` |
| `rollbackPlan` | `KNOOING_ROLLBACK_PLAN` | `rollback-plan` | nein | leer |
| `externalKey` | `KNOOING_EXTERNAL_KEY` | `external-key` | nein | Projekt und Pipeline-Lauf |

Statusmeldung (`mode: status`, Job `knooing-change-status`):

| Feld in der API | GitLab-Variable | GitHub-Eingabe | Pflicht | Standard |
|---|---|---|---|---|
| `outcome` | `KNOOING_OUTCOME` | `outcome` | ja: `successful`, `partial`, `failed`, `rolled_back` | `successful` |
| `note` | `KNOOING_NOTE` | `note` | nein | Verweis auf den Pipeline-Lauf |

Die Basis-Adresse steht in `KNOOING_API_URL` (GitHub: `api-url`), Standard ist `https://api.knooing.info`.

## Wichtig zu wissen

- Der Schlüssel wird im Header `X-API-Key` gesendet. Die Skripte geben ihn nie aus.
- Mit `externalKey` legt ein wiederholter Lauf derselben Pipeline keinen zweiten Change an. Die Antwort lautet dann HTTP 200 mit der ID des bestehenden Change-Datensatzes (`changeRecordId`), sofern ihn derselbe Schlüssel angelegt hat. Beide Vorlagen verwenden diese ID, die Statusmeldung funktioniert also auch beim wiederholten Lauf. Gehört der Datensatz einem anderen Schlüssel, bleibt die ID leer, und der Status-Schritt überspringt die Meldung.
- Ein Schlüssel kann den Status nur für Changes melden, die er selbst angelegt hat. Sonst antwortet die API mit 404.
- Die Statusmeldung gilt als Umsetzungsnachweis. Für `standard` und `normal` verlangt knooing dafür eine bereits erteilte Freigabe, sonst antwortet die API mit 409. Auch eine aktive Change-Freeze-Periode kann mit 409 abweisen. Nur `emergency` darf zuerst umgesetzt und danach freigegeben werden.
- Fehler erscheinen im Job-Protokoll als HTTP-Status mit der Meldung und dem Fehlercode der API, etwa `401` bei ungültigem Schlüssel oder `400 validation` bei einem ungültigen Feld.
