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 | |
| 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 | } |