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 fi mkdir -p "${DOTNET_ROOT}" curl -fsSL https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh bash dotnet-install.sh --channel 9.0 --install-dir "${DOTNET_ROOT}" dotnet --info # เตรียม cache สำหรับ Dependency-Check mkdir -p "${DC_DATA}" ''' } } 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})" } } }