← Zur Uebersicht

Das Jenkinsfile im Detail

Wichtiger Hinweis vorab: Fuer dieses Lernprojekt stand kein echter Jenkins-Server zur Verfuegung - das Jenkinsfile selbst konnte deshalb nicht gegen einen echten Jenkins ausgefuehrt werden. Was dagegen sehr wohl echt getestet wurde: jedes einzelne Shell-Kommando, das die Pipeline aufruft (mvn ..., ci/scripts/deploy.sh ...) - siehe ci/scripts/deploy.sh selbst, das manuell mit allen drei Zielstufen durchlaufen wurde. Dieselbe Grenze wie beim automatisierten Liberty-EAR-Deployment in Phase 5 (docs/50-websphere-config/03-liberty-serverstart-verifiziert.md) - ehrlich benannt statt verschwiegen.

Annahme: mvn ist auf dem Agenten bereits verfuegbar

Das Jenkinsfile hat bewusst keinen tools { maven '...'; jdk '...' }-Block - so ein Block muesste auf konkrete Namen verweisen, die unter "Global Tool Configuration" auf dem Ziel-Jenkins eingerichtet sind. Da fuer dieses Projekt kein echter Jenkins-Server zur Verfuegung stand, waeren solche Namen frei erfunden und wuerden eine Pruefbarkeit vortaeuschen, die nicht besteht. Stattdessen die ehrliche Annahme: der Jenkins-Agent, der diese Pipeline ausfuehrt, hat Maven und ein passendes JDK bereits auf dem PATH (z.B. ueber ein vorbereitetes Docker-Agent-Image). Wer dieses Jenkinsfile gegen einen echten Jenkins einsetzt, sollte diesen Block ergaenzen und an die dort konfigurierten Tool-Namen anpassen.

Struktur: Declarative Pipeline

Das Jenkinsfile nutzt Jenkins' Declarative Pipeline-Syntax (pipeline { ... }) statt der aelteren, freieren Scripted-Pipeline-Syntax (node { ... }) - Declarative erzwingt eine feste Struktur (Stages, Steps, klar getrennte post-Aktionen), die leichter zu lesen und zu validieren ist. Fuer eine Multibranch-Pipeline (mehrere Branches, jeder mit eigenem Pipeline-Lauf) ist das der Standardweg.

parameters: Wie weit soll dieser Lauf gehen?

parameters {
    choice(name: 'DEPLOY_STAGE', choices: ['none', 'test', 'qs', 'prod'], ...)
}

Ein Build-Parameter, den ein Nutzer beim manuellen Ausloesen waehlt (oder der bei automatischen Trigger-Laeufen auf seinem Default none bleibt). Jede when-Bedingung weiter unten prueft params.DEPLOY_STAGE, um zu entscheiden, ob ihre Stage ueberhaupt ausgefuehrt wird.

stage('Unit- und Modultests'): Coverage im Standardlauf

sh 'mvn -B -Pcoverage verify'

novaris-integration-tests (Phase 5) hat sein eigenes Surefire deaktiviert. Ohne das Profil integration bleiben diese Docker-Tests beim normalen Verify-Lauf uebersprungen. Das Profil coverage aktiviert JaCoCo fuer die Unit-Tests und erzeugt Reports unter target/site/jacoco/. Die echten Oracle-/IBM-MQ-Tests werden erst ueber den Pipeline-Parameter RUN_INTEGRATION_TESTS mit mvn -Pintegration verify ausgefuehrt.

-B (Batch-Modus) unterdrueckt interaktive Maven-Ausgaben/-Prompts - in einer CI-Umgebung ohne Terminal essenziell, sonst kann ein Build unerwartet haengen bleiben.

Die Pipeline wird zentral vom Jenkins aus ../../shared/enterprise-infrastructure verwaltet. Das projektspezifische Jenkinsfile bleibt die fachliche Build-/Deploy-Definition; Jenkins Configuration as Code verweist auf dieses Repository.

post { always { junit ... } }: Testergebnisse sichtbar machen

junit testResults: 'novaris-legacy-parent/*/target/surefire-reports/*.xml', allowEmptyResults: true

Das JUnit-Plugin von Jenkins liest die von Surefire erzeugten XML-Testberichte und macht sie als grafische Testuebersicht (bestanden/fehlgeschlagen/Trend ueber die Zeit) im Jenkins-UI sichtbar - unabhaengig davon, ob der Build insgesamt erfolgreich war oder nicht (always, nicht success), damit man auch bei einem fehlgeschlagenen Build sofort sieht, WELCHER Test fehlgeschlagen ist, ohne das Konsolenlog durchsuchen zu muessen. allowEmptyResults: true verhindert, dass ein Modul ohne Tests (z.B. novaris-policy-ear) den Schritt selbst zum Scheitern bringt.

input: die manuellen Freigabe-Gates

input message: "QS-Deployment von Build #${env.BUILD_NUMBER} freigeben?",
      submitter: 'novaris-qs-approvers'

input haelt die Pipeline an einer bestimmten Stelle an und wartet auf eine manuelle Bestaetigung im Jenkins-UI. submitter schraenkt ein, WER bestaetigen darf (eine in Jenkins konfigurierte Berechtigungsgruppe) - ohne dieses Attribut duerfte jeder mit allgemeinem Jenkins-Zugriff die Freigabe erteilen, was fuer ein PROD-Gate offensichtlich zu schwach waere.

withCredentials: Passwoerter nur zur Laufzeit, nie im Log

withCredentials([
    string(credentialsId: 'novaris-test-oracle-password', variable: 'NOVARIS_ORACLE_PASSWORD'),
    ...
]) {
    sh 'ci/scripts/deploy.sh test'
}

credentialsId verweist auf einen Eintrag im Jenkins-Credentials-Store (dort einmalig von einem Administrator hinterlegt, niemals im Repository). Innerhalb des withCredentials- Blocks steht der tatsaechliche Wert als Umgebungsvariable (NOVARIS_ORACLE_PASSWORD) zur Verfuegung - Jenkins maskiert diesen Wert automatisch in der Konsolenausgabe (jedes Vorkommen wird durch **** ersetzt), selbst wenn ein aufgerufenes Skript ihn versehentlich ausgeben wuerde. Das ist der Grund, warum ci/env/*.properties bewusst keine Passwoerter enthaelt (siehe 01-vier-stages-ueberblick.md) - die Trennung "unkritische Konfiguration in Dateien, Geheimnisse im Credentials-Store" ist hier durchgezogen, nicht nur behauptet: ci/scripts/deploy.sh prueft explizit, dass NOVARIS_ORACLE_PASSWORD/NOVARIS_DB2_PASSWORD beim Aufruf tatsaechlich gesetzt sind (Praesenz-Check, ohne die Werte selbst zu verwenden oder auszugeben) - so bleibt die withCredentials-Verkabelung keine reine Behauptung im Jenkinsfile, sondern etwas, das das Skript beim Fehlen sichtbar ablehnt.

when { allOf { ... } }: mehrere Bedingungen fuer PROD

when {
    allOf {
        expression { params.DEPLOY_STAGE == 'prod' }
        expression { env.BRANCH_NAME == 'main' || (env.BRANCH_NAME ?: '').startsWith('release/') }
    }
}

Beide Bedingungen muessen zutreffen - der gewaehlte Parameter UND der richtige Branch. (env.BRANCH_NAME ?: '') faengt den Fall ab, dass BRANCH_NAME in einer Nicht- Multibranch-Pipeline-Konfiguration gar nicht gesetzt ist (dann waere es null, und null.startsWith(...) wuerde eine NullPointerException werfen statt einfach false zu ergeben).

⌂ Cockpit