pipeline { agent none options { ansiColor('xterm'); timestamps() } environment { DOTNET_CLI_TELEMETRY_OPTOUT = '1' DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1' SONAR_PROJECT_KEY = 'as400api-dotnet' // <-- change if you like SONAR_PROJECT_NAME = 'AS400_API_DOTNET (.NET 9)' } stages { stage('Checkout') { agent any steps { checkout scm } } stage('SCA (NuGet vulnerabilities + OWASP)') { agent { docker { image 'mcr.microsoft.com/dotnet/sdk:9.0'; reuseNode true } } steps { sh ''' set -e dotnet --info dotnet restore echo "=== NuGet vulnerability audit ===" # Prints known vulnerable packages (won't fail the build by default) dotnet list package --vulnerable || true echo "=== OWASP Dependency-Check (try Docker first) ===" mkdir -p depcheck if command -v docker >/dev/null 2>&1; then docker run --rm \ -v "$PWD":/src \ -v "$PWD/depcheck":/report \ owasp/dependency-check:latest \ --scan /src --format "HTML" --out /report || true else echo "Docker not found; falling back to CLI zip…" curl -sSL -o depcheck.zip https://github.com/jeremylong/DependencyCheck/releases/latest/download/dependency-check.zip || true if [ -f depcheck.zip ]; then unzip -q depcheck.zip -d depcheckcli || true java -jar depcheckcli/dependency-check/bin/dependency-check.jar \ --scan . --format HTML --out depcheck || true fi fi echo "SCA reports generated in depcheck/" ''' } post { always { archiveArtifacts artifacts: 'depcheck/**', allowEmptyArchive: true } } } stage('SAST + Coverage (SonarQube + Tests)') { agent { docker { image 'mcr.microsoft.com/dotnet/sdk:9.0'; reuseNode true } } environment { SONAR_TOKEN = credentials('sonar-token') } steps { withSonarQubeEnv('SonarQubeServer') { sh ''' set -e dotnet tool install --global dotnet-sonarscanner --version 7.* dotnet tool install --global dotnet-reportgenerator-globaltool export PATH="$PATH:/root/.dotnet/tools" 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/**" dotnet build -c Release # run tests; produce OpenCover coverage for Sonar # If your test project path differs, specify it explicitly or run at solution level: dotnet test -c Release \ /p:CollectCoverage=true \ /p:CoverletOutputFormat=opencover \ /p:CoverletOutput=./TestResults/coverage dotnet sonarscanner end /d:sonar.login="$SONAR_TOKEN" # Generate a Cobertura report for Jenkins Coverage UI reportgenerator \ -reports:**/TestResults/**/coverage.opencover.xml \ -targetdir:coverage-report \ -reporttypes:Cobertura ''' } } post { always { 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 ''' set -e dotnet publish -c Release -o out ls -la out ''' archiveArtifacts artifacts: 'out/**', fingerprint: true } } } }