blob: 06616fe0c54a0bd6bde6a79e583d4eef7d320d77 [file] [log] [blame]
garciadeblasd2b741e2025-10-31 11:12:37 +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
mesajbdf44322025-10-20 17:21:34 +020016def ciHelper
17def DEFAULT_MODULE_NAME = 'nbi'
garciadeblasd2b741e2025-10-31 11:12:37 +010018
mesajbdf44322025-10-20 17:21:34 +020019pipeline {
20 agent { label 'pool' }
21 options { disableConcurrentBuilds() }
22 parameters {
23 // Core Gerrit / multibranch inputs
24 string(name: 'GERRIT_BRANCH', defaultValue: env.BRANCH_NAME ?: 'master', description: '')
25 string(name: 'GERRIT_PROJECT', defaultValue: 'osm/NBI', description: '')
26 string(name: 'GERRIT_REFSPEC', defaultValue: env.GERRIT_REFSPEC ?: '', description: '')
27 string(name: 'GERRIT_PATCHSET_REVISION', defaultValue: env.GERRIT_PATCHSET_REVISION ?: '', description: '')
28 string(name: 'PROJECT_URL_PREFIX', defaultValue: 'https://osm.etsi.org/gerrit', description: '')
29 string(name: 'ARTIFACTORY_SERVER', defaultValue: 'artifactory-osm', description: '')
30 string(name: 'DOCKER_ARGS', defaultValue: '', description: 'Extra docker args for docker run')
31
32 // Stage 3 parameters (mirroring central ci_stage_3.groovy)
33 // Core installer/E2E toggles
34 string(name: 'DOCKER_TAG', defaultValue: 'testing-daily', description: 'Tests image tag for opensourcemano/tests')
35 string(name: 'INSTALLER', defaultValue: 'Default', description: '')
36 string(name: 'OPENSTACK_BASE_IMAGE', defaultValue: 'ubuntu22.04', description: '')
37 string(name: 'OPENSTACK_OSM_FLAVOR', defaultValue: 'osm.sanity', description: '')
38
39 booleanParam(name: 'DO_BUILD', defaultValue: true, description: '')
40 booleanParam(name: 'DO_INSTALL', defaultValue: true, description: '')
41 booleanParam(name: 'DO_DOCKERPUSH', defaultValue: true, description: '')
42 booleanParam(name: 'DO_ROBOT', defaultValue: true, description: '')
43 string(name: 'ROBOT_TAG_NAME', defaultValue: 'sanity', description: 'Robot tag (sanity/regression/daily)')
44 string(name: 'ROBOT_PASS_THRESHOLD', defaultValue: '100.0', description: 'Pass threshold (%)')
45 string(name: 'ROBOT_UNSTABLE_THRESHOLD', defaultValue: '80.0', description: 'Unstable threshold (%)')
46 string(name: 'MODULE_NAME', defaultValue: 'NBI', description: 'Name of the module under test')
47
48 // Paths and configs
49 string(name: 'KUBECONFIG', defaultValue: '/home/jenkins/hive/kubeconfig.yaml', description: '')
50 string(name: 'CLOUDS', defaultValue: '/home/jenkins/hive/clouds.yaml', description: '')
51 string(name: 'ROBOT_VIM', defaultValue: '/home/jenkins/hive/robot-systest.cfg', description: '')
52 string(name: 'ROBOT_PORT_MAPPING_VIM', defaultValue: '/home/jenkins/hive/port-mapping-etsi-vim.yaml', description: '')
53 string(name: 'PROMETHEUS_CONFIG_VIM', defaultValue: '/home/jenkins/hive/etsi-vim-prometheus.json', description: '')
54 string(name: 'HIVE_VIM_1', defaultValue: '/home/jenkins/hive/openstack-etsi.rc', description: '')
55
56 // Feature flags and saves
57 booleanParam(name: 'TRY_JUJU_INSTALLATION', defaultValue: true, description: '')
58 booleanParam(name: 'TRY_OLD_SERVICE_ASSURANCE', defaultValue: false, description: '')
59 booleanParam(name: 'SAVE_CONTAINER_ON_FAIL', defaultValue: false, description: '')
60 booleanParam(name: 'SAVE_CONTAINER_ON_PASS', defaultValue: false, description: '')
61 booleanParam(name: 'SAVE_ARTIFACTS_ON_SMOKE_SUCCESS', defaultValue: true, description: '')
62 booleanParam(name: 'SAVE_ARTIFACTS_OVERRIDE', defaultValue: false, description: '')
63
64 // Optional publishing/flow controls
65 string(name: 'GPG_KEY_NAME', defaultValue: 'OSMETSI', description: '')
66 string(name: 'RELEASE', defaultValue: 'release', description: '')
67 string(name: 'REPO_DISTRO', defaultValue: 'unstable', description: '')
68 string(name: 'REPO_KEY_NAME', defaultValue: 'pubkey.asc', description: '')
69 string(name: 'COMMIT_ID', defaultValue: '', description: '')
70 string(name: 'UPSTREAM_JOB_NAME', defaultValue: '', description: '')
71 string(name: 'UPSTREAM_JOB_NUMBER', defaultValue: '', description: '')
72 string(name: 'UPSTREAM_SUFFIX', defaultValue: '-stage_2', description: '')
73 string(name: 'DOWNSTREAM_STAGE_NAME', defaultValue: 'osm-stage_4', description: '')
74 // Downstream params from NBI/Jenkinsfile NEW PIPELINE
75 booleanParam(name: 'TEST_INSTALL', defaultValue: false, description: 'Enable Stage 3/E2E in future')
76 string(name: 'IMAGENAME', defaultValue: 'opensourcemano/nbi', description: 'Image name for publish (reserved)')
77 }
78 environment {
79 MDG = "${params.GERRIT_PROJECT?.contains('/') ? params.GERRIT_PROJECT.split('/')[1] : params.GERRIT_PROJECT}"
80 CONTAINER_NAME = "${params.GERRIT_PROJECT}-${params.GERRIT_BRANCH}".toLowerCase()
81 TEST_IMAGE = 'overdrive3000/tox-osm:v1.6'
82 DOCKER_REGISTRY = 'osm.etsi.org:5050/devops/cicd/'
83 }
84 stages {
85 stage('Prepare') { steps { sh 'env' } }
86
87 stage('Checkout') {
88 steps {
89 checkout scm
90 script {
91 sh "git fetch --tags"
92 if (params.GERRIT_REFSPEC?.trim()) { sh "git fetch origin ${params.GERRIT_REFSPEC}" }
93 if (params.GERRIT_PATCHSET_REVISION?.trim()) { sh "git checkout -f ${params.GERRIT_PATCHSET_REVISION}" }
94 sh "sudo git clean -dfx || git clean -dfx"
95 }
96 }
97 }
98
99 stage('Clone devops (central)') {
100 steps {
101 dir('devops') {
102 sh "git init ."
103 sh "git remote remove origin || true"
104 sh "git remote add origin ${params.PROJECT_URL_PREFIX}/osm/devops"
105 sh "git fetch --depth=1 origin ${params.GERRIT_BRANCH}"
106 sh "git checkout -f FETCH_HEAD"
107 }
108 }
109 }
110
111 stage('License Scan') {
112 steps {
113 script {
114 def isMergeJob = env.JOB_NAME?.contains('merge')
115 if (!isMergeJob) { sh 'devops/tools/license_scan.sh' } else { echo 'skip the scan for merge' }
116 }
117 }
118 }
119
120 stage('Prepare Test Image') {
121 steps {
122 script {
123 // Use shared test image from registry; no local build needed
124 sh "docker pull ${env.TEST_IMAGE} || true"
125 }
126 }
127 }
128
129 stage('Unit Tests') {
130 steps {
131 script {
132 if (!ciHelper) {
133 ciHelper = load 'devops/jenkins/ci-pipelines/ci_helper.groovy'
134 }
135 def UID = sh(returnStdout: true, script: 'id -u').trim()
136 def GID = sh(returnStdout: true, script: 'id -g').trim()
137 def common = "-v ${env.WORKSPACE}:/tests -e UID=${UID} -e GID=${GID} " + (params.DOCKER_ARGS ?: '')
138
139 stage('Test') {
140 sh """
141 docker run --rm ${common} \
142 ${env.TEST_IMAGE} \
143 /tests/devops-stages/stage-test.sh
144 """
145 if (fileExists('coverage.xml')) { cobertura coberturaReportFile: 'coverage.xml' }
146 if (fileExists('nosetests.xml')) { junit 'nosetests.xml' }
147 }
148
149 stage('Changelog') {
150 sh 'mkdir -p changelog'
151 sh """
152 docker run --rm ${common} \
153 ${env.TEST_IMAGE} \
154 /bin/sh -lc 'devops/tools/generatechangelog-pipeline.sh > /tests/changelog/changelog-${MDG}.html'
155 """
156 }
157 }
158 }
159 }
160
161 stage('Build & Push Image') {
162 when { expression { return params.DO_DOCKERPUSH } }
163 steps {
164 script {
165 def moduleName = (env.MDG ?: DEFAULT_MODULE_NAME).toLowerCase()
166
167 if (!params.GERRIT_BRANCH) {
168 error 'GERRIT_BRANCH is required to tag the Docker image'
169 }
170 def sanitizedBranchName = params.GERRIT_BRANCH
171 .toLowerCase()
172 .replaceAll('[^a-z0-9._-]', '-')
173 def baseTagPrefix = "osm-${sanitizedBranchName}"
174 def buildNumber = env.BUILD_NUMBER ?: '0'
175 def isMergeJob = env.JOB_NAME?.contains('merge')
176 // Remove promotion logic from this stage
177 def moduleTags = []
178 if (isMergeJob) {
179 moduleTags << "${baseTagPrefix}-merge-${buildNumber}"
180 } else {
181 moduleTags << "${baseTagPrefix}-patchset-${buildNumber}"
182 }
183
184 def imageName = params.IMAGENAME ?: "opensourcemano/${moduleName}"
185 def primaryLocalImage = "${imageName}:${moduleTags[0]}"
186
187 withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'gitlab-registry',
188 usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) {
189 sh """
190 docker build -f Dockerfile.production -t ${primaryLocalImage} .
191 """
192 sh "docker login ${env.DOCKER_REGISTRY.split('/')[0]} -u ${USERNAME} -p ${PASSWORD}"
193 // Push build-scope tag(s) only. Promotion happens after tests.
194 moduleTags.each { tag ->
195 def localImage = "${imageName}:${tag}"
196 def remoteImage = "${env.DOCKER_REGISTRY}${imageName}:${tag}"
197 sh "docker tag ${localImage} ${remoteImage}"
198 sh "docker push ${remoteImage}"
199 }
200 // Stash the built image id for later promotion without rebuild
201 env.BUILT_IMAGE = primaryLocalImage
202 env.BUILT_TAG = moduleTags[0]
203 }
204 }
205 }
206 }
207
208 stage('E2E Test (robot)') {
209 when { expression { return params.DO_ROBOT && !env.JOB_NAME.contains('merge') } }
210 steps {
211 script {
212 def dowstreamJob = "osm-e2e/${params.GERRIT_BRANCH ?: 'master'}"
213 build job: dowstreamJob,
214 parameters: [
215 string(name: 'GERRIT_BRANCH', value: params.GERRIT_BRANCH ?: 'master'),
216 string(name: 'GERRIT_REFSPEC', value: params.GERRIT_REFSPEC ?: ''),
217 string(name: 'OPENSTACK_BASE_IMAGE', value: params.OPENSTACK_BASE_IMAGE),
218 string(name: 'OPENSTACK_OSM_FLAVOR', value: params.OPENSTACK_OSM_FLAVOR),
219 string(name: 'MODULE_NAME', value: params.MODULE_NAME ?: 'NBI'),
220 string(name: 'CONTAINER_NAME', value: env.BUILT_TAG),
221 booleanParam(name: 'DO_ROBOT', value: params.DO_ROBOT),
222 booleanParam(name: 'DO_INSTALL', value: params.DO_INSTALL),
223 booleanParam(name: 'SAVE_CONTAINER_ON_FAIL', value: params.SAVE_CONTAINER_ON_FAIL),
224 booleanParam(name: 'SAVE_CONTAINER_ON_PASS', value: params.SAVE_CONTAINER_ON_PASS)
225 ]
226 }
227 }
228 }
229 stage('Image Promotion') {
230 when { expression { return env.JOB_NAME?.contains('merge') } }
231 steps {
232 script {
233 def moduleName = (env.MDG ?: DEFAULT_MODULE_NAME).toLowerCase()
234 def sanitizedBranchName = params.GERRIT_BRANCH
235 .toLowerCase()
236 .replaceAll('[^a-z0-9._-]', '-')
237 def baseTagPrefix = "osm-${sanitizedBranchName}"
238 def targetTag = "${baseTagPrefix}-merge"
239 def imageName = params.IMAGENAME ?: "opensourcemano/${moduleName}"
240
241 withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'gitlab-registry',
242 usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) {
243 sh "docker login ${env.DOCKER_REGISTRY.split('/')[0]} -u ${USERNAME} -p ${PASSWORD}"
244 def remoteImage = "${env.DOCKER_REGISTRY}${imageName}:${targetTag}"
245 sh "docker tag ${env.BUILT_IMAGE} ${remoteImage}"
246 sh "docker push ${remoteImage}"
247 }
248 }
249 }
250 }
251
252 /*
253 Promotion should be done in a separate downstream job after E2E success to avoid
254 */
255 }
256 post {
257 always {
258 cleanWs()
259 }
260 }
261}