blob: 1aa67325d297125beac567484f4c4ede6a8074cb [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 */
29def getWorkspace() {
30 def workspace = sh script: 'pwd', returnStdout: true
31 workspace = workspace.trim()
32 return workspace
33}
34
35/**
Filip Pytloun81c864d2017-03-21 15:19:30 +010036 * Get UID of jenkins user.
37 * Must be run from context of node
38 */
39def getJenkinsUid() {
40 return sh (
41 script: 'id -u',
42 returnStdout: true
43 ).trim()
44}
45
46/**
47 * Get GID of jenkins user.
48 * Must be run from context of node
49 */
50def getJenkinsGid() {
51 return sh (
52 script: 'id -g',
53 returnStdout: true
54 ).trim()
55}
56
57/**
Jakub Josef79ecec32017-02-17 14:36:28 +010058 * Get credentials from store
59 *
60 * @param id Credentials name
61 */
Jakub Josef3d9d9ab2017-03-14 15:09:03 +010062def getCredentials(id, cred_type = "username_password") {
63 def credClass;
64 if(cred_type == "username_password"){
65 credClass = com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class
66 }else if(cred_type == "key"){
67 credClass = com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.class
68 }
Jakub Josef79ecec32017-02-17 14:36:28 +010069 def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
Jakub Josef3d9d9ab2017-03-14 15:09:03 +010070 credClass,
Jakub Josef79ecec32017-02-17 14:36:28 +010071 jenkins.model.Jenkins.instance
72 )
73
74 for (Iterator<String> credsIter = creds.iterator(); credsIter.hasNext();) {
75 c = credsIter.next();
76 if ( c.id == id ) {
77 return c;
78 }
79 }
80
81 throw new Exception("Could not find credentials for ID ${id}")
82}
83
84/**
85 * Abort build, wait for some time and ensure we will terminate
86 */
87def abortBuild() {
88 currentBuild.build().doStop()
89 sleep(180)
90 // just to be sure we will terminate
91 throw new InterruptedException()
92}
93
94/**
Jakub Josefbceaa322017-06-13 18:28:27 +020095 * Print pretty-printed string representation of given item
96 * @param item item to be pretty-printed (list, map, whatever)
97 */
98def prettyPrint(item){
99 println prettify(item)
100}
101
102/**
Jakub Josefb41c8d52017-03-24 13:52:24 +0100103 * Return pretty-printed string representation of given item
104 * @param item item to be pretty-printed (list, map, whatever)
105 * @return pretty-printed string
106 */
Jakub Josefbceaa322017-06-13 18:28:27 +0200107def prettify(item){
108 return groovy.json.JsonOutput.prettyPrint(toJson(item)).replace('\\n', System.getProperty('line.separator'))
Jakub Josefb41c8d52017-03-24 13:52:24 +0100109}
110
111/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100112 * Print informational message
113 *
114 * @param msg
115 * @param color Colorful output or not
116 */
117def infoMsg(msg, color = true) {
118 printMsg(msg, "cyan")
119}
120
121/**
122 * Print error message
123 *
124 * @param msg
125 * @param color Colorful output or not
126 */
127def errorMsg(msg, color = true) {
128 printMsg(msg, "red")
129}
130
131/**
132 * Print success message
133 *
134 * @param msg
135 * @param color Colorful output or not
136 */
137def successMsg(msg, color = true) {
138 printMsg(msg, "green")
139}
140
141/**
142 * Print warning message
143 *
144 * @param msg
145 * @param color Colorful output or not
146 */
147def warningMsg(msg, color = true) {
Jakub Josef0e7bd632017-03-16 16:25:05 +0100148 printMsg(msg, "yellow")
Jakub Josef79ecec32017-02-17 14:36:28 +0100149}
150
151/**
Jakub Josef952ae0b2017-03-14 19:04:21 +0100152 * Print debug message, this message will show only if DEBUG global variable is present
153 * @param msg
154 * @param color Colorful output or not
155 */
156def debugMsg(msg, color = true){
Jakub Josef9a836ac2017-04-24 12:26:02 +0200157 // if debug property exists on env, debug is enabled
Jakub Josef66976f62017-04-24 16:32:23 +0200158 if(env.getEnvironment().containsKey('DEBUG') && env['DEBUG'] == "true"){
Jakub Josef74b34692017-03-15 12:10:57 +0100159 printMsg("[DEBUG] ${msg}", "red")
Jakub Josef952ae0b2017-03-14 19:04:21 +0100160 }
161}
162
163/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100164 * Print message
165 *
166 * @param msg Message to be printed
167 * @param level Level of message (default INFO)
168 * @param color Color to use for output or false (default)
169 */
170def printMsg(msg, color = false) {
171 colors = [
172 'red' : '\u001B[31m',
173 'black' : '\u001B[30m',
174 'green' : '\u001B[32m',
175 'yellow': '\u001B[33m',
176 'blue' : '\u001B[34m',
177 'purple': '\u001B[35m',
178 'cyan' : '\u001B[36m',
179 'white' : '\u001B[37m',
180 'reset' : '\u001B[0m'
181 ]
182 if (color != false) {
Jakub Josefe6c562e2017-08-09 14:41:03 +0200183 print "${colors[color]}${msg}${colors.reset}"
Jakub Josef79ecec32017-02-17 14:36:28 +0100184 } else {
185 print "[${level}] ${msg}"
186 }
187}
188
189/**
190 * Traverse directory structure and return list of files
191 *
192 * @param path Path to search
193 * @param type Type of files to search (groovy.io.FileType.FILES)
194 */
195@NonCPS
196def getFiles(path, type=groovy.io.FileType.FILES) {
197 files = []
198 new File(path).eachFile(type) {
199 files[] = it
200 }
201 return files
202}
203
204/**
205 * Helper method to convert map into form of list of [key,value] to avoid
206 * unserializable exceptions
207 *
208 * @param m Map
209 */
210@NonCPS
211def entries(m) {
212 m.collect {k, v -> [k, v]}
213}
214
215/**
216 * Opposite of build-in parallel, run map of steps in serial
217 *
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200218 * @param steps Map of String<name>: CPSClosure2<step> (or list of closures)
Jakub Josef79ecec32017-02-17 14:36:28 +0100219 */
220def serial(steps) {
221 stepsArray = entries(steps)
222 for (i=0; i < stepsArray.size; i++) {
Jakub Josefd31de302017-05-15 13:59:18 +0200223 def step = stepsArray[i]
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200224 def dummySteps = [:]
225 def stepKey
Jakub Josef228aae92017-05-15 19:04:43 +0200226 if(step[1] instanceof List || step[1] instanceof Map){
Jakub Josef538be162017-05-15 19:11:48 +0200227 for(j=0;j < step[1].size(); j++){
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200228 if(step[1] instanceof List){
229 stepKey = j
230 }else if(step[1] instanceof Map){
231 stepKey = step[1].keySet()[j]
232 }
233 dummySteps.put("step-${step[0]}-${stepKey}",step[1][stepKey])
Jakub Josefd31de302017-05-15 13:59:18 +0200234 }
235 }else{
236 dummySteps.put(step[0], step[1])
237 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100238 parallel dummySteps
239 }
240}
241
242/**
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200243 * Partition given list to list of small lists
244 * @param inputList input list
245 * @param partitionSize (partition size, optional, default 5)
246 */
247def partitionList(inputList, partitionSize=5){
248 List<List<String>> partitions = new ArrayList<>();
249 for (int i=0; i<inputList.size(); i += partitionSize) {
250 partitions.add(new ArrayList<String>(inputList.subList(i, Math.min(i + partitionSize, inputList.size()))));
251 }
252 return partitions
253}
254
255/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100256 * Get password credentials from store
257 *
258 * @param id Credentials name
259 */
260def getPasswordCredentials(id) {
261 def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
262 com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
263 jenkins.model.Jenkins.instance
264 )
265
266 for (Iterator<String> credsIter = creds.iterator(); credsIter.hasNext();) {
267 c = credsIter.next();
268 if ( c.id == id ) {
269 return c;
270 }
271 }
272
273 throw new Exception("Could not find credentials for ID ${id}")
274}
275
276/**
277 * Get SSH credentials from store
278 *
279 * @param id Credentials name
280 */
281def getSshCredentials(id) {
282 def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
283 com.cloudbees.plugins.credentials.common.StandardUsernameCredentials.class,
284 jenkins.model.Jenkins.instance
285 )
286
287 for (Iterator<String> credsIter = creds.iterator(); credsIter.hasNext();) {
288 c = credsIter.next();
289 if ( c.id == id ) {
290 return c;
291 }
292 }
293
294 throw new Exception("Could not find credentials for ID ${id}")
295}
Jakub Josef79ecec32017-02-17 14:36:28 +0100296
297/**
298 * Tests Jenkins instance for existence of plugin with given name
299 * @param pluginName plugin short name to test
300 * @return boolean result
301 */
302@NonCPS
303def jenkinsHasPlugin(pluginName){
304 return Jenkins.instance.pluginManager.plugins.collect{p -> p.shortName}.contains(pluginName)
305}
306
307@NonCPS
308def _needNotification(notificatedTypes, buildStatus, jobName) {
309 if(notificatedTypes && notificatedTypes.contains("onchange")){
310 if(jobName){
311 def job = Jenkins.instance.getItem(jobName)
312 def numbuilds = job.builds.size()
313 if (numbuilds > 0){
314 //actual build is first for some reasons, so last finished build is second
315 def lastBuild = job.builds[1]
316 if(lastBuild){
317 if(lastBuild.result.toString().toLowerCase().equals(buildStatus)){
318 println("Build status didn't changed since last build, not sending notifications")
319 return false;
320 }
321 }
322 }
323 }
324 }else if(!notificatedTypes.contains(buildStatus)){
325 return false;
326 }
327 return true;
328}
329
330/**
331 * Send notification to all enabled notifications services
332 * @param buildStatus message type (success, warning, error), null means SUCCESSFUL
333 * @param msgText message text
334 * @param enabledNotifications list of enabled notification types, types: slack, hipchat, email, default empty
335 * @param notificatedTypes types of notifications will be sent, default onchange - notificate if current build result not equal last result;
336 * otherwise use - ["success","unstable","failed"]
337 * @param jobName optional job name param, if empty env.JOB_NAME will be used
Jakub Josefd0571152017-07-17 14:11:39 +0200338 * @param buildNumber build number param, if empty env.BUILD_NUM will be used
339 * @param buildUrl build url param, if empty env.BUILD_URL will be used
Jakub Josef79ecec32017-02-17 14:36:28 +0100340 * @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 +0200341 * @param mailTo mail TO param, it's mandatory for sending email notifications, this option enable mail notification
Jakub Josef79ecec32017-02-17 14:36:28 +0100342 */
343def sendNotification(buildStatus, msgText="", enabledNotifications = [], notificatedTypes=["onchange"], jobName=null, buildNumber=null, buildUrl=null, mailFrom="jenkins", mailTo=null){
344 // Default values
345 def colorName = 'blue'
346 def colorCode = '#0000FF'
347 def buildStatusParam = buildStatus != null && buildStatus != "" ? buildStatus : "SUCCESS"
348 def jobNameParam = jobName != null && jobName != "" ? jobName : env.JOB_NAME
349 def buildNumberParam = buildNumber != null && buildNumber != "" ? buildNumber : env.BUILD_NUMBER
350 def buildUrlParam = buildUrl != null && buildUrl != "" ? buildUrl : env.BUILD_URL
351 def subject = "${buildStatusParam}: Job '${jobNameParam} [${buildNumberParam}]'"
352 def summary = "${subject} (${buildUrlParam})"
353
354 if(msgText != null && msgText != ""){
355 summary+="\n${msgText}"
356 }
357 if(buildStatusParam.toLowerCase().equals("success")){
358 colorCode = "#00FF00"
359 colorName = "green"
360 }else if(buildStatusParam.toLowerCase().equals("unstable")){
361 colorCode = "#FFFF00"
362 colorName = "yellow"
363 }else if(buildStatusParam.toLowerCase().equals("failure")){
364 colorCode = "#FF0000"
365 colorName = "red"
366 }
367 if(_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)){
368 if(enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")){
369 try{
370 slackSend color: colorCode, message: summary
371 }catch(Exception e){
372 println("Calling slack plugin failed")
373 e.printStackTrace()
374 }
375 }
376 if(enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")){
377 try{
378 hipchatSend color: colorName.toUpperCase(), message: summary
379 }catch(Exception e){
380 println("Calling hipchat plugin failed")
381 e.printStackTrace()
382 }
383 }
384 if(enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != ""){
385 try{
386 mail body: summary, from: mailFrom, subject: subject, to: mailTo
387 }catch(Exception e){
388 println("Sending mail plugin failed")
389 e.printStackTrace()
390 }
391 }
392 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100393}
chnyda4e5ac792017-03-14 15:24:18 +0100394
395/**
396 * Execute linux command and catch nth element
397 * @param cmd command to execute
398 * @param index index to retrieve
399 * @return index-th element
400 */
401
402def cutOrDie(cmd, index)
403{
404 def common = new com.mirantis.mk.Common()
405 def output
406 try {
407 output = sh(script: cmd, returnStdout: true)
408 def result = output.tokenize(" ")[index]
409 return result;
410 } catch (Exception e) {
411 common.errorMsg("Failed to execute cmd: ${cmd}\n output: ${output}")
412 }
Filip Pytloun81c864d2017-03-21 15:19:30 +0100413}
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100414
415/**
416 * Check variable contains keyword
417 * @param variable keywork is searched (contains) here
418 * @param keyword string to look for
419 * @return True if variable contains keyword (case insensitive), False if do not contains or any of input isn't a string
420 */
421
422def checkContains(variable, keyword) {
Jakub Josef7a8dea22017-03-23 19:51:32 +0100423 if(env.getEnvironment().containsKey(variable)){
424 return env[variable] && env[variable].toLowerCase().contains(keyword.toLowerCase())
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100425 } else {
Tomáš Kukrálc76c1e02017-03-23 19:06:59 +0100426 return false
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100427 }
428}
Jakub Josefa877db52017-04-05 14:22:30 +0200429
430/**
431 * Parse JSON string to hashmap
432 * @param jsonString input JSON string
433 * @return created hashmap
434 */
435def parseJSON(jsonString){
436 def m = [:]
Jakub Josefb7ab8472017-04-05 14:56:53 +0200437 def lazyMap = new JsonSlurperClassic().parseText(jsonString)
Jakub Josefa877db52017-04-05 14:22:30 +0200438 m.putAll(lazyMap)
439 return m
440}
Jakub Josefed239cd2017-05-09 15:27:33 +0200441
442/**
443 * Test pipeline input parameter existence and validity (not null and not empty string)
444 * @param paramName input parameter name (usually uppercase)
445 */
446def validInputParam(paramName){
447 return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200448}
449
450/**
451 * Take list of hashmaps and count number of hashmaps with parameter equals eq
452 * @param lm list of hashmaps
453 * @param param define parameter of hashmap to read and compare
454 * @param eq desired value of hashmap parameter
455 * @return count of hashmaps meeting defined condition
456 */
457
458@NonCPS
459def countHashMapEquals(lm, param, eq) {
Tomáš Kukrál5dd12072017-06-13 15:54:44 +0200460 return lm.stream().filter{i -> i[param].equals(eq)}.collect(java.util.stream.Collectors.counting())
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200461}