diff --git a/Jenkinsfile b/Jenkinsfile index f7e4bca..4d733ab 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,221 +1,192 @@ pipeline { - agent none - options { ansiColor('xterm'); timestamps() } + agent any + + options { + ansiColor('xterm') + timestamps() + } environment { - DOTNET_CLI_TELEMETRY_OPTOUT = '1' - DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1' - SONAR_PROJECT_KEY = 'as400api-dotnet' - SONAR_PROJECT_NAME = 'AS400_API_DOTNET (.NET 9)' + // ถ้าใช้ 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') { - agent any - steps { checkout scm } + steps { + checkout([$class: 'GitSCM', + branches: [[name: '*/main']], + userRemoteConfigs: [[url: "${GIT_URL}"]] + ]) + } } stage('Install prerequisites') { - agent any steps { sh ''' set -euo pipefail - - install_deps() { - if command -v apt-get >/dev/null 2>&1; then - export DEBIAN_FRONTEND=noninteractive - apt-get update - # Common native deps for .NET + tools - apt-get install -y --no-install-recommends \ - libkrb5-3 zlib1g libstdc++6 ca-certificates curl unzip jq - - # Java (for OWASP Dependency-Check) - # prefer 17 headless; fall back to default-jre-headless - (apt-get install -y --no-install-recommends openjdk-17-jre-headless) || \ - (apt-get install -y --no-install-recommends default-jre-headless) || true - - # ---- ICU on Debian/Ubuntu ---- - # 1) try meta/dev names (older releases) - apt-get install -y --no-install-recommends libicu || true - apt-get install -y --no-install-recommends libicu-dev || true - - # 2) if still missing, detect versioned package (e.g., libicu74 on trixie) - if ! ldconfig -p 2>/dev/null | grep -qi 'libicu'; then - PKG="$(apt-cache search -n '^libicu[0-9]+$' | awk '{print $1}' | sort -V | tail -n1 || true)" - if [ -n "${PKG:-}" ]; then - echo "Installing detected ICU package: $PKG" - apt-get install -y --no-install-recommends "$PKG" - fi - fi - - # 3) final sanity - if ! ldconfig -p 2>/dev/null | grep -qi 'libicu'; then - echo "WARN: ICU not found after install attempts (will rely on invariant mode if needed)" - fi - - elif command -v dnf >/dev/null 2>&1; then - dnf install -y libicu krb5-libs zlib libstdc++ ca-certificates curl unzip java-17-openjdk-headless jq || true - elif command -v yum >/dev/null 2>&1; then - yum install -y libicu krb5-libs zlib libstdc++ ca-certificates curl unzip java-17-openjdk-headless jq || true - elif command -v apk >/dev/null 2>&1; then - apk add --no-cache icu-libs krb5-libs zlib libstdc++ ca-certificates curl unzip openjdk17-jre-headless jq || true - else - echo "Unsupported package manager. Please install ICU + Java manually." - exit 1 - fi - } - - install_deps - ''' - } - } - - - - // Bootstrap .NET SDK if dotnet is missing - stage('Bootstrap .NET SDK') { - agent any - steps { - sh ''' - set -e - if ! command -v dotnet >/dev/null 2>&1; then - echo "Installing .NET SDK locally for this build..." - curl -fsSL https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh - bash dotnet-install.sh --channel 9.0 --install-dir "$HOME/.dotnet" - fi - export PATH="$HOME/.dotnet:$PATH" - - # If ICU still missing (e.g., no libicu*.so found), enable invariant mode as a fallback - if ! ldconfig -p 2>/dev/null | grep -qi 'libicu'; then - export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 - echo "NOTE: Running .NET in globalization invariant mode (ICU not found)" + 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 vulnerabilities + OWASP)') { - agent any + stage('SCA (NuGet + OWASP)') { steps { sh ''' set -euo pipefail - export PATH="$HOME/.dotnet:$PATH" - echo "=== NuGet vulnerability audit ===" dotnet restore dotnet list package --vulnerable || true - echo "=== OWASP Dependency-Check (no Docker) ===" - rm -f depcheck.zip || true - rm -rf dependency-check || true + echo "=== OWASP Dependency-Check ===" + rm -rf depcheck mkdir -p depcheck API="https://api.github.com/repos/jeremylong/DependencyCheck/releases/latest" - - # Resolve the correct asset URL (ends with -release.zip) - echo "Resolving Dependency-Check latest asset URL from GitHub API..." - ASSET_URL="$(curl -fsSL "$API" \ - | jq -r '.assets[]?.browser_download_url | select(test("release\\\\.zip$"))' \ - | head -n1 || true)" - - # Fallback from tag_name if assets listing is throttled - if [ -z "${ASSET_URL:-}" ]; then - TAG="$(curl -fsSL "$API" | jq -r '.tag_name' || true)" - if [ -n "${TAG:-}" ]; then - VER="${TAG#v}" - ASSET_URL="https://github.com/jeremylong/DependencyCheck/releases/download/${TAG}/dependency-check-${VER}-release.zip" - fi - fi - - if [ -z "${ASSET_URL:-}" ]; then - echo "ERROR: Could not resolve Dependency-Check release asset URL." - exit 9 - fi - + 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" - - # Validate and extract - unzip -tq depcheck.zip || { echo "Downloaded file is not a valid ZIP"; exit 9; } - mkdir -p dependency-check unzip -q depcheck.zip -d dependency-check - DC_BIN="$(echo dependency-check/dependency-check*/bin/dependency-check.sh)" - if [ ! -x "$DC_BIN" ]; then - echo "ERROR: dependency-check.sh not found under extracted folder" - ls -la dependency-check || true - exit 9 - fi + DC_BIN="dependency-check/dependency-check/bin/dependency-check.sh" - # Generate HTML and XML reports (note: use multiple -f flags) - bash "$DC_BIN" \ - -f HTML -f XML \ - --project "AS400_API_DOTNET" \ - --scan "." \ - --out "depcheck" \ + # อัปเดตฐานข้อมูลครั้งแรก (และทุกครั้งที่ 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 - - echo "SCA reports generated in depcheck/" ''' } 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 + Coverage (SonarQube + Tests)') { - agent any + stage('SAST') { steps { sh ''' - set -e - export PATH="$HOME/.dotnet:$PATH" + set -euo pipefail - # run tests with coverage (cobertura) + produce TRX results for JUnit + 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 "trx;LogFileName=test_results.trx" \ + --logger "junit;LogFileName=test-results.xml" \ /p:CollectCoverage=true \ /p:CoverletOutput=coverage/ \ /p:CoverletOutputFormat=cobertura + # รวม coverage ไว้ที่เดียวเพื่อ publish/เก็บ artifacts mkdir -p coverage-report - # copy the cobertura file (adjust path if your solution layout differs) - COBERTURA_FILE=$(find . -type f -name "coverage.cobertura.xml" | head -n1 || true) - [ -n "$COBERTURA_FILE" ] && cp "$COBERTURA_FILE" coverage-report/Cobertura.xml || true - - # If SonarQube is configured, run scanner; otherwise skip gracefully. - if [ -n "${SONARQUBE_ENV_NAME:-}" ]; then - echo "SonarQube env variable detected: $SONARQUBE_ENV_NAME" - else - echo "SonarQube not configured; skipping Sonar scan." - exit 0 + COB=$(find . -type f -name "coverage.cobertura.xml" | head -n1 || true) + if [ -n "$COB" ]; then + cp "$COB" coverage-report/Cobertura.xml fi ''' } post { always { - // Publish TRX results (built-in) - junit '**/TestResults/**/*.trx' - // Archive coverage XML so you can inspect it + // รายงานผลเทส (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 Artifact') { - agent any + stage('Build') { steps { sh ''' - set -e - export PATH="$HOME/.dotnet:$PATH" + set -euo pipefail dotnet publish -c Release -o out - ls -la out ''' - archiveArtifacts artifacts: 'out/**', fingerprint: true + } + post { + success { + archiveArtifacts artifacts: 'out/**', allowEmptyArchive: false + } } } } + + post { + always { + echo "Pipeline finished (status: ${currentBuild.currentResult})" + } + } } \ No newline at end of file diff --git a/Jenkinsfile copy b/Jenkinsfile copy new file mode 100644 index 0000000..f7e4bca --- /dev/null +++ b/Jenkinsfile copy @@ -0,0 +1,221 @@ +pipeline { + agent none + options { ansiColor('xterm'); timestamps() } + + environment { + DOTNET_CLI_TELEMETRY_OPTOUT = '1' + DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1' + SONAR_PROJECT_KEY = 'as400api-dotnet' + SONAR_PROJECT_NAME = 'AS400_API_DOTNET (.NET 9)' + } + + stages { + stage('Checkout') { + agent any + steps { checkout scm } + } + + stage('Install prerequisites') { + agent any + steps { + sh ''' + set -euo pipefail + + install_deps() { + if command -v apt-get >/dev/null 2>&1; then + export DEBIAN_FRONTEND=noninteractive + apt-get update + # Common native deps for .NET + tools + apt-get install -y --no-install-recommends \ + libkrb5-3 zlib1g libstdc++6 ca-certificates curl unzip jq + + # Java (for OWASP Dependency-Check) + # prefer 17 headless; fall back to default-jre-headless + (apt-get install -y --no-install-recommends openjdk-17-jre-headless) || \ + (apt-get install -y --no-install-recommends default-jre-headless) || true + + # ---- ICU on Debian/Ubuntu ---- + # 1) try meta/dev names (older releases) + apt-get install -y --no-install-recommends libicu || true + apt-get install -y --no-install-recommends libicu-dev || true + + # 2) if still missing, detect versioned package (e.g., libicu74 on trixie) + if ! ldconfig -p 2>/dev/null | grep -qi 'libicu'; then + PKG="$(apt-cache search -n '^libicu[0-9]+$' | awk '{print $1}' | sort -V | tail -n1 || true)" + if [ -n "${PKG:-}" ]; then + echo "Installing detected ICU package: $PKG" + apt-get install -y --no-install-recommends "$PKG" + fi + fi + + # 3) final sanity + if ! ldconfig -p 2>/dev/null | grep -qi 'libicu'; then + echo "WARN: ICU not found after install attempts (will rely on invariant mode if needed)" + fi + + elif command -v dnf >/dev/null 2>&1; then + dnf install -y libicu krb5-libs zlib libstdc++ ca-certificates curl unzip java-17-openjdk-headless jq || true + elif command -v yum >/dev/null 2>&1; then + yum install -y libicu krb5-libs zlib libstdc++ ca-certificates curl unzip java-17-openjdk-headless jq || true + elif command -v apk >/dev/null 2>&1; then + apk add --no-cache icu-libs krb5-libs zlib libstdc++ ca-certificates curl unzip openjdk17-jre-headless jq || true + else + echo "Unsupported package manager. Please install ICU + Java manually." + exit 1 + fi + } + + install_deps + ''' + } + } + + + + // Bootstrap .NET SDK if dotnet is missing + stage('Bootstrap .NET SDK') { + agent any + steps { + sh ''' + set -e + if ! command -v dotnet >/dev/null 2>&1; then + echo "Installing .NET SDK locally for this build..." + curl -fsSL https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh + bash dotnet-install.sh --channel 9.0 --install-dir "$HOME/.dotnet" + fi + export PATH="$HOME/.dotnet:$PATH" + + # If ICU still missing (e.g., no libicu*.so found), enable invariant mode as a fallback + if ! ldconfig -p 2>/dev/null | grep -qi 'libicu'; then + export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 + echo "NOTE: Running .NET in globalization invariant mode (ICU not found)" + fi + + dotnet --info + ''' + } + } + + stage('SCA (NuGet vulnerabilities + OWASP)') { + agent any + steps { + sh ''' + set -euo pipefail + export PATH="$HOME/.dotnet:$PATH" + + echo "=== NuGet vulnerability audit ===" + dotnet restore + dotnet list package --vulnerable || true + + echo "=== OWASP Dependency-Check (no Docker) ===" + rm -f depcheck.zip || true + rm -rf dependency-check || true + mkdir -p depcheck + + API="https://api.github.com/repos/jeremylong/DependencyCheck/releases/latest" + + # Resolve the correct asset URL (ends with -release.zip) + echo "Resolving Dependency-Check latest asset URL from GitHub API..." + ASSET_URL="$(curl -fsSL "$API" \ + | jq -r '.assets[]?.browser_download_url | select(test("release\\\\.zip$"))' \ + | head -n1 || true)" + + # Fallback from tag_name if assets listing is throttled + if [ -z "${ASSET_URL:-}" ]; then + TAG="$(curl -fsSL "$API" | jq -r '.tag_name' || true)" + if [ -n "${TAG:-}" ]; then + VER="${TAG#v}" + ASSET_URL="https://github.com/jeremylong/DependencyCheck/releases/download/${TAG}/dependency-check-${VER}-release.zip" + fi + fi + + if [ -z "${ASSET_URL:-}" ]; then + echo "ERROR: Could not resolve Dependency-Check release asset URL." + exit 9 + fi + + echo "Downloading: $ASSET_URL" + curl -fL --retry 3 --retry-all-errors -o depcheck.zip "$ASSET_URL" + + # Validate and extract + unzip -tq depcheck.zip || { echo "Downloaded file is not a valid ZIP"; exit 9; } + mkdir -p dependency-check + unzip -q depcheck.zip -d dependency-check + + DC_BIN="$(echo dependency-check/dependency-check*/bin/dependency-check.sh)" + if [ ! -x "$DC_BIN" ]; then + echo "ERROR: dependency-check.sh not found under extracted folder" + ls -la dependency-check || true + exit 9 + fi + + # Generate HTML and XML reports (note: use multiple -f flags) + bash "$DC_BIN" \ + -f HTML -f XML \ + --project "AS400_API_DOTNET" \ + --scan "." \ + --out "depcheck" \ + --noupdate || true + + echo "SCA reports generated in depcheck/" + ''' + } + post { + always { + archiveArtifacts artifacts: 'depcheck/**', allowEmptyArchive: true + } + } + } + + stage('SAST + Coverage (SonarQube + Tests)') { + agent any + steps { + sh ''' + set -e + export PATH="$HOME/.dotnet:$PATH" + + # run tests with coverage (cobertura) + produce TRX results for JUnit + dotnet test \ + --logger "trx;LogFileName=test_results.trx" \ + /p:CollectCoverage=true \ + /p:CoverletOutput=coverage/ \ + /p:CoverletOutputFormat=cobertura + + mkdir -p coverage-report + # copy the cobertura file (adjust path if your solution layout differs) + COBERTURA_FILE=$(find . -type f -name "coverage.cobertura.xml" | head -n1 || true) + [ -n "$COBERTURA_FILE" ] && cp "$COBERTURA_FILE" coverage-report/Cobertura.xml || true + + # If SonarQube is configured, run scanner; otherwise skip gracefully. + if [ -n "${SONARQUBE_ENV_NAME:-}" ]; then + echo "SonarQube env variable detected: $SONARQUBE_ENV_NAME" + else + echo "SonarQube not configured; skipping Sonar scan." + exit 0 + fi + ''' + } + post { + always { + // Publish TRX results (built-in) + junit '**/TestResults/**/*.trx' + // Archive coverage XML so you can inspect it + archiveArtifacts artifacts: 'coverage-report/**', allowEmptyArchive: true + } + } + } + + stage('Build Artifact') { + agent any + steps { + sh ''' + set -e + export PATH="$HOME/.dotnet:$PATH" + dotnet publish -c Release -o out + ls -la out + ''' + archiveArtifacts artifacts: 'out/**', fingerprint: true + } + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index c7f4ed5..38caa92 100644 --- a/Program.cs +++ b/Program.cs @@ -8,7 +8,7 @@ using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Serilog; -// var builder = WebApplication.CreateBuilder(args); + var builderArgs = args ?? Array.Empty(); var builder = WebApplication.CreateBuilder(builderArgs);