| garciadeblas | d2332d0 | 2025-12-23 12:33:00 +0100 | [diff] [blame] | 1 | /* |
| 2 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 3 | you may not use this file except in compliance with the License. |
| 4 | You may obtain a copy of the License at |
| 5 | |
| 6 | http://www.apache.org/licenses/LICENSE-2.0 |
| 7 | |
| 8 | Unless required by applicable law or agreed to in writing, software |
| 9 | distributed under the License is distributed on an "AS IS" BASIS, |
| 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY, either express or |
| 11 | implied. |
| 12 | See the License for the specific language governing permissions and |
| 13 | limitations under the License. |
| 14 | */ |
| 15 | |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 16 | def DEFAULT_MODULE_NAME = 'mon' |
| 17 | |
| garciadeblas | d2332d0 | 2025-12-23 12:33:00 +0100 | [diff] [blame] | 18 | pipeline { |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 19 | agent { label 'pool' } |
| 20 | options { disableConcurrentBuilds() } |
| 21 | parameters { |
| 22 | // Core Gerrit / multibranch inputs |
| 23 | string(name: 'GERRIT_BRANCH', defaultValue: env.BRANCH_NAME ?: 'master', description: '') |
| 24 | string(name: 'GERRIT_PROJECT', defaultValue: 'osm/MON', description: '') |
| 25 | string(name: 'GERRIT_REFSPEC', defaultValue: env.GERRIT_REFSPEC ?: '', description: '') |
| 26 | string(name: 'GERRIT_PATCHSET_REVISION', defaultValue: env.GERRIT_PATCHSET_REVISION ?: '', description: '') |
| 27 | string(name: 'PROJECT_URL_PREFIX', defaultValue: 'https://osm.etsi.org/gerrit', description: '') |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 28 | string(name: 'DOCKER_ARGS', defaultValue: '', description: 'Extra docker args for docker run') |
| 29 | |
| caviedesj | f1ff02f | 2026-02-12 11:14:06 +0100 | [diff] [blame] | 30 | // Build and test configuration |
| caviedesj | fcb6491 | 2026-01-19 17:32:53 +0100 | [diff] [blame] | 31 | string(name: 'OPENSTACK_BASE_IMAGE', defaultValue: 'ubuntu24.04', description: '') |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 32 | string(name: 'OPENSTACK_OSM_FLAVOR', defaultValue: 'osm.sanity', description: '') |
| caviedesj | f1ff02f | 2026-02-12 11:14:06 +0100 | [diff] [blame] | 33 | string(name: 'MODULE_NAME', defaultValue: 'MON', description: 'Name of the module under test') |
| 34 | string(name: 'IMAGENAME', defaultValue: 'opensourcemano/mon', description: 'Image name for publish') |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 35 | |
| caviedesj | f1ff02f | 2026-02-12 11:14:06 +0100 | [diff] [blame] | 36 | // Stage control flags |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 37 | booleanParam(name: 'DO_INSTALL', defaultValue: true, description: '') |
| 38 | booleanParam(name: 'DO_DOCKERPUSH', defaultValue: true, description: '') |
| 39 | booleanParam(name: 'DO_ROBOT', defaultValue: true, description: '') |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 40 | booleanParam(name: 'SAVE_CONTAINER_ON_FAIL', defaultValue: false, description: '') |
| 41 | booleanParam(name: 'SAVE_CONTAINER_ON_PASS', defaultValue: false, description: '') |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 42 | } |
| 43 | environment { |
| 44 | MDG = "${params.GERRIT_PROJECT?.contains('/') ? params.GERRIT_PROJECT.split('/')[1] : params.GERRIT_PROJECT}" |
| 45 | CONTAINER_NAME = "${params.GERRIT_PROJECT}-${params.GERRIT_BRANCH}".toLowerCase() |
| caviedesj | cd18487 | 2026-02-12 19:03:53 +0100 | [diff] [blame] | 46 | TEST_IMAGE = 'opensourcemano/tox-osm:19.0' |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 47 | DOCKER_REGISTRY = 'osm.etsi.org:5050/devops/cicd/' |
| 48 | } |
| 49 | stages { |
| 50 | stage('Prepare') { steps { sh 'env' } } |
| 51 | |
| 52 | stage('Checkout') { |
| 53 | steps { |
| 54 | checkout scm |
| 55 | script { |
| 56 | sh "git fetch --tags" |
| 57 | if (params.GERRIT_REFSPEC?.trim()) { sh "git fetch origin ${params.GERRIT_REFSPEC}" } |
| 58 | if (params.GERRIT_PATCHSET_REVISION?.trim()) { sh "git checkout -f ${params.GERRIT_PATCHSET_REVISION}" } |
| 59 | sh "sudo git clean -dfx || git clean -dfx" |
| garciadeblas | d2332d0 | 2025-12-23 12:33:00 +0100 | [diff] [blame] | 60 | } |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 61 | } |
| garciadeblas | d2332d0 | 2025-12-23 12:33:00 +0100 | [diff] [blame] | 62 | } |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 63 | |
| 64 | stage('Clone devops (central)') { |
| 65 | steps { |
| 66 | dir('devops') { |
| 67 | sh "git init ." |
| 68 | sh "git remote remove origin || true" |
| 69 | sh "git remote add origin ${params.PROJECT_URL_PREFIX}/osm/devops" |
| 70 | sh "git fetch --depth=1 origin ${params.GERRIT_BRANCH}" |
| 71 | sh "git checkout -f FETCH_HEAD" |
| 72 | } |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | stage('License Scan') { |
| 77 | steps { |
| 78 | script { |
| 79 | def isMergeJob = env.JOB_NAME?.contains('merge') |
| 80 | if (!isMergeJob) { sh 'devops/tools/license_scan.sh' } else { echo 'skip the scan for merge' } |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | stage('Prepare Test Image') { |
| 86 | steps { |
| 87 | script { |
| 88 | // Use shared test image from registry; no local build needed |
| 89 | sh "docker pull ${env.TEST_IMAGE} || true" |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | |
| caviedesj | 31e83ba | 2026-01-08 18:33:54 +0100 | [diff] [blame] | 94 | stage('Tests') { |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 95 | steps { |
| 96 | script { |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 97 | def UID = sh(returnStdout: true, script: 'id -u').trim() |
| 98 | def GID = sh(returnStdout: true, script: 'id -g').trim() |
| 99 | def common = "-v ${env.WORKSPACE}:/tests -e UID=${UID} -e GID=${GID} " + (params.DOCKER_ARGS ?: '') |
| 100 | |
| caviedesj | 31e83ba | 2026-01-08 18:33:54 +0100 | [diff] [blame] | 101 | stage('Linting Tests') { |
| 102 | sh """ |
| 103 | docker run --rm ${common} \ |
| 104 | ${env.TEST_IMAGE} \ |
| 105 | /tests/devops-stages/stage-lint.sh |
| 106 | """ |
| 107 | } |
| 108 | |
| 109 | stage('Unit Tests') { |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 110 | sh """ |
| 111 | docker run --rm ${common} \ |
| 112 | ${env.TEST_IMAGE} \ |
| 113 | /tests/devops-stages/stage-test.sh |
| 114 | """ |
| caviedesj | 114cec1 | 2026-02-26 12:50:30 +0100 | [diff] [blame] | 115 | if (fileExists('coverage.xml')) { |
| 116 | recordCoverage tools: [[ |
| 117 | parser: 'COBERTURA', |
| 118 | pattern: 'coverage.xml' |
| 119 | ]], sourceDirectories: [] |
| 120 | } |
| 121 | |
| 122 | if (fileExists('nosetests.xml')) { |
| 123 | junit 'nosetests.xml' |
| 124 | } |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 125 | } |
| 126 | |
| 127 | stage('Changelog') { |
| 128 | sh 'mkdir -p changelog' |
| 129 | sh """ |
| 130 | docker run --rm ${common} \ |
| 131 | ${env.TEST_IMAGE} \ |
| garciadeblas | af00b51 | 2026-02-14 11:08:47 +0100 | [diff] [blame] | 132 | sh -c 'devops/tools/generatechangelog-pipeline.sh > /tests/changelog/changelog-${MDG}.html' |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 133 | """ |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | stage('Build & Push Image') { |
| 140 | when { expression { return params.DO_DOCKERPUSH } } |
| 141 | steps { |
| 142 | script { |
| 143 | def moduleName = (env.MDG ?: DEFAULT_MODULE_NAME).toLowerCase() |
| 144 | |
| 145 | if (!params.GERRIT_BRANCH) { |
| 146 | error 'GERRIT_BRANCH is required to tag the Docker image' |
| 147 | } |
| 148 | def sanitizedBranchName = params.GERRIT_BRANCH |
| 149 | .toLowerCase() |
| 150 | .replaceAll('[^a-z0-9._-]', '-') |
| 151 | def baseTagPrefix = "osm-${sanitizedBranchName}" |
| 152 | def buildNumber = env.BUILD_NUMBER ?: '0' |
| 153 | def isMergeJob = env.JOB_NAME?.contains('merge') |
| 154 | // Remove promotion logic from this stage |
| 155 | def moduleTags = [] |
| 156 | if (isMergeJob) { |
| caviedesj | 272cc5c | 2026-01-22 14:26:27 +0100 | [diff] [blame] | 157 | moduleTags << "${baseTagPrefix}-merge" |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 158 | } else { |
| 159 | moduleTags << "${baseTagPrefix}-patchset-${buildNumber}" |
| 160 | } |
| 161 | |
| 162 | def imageName = params.IMAGENAME ?: "opensourcemano/${moduleName}" |
| 163 | def primaryLocalImage = "${imageName}:${moduleTags[0]}" |
| 164 | |
| 165 | withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'gitlab-registry', |
| 166 | usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) { |
| 167 | sh """ |
| 168 | docker build -f Dockerfile.production -t ${primaryLocalImage} . |
| 169 | """ |
| 170 | sh "docker login ${env.DOCKER_REGISTRY.split('/')[0]} -u ${USERNAME} -p ${PASSWORD}" |
| 171 | // Push build-scope tag(s) only. Promotion happens after tests. |
| 172 | moduleTags.each { tag -> |
| 173 | def localImage = "${imageName}:${tag}" |
| 174 | def remoteImage = "${env.DOCKER_REGISTRY}${imageName}:${tag}" |
| 175 | sh "docker tag ${localImage} ${remoteImage}" |
| 176 | sh "docker push ${remoteImage}" |
| 177 | } |
| 178 | // Stash the built image id for later promotion without rebuild |
| 179 | env.BUILT_IMAGE = primaryLocalImage |
| 180 | env.BUILT_TAG = moduleTags[0] |
| 181 | } |
| 182 | } |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | stage('E2E Test (robot)') { |
| 187 | when { expression { return params.DO_ROBOT && !env.JOB_NAME.contains('merge') } } |
| 188 | steps { |
| 189 | script { |
| 190 | def dowstreamJob = "osm-e2e/${params.GERRIT_BRANCH ?: 'master'}" |
| 191 | build job: dowstreamJob, |
| 192 | parameters: [ |
| 193 | string(name: 'GERRIT_BRANCH', value: params.GERRIT_BRANCH ?: 'master'), |
| 194 | string(name: 'GERRIT_REFSPEC', value: params.GERRIT_REFSPEC ?: ''), |
| 195 | string(name: 'OPENSTACK_BASE_IMAGE', value: params.OPENSTACK_BASE_IMAGE), |
| 196 | string(name: 'OPENSTACK_OSM_FLAVOR', value: params.OPENSTACK_OSM_FLAVOR), |
| 197 | string(name: 'MODULE_NAME', value: params.MODULE_NAME ?: 'MON'), |
| 198 | string(name: 'CONTAINER_NAME', value: env.BUILT_TAG), |
| 199 | booleanParam(name: 'DO_ROBOT', value: params.DO_ROBOT), |
| 200 | booleanParam(name: 'DO_INSTALL', value: params.DO_INSTALL), |
| 201 | booleanParam(name: 'SAVE_CONTAINER_ON_FAIL', value: params.SAVE_CONTAINER_ON_FAIL), |
| 202 | booleanParam(name: 'SAVE_CONTAINER_ON_PASS', value: params.SAVE_CONTAINER_ON_PASS) |
| 203 | ] |
| 204 | } |
| 205 | } |
| 206 | } |
| 207 | stage('Image Promotion') { |
| 208 | when { expression { return env.JOB_NAME?.contains('merge') } } |
| 209 | steps { |
| 210 | script { |
| 211 | def moduleName = (env.MDG ?: DEFAULT_MODULE_NAME).toLowerCase() |
| 212 | def sanitizedBranchName = params.GERRIT_BRANCH |
| 213 | .toLowerCase() |
| 214 | .replaceAll('[^a-z0-9._-]', '-') |
| 215 | def baseTagPrefix = "osm-${sanitizedBranchName}" |
| 216 | def targetTag = "${baseTagPrefix}-merge" |
| 217 | def imageName = params.IMAGENAME ?: "opensourcemano/${moduleName}" |
| 218 | |
| 219 | withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'gitlab-registry', |
| 220 | usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) { |
| 221 | sh "docker login ${env.DOCKER_REGISTRY.split('/')[0]} -u ${USERNAME} -p ${PASSWORD}" |
| 222 | def remoteImage = "${env.DOCKER_REGISTRY}${imageName}:${targetTag}" |
| 223 | sh "docker tag ${env.BUILT_IMAGE} ${remoteImage}" |
| 224 | sh "docker push ${remoteImage}" |
| 225 | } |
| 226 | } |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | /* |
| 231 | Promotion should be done in a separate downstream job after E2E success to avoid |
| 232 | */ |
| 233 | } |
| caviedesj | 31e83ba | 2026-01-08 18:33:54 +0100 | [diff] [blame] | 234 | |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 235 | post { |
| 236 | always { |
| caviedesj | 31e83ba | 2026-01-08 18:33:54 +0100 | [diff] [blame] | 237 | // cleanWs() |
| 238 | deleteDir() |
| garciadeblas | 170d892 | 2025-12-26 12:11:25 +0100 | [diff] [blame] | 239 | } |
| 240 | } |
| garciadeblas | d2332d0 | 2025-12-23 12:33:00 +0100 | [diff] [blame] | 241 | } |
| 242 | |