diff --git a/Jenkinsfile b/Jenkinsfile index b44b643..8ed16f8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,11 +1,5 @@ pipeline { - agent { - docker { - image 'mcr.microsoft.com/dotnet/sdk:9.0' - args '-u root:root' - // label "${params.AGENT_LABEL?.trim() ?: ''}" - } - } + agent any options { ansiColor('xterm') @@ -65,6 +59,12 @@ pipeline { apt-get install -y --no-install-recommends openjdk-17-jre rm -rf /var/lib/apt/lists/* + # Install .NET SDK locally for the build user + mkdir -p "${WORKSPACE}/.dotnet" + curl -fsSL https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh + bash dotnet-install.sh --channel 9.0 --install-dir "${WORKSPACE}/.dotnet" + + echo '### dotnet --info' dotnet --info diff --git a/Jenkinsfile copy 3 b/Jenkinsfile copy 3 new file mode 100644 index 0000000..b44b643 --- /dev/null +++ b/Jenkinsfile copy 3 @@ -0,0 +1,197 @@ +pipeline { + agent { + docker { + image 'mcr.microsoft.com/dotnet/sdk:9.0' + args '-u root:root' + // label "${params.AGENT_LABEL?.trim() ?: ''}" + } + } + + options { + ansiColor('xterm') + timestamps() + buildDiscarder(logRotator(numToKeepStr: '15')) + disableConcurrentBuilds() + skipDefaultCheckout(true) + } + + parameters { + string(name: 'GIT_URL', defaultValue: 'file:///repos/AS400API.git', description: 'Git repository URL to clone') + string(name: 'GIT_BRANCH', defaultValue: 'main', description: 'Branch or ref to build') + // string(name: 'AGENT_LABEL', defaultValue: '', description: 'Optional Jenkins agent label with Docker CLI access (leave blank for default node)') + } + + environment { + DOTNET_CLI_TELEMETRY_OPTOUT = '1' + DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1' + DOTNET_NOLOGO = '1' + BUILD_CONFIGURATION = 'Release' + TEST_RESULTS_DIR = 'TestResults' + COVERAGE_DIR = 'TestResults/Coverage' + COVERAGE_FILE = 'TestResults/Coverage/coverage.opencover.xml' + PUBLISH_DIR = 'publish/Release' + SONAR_PROJECT_KEY = 'AS400API' + SONAR_PROJECT_NAME = 'AS400API' + } + + stages { + stage('Checkout') { + steps { + // Fetch the requested branch from the configured Git URL. + script { + if (!params.GIT_URL?.trim()) { + error 'GIT_URL parameter must not be empty.' + } + String branchSpec = params.GIT_BRANCH?.trim() ?: 'main' + if (!branchSpec.startsWith('*/')) { + branchSpec = "*/${branchSpec}" + } + checkout([ + $class : 'GitSCM', + branches : [[name: branchSpec]], + doGenerateSubmoduleConfigurations: false, + extensions : [[$class: 'CloneOption', depth: 0, noTags: false, shallow: false]], + userRemoteConfigs : [[url: params.GIT_URL.trim()]] + ]) + } + } + } + + stage('.NET SDK Info') { + steps { + // Sanity check the SDK and install required global tools (Java for Sonar + dotnet-sonarscanner). + sh '''#!/bin/bash -e + apt-get update + apt-get install -y --no-install-recommends openjdk-17-jre + rm -rf /var/lib/apt/lists/* + + echo '### dotnet --info' + dotnet --info + + echo '### Install/Update dotnet-sonarscanner' + dotnet tool install --global dotnet-sonarscanner || dotnet tool update --global dotnet-sonarscanner + ''' + script { + if (!env.PATH.contains('/root/.dotnet/tools')) { + env.PATH = "${env.PATH}:/root/.dotnet/tools" + } + } + } + } + + stage('Restore') { + steps { + // Restore solution dependencies. + sh '''#!/bin/bash -e + dotnet restore AS400API.sln + ''' + } + } + + stage('Build') { + steps { + // Build the solution in Release mode without failing on warnings. + sh '''#!/bin/bash -e + dotnet build AS400API.sln --configuration "${BUILD_CONFIGURATION}" --no-restore + ''' + } + } + + stage('Test & Coverage') { + steps { + // Execute tests once for fast feedback and capture TRX + OpenCover reports. + sh '''#!/bin/bash -e + rm -rf "${TEST_RESULTS_DIR}" + mkdir -p "${COVERAGE_DIR}" + dotnet test AS400API.sln --configuration "${BUILD_CONFIGURATION}" --no-build \ + /p:CollectCoverage=true \ + /p:CoverletOutputFormat=opencover \ + /p:CoverletOutput="${COVERAGE_DIR}/" \ + --logger "trx;LogFileName=test-results.trx" \ + --logger "junit;LogFileName=test-results.xml" + ''' + junit testResults: "${TEST_RESULTS_DIR}/**/*.xml", allowEmptyResults: false + publishCoverage adapters: [opencoverAdapter("${COVERAGE_FILE}")], sourceFileResolver: sourceFiles('STORE_LAST_BUILD'), failNoReports: true + } + } + + stage('SonarQube: Begin') { + steps { + // Kick off Sonar analysis with project metadata and coverage location. + script { + env.SONAR_PROJECT_VERSION = env.BUILD_NUMBER ?: '0.0.0' + } + withSonarQubeEnv('sonarqube') { + sh '''#!/bin/bash -e + dotnet sonarscanner begin \ + /k:"${SONAR_PROJECT_KEY}" \ + /n:"${SONAR_PROJECT_NAME}" \ + /v:"${SONAR_PROJECT_VERSION}" \ + /d:sonar.host.url="${SONAR_HOST_URL}" \ + /d:sonar.login="${SONAR_AUTH_TOKEN}" \ + /d:sonar.cs.opencover.reportsPaths="${COVERAGE_FILE}" + ''' + } + } + } + + stage('Rebuild for Sonar') { + steps { + // Run a rebuild under the Sonar scanner context so analysis picks up compilation data. + sh '''#!/bin/bash -e + dotnet build AS400API.sln --configuration "${BUILD_CONFIGURATION}" --no-restore --no-incremental + ''' + } + } + + stage('SonarQube: End') { + steps { + // Close the Sonar analysis and push data to the server. + withSonarQubeEnv('sonarqube') { + sh '''#!/bin/bash -e + dotnet sonarscanner end /d:sonar.login="${SONAR_AUTH_TOKEN}" + ''' + } + } + } + + stage('Quality Gate') { + steps { + // Wait for the Quality Gate result and fail fast on non-green outcomes. + timeout(time: 15, unit: 'MINUTES') { + script { + def qualityGate = waitForQualityGate() + if (qualityGate.status != 'OK') { + error "SonarQube Quality Gate failed: ${qualityGate.status}" + } + } + } + } + } + + stage('Archive') { + steps { + // Publish Release-ready binaries and test artifacts for downstream consumption. + sh '''#!/bin/bash -e + rm -rf "${PUBLISH_DIR}" + dotnet publish AS400API.sln --configuration "${BUILD_CONFIGURATION}" --no-restore --output "${PUBLISH_DIR}" + ''' + archiveArtifacts artifacts: "${PUBLISH_DIR}/**", fingerprint: true + archiveArtifacts artifacts: "${TEST_RESULTS_DIR}/**/*.trx", allowEmptyArchive: true + archiveArtifacts artifacts: "${COVERAGE_FILE}", allowEmptyArchive: true + } + } + } + + post { + always { + script { + try { + deleteDir() + } catch (org.jenkinsci.plugins.workflow.steps.MissingContextVariableException ignore) { + echo 'Workspace cleanup skipped because no workspace was provisioned.' + } + } + } + } +}