222 lines
7.3 KiB
Groovy
222 lines
7.3 KiB
Groovy
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 ให้ตั้งค่าตามระบบจริง
|
|
SONARQUBE_INSTANCE = 'SonarQube'
|
|
SONAR_PROJECT_KEY = 'AS400API'
|
|
}
|
|
|
|
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 dependency-check
|
|
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 -oq 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 จะโชว์รายงานสวยขึ้น
|
|
script {
|
|
try {
|
|
publishHTML(target: [
|
|
reportName: 'OWASP Dependency-Check',
|
|
reportDir: 'depcheck',
|
|
reportFiles: 'dependency-check-report.html',
|
|
keepAll: true,
|
|
alwaysLinkToLastBuild: true,
|
|
allowMissing: true
|
|
])
|
|
} catch (Throwable e) {
|
|
echo "Skipping HTML report publish (plugin unavailable?): ${e.getClass().getSimpleName()}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('SAST') {
|
|
steps {
|
|
script {
|
|
if (env.SONARQUBE_INSTANCE?.trim()) {
|
|
withSonarQubeEnv(env.SONARQUBE_INSTANCE) {
|
|
sh """
|
|
set -euo pipefail
|
|
|
|
echo "=== SAST with SonarQube (${env.SONARQUBE_INSTANCE}) ==="
|
|
dotnet tool update --global dotnet-sonarscanner || dotnet tool install --global dotnet-sonarscanner
|
|
export PATH="$HOME/.dotnet/tools:${PATH}"
|
|
|
|
dotnet clean -c Release
|
|
|
|
dotnet sonarscanner begin \
|
|
/k:"${env.SONAR_PROJECT_KEY}" \
|
|
/d:sonar.host.url="\$SONAR_HOST_URL" \
|
|
/d:sonar.login="\$SONAR_AUTH_TOKEN"
|
|
|
|
dotnet build -c Release /warnaserror- -p:TreatWarningsAsErrors=false
|
|
|
|
dotnet sonarscanner end /d:sonar.login="\$SONAR_AUTH_TOKEN"
|
|
"""
|
|
}
|
|
} else {
|
|
sh '''
|
|
set -euo pipefail
|
|
echo "=== SAST with Roslyn analyzers (no Sonar) ==="
|
|
dotnet clean -c Release
|
|
dotnet build -c Release \
|
|
-p:EnableNETAnalyzers=true \
|
|
-p:TreatWarningsAsErrors=true \
|
|
-warnaserror
|
|
'''
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Test + Coverage') {
|
|
steps {
|
|
sh '''
|
|
set -euo pipefail
|
|
|
|
# รันเทส + สร้างผลลัพธ์ JUnit XML + Coverage (Cobertura)
|
|
dotnet build -c Debug
|
|
|
|
dotnet test \
|
|
--logger "junit;LogFileName=test-results.xml" \
|
|
--results-directory "TestResults" \
|
|
/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})"
|
|
}
|
|
}
|
|
} |