blob: 0e5774eba64f99c8bd24bbb71928b283bb0b8659 [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) {
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) {
288 def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
289 com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
290 jenkins.model.Jenkins.instance
291 )
292
293 for (Iterator<String> credsIter = creds.iterator(); credsIter.hasNext();) {
294 c = credsIter.next();
295 if ( c.id == id ) {
296 return c;
297 }
298 }
299
300 throw new Exception("Could not find credentials for ID ${id}")
301}
302
303/**
304 * Get SSH credentials from store
305 *
306 * @param id Credentials name
307 */
308def getSshCredentials(id) {
309 def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
310 com.cloudbees.plugins.credentials.common.StandardUsernameCredentials.class,
311 jenkins.model.Jenkins.instance
312 )
313
314 for (Iterator<String> credsIter = creds.iterator(); credsIter.hasNext();) {
315 c = credsIter.next();
316 if ( c.id == id ) {
317 return c;
318 }
319 }
320
321 throw new Exception("Could not find credentials for ID ${id}")
322}
Jakub Josef79ecec32017-02-17 14:36:28 +0100323
324/**
325 * Tests Jenkins instance for existence of plugin with given name
326 * @param pluginName plugin short name to test
327 * @return boolean result
328 */
329@NonCPS
330def jenkinsHasPlugin(pluginName){
331 return Jenkins.instance.pluginManager.plugins.collect{p -> p.shortName}.contains(pluginName)
332}
333
334@NonCPS
335def _needNotification(notificatedTypes, buildStatus, jobName) {
336 if(notificatedTypes && notificatedTypes.contains("onchange")){
337 if(jobName){
338 def job = Jenkins.instance.getItem(jobName)
339 def numbuilds = job.builds.size()
340 if (numbuilds > 0){
341 //actual build is first for some reasons, so last finished build is second
342 def lastBuild = job.builds[1]
343 if(lastBuild){
344 if(lastBuild.result.toString().toLowerCase().equals(buildStatus)){
345 println("Build status didn't changed since last build, not sending notifications")
346 return false;
347 }
348 }
349 }
350 }
351 }else if(!notificatedTypes.contains(buildStatus)){
352 return false;
353 }
354 return true;
355}
356
357/**
358 * Send notification to all enabled notifications services
359 * @param buildStatus message type (success, warning, error), null means SUCCESSFUL
360 * @param msgText message text
361 * @param enabledNotifications list of enabled notification types, types: slack, hipchat, email, default empty
362 * @param notificatedTypes types of notifications will be sent, default onchange - notificate if current build result not equal last result;
363 * otherwise use - ["success","unstable","failed"]
364 * @param jobName optional job name param, if empty env.JOB_NAME will be used
Jakub Josefd0571152017-07-17 14:11:39 +0200365 * @param buildNumber build number param, if empty env.BUILD_NUM will be used
366 * @param buildUrl build url param, if empty env.BUILD_URL will be used
Jakub Josef79ecec32017-02-17 14:36:28 +0100367 * @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 +0200368 * @param mailTo mail TO param, it's mandatory for sending email notifications, this option enable mail notification
Jakub Josef79ecec32017-02-17 14:36:28 +0100369 */
370def sendNotification(buildStatus, msgText="", enabledNotifications = [], notificatedTypes=["onchange"], jobName=null, buildNumber=null, buildUrl=null, mailFrom="jenkins", mailTo=null){
371 // Default values
372 def colorName = 'blue'
373 def colorCode = '#0000FF'
374 def buildStatusParam = buildStatus != null && buildStatus != "" ? buildStatus : "SUCCESS"
375 def jobNameParam = jobName != null && jobName != "" ? jobName : env.JOB_NAME
376 def buildNumberParam = buildNumber != null && buildNumber != "" ? buildNumber : env.BUILD_NUMBER
377 def buildUrlParam = buildUrl != null && buildUrl != "" ? buildUrl : env.BUILD_URL
378 def subject = "${buildStatusParam}: Job '${jobNameParam} [${buildNumberParam}]'"
379 def summary = "${subject} (${buildUrlParam})"
380
381 if(msgText != null && msgText != ""){
382 summary+="\n${msgText}"
383 }
384 if(buildStatusParam.toLowerCase().equals("success")){
385 colorCode = "#00FF00"
386 colorName = "green"
387 }else if(buildStatusParam.toLowerCase().equals("unstable")){
388 colorCode = "#FFFF00"
389 colorName = "yellow"
390 }else if(buildStatusParam.toLowerCase().equals("failure")){
391 colorCode = "#FF0000"
392 colorName = "red"
393 }
394 if(_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)){
395 if(enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")){
396 try{
397 slackSend color: colorCode, message: summary
398 }catch(Exception e){
399 println("Calling slack plugin failed")
400 e.printStackTrace()
401 }
402 }
403 if(enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")){
404 try{
405 hipchatSend color: colorName.toUpperCase(), message: summary
406 }catch(Exception e){
407 println("Calling hipchat plugin failed")
408 e.printStackTrace()
409 }
410 }
411 if(enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != ""){
412 try{
413 mail body: summary, from: mailFrom, subject: subject, to: mailTo
414 }catch(Exception e){
415 println("Sending mail plugin failed")
416 e.printStackTrace()
417 }
418 }
419 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100420}
chnyda4e5ac792017-03-14 15:24:18 +0100421
422/**
423 * Execute linux command and catch nth element
424 * @param cmd command to execute
425 * @param index index to retrieve
426 * @return index-th element
427 */
428
429def cutOrDie(cmd, index)
430{
431 def common = new com.mirantis.mk.Common()
432 def output
433 try {
434 output = sh(script: cmd, returnStdout: true)
435 def result = output.tokenize(" ")[index]
436 return result;
437 } catch (Exception e) {
438 common.errorMsg("Failed to execute cmd: ${cmd}\n output: ${output}")
439 }
Filip Pytloun81c864d2017-03-21 15:19:30 +0100440}
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100441
442/**
443 * Check variable contains keyword
444 * @param variable keywork is searched (contains) here
445 * @param keyword string to look for
446 * @return True if variable contains keyword (case insensitive), False if do not contains or any of input isn't a string
447 */
448
449def checkContains(variable, keyword) {
Jakub Josef7a8dea22017-03-23 19:51:32 +0100450 if(env.getEnvironment().containsKey(variable)){
451 return env[variable] && env[variable].toLowerCase().contains(keyword.toLowerCase())
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100452 } else {
Tomáš Kukrálc76c1e02017-03-23 19:06:59 +0100453 return false
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100454 }
455}
Jakub Josefa877db52017-04-05 14:22:30 +0200456
457/**
458 * Parse JSON string to hashmap
459 * @param jsonString input JSON string
460 * @return created hashmap
461 */
462def parseJSON(jsonString){
463 def m = [:]
Jakub Josefb7ab8472017-04-05 14:56:53 +0200464 def lazyMap = new JsonSlurperClassic().parseText(jsonString)
Jakub Josefa877db52017-04-05 14:22:30 +0200465 m.putAll(lazyMap)
466 return m
467}
Jakub Josefed239cd2017-05-09 15:27:33 +0200468
469/**
470 * Test pipeline input parameter existence and validity (not null and not empty string)
471 * @param paramName input parameter name (usually uppercase)
472 */
473def validInputParam(paramName){
474 return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200475}
476
477/**
478 * Take list of hashmaps and count number of hashmaps with parameter equals eq
479 * @param lm list of hashmaps
480 * @param param define parameter of hashmap to read and compare
481 * @param eq desired value of hashmap parameter
482 * @return count of hashmaps meeting defined condition
483 */
484
485@NonCPS
486def countHashMapEquals(lm, param, eq) {
Tomáš Kukrál5dd12072017-06-13 15:54:44 +0200487 return lm.stream().filter{i -> i[param].equals(eq)}.collect(java.util.stream.Collectors.counting())
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200488}