blob: 15676a907810354a9ac853ba0d34d917e72777c6 [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/**
Alexander Evseevbc1fea42017-12-13 10:03:03 +010058 *
59 * Find credentials by ID
60 *
61 * @param credsId Credentials ID
62 * @param credsType Credentials type (optional)
63 *
64 */
65def getCredentialsById(String credsId, String credsType = 'any') {
66 def credClasses = [ // ordered by class name
67 sshKey: com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.class,
68 cert: com.cloudbees.plugins.credentials.common.CertificateCredentials.class,
69 password: com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
70 any: com.cloudbees.plugins.credentials.impl.BaseStandardCredentials.class,
71 dockerCert: org.jenkinsci.plugins.docker.commons.credentials.DockerServerCredentials.class,
72 file: org.jenkinsci.plugins.plaincredentials.FileCredentials.class,
73 string: org.jenkinsci.plugins.plaincredentials.StringCredentials.class,
74 ]
75 return com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
76 credClasses[credsType],
77 jenkins.model.Jenkins.instance
78 ).findAll {cred -> cred.id == credsId}[0]
79}
80
81/**
Jakub Josef79ecec32017-02-17 14:36:28 +010082 * Get credentials from store
83 *
84 * @param id Credentials name
85 */
Jakub Josef3d9d9ab2017-03-14 15:09:03 +010086def getCredentials(id, cred_type = "username_password") {
Alexander Evseevbc1fea42017-12-13 10:03:03 +010087 warningMsg('You are using obsolete function. Please switch to use `getCredentialsById()`')
Jakub Josef79ecec32017-02-17 14:36:28 +010088
Alexander Evseevbc1fea42017-12-13 10:03:03 +010089 type_map = [
90 username_password: 'password',
91 key: 'sshKey',
92 ]
Jakub Josef79ecec32017-02-17 14:36:28 +010093
Alexander Evseevbc1fea42017-12-13 10:03:03 +010094 return getCredentialsById(id, type_map[cred_type])
Jakub Josef79ecec32017-02-17 14:36:28 +010095}
96
97/**
98 * Abort build, wait for some time and ensure we will terminate
99 */
100def abortBuild() {
101 currentBuild.build().doStop()
102 sleep(180)
103 // just to be sure we will terminate
104 throw new InterruptedException()
105}
106
107/**
Jakub Josefbceaa322017-06-13 18:28:27 +0200108 * Print pretty-printed string representation of given item
109 * @param item item to be pretty-printed (list, map, whatever)
110 */
111def prettyPrint(item){
112 println prettify(item)
113}
114
115/**
Jakub Josefb41c8d52017-03-24 13:52:24 +0100116 * Return pretty-printed string representation of given item
117 * @param item item to be pretty-printed (list, map, whatever)
118 * @return pretty-printed string
119 */
Jakub Josefbceaa322017-06-13 18:28:27 +0200120def prettify(item){
121 return groovy.json.JsonOutput.prettyPrint(toJson(item)).replace('\\n', System.getProperty('line.separator'))
Jakub Josefb41c8d52017-03-24 13:52:24 +0100122}
123
124/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100125 * Print informational message
126 *
127 * @param msg
128 * @param color Colorful output or not
129 */
130def infoMsg(msg, color = true) {
131 printMsg(msg, "cyan")
132}
133
134/**
135 * Print error message
136 *
137 * @param msg
138 * @param color Colorful output or not
139 */
140def errorMsg(msg, color = true) {
141 printMsg(msg, "red")
142}
143
144/**
145 * Print success message
146 *
147 * @param msg
148 * @param color Colorful output or not
149 */
150def successMsg(msg, color = true) {
151 printMsg(msg, "green")
152}
153
154/**
155 * Print warning message
156 *
157 * @param msg
158 * @param color Colorful output or not
159 */
160def warningMsg(msg, color = true) {
Jakub Josef0e7bd632017-03-16 16:25:05 +0100161 printMsg(msg, "yellow")
Jakub Josef79ecec32017-02-17 14:36:28 +0100162}
163
164/**
Jakub Josef952ae0b2017-03-14 19:04:21 +0100165 * Print debug message, this message will show only if DEBUG global variable is present
166 * @param msg
167 * @param color Colorful output or not
168 */
169def debugMsg(msg, color = true){
Jakub Josef9a836ac2017-04-24 12:26:02 +0200170 // if debug property exists on env, debug is enabled
Jakub Josef66976f62017-04-24 16:32:23 +0200171 if(env.getEnvironment().containsKey('DEBUG') && env['DEBUG'] == "true"){
Jakub Josef74b34692017-03-15 12:10:57 +0100172 printMsg("[DEBUG] ${msg}", "red")
Jakub Josef952ae0b2017-03-14 19:04:21 +0100173 }
174}
175
176/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100177 * Print message
178 *
179 * @param msg Message to be printed
180 * @param level Level of message (default INFO)
181 * @param color Color to use for output or false (default)
182 */
183def printMsg(msg, color = false) {
184 colors = [
185 'red' : '\u001B[31m',
186 'black' : '\u001B[30m',
187 'green' : '\u001B[32m',
188 'yellow': '\u001B[33m',
189 'blue' : '\u001B[34m',
190 'purple': '\u001B[35m',
191 'cyan' : '\u001B[36m',
192 'white' : '\u001B[37m',
193 'reset' : '\u001B[0m'
194 ]
195 if (color != false) {
Jakub Josefe6c562e2017-08-09 14:41:03 +0200196 print "${colors[color]}${msg}${colors.reset}"
Jakub Josef79ecec32017-02-17 14:36:28 +0100197 } else {
198 print "[${level}] ${msg}"
199 }
200}
201
202/**
203 * Traverse directory structure and return list of files
204 *
205 * @param path Path to search
206 * @param type Type of files to search (groovy.io.FileType.FILES)
207 */
208@NonCPS
209def getFiles(path, type=groovy.io.FileType.FILES) {
210 files = []
211 new File(path).eachFile(type) {
212 files[] = it
213 }
214 return files
215}
216
217/**
218 * Helper method to convert map into form of list of [key,value] to avoid
219 * unserializable exceptions
220 *
221 * @param m Map
222 */
223@NonCPS
224def entries(m) {
225 m.collect {k, v -> [k, v]}
226}
227
228/**
229 * Opposite of build-in parallel, run map of steps in serial
230 *
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200231 * @param steps Map of String<name>: CPSClosure2<step> (or list of closures)
Jakub Josef79ecec32017-02-17 14:36:28 +0100232 */
233def serial(steps) {
234 stepsArray = entries(steps)
235 for (i=0; i < stepsArray.size; i++) {
Jakub Josefd31de302017-05-15 13:59:18 +0200236 def step = stepsArray[i]
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200237 def dummySteps = [:]
238 def stepKey
Jakub Josef228aae92017-05-15 19:04:43 +0200239 if(step[1] instanceof List || step[1] instanceof Map){
Jakub Josef538be162017-05-15 19:11:48 +0200240 for(j=0;j < step[1].size(); j++){
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200241 if(step[1] instanceof List){
242 stepKey = j
243 }else if(step[1] instanceof Map){
244 stepKey = step[1].keySet()[j]
245 }
246 dummySteps.put("step-${step[0]}-${stepKey}",step[1][stepKey])
Jakub Josefd31de302017-05-15 13:59:18 +0200247 }
248 }else{
249 dummySteps.put(step[0], step[1])
250 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100251 parallel dummySteps
252 }
253}
254
255/**
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200256 * Partition given list to list of small lists
257 * @param inputList input list
258 * @param partitionSize (partition size, optional, default 5)
259 */
260def partitionList(inputList, partitionSize=5){
261 List<List<String>> partitions = new ArrayList<>();
262 for (int i=0; i<inputList.size(); i += partitionSize) {
263 partitions.add(new ArrayList<String>(inputList.subList(i, Math.min(i + partitionSize, inputList.size()))));
264 }
265 return partitions
266}
267
268/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100269 * Get password credentials from store
270 *
271 * @param id Credentials name
272 */
273def getPasswordCredentials(id) {
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100274 return getCredentialsById(id, 'password')
Jakub Josef79ecec32017-02-17 14:36:28 +0100275}
276
277/**
278 * Get SSH credentials from store
279 *
280 * @param id Credentials name
281 */
282def getSshCredentials(id) {
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100283 return getCredentialsById(id, 'sshKey')
Jakub Josef79ecec32017-02-17 14:36:28 +0100284}
Jakub Josef79ecec32017-02-17 14:36:28 +0100285
286/**
287 * Tests Jenkins instance for existence of plugin with given name
288 * @param pluginName plugin short name to test
289 * @return boolean result
290 */
291@NonCPS
292def jenkinsHasPlugin(pluginName){
293 return Jenkins.instance.pluginManager.plugins.collect{p -> p.shortName}.contains(pluginName)
294}
295
296@NonCPS
297def _needNotification(notificatedTypes, buildStatus, jobName) {
298 if(notificatedTypes && notificatedTypes.contains("onchange")){
299 if(jobName){
300 def job = Jenkins.instance.getItem(jobName)
301 def numbuilds = job.builds.size()
302 if (numbuilds > 0){
303 //actual build is first for some reasons, so last finished build is second
304 def lastBuild = job.builds[1]
305 if(lastBuild){
306 if(lastBuild.result.toString().toLowerCase().equals(buildStatus)){
307 println("Build status didn't changed since last build, not sending notifications")
308 return false;
309 }
310 }
311 }
312 }
313 }else if(!notificatedTypes.contains(buildStatus)){
314 return false;
315 }
316 return true;
317}
318
319/**
320 * Send notification to all enabled notifications services
321 * @param buildStatus message type (success, warning, error), null means SUCCESSFUL
322 * @param msgText message text
323 * @param enabledNotifications list of enabled notification types, types: slack, hipchat, email, default empty
324 * @param notificatedTypes types of notifications will be sent, default onchange - notificate if current build result not equal last result;
325 * otherwise use - ["success","unstable","failed"]
326 * @param jobName optional job name param, if empty env.JOB_NAME will be used
Jakub Josefd0571152017-07-17 14:11:39 +0200327 * @param buildNumber build number param, if empty env.BUILD_NUM will be used
328 * @param buildUrl build url param, if empty env.BUILD_URL will be used
Jakub Josef79ecec32017-02-17 14:36:28 +0100329 * @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 +0200330 * @param mailTo mail TO param, it's mandatory for sending email notifications, this option enable mail notification
Jakub Josef79ecec32017-02-17 14:36:28 +0100331 */
332def sendNotification(buildStatus, msgText="", enabledNotifications = [], notificatedTypes=["onchange"], jobName=null, buildNumber=null, buildUrl=null, mailFrom="jenkins", mailTo=null){
333 // Default values
334 def colorName = 'blue'
335 def colorCode = '#0000FF'
336 def buildStatusParam = buildStatus != null && buildStatus != "" ? buildStatus : "SUCCESS"
337 def jobNameParam = jobName != null && jobName != "" ? jobName : env.JOB_NAME
338 def buildNumberParam = buildNumber != null && buildNumber != "" ? buildNumber : env.BUILD_NUMBER
339 def buildUrlParam = buildUrl != null && buildUrl != "" ? buildUrl : env.BUILD_URL
340 def subject = "${buildStatusParam}: Job '${jobNameParam} [${buildNumberParam}]'"
341 def summary = "${subject} (${buildUrlParam})"
342
343 if(msgText != null && msgText != ""){
344 summary+="\n${msgText}"
345 }
346 if(buildStatusParam.toLowerCase().equals("success")){
347 colorCode = "#00FF00"
348 colorName = "green"
349 }else if(buildStatusParam.toLowerCase().equals("unstable")){
350 colorCode = "#FFFF00"
351 colorName = "yellow"
352 }else if(buildStatusParam.toLowerCase().equals("failure")){
353 colorCode = "#FF0000"
354 colorName = "red"
355 }
356 if(_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)){
357 if(enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")){
358 try{
359 slackSend color: colorCode, message: summary
360 }catch(Exception e){
361 println("Calling slack plugin failed")
362 e.printStackTrace()
363 }
364 }
365 if(enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")){
366 try{
367 hipchatSend color: colorName.toUpperCase(), message: summary
368 }catch(Exception e){
369 println("Calling hipchat plugin failed")
370 e.printStackTrace()
371 }
372 }
373 if(enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != ""){
374 try{
375 mail body: summary, from: mailFrom, subject: subject, to: mailTo
376 }catch(Exception e){
377 println("Sending mail plugin failed")
378 e.printStackTrace()
379 }
380 }
381 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100382}
chnyda4e5ac792017-03-14 15:24:18 +0100383
384/**
385 * Execute linux command and catch nth element
386 * @param cmd command to execute
387 * @param index index to retrieve
388 * @return index-th element
389 */
390
391def cutOrDie(cmd, index)
392{
393 def common = new com.mirantis.mk.Common()
394 def output
395 try {
396 output = sh(script: cmd, returnStdout: true)
397 def result = output.tokenize(" ")[index]
398 return result;
399 } catch (Exception e) {
400 common.errorMsg("Failed to execute cmd: ${cmd}\n output: ${output}")
401 }
Filip Pytloun81c864d2017-03-21 15:19:30 +0100402}
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100403
404/**
405 * Check variable contains keyword
406 * @param variable keywork is searched (contains) here
407 * @param keyword string to look for
408 * @return True if variable contains keyword (case insensitive), False if do not contains or any of input isn't a string
409 */
410
411def checkContains(variable, keyword) {
Jakub Josef7a8dea22017-03-23 19:51:32 +0100412 if(env.getEnvironment().containsKey(variable)){
413 return env[variable] && env[variable].toLowerCase().contains(keyword.toLowerCase())
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100414 } else {
Tomáš Kukrálc76c1e02017-03-23 19:06:59 +0100415 return false
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100416 }
417}
Jakub Josefa877db52017-04-05 14:22:30 +0200418
419/**
420 * Parse JSON string to hashmap
421 * @param jsonString input JSON string
422 * @return created hashmap
423 */
424def parseJSON(jsonString){
425 def m = [:]
Jakub Josefb7ab8472017-04-05 14:56:53 +0200426 def lazyMap = new JsonSlurperClassic().parseText(jsonString)
Jakub Josefa877db52017-04-05 14:22:30 +0200427 m.putAll(lazyMap)
428 return m
429}
Jakub Josefed239cd2017-05-09 15:27:33 +0200430
431/**
432 * Test pipeline input parameter existence and validity (not null and not empty string)
433 * @param paramName input parameter name (usually uppercase)
434 */
435def validInputParam(paramName){
436 return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200437}
438
439/**
440 * Take list of hashmaps and count number of hashmaps with parameter equals eq
441 * @param lm list of hashmaps
442 * @param param define parameter of hashmap to read and compare
443 * @param eq desired value of hashmap parameter
444 * @return count of hashmaps meeting defined condition
445 */
446
447@NonCPS
448def countHashMapEquals(lm, param, eq) {
Tomáš Kukrál5dd12072017-06-13 15:54:44 +0200449 return lm.stream().filter{i -> i[param].equals(eq)}.collect(java.util.stream.Collectors.counting())
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200450}