Jakub Josef | 4edd743 | 2017-05-10 17:58:56 +0200 | [diff] [blame] | 1 | package com.mirantis.mk |
Jakub Josef | 93f08e2 | 2017-06-05 19:14:53 +0200 | [diff] [blame] | 2 | import com.cloudbees.groovy.cps.NonCPS |
Mykyta Karpin | 158e44d | 2024-10-10 14:54:14 +0200 | [diff] [blame^] | 3 | import org.jenkins.plugins.lockableresources.LockableResourcesManager as LRM |
Jakub Josef | 4edd743 | 2017-05-10 17:58:56 +0200 | [diff] [blame] | 4 | |
| 5 | /** |
| 6 | * |
| 7 | * Jenkins common functions |
| 8 | * |
| 9 | */ |
| 10 | |
| 11 | /** |
Kirill Mashchenko | 4300109 | 2018-12-25 05:28:53 +0400 | [diff] [blame] | 12 | * Returns a list of groups which user belongs |
| 13 | * @param username String |
| 14 | * @return list of groups [String] |
Jakub Josef | d44b697 | 2018-01-23 17:55:57 +0100 | [diff] [blame] | 15 | */ |
Kirill Mashchenko | 4300109 | 2018-12-25 05:28:53 +0400 | [diff] [blame] | 16 | def userGroups(username) { |
| 17 | res = [] |
| 18 | def authorities = Jenkins.instance.securityRealm.loadUserByUsername(username).getAuthorities() |
| 19 | authorities.each { |
| 20 | res.add(it.toString()) |
Jakub Josef | d44b697 | 2018-01-23 17:55:57 +0100 | [diff] [blame] | 21 | } |
Kirill Mashchenko | 4300109 | 2018-12-25 05:28:53 +0400 | [diff] [blame] | 22 | return res |
| 23 | } |
| 24 | |
| 25 | /** |
| 26 | * Check if user belongs to group |
| 27 | * @param username String |
| 28 | * @param group String |
| 29 | * @return boolean result |
| 30 | */ |
| 31 | def userInGroup(username, group) { |
| 32 | def authorities = userGroups(username) |
| 33 | return authorities.any{it==group} |
| 34 | } |
| 35 | |
| 36 | /** |
| 37 | * Check if user belongs to at least one of given groups |
| 38 | * @param username String |
| 39 | * @param groups [String] |
| 40 | * @return boolean result |
| 41 | */ |
| 42 | def userInGroups(username, groups) { |
| 43 | return groups.any{userInGroup(username, it)} |
| 44 | } |
| 45 | |
| 46 | /** |
| 47 | * Returns current username from build |
| 48 | * @return username String |
| 49 | */ |
| 50 | def currentUsername() { |
| 51 | username = '' |
| 52 | wrap([$class: 'BuildUser']) { |
Maxim Rasskazov | 2b7c3be | 2019-06-21 14:50:02 +0400 | [diff] [blame] | 53 | username = env.BUILD_USER_ID ?: 'jenkins' |
Kirill Mashchenko | 4300109 | 2018-12-25 05:28:53 +0400 | [diff] [blame] | 54 | } |
| 55 | if (username) { |
| 56 | return username |
| 57 | } else { |
| 58 | throw new Exception('cant get current username') |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | /** |
| 63 | * Check if current user belongs to at least one of given groups |
| 64 | * @param groups [String] |
| 65 | * @return boolean result |
| 66 | */ |
| 67 | def currentUserInGroups(groups) { |
| 68 | username = currentUsername() |
| 69 | return userInGroups(username, groups) |
| 70 | } |
| 71 | |
| 72 | /** |
| 73 | * Check if current user belongs to group |
| 74 | * @param group String |
| 75 | * @return boolean result |
| 76 | */ |
| 77 | def currentUserInGroup(group) { |
| 78 | username = currentUsername() |
| 79 | return userInGroup(username, group) |
Jakub Josef | d44b697 | 2018-01-23 17:55:57 +0100 | [diff] [blame] | 80 | } |
| 81 | |
| 82 | /** |
Jakub Josef | 4edd743 | 2017-05-10 17:58:56 +0200 | [diff] [blame] | 83 | * Get Jenkins job running builds |
| 84 | * @param jobName job name |
| 85 | * @return list of running builds |
| 86 | */ |
Jakub Josef | fbe8c7c | 2017-05-11 13:35:11 +0200 | [diff] [blame] | 87 | @NonCPS |
Jakub Josef | 4edd743 | 2017-05-10 17:58:56 +0200 | [diff] [blame] | 88 | def getJobRunningBuilds(jobName){ |
Kirill Mashchenko | 4300109 | 2018-12-25 05:28:53 +0400 | [diff] [blame] | 89 | def job = Jenkins.instance.items.find{it -> it.name.equals(jobName)} |
| 90 | if(job){ |
| 91 | return job.builds.findAll{build -> build.isBuilding()} |
| 92 | } |
| 93 | return [] |
Jakub Josef | 93f08e2 | 2017-06-05 19:14:53 +0200 | [diff] [blame] | 94 | } |
| 95 | |
| 96 | @NonCPS |
| 97 | def getRunningBuilds(job){ |
Kirill Mashchenko | 4300109 | 2018-12-25 05:28:53 +0400 | [diff] [blame] | 98 | return job.builds.findAll{build -> build.isBuilding()} |
Jakub Josef | 93f08e2 | 2017-06-05 19:14:53 +0200 | [diff] [blame] | 99 | } |
| 100 | |
| 101 | @NonCPS |
| 102 | def killStuckBuilds(maxSeconds, job){ |
Kirill Mashchenko | 4300109 | 2018-12-25 05:28:53 +0400 | [diff] [blame] | 103 | def common = new com.mirantis.mk.Common() |
| 104 | def result = true |
| 105 | def runningBuilds = getRunningBuilds(job) |
| 106 | def jobName = job.name |
| 107 | for(int j=0; j < runningBuilds.size(); j++){ |
| 108 | int durationInSeconds = (System.currentTimeMillis() - runningBuilds[j].getTimeInMillis())/1000.0 |
| 109 | if(durationInSeconds > maxSeconds){ |
| 110 | result = false |
| 111 | def buildId = runningBuilds[j].id |
| 112 | common.infoMsg("Aborting ${jobName}-${buildId} which is running for ${durationInSeconds}s") |
| 113 | try{ |
| 114 | runningBuilds[j].finish(hudson.model.Result.ABORTED, new java.io.IOException("Aborting build by long running jobs killer")); |
| 115 | result = true |
| 116 | }catch(e){ |
| 117 | common.errorMsg("Error occured during aborting build: Exception: ${e}") |
| 118 | } |
| 119 | } |
Jakub Josef | 93f08e2 | 2017-06-05 19:14:53 +0200 | [diff] [blame] | 120 | } |
Kirill Mashchenko | 4300109 | 2018-12-25 05:28:53 +0400 | [diff] [blame] | 121 | return result |
Jakub Josef | d44b697 | 2018-01-23 17:55:57 +0100 | [diff] [blame] | 122 | } |
Richard Felkl | 838892f | 2018-06-12 17:58:20 +0200 | [diff] [blame] | 123 | |
| 124 | /** |
| 125 | * Get Jenkins job object |
| 126 | * @param jobName job name |
| 127 | * @return job object that matches jobName |
| 128 | */ |
Denis Egorenko | b1a40b6 | 2019-01-11 18:04:57 +0400 | [diff] [blame] | 129 | def getJobByName(jobName, regexp=false){ |
Richard Felkl | 838892f | 2018-06-12 17:58:20 +0200 | [diff] [blame] | 130 | for(item in Hudson.instance.items) { |
Denis Egorenko | b1a40b6 | 2019-01-11 18:04:57 +0400 | [diff] [blame] | 131 | if (regexp && item.name ==~ jobName || item.name == jobName) { |
Richard Felkl | 838892f | 2018-06-12 17:58:20 +0200 | [diff] [blame] | 132 | return item |
| 133 | } |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | /** |
| 138 | * Get Jenkins job parameters |
| 139 | * @param jobName job name |
| 140 | * @return HashMap with parameter names as keys and their values as values |
| 141 | */ |
| 142 | def getJobParameters(jobName){ |
| 143 | def job = getJobByName(jobName) |
| 144 | def prop = job.getProperty(ParametersDefinitionProperty.class) |
| 145 | def params = new java.util.HashMap<String,String>() |
| 146 | if(prop != null) { |
| 147 | for(param in prop.getParameterDefinitions()) { |
| 148 | params.put(param.name, param.defaultValue) |
| 149 | } |
| 150 | } |
| 151 | return params |
| 152 | } |
Denis Egorenko | 46ff138 | 2018-12-14 15:46:36 +0400 | [diff] [blame] | 153 | |
| 154 | /** |
| 155 | * Get list of causes actions for given build |
| 156 | * |
| 157 | * @param build Job build object (like, currentBuild.rawBuild) |
| 158 | * @return list of causes actions for given build |
| 159 | */ |
| 160 | @NonCPS |
| 161 | def getBuildCauseActions(build) { |
Denis Egorenko | 270c530 | 2019-01-10 20:53:01 +0400 | [diff] [blame] | 162 | for(action in build.actions) { |
| 163 | if (action instanceof hudson.model.CauseAction) { |
| 164 | return action.causes |
| 165 | } |
Denis Egorenko | 46ff138 | 2018-12-14 15:46:36 +0400 | [diff] [blame] | 166 | } |
Denis Egorenko | 270c530 | 2019-01-10 20:53:01 +0400 | [diff] [blame] | 167 | return [] |
Denis Egorenko | 46ff138 | 2018-12-14 15:46:36 +0400 | [diff] [blame] | 168 | } |
| 169 | |
| 170 | /** |
| 171 | * Get list of builds, triggered by Gerrit with given build |
| 172 | * @param build Job build object (like, currentBuild.rawBuild) |
| 173 | * @return list of builds with names and numbers |
| 174 | */ |
| 175 | @NonCPS |
| 176 | def getGerritBuildContext(build) { |
| 177 | def causes = getBuildCauseActions(build) |
Denis Egorenko | 270c530 | 2019-01-10 20:53:01 +0400 | [diff] [blame] | 178 | for(cause in causes) { |
| 179 | if (cause instanceof com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritCause) { |
| 180 | return cause.context.getOtherBuilds() |
| 181 | } |
Denis Egorenko | 46ff138 | 2018-12-14 15:46:36 +0400 | [diff] [blame] | 182 | } |
Denis Egorenko | 270c530 | 2019-01-10 20:53:01 +0400 | [diff] [blame] | 183 | return [] |
Denis Egorenko | 46ff138 | 2018-12-14 15:46:36 +0400 | [diff] [blame] | 184 | } |
| 185 | |
| 186 | /** |
| 187 | * Wait for other jobs |
| 188 | * @param config config parameter: |
| 189 | * builds - List of job build objects, which should be checked |
| 190 | * checkBuilds - List of job names or regexps, which should be used to check provided builds list |
| 191 | * regexp - Wheither to use regexp or simple string matching |
| 192 | */ |
Denis Egorenko | 270c530 | 2019-01-10 20:53:01 +0400 | [diff] [blame] | 193 | @NonCPS |
Denis Egorenko | 46ff138 | 2018-12-14 15:46:36 +0400 | [diff] [blame] | 194 | def waitForOtherBuilds(LinkedHashMap config){ |
Denis Egorenko | 270c530 | 2019-01-10 20:53:01 +0400 | [diff] [blame] | 195 | def context = config.get('context', 'gerrit') |
| 196 | def builds = [] |
| 197 | if (context == 'gerrit') { |
| 198 | builds = getGerritBuildContext(currentBuild.rawBuild) |
| 199 | } else if (context == 'custom') { |
| 200 | builds = config.get('builds') |
| 201 | } |
Denis Egorenko | 46ff138 | 2018-12-14 15:46:36 +0400 | [diff] [blame] | 202 | def checkBuilds = config.get('checkBuilds') |
| 203 | def regexp = config.get('regexp', false) |
Denis Egorenko | 270c530 | 2019-01-10 20:53:01 +0400 | [diff] [blame] | 204 | |
Denis Egorenko | 46ff138 | 2018-12-14 15:46:36 +0400 | [diff] [blame] | 205 | def waitForBuilds = builds.findAll { build -> |
| 206 | def jobName = build.fullDisplayName.tokenize(' ')[0] |
| 207 | if (regexp) { |
| 208 | checkBuilds.find { jobName ==~ it } |
| 209 | } else { |
| 210 | jobName in checkBuilds |
| 211 | } |
| 212 | } |
Denis Egorenko | 270c530 | 2019-01-10 20:53:01 +0400 | [diff] [blame] | 213 | |
| 214 | def buildsMap = [] |
Denis Egorenko | 46ff138 | 2018-12-14 15:46:36 +0400 | [diff] [blame] | 215 | if (waitForBuilds) { |
| 216 | def waiting = true |
Denis Egorenko | 270c530 | 2019-01-10 20:53:01 +0400 | [diff] [blame] | 217 | print "\u001B[36mWaiting for next jobs: ${waitForBuilds}\u001B[0m" |
Denis Egorenko | 46ff138 | 2018-12-14 15:46:36 +0400 | [diff] [blame] | 218 | while(waiting) { |
| 219 | waiting = false |
| 220 | waitForBuilds.each { job -> |
| 221 | if (job.inProgress) { |
| 222 | waiting = true |
Denis Egorenko | 270c530 | 2019-01-10 20:53:01 +0400 | [diff] [blame] | 223 | } else { |
| 224 | buildInfo = [ |
| 225 | 'jobName': job.fullDisplayName.tokenize(' ')[0], |
| 226 | 'jobNumber': job.number, |
| 227 | ] |
| 228 | buildsMap.add(buildInfo) |
Denis Egorenko | 46ff138 | 2018-12-14 15:46:36 +0400 | [diff] [blame] | 229 | } |
| 230 | } |
| 231 | } |
| 232 | } |
Denis Egorenko | 270c530 | 2019-01-10 20:53:01 +0400 | [diff] [blame] | 233 | return buildsMap |
Denis Egorenko | 46ff138 | 2018-12-14 15:46:36 +0400 | [diff] [blame] | 234 | } |
azvyagintsev | eb81735 | 2019-11-08 13:30:17 +0200 | [diff] [blame] | 235 | |
| 236 | /** |
| 237 | * Check dependency jobs passed successfully |
| 238 | |
Владислав Наумов | 90caa88 | 2020-08-12 17:20:12 +0200 | [diff] [blame] | 239 | * @param block (bool) Block child jobs in case of parent dependencies failed |
| 240 | * @param allowNotBuilt (bool) Approve not_built status of the dependency job |
| 241 | * @return (map)[ |
| 242 | * status: (bool) True if there are no failed dependencies |
| 243 | * log: (string) Verbose description |
| 244 | * ] |
azvyagintsev | eb81735 | 2019-11-08 13:30:17 +0200 | [diff] [blame] | 245 | */ |
Владислав Наумов | 90caa88 | 2020-08-12 17:20:12 +0200 | [diff] [blame] | 246 | def checkDependencyJobs(block = true, allowNotBuilt = false) { |
azvyagintsev | eb81735 | 2019-11-08 13:30:17 +0200 | [diff] [blame] | 247 | def common = new com.mirantis.mk.Common() |
Владислав Наумов | 90caa88 | 2020-08-12 17:20:12 +0200 | [diff] [blame] | 248 | def acceptedStatuses = ['SUCCESS'] |
| 249 | if (allowNotBuilt) { |
| 250 | acceptedStatuses.add('NOT_BUILT') |
| 251 | } |
| 252 | |
azvyagintsev | eb81735 | 2019-11-08 13:30:17 +0200 | [diff] [blame] | 253 | depList = [] |
| 254 | if (env.TRIGGER_DEPENDENCY_KEYS){ |
| 255 | common.infoMsg('Job may depends on parent jobs, check if dependency jobs exist...') |
| 256 | depKeys = env.TRIGGER_DEPENDENCY_KEYS.toString() |
| 257 | depList = depKeys.split() |
| 258 | if (depList){ |
Владислав Наумов | 90caa88 | 2020-08-12 17:20:12 +0200 | [diff] [blame] | 259 | common.infoMsg("Here is dependency jobs-list: ${depList} , accepted job statuses are: ${acceptedStatuses}") |
azvyagintsev | eb81735 | 2019-11-08 13:30:17 +0200 | [diff] [blame] | 260 | for (String item : depList) { |
| 261 | prjName = item.replaceAll('[^a-zA-Z0-9]+', '_') |
| 262 | triggerResult = 'TRIGGER_' + prjName.toUpperCase() + '_BUILD_RESULT' |
| 263 | triggerJobName = 'TRIGGER_' + prjName.toUpperCase() + '_BUILD_NAME' |
| 264 | triggerJobBuild = 'TRIGGER_' + prjName.toUpperCase() + '_BUILD_NUMBER' |
Владислав Наумов | 90caa88 | 2020-08-12 17:20:12 +0200 | [diff] [blame] | 265 | if (!acceptedStatuses.contains(env.getProperty(triggerResult))) { |
azvyagintsev | eb81735 | 2019-11-08 13:30:17 +0200 | [diff] [blame] | 266 | msg = "Dependency job ${env.getProperty(triggerJobName)} #${env.getProperty(triggerJobBuild)} is ${env.getProperty(triggerResult)}" |
| 267 | common.warningMsg(msg) |
| 268 | if (block){ |
| 269 | currentBuild.result = 'NOT_BUILT' |
| 270 | currentBuild.description = msg |
| 271 | } |
| 272 | return [status: false, log: msg, jobs: depList] |
| 273 | } |
| 274 | } |
| 275 | } |
| 276 | } else { |
| 277 | common.infoMsg('There is no job-dependencies') |
| 278 | } |
| 279 | return [status: true, log: '', jobs: depList] |
| 280 | } |
Владислав Наумов | 34e9ed8 | 2021-02-02 17:04:56 +0100 | [diff] [blame] | 281 | |
| 282 | /** |
| 283 | * Return jenkins infra metadata according to specified jenkins intstance |
| 284 | |
| 285 | * @param jenkinsServerURL (string) URL to jenkins server in form: env.JENKINS_URL |
| 286 | * @return (map)[ |
| 287 | * jenkins_service_user: (string) name of jenkins user needed for gerrit ops |
| 288 | * ] |
| 289 | */ |
| 290 | def getJenkinsInfraMetadata(jenkinsServerURL) { |
| 291 | def meta = [ |
| 292 | jenkins_service_user: '', |
| 293 | ] |
| 294 | |
| 295 | switch (jenkinsServerURL) { |
| 296 | case 'https://ci.mcp.mirantis.net/': |
| 297 | meta['jenkins_service_user'] = 'mcp-jenkins' |
| 298 | break |
| 299 | case 'https://mcc-ci.infra.mirantis.net/': |
| 300 | meta['jenkins_service_user'] = 'mcc-ci-jenkins' |
| 301 | break |
| 302 | default: |
| 303 | error("Failed to detect jenkins service user, supported jenkins platforms: 'https://ci.mcp.mirantis.net/' 'https://mcc-ci.infra.mirantis.net/'") |
| 304 | } |
| 305 | |
| 306 | return meta |
| 307 | } |
Владислав Наумов | 468bc74 | 2021-04-08 14:23:43 +0200 | [diff] [blame] | 308 | |
| 309 | /** |
| 310 | * Get list of all jenkins workers matched desired label |
| 311 | * |
| 312 | * @param labelString (string) desired worker label |
| 313 | * @return (list) all workers, currently matched label |
| 314 | */ |
| 315 | @NonCPS |
| 316 | def getWorkers(String labelString = null) { |
| 317 | def workerLabel = hudson.model.labels.LabelAtom.get(labelString) |
| 318 | def workers = [] |
| 319 | hudson.model.Hudson.instance.slaves.each { |
| 320 | if (it.getComputer().isOnline()) { |
| 321 | if (workerLabel) { |
| 322 | if (workerLabel in it.getAssignedLabels()) { |
| 323 | workers << it.name |
| 324 | } |
| 325 | } else { |
| 326 | // if labelString is null, getting all workers |
| 327 | workers << it.name |
| 328 | } |
| 329 | } |
| 330 | } |
| 331 | return workers |
| 332 | } |
Mykyta Karpin | 158e44d | 2024-10-10 14:54:14 +0200 | [diff] [blame^] | 333 | |
| 334 | /** |
| 335 | * Get deployment environment and related jenkins lock label and lock resource |
| 336 | * |
| 337 | * @param initialEnv (string) Name of initially requested environment e.g. imc-eu or auto |
| 338 | * @param namespace (string) Name of environment namespace e.g imc-oscore-team |
| 339 | * @param resources (int) Quantity of required lockable resources |
| 340 | * @param candidateEnvs (list) List of names of env candidates to choose between |
| 341 | * @return (list) List whith environment name, lock label and lock resource |
| 342 | */ |
| 343 | def getEnvWithLockableResources(initialEnv, namespace, resources = 1, candidateEnvs = ["imc-eu", "imc-us"]){ |
| 344 | def common = new com.mirantis.mk.Common() |
| 345 | def lockResource = null |
| 346 | def env = initialEnv |
| 347 | def lockLabel = "${namespace}-${env}" |
| 348 | def lrm = LRM.get() |
| 349 | if (initialEnv == "auto"){ |
| 350 | def freeResources = [:] |
| 351 | for (cEnv in candidateEnvs){ |
| 352 | def label = "${namespace}-${cEnv}" |
| 353 | freeResources[label] = lrm.getFreeResourceAmount(label) |
| 354 | } |
| 355 | common.infoMsg("Detecting target environment from candidates ${freeResources}") |
| 356 | def max = 0 |
| 357 | def keys = freeResources.keySet().toList() |
| 358 | Collections.shuffle(keys) |
| 359 | for (key in keys){ |
| 360 | if (freeResources[key] >= max){ |
| 361 | max = freeResources[key] |
| 362 | lockLabel = key |
| 363 | } |
| 364 | } |
| 365 | if (max < resources){ |
| 366 | lockLabel = keys[0] |
| 367 | } |
| 368 | env = lockLabel.replaceAll("${namespace}-", "") |
| 369 | common.infoMsg("Detected target environment ${env} lock ${lockLabel}") |
| 370 | } |
| 371 | // If no label configured on existing resources, create random lockresource |
| 372 | if (! lrm.isValidLabel(lockLabel)){ |
| 373 | common.infoMsg("Running without locking, lock label ${lockLabel} does not exist") |
| 374 | lockLabel = null |
| 375 | lockResource = UUID.randomUUID().toString() |
| 376 | } |
| 377 | return [env, lockLabel, lockResource] |
| 378 | } |