Meridio: update node to 2204
[infra/cicd.git] / jjb / nsm / Jenkinsfile
1 /*
2 Copyright (c) 2022 Nordix Foundation
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8     http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
18
19 pending = 'PENDING'
20 success = 'SUCCESS'
21 failure = 'FAILURE'
22 base_image = 'base-image'
23 in_progress = 'In Progress.'
24 completed = 'Completed.'
25 failed = 'Failed'
26
27 exception_message_exec = 'failed to execute the following command: '
28 exception_message_code_generation = 'Generated code verification failed'
29
30 node('nordix-nsm-build-ubuntu2204') {
31     build_number = env.BUILD_NUMBER
32     workspace = env.WORKSPACE
33     ws("${workspace}/${build_number}") {
34         def image_names = params.IMAGE_NAMES.split(' ')
35         def version = params.IMAGE_VERSION
36         def e2e_enabled = params.E2E_ENABLED
37         def git_project = params.GIT_PROJECT
38         def current_branch = params.CURRENT_BRANCH
39         def default_branch = params.DEFAULT_BRANCH
40         def build_steps = params.BUILD_STEPS
41         def image_registry = params.IMAGE_REGISTRY
42         def local_version =  "${env.JOB_NAME}-${build_number}"
43
44         timeout(30) {
45             stage('Clone/Checkout') {
46                 git branch: default_branch, url: git_project
47                 checkout([
48                     $class: 'GitSCM',
49                     branches: [[name: current_branch]],
50                     extensions: [],
51                     userRemoteConfigs: [[
52                         refspec: '+refs/pull/*/head:refs/remotes/origin/pr/*',
53                         url: git_project
54                     ]]
55                 ])
56                 sh 'git show'
57             }
58             stage('Verify') {
59                 Verify().call()
60                 if (currentBuild.result == 'FAILURE') {
61                     Error('Failed at verification stage').call()
62                 }
63             }
64             stage('Docker login') {
65                 if (env.DRY_RUN != 'true') {
66                     withCredentials([usernamePassword(credentialsId: 'nordix-cicd-harbor-credentials', passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USERNAME')]) {
67                         sh '''#!/bin/bash -eu
68                     echo $HARBOR_PASSWORD | docker login --username $HARBOR_USERNAME --password-stdin $IMAGE_REGISTRY
69                     '''
70                     }
71                 } else {
72                     Utils.markStageSkippedForConditional('Docker login')
73                 }
74             }
75             stage('Base Image') {
76                 BaseImage(version, build_steps, image_registry, local_version).call()
77             }
78             stage('Images') {
79                 Images(image_names, version, build_steps, image_registry, local_version).call()
80                 if (currentBuild.result == 'FAILURE') {
81                     Error('Failed to build image(s)').call()
82                 }
83             }
84             stage('E2E') {
85                 if (e2e_enabled == 'true' && env.DRY_RUN != 'true') {
86                     E2e(e2e_enabled).call()
87                 } else {
88                     Utils.markStageSkippedForConditional('E2E')
89                 }
90             }
91         }
92         stage('Cleanup') {
93             Cleanup()
94         }
95     }
96 }
97
98 // Static analysis: Runs the GeneratedCode function and then UnitTests and Linter in parallel
99 def Verify() {
100     return {
101         GeneratedCode().call() // cannot generate code and run the linter and tests at the same time
102         // Linter().call()
103         // UnitTests().call()
104         def stages = [:]
105         stages.put('Unit Tests', UnitTests())
106         stages.put('Linter', Linter())
107         // stages.put('Generated code verification', GeneratedCode())
108         parallel(stages)
109     }
110 }
111
112 // Runs the unit tests and set the github commit status
113 def UnitTests() {
114     return {
115         def context = 'Unit Tests'
116         stage('Unit Tests') {
117             def command = 'make test'
118             try {
119                 SetBuildStatus(in_progress, context, pending)
120                 ExecSh(command).call()
121                 SetBuildStatus(completed, context, success)
122             } catch (Exception e) {
123                 SetBuildStatus(failed, context, failure)
124                 unstable "${exception_message_exec} ${command}"
125                 currentBuild.result = 'FAILURE'
126             }
127         }
128     }
129 }
130
131 // Runs the linter and set the github commit status
132 def Linter() {
133     return {
134         def context = 'Linter'
135         stage('Linter') {
136             def command = 'make lint'
137             try {
138                 SetBuildStatus(in_progress, context, pending)
139                 ExecSh(command).call()
140                 SetBuildStatus(completed, context, success)
141             } catch (Exception e) {
142                 SetBuildStatus(failed, context, failure)
143                 unstable "${exception_message_exec} ${command}"
144                 currentBuild.result = 'FAILURE'
145             }
146         }
147     }
148 }
149
150 // Check if code has been generated correctly and set the github commit status:
151 // go.mod: runs "go mod tidy"
152 // go generate ./...: Code should be generated using "make genrate" command
153 // proto: skipped due to version of protoc
154 // If files are generated correctly then GetModifiedFiles function should return an empty string
155 def GeneratedCode() {
156     return {
157         def context = 'Generated code verification'
158         SetBuildStatus(in_progress, context, pending)
159         stage('go mod tidy') {
160             def command = 'go mod tidy'
161             try {
162                 ExecSh(command).call()
163                 if (GetModifiedFiles() != '') {
164                     throw new Exception(exception_message_code_generation)
165                 }
166             } catch (Exception e) {
167                 SetBuildStatus(failed, context, failure)
168                 Error(exception_message_exec + command).call()
169             }
170         }
171         stage('go generate ./...') {
172             def command = 'make generate'
173             try {
174                 ExecSh(command).call()
175                 if (GetModifiedFiles() != '') {
176                     throw new Exception(exception_message_code_generation)
177                 }
178             } catch (Exception e) {
179                 SetBuildStatus(failed, context, failure)
180                 Error(exception_message_exec + command).call()
181             }
182         }
183         stage('Proto') {
184             // TODO: protoc version could be different
185             Utils.markStageSkippedForConditional('Proto')
186         }
187         SetBuildStatus(completed, context, success)
188     }
189 }
190
191 def BaseImage(version, build_steps, registry, local_version) {
192     return {
193         Build(base_image, version, build_steps, registry, local_version).call()
194     }
195 }
196
197 // Call Build function for every images in parallel
198 def Images(images, version, build_steps, registry, local_version) {
199     return {
200         def stages = [:]
201         for (i in images) {
202             stages.put(i, Build(i, version, build_steps, registry, local_version))
203         }
204         parallel(stages)
205     }
206 }
207
208 // Build set the github commit status
209 def Build(image, version, build_steps, registry, local_version) {
210     return {
211         stage("${image} (${version}): ${build_steps}") {
212             def context = "Image: ${image}"
213             def in_progress_message = "${in_progress} (${build_steps})"
214             def completed_message = "${completed} (${build_steps})"
215             def failed_message = "${failed} (${build_steps})"
216             def command = "make ${image} VERSION=${version} BUILD_STEPS='${build_steps}' REGISTRY=${registry} LOCAL_VERSION=${local_version} BASE_IMAGE=${base_image}:${local_version}"
217             try {
218                 SetBuildStatus(in_progress_message, context, pending)
219                 ExecSh(command).call()
220                 SetBuildStatus(completed_message, context, success)
221             } catch (Exception e) {
222                 SetBuildStatus(failed_message, context, failure)
223                 unstable "${exception_message_exec} ${command}"
224                 currentBuild.result = 'FAILURE'
225             }
226         }
227     }
228 }
229
230 // Run the E2e Tests
231 // Currently skipped
232 def E2e(e2e_enabled) {
233     return {
234         echo 'make e2e' // todo
235     }
236 }
237
238 // Raise error in Jenkins job
239 def Error(e) {
240     return {
241         sh 'git diff'
242         sh 'git status -s'
243         Cleanup()
244         error e
245     }
246 }
247
248 // Cleanup directory
249 def Cleanup() {
250     cleanWs()
251 }
252
253 // Execute command
254 def ExecSh(command) {
255     return {
256         if (env.DRY_RUN != 'true') {
257             sh """
258                 . \${HOME}/.profile
259                 ${command}
260             """
261         } else {
262             echo "${command}"
263         }
264     }
265 }
266
267 // Set the commit status on Github
268 // https://plugins.jenkins.io/github/#plugin-content-pipeline-examples
269 def SetBuildStatus(String message, String context, String state) {
270     if (env.DRY_RUN != 'true') {
271         step([
272             $class: 'GitHubCommitStatusSetter',
273             reposSource: [$class: 'ManuallyEnteredRepositorySource', url: 'https://github.com/Nordix/Meridio'],
274             commitShaSource: [$class: 'ManuallyEnteredShaSource', sha: GetCommitSha()],
275             contextSource: [$class: 'ManuallyEnteredCommitContextSource', context: context],
276             errorHandlers: [[$class: 'ShallowAnyErrorHandler']], // Prevent GitHubCommitStatusSetter to set the job status to unstable
277             statusResultSource: [ $class: 'ConditionalStatusResultSource', results: [[$class: 'AnyBuildResult', message: message, state: state]] ]
278         ])
279     }
280 }
281
282 // Return the current commit sha
283 def GetCommitSha() {
284     return sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
285 }
286
287 // Returns if any files has been modified/added/removed
288 def GetModifiedFiles() {
289     return sh(script: 'git status -s', returnStdout: true).trim()
290 }