Lab 07: Build-Artefakte erzeugen und publizieren¶
Hintergrund¶
Build-Artefakte sind die Ergebnisse eines Build-Prozesses: kompilierte Binaries, Pakete, Konfigurationsdateien oder Deployables. In Azure Pipelines gibt es zwei Mechanismen:
- Pipeline Artifacts (empfohlen): Schneller Upload/Download, verwendet Azure
Pipelines internen Speicher. Keywords:
publishunddownload. - Build Artifacts (Legacy): Älterer Mechanismus, verwendet
PublishBuildArtifacts@1undDownloadBuildArtifacts@0Tasks.
Artefakte überleben die Lebensdauer eines Jobs und können zwischen Stages, Jobs und sogar zwischen Pipeline-Runs geteilt werden. In Lab 06 haben wir Artefakte bereits genutzt, um Build-Ergebnisse zwischen Stages zu übertragen. In diesem Lab vertiefen wir das Thema: Wir erzeugen ein Build-Manifest mit Metadaten, publizieren Test-Reports im JUnit-Format und verifizieren in einer eigenen Stage, dass alle erwarteten Artefakte vorhanden und vollständig sind.
Zusätzlich lernen wir den PublishTestResults@2-Task kennen, der
Test-Ergebnisse im JUnit-Format an Azure DevOps übergibt. Die Ergebnisse
erscheinen dann im
Tests-Tab des Pipeline-Runs - mit Übersicht über bestandene und
fehlgeschlagene Tests, Laufzeiten und Trends über mehrere Builds hinweg.
Aufgabenstellung¶
Schritt 1: Build-Skript erstellen¶
Für dieses Lab erweitern wir das Projekt um ein Build-Skript, das die Anwendung baut und ein Build-Manifest mit Metadaten erzeugt. Das Manifest enthält Informationen wie die Build-Nummer, den Git-Commit und eine Liste der erzeugten Dateien. Solche Manifeste sind in der Praxis nützlich, um nachzuvollziehen, welcher Code in welchem Build enthalten ist.
Erstelle die Datei build.sh im Wurzelverzeichnis deines
hello-pipeline-Repositorys:
#!/bin/bash
set -e
echo "=== Build gestartet ==="
BUILD_NUMBER=${1:-"local"}
echo "Build Number: $BUILD_NUMBER"
# Erstelle dist-Verzeichnis
rm -rf dist
mkdir -p dist
# Dateien kopieren
cp src/*.js dist/
cp index.html dist/
# Version in die App einbauen
echo "const BUILD_INFO = { version: '1.0.0', build: '$BUILD_NUMBER', date: '$(date -u +%Y-%m-%dT%H:%M:%SZ)' };" >> dist/app.js
# Build-Manifest erstellen
cat > dist/build-manifest.json << MANIFESTEOF
{
"version": "1.0.0",
"buildNumber": "$BUILD_NUMBER",
"buildDate": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"gitCommit": "$(git rev-parse HEAD 2>/dev/null || echo 'unknown')",
"gitBranch": "$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'unknown')",
"files": $(find dist -type f -name '*.js' -o -name '*.html' | sort | python3 -c "import sys,json; print(json.dumps([l.strip().replace('dist/','') for l in sys.stdin]))")
}
MANIFESTEOF
echo ""
echo "=== Build erfolgreich ==="
echo "Artefakte in dist/:"
ls -la dist/
echo ""
echo "Build-Manifest:"
cat dist/build-manifest.json
Das Skript nimmt die Build-Nummer als Argument entgegen (oder verwendet
"local" als Fallback), kopiert die Anwendungsdateien in ein dist/-
Verzeichnis und erzeugt ein JSON-Manifest mit Build-Metadaten. In der Pipeline
übergeben wir die vordefinierte Variable $(Build.BuildNumber), sodass jeder
Build eindeutig identifizierbar ist.
Mache das Skript ausführbar:
Auf UNIX-Systemen (Linux, macOS):
git checkout master
git add build.sh
chmod +x build.sh
Auf Windows:
git checkout master
git add build.sh
# chmod existiert nicht auf Windows. Stattdessen setzen wir das
# Executable-Bit direkt im Git-Index — der Pipeline-Agent (Linux)
# braucht es, damit bash das Skript ausführen kann.
git update-index --chmod=+x build.sh
Schritt 2: Test-Report-Generator erstellen¶
Azure DevOps kann Test-Ergebnisse im JUnit-XML-Format einlesen und im Tests-Tab des Pipeline-Runs visualisieren. Da unser minimales Test-Framework aus Lab 06 kein JUnit-XML erzeugt, erstellen wir ein kleines Skript, das einen Report im richtigen Format generiert.
Erstelle die Datei test/generate-report.sh:
#!/bin/bash
mkdir -p test-results
cat > test-results/test-report.xml << XMLEOF
<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="3" failures="0" time="0.42">
<testsuite name="hello-pipeline" tests="3" failures="0" time="0.42">
<testcase name="greet returns correct message" time="0.12"/>
<testcase name="add(2,3) returns 5" time="0.15"/>
<testcase name="add(-1,1) returns 0" time="0.15"/>
</testsuite>
</testsuites>
XMLEOF
echo "Test-Report erstellt: test-results/test-report.xml"
Das Skript erzeugt eine statische JUnit-XML-Datei mit drei bestandenen Tests. In
einem realen Projekt würde das Test-Framework (z. B. Jest, Mocha, pytest) diesen
Report automatisch erzeugen. Hier simulieren wir das Ergebnis, um den
PublishTestResults@2-Task zu demonstrieren.
Mache auch dieses Skript ausführbar:
Auf UNIX-Systemen (Linux, macOS):
chmod +x test/generate-report.sh
Auf Windows:
git add test/generate-report.sh
git update-index --chmod=+x test/generate-report.sh
Schritt 3: Pipeline mit Artefakt-Management¶
Jetzt erstellen wir eine Pipeline mit drei Stages, die den vollständigen
Artefakt-Lebenszyklus abbilden: Erzeugen, Testen und Verifizieren. Ersetze den
Inhalt von azure-pipelines.yml:
trigger:
branches:
include:
- master
variables:
nodeVersion: '20.x'
stages:
# ===== Build Stage =====
- stage: Build
displayName: 'Build'
jobs:
- job: BuildApp
displayName: 'Build Application'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '$(nodeVersion)'
displayName: 'Node.js installieren'
- script: bash build.sh $(Build.BuildNumber)
displayName: 'Build ausführen'
# Pipeline Artifact publizieren (empfohlene Methode)
- publish: $(System.DefaultWorkingDirectory)/dist
artifact: app-dist
displayName: 'App-Artefakte publizieren'
# Build-Manifest separat publizieren
- publish: $(System.DefaultWorkingDirectory)/dist/build-manifest.json
artifact: build-metadata
displayName: 'Build-Metadata publizieren'
# ===== Test Stage =====
- stage: Test
displayName: 'Test'
dependsOn: Build
jobs:
- job: RunTests
displayName: 'Tests ausführen'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '$(nodeVersion)'
displayName: 'Node.js installieren'
- script: npm test
displayName: 'Unit Tests'
# Test-Report generieren
- script: bash test/generate-report.sh
displayName: 'Test-Report generieren'
# Test-Ergebnisse publizieren (JUnit-Format)
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: 'test-results/test-report.xml'
testRunTitle: 'Unit Tests'
displayName: 'Test-Ergebnisse publizieren'
# Test-Report als Artefakt
- publish: $(System.DefaultWorkingDirectory)/test-results
artifact: test-reports
displayName: 'Test-Reports publizieren'
# ===== Verify Stage =====
- stage: Verify
displayName: 'Verify Artifacts'
dependsOn:
- Build
- Test
jobs:
- job: VerifyArtifacts
displayName: 'Artefakte verifizieren'
pool:
vmImage: 'ubuntu-latest'
steps:
# Artefakte herunterladen
- download: current
artifact: app-dist
displayName: 'App-Artefakte herunterladen'
- download: current
artifact: build-metadata
displayName: 'Build-Metadata herunterladen'
- download: current
artifact: test-reports
displayName: 'Test-Reports herunterladen'
- script: |
echo "=== Heruntergeladene Artefakte ==="
echo ""
echo "--- App Distribution ---"
ls -la $(Pipeline.Workspace)/app-dist/
echo ""
echo "--- Build Metadata ---"
cat $(Pipeline.Workspace)/build-metadata/build-manifest.json
echo ""
echo "--- Test Reports ---"
ls -la $(Pipeline.Workspace)/test-reports/
echo ""
echo "=== Validierung ==="
# Prüfe, ob alle erwarteten Dateien vorhanden sind
for file in app.js app-dist/index.html; do
base=$(basename $file)
if [ -f "$(Pipeline.Workspace)/app-dist/$base" ]; then
echo "OK: $base gefunden"
else
echo "FEHLER: $base fehlt!"
exit 1
fi
done
echo ""
echo "Alle Artefakte erfolgreich verifiziert!"
displayName: 'Artefakte prüfen und validieren'
Gehe die Pipeline Abschnitt für Abschnitt durch:
- Build-Stage: Ruft das Build-Skript auf und übergibt
$(Build.BuildNumber)als Argument. Anschließend werden zwei separate Artefakte publiziert:app-distenthält die komplette Distribution (alle Dateien imdist/-Verzeichnis),build-metadataenthält nur das Build-Manifest. Die Trennung erlaubt es, in späteren Stages gezielt nur die Metadaten herunterzuladen, ohne die gesamte Distribution zu übertragen. - Test-Stage: Führt die Unit Tests aus und generiert anschließend den
JUnit-XML-Report. Der
PublishTestResults@2-Task übergibt diesen Report an Azure DevOps, wo er im Tests-Tab des Pipeline-Runs erscheint. Zusätzlich wird der Report als Artefakttest-reportspubliziert, damit er auch außerhalb von Azure DevOps verfügbar ist (z. B. für externe Reporting-Tools). - Verify-Stage: Hängt von beiden vorherigen Stages ab
(
dependsOn: [Build, Test]) und lädt alle drei Artefakte herunter. Die heruntergeladenen Dateien landen unter$(Pipeline.Workspace)/<artefakt-name>/. Ein Validierungsskript prüft, ob alle erwarteten Dateien vorhanden sind, und bricht mit Exit-Code 1 ab, falls etwas fehlt. In der Praxis könnte diese Stage auch Checksummen prüfen oder Artefakte signieren.
Schritt 4: Committen und Pipeline starten¶
git add build.sh test/generate-report.sh azure-pipelines.yml
git commit -m "Add artifact publishing and verification"
git push origin master
Schritt 5: Artefakte im Browser ansehen¶
- Öffne den Pipeline-Run im Browser.
- Klicke auf "Summary" (oder "Zusammenfassung").
- Im Bereich "Related" siehst du die publizierten Artefakte:
app-dist(3 Dateien:app.js,index.html,build-manifest.json)build-metadata(1 Datei:build-manifest.json)test-reports(1 Datei:test-report.xml)
- Klicke auf ein Artefakt, um es herunterzuladen oder die Dateien zu durchsuchen. Azure DevOps zeigt eine Datei-Übersicht mit Größe und Pfad.
- Wechsle zum Tab "Tests": Hier siehst du die vom
PublishTestResults@2-Task publizierten Ergebnisse - drei bestandene Tests mit Laufzeiten. Bei mehreren Builds zeigt Azure DevOps hier auch Trends an (z. B. ob die Testanzahl wächst oder Tests instabil sind).
Aufräumen¶
Kein Aufräumen nötig - Artefakte werden automatisch nach der konfigurierten Aufbewahrungsdauer (Standard: 30 Tage) gelöscht. Die Aufbewahrungsdauer kann unter Project Settings > Settings > Retention policy angepasst werden.