2025-10-17 17:19:55 +07:00
|
|
|
pipeline {
|
2025-10-28 13:07:28 +07:00
|
|
|
agent any
|
2025-10-21 00:08:03 +07:00
|
|
|
|
2025-10-27 21:48:23 +07:00
|
|
|
options {
|
|
|
|
|
ansiColor('xterm')
|
|
|
|
|
timestamps()
|
|
|
|
|
buildDiscarder(logRotator(numToKeepStr: '15'))
|
|
|
|
|
disableConcurrentBuilds()
|
|
|
|
|
skipDefaultCheckout(true)
|
|
|
|
|
}
|
2025-10-17 17:19:55 +07:00
|
|
|
|
2025-10-27 21:48:23 +07:00
|
|
|
parameters {
|
2025-10-28 00:27:55 +07:00
|
|
|
string(name: 'GIT_URL', defaultValue: 'file:///repos/AS400API.git', description: 'Git repository URL to clone')
|
2025-10-27 22:47:50 +07:00
|
|
|
string(name: 'GIT_BRANCH', defaultValue: 'main', description: 'Branch or ref to build')
|
2025-10-28 00:49:59 +07:00
|
|
|
// string(name: 'AGENT_LABEL', defaultValue: '', description: 'Optional Jenkins agent label with Docker CLI access (leave blank for default node)')
|
2025-10-27 21:48:23 +07:00
|
|
|
}
|
2025-10-21 00:08:03 +07:00
|
|
|
|
2025-10-27 21:48:23 +07:00
|
|
|
environment {
|
|
|
|
|
DOTNET_CLI_TELEMETRY_OPTOUT = '1'
|
|
|
|
|
DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1'
|
|
|
|
|
DOTNET_NOLOGO = '1'
|
2025-10-27 22:47:50 +07:00
|
|
|
BUILD_CONFIGURATION = 'Release'
|
|
|
|
|
TEST_RESULTS_DIR = 'TestResults'
|
|
|
|
|
COVERAGE_DIR = 'TestResults/Coverage'
|
|
|
|
|
COVERAGE_FILE = 'TestResults/Coverage/coverage.opencover.xml'
|
2025-10-28 15:42:29 +07:00
|
|
|
COVERAGE_HTML_DIR = 'TestResults/Coverage/html'
|
2025-10-28 13:54:54 +07:00
|
|
|
|
|
|
|
|
// Path ติดตั้ง dotnet ชั่วคราวใน pipeline
|
|
|
|
|
DOTNET_ROOT = "${WORKSPACE}/.dotnet"
|
|
|
|
|
PATH = "${DOTNET_ROOT}:${PATH}"
|
|
|
|
|
|
2025-10-27 22:47:50 +07:00
|
|
|
PUBLISH_DIR = 'publish/Release'
|
2025-10-27 21:48:23 +07:00
|
|
|
SONAR_PROJECT_KEY = 'AS400API'
|
2025-10-27 22:47:50 +07:00
|
|
|
SONAR_PROJECT_NAME = 'AS400API'
|
2025-10-27 21:48:23 +07:00
|
|
|
}
|
2025-10-21 00:08:03 +07:00
|
|
|
|
2025-10-27 21:48:23 +07:00
|
|
|
stages {
|
|
|
|
|
stage('Checkout') {
|
|
|
|
|
steps {
|
2025-10-27 22:47:50 +07:00
|
|
|
// Fetch the requested branch from the configured Git URL.
|
2025-10-27 21:48:23 +07:00
|
|
|
script {
|
2025-10-27 22:47:50 +07:00
|
|
|
if (!params.GIT_URL?.trim()) {
|
|
|
|
|
error 'GIT_URL parameter must not be empty.'
|
2025-10-27 21:48:23 +07:00
|
|
|
}
|
2025-10-27 22:47:50 +07:00
|
|
|
String branchSpec = params.GIT_BRANCH?.trim() ?: 'main'
|
|
|
|
|
if (!branchSpec.startsWith('*/')) {
|
|
|
|
|
branchSpec = "*/${branchSpec}"
|
2025-10-27 21:48:23 +07:00
|
|
|
}
|
|
|
|
|
checkout([
|
|
|
|
|
$class : 'GitSCM',
|
2025-10-27 22:47:50 +07:00
|
|
|
branches : [[name: branchSpec]],
|
2025-10-27 21:48:23 +07:00
|
|
|
doGenerateSubmoduleConfigurations: false,
|
|
|
|
|
extensions : [[$class: 'CloneOption', depth: 0, noTags: false, shallow: false]],
|
2025-10-27 22:47:50 +07:00
|
|
|
userRemoteConfigs : [[url: params.GIT_URL.trim()]]
|
2025-10-27 21:48:23 +07:00
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-21 00:08:03 +07:00
|
|
|
|
2025-10-27 22:47:50 +07:00
|
|
|
stage('.NET SDK Info') {
|
2025-10-27 21:48:23 +07:00
|
|
|
steps {
|
2025-10-27 22:47:50 +07:00
|
|
|
// Sanity check the SDK and install required global tools (Java for Sonar + dotnet-sonarscanner).
|
|
|
|
|
sh '''#!/bin/bash -e
|
2025-10-27 21:48:23 +07:00
|
|
|
apt-get update
|
2025-10-28 13:18:40 +07:00
|
|
|
apt-get install -y --no-install-recommends openjdk-21-jre-headless
|
2025-10-17 17:19:55 +07:00
|
|
|
|
2025-10-28 13:57:03 +07:00
|
|
|
# --- Install ICU runtime (Debian uses versioned package names) ---
|
|
|
|
|
if ! ldconfig -p | grep -qi libicu; then
|
|
|
|
|
PKG="$(apt-cache search '^libicu[0-9]+$' | awk '{print $1}' | head -n1 || true)"
|
|
|
|
|
if [ -n "$PKG" ]; then
|
|
|
|
|
echo "Installing ICU package: ${PKG}"
|
|
|
|
|
apt-get install -y --no-install-recommends "${PKG}"
|
2025-10-28 14:03:00 +07:00
|
|
|
else
|
|
|
|
|
echo "Installing fallback ICU development package..."
|
|
|
|
|
apt-get install -y --no-install-recommends libicu-dev
|
2025-10-28 13:57:03 +07:00
|
|
|
fi
|
|
|
|
|
fi
|
2025-10-28 14:03:00 +07:00
|
|
|
rm -rf /var/lib/apt/lists/*
|
2025-10-28 13:57:03 +07:00
|
|
|
|
2025-10-28 13:07:28 +07:00
|
|
|
# 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"
|
|
|
|
|
|
|
|
|
|
|
2025-10-27 22:47:50 +07:00
|
|
|
echo '### dotnet --info'
|
2025-10-27 21:48:23 +07:00
|
|
|
dotnet --info
|
2025-10-17 17:19:55 +07:00
|
|
|
|
2025-10-28 15:42:29 +07:00
|
|
|
echo '### Install/Update dotnet tooling'
|
2025-10-27 22:47:50 +07:00
|
|
|
dotnet tool install --global dotnet-sonarscanner || dotnet tool update --global dotnet-sonarscanner
|
2025-10-28 15:42:29 +07:00
|
|
|
dotnet tool install --global dotnet-reportgenerator-globaltool || dotnet tool update --global dotnet-reportgenerator-globaltool
|
2025-10-27 21:48:23 +07:00
|
|
|
'''
|
2025-10-27 22:47:50 +07:00
|
|
|
script {
|
2025-10-28 18:33:31 +07:00
|
|
|
String dotnetToolsPath = "${env.HOME ?: '/var/jenkins_home'}/.dotnet/tools"
|
|
|
|
|
if (!env.PATH.contains(dotnetToolsPath)) {
|
|
|
|
|
env.PATH = "${env.PATH}:${dotnetToolsPath}"
|
2025-10-27 23:01:50 +07:00
|
|
|
}
|
2025-10-27 22:47:50 +07:00
|
|
|
}
|
2025-10-27 21:48:23 +07:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-25 22:25:21 +07:00
|
|
|
|
2025-10-27 21:48:23 +07:00
|
|
|
stage('Restore') {
|
|
|
|
|
steps {
|
2025-10-27 22:47:50 +07:00
|
|
|
// Restore solution dependencies.
|
|
|
|
|
sh '''#!/bin/bash -e
|
2025-10-27 21:48:23 +07:00
|
|
|
dotnet restore AS400API.sln
|
|
|
|
|
'''
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-25 22:25:21 +07:00
|
|
|
|
2025-10-27 22:47:50 +07:00
|
|
|
stage('Build') {
|
2025-10-27 21:48:23 +07:00
|
|
|
steps {
|
2025-10-27 22:47:50 +07:00
|
|
|
// Build the solution in Release mode without failing on warnings.
|
|
|
|
|
sh '''#!/bin/bash -e
|
|
|
|
|
dotnet build AS400API.sln --configuration "${BUILD_CONFIGURATION}" --no-restore
|
|
|
|
|
'''
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-20 12:01:53 +07:00
|
|
|
|
2025-10-27 22:47:50 +07:00
|
|
|
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}"
|
2025-10-28 15:42:29 +07:00
|
|
|
mkdir -p "${COVERAGE_HTML_DIR}"
|
2025-10-27 22:47:50 +07:00
|
|
|
dotnet test AS400API.sln --configuration "${BUILD_CONFIGURATION}" --no-build \
|
|
|
|
|
/p:CollectCoverage=true \
|
|
|
|
|
/p:CoverletOutputFormat=opencover \
|
2025-10-28 16:21:26 +07:00
|
|
|
/p:CoverletOutput="${WORKSPACE}/${COVERAGE_DIR}/" \
|
2025-10-28 15:37:14 +07:00
|
|
|
--results-directory "${TEST_RESULTS_DIR}" \
|
2025-10-27 22:47:50 +07:00
|
|
|
--logger "trx;LogFileName=test-results.trx" \
|
|
|
|
|
--logger "junit;LogFileName=test-results.xml"
|
2025-10-28 15:42:29 +07:00
|
|
|
|
2025-10-28 15:49:52 +07:00
|
|
|
"${HOME}/.dotnet/tools/reportgenerator" \
|
2025-10-28 15:42:29 +07:00
|
|
|
-reports:"${COVERAGE_FILE}" \
|
|
|
|
|
-targetdir:"${COVERAGE_HTML_DIR}" \
|
|
|
|
|
-reporttypes:"HtmlInline_AzurePipelines;Cobertura"
|
2025-10-27 22:47:50 +07:00
|
|
|
'''
|
|
|
|
|
junit testResults: "${TEST_RESULTS_DIR}/**/*.xml", allowEmptyResults: false
|
2025-10-28 15:42:29 +07:00
|
|
|
publishHTML(target: [
|
|
|
|
|
allowMissing : false,
|
|
|
|
|
keepAll : true,
|
|
|
|
|
reportDir : "${COVERAGE_HTML_DIR}",
|
|
|
|
|
reportFiles : 'index.html',
|
|
|
|
|
reportName : 'Code Coverage'
|
|
|
|
|
])
|
2025-10-21 08:03:24 +07:00
|
|
|
}
|
2025-10-17 17:52:49 +07:00
|
|
|
}
|
2025-10-17 17:19:55 +07:00
|
|
|
|
2025-10-27 22:47:50 +07:00
|
|
|
stage('SonarQube: Begin') {
|
2025-10-27 21:48:23 +07:00
|
|
|
steps {
|
2025-10-27 22:47:50 +07:00
|
|
|
// Kick off Sonar analysis with project metadata and coverage location.
|
2025-10-27 21:48:23 +07:00
|
|
|
script {
|
2025-10-27 22:47:50 +07:00
|
|
|
env.SONAR_PROJECT_VERSION = env.BUILD_NUMBER ?: '0.0.0'
|
|
|
|
|
}
|
2025-10-28 18:28:53 +07:00
|
|
|
withSonarQubeEnv('SonarQube') {
|
2025-10-27 22:47:50 +07:00
|
|
|
sh '''#!/bin/bash -e
|
2025-10-28 20:11:04 +07:00
|
|
|
|
|
|
|
|
# Ensure scanner is available and PATH includes global tools
|
|
|
|
|
dotnet tool update --global dotnet-sonarscanner
|
2025-10-28 20:35:30 +07:00
|
|
|
export PATH="$PATH:${HOME}/.dotnet/tools"
|
2025-10-28 20:11:04 +07:00
|
|
|
|
2025-10-28 20:25:54 +07:00
|
|
|
# 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}"
|
2025-10-28 20:11:04 +07:00
|
|
|
|
|
|
|
|
|
2025-10-27 22:47:50 +07:00
|
|
|
dotnet sonarscanner begin \
|
2025-10-28 20:11:04 +07:00
|
|
|
/k:AS400API \
|
2025-10-28 20:49:41 +07:00
|
|
|
/d:sonar.cs.opencover.reportsPaths="${COVERAGE_FILE}" \
|
|
|
|
|
/d:sonar.exclusions="TestResults/Coverage/html/**"
|
2025-10-28 20:11:04 +07:00
|
|
|
|
2025-10-27 22:47:50 +07:00
|
|
|
'''
|
2025-10-27 21:48:23 +07:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-21 12:12:52 +07:00
|
|
|
}
|
2025-10-25 21:52:18 +07:00
|
|
|
|
2025-10-27 22:47:50 +07:00
|
|
|
stage('Rebuild for Sonar') {
|
2025-10-27 21:48:23 +07:00
|
|
|
steps {
|
2025-10-27 22:47:50 +07:00
|
|
|
// 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
|
2025-10-27 21:48:23 +07:00
|
|
|
'''
|
|
|
|
|
}
|
2025-10-17 17:19:55 +07:00
|
|
|
}
|
|
|
|
|
|
2025-10-27 22:47:50 +07:00
|
|
|
stage('SonarQube: End') {
|
2025-10-27 21:48:23 +07:00
|
|
|
steps {
|
2025-10-27 22:47:50 +07:00
|
|
|
// Close the Sonar analysis and push data to the server.
|
2025-10-28 18:28:53 +07:00
|
|
|
withSonarQubeEnv('SonarQube') {
|
2025-10-27 22:47:50 +07:00
|
|
|
sh '''#!/bin/bash -e
|
2025-10-28 20:35:30 +07:00
|
|
|
export PATH="$PATH:${HOME}/.dotnet/tools"
|
2025-10-28 20:29:01 +07:00
|
|
|
dotnet sonarscanner end
|
2025-10-27 22:47:50 +07:00
|
|
|
'''
|
2025-10-27 21:48:23 +07:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-21 00:08:03 +07:00
|
|
|
}
|
2025-10-21 12:12:52 +07:00
|
|
|
|
2025-10-27 22:47:50 +07:00
|
|
|
stage('Quality Gate') {
|
2025-10-27 21:48:23 +07:00
|
|
|
steps {
|
2025-10-27 22:47:50 +07:00
|
|
|
// Wait for the Quality Gate result and fail fast on non-green outcomes.
|
|
|
|
|
timeout(time: 15, unit: 'MINUTES') {
|
2025-10-27 22:59:05 +07:00
|
|
|
script {
|
|
|
|
|
def qualityGate = waitForQualityGate()
|
|
|
|
|
if (qualityGate.status != 'OK') {
|
|
|
|
|
error "SonarQube Quality Gate failed: ${qualityGate.status}"
|
|
|
|
|
}
|
2025-10-27 22:47:50 +07:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-27 21:48:23 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-27 22:47:50 +07:00
|
|
|
stage('Archive') {
|
2025-10-27 21:48:23 +07:00
|
|
|
steps {
|
2025-10-27 22:47:50 +07:00
|
|
|
// 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}"
|
2025-10-27 21:48:23 +07:00
|
|
|
'''
|
2025-10-27 22:47:50 +07:00
|
|
|
archiveArtifacts artifacts: "${PUBLISH_DIR}/**", fingerprint: true
|
|
|
|
|
archiveArtifacts artifacts: "${TEST_RESULTS_DIR}/**/*.trx", allowEmptyArchive: true
|
|
|
|
|
archiveArtifacts artifacts: "${COVERAGE_FILE}", allowEmptyArchive: true
|
2025-10-28 15:42:29 +07:00
|
|
|
archiveArtifacts artifacts: "${COVERAGE_HTML_DIR}/**", allowEmptyArchive: true
|
2025-10-27 21:48:23 +07:00
|
|
|
}
|
2025-10-21 12:12:52 +07:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-21 00:08:03 +07:00
|
|
|
|
2025-10-27 21:48:23 +07:00
|
|
|
post {
|
|
|
|
|
always {
|
2025-10-27 23:25:17 +07:00
|
|
|
script {
|
|
|
|
|
try {
|
|
|
|
|
deleteDir()
|
|
|
|
|
} catch (org.jenkinsci.plugins.workflow.steps.MissingContextVariableException ignore) {
|
|
|
|
|
echo 'Workspace cleanup skipped because no workspace was provisioned.'
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-27 21:48:23 +07:00
|
|
|
}
|
2025-10-17 17:19:55 +07:00
|
|
|
}
|
2025-10-27 21:48:23 +07:00
|
|
|
}
|