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 | 26c167d | 2022-09-09 13:55:17 +0200 | [diff] [blame^] | 27 | node('nordix-nsm-build-ubuntu1804') { |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 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 | |
Lionel Jouin | 26c167d | 2022-09-09 13:55:17 +0200 | [diff] [blame^] | 41 | timeout(30) { |
| 42 | stage('Clone/Checkout') { |
| 43 | git branch: default_branch, url: git_project |
| 44 | checkout([ |
| 45 | $class: 'GitSCM', |
| 46 | branches: [[name: current_branch]], |
| 47 | extensions: [], |
| 48 | userRemoteConfigs: [[ |
| 49 | refspec: '+refs/pull/*/head:refs/remotes/origin/pr/*', |
| 50 | url: git_project |
| 51 | ]] |
| 52 | ]) |
| 53 | sh 'git show' |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 54 | } |
Lionel Jouin | 26c167d | 2022-09-09 13:55:17 +0200 | [diff] [blame^] | 55 | stage('Verify') { |
| 56 | Verify().call() |
| 57 | } |
| 58 | stage('Docker login') { |
| 59 | withCredentials([usernamePassword(credentialsId: 'nordixinfra-harbor-creds-wrapper', passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USERNAME')]) { |
| 60 | sh '''#!/bin/bash -eu |
| 61 | echo $HARBOR_PASSWORD | docker login --username $HARBOR_USERNAME --password-stdin $IMAGE_REGISTRY |
| 62 | ''' |
| 63 | } |
| 64 | } |
| 65 | stage('Base Image') { |
| 66 | BaseImage(version, build_steps, image_registry, local_version).call() |
| 67 | } |
| 68 | stage('Images') { |
| 69 | Images(image_names, version, build_steps, image_registry, local_version).call() |
| 70 | } |
| 71 | stage('E2E') { |
| 72 | E2e(e2e_enabled).call() |
| 73 | } |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 74 | } |
| 75 | stage('Cleanup') { |
| 76 | Cleanup() |
| 77 | } |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | // Static analysis: Runs the GeneratedCode function and then UnitTests and Linter in parallel |
| 82 | def Verify() { |
| 83 | return { |
| 84 | GeneratedCode().call() // cannot generate code and run the linter and tests at the same time |
| 85 | // Linter().call() |
| 86 | // UnitTests().call() |
| 87 | def stages = [:] |
| 88 | stages.put('Unit Tests', UnitTests()) |
| 89 | stages.put('Linter', Linter()) |
| 90 | // stages.put('Generated code verification', GeneratedCode()) |
| 91 | parallel(stages) |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | // Runs the unit tests and set the github commit status |
| 96 | def UnitTests() { |
| 97 | return { |
| 98 | def context = 'Unit Tests' |
| 99 | stage('Unit Tests') { |
| 100 | try { |
| 101 | SetBuildStatus(in_progress, context, pending) |
robert.tomczyk | 0f89335 | 2022-09-08 18:26:30 +0100 | [diff] [blame] | 102 | sh ''' |
| 103 | . \${HOME}/.profile |
| 104 | make test |
| 105 | ''' |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 106 | SetBuildStatus(completed, context, success) |
| 107 | } catch (Exception e) { |
| 108 | SetBuildStatus(failed, context, failure) |
| 109 | Error(e).call() |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | // Runs the linter and set the github commit status |
| 116 | def Linter() { |
| 117 | return { |
| 118 | def context = 'Linter' |
| 119 | stage('Linter') { |
| 120 | try { |
| 121 | SetBuildStatus(in_progress, context, pending) |
robert.tomczyk | 0f89335 | 2022-09-08 18:26:30 +0100 | [diff] [blame] | 122 | sh ''' |
| 123 | . \${HOME}/.profile |
| 124 | make lint |
| 125 | ''' |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 126 | SetBuildStatus(completed, context, success) |
| 127 | } catch (Exception e) { |
| 128 | SetBuildStatus(failed, context, failure) |
| 129 | Error(e).call() |
| 130 | } |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | // Check if code has been generated correctly and set the github commit status: |
| 136 | // go.mod: runs "go mod tidy" |
| 137 | // go generate ./...: Code should be generated using "make genrate" command |
| 138 | // proto: skipped due to version of protoc |
| 139 | // If files are generated correctly then GetModifiedFiles function should return an empty string |
| 140 | def GeneratedCode() { |
| 141 | return { |
| 142 | def context = 'Generated code verification' |
| 143 | def exception_message = 'Generated code verification failed' |
| 144 | SetBuildStatus(in_progress, context, pending) |
| 145 | stage('go mod tidy') { |
| 146 | try { |
robert.tomczyk | 0f89335 | 2022-09-08 18:26:30 +0100 | [diff] [blame] | 147 | sh ''' |
| 148 | . \${HOME}/.profile |
| 149 | go mod tidy |
| 150 | ''' |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 151 | if (GetModifiedFiles() != '') { |
| 152 | throw new Exception(exception_message) |
| 153 | } |
| 154 | } catch (Exception e) { |
| 155 | SetBuildStatus(failed, context, failure) |
| 156 | sh 'git diff' |
| 157 | sh 'git status -s' |
| 158 | Error(e).call() |
| 159 | } |
| 160 | } |
| 161 | stage('go generate ./...') { |
| 162 | try { |
robert.tomczyk | 0f89335 | 2022-09-08 18:26:30 +0100 | [diff] [blame] | 163 | sh ''' |
| 164 | . \${HOME}/.profile |
| 165 | make generate |
| 166 | ''' |
Lionel Jouin | 85c872e | 2022-08-10 15:08:42 +0200 | [diff] [blame] | 167 | if (GetModifiedFiles() != '') { |
| 168 | throw new Exception(exception_message) |
| 169 | } |
| 170 | } catch (Exception e) { |
| 171 | SetBuildStatus(failed, context, failure) |
| 172 | sh 'git diff' |
| 173 | sh 'git status -s' |
| 174 | Error(e).call() |
| 175 | } |
| 176 | } |
| 177 | stage('Proto') { |
| 178 | // TODO: protoc version could be different |
| 179 | Utils.markStageSkippedForConditional('Proto') |
| 180 | // try { |
| 181 | // sh 'make proto' |
| 182 | // if (GetModifiedFiles() != '') { |
| 183 | // throw new Exception(exception_message) |
| 184 | // } |
| 185 | // } catch (Exception e) { |
| 186 | // SetBuildStatus(failed, context, failure) |
| 187 | // sh 'git diff' |
| 188 | // sh 'git status -s' |
| 189 | // Error(e).call() |
| 190 | // } |
| 191 | } |
| 192 | SetBuildStatus(completed, context, success) |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | def BaseImage(version, build_steps, registry, local_version) { |
| 197 | return { |
| 198 | Build(base_image, version, build_steps, registry, local_version).call() |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | // Call Build function for every images in parallel |
| 203 | def Images(images, version, build_steps, registry, local_version) { |
| 204 | return { |
| 205 | def stages = [:] |
| 206 | for (i in images) { |
| 207 | stages.put(i, Build(i, version, build_steps, registry, local_version)) |
| 208 | } |
| 209 | parallel(stages) |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | // Build set the github commit status |
| 214 | def Build(image, version, build_steps, registry, local_version) { |
| 215 | return { |
| 216 | stage("${image} (${version}): ${build_steps}") { |
| 217 | def context = "Image: ${image}" |
| 218 | def in_progress_message = "${in_progress} (${build_steps})" |
| 219 | def completed_message = "${completed} (${build_steps})" |
| 220 | def failed_message = "${failed} (${build_steps})" |
| 221 | try { |
| 222 | SetBuildStatus(in_progress_message, context, pending) |
| 223 | sh "make ${image} VERSION=${version} BUILD_STEPS='${build_steps}' REGISTRY=${registry} LOCAL_VERSION=${local_version} BASE_IMAGE=${base_image}:${local_version}" |
| 224 | SetBuildStatus(completed_message, context, success) |
| 225 | } catch (Exception e) { |
| 226 | SetBuildStatus(failed_message, context, failure) |
| 227 | Error(e).call() |
| 228 | } |
| 229 | } |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | // Run the E2e Tests |
| 234 | // Currently skipped |
| 235 | def E2e(e2e_enabled) { |
| 236 | if (e2e_enabled == 'true') { |
| 237 | return { |
| 238 | echo 'make e2e' // todo |
| 239 | } |
| 240 | } else { |
| 241 | return { |
| 242 | Utils.markStageSkippedForConditional('E2E') |
| 243 | } |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | // Raise error in Jenkins job |
| 248 | def Error(e) { |
| 249 | return { |
| 250 | Cleanup() |
| 251 | error e |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | // Cleanup directory |
| 256 | def Cleanup() { |
| 257 | cleanWs() |
| 258 | } |
| 259 | |
| 260 | // Set the commit status on Github |
| 261 | // https://plugins.jenkins.io/github/#plugin-content-pipeline-examples |
| 262 | def SetBuildStatus(String message, String context, String state) { |
| 263 | step([ |
| 264 | $class: 'GitHubCommitStatusSetter', |
| 265 | reposSource: [$class: 'ManuallyEnteredRepositorySource', url: 'https://github.com/Nordix/Meridio'], |
| 266 | commitShaSource: [$class: 'ManuallyEnteredShaSource', sha: GetCommitSha()], |
| 267 | contextSource: [$class: 'ManuallyEnteredCommitContextSource', context: context], |
| 268 | errorHandlers: [[$class: 'ChangingBuildStatusErrorHandler', result: 'UNSTABLE']], |
| 269 | statusResultSource: [ $class: 'ConditionalStatusResultSource', results: [[$class: 'AnyBuildResult', message: message, state: state]] ] |
| 270 | ]) |
| 271 | } |
| 272 | |
| 273 | // Return the current commit sha |
| 274 | def GetCommitSha() { |
| 275 | return sh(script: 'git rev-parse HEAD', returnStdout: true).trim() |
| 276 | } |
| 277 | |
| 278 | // Returns if any files has been modified/added/removed |
| 279 | def GetModifiedFiles() { |
| 280 | return sh(script: 'git status -s', returnStdout: true).trim() |
| 281 | } |