Anleitung · 12 Min

Aus dem Repository deployen

Das Repository ist die Quelle einer Archestack-App, so wie Quellcode die Quelle eines Programms ist. Ein Deployment behandelt Archestack als Compiler: Eine Pipeline baut aus den Dateien des Repositorys ein Wegwerf-Archestack, führt die Tests dagegen aus, und nur wenn sie bestehen, plant sie die Änderung gegen die gehostete Umgebung und wendet sie dort an. Die gehostete Umgebung bedient ihre Benutzer dabei die ganze Zeit weiter.

Status: Die Archestack CLI führt jede der folgenden Stufen aus, aus einem Skriptschritt in jeder beliebigen Pipeline. Fertige Pipeline-Tasks für Azure DevOps und GitHub Actions, die sie kapseln, sind in Entwicklung; Was heute läuft sagt, was jede Stufe ausführt.

Dein CI-System

Die Pipeline

merge to main
  build     a throwaway Archestack made from the repository's files
  test      every package's test cases, each in a sandbox
  plan      against tst: artifacts and SQL, with a plan hash
  deploy    tst: schema, then files, then the release
  approve   the plan against acc, then deploy acc
  approve   the plan against prd, then deploy prd

Ein Pull Request führt den Build, die Tests und den Plan gegen die Testumgebung aus, sodass ein Reviewer sieht, was sich ändern würde, bevor etwas gemergt wird. Ein Merge in den Main-Branch führt jede Stufe aus, und Abnahme und Produktion warten jeweils darauf, dass eine Person ihren Plan genehmigt.

1. Build: Archestack als Compiler

Die Pipeline startet auf dem Build-Agent ein leeres Archestack, in Docker aus demselben API-Image, das die Umgebungen ausführen, oder aus einem Laufzeitordner wie dem, mit dem die VS Code-Erweiterung Projekte ausführt, und lädt das Repository hinein: die Tabellen aller Packages in einem Deployment, dann die Artefakte jedes Packages, Abhängigkeiten vor den Packages, die sie brauchen. Hier zeigt sich ein Fehler zuerst, so wie ein Compilerfehler: Ein Verweis auf eine Business Entity, eine Tabelle oder ein Skript, die es nicht gibt, oder ein Name, den schon ein anderes Package verwendet, stoppt das Laden. Das Ergebnis ist eine Umgebung, die allein aus den Dateien entstanden ist, ohne Überbleibsel von irgendjemandes Rechner.

2. Test

Die Testfälle jedes Packages laufen gegen den Build, jeder in einer eigenen Sandbox-Datenbank, und Mails und Benachrichtigungen werden aufgezeichnet statt verschickt. Jedes Skript, das ein Test ausführt, wird zuerst kompiliert, ein Skript, das nicht mehr kompiliert, lässt seinen Test also fehlschlagen. Ein einziger fehlgeschlagener Fall stoppt die Pipeline, und nichts wird deployt.

3. Gegen das Ziel planen

Dieselben Dateien werden gegen die Zielumgebung geplant: welche Artefakte dort angelegt, geändert oder entfernt werden und welche Tabellenänderungen als SQL laufen, mit dem Risiko jeder Operation und den typisierten Skripten, die dadurch nicht mehr kompilieren würden. Der Plan trägt einen Hash. Das Deployment wendet genau den Plan an, der genehmigt wurde: Hat sich das Ziel in der Zwischenzeit geändert, wird es abgelehnt und der Plan neu erstellt.

4. Deployen: Schema, Dateien, Release

  1. Schema. Die Tabellenänderungen laufen als Datenbank-Deployment und stehen wie jedes andere in der Deployment-Historie der Umgebung. Eine Änderung, die Daten verliert, läuft nur, wenn die Pipeline Datenverlust ausdrücklich erlaubt.
  2. Dateien. Die Artefakte werden in einer Transaktion geschrieben, die Benutzer sehen also die ganze neue Version eines Packages oder nichts davon. Ein Deployment berührt nur, was das Package enthält und anlegt, und nie, was jede Umgebung selbst entscheidet.
  3. Release. Sobald die Dateien angekommen sind, veröffentlicht die Pipeline selbst ein Release. Das ist der Moment, in dem die neuen Pages und App Layouts die veröffentlichte App erreichen, und niemand veröffentlicht von Hand. Jedes Deployment ist eine neue Version der App mit einem eigenen Release.

Eine Umgebung, die noch nie ein Release veröffentlicht hat, zeigt ihre Entwürfe, die neuen Dateien wären also in dem Moment sichtbar, in dem sie ankommen. Vor ihrem ersten Deployment in eine solche Umgebung veröffentlicht die Pipeline deshalb ein Release dessen, was die Umgebung bereits enthält, und von da an warten die neuen Dateien als Entwürfe auf ihr eigenes Release, wie bei jedem späteren Deployment.

Warum die Umgebung weiterläuft

  • Nichts startet neu. Ein Deployment sind Daten in den Datenbanken der Umgebung, kein neuer Build des Servers. Die API antwortet die ganze Zeit weiter, und ein geändertes Skript wird kompiliert, wenn es zum ersten Mal läuft.
  • Dateien kommen auf einmal an. Eine Transaktion pro Package, sodass niemand gegen ein halbes Package arbeitet.
  • Pages wechseln in einem Schritt. Die veröffentlichte App liest das aktuelle Release. Neue Pages warten als Entwürfe, bis das Release veröffentlicht wird, was alle Benutzer im selben Moment auf sie umstellt. Wer das vorherige Release aktiviert, rollt zurück; das stellt die Pages und Layouts wieder her, nie Daten oder Tabellen.
  • Tabellenänderungen werden geplant und geprüft. Eine Tabelle oder eine Spalte, die NULL zulässt, hinzuzufügen, stört niemanden, und alles, was Daten verliert, braucht ein ausdrückliches Ja.

Erst erweitern, dann zurückbauen

Script Modules, Business Events und Business Entities sind live, sobald ihre Dateien ankommen, nicht erst mit dem Release. Eine Änderung, die eine Spalte umbenennt oder entfernt, geht deshalb in zwei Deployments raus:

  1. Erweitern. Füge die neue Tabelle oder Spalte hinzu und deploye die Skripte und Pages, die sie verwenden, während die alte noch da ist.
  2. Zurückbauen. Entferne die alte Spalte in einem späteren Deployment, sobald nichts sie mehr liest. Der Plan markiert das Entfernen als Datenverlust, und die Prüfung der typisierten Skripte sagt, ob noch irgendetwas sie verwendet.

Eine Änderung, die eine große Tabelle neu schreibt, etwa ein neuer Typ für eine Spalte, hält eine Sperre auf dieser Tabelle, solange sie läuft. Plane solche Änderungen für einen ruhigen Moment ein.

Umgebungen

Das Repository führt seine Umgebungen in archestack.json auf. Eine Umgebung mit "mode": "pipeline" wird nur von der Pipeline deployt, und die VS Code-Erweiterung weigert sich, von Hand dorthin zu pushen:

"environments": {
  "tst": { "url": "https://acme-tst.example.com", "apiUrl": "https://api.example.com", "workspace": "acme-tst.example.com", "mode": "pipeline" },
  "acc": { "url": "https://acme-acc.example.com", "apiUrl": "https://api.example.com", "workspace": "acme-acc.example.com", "mode": "pipeline" },
  "prd": { "url": "https://acme.example.com", "apiUrl": "https://api.example.com", "workspace": "acme.example.com", "mode": "pipeline" }
}

Was jede Umgebung selbst entscheidet, steht weder in den Dateien noch in irgendeinem Deployment: Benutzer und Rollen, Page-Berechtigungen, was öffentlich ist, Data-API-Freigaben und -Schlüssel, Mail-Konten und Verbindungen zu anderen Systemen. Die Administratoren jeder Umgebung legen sie fest, ein Deployment kann also nie etwas öffentlich machen oder jemandem Zugriff geben.

Die Pipeline-Datei

Die Pipeline liegt im Repository und deployt von dort: Jeder Lauf checkt einen Commit aus, und genau diese Dateien werden gebaut, getestet und deployt. Aus einem Workspace wird nichts exportiert. Unten steht dieselbe Pipeline für drei CI-Systeme, jede bringt eine Änderung in die Test-, Abnahme- und Produktionsumgebung. Die archestack-Befehle sind die der Archestack CLI, jedes andere CI-System, das .NET und Docker unter Linux ausführt, führt also dieselben Schritte aus.

  • Build und Test läuft bei jedem Pull Request und jedem Merge in den Main-Branch. Diese Stufe baut aus dem Commit ein Wegwerf-Archestack, führt die Tests aller Packages aus und packt das Repository in ein einziges Artefakt, sodass jede Umgebung genau dieselben Dateien bekommt.
  • Ein Plan- und ein Deploy-Schritt pro Umgebung. Der Plan wird gegen die Umgebung erstellt und im Lauf angezeigt; das Deployment wendet dann genau diesen Plan an, bei Abnahme und Produktion nach einer Genehmigung. Die Umgebungen kommen nacheinander dran, Abnahme wird also erst geplant, wenn Test deployt ist, und Produktion erst, wenn Abnahme deployt ist. Ein Pull Request stoppt nach dem Plan gegen die Testumgebung.
  • Deploy-Tokens. Das Token jeder Umgebung wird im Builder dieser Umgebung erstellt, unter Operations, Deploy Tokens. Es wird nur einmal angezeigt, handelt als Administrator und dient nur dem Deployment: Die Daten der Umgebung liest es nie. Die Adresse der Umgebung kommt aus archestack.json.
Dein CI-System

Azure DevOps

Die Pipeline ist azure-pipelines.yml im Stammverzeichnis des Repositorys:

# azure-pipelines.yml: build and test, then plan and deploy test, acceptance and production
trigger:
  branches:
    include: [main]

pr:
  branches:
    include: [main]

parameters:
  - name: environments
    type: object
    default: [tst, acc, prd]
  - name: allowDataLoss
    displayName: Allow changes that lose data
    type: boolean
    default: false

pool:
  vmImage: ubuntu-latest

stages:
  - stage: build
    displayName: Build and test
    jobs:
      - job: build
        steps:
          - checkout: self
            displayName: The repository at this commit
          - script: |
              dotnet tool install --global Archestack.Cli
              echo "##vso[task.prependpath]$HOME/.dotnet/tools"
            displayName: Install the Archestack CLI
          - script: archestack build
            displayName: Build a throwaway Archestack from the repository
          - script: archestack test --junit $(Agent.TempDirectory)/tests.xml
            displayName: Run every package's tests
          - task: PublishTestResults@2
            condition: succeededOrFailed()
            inputs:
              testResultsFormat: JUnit
              testResultsFiles: $(Agent.TempDirectory)/tests.xml
          - script: archestack pack --out $(Build.ArtifactStagingDirectory)/app.zip
            displayName: Pack the repository
          - publish: $(Build.ArtifactStagingDirectory)
            artifact: app

  - ${{ each env in parameters.environments }}:
    - stage: plan_${{ env }}
      displayName: Plan ${{ env }}
      ${{ if ne(env, 'tst') }}:
        condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
      variables:
        - group: archestack-${{ env }}
      jobs:
        - job: plan
          steps:
            - download: current
              artifact: app
            - script: |
                dotnet tool install --global Archestack.Cli
                echo "##vso[task.prependpath]$HOME/.dotnet/tools"
              displayName: Install the Archestack CLI
            - script: >-
                archestack plan --env ${{ env }}
                --from $(Pipeline.Workspace)/app/app.zip
                --allow-data-loss ${{ parameters.allowDataLoss }}
                --out $(Build.ArtifactStagingDirectory)/plan.json
                --summary $(Build.ArtifactStagingDirectory)/plan.md
              displayName: Plan against ${{ env }}
              env:
                ARCHESTACK_TOKEN: $(ARCHESTACK_TOKEN)
            - script: echo "##vso[task.uploadsummary]$(Build.ArtifactStagingDirectory)/plan.md"
              displayName: Show the plan in the run
            - publish: $(Build.ArtifactStagingDirectory)
              artifact: plan-${{ env }}

    - stage: deploy_${{ env }}
      displayName: Deploy ${{ env }}
      condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
      variables:
        - group: archestack-${{ env }}
      jobs:
        - deployment: deploy
          environment: archestack-${{ env }}
          strategy:
            runOnce:
              deploy:
                steps:
                  - download: current
                    artifact: app
                  - download: current
                    artifact: plan-${{ env }}
                  - script: |
                      dotnet tool install --global Archestack.Cli
                      echo "##vso[task.prependpath]$HOME/.dotnet/tools"
                    displayName: Install the Archestack CLI
                  - script: >-
                      archestack deploy --env ${{ env }}
                      --from $(Pipeline.Workspace)/app/app.zip
                      --plan $(Pipeline.Workspace)/plan-${{ env }}/plan.json
                    displayName: Schema, files and release
                    env:
                      ARCHESTACK_TOKEN: $(ARCHESTACK_TOKEN)
  • Genehmigungen. Lege unter Pipelines, Environments die Umgebungen archestack-tst, archestack-acc und archestack-prd an und füge Abnahme und Produktion einen Approvals-Check hinzu. Azure DevOps wartet dann auf eine genehmigende Person, bevor diese Deploy-Stufen starten, mit dem Plan in der Zusammenfassung des Laufs. Die Testergebnisse werden im Lauf veröffentlicht.
  • Zugangsdaten. Das Deploy-Token jeder Umgebung ist das Secret ARCHESTACK_TOKEN in einer Variablengruppe mit demselben Namen wie die Umgebung, etwa archestack-prd.
  • Datenverlust. Allow changes that lose data ist ein Kontrollkästchen, wenn ein Lauf von Hand gestartet wird, standardmäßig aus. Ohne diese Option stoppt ein Plan, der eine Tabelle oder eine Spalte entfernt, die Pipeline.
  • Pull Requests auf Azure Repos. Azure Repos ignoriert den Abschnitt pr:; füge die Pipeline stattdessen als build validation branch policy auf dem Main-Branch hinzu.
  • Eine im Web-Editor angelegte Pipeline wird als neue Datei gespeichert, die nach der Pipeline benannt ist. Richte die Pipeline in ihren Einstellungen auf azure-pipelines.yml aus, sonst ist die Datei, die du bearbeitest, nicht die, die läuft.

GitHub Actions

Der Workflow ist .github/workflows/archestack.yml:

# .github/workflows/archestack.yml: build and test, then plan and deploy test, acceptance and production
name: Archestack

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:
    inputs:
      allow-data-loss:
        description: Allow changes that lose data
        type: boolean
        default: false

permissions:
  contents: read

jobs:
  build:
    name: Build and test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 8.0.x
      - name: Install the Archestack CLI
        run: dotnet tool install --global Archestack.Cli
      - name: Build a throwaway Archestack from the repository
        run: archestack build
      - name: Run every package's tests
        run: archestack test --junit "$RUNNER_TEMP/tests.xml"
      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: tests
          path: ${{ runner.temp }}/tests.xml
      - name: Pack the repository
        run: archestack pack --out "$RUNNER_TEMP/app/app.zip"
      - uses: actions/upload-artifact@v4
        with:
          name: app
          path: ${{ runner.temp }}/app

  # A pull request stops after the plan against test.
  tst:
    needs: build
    uses: ./.github/workflows/archestack-deploy.yml
    with:
      environment: tst
      deploy: ${{ github.event_name != 'pull_request' }}
      allow-data-loss: ${{ inputs.allow-data-loss == true }}
    secrets:
      token: ${{ secrets.ARCHESTACK_TOKEN_TST }}

  acc:
    if: github.event_name != 'pull_request'
    needs: tst
    uses: ./.github/workflows/archestack-deploy.yml
    with:
      environment: acc
      allow-data-loss: ${{ inputs.allow-data-loss == true }}
    secrets:
      token: ${{ secrets.ARCHESTACK_TOKEN_ACC }}

  prd:
    if: github.event_name != 'pull_request'
    needs: acc
    uses: ./.github/workflows/archestack-deploy.yml
    with:
      environment: prd
      allow-data-loss: ${{ inputs.allow-data-loss == true }}
    secrets:
      token: ${{ secrets.ARCHESTACK_TOKEN_PRD }}

Jede Umgebung wird vom selben wiederverwendbaren Workflow daneben geplant und deployt:

# .github/workflows/archestack-deploy.yml: plans the packed files against one environment, then deploys that plan
name: Archestack environment

on:
  workflow_call:
    inputs:
      environment:
        type: string
        required: true
      deploy:
        type: boolean
        default: true
      allow-data-loss:
        type: boolean
        default: false
    secrets:
      token:
        required: true

jobs:
  plan:
    name: Plan ${{ inputs.environment }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app
          path: app
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 8.0.x
      - run: dotnet tool install --global Archestack.Cli
      - name: Plan against ${{ inputs.environment }}
        run: >-
          archestack plan --env ${{ inputs.environment }}
          --from app/app.zip
          --allow-data-loss ${{ inputs.allow-data-loss }}
          --out plan/plan.json
          --summary plan/plan.md
        env:
          ARCHESTACK_TOKEN: ${{ secrets.token }}
      - name: Show the plan in the run
        if: ${{ !cancelled() }}
        run: cat plan/plan.md >> "$GITHUB_STEP_SUMMARY"
      - uses: actions/upload-artifact@v4
        with:
          name: plan-${{ inputs.environment }}
          path: plan

  deploy:
    name: Deploy ${{ inputs.environment }}
    if: inputs.deploy
    needs: plan
    runs-on: ubuntu-latest
    environment: archestack-${{ inputs.environment }}
    concurrency: archestack-${{ inputs.environment }}
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app
          path: app
      - uses: actions/download-artifact@v4
        with:
          name: plan-${{ inputs.environment }}
          path: plan
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 8.0.x
      - run: dotnet tool install --global Archestack.Cli
      - name: Schema, files and release
        run: archestack deploy --env ${{ inputs.environment }} --from app/app.zip --plan plan/plan.json
        env:
          ARCHESTACK_TOKEN: ${{ secrets.token }}
  • Genehmigungen. Lege unter Settings, Environments die Umgebungen archestack-tst, archestack-acc und archestack-prd an und füge Abnahme und Produktion required reviewers hinzu. Der Deploy-Job wartet auf eine prüfende Person, mit dem Plan schon in der Zusammenfassung des Laufs. In einem privaten Repository brauchen Environments GitHub Pro, Team oder Enterprise.
  • Zugangsdaten. Die Deploy-Tokens sind die Repository-Secrets ARCHESTACK_TOKEN_TST, ARCHESTACK_TOKEN_ACC und ARCHESTACK_TOKEN_PRD, damit der Plan vor der Genehmigung laufen kann. Ein Workflow auf jedem Branch des Repositorys kann ein Repository-Secret lesen; um das Token der Produktion genehmigten Läufen vorzubehalten, mache es stattdessen zu einem Environment-Secret von archestack-prd, dann läuft sein Plan nach der Genehmigung.
  • Datenverlust. Allow changes that lose data ist ein Kontrollkästchen unter Run workflow, standardmäßig aus.
  • Pull Requests aus Forks bekommen keine Secrets, ihr Plan gegen die Testumgebung schlägt also fehl; Pull Requests aus Branches des Repositorys selbst planen wie gewohnt.
  • concurrency verhindert, dass zwei Läufe gleichzeitig in dieselbe Umgebung deployen. Die Testergebnisse werden als Artefakt tests des Laufs aufbewahrt.

GitLab CI

Die Pipeline ist .gitlab-ci.yml im Stammverzeichnis des Repositorys:

# .gitlab-ci.yml: build and test, then plan and deploy test, acceptance and production
workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

variables:
  ALLOW_DATA_LOSS:
    value: "false"
    description: Allow changes that lose data (true or false)

stages: [build, tst, acc, prd]

default:
  image: mcr.microsoft.com/dotnet/sdk:8.0
  before_script:
    - dotnet tool install --global Archestack.Cli
    - export PATH="$PATH:$HOME/.dotnet/tools"

build:
  stage: build
  # The throwaway Archestack runs in Docker, here a Docker service beside the job.
  services:
    - docker:27-dind
  variables:
    DOCKER_HOST: tcp://docker:2375
    DOCKER_TLS_CERTDIR: ""
  script:
    - curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-27.3.1.tgz | tar -xz -C /usr/local/bin --strip-components=1 docker/docker
    - archestack build
    - archestack test --junit tests.xml
    - archestack pack --out app.zip
  artifacts:
    when: always
    paths: [app.zip]
    reports:
      junit: tests.xml

.plan:
  script:
    - archestack plan --env "$TARGET" --from app.zip --allow-data-loss "$ALLOW_DATA_LOSS" --out plan.json --summary plan.md
  artifacts:
    when: always
    expose_as: plan
    paths: [plan.md, plan.json]

.deploy:
  script:
    - archestack deploy --env "$TARGET" --from app.zip --plan plan.json

# A merge request stops after the plan against test.
plan-tst:
  extends: .plan
  stage: tst
  needs: [build]
  variables: { TARGET: tst }

deploy-tst:
  extends: .deploy
  stage: tst
  needs: [build, plan-tst]
  environment: archestack-tst
  resource_group: archestack-tst
  variables: { TARGET: tst }
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

plan-acc:
  extends: .plan
  stage: acc
  needs: [build, deploy-tst]
  variables: { TARGET: acc }
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

deploy-acc:
  extends: .deploy
  stage: acc
  needs: [build, plan-acc]
  environment: archestack-acc
  resource_group: archestack-acc
  variables: { TARGET: acc }
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual
  allow_failure: false

plan-prd:
  extends: .plan
  stage: prd
  needs: [build, deploy-acc]
  variables: { TARGET: prd }
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

deploy-prd:
  extends: .deploy
  stage: prd
  needs: [build, plan-prd]
  environment: archestack-prd
  resource_group: archestack-prd
  variables: { TARGET: prd }
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual
  allow_failure: false
  • Docker. Der Build führt das Wegwerf-Archestack auf einem docker:dind-Service neben dem Job aus, was einen Runner braucht, der privilegierte Container erlaubt, wie es die eigenen Runner von GitLab.com tun. DOCKER_HOST sagt der CLI, wo Docker ist, und sie erreicht das Archestack dort.
  • Genehmigungen. Das Deployment in Abnahme und Produktion sind manuelle Jobs, die die Pipeline blockieren und jeweils gestartet werden, sobald ihr Plan gelesen wurde. Jeder Plan wird bei seinem Job aufbewahrt, und der Plan gegen die Testumgebung ist auch im Merge Request verlinkt. Mit GitLab Premium schütze diese Umgebungen zusätzlich und verlange Genehmigungen.
  • Zugangsdaten. Die Deploy-Tokens sind die maskierten CI/CD-Variablen ARCHESTACK_TOKEN_TST, ARCHESTACK_TOKEN_ACC und ARCHESTACK_TOKEN_PRD. Schütze die von Abnahme und Produktion, sodass nur der Main-Branch sie liest. Eine geschützte Variable erreicht keine Merge-Request-Pipeline, lass die der Testumgebung also ungeschützt, damit Merge Requests gegen die Testumgebung planen können.
  • Datenverlust. Starte die Pipeline von Hand mit ALLOW_DATA_LOSS auf true. Testergebnisse erscheinen im Merge Request.
  • resource_group verhindert, dass zwei Pipelines gleichzeitig in dieselbe Umgebung deployen.

Die Archestack CLI

Die CLI ist das .NET-Tool Archestack.Cli, installiert mit dotnet tool install --global Archestack.Cli auf jedem Agent mit .NET 8 oder neuer. Ihre Befehle laufen im Stammverzeichnis des Repositorys oder nehmen mit --from einen Ordner oder ein gepacktes ZIP:

  • archestack build startet das Wegwerf-Archestack und lädt das Repository hinein. Es läuft in Docker aus dem Image, das der Abschnitt project von archestack.json nennt, sonst aus dem neuesten veröffentlichten; --image wählt ein anderes, und --runtime führt einen Laufzeitordner ohne Docker aus. Es ist fertig, sobald die NuGet-Pakete, die das Repository nennt, wiederhergestellt sind, und läuft für den nächsten Schritt weiter, bis archestack stop es beendet. --nuget-cache <folder> behält diese Downloads für den nächsten Build, der sie sonst erneut herunterlädt; zwei Builds, die gleichzeitig laufen, brauchen je einen eigenen Ordner.
  • archestack test führt die Testfälle aller Packages dagegen aus, vier gleichzeitig, und schreibt mit --junit JUnit.
  • archestack pack --out app.zip packt archestack.json, packages/ und workspace/. Dieselben Dateien ergeben immer dieselben Bytes, und der Plan hält ihren Hash fest, ein Deployment lehnt also andere Dateien als die geplanten ab.
  • archestack plan und archestack deploy funktionieren nur bei einer Umgebung mit "mode": "pipeline", mit ihrem Deploy-Token in ARCHESTACK_TOKEN_<ENV> (etwa ARCHESTACK_TOKEN_PRD) oder ARCHESTACK_TOKEN. Ein Artefakt, das aus dem Repository gelöscht wurde, wird aus seinem Package genommen und bleibt in der Umgebung; plane mit --delete-removed true, um es auch dort zu löschen. Ein Package in der Umgebung, das das Repository nicht enthält, bleibt unangetastet und wird im Plan genannt. Nennt das Repository NuGet-Pakete, fragen beide zuerst die Umgebung, ob sie sich dort wiederherstellen lassen, und deploy wartet nach dem letzten Push bis zu fünf Minuten, bis die Umgebung sie wiederhergestellt hat, bevor es das Release veröffentlicht. Schlägt dieses Warten fehl, sind die Dateien deployt, aber es wird kein Release veröffentlicht.

Der Exit-Code sagt, was einen Schritt gestoppt hat: 3 das Token wurde abgelehnt, 4 die Umgebung hat sich seit dem Plan geändert (neu planen), 5 die Dateien lassen sich so, wie sie sind, nicht anwenden, 6 der Plan verliert Daten und das war nicht erlaubt, 7 ein Test ist fehlgeschlagen, 9 der Server oder das Netzwerk hat versagt, 10 die Umgebung läuft mit einer anderen Hauptversion von Archestack als die CLI oder mit einer ohne NuGet-Unterstützung. NuGet-Pakete, die sich mit den Dateien nicht wiederherstellen lassen, ergeben 5, ebenso eine Umgebung, in der NuGet-Pakete oder der typisierte Datenzugriff ausgeschaltet sind. Bei plan und deploy ergibt ein NuGet-Fehler, den die Dateien nicht beheben können, 9: etwa ein Server mit zu wenig Speicherplatz, ein Feed, den er nicht erreicht, oder eine Wiederherstellung nach dem letzten Push, die fehlschlug oder nicht in fünf Minuten fertig wurde. Derselbe Schritt kann dann beim nächsten Lauf gelingen.

Was heute läuft

  • Build, Test, Planen und Deployen laufen über die CLI, aus Skriptschritten wie in den Pipelines oben. Auf dem Rechner eines Entwicklers ist das Projekt der VS Code-Erweiterung derselbe Build: Reset Project Database erstellt es von Grund auf, und Run Tests führt die Tests aller Packages dagegen aus.
  • Pipeline-Tasks für Azure DevOps und GitHub Actions, die die CLI kapseln: in Entwicklung.

Stolperfallen

  • Ein Rollback macht Tabellen und Daten nicht rückgängig. Jedes Deployment ist ein Release, jede Version, die die Pipeline deployt hat, lässt sich also wieder aktivieren, aber das stellt nur Pages und Layouts wieder her. Tabellen zurückzuholen ist ein eigenes Deployment, geplant und geprüft wie jedes andere.
  • Script Modules und Business Events warten nicht auf das Release. Sie sind live, sobald ihre Dateien ankommen, weshalb eine Änderung, die etwas entfernt, in zwei Deployments rausgeht.
  • Jede Umgebung lädt ihre NuGet-Pakete selbst herunter. Die NuGet-Pakete, die die nuget.json eines Packages deklariert, werden von jeder Umgebung heruntergeladen, die das Package bekommt, der Build eingeschlossen, jede muss also nuget.org oder den Feed erreichen, mit dem ihre Server konfiguriert sind. Der Build nimmt Scripting__NuGet__Sources und die Einstellungen Scripting__NuGet__Feeds__* aus den Umgebungsvariablen, mit denen die CLI läuft. Ein Push, dessen NuGet-Pakete sich nicht wiederherstellen lassen, wird abgelehnt und ändert nichts: Der Befehl plan der CLI prüft das, bevor irgendetwas deployt wird, und build und deploy warten, bis sie wiederhergestellt sind.

Wie es weitergeht

Im Team arbeiten beschreibt, wie sich Entwickler das Repository teilen, das die Pipeline deployt. Workspaces und Hosting erklärt die Umgebungen, in die sie deployt, und Database Deployments den Schema-Schritt.