blob: 914dbbb6f53da4cc8c14a315a401ce8db4f0bf64 [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
Lionel Jouin4b6b6f52022-10-14 16:22:23 +020027exception_message_exec = 'failed to execute the following command: '
28exception_message_code_generation = 'Generated code verification failed'
29
Lionel Jouin1317f9e2022-11-17 11:20:32 +010030node('nordix-nsm-build-ubuntu2204') {
Lionel Jouin85c872e2022-08-10 15:08:42 +020031 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
Lionel Jouin5737e072023-03-21 17:40:21 +010037 def helm_chart_upload = params.HELM_CHART_UPLOAD
38 def security_scan_enabled = params.SECURITY_SCAN_ENABLED
Lionel Jouin85c872e2022-08-10 15:08:42 +020039 def git_project = params.GIT_PROJECT
40 def current_branch = params.CURRENT_BRANCH
41 def default_branch = params.DEFAULT_BRANCH
42 def build_steps = params.BUILD_STEPS
43 def image_registry = params.IMAGE_REGISTRY
44 def local_version = "${env.JOB_NAME}-${build_number}"
45
Lionel Jouin26c167d2022-09-09 13:55:17 +020046 timeout(30) {
47 stage('Clone/Checkout') {
48 git branch: default_branch, url: git_project
49 checkout([
50 $class: 'GitSCM',
51 branches: [[name: current_branch]],
52 extensions: [],
53 userRemoteConfigs: [[
54 refspec: '+refs/pull/*/head:refs/remotes/origin/pr/*',
55 url: git_project
56 ]]
57 ])
58 sh 'git show'
Lionel Jouin85c872e2022-08-10 15:08:42 +020059 }
Lionel Jouin5737e072023-03-21 17:40:21 +010060 Verify().call()
Lionel Jouin26c167d2022-09-09 13:55:17 +020061 stage('Docker login') {
Lionel Jouinc4037892022-11-16 15:53:04 +010062 if (env.DRY_RUN != 'true') {
63 withCredentials([usernamePassword(credentialsId: 'nordix-cicd-harbor-credentials', passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USERNAME')]) {
64 sh '''#!/bin/bash -eu
Lionel Jouin26c167d2022-09-09 13:55:17 +020065 echo $HARBOR_PASSWORD | docker login --username $HARBOR_USERNAME --password-stdin $IMAGE_REGISTRY
66 '''
Lionel Jouinc4037892022-11-16 15:53:04 +010067 }
68 } else {
Lionel Jouin5737e072023-03-21 17:40:21 +010069 echo 'Docker login'
Lionel Jouin26c167d2022-09-09 13:55:17 +020070 }
71 }
72 stage('Base Image') {
73 BaseImage(version, build_steps, image_registry, local_version).call()
74 }
75 stage('Images') {
76 Images(image_names, version, build_steps, image_registry, local_version).call()
Lionel Jouinc4037892022-11-16 15:53:04 +010077 if (currentBuild.result == 'FAILURE') {
78 Error('Failed to build image(s)').call()
79 }
Lionel Jouin26c167d2022-09-09 13:55:17 +020080 }
Lionel Jouin5737e072023-03-21 17:40:21 +010081 stage('Helm Chart') {
82 HelmChart(helm_chart_upload, version).call()
83 }
84 stage('Security Scan') {
85 if (security_scan_enabled == true) {
86 SecurityScan(current_branch, version).call()
87 } else {
88 Utils.markStageSkippedForConditional('Security Scan')
89 }
90 }
Lionel Jouin26c167d2022-09-09 13:55:17 +020091 stage('E2E') {
Lionel Jouin5737e072023-03-21 17:40:21 +010092 if (e2e_enabled == true) {
93 E2e(current_branch, version).call()
Lionel Jouinc4037892022-11-16 15:53:04 +010094 } else {
95 Utils.markStageSkippedForConditional('E2E')
96 }
Lionel Jouin26c167d2022-09-09 13:55:17 +020097 }
Lionel Jouin85c872e2022-08-10 15:08:42 +020098 }
99 stage('Cleanup') {
100 Cleanup()
101 }
102 }
103}
104
Lionel Jouin5737e072023-03-21 17:40:21 +0100105// Verify the Generated code, UnitTests and Linter
Lionel Jouin85c872e2022-08-10 15:08:42 +0200106def Verify() {
107 return {
108 GeneratedCode().call() // cannot generate code and run the linter and tests at the same time
Lionel Jouin5737e072023-03-21 17:40:21 +0100109 Linter().call()
110 UnitTests().call()
Lionel Jouin85c872e2022-08-10 15:08:42 +0200111 }
112}
113
114// Runs the unit tests and set the github commit status
115def UnitTests() {
116 return {
117 def context = 'Unit Tests'
118 stage('Unit Tests') {
Lionel Jouin4b6b6f52022-10-14 16:22:23 +0200119 def command = 'make test'
Lionel Jouin85c872e2022-08-10 15:08:42 +0200120 try {
121 SetBuildStatus(in_progress, context, pending)
Lionel Jouin4b6b6f52022-10-14 16:22:23 +0200122 ExecSh(command).call()
Lionel Jouin85c872e2022-08-10 15:08:42 +0200123 SetBuildStatus(completed, context, success)
124 } catch (Exception e) {
125 SetBuildStatus(failed, context, failure)
Lionel Jouin5737e072023-03-21 17:40:21 +0100126 Error("${exception_message_exec} ${command}").call()
Lionel Jouin85c872e2022-08-10 15:08:42 +0200127 }
128 }
129 }
130}
131
132// Runs the linter and set the github commit status
133def Linter() {
134 return {
135 def context = 'Linter'
136 stage('Linter') {
Lionel Jouin4b6b6f52022-10-14 16:22:23 +0200137 def command = 'make lint'
Lionel Jouin85c872e2022-08-10 15:08:42 +0200138 try {
139 SetBuildStatus(in_progress, context, pending)
Lionel Jouin4b6b6f52022-10-14 16:22:23 +0200140 ExecSh(command).call()
Lionel Jouin85c872e2022-08-10 15:08:42 +0200141 SetBuildStatus(completed, context, success)
142 } catch (Exception e) {
143 SetBuildStatus(failed, context, failure)
Lionel Jouin5737e072023-03-21 17:40:21 +0100144 Error("${exception_message_exec} ${command}").call()
Lionel Jouin85c872e2022-08-10 15:08:42 +0200145 }
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
155def GeneratedCode() {
156 return {
157 def context = 'Generated code verification'
Lionel Jouin5737e072023-03-21 17:40:21 +0100158 stage('Generated code verification') {
159 def command = 'make go-generate manifests generate-controller'
Lionel Jouin85c872e2022-08-10 15:08:42 +0200160 try {
Lionel Jouin5737e072023-03-21 17:40:21 +0100161 SetBuildStatus(in_progress, context, pending)
Lionel Jouin4b6b6f52022-10-14 16:22:23 +0200162 ExecSh(command).call()
Lionel Jouin85c872e2022-08-10 15:08:42 +0200163 if (GetModifiedFiles() != '') {
Lionel Jouin4b6b6f52022-10-14 16:22:23 +0200164 throw new Exception(exception_message_code_generation)
Lionel Jouin85c872e2022-08-10 15:08:42 +0200165 }
Lionel Jouin5737e072023-03-21 17:40:21 +0100166 SetBuildStatus(completed, context, success)
Lionel Jouin85c872e2022-08-10 15:08:42 +0200167 } catch (Exception e) {
168 SetBuildStatus(failed, context, failure)
Lionel Jouin4b6b6f52022-10-14 16:22:23 +0200169 Error(exception_message_exec + command).call()
Lionel Jouin85c872e2022-08-10 15:08:42 +0200170 }
171 }
Lionel Jouin85c872e2022-08-10 15:08:42 +0200172 }
173}
174
175def BaseImage(version, build_steps, registry, local_version) {
176 return {
177 Build(base_image, version, build_steps, registry, local_version).call()
178 }
179}
180
181// Call Build function for every images in parallel
182def Images(images, version, build_steps, registry, local_version) {
183 return {
184 def stages = [:]
185 for (i in images) {
186 stages.put(i, Build(i, version, build_steps, registry, local_version))
187 }
188 parallel(stages)
189 }
190}
191
192// Build set the github commit status
193def Build(image, version, build_steps, registry, local_version) {
194 return {
195 stage("${image} (${version}): ${build_steps}") {
196 def context = "Image: ${image}"
197 def in_progress_message = "${in_progress} (${build_steps})"
198 def completed_message = "${completed} (${build_steps})"
199 def failed_message = "${failed} (${build_steps})"
Lionel Jouin4b6b6f52022-10-14 16:22:23 +0200200 def command = "make ${image} VERSION=${version} BUILD_STEPS='${build_steps}' REGISTRY=${registry} LOCAL_VERSION=${local_version} BASE_IMAGE=${base_image}:${local_version}"
Lionel Jouin85c872e2022-08-10 15:08:42 +0200201 try {
202 SetBuildStatus(in_progress_message, context, pending)
Lionel Jouin4b6b6f52022-10-14 16:22:23 +0200203 ExecSh(command).call()
Lionel Jouin85c872e2022-08-10 15:08:42 +0200204 SetBuildStatus(completed_message, context, success)
205 } catch (Exception e) {
206 SetBuildStatus(failed_message, context, failure)
Lionel Jouinc4037892022-11-16 15:53:04 +0100207 unstable "${exception_message_exec} ${command}"
208 currentBuild.result = 'FAILURE'
Lionel Jouin85c872e2022-08-10 15:08:42 +0200209 }
210 }
211 }
212}
213
Lionel Jouin5737e072023-03-21 17:40:21 +0100214// Generate and upload the helm chart
215def HelmChart(helm_chart_upload, version) {
216 return {
217 parallel(
218 'Helm Chart': {
219 stage('Generate Helm Chart') {
220 def context = 'Generate Helm Chart'
221 def command = "make generate-helm-chart VERSION=${version}"
222 try {
223 SetBuildStatus(in_progress, context, pending)
224 ExecSh(command).call()
225 SetBuildStatus(completed, context, success)
226 } catch (Exception e) {
227 SetBuildStatus(failed, context, failure)
228 Error("${exception_message_exec} ${command}").call()
229 }
230 }
231 stage('Upload Helm Chart') {
232 if (helm_chart_upload == true) {
233 withCredentials([string(credentialsId: 'nsm-nordix-artifactory-api-key', variable: 'API_KEY')]) {
234 ExecSh("""
235 charts=\$(cd _output/helm/ && ls *.tgz)
236 for chart in \$charts
237 do
238 curl -H 'X-JFrog-Art-Api:${API_KEY}' -T _output/helm/\$chart \"https://artifactory.nordix.org/artifactory/cloud-native/meridio/\$chart\"
239 done
240 """).call()
241 }
242 } else {
243 Utils.markStageSkippedForConditional('Upload Helm Chart')
244 }
245 }
246 }
247 )
248 }
249}
250
251// Run the security scan job
252def SecurityScan(current_branch, version) {
253 return {
254 build job: 'meridio-periodic-security-scan', parameters: [
255 string(name: 'IMAGE_VERSION', value: "$version"),
256 string(name: 'CURRENT_BRANCH', value: "$current_branch"),
257 string(name: 'DRY_RUN', value: env.DRY_RUN)
258 ], wait: true
259 }
260}
261
Lionel Jouin85c872e2022-08-10 15:08:42 +0200262// Run the E2e Tests
263// Currently skipped
Lionel Jouin5737e072023-03-21 17:40:21 +0100264def E2e(current_branch, version) {
Lionel Jouinc4037892022-11-16 15:53:04 +0100265 return {
Lionel Jouin5737e072023-03-21 17:40:21 +0100266 build job: 'meridio-e2e-test-kind', parameters: [
267 string(name: 'MERIDIO_VERSION', value: "$version"),
268 string(name: 'TAPA_VERSION', value: "$version"),
269 string(name: 'CURRENT_BRANCH', value: "$current_branch"),
270 string(name: 'DRY_RUN', value: env.DRY_RUN)
271 ], wait: true
Lionel Jouin85c872e2022-08-10 15:08:42 +0200272 }
273}
274
275// Raise error in Jenkins job
276def Error(e) {
277 return {
Lionel Jouin4b6b6f52022-10-14 16:22:23 +0200278 sh 'git diff'
279 sh 'git status -s'
Lionel Jouin85c872e2022-08-10 15:08:42 +0200280 Cleanup()
281 error e
282 }
283}
284
285// Cleanup directory
286def Cleanup() {
287 cleanWs()
288}
289
Lionel Jouin4b6b6f52022-10-14 16:22:23 +0200290// Execute command
291def ExecSh(command) {
292 return {
Lionel Jouinc4037892022-11-16 15:53:04 +0100293 if (env.DRY_RUN != 'true') {
294 sh """
295 . \${HOME}/.profile
296 ${command}
297 """
298 } else {
299 echo "${command}"
300 }
Lionel Jouin4b6b6f52022-10-14 16:22:23 +0200301 }
302}
303
Lionel Jouin85c872e2022-08-10 15:08:42 +0200304// Set the commit status on Github
305// https://plugins.jenkins.io/github/#plugin-content-pipeline-examples
306def SetBuildStatus(String message, String context, String state) {
Lionel Jouinc4037892022-11-16 15:53:04 +0100307 if (env.DRY_RUN != 'true') {
308 step([
309 $class: 'GitHubCommitStatusSetter',
310 reposSource: [$class: 'ManuallyEnteredRepositorySource', url: 'https://github.com/Nordix/Meridio'],
311 commitShaSource: [$class: 'ManuallyEnteredShaSource', sha: GetCommitSha()],
312 contextSource: [$class: 'ManuallyEnteredCommitContextSource', context: context],
313 errorHandlers: [[$class: 'ShallowAnyErrorHandler']], // Prevent GitHubCommitStatusSetter to set the job status to unstable
314 statusResultSource: [ $class: 'ConditionalStatusResultSource', results: [[$class: 'AnyBuildResult', message: message, state: state]] ]
315 ])
316 }
Lionel Jouin85c872e2022-08-10 15:08:42 +0200317}
318
319// Return the current commit sha
320def GetCommitSha() {
321 return sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
322}
323
324// Returns if any files has been modified/added/removed
325def GetModifiedFiles() {
326 return sh(script: 'git status -s', returnStdout: true).trim()
327}