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 } } } }