pipeline { agent none environment { // Optional: tweak for speed DOTNET_CLI_TELEMETRY_OPTOUT = '1' DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1' // Sonar: set host via withSonarQubeEnv; token via credentials below SONAR_PROJECT_KEY = 'AS400-API' SONAR_PROJECT_NAME = 'AS/400 API (.NET9)' } stages { stage('Checkout') { agent any steps { checkout scm } } stage('SCA (Dependency Audit)') { // Run inside official .NET 9 SDK image agent { docker { image 'mcr.microsoft.com/dotnet/sdk:9.0'; reuseNode true } } steps { sh ''' dotnet --info # 1) Built-in vulnerability audit (NuGet advisory DB) dotnet restore # dotnet list package --vulnerable exits 0 even when vulnerabilities found; use Jenkins Warnings NG to surface, or grep to fail on high dotnet list package --vulnerable || true # 2) (Optional) OWASP Dependency-Check via Docker # Scans csproj/packages.lock.json for known CVEs mkdir -p depcheck docker run --rm \ -v "$PWD":/src \ -v "$PWD/depcheck":/report \ owasp/dependency-check:latest \ --scan /src \ --format "HTML" \ --out /report || true echo "Dependency-Check report at depcheck/dependency-check-report.html" ''' // You can archive the HTML report for viewing in Jenkins archiveArtifacts artifacts: 'depcheck/**', allowEmptyArchive: true } } stage('SAST + Coverage (SonarQube + Tests)') { agent { docker { // Use SDK image; we’ll install scanner + reportgenerator inside image 'mcr.microsoft.com/dotnet/sdk:9.0' reuseNode true } } environment { SONAR_TOKEN = credentials('sonar-token') } steps { withSonarQubeEnv('SonarQubeServer') { sh ''' # Install dotnet tools we need dotnet tool install --global dotnet-sonarscanner --version 7.* dotnet tool install --global dotnet-reportgenerator-globaltool export PATH="$PATH:/root/.dotnet/tools" # Begin Sonar analysis (SAST + quality gates) dotnet sonarscanner begin \ /k:"$SONAR_PROJECT_KEY" \ /n:"$SONAR_PROJECT_NAME" \ /d:sonar.host.url="$SONAR_HOST_URL" \ /d:sonar.login="$SONAR_TOKEN" \ /d:sonar.cs.opencover.reportsPaths="**/TestResults/**/coverage.opencover.xml" \ /d:sonar.coverage.exclusions="**/*.cshtml,**/Migrations/**" # Build (needed for Sonar to analyze) dotnet restore dotnet build -c Release --no-restore # Test + Coverage (OpenCover format for Sonar) # This uses coverlet.msbuild (works without editing csproj) # Generates: .//TestResults//coverage.opencover.xml dotnet test -c Release --no-build \ /p:CollectCoverage=true \ /p:CoverletOutputFormat=opencover \ /p:CoverletOutput=./TestResults/coverage # End Sonar (uploads results to server) dotnet sonarscanner end /d:sonar.login="$SONAR_TOKEN" # Optional: create a single Cobertura report for Jenkins UI reportgenerator \ -reports:**/TestResults/**/coverage.opencover.xml \ -targetdir:coverage-report \ -reporttypes:Cobertura ''' } } post { always { // Publish coverage into Jenkins (Cobertura) publishCoverage adapters: [coberturaAdapter('coverage-report/Cobertura.xml')], sourceFileResolver: sourceFiles('STORE_LAST_BUILD') junit '**/TestResults/**/*.trx' archiveArtifacts artifacts: 'coverage-report/**', allowEmptyArchive: true } } } stage('Build Artifact') { agent { docker { image 'mcr.microsoft.com/dotnet/sdk:9.0'; reuseNode true } } steps { sh ''' dotnet publish -c Release -o out ls -la out ''' archiveArtifacts artifacts: 'out/**', fingerprint: true } } } options { ansiColor('xterm') timestamps() } }