blob: 722467629a2fc95200848bd7c5575282b1d2463d [file] [log] [blame]
Lionel Jouin85c872e2022-08-10 15:08:42 +02001/*
2Copyright (c) 2022 Nordix Foundation
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
18
19pending = 'PENDING'
20success = 'SUCCESS'
21failure = 'FAILURE'
22base_image = 'base-image'
23in_progress = 'In Progress.'
24completed = 'Completed.'
25failed = 'Failed'
26
27node {
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
80def 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
94def 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
111def 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
132def 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
182def 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
189def 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
200def 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
221def 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
234def Error(e) {
235 return {
236 Cleanup()
237 error e
238 }
239}
240
241// Cleanup directory
242def Cleanup() {
243 cleanWs()
244}
245
246// Set the commit status on Github
247// https://plugins.jenkins.io/github/#plugin-content-pipeline-examples
248def 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
260def GetCommitSha() {
261 return sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
262}
263
264// Returns if any files has been modified/added/removed
265def GetModifiedFiles() {
266 return sh(script: 'git status -s', returnStdout: true).trim()
267}