blob: 7ad183427f064c9364aa69c41c0b2ba446619fab [file] [log] [blame]
Jakub Josef79ecec32017-02-17 14:36:28 +01001package com.mirantis.mk
Jakub Josef6d8082b2017-08-09 12:40:50 +02002
Jakub Josefb41c8d52017-03-24 13:52:24 +01003import static groovy.json.JsonOutput.prettyPrint
4import static groovy.json.JsonOutput.toJson
Jakub Josef6d8082b2017-08-09 12:40:50 +02005
Jakub Josefbceaa322017-06-13 18:28:27 +02006import com.cloudbees.groovy.cps.NonCPS
Jakub Josefb7ab8472017-04-05 14:56:53 +02007import groovy.json.JsonSlurperClassic
Jakub Josef79ecec32017-02-17 14:36:28 +01008/**
9 *
10 * Common functions
11 *
12 */
13
14/**
15 * Generate current timestamp
16 *
17 * @param format Defaults to yyyyMMddHHmmss
18 */
19def getDatetime(format="yyyyMMddHHmmss") {
20 def now = new Date();
21 return now.format(format, TimeZone.getTimeZone('UTC'));
22}
23
24/**
Jakub Josef79ecec32017-02-17 14:36:28 +010025 * Return workspace.
26 * Currently implemented by calling pwd so it won't return relevant result in
27 * dir context
28 */
Jakub Josefa661b8c2018-01-17 14:51:25 +010029def getWorkspace(includeBuildNum=false) {
Jakub Josef79ecec32017-02-17 14:36:28 +010030 def workspace = sh script: 'pwd', returnStdout: true
31 workspace = workspace.trim()
Jakub Josefa661b8c2018-01-17 14:51:25 +010032 if(includeBuildNum){
33 if(!workspace.endsWith("/")){
34 workspace += "/"
35 }
36 workspace += env.BUILD_NUMBER
37 }
Jakub Josef79ecec32017-02-17 14:36:28 +010038 return workspace
39}
40
41/**
Filip Pytloun81c864d2017-03-21 15:19:30 +010042 * Get UID of jenkins user.
43 * Must be run from context of node
44 */
45def getJenkinsUid() {
46 return sh (
47 script: 'id -u',
48 returnStdout: true
49 ).trim()
50}
51
52/**
53 * Get GID of jenkins user.
54 * Must be run from context of node
55 */
56def getJenkinsGid() {
57 return sh (
58 script: 'id -g',
59 returnStdout: true
60 ).trim()
61}
62
63/**
Jakub Josefc8074db2018-01-30 13:33:20 +010064 * Returns Jenkins user uid and gid in one list (in that order)
65 * Must be run from context of node
66 */
67def getJenkinsUserIds(){
68 return sh(script: "id -u && id -g", returnStdout: true).tokenize("\n")
69}
70
71/**
Alexander Evseevbc1fea42017-12-13 10:03:03 +010072 *
73 * Find credentials by ID
74 *
75 * @param credsId Credentials ID
76 * @param credsType Credentials type (optional)
77 *
78 */
79def getCredentialsById(String credsId, String credsType = 'any') {
80 def credClasses = [ // ordered by class name
81 sshKey: com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.class,
82 cert: com.cloudbees.plugins.credentials.common.CertificateCredentials.class,
83 password: com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
84 any: com.cloudbees.plugins.credentials.impl.BaseStandardCredentials.class,
85 dockerCert: org.jenkinsci.plugins.docker.commons.credentials.DockerServerCredentials.class,
86 file: org.jenkinsci.plugins.plaincredentials.FileCredentials.class,
87 string: org.jenkinsci.plugins.plaincredentials.StringCredentials.class,
88 ]
89 return com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
90 credClasses[credsType],
91 jenkins.model.Jenkins.instance
92 ).findAll {cred -> cred.id == credsId}[0]
93}
94
95/**
Jakub Josef79ecec32017-02-17 14:36:28 +010096 * Get credentials from store
97 *
98 * @param id Credentials name
99 */
Jakub Josef3d9d9ab2017-03-14 15:09:03 +0100100def getCredentials(id, cred_type = "username_password") {
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100101 warningMsg('You are using obsolete function. Please switch to use `getCredentialsById()`')
Jakub Josef79ecec32017-02-17 14:36:28 +0100102
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100103 type_map = [
104 username_password: 'password',
105 key: 'sshKey',
106 ]
Jakub Josef79ecec32017-02-17 14:36:28 +0100107
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100108 return getCredentialsById(id, type_map[cred_type])
Jakub Josef79ecec32017-02-17 14:36:28 +0100109}
110
111/**
112 * Abort build, wait for some time and ensure we will terminate
113 */
114def abortBuild() {
115 currentBuild.build().doStop()
116 sleep(180)
117 // just to be sure we will terminate
118 throw new InterruptedException()
119}
120
121/**
Jakub Josefbceaa322017-06-13 18:28:27 +0200122 * Print pretty-printed string representation of given item
123 * @param item item to be pretty-printed (list, map, whatever)
124 */
125def prettyPrint(item){
126 println prettify(item)
127}
128
129/**
Jakub Josefb41c8d52017-03-24 13:52:24 +0100130 * Return pretty-printed string representation of given item
131 * @param item item to be pretty-printed (list, map, whatever)
132 * @return pretty-printed string
133 */
Jakub Josefbceaa322017-06-13 18:28:27 +0200134def prettify(item){
135 return groovy.json.JsonOutput.prettyPrint(toJson(item)).replace('\\n', System.getProperty('line.separator'))
Jakub Josefb41c8d52017-03-24 13:52:24 +0100136}
137
138/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100139 * Print informational message
140 *
141 * @param msg
142 * @param color Colorful output or not
143 */
144def infoMsg(msg, color = true) {
145 printMsg(msg, "cyan")
146}
147
148/**
149 * Print error message
150 *
151 * @param msg
152 * @param color Colorful output or not
153 */
154def errorMsg(msg, color = true) {
155 printMsg(msg, "red")
156}
157
158/**
159 * Print success message
160 *
161 * @param msg
162 * @param color Colorful output or not
163 */
164def successMsg(msg, color = true) {
165 printMsg(msg, "green")
166}
167
168/**
169 * Print warning message
170 *
171 * @param msg
172 * @param color Colorful output or not
173 */
174def warningMsg(msg, color = true) {
Jakub Josef0e7bd632017-03-16 16:25:05 +0100175 printMsg(msg, "yellow")
Jakub Josef79ecec32017-02-17 14:36:28 +0100176}
177
178/**
Jakub Josef952ae0b2017-03-14 19:04:21 +0100179 * Print debug message, this message will show only if DEBUG global variable is present
180 * @param msg
181 * @param color Colorful output or not
182 */
183def debugMsg(msg, color = true){
Jakub Josef9a836ac2017-04-24 12:26:02 +0200184 // if debug property exists on env, debug is enabled
Jakub Josef66976f62017-04-24 16:32:23 +0200185 if(env.getEnvironment().containsKey('DEBUG') && env['DEBUG'] == "true"){
Jakub Josef74b34692017-03-15 12:10:57 +0100186 printMsg("[DEBUG] ${msg}", "red")
Jakub Josef952ae0b2017-03-14 19:04:21 +0100187 }
188}
189
190/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100191 * Print message
192 *
193 * @param msg Message to be printed
194 * @param level Level of message (default INFO)
195 * @param color Color to use for output or false (default)
196 */
197def printMsg(msg, color = false) {
198 colors = [
199 'red' : '\u001B[31m',
200 'black' : '\u001B[30m',
201 'green' : '\u001B[32m',
202 'yellow': '\u001B[33m',
203 'blue' : '\u001B[34m',
204 'purple': '\u001B[35m',
205 'cyan' : '\u001B[36m',
206 'white' : '\u001B[37m',
207 'reset' : '\u001B[0m'
208 ]
209 if (color != false) {
Jakub Josefe6c562e2017-08-09 14:41:03 +0200210 print "${colors[color]}${msg}${colors.reset}"
Jakub Josef79ecec32017-02-17 14:36:28 +0100211 } else {
212 print "[${level}] ${msg}"
213 }
214}
215
216/**
217 * Traverse directory structure and return list of files
218 *
219 * @param path Path to search
220 * @param type Type of files to search (groovy.io.FileType.FILES)
221 */
222@NonCPS
223def getFiles(path, type=groovy.io.FileType.FILES) {
224 files = []
225 new File(path).eachFile(type) {
226 files[] = it
227 }
228 return files
229}
230
231/**
232 * Helper method to convert map into form of list of [key,value] to avoid
233 * unserializable exceptions
234 *
235 * @param m Map
236 */
237@NonCPS
238def entries(m) {
239 m.collect {k, v -> [k, v]}
240}
241
242/**
243 * Opposite of build-in parallel, run map of steps in serial
244 *
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200245 * @param steps Map of String<name>: CPSClosure2<step> (or list of closures)
Jakub Josef79ecec32017-02-17 14:36:28 +0100246 */
247def serial(steps) {
248 stepsArray = entries(steps)
249 for (i=0; i < stepsArray.size; i++) {
Jakub Josefd31de302017-05-15 13:59:18 +0200250 def step = stepsArray[i]
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200251 def dummySteps = [:]
252 def stepKey
Jakub Josef228aae92017-05-15 19:04:43 +0200253 if(step[1] instanceof List || step[1] instanceof Map){
Jakub Josef538be162017-05-15 19:11:48 +0200254 for(j=0;j < step[1].size(); j++){
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200255 if(step[1] instanceof List){
256 stepKey = j
257 }else if(step[1] instanceof Map){
258 stepKey = step[1].keySet()[j]
259 }
260 dummySteps.put("step-${step[0]}-${stepKey}",step[1][stepKey])
Jakub Josefd31de302017-05-15 13:59:18 +0200261 }
262 }else{
263 dummySteps.put(step[0], step[1])
264 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100265 parallel dummySteps
266 }
267}
268
269/**
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200270 * Partition given list to list of small lists
271 * @param inputList input list
272 * @param partitionSize (partition size, optional, default 5)
273 */
274def partitionList(inputList, partitionSize=5){
275 List<List<String>> partitions = new ArrayList<>();
276 for (int i=0; i<inputList.size(); i += partitionSize) {
277 partitions.add(new ArrayList<String>(inputList.subList(i, Math.min(i + partitionSize, inputList.size()))));
278 }
279 return partitions
280}
281
282/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100283 * Get password credentials from store
284 *
285 * @param id Credentials name
286 */
287def getPasswordCredentials(id) {
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100288 return getCredentialsById(id, 'password')
Jakub Josef79ecec32017-02-17 14:36:28 +0100289}
290
291/**
292 * Get SSH credentials from store
293 *
294 * @param id Credentials name
295 */
296def getSshCredentials(id) {
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100297 return getCredentialsById(id, 'sshKey')
Jakub Josef79ecec32017-02-17 14:36:28 +0100298}
Jakub Josef79ecec32017-02-17 14:36:28 +0100299
300/**
301 * Tests Jenkins instance for existence of plugin with given name
302 * @param pluginName plugin short name to test
303 * @return boolean result
304 */
305@NonCPS
306def jenkinsHasPlugin(pluginName){
307 return Jenkins.instance.pluginManager.plugins.collect{p -> p.shortName}.contains(pluginName)
308}
309
310@NonCPS
311def _needNotification(notificatedTypes, buildStatus, jobName) {
312 if(notificatedTypes && notificatedTypes.contains("onchange")){
313 if(jobName){
314 def job = Jenkins.instance.getItem(jobName)
315 def numbuilds = job.builds.size()
316 if (numbuilds > 0){
317 //actual build is first for some reasons, so last finished build is second
318 def lastBuild = job.builds[1]
319 if(lastBuild){
320 if(lastBuild.result.toString().toLowerCase().equals(buildStatus)){
321 println("Build status didn't changed since last build, not sending notifications")
322 return false;
323 }
324 }
325 }
326 }
327 }else if(!notificatedTypes.contains(buildStatus)){
328 return false;
329 }
330 return true;
331}
332
333/**
334 * Send notification to all enabled notifications services
335 * @param buildStatus message type (success, warning, error), null means SUCCESSFUL
336 * @param msgText message text
337 * @param enabledNotifications list of enabled notification types, types: slack, hipchat, email, default empty
338 * @param notificatedTypes types of notifications will be sent, default onchange - notificate if current build result not equal last result;
339 * otherwise use - ["success","unstable","failed"]
340 * @param jobName optional job name param, if empty env.JOB_NAME will be used
Jakub Josefd0571152017-07-17 14:11:39 +0200341 * @param buildNumber build number param, if empty env.BUILD_NUM will be used
342 * @param buildUrl build url param, if empty env.BUILD_URL will be used
Jakub Josef79ecec32017-02-17 14:36:28 +0100343 * @param mailFrom mail FROM param, if empty "jenkins" will be used, it's mandatory for sending email notifications
Jakub Josefd0571152017-07-17 14:11:39 +0200344 * @param mailTo mail TO param, it's mandatory for sending email notifications, this option enable mail notification
Jakub Josef79ecec32017-02-17 14:36:28 +0100345 */
346def sendNotification(buildStatus, msgText="", enabledNotifications = [], notificatedTypes=["onchange"], jobName=null, buildNumber=null, buildUrl=null, mailFrom="jenkins", mailTo=null){
347 // Default values
348 def colorName = 'blue'
349 def colorCode = '#0000FF'
350 def buildStatusParam = buildStatus != null && buildStatus != "" ? buildStatus : "SUCCESS"
351 def jobNameParam = jobName != null && jobName != "" ? jobName : env.JOB_NAME
352 def buildNumberParam = buildNumber != null && buildNumber != "" ? buildNumber : env.BUILD_NUMBER
353 def buildUrlParam = buildUrl != null && buildUrl != "" ? buildUrl : env.BUILD_URL
354 def subject = "${buildStatusParam}: Job '${jobNameParam} [${buildNumberParam}]'"
355 def summary = "${subject} (${buildUrlParam})"
356
357 if(msgText != null && msgText != ""){
358 summary+="\n${msgText}"
359 }
360 if(buildStatusParam.toLowerCase().equals("success")){
361 colorCode = "#00FF00"
362 colorName = "green"
363 }else if(buildStatusParam.toLowerCase().equals("unstable")){
364 colorCode = "#FFFF00"
365 colorName = "yellow"
366 }else if(buildStatusParam.toLowerCase().equals("failure")){
367 colorCode = "#FF0000"
368 colorName = "red"
369 }
370 if(_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)){
371 if(enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")){
372 try{
373 slackSend color: colorCode, message: summary
374 }catch(Exception e){
375 println("Calling slack plugin failed")
376 e.printStackTrace()
377 }
378 }
379 if(enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")){
380 try{
381 hipchatSend color: colorName.toUpperCase(), message: summary
382 }catch(Exception e){
383 println("Calling hipchat plugin failed")
384 e.printStackTrace()
385 }
386 }
387 if(enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != ""){
388 try{
389 mail body: summary, from: mailFrom, subject: subject, to: mailTo
390 }catch(Exception e){
391 println("Sending mail plugin failed")
392 e.printStackTrace()
393 }
394 }
395 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100396}
chnyda4e5ac792017-03-14 15:24:18 +0100397
398/**
399 * Execute linux command and catch nth element
400 * @param cmd command to execute
401 * @param index index to retrieve
402 * @return index-th element
403 */
404
405def cutOrDie(cmd, index)
406{
407 def common = new com.mirantis.mk.Common()
408 def output
409 try {
410 output = sh(script: cmd, returnStdout: true)
411 def result = output.tokenize(" ")[index]
412 return result;
413 } catch (Exception e) {
414 common.errorMsg("Failed to execute cmd: ${cmd}\n output: ${output}")
415 }
Filip Pytloun81c864d2017-03-21 15:19:30 +0100416}
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100417
418/**
419 * Check variable contains keyword
420 * @param variable keywork is searched (contains) here
421 * @param keyword string to look for
422 * @return True if variable contains keyword (case insensitive), False if do not contains or any of input isn't a string
423 */
424
425def checkContains(variable, keyword) {
Jakub Josef7a8dea22017-03-23 19:51:32 +0100426 if(env.getEnvironment().containsKey(variable)){
427 return env[variable] && env[variable].toLowerCase().contains(keyword.toLowerCase())
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100428 } else {
Tomáš Kukrálc76c1e02017-03-23 19:06:59 +0100429 return false
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100430 }
431}
Jakub Josefa877db52017-04-05 14:22:30 +0200432
433/**
434 * Parse JSON string to hashmap
435 * @param jsonString input JSON string
436 * @return created hashmap
437 */
438def parseJSON(jsonString){
439 def m = [:]
Jakub Josefb7ab8472017-04-05 14:56:53 +0200440 def lazyMap = new JsonSlurperClassic().parseText(jsonString)
Jakub Josefa877db52017-04-05 14:22:30 +0200441 m.putAll(lazyMap)
442 return m
443}
Jakub Josefed239cd2017-05-09 15:27:33 +0200444
445/**
446 * Test pipeline input parameter existence and validity (not null and not empty string)
447 * @param paramName input parameter name (usually uppercase)
448 */
449def validInputParam(paramName){
450 return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200451}
452
453/**
454 * Take list of hashmaps and count number of hashmaps with parameter equals eq
455 * @param lm list of hashmaps
456 * @param param define parameter of hashmap to read and compare
457 * @param eq desired value of hashmap parameter
458 * @return count of hashmaps meeting defined condition
459 */
460
461@NonCPS
462def countHashMapEquals(lm, param, eq) {
Tomáš Kukrál5dd12072017-06-13 15:54:44 +0200463 return lm.stream().filter{i -> i[param].equals(eq)}.collect(java.util.stream.Collectors.counting())
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200464}
Vasyl Saienkofb055b12017-11-23 18:15:23 +0200465
466/**
467 * Execute shell command and return stdout, stderr and status
468 *
469 * @param cmd Command to execute
470 * @return map with stdout, stderr, status keys
471 */
472
473def shCmdStatus(cmd) {
474 def res = [:]
475 def stderr = sh(script: 'mktemp', returnStdout: true).trim()
476 def stdout = sh(script: 'mktemp', returnStdout: true).trim()
477
478 try {
479 def status = sh(script:"${cmd} 1>${stdout} 2>${stderr}", returnStatus: true)
480 res['stderr'] = sh(script: "cat ${stderr}", returnStdout: true)
481 res['stdout'] = sh(script: "cat ${stdout}", returnStdout: true)
482 res['status'] = status
483 } finally {
484 sh(script: "rm ${stderr}", returnStdout: true)
485 sh(script: "rm ${stdout}", returnStdout: true)
486 }
487
488 return res
489}
Richard Felkl66a242d2018-01-25 15:27:15 +0100490
491
492/**
493 * Retry commands passed to body
494 *
495 * @param times Number of retries
Jakub Josef61463c72018-02-13 16:10:56 +0100496 * @param delay Delay between retries (in seconds)
Richard Felkl66a242d2018-01-25 15:27:15 +0100497 * @param body Commands to be in retry block
498 * @return calling commands in body
499 * @example retry(3,5){ function body }
500 * retry{ function body }
501 */
502
503def retry(int times = 5, int delay = 0, Closure body) {
504 int retries = 0
505 def exceptions = []
506 while(retries++ < times) {
507 try {
508 return body.call()
509 } catch(e) {
510 sleep(delay)
511 }
512 }
513 currentBuild.result = "FAILURE"
514 throw new Exception("Failed after $times retries")
515}
Dmitry Pyzhov0d0d8522018-05-15 22:37:37 +0300516
517
518/**
519 * Wait for user input with timeout
520 *
521 * @param timeoutInSeconds Timeout
522 * @param options Options for input widget
523 */
524def waitForInputThenPass(timeoutInSeconds, options=[message: 'Ready to go?']) {
525 def userInput = true
526 try {
527 timeout(time: timeoutInSeconds, unit: 'SECONDS') {
528 userInput = input options
529 }
530 } catch(err) { // timeout reached or input false
531 def user = err.getCauses()[0].getUser()
532 if('SYSTEM' == user.toString()) { // SYSTEM means timeout.
533 println("Timeout, proceeding")
534 } else {
535 userInput = false
536 println("Aborted by: [${user}]")
537 throw err
538 }
539 }
540 return userInput
541}
Oleksii Grudev9e1d97a2018-06-29 16:04:30 +0300542
543/**
544 * Function receives Map variable as input and sorts it
545 * by values ascending. Returns sorted Map
546 * @param _map Map variable
547 */
548@NonCPS
549def SortMapByValueAsc(_map) {
550 def sortedMap = _map.sort {it.value}
551 return sortedMap
552}