pipeline { agent any options { ansiColor('xterm') timestamps() } environment { // ถ้าใช้ local bare repo GIT_URL = 'file:///repos/AS400API.git' // Path ติดตั้ง dotnet ชั่วคราวใน pipeline DOTNET_ROOT = "${WORKSPACE}/.dotnet" PATH = "${DOTNET_ROOT}:${PATH}" // Dependency-Check cache (แมพกับ volume/โฟลเดอร์บน Jenkins) DC_DATA = "${JENKINS_HOME}/.dc-cache" // ถ้าจะใช้ SonarQube ให้ตั้งค่าตามระบบจริง // SONAR_HOST_URL = 'http://sonarqube:9000' // SONAR_TOKEN = credentials('SONAR_TOKEN') } stages { stage('Checkout') { steps { checkout([$class: 'GitSCM', branches: [[name: '*/main']], userRemoteConfigs: [[url: "${GIT_URL}"]] ]) } } stage('Install prerequisites') { steps { sh ''' set -euo pipefail if command -v apt-get >/dev/null 2>&1; then export DEBIAN_FRONTEND=noninteractive apt-get update apt-get install -y --no-install-recommends \ ca-certificates curl unzip jq git openjdk-21-jre-headless # --- Install ICU runtime (Debian uses versioned package names) --- if ! ldconfig -p | grep -qi libicu; then PKG="$(apt-cache search '^libicu[0-9]+$' | awk '{print $1}' | head -n1 || true)" if [ -n "$PKG" ]; then echo "Installing ICU package: ${PKG}" apt-get install -y --no-install-recommends "${PKG}" else # Fallback: dev package also provides the libs (heavier) echo "Falling back to libicu-dev..." apt-get install -y --no-install-recommends libicu-dev fi fi fi # Install .NET SDK locally for the build user mkdir -p "${WORKSPACE}/.dotnet" curl -fsSL https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh bash dotnet-install.sh --channel 9.0 --install-dir "${WORKSPACE}/.dotnet" export PATH="${WORKSPACE}/.dotnet:${PATH}" dotnet --info ''' } } stage('SCA (NuGet + OWASP)') { steps { sh ''' set -euo pipefail echo "=== NuGet vulnerability audit ===" dotnet restore dotnet list package --vulnerable || true echo "=== OWASP Dependency-Check ===" rm -rf depcheck mkdir -p depcheck API="https://api.github.com/repos/jeremylong/DependencyCheck/releases/latest" echo "Resolving latest Dependency-Check..." ASSET_URL=$(curl -fsSL "$API" | jq -r '.assets[]?.browser_download_url | select(test("release\\\\.zip$"))' | head -n1) echo "Downloading: $ASSET_URL" curl -fL --retry 3 --retry-all-errors -o depcheck.zip "$ASSET_URL" unzip -q depcheck.zip -d dependency-check DC_BIN="dependency-check/dependency-check/bin/dependency-check.sh" # อัปเดตฐานข้อมูลครั้งแรก (และทุกครั้งที่ cache ว่าง) bash "$DC_BIN" --data "${DC_DATA}" --updateonly || true # สแกนจริง (เร็ว เพราะใช้ cache) bash "$DC_BIN" -f HTML -f XML \ --project "AS400API" \ --scan . \ --out depcheck \ --data "${DC_DATA}" \ --noupdate || true ''' } post { always { archiveArtifacts artifacts: 'depcheck/**', allowEmptyArchive: true // ถ้ามี HTML Publisher plugin จะโชว์รายงานสวยขึ้น publishHTML(target: [ reportName: 'OWASP Dependency-Check', reportDir: 'depcheck', reportFiles: 'dependency-check-report.html', keepAll: true, alwaysLinkToLastBuild: true, allowMissing: true ]) } } } stage('SAST') { steps { sh ''' set -euo pipefail if [ -n "${SONAR_HOST_URL:-}" ] && [ -n "${SONAR_TOKEN:-}" ]; then echo "=== SAST with SonarQube ===" # ถ้าใช้ sonarscanner for .NET (แนะนำ) dotnet tool update --global dotnet-sonarscanner || dotnet tool install --global dotnet-sonarscanner export PATH="$HOME/.dotnet/tools:${PATH}" dotnet-sonarscanner begin \ /k:"AS400API" \ /d:sonar.host.url="${SONAR_HOST_URL}" \ /d:sonar.login="${SONAR_TOKEN}" dotnet build -c Release dotnet-sonarscanner end /d:sonar.login="${SONAR_TOKEN}" else echo "=== SAST with Roslyn analyzers (no Sonar) ===" # เปิด .NET analyzers และ treat warnings เป็น error dotnet build -c Release \ -p:EnableNETAnalyzers=true \ -p:TreatWarningsAsErrors=true \ -warnaserror fi ''' } } stage('Test + Coverage') { steps { sh ''' set -euo pipefail # รันเทส + สร้างผลลัพธ์ JUnit XML + Coverage (Cobertura) dotnet test \ --logger "junit;LogFileName=test-results.xml" \ /p:CollectCoverage=true \ /p:CoverletOutput=coverage/ \ /p:CoverletOutputFormat=cobertura # รวม coverage ไว้ที่เดียวเพื่อ publish/เก็บ artifacts mkdir -p coverage-report COB=$(find . -type f -name "coverage.cobertura.xml" | head -n1 || true) if [ -n "$COB" ]; then cp "$COB" coverage-report/Cobertura.xml fi ''' } post { always { // รายงานผลเทส (JUnit) junit allowEmptyResults: false, testResults: '**/TestResults/**/*.xml' // เก็บไฟล์ coverage archiveArtifacts artifacts: 'coverage-report/**', allowEmptyArchive: true // (ทางเลือก) ถ้าติดตั้ง Coverage plugin // publishCoverage adapters: [coberturaAdapter('coverage-report/Cobertura.xml')], sourceFileResolver: sourceFiles('STORE_LAST_BUILD') } } } stage('Build') { steps { sh ''' set -euo pipefail dotnet publish -c Release -o out ''' } post { success { archiveArtifacts artifacts: 'out/**', allowEmptyArchive: false } } } } post { always { echo "Pipeline finished (status: ${currentBuild.currentResult})" } } }