diff --git a/Jenkinsfile b/Jenkinsfile index bef48a8..13a1b68 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -14,12 +14,12 @@ pipeline { DOTNET_ROOT = "${WORKSPACE}/.dotnet" PATH = "${DOTNET_ROOT}:${PATH}" - // Dependency-Check cache (แมพกับ volume/โฟลเดอร์บน Jenkins) + // Dependency-Check cache DC_DATA = "${JENKINS_HOME}/.dc-cache" - // ถ้าจะใช้ SonarQube ให้ตั้งค่าตามระบบจริง + // SonarQube SONARQUBE_INSTANCE = 'SonarQube' - SONAR_PROJECT_KEY = 'AS400API' + SONAR_PROJECT_KEY = 'AS400API' } stages { @@ -50,7 +50,6 @@ pipeline { 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 @@ -88,11 +87,7 @@ pipeline { 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 . \ @@ -104,7 +99,6 @@ pipeline { post { always { archiveArtifacts artifacts: 'depcheck/**', allowEmptyArchive: true - // ถ้ามี HTML Publisher plugin จะโชว์รายงานสวยขึ้น script { try { publishHTML(target: [ @@ -116,50 +110,39 @@ pipeline { allowMissing: true ]) } catch (Throwable e) { - echo "Skipping HTML report publish (plugin unavailable?): ${e.getClass().getSimpleName()}" + echo "Skipping HTML report publish: ${e.getClass().getSimpleName()}" } } } } } - stage('SAST') { + stage('SAST with SonarQube') { steps { - script { - if (env.SONARQUBE_INSTANCE?.trim()) { - withSonarQubeEnv(env.SONARQUBE_INSTANCE) { - sh """ - set -euo pipefail + withSonarQubeEnv("${SONARQUBE_INSTANCE}") { + sh ''' + set -euo pipefail + echo "=== SAST with SonarQube ===" - 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 tool update --global dotnet-sonarscanner + export PATH="$PATH:/root/.dotnet/tools" - dotnet clean -c Release + dotnet sonarscanner begin \ + /k:"'"${SONAR_PROJECT_KEY}"'" \ + /d:sonar.host.url="$SONAR_HOST_URL" \ + /d:sonar.login="$SONAR_AUTH_TOKEN" \ + /d:sonar.exclusions="**/bin/**,**/obj/**" \ + /d:sonar.test.exclusions="**/*.Tests/**" - 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 \ - -p:TreatWarningsAsErrors=false \ - -warnaserror - - 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 - ''' - } + dotnet clean -c Release + # สำคัญ: ปิด warnings-as-errors + dotnet build -c Release /warnaserror- -p:TreatWarningsAsErrors=false + ''' + } + } + post { + always { + sh 'dotnet sonarscanner end /d:sonar.login="$SONAR_AUTH_TOKEN" || true' } } } @@ -168,10 +151,7 @@ pipeline { steps { sh ''' set -euo pipefail - - # รันเทส + สร้างผลลัพธ์ JUnit XML + Coverage (Cobertura) dotnet build -c Debug - dotnet test \ --logger "junit;LogFileName=test-results.xml" \ --results-directory "TestResults" \ @@ -179,7 +159,6 @@ pipeline { /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 @@ -189,14 +168,8 @@ pipeline { } 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') } } } @@ -214,11 +187,19 @@ pipeline { } } } - } + + stage('Quality Gate') { + steps { + timeout(time: 30, unit: 'MINUTES') { + waitForQualityGate abortPipeline: true + } + } + } + } // end stages post { always { echo "Pipeline finished (status: ${currentBuild.currentResult})" } } -} \ No newline at end of file +} // end pipeline \ No newline at end of file