NSM: Update Jenkinsfile for meridio
[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 node ('nordix-nsm-build-ubuntu1804') {
28     build_number = env.BUILD_NUMBER
29     workspace = env.WORKSPACE
30     ws("${workspace}/${build_number}") {
31         def image_names = params.IMAGE_NAMES.split(' ')
32         def version = params.IMAGE_VERSION
33         def e2e_enabled = params.E2E_ENABLED
34         def git_project = params.GIT_PROJECT
35         def current_branch = params.CURRENT_BRANCH
36         def default_branch = params.DEFAULT_BRANCH
37         def build_steps = params.BUILD_STEPS
38         def image_registry = params.IMAGE_REGISTRY
39         def local_version =  "${env.JOB_NAME}-${build_number}"
40
41         stage('Clone/Checkout') {
42             git branch: default_branch, url: git_project
43             checkout([
44                 $class: 'GitSCM',
45                 branches: [[name: current_branch]],
46                 extensions: [],
47                 userRemoteConfigs: [[
48                     refspec: '+refs/pull/*/head:refs/remotes/origin/pr/*',
49                     url: git_project
50                 ]]
51             ])
52             sh 'git show'
53         }
54         stage('Verify') {
55             Verify().call()
56         }
57         stage('Docker login') {
58             wrap([$class: 'MaskPasswordsBuildWrapper', varPasswordPairs: [[password: env.HARBOR_USERNAME, var: 'HARBOR_USERNAME'], [password: env.HARBOR_PASSWORD, var: 'HARBOR_PASSWORD'], [password: image_registry, var: 'IMAGE_REGISTRY']]]) {
59                 sh '''#!/bin/bash -eu
60                 echo ${HARBOR_PASSWORD} | docker login --username ${HARBOR_USERNAME} --password-stdin ${IMAGE_REGISTRY}
61                 '''
62             }
63         }
64         stage('Base Image') {
65             BaseImage(version, build_steps, image_registry, local_version).call()
66         }
67         stage('Images') {
68             Images(image_names, version, build_steps, image_registry, local_version).call()
69         }
70         stage('E2E') {
71             E2e(e2e_enabled).call()
72         }
73         stage('Cleanup') {
74             Cleanup()
75         }
76     }
77 }
78
79 // Static analysis: Runs the GeneratedCode function and then UnitTests and Linter in parallel
80 def Verify() {
81     return {
82         GeneratedCode().call() // cannot generate code and run the linter and tests at the same time
83         // Linter().call()
84         // UnitTests().call()
85         def stages = [:]
86         stages.put('Unit Tests', UnitTests())
87         stages.put('Linter', Linter())
88         // stages.put('Generated code verification', GeneratedCode())
89         parallel(stages)
90     }
91 }
92
93 // Runs the unit tests and set the github commit status
94 def UnitTests() {
95     return {
96         def context = 'Unit Tests'
97         stage('Unit Tests') {
98             try {
99                 SetBuildStatus(in_progress, context, pending)
100                 sh '''
101                     . \${HOME}/.profile
102                     make test
103                 '''
104                 SetBuildStatus(completed, context, success)
105             } catch (Exception e) {
106                 SetBuildStatus(failed, context, failure)
107                 Error(e).call()
108             }
109         }
110     }
111 }
112
113 // Runs the linter and set the github commit status
114 def Linter() {
115     return {
116         def context = 'Linter'
117         stage('Linter') {
118             try {
119                 SetBuildStatus(in_progress, context, pending)
120                 sh '''
121                     . \${HOME}/.profile
122                    make lint
123                 '''
124                 SetBuildStatus(completed, context, success)
125             } catch (Exception e) {
126                 SetBuildStatus(failed, context, failure)
127                 Error(e).call()
128             }
129         }
130     }
131 }
132
133 // Check if code has been generated correctly and set the github commit status:
134 // go.mod: runs "go mod tidy"
135 // go generate ./...: Code should be generated using "make genrate" command
136 // proto: skipped due to version of protoc
137 // If files are generated correctly then GetModifiedFiles function should return an empty string
138 def GeneratedCode() {
139     return {
140         def context = 'Generated code verification'
141         def exception_message = 'Generated code verification failed'
142         SetBuildStatus(in_progress, context, pending)
143         stage('go mod tidy') {
144             try {
145                 sh '''
146                     . \${HOME}/.profile
147                     go mod tidy
148                 '''
149                 if (GetModifiedFiles() != '') {
150                     throw new Exception(exception_message)
151                 }
152             } catch (Exception e) {
153                 SetBuildStatus(failed, context, failure)
154                 sh 'git diff'
155                 sh 'git status -s'
156                 Error(e).call()
157             }
158         }
159         stage('go generate ./...') {
160             try {
161                 sh '''
162                     . \${HOME}/.profile
163                     make generate
164                 '''
165                 if (GetModifiedFiles() != '') {
166                     throw new Exception(exception_message)
167                 }
168             } catch (Exception e) {
169                 SetBuildStatus(failed, context, failure)
170                 sh 'git diff'
171                 sh 'git status -s'
172                 Error(e).call()
173             }
174         }
175         stage('Proto') {
176             // TODO: protoc version could be different
177             Utils.markStageSkippedForConditional('Proto')
178         // try {
179         //     sh 'make proto'
180         //     if (GetModifiedFiles() != '') {
181         //         throw new Exception(exception_message)
182         //     }
183         // } catch (Exception e) {
184         //     SetBuildStatus(failed, context, failure)
185         //     sh 'git diff'
186         //     sh 'git status -s'
187         //     Error(e).call()
188         // }
189         }
190         SetBuildStatus(completed, context, success)
191     }
192 }
193
194 def BaseImage(version, build_steps, registry, local_version) {
195     return {
196         Build(base_image, version, build_steps, registry, local_version).call()
197     }
198 }
199
200 // Call Build function for every images in parallel
201 def Images(images, version, build_steps, registry, local_version) {
202     return {
203         def stages = [:]
204         for (i in images) {
205             stages.put(i, Build(i, version, build_steps, registry, local_version))
206         }
207         parallel(stages)
208     }
209 }
210
211 // Build set the github commit status
212 def Build(image, version, build_steps, registry, local_version) {
213     return {
214         stage("${image} (${version}): ${build_steps}") {
215             def context = "Image: ${image}"
216             def in_progress_message = "${in_progress} (${build_steps})"
217             def completed_message = "${completed} (${build_steps})"
218             def failed_message = "${failed} (${build_steps})"
219             try {
220                 SetBuildStatus(in_progress_message, context, pending)
221                 sh "make ${image} VERSION=${version} BUILD_STEPS='${build_steps}' REGISTRY=${registry} LOCAL_VERSION=${local_version} BASE_IMAGE=${base_image}:${local_version}"
222                 SetBuildStatus(completed_message, context, success)
223             } catch (Exception e) {
224                 SetBuildStatus(failed_message, context, failure)
225                 Error(e).call()
226             }
227         }
228     }
229 }
230
231 // Run the E2e Tests
232 // Currently skipped
233 def E2e(e2e_enabled) {
234     if (e2e_enabled == 'true') {
235         return {
236             echo 'make e2e' // todo
237         }
238     } else {
239         return {
240             Utils.markStageSkippedForConditional('E2E')
241         }
242     }
243 }
244
245 // Raise error in Jenkins job
246 def Error(e) {
247     return {
248         Cleanup()
249         error e
250     }
251 }
252
253 // Cleanup directory
254 def Cleanup() {
255     cleanWs()
256 }
257
258 // Set the commit status on Github
259 // https://plugins.jenkins.io/github/#plugin-content-pipeline-examples
260 def SetBuildStatus(String message, String context, String state) {
261     step([
262         $class: 'GitHubCommitStatusSetter',
263         reposSource: [$class: 'ManuallyEnteredRepositorySource', url: 'https://github.com/Nordix/Meridio'],
264         commitShaSource: [$class: 'ManuallyEnteredShaSource', sha: GetCommitSha()],
265         contextSource: [$class: 'ManuallyEnteredCommitContextSource', context: context],
266         errorHandlers: [[$class: 'ChangingBuildStatusErrorHandler', result: 'UNSTABLE']],
267         statusResultSource: [ $class: 'ConditionalStatusResultSource', results: [[$class: 'AnyBuildResult', message: message, state: state]] ]
268   ])
269 }
270
271 // Return the current commit sha
272 def GetCommitSha() {
273     return sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
274 }
275
276 // Returns if any files has been modified/added/removed
277 def GetModifiedFiles() {
278     return sh(script: 'git status -s', returnStdout: true).trim()
279 }