blob: c03549f3fefc72f34767379ceda726cc74a26629 [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
6@Grab(group='org.yaml', module='snakeyaml', version='1.17')
7import org.yaml.snakeyaml.Yaml
8import org.yaml.snakeyaml.DumperOptions
Jakub Josefbceaa322017-06-13 18:28:27 +02009import com.cloudbees.groovy.cps.NonCPS
Jakub Josefb7ab8472017-04-05 14:56:53 +020010import groovy.json.JsonSlurperClassic
Jakub Josef79ecec32017-02-17 14:36:28 +010011/**
12 *
13 * Common functions
14 *
15 */
16
17/**
18 * Generate current timestamp
19 *
20 * @param format Defaults to yyyyMMddHHmmss
21 */
22def getDatetime(format="yyyyMMddHHmmss") {
23 def now = new Date();
24 return now.format(format, TimeZone.getTimeZone('UTC'));
25}
26
27/**
Jakub Josef79ecec32017-02-17 14:36:28 +010028 * Return workspace.
29 * Currently implemented by calling pwd so it won't return relevant result in
30 * dir context
31 */
32def getWorkspace() {
33 def workspace = sh script: 'pwd', returnStdout: true
34 workspace = workspace.trim()
35 return workspace
36}
37
38/**
Filip Pytloun81c864d2017-03-21 15:19:30 +010039 * Get UID of jenkins user.
40 * Must be run from context of node
41 */
42def getJenkinsUid() {
43 return sh (
44 script: 'id -u',
45 returnStdout: true
46 ).trim()
47}
48
49/**
50 * Get GID of jenkins user.
51 * Must be run from context of node
52 */
53def getJenkinsGid() {
54 return sh (
55 script: 'id -g',
56 returnStdout: true
57 ).trim()
58}
59
60/**
Jakub Josef6d8082b2017-08-09 12:40:50 +020061 * Convert YAML document to Map object
62 * @param data YAML string
63 */
64@NonCPS
65def loadYAML(String data) {
66 def yaml = new Yaml()
67 return yaml.load(data)
68}
69
70
71/**
72 * Convert Map object to YAML string
73 * @param map Map object
74 */
75@NonCPS
76def dumpYAML(Map map) {
77 def dumperOptions = new DumperOptions()
78 dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK)
79 def yaml = new Yaml(dumperOptions)
80 return yaml.dump(map)
81}
82
83
84/**
Jakub Josef79ecec32017-02-17 14:36:28 +010085 * Get credentials from store
86 *
87 * @param id Credentials name
88 */
Jakub Josef3d9d9ab2017-03-14 15:09:03 +010089def getCredentials(id, cred_type = "username_password") {
90 def credClass;
91 if(cred_type == "username_password"){
92 credClass = com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class
93 }else if(cred_type == "key"){
94 credClass = com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.class
95 }
Jakub Josef79ecec32017-02-17 14:36:28 +010096 def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
Jakub Josef3d9d9ab2017-03-14 15:09:03 +010097 credClass,
Jakub Josef79ecec32017-02-17 14:36:28 +010098 jenkins.model.Jenkins.instance
99 )
100
101 for (Iterator<String> credsIter = creds.iterator(); credsIter.hasNext();) {
102 c = credsIter.next();
103 if ( c.id == id ) {
104 return c;
105 }
106 }
107
108 throw new Exception("Could not find credentials for ID ${id}")
109}
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) {
210 wrap([$class: 'AnsiColorBuildWrapper']) {
211 print "${colors[color]}${msg}${colors.reset}"
212 }
213 } else {
214 print "[${level}] ${msg}"
215 }
216}
217
218/**
219 * Traverse directory structure and return list of files
220 *
221 * @param path Path to search
222 * @param type Type of files to search (groovy.io.FileType.FILES)
223 */
224@NonCPS
225def getFiles(path, type=groovy.io.FileType.FILES) {
226 files = []
227 new File(path).eachFile(type) {
228 files[] = it
229 }
230 return files
231}
232
233/**
234 * Helper method to convert map into form of list of [key,value] to avoid
235 * unserializable exceptions
236 *
237 * @param m Map
238 */
239@NonCPS
240def entries(m) {
241 m.collect {k, v -> [k, v]}
242}
243
244/**
245 * Opposite of build-in parallel, run map of steps in serial
246 *
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200247 * @param steps Map of String<name>: CPSClosure2<step> (or list of closures)
Jakub Josef79ecec32017-02-17 14:36:28 +0100248 */
249def serial(steps) {
250 stepsArray = entries(steps)
251 for (i=0; i < stepsArray.size; i++) {
Jakub Josefd31de302017-05-15 13:59:18 +0200252 def step = stepsArray[i]
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200253 def dummySteps = [:]
254 def stepKey
Jakub Josef228aae92017-05-15 19:04:43 +0200255 if(step[1] instanceof List || step[1] instanceof Map){
Jakub Josef538be162017-05-15 19:11:48 +0200256 for(j=0;j < step[1].size(); j++){
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200257 if(step[1] instanceof List){
258 stepKey = j
259 }else if(step[1] instanceof Map){
260 stepKey = step[1].keySet()[j]
261 }
262 dummySteps.put("step-${step[0]}-${stepKey}",step[1][stepKey])
Jakub Josefd31de302017-05-15 13:59:18 +0200263 }
264 }else{
265 dummySteps.put(step[0], step[1])
266 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100267 parallel dummySteps
268 }
269}
270
271/**
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200272 * Partition given list to list of small lists
273 * @param inputList input list
274 * @param partitionSize (partition size, optional, default 5)
275 */
276def partitionList(inputList, partitionSize=5){
277 List<List<String>> partitions = new ArrayList<>();
278 for (int i=0; i<inputList.size(); i += partitionSize) {
279 partitions.add(new ArrayList<String>(inputList.subList(i, Math.min(i + partitionSize, inputList.size()))));
280 }
281 return partitions
282}
283
284/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100285 * Get password credentials from store
286 *
287 * @param id Credentials name
288 */
289def getPasswordCredentials(id) {
290 def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
291 com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
292 jenkins.model.Jenkins.instance
293 )
294
295 for (Iterator<String> credsIter = creds.iterator(); credsIter.hasNext();) {
296 c = credsIter.next();
297 if ( c.id == id ) {
298 return c;
299 }
300 }
301
302 throw new Exception("Could not find credentials for ID ${id}")
303}
304
305/**
306 * Get SSH credentials from store
307 *
308 * @param id Credentials name
309 */
310def getSshCredentials(id) {
311 def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
312 com.cloudbees.plugins.credentials.common.StandardUsernameCredentials.class,
313 jenkins.model.Jenkins.instance
314 )
315
316 for (Iterator<String> credsIter = creds.iterator(); credsIter.hasNext();) {
317 c = credsIter.next();
318 if ( c.id == id ) {
319 return c;
320 }
321 }
322
323 throw new Exception("Could not find credentials for ID ${id}")
324}
Jakub Josef79ecec32017-02-17 14:36:28 +0100325
326/**
327 * Tests Jenkins instance for existence of plugin with given name
328 * @param pluginName plugin short name to test
329 * @return boolean result
330 */
331@NonCPS
332def jenkinsHasPlugin(pluginName){
333 return Jenkins.instance.pluginManager.plugins.collect{p -> p.shortName}.contains(pluginName)
334}
335
336@NonCPS
337def _needNotification(notificatedTypes, buildStatus, jobName) {
338 if(notificatedTypes && notificatedTypes.contains("onchange")){
339 if(jobName){
340 def job = Jenkins.instance.getItem(jobName)
341 def numbuilds = job.builds.size()
342 if (numbuilds > 0){
343 //actual build is first for some reasons, so last finished build is second
344 def lastBuild = job.builds[1]
345 if(lastBuild){
346 if(lastBuild.result.toString().toLowerCase().equals(buildStatus)){
347 println("Build status didn't changed since last build, not sending notifications")
348 return false;
349 }
350 }
351 }
352 }
353 }else if(!notificatedTypes.contains(buildStatus)){
354 return false;
355 }
356 return true;
357}
358
359/**
360 * Send notification to all enabled notifications services
361 * @param buildStatus message type (success, warning, error), null means SUCCESSFUL
362 * @param msgText message text
363 * @param enabledNotifications list of enabled notification types, types: slack, hipchat, email, default empty
364 * @param notificatedTypes types of notifications will be sent, default onchange - notificate if current build result not equal last result;
365 * otherwise use - ["success","unstable","failed"]
366 * @param jobName optional job name param, if empty env.JOB_NAME will be used
Jakub Josefd0571152017-07-17 14:11:39 +0200367 * @param buildNumber build number param, if empty env.BUILD_NUM will be used
368 * @param buildUrl build url param, if empty env.BUILD_URL will be used
Jakub Josef79ecec32017-02-17 14:36:28 +0100369 * @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 +0200370 * @param mailTo mail TO param, it's mandatory for sending email notifications, this option enable mail notification
Jakub Josef79ecec32017-02-17 14:36:28 +0100371 */
372def sendNotification(buildStatus, msgText="", enabledNotifications = [], notificatedTypes=["onchange"], jobName=null, buildNumber=null, buildUrl=null, mailFrom="jenkins", mailTo=null){
373 // Default values
374 def colorName = 'blue'
375 def colorCode = '#0000FF'
376 def buildStatusParam = buildStatus != null && buildStatus != "" ? buildStatus : "SUCCESS"
377 def jobNameParam = jobName != null && jobName != "" ? jobName : env.JOB_NAME
378 def buildNumberParam = buildNumber != null && buildNumber != "" ? buildNumber : env.BUILD_NUMBER
379 def buildUrlParam = buildUrl != null && buildUrl != "" ? buildUrl : env.BUILD_URL
380 def subject = "${buildStatusParam}: Job '${jobNameParam} [${buildNumberParam}]'"
381 def summary = "${subject} (${buildUrlParam})"
382
383 if(msgText != null && msgText != ""){
384 summary+="\n${msgText}"
385 }
386 if(buildStatusParam.toLowerCase().equals("success")){
387 colorCode = "#00FF00"
388 colorName = "green"
389 }else if(buildStatusParam.toLowerCase().equals("unstable")){
390 colorCode = "#FFFF00"
391 colorName = "yellow"
392 }else if(buildStatusParam.toLowerCase().equals("failure")){
393 colorCode = "#FF0000"
394 colorName = "red"
395 }
396 if(_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)){
397 if(enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")){
398 try{
399 slackSend color: colorCode, message: summary
400 }catch(Exception e){
401 println("Calling slack plugin failed")
402 e.printStackTrace()
403 }
404 }
405 if(enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")){
406 try{
407 hipchatSend color: colorName.toUpperCase(), message: summary
408 }catch(Exception e){
409 println("Calling hipchat plugin failed")
410 e.printStackTrace()
411 }
412 }
413 if(enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != ""){
414 try{
415 mail body: summary, from: mailFrom, subject: subject, to: mailTo
416 }catch(Exception e){
417 println("Sending mail plugin failed")
418 e.printStackTrace()
419 }
420 }
421 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100422}
chnyda4e5ac792017-03-14 15:24:18 +0100423
424/**
425 * Execute linux command and catch nth element
426 * @param cmd command to execute
427 * @param index index to retrieve
428 * @return index-th element
429 */
430
431def cutOrDie(cmd, index)
432{
433 def common = new com.mirantis.mk.Common()
434 def output
435 try {
436 output = sh(script: cmd, returnStdout: true)
437 def result = output.tokenize(" ")[index]
438 return result;
439 } catch (Exception e) {
440 common.errorMsg("Failed to execute cmd: ${cmd}\n output: ${output}")
441 }
Filip Pytloun81c864d2017-03-21 15:19:30 +0100442}
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100443
444/**
445 * Check variable contains keyword
446 * @param variable keywork is searched (contains) here
447 * @param keyword string to look for
448 * @return True if variable contains keyword (case insensitive), False if do not contains or any of input isn't a string
449 */
450
451def checkContains(variable, keyword) {
Jakub Josef7a8dea22017-03-23 19:51:32 +0100452 if(env.getEnvironment().containsKey(variable)){
453 return env[variable] && env[variable].toLowerCase().contains(keyword.toLowerCase())
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100454 } else {
Tomáš Kukrálc76c1e02017-03-23 19:06:59 +0100455 return false
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100456 }
457}
Jakub Josefa877db52017-04-05 14:22:30 +0200458
459/**
460 * Parse JSON string to hashmap
461 * @param jsonString input JSON string
462 * @return created hashmap
463 */
464def parseJSON(jsonString){
465 def m = [:]
Jakub Josefb7ab8472017-04-05 14:56:53 +0200466 def lazyMap = new JsonSlurperClassic().parseText(jsonString)
Jakub Josefa877db52017-04-05 14:22:30 +0200467 m.putAll(lazyMap)
468 return m
469}
Jakub Josefed239cd2017-05-09 15:27:33 +0200470
471/**
472 * Test pipeline input parameter existence and validity (not null and not empty string)
473 * @param paramName input parameter name (usually uppercase)
474 */
475def validInputParam(paramName){
476 return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200477}
478
479/**
480 * Take list of hashmaps and count number of hashmaps with parameter equals eq
481 * @param lm list of hashmaps
482 * @param param define parameter of hashmap to read and compare
483 * @param eq desired value of hashmap parameter
484 * @return count of hashmaps meeting defined condition
485 */
486
487@NonCPS
488def countHashMapEquals(lm, param, eq) {
Tomáš Kukrál5dd12072017-06-13 15:54:44 +0200489 return lm.stream().filter{i -> i[param].equals(eq)}.collect(java.util.stream.Collectors.counting())
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200490}