Merge "Meridio: New PR and periodic jobs"
[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 {
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 'make test'
101                 SetBuildStatus(completed, context, success)
102             } catch (Exception e) {
103                 SetBuildStatus(failed, context, failure)
104                 Error(e).call()
105             }
106         }
107     }
108 }
109
110 // Runs the linter and set the github commit status
111 def Linter() {
112     return {
113         def context = 'Linter'
114         stage('Linter') {
115             try {
116                 SetBuildStatus(in_progress, context, pending)
117                 sh 'make lint'
118                 SetBuildStatus(completed, context, success)
119             } catch (Exception e) {
120                 SetBuildStatus(failed, context, failure)
121                 Error(e).call()
122             }
123         }
124     }
125 }
126
127 // Check if code has been generated correctly and set the github commit status:
128 // go.mod: runs "go mod tidy"
129 // go generate ./...: Code should be generated using "make genrate" command
130 // proto: skipped due to version of protoc
131 // If files are generated correctly then GetModifiedFiles function should return an empty string
132 def GeneratedCode() {
133     return {
134         def context = 'Generated code verification'
135         def exception_message = 'Generated code verification failed'
136         SetBuildStatus(in_progress, context, pending)
137         stage('go mod tidy') {
138             try {
139                 sh 'go mod tidy'
140                 if (GetModifiedFiles() != '') {
141                     throw new Exception(exception_message)
142                 }
143             } catch (Exception e) {
144                 SetBuildStatus(failed, context, failure)
145                 sh 'git diff'
146                 sh 'git status -s'
147                 Error(e).call()
148             }
149         }
150         stage('go generate ./...') {
151             try {
152                 sh 'make generate'
153                 if (GetModifiedFiles() != '') {
154                     throw new Exception(exception_message)
155                 }
156             } catch (Exception e) {
157                 SetBuildStatus(failed, context, failure)
158                 sh 'git diff'
159                 sh 'git status -s'
160                 Error(e).call()
161             }
162         }
163         stage('Proto') {
164             // TODO: protoc version could be different
165             Utils.markStageSkippedForConditional('Proto')
166         // try {
167         //     sh 'make proto'
168         //     if (GetModifiedFiles() != '') {
169         //         throw new Exception(exception_message)
170         //     }
171         // } catch (Exception e) {
172         //     SetBuildStatus(failed, context, failure)
173         //     sh 'git diff'
174         //     sh 'git status -s'
175         //     Error(e).call()
176         // }
177         }
178         SetBuildStatus(completed, context, success)
179     }
180 }
181
182 def BaseImage(version, build_steps, registry, local_version) {
183     return {
184         Build(base_image, version, build_steps, registry, local_version).call()
185     }
186 }
187
188 // Call Build function for every images in parallel
189 def Images(images, version, build_steps, registry, local_version) {
190     return {
191         def stages = [:]
192         for (i in images) {
193             stages.put(i, Build(i, version, build_steps, registry, local_version))
194         }
195         parallel(stages)
196     }
197 }
198
199 // Build set the github commit status
200 def Build(image, version, build_steps, registry, local_version) {
201     return {
202         stage("${image} (${version}): ${build_steps}") {
203             def context = "Image: ${image}"
204             def in_progress_message = "${in_progress} (${build_steps})"
205             def completed_message = "${completed} (${build_steps})"
206             def failed_message = "${failed} (${build_steps})"
207             try {
208                 SetBuildStatus(in_progress_message, context, pending)
209                 sh "make ${image} VERSION=${version} BUILD_STEPS='${build_steps}' REGISTRY=${registry} LOCAL_VERSION=${local_version} BASE_IMAGE=${base_image}:${local_version}"
210                 SetBuildStatus(completed_message, context, success)
211             } catch (Exception e) {
212                 SetBuildStatus(failed_message, context, failure)
213                 Error(e).call()
214             }
215         }
216     }
217 }
218
219 // Run the E2e Tests
220 // Currently skipped
221 def E2e(e2e_enabled) {
222     if (e2e_enabled == 'true') {
223         return {
224             echo 'make e2e' // todo
225         }
226     } else {
227         return {
228             Utils.markStageSkippedForConditional('E2E')
229         }
230     }
231 }
232
233 // Raise error in Jenkins job
234 def Error(e) {
235     return {
236         Cleanup()
237         error e
238     }
239 }
240
241 // Cleanup directory
242 def Cleanup() {
243     cleanWs()
244 }
245
246 // Set the commit status on Github
247 // https://plugins.jenkins.io/github/#plugin-content-pipeline-examples
248 def SetBuildStatus(String message, String context, String state) {
249     step([
250         $class: 'GitHubCommitStatusSetter',
251         reposSource: [$class: 'ManuallyEnteredRepositorySource', url: 'https://github.com/Nordix/Meridio'],
252         commitShaSource: [$class: 'ManuallyEnteredShaSource', sha: GetCommitSha()],
253         contextSource: [$class: 'ManuallyEnteredCommitContextSource', context: context],
254         errorHandlers: [[$class: 'ChangingBuildStatusErrorHandler', result: 'UNSTABLE']],
255         statusResultSource: [ $class: 'ConditionalStatusResultSource', results: [[$class: 'AnyBuildResult', message: message, state: state]] ]
256   ])
257 }
258
259 // Return the current commit sha
260 def GetCommitSha() {
261     return sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
262 }
263
264 // Returns if any files has been modified/added/removed
265 def GetModifiedFiles() {
266     return sh(script: 'git status -s', returnStdout: true).trim()
267 }