Zum Inhalt

Neue Funktionalität bauen

Neue Funktionalität ist eine Änderung im Code-Repository. Eine Änderung, die nur andere Werte braucht, ist eine Konfigurationsänderung; siehe Konfiguration. Jedes neue Feature folgt den Regeln zur Abwärtskompatibilität: opt-in, standardmäßig aus.

flowchart LR
    a["1. Module"] --> b["2. Wire into a layer<br/>+ feature flag"]
    b --> c["3. Test"]
    c --> d["4. Deploy to dev<br/>(local / Demo canary)"]
    d --> e["5. PR + CI"]
    e --> f["Release"]

0. Deine Umgebung einrichten

  1. Klone xms-cloud-platform-code und öffne es im Dev Container (VS Code: Reopen in Container). Er enthält PowerShell 7, Terraform, die Azure CLI und Pester.
  2. Der Dev Container mountet ../config, relativ zum Code-Repository, nach /workspaces/config. Klone ein Konfigurations- Repository als config neben den Code oder nutze die Demo-Konfiguration in config/Demo/canary.
  3. Kopiere .env.example nach .env und setze MCP_CONFIG, zum Beispiel MCP_CONFIG=/workspaces/xms-cloud-platform-code/config/Demo/canary/mcp.json.
  4. Installiere die Git-Hooks: lefthook install.
  5. Branche von main ab. feature/<name> erhält ein GitVersion-Label (-<name>); users/<initials>/<name> ist die übliche Konvention für persönliche Arbeit.

1. Module

Terraform ist in drei Ebenen organisiert:

Ebene Ordner Regel
Module infra/modules/<name>/ Ein Ressourcentyp oder eine enge Gruppe. Kein Wissen über Layer oder Config-Dateien. Dateien: main.tf, variables.tf, output.tf/outputs.tf, versions.tf, optional tests/.
Template infra/templates/<name>/ Setzt Module zu einem Feature zusammen (zum Beispiel application_gateway, virtual_hub). Berechnet Namen aus den scaffold_*-Variablen.
Layer infra/deploy/<layer>/ Root Module mit Backend, Providern und Variablen. Ruft Templates hinter Feature Flags auf. Variablen sind nach Feature aufgeteilt: variables_<feature>.tf.

Richtlinien (siehe auch .agents/skills/terraform-style-guide im Code-Repository):

  • Führe terraform fmt aus. Nutze create_* / use_*_module-Booleans für optionale Features und *_id-Inputs für Abhängigkeiten.
  • Nutze dynamic-Blöcke für optionale verschachtelte Konfiguration.
  • Bilde Namen aus scaffold_company_short_name, scaffold_environment und scaffold_location_short_name und füge eine overwrite_*_name-Variable hinzu, damit Kunden mit bestehenden Namen diese behalten können.
  • Wenn das Feature JSON aus dem Konfigurations-Repository liest, nimm eine *_relative_path-Variable und bilde den Pfad als "${var.config_path}/${var.<x>_relative_path}". Denk daran, dass fileset() bei einem falschen Pfad nichts zurückgibt.
  • Pinne Provider in Root Modules auf eine exakte Version. Sonst schlägt der Pin-Check fehl.

Spoke-Features

Das spoke-Terraform liegt in src/Xebia.Mcp/Files/terraform/spoke-canvas/ (deploy/subscription, deploy/spoke, modules/). Eine neue spoke-Eigenschaft braucht:

  1. das Terraform, das sie liest, mit einem Standardwert für den Fall, dass die Eigenschaft fehlt;
  2. eine optionale Eigenschaft in src/Xebia.Mcp/Files/json/spokeschema.json;
  3. eine Zeile in der spoke-JSON-Referenz.

2. In einen Layer einbinden, hinter einem Flag

# infra/deploy/connectivity/variables_my_feature.tf
variable "use_my_feature_module" {
  description = "Deploy my feature. Default false keeps existing deployments unchanged."
  type        = bool
  default     = false
}

# infra/deploy/connectivity/main.tf
module "my_feature" {
  source = "../../templates/my_feature"
  count  = var.use_my_feature_module ? 1 : 0
  # ...
}

Danach:

  • füge das Flag und seine zugehörigen Variablen zur Demo-Konfiguration hinzu (config/Demo/canary/<region>/<layer>.auto.tfvars);
  • füge das Flag zur Feature-Flag-Referenz hinzu;
  • falls Konsumenten oder Operatoren es anfragen werden, ergänze oder erweitere ein Runbook unter Häufige Anfragen;
  • falls die Entscheidung architektonisch ist, schreibe einen ADR (docs/adr/, Vorlage adr-template.md).

Änderungen an der Orchestrierung

Logik, die entscheidet, was läuft (if/else, Schleifen, Matrizen, Parsing), gehört in eine PowerShell-Funktion in src/Xebia.Mcp/Functions/, nicht in YAML (ADR-0011, CONTRIBUTING.md):

  • Öffentliche Funktionen in Functions/Public/<Domain>/, interne in Functions/Private/<Domain>/.
  • Comment-based Help mit .SYNOPSIS, .DESCRIPTION, .PARAMETER, .EXAMPLE, .NOTES.
  • begin/clean-Blöcke, die begin.ps1 / clean.ps1 per Dot-Sourcing einbinden.
  • Ändere das Azure-DevOps-Template und den GitHub-Workflow im selben pull request.

3. Test

Test Wo Ausführen mit Läuft in
Terraform validate + docs jeder Layer automatisch beim Commit lefthook pre-commit
Terraform-Modul-Tests infra/modules/<name>/tests/*.tftest.hcl (mit mock_provider) terraform -chdir=infra/modules/<name> test, oder automatisch für gestagte Module (scripts/Run-TerraformTest.ps1) lefthook pre-commit
Provider-Pin-Check infra/deploy/** pwsh scripts/Test-TerraformRootProviderPins.ps1 lefthook + GitHub CI
PowerShell-Unit-Tests Functions/**/Tests/<Function>.Unit.Tests.ps1 pwsh ./dev/dev.ps1 -Action Test Prepare-Schritt jeder Pipeline
Paritätsprüfungen modulweit (Dateiname = Funktionsname, jede öffentliche Funktion hat einen Unit-Test, …) pwsh ./dev/dev.ps1 -Action Test Prepare-Schritt
Config-Tests src/Xebia.Mcp/Tests/{Config,SpokeConfig,Smoke} pwsh ./dev/dev.ps1 -Action Test (verwendet MCP_CONFIG) Prepare-Schritt jeder Pipeline

Jede öffentliche Funktion braucht einen Unit-Test; sonst schlägt Assert-XebiaPublicFunctionUnitTestParity fehl. Mocke Azure-CLI- und Terraform-Aufrufe.

4. Auf dev deployen

Das Entwicklungsziel ist der Demo-Tenant, Konfiguration config/Demo/canary im Code-Repository.

Lokal (schnelles Feedback, dein eigener Azure-Login):

Import-Module ./src/Xebia.Mcp/Xebia.Mcp.psd1 -Force
Initialize-XebiaDotEnv
Sync-XebiaInfraToMcpModule -Mappings @(@{ Description = 'cloud-platform'; Source = './infra'; Target = 'terraform/cloud-platform/' }) -WhatIf:$false
$config = Resolve-XebiaMcpConfiguration -Path $env:MCP_CONFIG

# Plan one layer (WhatIf on: plan, no apply) — same as dev.ps1 does by default
$global:WhatIfPreference = $true
Deploy-XebiaLocal -Config $config -DeployName connectivity -Region weu

# Plan and apply
$global:WhatIfPreference = $false
Deploy-XebiaLocal -Config $config -DeployName connectivity -Region weu

(dev/dev.ps1 -Action HubDeploy ist die vorgesehene Abkürzung, ist aber auf main defekt, siehe Bekannte Probleme.)

Über Workflow/Pipeline (wie bei Kunden): Führe die Entry Pipelines des Code-Repositorys (hub.yaml, spoke.yaml) von deinem Branch aus gegen die Demo-Konfiguration aus. Nutze mode: Plan oder ApproveModifications.

Prüfe zwei Dinge:

  1. Mit ausgeschaltetem Flag: Der Plan zeigt keine Änderungen. Das belegt die Abwärtskompatibilität.
  2. Mit eingeschaltetem Flag: Der Plan zeigt nur die neuen Ressourcen, und das Apply funktioniert.

5. Pull Request und CI

  1. Committe mit Conventional Commits (feat(connectivity): add …, fix(ps): …), auf US-Englisch (docs/coding-standards/).
  2. Öffne einen pull request nach main. Beschreibe die Änderung und das Flag und verlinke das Work Item.
  3. Setze den Versionssprung in die Merge-Message: +semver: minor für ein Feature, +semver: patch für einen Fix, +semver: major für einen Breaking Change, +semver: none nur für Doku. Siehe Releasing.
  4. CI läuft:

    Plattform Pipeline Was sie macht
    Azure DevOps pull-request.yaml (PR nach main) Baut den Container aus dem PR-Branch und führt dann die hub-Stages (deploy und destroy) und spoke-Stages (deploy und destroy, Glob **/*str*.json) im Plan / WhatIf gegen canary aus
    GitHub ci.yml (PR, der infra/deploy/** berührt) Provider-Pin-Check
  5. Ein Reviewer prüft: Flag standardmäßig aus, moved-Blöcke bei Umbenennungen, Unit-Tests, sowohl ADO als auch GitHub geändert, Doku aktualisiert.

  6. Merge. Wenn der Fix auch in eine unterstützte Version muss, cherry-picke ihn auf den Branch release/vX.Y (siehe Releasing).