blob: 97b28c88c4ef07e14e0061b172b20d47dd8a7a6a [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/**
Alexander Evseevbc1fea42017-12-13 10:03:03 +010064 *
65 * Find credentials by ID
66 *
67 * @param credsId Credentials ID
68 * @param credsType Credentials type (optional)
69 *
70 */
71def getCredentialsById(String credsId, String credsType = 'any') {
72 def credClasses = [ // ordered by class name
73 sshKey: com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.class,
74 cert: com.cloudbees.plugins.credentials.common.CertificateCredentials.class,
75 password: com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
76 any: com.cloudbees.plugins.credentials.impl.BaseStandardCredentials.class,
77 dockerCert: org.jenkinsci.plugins.docker.commons.credentials.DockerServerCredentials.class,
78 file: org.jenkinsci.plugins.plaincredentials.FileCredentials.class,
79 string: org.jenkinsci.plugins.plaincredentials.StringCredentials.class,
80 ]
81 return com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
82 credClasses[credsType],
83 jenkins.model.Jenkins.instance
84 ).findAll {cred -> cred.id == credsId}[0]
85}
86
87/**
Jakub Josef79ecec32017-02-17 14:36:28 +010088 * Get credentials from store
89 *
90 * @param id Credentials name
91 */
Jakub Josef3d9d9ab2017-03-14 15:09:03 +010092def getCredentials(id, cred_type = "username_password") {
Alexander Evseevbc1fea42017-12-13 10:03:03 +010093 warningMsg('You are using obsolete function. Please switch to use `getCredentialsById()`')
Jakub Josef79ecec32017-02-17 14:36:28 +010094
Alexander Evseevbc1fea42017-12-13 10:03:03 +010095 type_map = [
96 username_password: 'password',
97 key: 'sshKey',
98 ]
Jakub Josef79ecec32017-02-17 14:36:28 +010099
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100100 return getCredentialsById(id, type_map[cred_type])
Jakub Josef79ecec32017-02-17 14:36:28 +0100101}
102
103/**
104 * Abort build, wait for some time and ensure we will terminate
105 */
106def abortBuild() {
107 currentBuild.build().doStop()
108 sleep(180)
109 // just to be sure we will terminate
110 throw new InterruptedException()
111}
112
113/**
Jakub Josefbceaa322017-06-13 18:28:27 +0200114 * Print pretty-printed string representation of given item
115 * @param item item to be pretty-printed (list, map, whatever)
116 */
117def prettyPrint(item){
118 println prettify(item)
119}
120
121/**
Jakub Josefb41c8d52017-03-24 13:52:24 +0100122 * Return pretty-printed string representation of given item
123 * @param item item to be pretty-printed (list, map, whatever)
124 * @return pretty-printed string
125 */
Jakub Josefbceaa322017-06-13 18:28:27 +0200126def prettify(item){
127 return groovy.json.JsonOutput.prettyPrint(toJson(item)).replace('\\n', System.getProperty('line.separator'))
Jakub Josefb41c8d52017-03-24 13:52:24 +0100128}
129
130/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100131 * Print informational message
132 *
133 * @param msg
134 * @param color Colorful output or not
135 */
136def infoMsg(msg, color = true) {
137 printMsg(msg, "cyan")
138}
139
140/**
141 * Print error message
142 *
143 * @param msg
144 * @param color Colorful output or not
145 */
146def errorMsg(msg, color = true) {
147 printMsg(msg, "red")
148}
149
150/**
151 * Print success message
152 *
153 * @param msg
154 * @param color Colorful output or not
155 */
156def successMsg(msg, color = true) {
157 printMsg(msg, "green")
158}
159
160/**
161 * Print warning message
162 *
163 * @param msg
164 * @param color Colorful output or not
165 */
166def warningMsg(msg, color = true) {
Jakub Josef0e7bd632017-03-16 16:25:05 +0100167 printMsg(msg, "yellow")
Jakub Josef79ecec32017-02-17 14:36:28 +0100168}
169
170/**
Jakub Josef952ae0b2017-03-14 19:04:21 +0100171 * Print debug message, this message will show only if DEBUG global variable is present
172 * @param msg
173 * @param color Colorful output or not
174 */
175def debugMsg(msg, color = true){
Jakub Josef9a836ac2017-04-24 12:26:02 +0200176 // if debug property exists on env, debug is enabled
Jakub Josef66976f62017-04-24 16:32:23 +0200177 if(env.getEnvironment().containsKey('DEBUG') && env['DEBUG'] == "true"){
Jakub Josef74b34692017-03-15 12:10:57 +0100178 printMsg("[DEBUG] ${msg}", "red")
Jakub Josef952ae0b2017-03-14 19:04:21 +0100179 }
180}
181
182/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100183 * Print message
184 *
185 * @param msg Message to be printed
186 * @param level Level of message (default INFO)
187 * @param color Color to use for output or false (default)
188 */
189def printMsg(msg, color = false) {
190 colors = [
191 'red' : '\u001B[31m',
192 'black' : '\u001B[30m',
193 'green' : '\u001B[32m',
194 'yellow': '\u001B[33m',
195 'blue' : '\u001B[34m',
196 'purple': '\u001B[35m',
197 'cyan' : '\u001B[36m',
198 'white' : '\u001B[37m',
199 'reset' : '\u001B[0m'
200 ]
201 if (color != false) {
Jakub Josefe6c562e2017-08-09 14:41:03 +0200202 print "${colors[color]}${msg}${colors.reset}"
Jakub Josef79ecec32017-02-17 14:36:28 +0100203 } else {
204 print "[${level}] ${msg}"
205 }
206}
207
208/**
209 * Traverse directory structure and return list of files
210 *
211 * @param path Path to search
212 * @param type Type of files to search (groovy.io.FileType.FILES)
213 */
214@NonCPS
215def getFiles(path, type=groovy.io.FileType.FILES) {
216 files = []
217 new File(path).eachFile(type) {
218 files[] = it
219 }
220 return files
221}
222
223/**
224 * Helper method to convert map into form of list of [key,value] to avoid
225 * unserializable exceptions
226 *
227 * @param m Map
228 */
229@NonCPS
230def entries(m) {
231 m.collect {k, v -> [k, v]}
232}
233
234/**
235 * Opposite of build-in parallel, run map of steps in serial
236 *
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200237 * @param steps Map of String<name>: CPSClosure2<step> (or list of closures)
Jakub Josef79ecec32017-02-17 14:36:28 +0100238 */
239def serial(steps) {
240 stepsArray = entries(steps)
241 for (i=0; i < stepsArray.size; i++) {
Jakub Josefd31de302017-05-15 13:59:18 +0200242 def step = stepsArray[i]
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200243 def dummySteps = [:]
244 def stepKey
Jakub Josef228aae92017-05-15 19:04:43 +0200245 if(step[1] instanceof List || step[1] instanceof Map){
Jakub Josef538be162017-05-15 19:11:48 +0200246 for(j=0;j < step[1].size(); j++){
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200247 if(step[1] instanceof List){
248 stepKey = j
249 }else if(step[1] instanceof Map){
250 stepKey = step[1].keySet()[j]
251 }
252 dummySteps.put("step-${step[0]}-${stepKey}",step[1][stepKey])
Jakub Josefd31de302017-05-15 13:59:18 +0200253 }
254 }else{
255 dummySteps.put(step[0], step[1])
256 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100257 parallel dummySteps
258 }
259}
260
261/**
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200262 * Partition given list to list of small lists
263 * @param inputList input list
264 * @param partitionSize (partition size, optional, default 5)
265 */
266def partitionList(inputList, partitionSize=5){
267 List<List<String>> partitions = new ArrayList<>();
268 for (int i=0; i<inputList.size(); i += partitionSize) {
269 partitions.add(new ArrayList<String>(inputList.subList(i, Math.min(i + partitionSize, inputList.size()))));
270 }
271 return partitions
272}
273
274/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100275 * Get password credentials from store
276 *
277 * @param id Credentials name
278 */
279def getPasswordCredentials(id) {
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100280 return getCredentialsById(id, 'password')
Jakub Josef79ecec32017-02-17 14:36:28 +0100281}
282
283/**
284 * Get SSH credentials from store
285 *
286 * @param id Credentials name
287 */
288def getSshCredentials(id) {
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100289 return getCredentialsById(id, 'sshKey')
Jakub Josef79ecec32017-02-17 14:36:28 +0100290}
Jakub Josef79ecec32017-02-17 14:36:28 +0100291
292/**
293 * Tests Jenkins instance for existence of plugin with given name
294 * @param pluginName plugin short name to test
295 * @return boolean result
296 */
297@NonCPS
298def jenkinsHasPlugin(pluginName){
299 return Jenkins.instance.pluginManager.plugins.collect{p -> p.shortName}.contains(pluginName)
300}
301
302@NonCPS
303def _needNotification(notificatedTypes, buildStatus, jobName) {
304 if(notificatedTypes && notificatedTypes.contains("onchange")){
305 if(jobName){
306 def job = Jenkins.instance.getItem(jobName)
307 def numbuilds = job.builds.size()
308 if (numbuilds > 0){
309 //actual build is first for some reasons, so last finished build is second
310 def lastBuild = job.builds[1]
311 if(lastBuild){
312 if(lastBuild.result.toString().toLowerCase().equals(buildStatus)){
313 println("Build status didn't changed since last build, not sending notifications")
314 return false;
315 }
316 }
317 }
318 }
319 }else if(!notificatedTypes.contains(buildStatus)){
320 return false;
321 }
322 return true;
323}
324
325/**
326 * Send notification to all enabled notifications services
327 * @param buildStatus message type (success, warning, error), null means SUCCESSFUL
328 * @param msgText message text
329 * @param enabledNotifications list of enabled notification types, types: slack, hipchat, email, default empty
330 * @param notificatedTypes types of notifications will be sent, default onchange - notificate if current build result not equal last result;
331 * otherwise use - ["success","unstable","failed"]
332 * @param jobName optional job name param, if empty env.JOB_NAME will be used
Jakub Josefd0571152017-07-17 14:11:39 +0200333 * @param buildNumber build number param, if empty env.BUILD_NUM will be used
334 * @param buildUrl build url param, if empty env.BUILD_URL will be used
Jakub Josef79ecec32017-02-17 14:36:28 +0100335 * @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 +0200336 * @param mailTo mail TO param, it's mandatory for sending email notifications, this option enable mail notification
Jakub Josef79ecec32017-02-17 14:36:28 +0100337 */
338def sendNotification(buildStatus, msgText="", enabledNotifications = [], notificatedTypes=["onchange"], jobName=null, buildNumber=null, buildUrl=null, mailFrom="jenkins", mailTo=null){
339 // Default values
340 def colorName = 'blue'
341 def colorCode = '#0000FF'
342 def buildStatusParam = buildStatus != null && buildStatus != "" ? buildStatus : "SUCCESS"
343 def jobNameParam = jobName != null && jobName != "" ? jobName : env.JOB_NAME
344 def buildNumberParam = buildNumber != null && buildNumber != "" ? buildNumber : env.BUILD_NUMBER
345 def buildUrlParam = buildUrl != null && buildUrl != "" ? buildUrl : env.BUILD_URL
346 def subject = "${buildStatusParam}: Job '${jobNameParam} [${buildNumberParam}]'"
347 def summary = "${subject} (${buildUrlParam})"
348
349 if(msgText != null && msgText != ""){
350 summary+="\n${msgText}"
351 }
352 if(buildStatusParam.toLowerCase().equals("success")){
353 colorCode = "#00FF00"
354 colorName = "green"
355 }else if(buildStatusParam.toLowerCase().equals("unstable")){
356 colorCode = "#FFFF00"
357 colorName = "yellow"
358 }else if(buildStatusParam.toLowerCase().equals("failure")){
359 colorCode = "#FF0000"
360 colorName = "red"
361 }
362 if(_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)){
363 if(enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")){
364 try{
365 slackSend color: colorCode, message: summary
366 }catch(Exception e){
367 println("Calling slack plugin failed")
368 e.printStackTrace()
369 }
370 }
371 if(enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")){
372 try{
373 hipchatSend color: colorName.toUpperCase(), message: summary
374 }catch(Exception e){
375 println("Calling hipchat plugin failed")
376 e.printStackTrace()
377 }
378 }
379 if(enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != ""){
380 try{
381 mail body: summary, from: mailFrom, subject: subject, to: mailTo
382 }catch(Exception e){
383 println("Sending mail plugin failed")
384 e.printStackTrace()
385 }
386 }
387 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100388}
chnyda4e5ac792017-03-14 15:24:18 +0100389
390/**
391 * Execute linux command and catch nth element
392 * @param cmd command to execute
393 * @param index index to retrieve
394 * @return index-th element
395 */
396
397def cutOrDie(cmd, index)
398{
399 def common = new com.mirantis.mk.Common()
400 def output
401 try {
402 output = sh(script: cmd, returnStdout: true)
403 def result = output.tokenize(" ")[index]
404 return result;
405 } catch (Exception e) {
406 common.errorMsg("Failed to execute cmd: ${cmd}\n output: ${output}")
407 }
Filip Pytloun81c864d2017-03-21 15:19:30 +0100408}
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100409
410/**
411 * Check variable contains keyword
412 * @param variable keywork is searched (contains) here
413 * @param keyword string to look for
414 * @return True if variable contains keyword (case insensitive), False if do not contains or any of input isn't a string
415 */
416
417def checkContains(variable, keyword) {
Jakub Josef7a8dea22017-03-23 19:51:32 +0100418 if(env.getEnvironment().containsKey(variable)){
419 return env[variable] && env[variable].toLowerCase().contains(keyword.toLowerCase())
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100420 } else {
Tomáš Kukrálc76c1e02017-03-23 19:06:59 +0100421 return false
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100422 }
423}
Jakub Josefa877db52017-04-05 14:22:30 +0200424
425/**
426 * Parse JSON string to hashmap
427 * @param jsonString input JSON string
428 * @return created hashmap
429 */
430def parseJSON(jsonString){
431 def m = [:]
Jakub Josefb7ab8472017-04-05 14:56:53 +0200432 def lazyMap = new JsonSlurperClassic().parseText(jsonString)
Jakub Josefa877db52017-04-05 14:22:30 +0200433 m.putAll(lazyMap)
434 return m
435}
Jakub Josefed239cd2017-05-09 15:27:33 +0200436
437/**
438 * Test pipeline input parameter existence and validity (not null and not empty string)
439 * @param paramName input parameter name (usually uppercase)
440 */
441def validInputParam(paramName){
442 return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200443}
444
445/**
446 * Take list of hashmaps and count number of hashmaps with parameter equals eq
447 * @param lm list of hashmaps
448 * @param param define parameter of hashmap to read and compare
449 * @param eq desired value of hashmap parameter
450 * @return count of hashmaps meeting defined condition
451 */
452
453@NonCPS
454def countHashMapEquals(lm, param, eq) {
Tomáš Kukrál5dd12072017-06-13 15:54:44 +0200455 return lm.stream().filter{i -> i[param].equals(eq)}.collect(java.util.stream.Collectors.counting())
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200456}
Vasyl Saienkofb055b12017-11-23 18:15:23 +0200457
458/**
459 * Execute shell command and return stdout, stderr and status
460 *
461 * @param cmd Command to execute
462 * @return map with stdout, stderr, status keys
463 */
464
465def shCmdStatus(cmd) {
466 def res = [:]
467 def stderr = sh(script: 'mktemp', returnStdout: true).trim()
468 def stdout = sh(script: 'mktemp', returnStdout: true).trim()
469
470 try {
471 def status = sh(script:"${cmd} 1>${stdout} 2>${stderr}", returnStatus: true)
472 res['stderr'] = sh(script: "cat ${stderr}", returnStdout: true)
473 res['stdout'] = sh(script: "cat ${stdout}", returnStdout: true)
474 res['status'] = status
475 } finally {
476 sh(script: "rm ${stderr}", returnStdout: true)
477 sh(script: "rm ${stdout}", returnStdout: true)
478 }
479
480 return res
481}