blob: 031bf6f3dfaff66fc045557c15891226746054b5 [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
azvyagintsev6bda9422018-08-20 11:57:05 +03008
Jakub Josef79ecec32017-02-17 14:36:28 +01009/**
10 *
11 * Common functions
12 *
13 */
14
15/**
16 * Generate current timestamp
17 *
azvyagintsev6bda9422018-08-20 11:57:05 +030018 * @param format Defaults to yyyyMMddHHmmss
Jakub Josef79ecec32017-02-17 14:36:28 +010019 */
azvyagintsev6bda9422018-08-20 11:57:05 +030020def getDatetime(format = "yyyyMMddHHmmss") {
Jakub Josef79ecec32017-02-17 14:36:28 +010021 def now = new Date();
22 return now.format(format, TimeZone.getTimeZone('UTC'));
23}
24
25/**
Jakub Josef79ecec32017-02-17 14:36:28 +010026 * Return workspace.
27 * Currently implemented by calling pwd so it won't return relevant result in
28 * dir context
29 */
azvyagintsev6bda9422018-08-20 11:57:05 +030030def getWorkspace(includeBuildNum = false) {
Jakub Josef79ecec32017-02-17 14:36:28 +010031 def workspace = sh script: 'pwd', returnStdout: true
32 workspace = workspace.trim()
azvyagintsev6bda9422018-08-20 11:57:05 +030033 if (includeBuildNum) {
34 if (!workspace.endsWith("/")) {
35 workspace += "/"
36 }
37 workspace += env.BUILD_NUMBER
Jakub Josefa661b8c2018-01-17 14:51:25 +010038 }
Jakub Josef79ecec32017-02-17 14:36:28 +010039 return workspace
40}
41
42/**
Dmitry Teselkind4adf972020-02-13 18:24:59 +030043 * Get absolute path via 'realink'
44 * -m, --canonicalize-missing
45 * canonicalize by following every symlink in every component of the given name recursively,
46 * without requirements on components existence
47 */
48def getAbsolutePath(String path) {
49 def absPath = sh script: "readlink -m ${path}", returnStdout: true
50 return absPath.trim()
51}
52
53/**
Filip Pytloun81c864d2017-03-21 15:19:30 +010054 * Get UID of jenkins user.
55 * Must be run from context of node
56 */
57def getJenkinsUid() {
azvyagintsev6bda9422018-08-20 11:57:05 +030058 return sh(
Filip Pytloun81c864d2017-03-21 15:19:30 +010059 script: 'id -u',
60 returnStdout: true
61 ).trim()
62}
63
64/**
65 * Get GID of jenkins user.
66 * Must be run from context of node
67 */
68def getJenkinsGid() {
azvyagintsev6bda9422018-08-20 11:57:05 +030069 return sh(
Filip Pytloun81c864d2017-03-21 15:19:30 +010070 script: 'id -g',
71 returnStdout: true
72 ).trim()
73}
74
75/**
Jakub Josefc8074db2018-01-30 13:33:20 +010076 * Returns Jenkins user uid and gid in one list (in that order)
77 * Must be run from context of node
78 */
azvyagintsev6bda9422018-08-20 11:57:05 +030079def getJenkinsUserIds() {
Jakub Josefc8074db2018-01-30 13:33:20 +010080 return sh(script: "id -u && id -g", returnStdout: true).tokenize("\n")
81}
82
83/**
Alexander Evseevbc1fea42017-12-13 10:03:03 +010084 *
85 * Find credentials by ID
86 *
azvyagintsev6bda9422018-08-20 11:57:05 +030087 * @param credsId Credentials ID
88 * @param credsType Credentials type (optional)
Alexander Evseevbc1fea42017-12-13 10:03:03 +010089 *
90 */
91def getCredentialsById(String credsId, String credsType = 'any') {
92 def credClasses = [ // ordered by class name
azvyagintsev6bda9422018-08-20 11:57:05 +030093 sshKey : com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.class,
94 cert : com.cloudbees.plugins.credentials.common.CertificateCredentials.class,
95 password : com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
96 any : com.cloudbees.plugins.credentials.impl.BaseStandardCredentials.class,
97 dockerCert: org.jenkinsci.plugins.docker.commons.credentials.DockerServerCredentials.class,
98 file : org.jenkinsci.plugins.plaincredentials.FileCredentials.class,
99 string : org.jenkinsci.plugins.plaincredentials.StringCredentials.class,
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100100 ]
101 return com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
102 credClasses[credsType],
103 jenkins.model.Jenkins.instance
azvyagintsev6bda9422018-08-20 11:57:05 +0300104 ).findAll { cred -> cred.id == credsId }[0]
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100105}
106
107/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100108 * Get credentials from store
109 *
azvyagintsev6bda9422018-08-20 11:57:05 +0300110 * @param id Credentials name
Jakub Josef79ecec32017-02-17 14:36:28 +0100111 */
Jakub Josef3d9d9ab2017-03-14 15:09:03 +0100112def getCredentials(id, cred_type = "username_password") {
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100113 warningMsg('You are using obsolete function. Please switch to use `getCredentialsById()`')
Jakub Josef79ecec32017-02-17 14:36:28 +0100114
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100115 type_map = [
116 username_password: 'password',
azvyagintsev6bda9422018-08-20 11:57:05 +0300117 key : 'sshKey',
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100118 ]
Jakub Josef79ecec32017-02-17 14:36:28 +0100119
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100120 return getCredentialsById(id, type_map[cred_type])
Jakub Josef79ecec32017-02-17 14:36:28 +0100121}
122
123/**
124 * Abort build, wait for some time and ensure we will terminate
125 */
126def abortBuild() {
127 currentBuild.build().doStop()
128 sleep(180)
129 // just to be sure we will terminate
130 throw new InterruptedException()
131}
132
133/**
Jakub Josefbceaa322017-06-13 18:28:27 +0200134 * Print pretty-printed string representation of given item
135 * @param item item to be pretty-printed (list, map, whatever)
136 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300137def prettyPrint(item) {
Jakub Josefbceaa322017-06-13 18:28:27 +0200138 println prettify(item)
139}
140
141/**
Jakub Josefb41c8d52017-03-24 13:52:24 +0100142 * Return pretty-printed string representation of given item
143 * @param item item to be pretty-printed (list, map, whatever)
144 * @return pretty-printed string
145 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300146def prettify(item) {
Jakub Josefbceaa322017-06-13 18:28:27 +0200147 return groovy.json.JsonOutput.prettyPrint(toJson(item)).replace('\\n', System.getProperty('line.separator'))
Jakub Josefb41c8d52017-03-24 13:52:24 +0100148}
149
150/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100151 * Print informational message
152 *
153 * @param msg
154 * @param color Colorful output or not
155 */
156def infoMsg(msg, color = true) {
157 printMsg(msg, "cyan")
158}
159
160/**
Sergey Galkin7137ad02019-11-07 14:52:13 +0400161 * Print informational message
162 *
163 * @param msg
164 * @param color Colorful output or not
165 */
166def infoSensitivityMsg(msg, color = true, replacing = []) {
167 printSensitivityMsg(msg, "cyan", replacing)
168}
169
170/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100171 * Print error message
172 *
173 * @param msg
174 * @param color Colorful output or not
175 */
176def errorMsg(msg, color = true) {
177 printMsg(msg, "red")
178}
179
180/**
181 * Print success message
182 *
183 * @param msg
184 * @param color Colorful output or not
185 */
186def successMsg(msg, color = true) {
187 printMsg(msg, "green")
188}
189
190/**
191 * Print warning message
192 *
193 * @param msg
194 * @param color Colorful output or not
195 */
196def warningMsg(msg, color = true) {
Jakub Josef0e7bd632017-03-16 16:25:05 +0100197 printMsg(msg, "yellow")
Jakub Josef79ecec32017-02-17 14:36:28 +0100198}
199
200/**
Jakub Josef952ae0b2017-03-14 19:04:21 +0100201 * Print debug message, this message will show only if DEBUG global variable is present
202 * @param msg
203 * @param color Colorful output or not
204 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300205def debugMsg(msg, color = true) {
Jakub Josef9a836ac2017-04-24 12:26:02 +0200206 // if debug property exists on env, debug is enabled
azvyagintsev6bda9422018-08-20 11:57:05 +0300207 if (env.getEnvironment().containsKey('DEBUG') && env['DEBUG'] == "true") {
Jakub Josef74b34692017-03-15 12:10:57 +0100208 printMsg("[DEBUG] ${msg}", "red")
Jakub Josef952ae0b2017-03-14 19:04:21 +0100209 }
210}
211
azvyagintsevf6e77912018-09-07 15:41:09 +0300212def getColorizedString(msg, color) {
213 def colorMap = [
214 'red' : '\u001B[31m',
215 'black' : '\u001B[30m',
216 'green' : '\u001B[32m',
217 'yellow': '\u001B[33m',
218 'blue' : '\u001B[34m',
219 'purple': '\u001B[35m',
220 'cyan' : '\u001B[36m',
221 'white' : '\u001B[37m',
222 'reset' : '\u001B[0m'
223 ]
Vasyl Saienko00ef98b2018-09-05 10:34:32 +0300224
azvyagintsevf6e77912018-09-07 15:41:09 +0300225 return "${colorMap[color]}${msg}${colorMap.reset}"
Vasyl Saienko00ef98b2018-09-05 10:34:32 +0300226}
227
Jakub Josef952ae0b2017-03-14 19:04:21 +0100228/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100229 * Print message
230 *
azvyagintsev6bda9422018-08-20 11:57:05 +0300231 * @param msg Message to be printed
Vasyl Saienko00ef98b2018-09-05 10:34:32 +0300232 * @param color Color to use for output
Jakub Josef79ecec32017-02-17 14:36:28 +0100233 */
Vasyl Saienko00ef98b2018-09-05 10:34:32 +0300234def printMsg(msg, color) {
235 print getColorizedString(msg, color)
Jakub Josef79ecec32017-02-17 14:36:28 +0100236}
237
238/**
Sergey Galkin7137ad02019-11-07 14:52:13 +0400239 * Print sensitivity message
240 *
241 * @param msg Message to be printed
242 * @param color Color to use for output
243 * @param replacing List with maps for deletion (passwords, logins, etc).
244 * The first () matching is mandatory !
245 * Example:
246 * [/ (OS_PASSWORD=)(.*?)+ /,
247 * / (password = )(.*?)+ /,
248 * / (password )(.*?) / ]
249 */
250def printSensitivityMsg(msg, color, replacing = []) {
251 for (i in replacing) {
252 msg = msg.replaceAll(i, ' $1XXXXXX ')
253 }
254 printMsg(msg, color)
255}
256
257/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100258 * Traverse directory structure and return list of files
259 *
260 * @param path Path to search
261 * @param type Type of files to search (groovy.io.FileType.FILES)
262 */
263@NonCPS
azvyagintsev6bda9422018-08-20 11:57:05 +0300264def getFiles(path, type = groovy.io.FileType.FILES) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100265 files = []
266 new File(path).eachFile(type) {
267 files[] = it
268 }
269 return files
270}
271
272/**
273 * Helper method to convert map into form of list of [key,value] to avoid
274 * unserializable exceptions
275 *
276 * @param m Map
277 */
278@NonCPS
279def entries(m) {
azvyagintsev6bda9422018-08-20 11:57:05 +0300280 m.collect { k, v -> [k, v] }
Jakub Josef79ecec32017-02-17 14:36:28 +0100281}
282
283/**
284 * Opposite of build-in parallel, run map of steps in serial
285 *
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200286 * @param steps Map of String<name>: CPSClosure2<step> (or list of closures)
Jakub Josef79ecec32017-02-17 14:36:28 +0100287 */
288def serial(steps) {
289 stepsArray = entries(steps)
azvyagintsev6bda9422018-08-20 11:57:05 +0300290 for (i = 0; i < stepsArray.size; i++) {
Jakub Josefd31de302017-05-15 13:59:18 +0200291 def step = stepsArray[i]
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200292 def dummySteps = [:]
293 def stepKey
azvyagintsev6bda9422018-08-20 11:57:05 +0300294 if (step[1] instanceof List || step[1] instanceof Map) {
295 for (j = 0; j < step[1].size(); j++) {
296 if (step[1] instanceof List) {
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200297 stepKey = j
azvyagintsev6bda9422018-08-20 11:57:05 +0300298 } else if (step[1] instanceof Map) {
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200299 stepKey = step[1].keySet()[j]
300 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300301 dummySteps.put("step-${step[0]}-${stepKey}", step[1][stepKey])
Jakub Josefd31de302017-05-15 13:59:18 +0200302 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300303 } else {
Jakub Josefd31de302017-05-15 13:59:18 +0200304 dummySteps.put(step[0], step[1])
305 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100306 parallel dummySteps
307 }
308}
309
310/**
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200311 * Partition given list to list of small lists
312 * @param inputList input list
313 * @param partitionSize (partition size, optional, default 5)
314 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300315def partitionList(inputList, partitionSize = 5) {
316 List<List<String>> partitions = new ArrayList<>();
317 for (int i = 0; i < inputList.size(); i += partitionSize) {
318 partitions.add(new ArrayList<String>(inputList.subList(i, Math.min(i + partitionSize, inputList.size()))));
319 }
320 return partitions
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200321}
322
323/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100324 * Get password credentials from store
325 *
azvyagintsev6bda9422018-08-20 11:57:05 +0300326 * @param id Credentials name
Jakub Josef79ecec32017-02-17 14:36:28 +0100327 */
328def getPasswordCredentials(id) {
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100329 return getCredentialsById(id, 'password')
Jakub Josef79ecec32017-02-17 14:36:28 +0100330}
331
332/**
333 * Get SSH credentials from store
334 *
azvyagintsev6bda9422018-08-20 11:57:05 +0300335 * @param id Credentials name
Jakub Josef79ecec32017-02-17 14:36:28 +0100336 */
337def getSshCredentials(id) {
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100338 return getCredentialsById(id, 'sshKey')
Jakub Josef79ecec32017-02-17 14:36:28 +0100339}
Jakub Josef79ecec32017-02-17 14:36:28 +0100340
341/**
342 * Tests Jenkins instance for existence of plugin with given name
343 * @param pluginName plugin short name to test
344 * @return boolean result
345 */
346@NonCPS
azvyagintsev6bda9422018-08-20 11:57:05 +0300347def jenkinsHasPlugin(pluginName) {
348 return Jenkins.instance.pluginManager.plugins.collect { p -> p.shortName }.contains(pluginName)
Jakub Josef79ecec32017-02-17 14:36:28 +0100349}
350
351@NonCPS
352def _needNotification(notificatedTypes, buildStatus, jobName) {
azvyagintsev6bda9422018-08-20 11:57:05 +0300353 if (notificatedTypes && notificatedTypes.contains("onchange")) {
354 if (jobName) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100355 def job = Jenkins.instance.getItem(jobName)
356 def numbuilds = job.builds.size()
azvyagintsev6bda9422018-08-20 11:57:05 +0300357 if (numbuilds > 0) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100358 //actual build is first for some reasons, so last finished build is second
359 def lastBuild = job.builds[1]
azvyagintsev6bda9422018-08-20 11:57:05 +0300360 if (lastBuild) {
361 if (lastBuild.result.toString().toLowerCase().equals(buildStatus)) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100362 println("Build status didn't changed since last build, not sending notifications")
363 return false;
364 }
365 }
366 }
367 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300368 } else if (!notificatedTypes.contains(buildStatus)) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100369 return false;
370 }
371 return true;
372}
373
374/**
375 * Send notification to all enabled notifications services
376 * @param buildStatus message type (success, warning, error), null means SUCCESSFUL
377 * @param msgText message text
378 * @param enabledNotifications list of enabled notification types, types: slack, hipchat, email, default empty
379 * @param notificatedTypes types of notifications will be sent, default onchange - notificate if current build result not equal last result;
380 * otherwise use - ["success","unstable","failed"]
381 * @param jobName optional job name param, if empty env.JOB_NAME will be used
Jakub Josefd0571152017-07-17 14:11:39 +0200382 * @param buildNumber build number param, if empty env.BUILD_NUM will be used
383 * @param buildUrl build url param, if empty env.BUILD_URL will be used
Jakub Josef79ecec32017-02-17 14:36:28 +0100384 * @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 +0200385 * @param mailTo mail TO param, it's mandatory for sending email notifications, this option enable mail notification
Jakub Josef79ecec32017-02-17 14:36:28 +0100386 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300387def sendNotification(buildStatus, msgText = "", enabledNotifications = [], notificatedTypes = ["onchange"], jobName = null, buildNumber = null, buildUrl = null, mailFrom = "jenkins", mailTo = null) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100388 // Default values
389 def colorName = 'blue'
390 def colorCode = '#0000FF'
391 def buildStatusParam = buildStatus != null && buildStatus != "" ? buildStatus : "SUCCESS"
392 def jobNameParam = jobName != null && jobName != "" ? jobName : env.JOB_NAME
393 def buildNumberParam = buildNumber != null && buildNumber != "" ? buildNumber : env.BUILD_NUMBER
394 def buildUrlParam = buildUrl != null && buildUrl != "" ? buildUrl : env.BUILD_URL
395 def subject = "${buildStatusParam}: Job '${jobNameParam} [${buildNumberParam}]'"
396 def summary = "${subject} (${buildUrlParam})"
397
azvyagintsev6bda9422018-08-20 11:57:05 +0300398 if (msgText != null && msgText != "") {
399 summary += "\n${msgText}"
Jakub Josef79ecec32017-02-17 14:36:28 +0100400 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300401 if (buildStatusParam.toLowerCase().equals("success")) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100402 colorCode = "#00FF00"
403 colorName = "green"
azvyagintsev6bda9422018-08-20 11:57:05 +0300404 } else if (buildStatusParam.toLowerCase().equals("unstable")) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100405 colorCode = "#FFFF00"
406 colorName = "yellow"
azvyagintsev6bda9422018-08-20 11:57:05 +0300407 } else if (buildStatusParam.toLowerCase().equals("failure")) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100408 colorCode = "#FF0000"
409 colorName = "red"
410 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300411 if (_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)) {
412 if (enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")) {
413 try {
Jakub Josef79ecec32017-02-17 14:36:28 +0100414 slackSend color: colorCode, message: summary
azvyagintsev6bda9422018-08-20 11:57:05 +0300415 } catch (Exception e) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100416 println("Calling slack plugin failed")
417 e.printStackTrace()
418 }
419 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300420 if (enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")) {
421 try {
Jakub Josef79ecec32017-02-17 14:36:28 +0100422 hipchatSend color: colorName.toUpperCase(), message: summary
azvyagintsev6bda9422018-08-20 11:57:05 +0300423 } catch (Exception e) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100424 println("Calling hipchat plugin failed")
425 e.printStackTrace()
426 }
427 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300428 if (enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != "") {
429 try {
Jakub Josef79ecec32017-02-17 14:36:28 +0100430 mail body: summary, from: mailFrom, subject: subject, to: mailTo
azvyagintsev6bda9422018-08-20 11:57:05 +0300431 } catch (Exception e) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100432 println("Sending mail plugin failed")
433 e.printStackTrace()
434 }
435 }
436 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100437}
chnyda4e5ac792017-03-14 15:24:18 +0100438
439/**
440 * Execute linux command and catch nth element
441 * @param cmd command to execute
442 * @param index index to retrieve
443 * @return index-th element
444 */
445
azvyagintsev6bda9422018-08-20 11:57:05 +0300446def cutOrDie(cmd, index) {
chnyda4e5ac792017-03-14 15:24:18 +0100447 def common = new com.mirantis.mk.Common()
448 def output
449 try {
azvyagintsev6bda9422018-08-20 11:57:05 +0300450 output = sh(script: cmd, returnStdout: true)
451 def result = output.tokenize(" ")[index]
452 return result;
chnyda4e5ac792017-03-14 15:24:18 +0100453 } catch (Exception e) {
azvyagintsev6bda9422018-08-20 11:57:05 +0300454 common.errorMsg("Failed to execute cmd: ${cmd}\n output: ${output}")
chnyda4e5ac792017-03-14 15:24:18 +0100455 }
Filip Pytloun81c864d2017-03-21 15:19:30 +0100456}
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100457
458/**
459 * Check variable contains keyword
460 * @param variable keywork is searched (contains) here
461 * @param keyword string to look for
462 * @return True if variable contains keyword (case insensitive), False if do not contains or any of input isn't a string
463 */
464
465def checkContains(variable, keyword) {
azvyagintsev6bda9422018-08-20 11:57:05 +0300466 if (env.getEnvironment().containsKey(variable)) {
Jakub Josef7a8dea22017-03-23 19:51:32 +0100467 return env[variable] && env[variable].toLowerCase().contains(keyword.toLowerCase())
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100468 } else {
Tomáš Kukrálc76c1e02017-03-23 19:06:59 +0100469 return false
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100470 }
471}
Jakub Josefa877db52017-04-05 14:22:30 +0200472
473/**
474 * Parse JSON string to hashmap
475 * @param jsonString input JSON string
476 * @return created hashmap
477 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300478def parseJSON(jsonString) {
479 def m = [:]
480 def lazyMap = new JsonSlurperClassic().parseText(jsonString)
481 m.putAll(lazyMap)
482 return m
Jakub Josefa877db52017-04-05 14:22:30 +0200483}
Jakub Josefed239cd2017-05-09 15:27:33 +0200484
485/**
Vasyl Saienko79d6f3b2018-10-19 09:13:46 +0300486 *
487 * Deep merge of Map items. Merges variable number of maps in to onto.
488 * Using the following rules:
489 * - Lists are appended
490 * - Maps are updated
491 * - other object types are replaced.
492 *
493 *
494 * @param onto Map object to merge in
495 * @param overrides Map objects to merge to onto
496*/
497def mergeMaps(Map onto, Map... overrides){
498 if (!overrides){
499 return onto
500 }
501 else if (overrides.length == 1) {
502 overrides[0]?.each { k, v ->
Vasyl Saienkof92cf4f2019-07-10 12:39:25 +0300503 if (k in onto.keySet()) {
504 if (v in Map && onto[k] in Map){
505 mergeMaps((Map) onto[k], (Map) v)
506 } else if (v in List) {
507 onto[k] += v
508 } else {
509 onto[k] = v
510 }
Vasyl Saienko79d6f3b2018-10-19 09:13:46 +0300511 } else {
512 onto[k] = v
513 }
514 }
515 return onto
516 }
517 return overrides.inject(onto, { acc, override -> mergeMaps(acc, override ?: [:]) })
518}
519
520/**
Jakub Josefed239cd2017-05-09 15:27:33 +0200521 * Test pipeline input parameter existence and validity (not null and not empty string)
522 * @param paramName input parameter name (usually uppercase)
azvyagintsevc8ecdfd2018-09-11 12:47:15 +0300523 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300524def validInputParam(paramName) {
azvyagintsevc8ecdfd2018-09-11 12:47:15 +0300525 if (paramName instanceof java.lang.String) {
526 return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
527 }
528 return false
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200529}
530
531/**
532 * Take list of hashmaps and count number of hashmaps with parameter equals eq
533 * @param lm list of hashmaps
534 * @param param define parameter of hashmap to read and compare
535 * @param eq desired value of hashmap parameter
536 * @return count of hashmaps meeting defined condition
537 */
538
539@NonCPS
540def countHashMapEquals(lm, param, eq) {
azvyagintsev6bda9422018-08-20 11:57:05 +0300541 return lm.stream().filter { i -> i[param].equals(eq) }.collect(java.util.stream.Collectors.counting())
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200542}
Vasyl Saienkofb055b12017-11-23 18:15:23 +0200543
544/**
545 * Execute shell command and return stdout, stderr and status
546 *
547 * @param cmd Command to execute
548 * @return map with stdout, stderr, status keys
549 */
550
551def shCmdStatus(cmd) {
azvyagintsev386e94e2019-06-13 13:39:04 +0300552 // Set +x , to hide odd messages about temp file manipulations
Vasyl Saienkofb055b12017-11-23 18:15:23 +0200553 def res = [:]
azvyagintsev386e94e2019-06-13 13:39:04 +0300554 def stderr = sh(script: 'set +x ; mktemp', returnStdout: true).trim()
555 def stdout = sh(script: 'set +x ; mktemp', returnStdout: true).trim()
Vasyl Saienkofb055b12017-11-23 18:15:23 +0200556
557 try {
azvyagintsev6bda9422018-08-20 11:57:05 +0300558 def status = sh(script: "${cmd} 1>${stdout} 2>${stderr}", returnStatus: true)
azvyagintsev386e94e2019-06-13 13:39:04 +0300559 res['stderr'] = sh(script: "set +x; cat ${stderr}", returnStdout: true).trim()
560 res['stdout'] = sh(script: "set +x; cat ${stdout}", returnStdout: true).trim()
Vasyl Saienkofb055b12017-11-23 18:15:23 +0200561 res['status'] = status
562 } finally {
azvyagintsev386e94e2019-06-13 13:39:04 +0300563 sh(script: "set +x; rm ${stderr}")
564 sh(script: "set +x; rm ${stdout}")
Vasyl Saienkofb055b12017-11-23 18:15:23 +0200565 }
566
567 return res
568}
Richard Felkl66a242d2018-01-25 15:27:15 +0100569
Richard Felkl66a242d2018-01-25 15:27:15 +0100570/**
571 * Retry commands passed to body
572 *
Martin Polreich331f2b62019-02-08 10:16:52 +0100573 * Don't use common.retry method for retrying salt.enforceState method. Use retries parameter
574 * built-in the salt.enforceState method instead to ensure correct functionality.
575 *
Richard Felkl66a242d2018-01-25 15:27:15 +0100576 * @param times Number of retries
Jakub Josef61463c72018-02-13 16:10:56 +0100577 * @param delay Delay between retries (in seconds)
Richard Felkl66a242d2018-01-25 15:27:15 +0100578 * @param body Commands to be in retry block
579 * @return calling commands in body
azvyagintsev6bda9422018-08-20 11:57:05 +0300580 * @example retry ( 3 , 5 ) { function body }* retry{ function body }
Richard Felkl66a242d2018-01-25 15:27:15 +0100581 */
582
583def retry(int times = 5, int delay = 0, Closure body) {
584 int retries = 0
azvyagintsev6bda9422018-08-20 11:57:05 +0300585 while (retries++ < times) {
Richard Felkl66a242d2018-01-25 15:27:15 +0100586 try {
587 return body.call()
azvyagintsev6bda9422018-08-20 11:57:05 +0300588 } catch (e) {
Denis Egorenko900a3af2019-01-14 12:54:56 +0400589 errorMsg(e.toString())
Richard Felkl66a242d2018-01-25 15:27:15 +0100590 sleep(delay)
591 }
592 }
Richard Felkl66a242d2018-01-25 15:27:15 +0100593 throw new Exception("Failed after $times retries")
594}
Dmitry Pyzhov0d0d8522018-05-15 22:37:37 +0300595
Dmitry Pyzhov0d0d8522018-05-15 22:37:37 +0300596/**
597 * Wait for user input with timeout
598 *
599 * @param timeoutInSeconds Timeout
600 * @param options Options for input widget
601 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300602def waitForInputThenPass(timeoutInSeconds, options = [message: 'Ready to go?']) {
603 def userInput = true
604 try {
605 timeout(time: timeoutInSeconds, unit: 'SECONDS') {
606 userInput = input options
607 }
608 } catch (err) { // timeout reached or input false
609 def user = err.getCauses()[0].getUser()
610 if ('SYSTEM' == user.toString()) { // SYSTEM means timeout.
611 println("Timeout, proceeding")
612 } else {
613 userInput = false
614 println("Aborted by: [${user}]")
615 throw err
616 }
Dmitry Pyzhov0d0d8522018-05-15 22:37:37 +0300617 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300618 return userInput
Dmitry Pyzhov0d0d8522018-05-15 22:37:37 +0300619}
Oleksii Grudev9e1d97a2018-06-29 16:04:30 +0300620
621/**
622 * Function receives Map variable as input and sorts it
623 * by values ascending. Returns sorted Map
624 * @param _map Map variable
625 */
626@NonCPS
627def SortMapByValueAsc(_map) {
azvyagintsev6bda9422018-08-20 11:57:05 +0300628 def sortedMap = _map.sort { it.value }
Oleksii Grudev9e1d97a2018-06-29 16:04:30 +0300629 return sortedMap
630}
azvyagintsev99d35842018-08-17 20:26:34 +0300631
632/**
633 * Compare 'old' and 'new' dir's recursively
634 * @param diffData =' Only in new/XXX/infra: secrets.yml
635 Files old/XXX/init.yml and new/XXX/init.yml differ
636 Only in old/XXX/infra: secrets11.yml '
637 *
638 * @return
639 * - new:
640 - XXX/secrets.yml
641 - diff:
642 - XXX/init.yml
643 - removed:
644 - XXX/secrets11.yml
645
646 */
647def diffCheckMultidir(diffData) {
azvyagintsevab5637b2018-08-20 18:18:15 +0300648 common = new com.mirantis.mk.Common()
649 // Some global constants. Don't change\move them!
650 keyNew = 'new'
651 keyRemoved = 'removed'
652 keyDiff = 'diff'
653 def output = [
654 new : [],
655 removed: [],
656 diff : [],
657 ]
658 String pathSep = '/'
659 diffData.each { line ->
660 def job_file = ''
661 def job_type = ''
662 if (line.startsWith('Files old/')) {
663 job_file = new File(line.replace('Files old/', '').tokenize()[0])
664 job_type = keyDiff
665 } else if (line.startsWith('Only in new/')) {
666 // get clean normalized filepath, under new/
667 job_file = new File(line.replace('Only in new/', '').replace(': ', pathSep)).toString()
668 job_type = keyNew
669 } else if (line.startsWith('Only in old/')) {
670 // get clean normalized filepath, under old/
671 job_file = new File(line.replace('Only in old/', '').replace(': ', pathSep)).toString()
672 job_type = keyRemoved
673 } else {
674 common.warningMsg("Not parsed diff line: ${line}!")
675 }
676 if (job_file != '') {
677 output[job_type].push(job_file)
678 }
azvyagintsev99d35842018-08-17 20:26:34 +0300679 }
azvyagintsevab5637b2018-08-20 18:18:15 +0300680 return output
azvyagintsev99d35842018-08-17 20:26:34 +0300681}
682
683/**
684 * Compare 2 folder, file by file
685 * Structure should be:
686 * ${compRoot}/
687 └── diff - diff results will be save here
688 ├── new - input folder with data
689 ├── old - input folder with data
690 ├── pillar.diff - globall diff will be saved here
691 * b_url - usual env.BUILD_URL, to be add into description
azvyagintsev3bbcafe2018-08-20 19:36:16 +0300692 * grepOpts - General grep cmdline; Could be used to pass some magic
693 * regexp into after-diff listing file(pillar.diff)
694 * Example: '-Ev infra/secrets.yml'
azvyagintsev99d35842018-08-17 20:26:34 +0300695 * return - html-based string
696 * TODO: allow to specify subdir for results?
azvyagintsev99d35842018-08-17 20:26:34 +0300697 **/
Denis Egorenko4551f372018-09-11 16:36:13 +0400698
azvyagintsev3bbcafe2018-08-20 19:36:16 +0300699def comparePillars(compRoot, b_url, grepOpts) {
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000700
azvyagintsevab5637b2018-08-20 18:18:15 +0300701 // Some global constants. Don't change\move them!
702 keyNew = 'new'
703 keyRemoved = 'removed'
704 keyDiff = 'diff'
705 def diff_status = 0
706 // FIXME
707 httpWS = b_url + '/artifact/'
708 dir(compRoot) {
azvyagintsev3bbcafe2018-08-20 19:36:16 +0300709 // If diff empty - exit 0
710 diff_status = sh(script: 'diff -q -r old/ new/ > pillar.diff',
azvyagintsevab5637b2018-08-20 18:18:15 +0300711 returnStatus: true,
712 )
azvyagintsev24d49652018-08-21 19:33:51 +0300713 }
azvyagintsevc0fe1442018-08-21 20:01:34 +0300714 // Unfortunately, diff not able to work with dir-based regexp
715 if (diff_status == 1 && grepOpts) {
716 dir(compRoot) {
717 grep_status = sh(script: """
azvyagintsev3bbcafe2018-08-20 19:36:16 +0300718 cp -v pillar.diff pillar_orig.diff
719 grep ${grepOpts} pillar_orig.diff > pillar.diff
720 """,
azvyagintsevc0fe1442018-08-21 20:01:34 +0300721 returnStatus: true
722 )
azvyagintsevf6e77912018-09-07 15:41:09 +0300723 if (grep_status == 1) {
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000724 warningMsg("Grep regexp ${grepOpts} removed all diff!")
azvyagintsevc0fe1442018-08-21 20:01:34 +0300725 diff_status = 0
azvyagintsevb99f87c2018-08-21 19:43:59 +0300726 }
azvyagintsev3bbcafe2018-08-20 19:36:16 +0300727 }
azvyagintsevc0fe1442018-08-21 20:01:34 +0300728 }
729 // Set job description
Denis Egorenko4551f372018-09-11 16:36:13 +0400730 description = ''
azvyagintsevc0fe1442018-08-21 20:01:34 +0300731 if (diff_status == 1) {
azvyagintsevab5637b2018-08-20 18:18:15 +0300732 // Analyse output file and prepare array with results
733 String data_ = readFile file: "${compRoot}/pillar.diff"
734 def diff_list = diffCheckMultidir(data_.split("\\r?\\n"))
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000735 infoMsg(diff_list)
azvyagintsevab5637b2018-08-20 18:18:15 +0300736 dir(compRoot) {
737 if (diff_list[keyDiff].size() > 0) {
738 if (!fileExists('diff')) {
739 sh('mkdir -p diff')
740 }
741 description += '<b>CHANGED</b><ul>'
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000742 infoMsg('Changed items:')
Denis Egorenko4551f372018-09-11 16:36:13 +0400743 def stepsForParallel = [:]
744 stepsForParallel.failFast = true
745 diff_list[keyDiff].each {
746 stepsForParallel.put("Differ for:${it}",
747 {
748 // We don't want to handle sub-dirs structure. So, simply make diff 'flat'
749 def item_f = it.toString().replace('/', '_')
750 description += "<li><a href=\"${httpWS}/diff/${item_f}/*view*/\">${it}</a></li>"
751 // Generate diff file
752 def diff_exit_code = sh([
753 script : "diff -U 50 old/${it} new/${it} > diff/${item_f}",
754 returnStdout: false,
755 returnStatus: true,
756 ])
757 // catch normal errors, diff should always return 1
758 if (diff_exit_code != 1) {
759 error 'Error with diff file generation'
760 }
761 })
azvyagintsevab5637b2018-08-20 18:18:15 +0300762 }
Denis Egorenko4551f372018-09-11 16:36:13 +0400763
764 parallel stepsForParallel
azvyagintsevab5637b2018-08-20 18:18:15 +0300765 }
766 if (diff_list[keyNew].size() > 0) {
767 description += '<b>ADDED</b><ul>'
768 for (item in diff_list[keyNew]) {
769 description += "<li><a href=\"${httpWS}/new/${item}/*view*/\">${item}</a></li>"
770 }
771 }
772 if (diff_list[keyRemoved].size() > 0) {
773 description += '<b>DELETED</b><ul>'
774 for (item in diff_list[keyRemoved]) {
775 description += "<li><a href=\"${httpWS}/old/${item}/*view*/\">${item}</a></li>"
776 }
777 }
Denis Egorenko62120962019-03-15 11:24:32 +0400778 def cwd = sh(script: 'basename $(pwd)', returnStdout: true).trim()
779 sh "tar -cf old_${cwd}.tar.gz old/ && rm -rf old/"
780 sh "tar -cf new_${cwd}.tar.gz new/ && rm -rf new/"
azvyagintsevab5637b2018-08-20 18:18:15 +0300781 }
azvyagintsev99d35842018-08-17 20:26:34 +0300782 }
azvyagintsevab5637b2018-08-20 18:18:15 +0300783
784 if (description != '') {
785 dir(compRoot) {
786 archiveArtifacts([
787 artifacts : '**',
788 allowEmptyArchive: true,
789 ])
790 }
791 return description.toString()
792 } else {
azvyagintseva57c82a2018-09-20 12:17:24 +0300793 return '<b>No job changes</b>'
azvyagintsevab5637b2018-08-20 18:18:15 +0300794 }
azvyagintsev99d35842018-08-17 20:26:34 +0300795}
azvyagintsevab5637b2018-08-20 18:18:15 +0300796
797/**
798 * Simple function, to get basename from string.
799 * line - path-string
800 * remove_ext - string, optionl. Drop file extenstion.
801 **/
802def GetBaseName(line, remove_ext) {
803 filename = line.toString().split('/').last()
804 if (remove_ext && filename.endsWith(remove_ext.toString())) {
805 filename = filename.take(filename.lastIndexOf(remove_ext.toString()))
806 }
807 return filename
Vasyl Saienko00ef98b2018-09-05 10:34:32 +0300808}
Vasyl Saienko723cbbc2018-09-05 11:08:52 +0300809
810/**
azvyagintsevf6e77912018-09-07 15:41:09 +0300811 * Return colored string of specific stage in stageMap
812 *
813 * @param stageMap LinkedHashMap object.
814 * @param stageName The name of current stage we are going to execute.
815 * @param color Text color
816 * */
817def getColoredStageView(stageMap, stageName, color) {
818 def stage = stageMap[stageName]
819 def banner = []
820 def currentStageIndex = new ArrayList<String>(stageMap.keySet()).indexOf(stageName)
821 def numberOfStages = stageMap.keySet().size() - 1
Vasyl Saienko723cbbc2018-09-05 11:08:52 +0300822
Vasyl Saienko723cbbc2018-09-05 11:08:52 +0300823 banner.add(getColorizedString(
azvyagintsevf6e77912018-09-07 15:41:09 +0300824 "=========== Stage ${currentStageIndex}/${numberOfStages}: ${stageName} ===========", color))
825 for (stage_item in stage.keySet()) {
826 banner.add(getColorizedString(
827 "${stage_item}: ${stage[stage_item]}", color))
Vasyl Saienko723cbbc2018-09-05 11:08:52 +0300828 }
azvyagintsevf6e77912018-09-07 15:41:09 +0300829 banner.add('\n')
830
831 return banner
832}
833
834/**
835 * Pring stageMap to console with specified color
836 *
837 * @param stageMap LinkedHashMap object with stages information.
838 * @param currentStage The name of current stage we are going to execute.
839 *
840 * */
841def printCurrentStage(stageMap, currentStage) {
842 print getColoredStageView(stageMap, currentStage, "cyan").join('\n')
843}
844
845/**
846 * Pring stageMap to console with specified color
847 *
848 * @param stageMap LinkedHashMap object.
849 * @param baseColor Text color (default white)
850 * */
851def printStageMap(stageMap, baseColor = "white") {
852 def banner = []
853 def index = 0
854 for (stage_name in stageMap.keySet()) {
855 banner.addAll(getColoredStageView(stageMap, stage_name, baseColor))
856 }
857 print banner.join('\n')
858}
859
860/**
861 * Wrap provided code in stage, and do interactive retires if needed.
862 *
863 * @param stageMap LinkedHashMap object with stages information.
864 * @param currentStage The name of current stage we are going to execute.
865 * @param target Target host to execute stage on.
866 * @param interactive Boolean flag to specify if interaction with user is enabled.
867 * @param body Command to be in stage block.
868 * */
869def stageWrapper(stageMap, currentStage, target, interactive = true, Closure body) {
870 def common = new com.mirantis.mk.Common()
871 def banner = []
872
873 printCurrentStage(stageMap, currentStage)
874
875 stage(currentStage) {
Vasyl Saienkobc6debb2018-09-10 14:08:09 +0300876 if (interactive){
azvyagintsevf6e77912018-09-07 15:41:09 +0300877 input message: getColorizedString("We are going to execute stage \'${currentStage}\' on the following target ${target}.\nPlease review stage information above.", "yellow")
Vasyl Saienkobc6debb2018-09-10 14:08:09 +0300878 }
879 try {
Vasyl Saienkobc6debb2018-09-10 14:08:09 +0300880 stageMap[currentStage]['Status'] = "SUCCESS"
Victor Ryzhenkin49d67812019-01-09 15:28:21 +0400881 return body.call()
Vasyl Saienkobc6debb2018-09-10 14:08:09 +0300882 } catch (Exception err) {
883 def msg = "Stage ${currentStage} failed with the following exception:\n${err}"
884 print getColorizedString(msg, "yellow")
885 common.errorMsg(err)
886 if (interactive) {
887 input message: getColorizedString("Please make sure problem is fixed to proceed with retry. Ready to proceed?", "yellow")
888 stageMap[currentStage]['Status'] = "RETRYING"
889 stageWrapper(stageMap, currentStage, target, interactive, body)
890 } else {
891 error(msg)
azvyagintsevf6e77912018-09-07 15:41:09 +0300892 }
Vasyl Saienkobc6debb2018-09-10 14:08:09 +0300893 }
azvyagintsevf6e77912018-09-07 15:41:09 +0300894 }
895}
896
897/**
898 * Ugly transition solution for internal tests.
899 * 1) Check input => transform to static result, based on runtime and input
900 * 2) Check remote-binary repo for exact resource
azvyagintsevf6fce3d2018-12-28 15:30:33 +0200901 * Return: changes each linux_system_* cto false, in case broken url in some of them
902 */
azvyagintsevf6e77912018-09-07 15:41:09 +0300903
904def checkRemoteBinary(LinkedHashMap config, List extraScmExtensions = []) {
905 def common = new com.mirantis.mk.Common()
azvyagintsevf6fce3d2018-12-28 15:30:33 +0200906 def res = [:]
azvyagintsevf6e77912018-09-07 15:41:09 +0300907 res['MirrorRoot'] = config.get('globalMirrorRoot', env["BIN_MIRROR_ROOT"] ? env["BIN_MIRROR_ROOT"] : "http://mirror.mirantis.com/")
908 // Reclass-like format's. To make life eazy!
azvyagintsev603d95b2018-11-09 15:37:10 +0200909 res['mcp_version'] = config.get('mcp_version', env["BIN_APT_MCP_VERSION"] ? env["BIN_APT_MCP_VERSION"] : 'nightly')
azvyagintsevf6fce3d2018-12-28 15:30:33 +0200910 res['linux_system_repo_url'] = config.get('linux_system_repo_url', env['BIN_linux_system_repo_url'] ? env['BIN_linux_system_repo_url'] : "${res['MirrorRoot']}/${res['mcp_version']}/")
911 res['linux_system_repo_ubuntu_url'] = config.get('linux_system_repo_ubuntu_url', env['BIN_linux_system_repo_ubuntu_url'] ? env['BIN_linux_system_repo_ubuntu_url'] : "${res['MirrorRoot']}/${res['mcp_version']}/ubuntu/")
912 res['linux_system_repo_mcp_salt_url'] = config.get('linux_system_repo_mcp_salt_url', env['BIN_linux_system_repo_mcp_salt_url'] ? env['BIN_linux_system_repo_mcp_salt_url'] : "${res['MirrorRoot']}/${res['mcp_version']}/salt-formulas/")
azvyagintsevf6e77912018-09-07 15:41:09 +0300913
914 if (config.get('verify', true)) {
azvyagintsevf6fce3d2018-12-28 15:30:33 +0200915 res.each { key, val ->
916 if (key.toString().startsWith('linux_system_repo')) {
917 def MirrorRootStatus = sh(script: "wget --auth-no-challenge --spider ${val} 2>/dev/null", returnStatus: true)
918 if (MirrorRootStatus != 0) {
919 common.warningMsg("Resource: '${key}' at '${val}' not exist!")
920 res[key] = false
921 }
922 }
azvyagintsevf6e77912018-09-07 15:41:09 +0300923 }
924 }
925 return res
Vasyl Saienko723cbbc2018-09-05 11:08:52 +0300926}
Denis Egorenkoeded0d42018-09-26 13:25:49 +0400927
928/**
929 * Workaround to update env properties, like GERRIT_* vars,
930 * which should be passed from upstream job to downstream.
931 * Will not fail entire job in case any issues.
932 * @param envVar - EnvActionImpl env job
933 * @param extraVars - Multiline YAML text with extra vars
934 */
935def mergeEnv(envVar, extraVars) {
Denis Egorenko6d3cc232018-09-27 17:42:58 +0400936 def common = new com.mirantis.mk.Common()
Denis Egorenkoeded0d42018-09-26 13:25:49 +0400937 try {
938 def extraParams = readYaml text: extraVars
939 for(String key in extraParams.keySet()) {
940 envVar[key] = extraParams[key]
941 common.warningMsg("Parameter ${key} is updated from EXTRA vars.")
942 }
943 } catch (Exception e) {
944 common.errorMsg("Can't update env parameteres, because: ${e.toString()}")
945 }
946}
Denis Egorenko599bd632018-09-28 15:24:37 +0400947
948/**
949 * Wrapper around parallel pipeline function
950 * with ability to restrict number of parallel threads
951 * running simultaneously
952 *
953 * @param branches - Map with Clousers to be executed
954 * @param maxParallelJob - Integer number of parallel threads allowed
955 * to run simultaneously
956 */
957def runParallel(branches, maxParallelJob = 10) {
958 def runningSteps = 0
959 branches.each { branchName, branchBody ->
960 if (branchBody instanceof Closure) {
961 branches[branchName] = {
962 while (!(runningSteps < maxParallelJob)) {
963 continue
964 }
965 runningSteps += 1
966 branchBody.call()
967 runningSteps -= 1
968 }
969 }
970 }
971 if (branches) {
972 parallel branches
973 }
974}
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000975
976/**
977 * Ugly processing basic funcs with /etc/apt
Denis Egorenko5cea1412018-10-18 16:40:11 +0400978 * @param repoConfig YAML text or Map
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000979 * Example :
Denis Egorenko5cea1412018-10-18 16:40:11 +0400980 repoConfig = '''
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000981 ---
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000982 aprConfD: |-
Denis Egorenkoe02a1b22018-10-19 17:47:53 +0400983 APT::Get::AllowUnauthenticated 'true';
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000984 repo:
Denis Egorenkoe02a1b22018-10-19 17:47:53 +0400985 mcp_saltstack:
986 source: "deb [arch=amd64] http://mirror.mirantis.com/nightly/saltstack-2017.7/xenial xenial main"
987 pin:
988 - package: "libsodium18"
989 pin: "release o=SaltStack"
990 priority: 50
991 - package: "*"
992 pin: "release o=SaltStack"
993 priority: "1100"
994 repo_key: "http://mirror.mirantis.com/public.gpg"
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000995 '''
996 *
997 */
998
Denis Egorenko5cea1412018-10-18 16:40:11 +0400999def debianExtraRepos(repoConfig) {
1000 def config = null
1001 if (repoConfig instanceof Map) {
1002 config = repoConfig
1003 } else {
1004 config = readYaml text: repoConfig
1005 }
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +00001006 if (config.get('repo', false)) {
1007 for (String repo in config['repo'].keySet()) {
Denis Egorenko395aa212018-10-11 15:11:28 +04001008 source = config['repo'][repo]['source']
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +00001009 warningMsg("Write ${source} > /etc/apt/sources.list.d/${repo}.list")
1010 sh("echo '${source}' > /etc/apt/sources.list.d/${repo}.list")
Denis Egorenko395aa212018-10-11 15:11:28 +04001011 if (config['repo'][repo].containsKey('repo_key')) {
1012 key = config['repo'][repo]['repo_key']
1013 sh("wget -O - '${key}' | apt-key add -")
1014 }
Denis Egorenkoe02a1b22018-10-19 17:47:53 +04001015 if (config['repo'][repo]['pin']) {
1016 def repoPins = []
1017 for (Map pin in config['repo'][repo]['pin']) {
1018 repoPins.add("Package: ${pin['package']}")
1019 repoPins.add("Pin: ${pin['pin']}")
1020 repoPins.add("Pin-Priority: ${pin['priority']}")
Denis Egorenko1c93d122018-11-02 12:14:05 +04001021 // additional empty line between pins
1022 repoPins.add('\n')
Denis Egorenkoe02a1b22018-10-19 17:47:53 +04001023 }
1024 if (repoPins) {
1025 repoPins.add(0, "### Extra ${repo} repo pin start ###")
1026 repoPins.add("### Extra ${repo} repo pin end ###")
1027 repoPinning = repoPins.join('\n')
1028 warningMsg("Adding pinning \n${repoPinning}\n => /etc/apt/preferences.d/${repo}")
1029 sh("echo '${repoPinning}' > /etc/apt/preferences.d/${repo}")
1030 }
1031 }
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +00001032 }
1033 }
1034 if (config.get('aprConfD', false)) {
1035 for (String pref in config['aprConfD'].tokenize('\n')) {
1036 warningMsg("Adding ${pref} => /etc/apt/apt.conf.d/99setupAndTestNode")
1037 sh("echo '${pref}' >> /etc/apt/apt.conf.d/99setupAndTestNode")
1038 }
1039 sh('cat /etc/apt/apt.conf.d/99setupAndTestNode')
1040 }
1041}
Denis Egorenkoeaf78db2019-02-06 17:01:38 +04001042
1043/**
1044 * Parse date from string
1045 * @param String date - date to parse
1046 * @param String format - date format in provided date string value
1047 *
1048 * return new Date() object
1049 */
1050Date parseDate(String date, String format) {
1051 return Date.parse(format, date)
1052}
Denis Egorenko815758d2019-07-08 15:54:08 +04001053
1054/**
1055 * Generate Random Hash string
1056 * @param n Hash length
1057 * @param pool Pool to use for hash generation
1058*/
1059def generateRandomHashString(int n, ArrayList pool = []) {
1060 if (!pool) {
1061 pool = ['a'..'z','A'..'Z',0..9,'_','+','='].flatten()
1062 }
1063 Random rand = new Random(System.currentTimeMillis())
1064 return (1..n).collect { pool[rand.nextInt(pool.size())] }.join()
1065}
Mykyta Karpin70cd3332019-09-16 18:31:03 +03001066
1067/**
1068 * Checks whether string is semver complaint version
1069 * @param string version
1070*/
1071
1072def isSemVer(version){
1073 // Official regex for Semver2 (https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string)
1074 String semVerRegex = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
1075 return version ==~ semVerRegex
1076}