blob: 9e1526667c1e1e1914be24f3e83d03ff9f540d67 [file] [log] [blame]
garciadeblas0dafa222025-12-23 22:53:50 +01001/*
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
caviedesjf3b2e5f2026-01-09 11:45:02 +010016def DEFAULT_MODULE_NAME = 'tests'
garciadeblas0dafa222025-12-23 22:53:50 +010017
caviedesjf3b2e5f2026-01-09 11:45:02 +010018pipeline {
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/tests', 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: '')
caviedesjf3b2e5f2026-01-09 11:45:02 +010028 string(name: 'DOCKER_ARGS', defaultValue: '', description: 'Extra docker args for docker run')
29
caviedesjf3d5e7a2026-02-12 11:33:07 +010030 // OpenStack configuration
caviedesj9bacd942026-01-20 16:31:02 +010031 string(name: 'OPENSTACK_BASE_IMAGE', defaultValue: 'ubuntu24.04', description: '')
caviedesjf3b2e5f2026-01-09 11:45:02 +010032 string(name: 'OPENSTACK_OSM_FLAVOR', defaultValue: 'osm.sanity', description: '')
33
caviedesjf3d5e7a2026-02-12 11:33:07 +010034 // Pipeline control flags
caviedesjf3b2e5f2026-01-09 11:45:02 +010035 booleanParam(name: 'DO_INSTALL', defaultValue: true, description: '')
36 booleanParam(name: 'DO_DOCKERPUSH', defaultValue: true, description: '')
37 booleanParam(name: 'DO_ROBOT', defaultValue: true, description: '')
caviedesjf3d5e7a2026-02-12 11:33:07 +010038
39 // Module configuration
caviedesjf3b2e5f2026-01-09 11:45:02 +010040 string(name: 'MODULE_NAME', defaultValue: 'tests', description: 'Name of the module under test')
caviedesjf3d5e7a2026-02-12 11:33:07 +010041 string(name: 'IMAGENAME', defaultValue: 'opensourcemano/tests', description: 'Image name for publish')
caviedesjf3b2e5f2026-01-09 11:45:02 +010042
caviedesjf3d5e7a2026-02-12 11:33:07 +010043 // Container save options
caviedesjf3b2e5f2026-01-09 11:45:02 +010044 booleanParam(name: 'SAVE_CONTAINER_ON_FAIL', defaultValue: false, description: '')
45 booleanParam(name: 'SAVE_CONTAINER_ON_PASS', defaultValue: false, description: '')
caviedesjf3b2e5f2026-01-09 11:45:02 +010046 }
47 environment {
48 MDG = "${params.GERRIT_PROJECT?.contains('/') ? params.GERRIT_PROJECT.split('/')[1] : params.GERRIT_PROJECT}"
49 CONTAINER_NAME = "${params.GERRIT_PROJECT}-${params.GERRIT_BRANCH}".toLowerCase()
caviedesj28e7dcd2026-02-12 18:43:13 +010050 TEST_IMAGE = 'opensourcemano/tox-osm:19.0'
caviedesjf3b2e5f2026-01-09 11:45:02 +010051 DOCKER_REGISTRY = 'osm.etsi.org:5050/devops/cicd/'
52 }
53 stages {
54 stage('Prepare') { steps { sh 'env' } }
55
56 stage('Checkout') {
57 steps {
58 checkout scm
59 script {
60 sh "git fetch --tags"
61 if (params.GERRIT_REFSPEC?.trim()) { sh "git fetch origin ${params.GERRIT_REFSPEC}" }
62 if (params.GERRIT_PATCHSET_REVISION?.trim()) { sh "git checkout -f ${params.GERRIT_PATCHSET_REVISION}" }
63 sh "sudo git clean -dfx || git clean -dfx"
64 }
65 }
66 }
67
68 stage('Clone devops (central)') {
69 steps {
70 dir('devops') {
71 sh "git init ."
72 sh "git remote remove origin || true"
73 sh "git remote add origin ${params.PROJECT_URL_PREFIX}/osm/devops"
74 sh "git fetch --depth=1 origin ${params.GERRIT_BRANCH}"
75 sh "git checkout -f FETCH_HEAD"
76 }
77 }
78 }
79
80 stage('License Scan') {
81 steps {
82 script {
83 def isMergeJob = env.JOB_NAME?.contains('merge')
84 if (!isMergeJob) { sh 'devops/tools/license_scan.sh' } else { echo 'skip the scan for merge' }
85 }
86 }
87 }
88
89 stage('Prepare Test Image') {
90 steps {
91 script {
92 // Use shared test image from registry; no local build needed
93 sh "docker pull ${env.TEST_IMAGE} || true"
94 }
95 }
96 }
97
98 stage('Tests') {
99 steps {
100 script {
101 def UID = sh(returnStdout: true, script: 'id -u').trim()
102 def GID = sh(returnStdout: true, script: 'id -g').trim()
103 def common = "-v ${env.WORKSPACE}:/tests -e UID=${UID} -e GID=${GID} " + (params.DOCKER_ARGS ?: '')
104
105 stage('Linting Tests') {
106 sh """
107 docker run --rm ${common} \
108 ${env.TEST_IMAGE} \
109 /tests/devops-stages/stage-lint.sh
110 """
111 }
112
113 stage('Unit Tests') {
114 sh """
115 docker run --rm ${common} \
116 ${env.TEST_IMAGE} \
117 /tests/devops-stages/stage-test.sh
118 """
119 if (fileExists('coverage.xml')) { cobertura coberturaReportFile: 'coverage.xml' }
120 if (fileExists('nosetests.xml')) { junit 'nosetests.xml' }
121 }
122
123 stage('Changelog') {
124 sh 'mkdir -p changelog'
125 sh """
126 docker run --rm ${common} \
127 ${env.TEST_IMAGE} \
garciadeblasc8159182026-02-14 11:10:38 +0100128 sh -c 'devops/tools/generatechangelog-pipeline.sh > /tests/changelog/changelog-${MDG}.html'
caviedesjf3b2e5f2026-01-09 11:45:02 +0100129 /bin/sh -lc 'devops/tools/generatechangelog-pipeline.sh > /tests/changelog/changelog-${MDG}.html'
130 """
131 }
132 }
133 }
134 }
135
136 stage('Build & Push Image') {
137 when { expression { return params.DO_DOCKERPUSH } }
138 steps {
139 script {
140 def moduleName = (env.MDG ?: DEFAULT_MODULE_NAME).toLowerCase()
141
142 if (!params.GERRIT_BRANCH) {
143 error 'GERRIT_BRANCH is required to tag the Docker image'
144 }
145 def sanitizedBranchName = params.GERRIT_BRANCH
146 .toLowerCase()
147 .replaceAll('[^a-z0-9._-]', '-')
148 def baseTagPrefix = "osm-${sanitizedBranchName}"
149 def buildNumber = env.BUILD_NUMBER ?: '0'
150 def isMergeJob = env.JOB_NAME?.contains('merge')
151 // Remove promotion logic from this stage
152 def moduleTags = []
153 if (isMergeJob) {
caviedesj25c04162026-01-22 14:24:52 +0100154 moduleTags << "${baseTagPrefix}-merge"
caviedesjf3b2e5f2026-01-09 11:45:02 +0100155 } else {
156 moduleTags << "${baseTagPrefix}-patchset-${buildNumber}"
157 }
158
159 def imageName = params.IMAGENAME ?: "opensourcemano/${moduleName}"
160 def primaryLocalImage = "${imageName}:${moduleTags[0]}"
161
162 withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'gitlab-registry',
163 usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) {
164 sh """
165 docker build -f Dockerfile.production -t ${primaryLocalImage} .
166 """
167 sh "docker login ${env.DOCKER_REGISTRY.split('/')[0]} -u ${USERNAME} -p ${PASSWORD}"
168 // Push build-scope tag(s) only. Promotion happens after tests.
169 moduleTags.each { tag ->
170 def localImage = "${imageName}:${tag}"
171 def remoteImage = "${env.DOCKER_REGISTRY}${imageName}:${tag}"
172 sh "docker tag ${localImage} ${remoteImage}"
173 sh "docker push ${remoteImage}"
174 }
175 // Stash the built image id for later promotion without rebuild
176 env.BUILT_IMAGE = primaryLocalImage
177 env.BUILT_TAG = moduleTags[0]
178 }
179 }
180 }
181 }
182
183 stage('E2E Test (robot)') {
184 when { expression { return params.DO_ROBOT && !env.JOB_NAME.contains('merge') } }
185 steps {
186 script {
187 def dowstreamJob = "osm-e2e/${params.GERRIT_BRANCH ?: 'master'}"
188 build job: dowstreamJob,
189 parameters: [
190 string(name: 'GERRIT_BRANCH', value: params.GERRIT_BRANCH ?: 'master'),
191 string(name: 'GERRIT_REFSPEC', value: params.GERRIT_REFSPEC ?: ''),
192 string(name: 'OPENSTACK_BASE_IMAGE', value: params.OPENSTACK_BASE_IMAGE),
193 string(name: 'OPENSTACK_OSM_FLAVOR', value: params.OPENSTACK_OSM_FLAVOR),
194 string(name: 'MODULE_NAME', value: params.MODULE_NAME ?: 'tests'),
195 string(name: 'CONTAINER_NAME', value: env.BUILT_TAG),
196 booleanParam(name: 'DO_ROBOT', value: params.DO_ROBOT),
197 booleanParam(name: 'DO_INSTALL', value: params.DO_INSTALL),
198 booleanParam(name: 'SAVE_CONTAINER_ON_FAIL', value: params.SAVE_CONTAINER_ON_FAIL),
199 booleanParam(name: 'SAVE_CONTAINER_ON_PASS', value: params.SAVE_CONTAINER_ON_PASS)
200 ]
201 }
202 }
203 }
204 stage('Image Promotion') {
205 when { expression { return env.JOB_NAME?.contains('merge') } }
206 steps {
207 script {
208 def moduleName = (env.MDG ?: DEFAULT_MODULE_NAME).toLowerCase()
209 def sanitizedBranchName = params.GERRIT_BRANCH
210 .toLowerCase()
211 .replaceAll('[^a-z0-9._-]', '-')
212 def baseTagPrefix = "osm-${sanitizedBranchName}"
213 def targetTag = "${baseTagPrefix}-merge"
214 def imageName = params.IMAGENAME ?: "opensourcemano/${moduleName}"
215
216 withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'gitlab-registry',
217 usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) {
218 sh "docker login ${env.DOCKER_REGISTRY.split('/')[0]} -u ${USERNAME} -p ${PASSWORD}"
219 def remoteImage = "${env.DOCKER_REGISTRY}${imageName}:${targetTag}"
220 sh "docker tag ${env.BUILT_IMAGE} ${remoteImage}"
221 sh "docker push ${remoteImage}"
222 }
223 }
224 }
225 }
226
227 /*
228 Promotion should be done in a separate downstream job after E2E success to avoid
229 */
230 }
231
232 post {
233 always {
234 // cleanWs()
235 deleteDir()
236 }
237 }
238}