Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 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 | |
Lionel Jouin | 4b6b6f5 | 2022-10-14 16:22:23 +0200 | [diff] [blame] | 27 | exception_message_exec = 'failed to execute the following command: ' |
| 28 | exception_message_code_generation = 'Generated code verification failed' |
| 29 | |
Lionel Jouin | 1317f9e | 2022-11-17 11:20:32 +0100 | [diff] [blame] | 30 | node('nordix-nsm-build-ubuntu2204') { |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 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 |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 37 | def helm_chart_upload = params.HELM_CHART_UPLOAD |
| 38 | def security_scan_enabled = params.SECURITY_SCAN_ENABLED |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 39 | 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 Jouin | 26c167d | 2022-09-09 13:55:17 +0200 | [diff] [blame] | 46 | 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 Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 59 | } |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 60 | Verify().call() |
Lionel Jouin | 26c167d | 2022-09-09 13:55:17 +0200 | [diff] [blame] | 61 | stage('Docker login') { |
Lionel Jouin | c403789 | 2022-11-16 15:53:04 +0100 | [diff] [blame] | 62 | 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 Jouin | 26c167d | 2022-09-09 13:55:17 +0200 | [diff] [blame] | 65 | echo $HARBOR_PASSWORD | docker login --username $HARBOR_USERNAME --password-stdin $IMAGE_REGISTRY |
| 66 | ''' |
Lionel Jouin | c403789 | 2022-11-16 15:53:04 +0100 | [diff] [blame] | 67 | } |
| 68 | } else { |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 69 | echo 'Docker login' |
Lionel Jouin | 26c167d | 2022-09-09 13:55:17 +0200 | [diff] [blame] | 70 | } |
| 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 Jouin | c403789 | 2022-11-16 15:53:04 +0100 | [diff] [blame] | 77 | if (currentBuild.result == 'FAILURE') { |
| 78 | Error('Failed to build image(s)').call() |
| 79 | } |
Lionel Jouin | 26c167d | 2022-09-09 13:55:17 +0200 | [diff] [blame] | 80 | } |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 81 | 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 Jouin | 26c167d | 2022-09-09 13:55:17 +0200 | [diff] [blame] | 91 | stage('E2E') { |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 92 | if (e2e_enabled == true) { |
| 93 | E2e(current_branch, version).call() |
Lionel Jouin | c403789 | 2022-11-16 15:53:04 +0100 | [diff] [blame] | 94 | } else { |
| 95 | Utils.markStageSkippedForConditional('E2E') |
| 96 | } |
Lionel Jouin | 26c167d | 2022-09-09 13:55:17 +0200 | [diff] [blame] | 97 | } |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 98 | } |
| 99 | stage('Cleanup') { |
| 100 | Cleanup() |
| 101 | } |
| 102 | } |
| 103 | } |
| 104 | |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 105 | // Verify the Generated code, UnitTests and Linter |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 106 | def Verify() { |
| 107 | return { |
| 108 | GeneratedCode().call() // cannot generate code and run the linter and tests at the same time |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 109 | Linter().call() |
| 110 | UnitTests().call() |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 111 | } |
| 112 | } |
| 113 | |
| 114 | // Runs the unit tests and set the github commit status |
| 115 | def UnitTests() { |
| 116 | return { |
| 117 | def context = 'Unit Tests' |
| 118 | stage('Unit Tests') { |
Lionel Jouin | 4b6b6f5 | 2022-10-14 16:22:23 +0200 | [diff] [blame] | 119 | def command = 'make test' |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 120 | try { |
| 121 | SetBuildStatus(in_progress, context, pending) |
Lionel Jouin | 4b6b6f5 | 2022-10-14 16:22:23 +0200 | [diff] [blame] | 122 | ExecSh(command).call() |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 123 | SetBuildStatus(completed, context, success) |
| 124 | } catch (Exception e) { |
| 125 | SetBuildStatus(failed, context, failure) |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 126 | Error("${exception_message_exec} ${command}").call() |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 127 | } |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | // Runs the linter and set the github commit status |
| 133 | def Linter() { |
| 134 | return { |
| 135 | def context = 'Linter' |
| 136 | stage('Linter') { |
Lionel Jouin | 4b6b6f5 | 2022-10-14 16:22:23 +0200 | [diff] [blame] | 137 | def command = 'make lint' |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 138 | try { |
| 139 | SetBuildStatus(in_progress, context, pending) |
Lionel Jouin | 4b6b6f5 | 2022-10-14 16:22:23 +0200 | [diff] [blame] | 140 | ExecSh(command).call() |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 141 | SetBuildStatus(completed, context, success) |
| 142 | } catch (Exception e) { |
| 143 | SetBuildStatus(failed, context, failure) |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 144 | Error("${exception_message_exec} ${command}").call() |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 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' |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 158 | stage('Generated code verification') { |
| 159 | def command = 'make go-generate manifests generate-controller' |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 160 | try { |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 161 | SetBuildStatus(in_progress, context, pending) |
Lionel Jouin | 4b6b6f5 | 2022-10-14 16:22:23 +0200 | [diff] [blame] | 162 | ExecSh(command).call() |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 163 | if (GetModifiedFiles() != '') { |
Lionel Jouin | 4b6b6f5 | 2022-10-14 16:22:23 +0200 | [diff] [blame] | 164 | throw new Exception(exception_message_code_generation) |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 165 | } |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 166 | SetBuildStatus(completed, context, success) |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 167 | } catch (Exception e) { |
| 168 | SetBuildStatus(failed, context, failure) |
Lionel Jouin | 4b6b6f5 | 2022-10-14 16:22:23 +0200 | [diff] [blame] | 169 | Error(exception_message_exec + command).call() |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 170 | } |
| 171 | } |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 172 | } |
| 173 | } |
| 174 | |
| 175 | def 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 |
| 182 | def 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 |
| 193 | def 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 Jouin | 4b6b6f5 | 2022-10-14 16:22:23 +0200 | [diff] [blame] | 200 | def command = "make ${image} VERSION=${version} BUILD_STEPS='${build_steps}' REGISTRY=${registry} LOCAL_VERSION=${local_version} BASE_IMAGE=${base_image}:${local_version}" |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 201 | try { |
| 202 | SetBuildStatus(in_progress_message, context, pending) |
Lionel Jouin | 4b6b6f5 | 2022-10-14 16:22:23 +0200 | [diff] [blame] | 203 | ExecSh(command).call() |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 204 | SetBuildStatus(completed_message, context, success) |
| 205 | } catch (Exception e) { |
| 206 | SetBuildStatus(failed_message, context, failure) |
Lionel Jouin | c403789 | 2022-11-16 15:53:04 +0100 | [diff] [blame] | 207 | unstable "${exception_message_exec} ${command}" |
| 208 | currentBuild.result = 'FAILURE' |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 209 | } |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 214 | // Generate and upload the helm chart |
| 215 | def 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 |
| 252 | def 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 Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 262 | // Run the E2e Tests |
| 263 | // Currently skipped |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 264 | def E2e(current_branch, version) { |
Lionel Jouin | c403789 | 2022-11-16 15:53:04 +0100 | [diff] [blame] | 265 | return { |
Lionel Jouin | 5737e07 | 2023-03-21 17:40:21 +0100 | [diff] [blame] | 266 | 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 Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 272 | } |
| 273 | } |
| 274 | |
| 275 | // Raise error in Jenkins job |
| 276 | def Error(e) { |
| 277 | return { |
Lionel Jouin | 4b6b6f5 | 2022-10-14 16:22:23 +0200 | [diff] [blame] | 278 | sh 'git diff' |
| 279 | sh 'git status -s' |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 280 | Cleanup() |
| 281 | error e |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | // Cleanup directory |
| 286 | def Cleanup() { |
| 287 | cleanWs() |
| 288 | } |
| 289 | |
Lionel Jouin | 4b6b6f5 | 2022-10-14 16:22:23 +0200 | [diff] [blame] | 290 | // Execute command |
| 291 | def ExecSh(command) { |
| 292 | return { |
Lionel Jouin | c403789 | 2022-11-16 15:53:04 +0100 | [diff] [blame] | 293 | if (env.DRY_RUN != 'true') { |
| 294 | sh """ |
| 295 | . \${HOME}/.profile |
| 296 | ${command} |
| 297 | """ |
| 298 | } else { |
| 299 | echo "${command}" |
| 300 | } |
Lionel Jouin | 4b6b6f5 | 2022-10-14 16:22:23 +0200 | [diff] [blame] | 301 | } |
| 302 | } |
| 303 | |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 304 | // Set the commit status on Github |
| 305 | // https://plugins.jenkins.io/github/#plugin-content-pipeline-examples |
| 306 | def SetBuildStatus(String message, String context, String state) { |
Lionel Jouin | c403789 | 2022-11-16 15:53:04 +0100 | [diff] [blame] | 307 | 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 Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 317 | } |
| 318 | |
| 319 | // Return the current commit sha |
| 320 | def GetCommitSha() { |
| 321 | return sh(script: 'git rev-parse HEAD', returnStdout: true).trim() |
| 322 | } |
| 323 | |
| 324 | // Returns if any files has been modified/added/removed |
| 325 | def GetModifiedFiles() { |
| 326 | return sh(script: 'git status -s', returnStdout: true).trim() |
| 327 | } |