blob: c8f2e0510150a17d3c524e7233ee6ef7ddc1c997 [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/**
Filip Pytloun81c864d2017-03-21 15:19:30 +010043 * Get UID of jenkins user.
44 * Must be run from context of node
45 */
46def getJenkinsUid() {
azvyagintsev6bda9422018-08-20 11:57:05 +030047 return sh(
Filip Pytloun81c864d2017-03-21 15:19:30 +010048 script: 'id -u',
49 returnStdout: true
50 ).trim()
51}
52
53/**
54 * Get GID of jenkins user.
55 * Must be run from context of node
56 */
57def getJenkinsGid() {
azvyagintsev6bda9422018-08-20 11:57:05 +030058 return sh(
Filip Pytloun81c864d2017-03-21 15:19:30 +010059 script: 'id -g',
60 returnStdout: true
61 ).trim()
62}
63
64/**
Jakub Josefc8074db2018-01-30 13:33:20 +010065 * Returns Jenkins user uid and gid in one list (in that order)
66 * Must be run from context of node
67 */
azvyagintsev6bda9422018-08-20 11:57:05 +030068def getJenkinsUserIds() {
Jakub Josefc8074db2018-01-30 13:33:20 +010069 return sh(script: "id -u && id -g", returnStdout: true).tokenize("\n")
70}
71
72/**
Alexander Evseevbc1fea42017-12-13 10:03:03 +010073 *
74 * Find credentials by ID
75 *
azvyagintsev6bda9422018-08-20 11:57:05 +030076 * @param credsId Credentials ID
77 * @param credsType Credentials type (optional)
Alexander Evseevbc1fea42017-12-13 10:03:03 +010078 *
79 */
80def getCredentialsById(String credsId, String credsType = 'any') {
81 def credClasses = [ // ordered by class name
azvyagintsev6bda9422018-08-20 11:57:05 +030082 sshKey : com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.class,
83 cert : com.cloudbees.plugins.credentials.common.CertificateCredentials.class,
84 password : com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
85 any : com.cloudbees.plugins.credentials.impl.BaseStandardCredentials.class,
86 dockerCert: org.jenkinsci.plugins.docker.commons.credentials.DockerServerCredentials.class,
87 file : org.jenkinsci.plugins.plaincredentials.FileCredentials.class,
88 string : org.jenkinsci.plugins.plaincredentials.StringCredentials.class,
Alexander Evseevbc1fea42017-12-13 10:03:03 +010089 ]
90 return com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
91 credClasses[credsType],
92 jenkins.model.Jenkins.instance
azvyagintsev6bda9422018-08-20 11:57:05 +030093 ).findAll { cred -> cred.id == credsId }[0]
Alexander Evseevbc1fea42017-12-13 10:03:03 +010094}
95
96/**
Jakub Josef79ecec32017-02-17 14:36:28 +010097 * Get credentials from store
98 *
azvyagintsev6bda9422018-08-20 11:57:05 +030099 * @param id Credentials name
Jakub Josef79ecec32017-02-17 14:36:28 +0100100 */
Jakub Josef3d9d9ab2017-03-14 15:09:03 +0100101def getCredentials(id, cred_type = "username_password") {
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100102 warningMsg('You are using obsolete function. Please switch to use `getCredentialsById()`')
Jakub Josef79ecec32017-02-17 14:36:28 +0100103
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100104 type_map = [
105 username_password: 'password',
azvyagintsev6bda9422018-08-20 11:57:05 +0300106 key : 'sshKey',
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100107 ]
Jakub Josef79ecec32017-02-17 14:36:28 +0100108
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100109 return getCredentialsById(id, type_map[cred_type])
Jakub Josef79ecec32017-02-17 14:36:28 +0100110}
111
112/**
113 * Abort build, wait for some time and ensure we will terminate
114 */
115def abortBuild() {
116 currentBuild.build().doStop()
117 sleep(180)
118 // just to be sure we will terminate
119 throw new InterruptedException()
120}
121
122/**
Jakub Josefbceaa322017-06-13 18:28:27 +0200123 * Print pretty-printed string representation of given item
124 * @param item item to be pretty-printed (list, map, whatever)
125 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300126def prettyPrint(item) {
Jakub Josefbceaa322017-06-13 18:28:27 +0200127 println prettify(item)
128}
129
130/**
Jakub Josefb41c8d52017-03-24 13:52:24 +0100131 * Return pretty-printed string representation of given item
132 * @param item item to be pretty-printed (list, map, whatever)
133 * @return pretty-printed string
134 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300135def prettify(item) {
Jakub Josefbceaa322017-06-13 18:28:27 +0200136 return groovy.json.JsonOutput.prettyPrint(toJson(item)).replace('\\n', System.getProperty('line.separator'))
Jakub Josefb41c8d52017-03-24 13:52:24 +0100137}
138
139/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100140 * Print informational message
141 *
142 * @param msg
143 * @param color Colorful output or not
144 */
145def infoMsg(msg, color = true) {
146 printMsg(msg, "cyan")
147}
148
149/**
Sergey Galkind25fb782019-11-07 14:52:13 +0400150 * Print informational message
151 *
152 * @param msg
153 * @param color Colorful output or not
154 */
155def infoSensitivityMsg(msg, color = true, replacing = []) {
156 printSensitivityMsg(msg, "cyan", replacing)
157}
158
159/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100160 * Print error message
161 *
162 * @param msg
163 * @param color Colorful output or not
164 */
165def errorMsg(msg, color = true) {
166 printMsg(msg, "red")
167}
168
169/**
170 * Print success message
171 *
172 * @param msg
173 * @param color Colorful output or not
174 */
175def successMsg(msg, color = true) {
176 printMsg(msg, "green")
177}
178
179/**
180 * Print warning message
181 *
182 * @param msg
183 * @param color Colorful output or not
184 */
185def warningMsg(msg, color = true) {
Jakub Josef0e7bd632017-03-16 16:25:05 +0100186 printMsg(msg, "yellow")
Jakub Josef79ecec32017-02-17 14:36:28 +0100187}
188
189/**
Jakub Josef952ae0b2017-03-14 19:04:21 +0100190 * Print debug message, this message will show only if DEBUG global variable is present
191 * @param msg
192 * @param color Colorful output or not
193 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300194def debugMsg(msg, color = true) {
Jakub Josef9a836ac2017-04-24 12:26:02 +0200195 // if debug property exists on env, debug is enabled
azvyagintsev6bda9422018-08-20 11:57:05 +0300196 if (env.getEnvironment().containsKey('DEBUG') && env['DEBUG'] == "true") {
Jakub Josef74b34692017-03-15 12:10:57 +0100197 printMsg("[DEBUG] ${msg}", "red")
Jakub Josef952ae0b2017-03-14 19:04:21 +0100198 }
199}
200
azvyagintsevf6e77912018-09-07 15:41:09 +0300201def getColorizedString(msg, color) {
202 def colorMap = [
203 'red' : '\u001B[31m',
204 'black' : '\u001B[30m',
205 'green' : '\u001B[32m',
206 'yellow': '\u001B[33m',
207 'blue' : '\u001B[34m',
208 'purple': '\u001B[35m',
209 'cyan' : '\u001B[36m',
210 'white' : '\u001B[37m',
211 'reset' : '\u001B[0m'
212 ]
Vasyl Saienko00ef98b2018-09-05 10:34:32 +0300213
azvyagintsevf6e77912018-09-07 15:41:09 +0300214 return "${colorMap[color]}${msg}${colorMap.reset}"
Vasyl Saienko00ef98b2018-09-05 10:34:32 +0300215}
216
Jakub Josef952ae0b2017-03-14 19:04:21 +0100217/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100218 * Print message
219 *
azvyagintsev6bda9422018-08-20 11:57:05 +0300220 * @param msg Message to be printed
Vasyl Saienko00ef98b2018-09-05 10:34:32 +0300221 * @param color Color to use for output
Jakub Josef79ecec32017-02-17 14:36:28 +0100222 */
Vasyl Saienko00ef98b2018-09-05 10:34:32 +0300223def printMsg(msg, color) {
224 print getColorizedString(msg, color)
Jakub Josef79ecec32017-02-17 14:36:28 +0100225}
226
227/**
Sergey Galkind25fb782019-11-07 14:52:13 +0400228 * Print sensitivity message
229 *
230 * @param msg Message to be printed
231 * @param color Color to use for output
232 * @param replacing List with maps for deletion (passwords, logins, etc).
233 * The first () matching is mandatory !
234 * Example:
235 * [/ (OS_PASSWORD=)(.*?)+ /,
236 * / (password = )(.*?)+ /,
237 * / (password )(.*?) / ]
238 */
239def printSensitivityMsg(msg, color, replacing = []) {
240 for (i in replacing) {
241 msg = msg.replaceAll(i, ' $1XXXXXX ')
242 }
243 printMsg(msg, color)
244}
245
246/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100247 * Traverse directory structure and return list of files
248 *
249 * @param path Path to search
250 * @param type Type of files to search (groovy.io.FileType.FILES)
251 */
252@NonCPS
azvyagintsev6bda9422018-08-20 11:57:05 +0300253def getFiles(path, type = groovy.io.FileType.FILES) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100254 files = []
255 new File(path).eachFile(type) {
256 files[] = it
257 }
258 return files
259}
260
261/**
262 * Helper method to convert map into form of list of [key,value] to avoid
263 * unserializable exceptions
264 *
265 * @param m Map
266 */
267@NonCPS
268def entries(m) {
azvyagintsev6bda9422018-08-20 11:57:05 +0300269 m.collect { k, v -> [k, v] }
Jakub Josef79ecec32017-02-17 14:36:28 +0100270}
271
272/**
273 * Opposite of build-in parallel, run map of steps in serial
274 *
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200275 * @param steps Map of String<name>: CPSClosure2<step> (or list of closures)
Jakub Josef79ecec32017-02-17 14:36:28 +0100276 */
277def serial(steps) {
278 stepsArray = entries(steps)
azvyagintsev6bda9422018-08-20 11:57:05 +0300279 for (i = 0; i < stepsArray.size; i++) {
Jakub Josefd31de302017-05-15 13:59:18 +0200280 def step = stepsArray[i]
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200281 def dummySteps = [:]
282 def stepKey
azvyagintsev6bda9422018-08-20 11:57:05 +0300283 if (step[1] instanceof List || step[1] instanceof Map) {
284 for (j = 0; j < step[1].size(); j++) {
285 if (step[1] instanceof List) {
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200286 stepKey = j
azvyagintsev6bda9422018-08-20 11:57:05 +0300287 } else if (step[1] instanceof Map) {
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200288 stepKey = step[1].keySet()[j]
289 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300290 dummySteps.put("step-${step[0]}-${stepKey}", step[1][stepKey])
Jakub Josefd31de302017-05-15 13:59:18 +0200291 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300292 } else {
Jakub Josefd31de302017-05-15 13:59:18 +0200293 dummySteps.put(step[0], step[1])
294 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100295 parallel dummySteps
296 }
297}
298
299/**
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200300 * Partition given list to list of small lists
301 * @param inputList input list
302 * @param partitionSize (partition size, optional, default 5)
303 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300304def partitionList(inputList, partitionSize = 5) {
305 List<List<String>> partitions = new ArrayList<>();
306 for (int i = 0; i < inputList.size(); i += partitionSize) {
307 partitions.add(new ArrayList<String>(inputList.subList(i, Math.min(i + partitionSize, inputList.size()))));
308 }
309 return partitions
Jakub Josef7fb8bbd2017-05-15 16:02:44 +0200310}
311
312/**
Jakub Josef79ecec32017-02-17 14:36:28 +0100313 * Get password credentials from store
314 *
azvyagintsev6bda9422018-08-20 11:57:05 +0300315 * @param id Credentials name
Jakub Josef79ecec32017-02-17 14:36:28 +0100316 */
317def getPasswordCredentials(id) {
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100318 return getCredentialsById(id, 'password')
Jakub Josef79ecec32017-02-17 14:36:28 +0100319}
320
321/**
322 * Get SSH credentials from store
323 *
azvyagintsev6bda9422018-08-20 11:57:05 +0300324 * @param id Credentials name
Jakub Josef79ecec32017-02-17 14:36:28 +0100325 */
326def getSshCredentials(id) {
Alexander Evseevbc1fea42017-12-13 10:03:03 +0100327 return getCredentialsById(id, 'sshKey')
Jakub Josef79ecec32017-02-17 14:36:28 +0100328}
Jakub Josef79ecec32017-02-17 14:36:28 +0100329
330/**
331 * Tests Jenkins instance for existence of plugin with given name
332 * @param pluginName plugin short name to test
333 * @return boolean result
334 */
335@NonCPS
azvyagintsev6bda9422018-08-20 11:57:05 +0300336def jenkinsHasPlugin(pluginName) {
337 return Jenkins.instance.pluginManager.plugins.collect { p -> p.shortName }.contains(pluginName)
Jakub Josef79ecec32017-02-17 14:36:28 +0100338}
339
340@NonCPS
341def _needNotification(notificatedTypes, buildStatus, jobName) {
azvyagintsev6bda9422018-08-20 11:57:05 +0300342 if (notificatedTypes && notificatedTypes.contains("onchange")) {
343 if (jobName) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100344 def job = Jenkins.instance.getItem(jobName)
345 def numbuilds = job.builds.size()
azvyagintsev6bda9422018-08-20 11:57:05 +0300346 if (numbuilds > 0) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100347 //actual build is first for some reasons, so last finished build is second
348 def lastBuild = job.builds[1]
azvyagintsev6bda9422018-08-20 11:57:05 +0300349 if (lastBuild) {
350 if (lastBuild.result.toString().toLowerCase().equals(buildStatus)) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100351 println("Build status didn't changed since last build, not sending notifications")
352 return false;
353 }
354 }
355 }
356 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300357 } else if (!notificatedTypes.contains(buildStatus)) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100358 return false;
359 }
360 return true;
361}
362
363/**
364 * Send notification to all enabled notifications services
365 * @param buildStatus message type (success, warning, error), null means SUCCESSFUL
366 * @param msgText message text
367 * @param enabledNotifications list of enabled notification types, types: slack, hipchat, email, default empty
368 * @param notificatedTypes types of notifications will be sent, default onchange - notificate if current build result not equal last result;
369 * otherwise use - ["success","unstable","failed"]
370 * @param jobName optional job name param, if empty env.JOB_NAME will be used
Jakub Josefd0571152017-07-17 14:11:39 +0200371 * @param buildNumber build number param, if empty env.BUILD_NUM will be used
372 * @param buildUrl build url param, if empty env.BUILD_URL will be used
Jakub Josef79ecec32017-02-17 14:36:28 +0100373 * @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 +0200374 * @param mailTo mail TO param, it's mandatory for sending email notifications, this option enable mail notification
Jakub Josef79ecec32017-02-17 14:36:28 +0100375 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300376def sendNotification(buildStatus, msgText = "", enabledNotifications = [], notificatedTypes = ["onchange"], jobName = null, buildNumber = null, buildUrl = null, mailFrom = "jenkins", mailTo = null) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100377 // Default values
378 def colorName = 'blue'
379 def colorCode = '#0000FF'
380 def buildStatusParam = buildStatus != null && buildStatus != "" ? buildStatus : "SUCCESS"
381 def jobNameParam = jobName != null && jobName != "" ? jobName : env.JOB_NAME
382 def buildNumberParam = buildNumber != null && buildNumber != "" ? buildNumber : env.BUILD_NUMBER
383 def buildUrlParam = buildUrl != null && buildUrl != "" ? buildUrl : env.BUILD_URL
384 def subject = "${buildStatusParam}: Job '${jobNameParam} [${buildNumberParam}]'"
385 def summary = "${subject} (${buildUrlParam})"
386
azvyagintsev6bda9422018-08-20 11:57:05 +0300387 if (msgText != null && msgText != "") {
388 summary += "\n${msgText}"
Jakub Josef79ecec32017-02-17 14:36:28 +0100389 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300390 if (buildStatusParam.toLowerCase().equals("success")) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100391 colorCode = "#00FF00"
392 colorName = "green"
azvyagintsev6bda9422018-08-20 11:57:05 +0300393 } else if (buildStatusParam.toLowerCase().equals("unstable")) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100394 colorCode = "#FFFF00"
395 colorName = "yellow"
azvyagintsev6bda9422018-08-20 11:57:05 +0300396 } else if (buildStatusParam.toLowerCase().equals("failure")) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100397 colorCode = "#FF0000"
398 colorName = "red"
399 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300400 if (_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)) {
401 if (enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")) {
402 try {
Jakub Josef79ecec32017-02-17 14:36:28 +0100403 slackSend color: colorCode, message: summary
azvyagintsev6bda9422018-08-20 11:57:05 +0300404 } catch (Exception e) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100405 println("Calling slack plugin failed")
406 e.printStackTrace()
407 }
408 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300409 if (enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")) {
410 try {
Jakub Josef79ecec32017-02-17 14:36:28 +0100411 hipchatSend color: colorName.toUpperCase(), message: summary
azvyagintsev6bda9422018-08-20 11:57:05 +0300412 } catch (Exception e) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100413 println("Calling hipchat plugin failed")
414 e.printStackTrace()
415 }
416 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300417 if (enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != "") {
418 try {
Jakub Josef79ecec32017-02-17 14:36:28 +0100419 mail body: summary, from: mailFrom, subject: subject, to: mailTo
azvyagintsev6bda9422018-08-20 11:57:05 +0300420 } catch (Exception e) {
Jakub Josef79ecec32017-02-17 14:36:28 +0100421 println("Sending mail plugin failed")
422 e.printStackTrace()
423 }
424 }
425 }
Filip Pytloun49d66302017-03-06 10:26:22 +0100426}
chnyda4e5ac792017-03-14 15:24:18 +0100427
428/**
429 * Execute linux command and catch nth element
430 * @param cmd command to execute
431 * @param index index to retrieve
432 * @return index-th element
433 */
434
azvyagintsev6bda9422018-08-20 11:57:05 +0300435def cutOrDie(cmd, index) {
chnyda4e5ac792017-03-14 15:24:18 +0100436 def common = new com.mirantis.mk.Common()
437 def output
438 try {
azvyagintsev6bda9422018-08-20 11:57:05 +0300439 output = sh(script: cmd, returnStdout: true)
440 def result = output.tokenize(" ")[index]
441 return result;
chnyda4e5ac792017-03-14 15:24:18 +0100442 } catch (Exception e) {
azvyagintsev6bda9422018-08-20 11:57:05 +0300443 common.errorMsg("Failed to execute cmd: ${cmd}\n output: ${output}")
chnyda4e5ac792017-03-14 15:24:18 +0100444 }
Filip Pytloun81c864d2017-03-21 15:19:30 +0100445}
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100446
447/**
448 * Check variable contains keyword
449 * @param variable keywork is searched (contains) here
450 * @param keyword string to look for
451 * @return True if variable contains keyword (case insensitive), False if do not contains or any of input isn't a string
452 */
453
454def checkContains(variable, keyword) {
azvyagintsev6bda9422018-08-20 11:57:05 +0300455 if (env.getEnvironment().containsKey(variable)) {
Jakub Josef7a8dea22017-03-23 19:51:32 +0100456 return env[variable] && env[variable].toLowerCase().contains(keyword.toLowerCase())
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100457 } else {
Tomáš Kukrálc76c1e02017-03-23 19:06:59 +0100458 return false
Tomáš Kukrál767dd732017-03-23 10:38:59 +0100459 }
460}
Jakub Josefa877db52017-04-05 14:22:30 +0200461
462/**
463 * Parse JSON string to hashmap
464 * @param jsonString input JSON string
465 * @return created hashmap
466 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300467def parseJSON(jsonString) {
468 def m = [:]
469 def lazyMap = new JsonSlurperClassic().parseText(jsonString)
470 m.putAll(lazyMap)
471 return m
Jakub Josefa877db52017-04-05 14:22:30 +0200472}
Jakub Josefed239cd2017-05-09 15:27:33 +0200473
474/**
Vasyl Saienko79d6f3b2018-10-19 09:13:46 +0300475 *
476 * Deep merge of Map items. Merges variable number of maps in to onto.
477 * Using the following rules:
478 * - Lists are appended
479 * - Maps are updated
480 * - other object types are replaced.
481 *
482 *
483 * @param onto Map object to merge in
484 * @param overrides Map objects to merge to onto
485*/
486def mergeMaps(Map onto, Map... overrides){
487 if (!overrides){
488 return onto
489 }
490 else if (overrides.length == 1) {
491 overrides[0]?.each { k, v ->
492 if (v in Map && onto[k] in Map){
493 mergeMaps((Map) onto[k], (Map) v)
494 } else if (v in List) {
495 onto[k] += v
496 } else {
497 onto[k] = v
498 }
499 }
500 return onto
501 }
502 return overrides.inject(onto, { acc, override -> mergeMaps(acc, override ?: [:]) })
503}
504
505/**
Jakub Josefed239cd2017-05-09 15:27:33 +0200506 * Test pipeline input parameter existence and validity (not null and not empty string)
507 * @param paramName input parameter name (usually uppercase)
azvyagintsevc8ecdfd2018-09-11 12:47:15 +0300508 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300509def validInputParam(paramName) {
azvyagintsevc8ecdfd2018-09-11 12:47:15 +0300510 if (paramName instanceof java.lang.String) {
511 return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
512 }
513 return false
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200514}
515
516/**
517 * Take list of hashmaps and count number of hashmaps with parameter equals eq
518 * @param lm list of hashmaps
519 * @param param define parameter of hashmap to read and compare
520 * @param eq desired value of hashmap parameter
521 * @return count of hashmaps meeting defined condition
522 */
523
524@NonCPS
525def countHashMapEquals(lm, param, eq) {
azvyagintsev6bda9422018-08-20 11:57:05 +0300526 return lm.stream().filter { i -> i[param].equals(eq) }.collect(java.util.stream.Collectors.counting())
Tomáš Kukráld34fe872017-06-13 10:50:50 +0200527}
Vasyl Saienkofb055b12017-11-23 18:15:23 +0200528
529/**
530 * Execute shell command and return stdout, stderr and status
531 *
532 * @param cmd Command to execute
533 * @return map with stdout, stderr, status keys
534 */
535
536def shCmdStatus(cmd) {
azvyagintsevb8b7f922019-06-13 13:39:04 +0300537 // Set +x , to hide odd messages about temp file manipulations
Vasyl Saienkofb055b12017-11-23 18:15:23 +0200538 def res = [:]
azvyagintsevb8b7f922019-06-13 13:39:04 +0300539 def stderr = sh(script: 'set +x ; mktemp', returnStdout: true).trim()
540 def stdout = sh(script: 'set +x ; mktemp', returnStdout: true).trim()
Vasyl Saienkofb055b12017-11-23 18:15:23 +0200541
542 try {
azvyagintsev6bda9422018-08-20 11:57:05 +0300543 def status = sh(script: "${cmd} 1>${stdout} 2>${stderr}", returnStatus: true)
azvyagintsevb8b7f922019-06-13 13:39:04 +0300544 res['stderr'] = sh(script: "set +x; cat ${stderr}", returnStdout: true).trim()
545 res['stdout'] = sh(script: "set +x; cat ${stdout}", returnStdout: true).trim()
Vasyl Saienkofb055b12017-11-23 18:15:23 +0200546 res['status'] = status
547 } finally {
azvyagintsevb8b7f922019-06-13 13:39:04 +0300548 sh(script: "set +x; rm ${stderr}")
549 sh(script: "set +x; rm ${stdout}")
Vasyl Saienkofb055b12017-11-23 18:15:23 +0200550 }
551
552 return res
553}
Richard Felkl66a242d2018-01-25 15:27:15 +0100554
Richard Felkl66a242d2018-01-25 15:27:15 +0100555/**
556 * Retry commands passed to body
557 *
Martin Polreich759d0172019-02-08 10:16:52 +0100558 * Don't use common.retry method for retrying salt.enforceState method. Use retries parameter
559 * built-in the salt.enforceState method instead to ensure correct functionality.
560 *
Richard Felkl66a242d2018-01-25 15:27:15 +0100561 * @param times Number of retries
Jakub Josef61463c72018-02-13 16:10:56 +0100562 * @param delay Delay between retries (in seconds)
Richard Felkl66a242d2018-01-25 15:27:15 +0100563 * @param body Commands to be in retry block
564 * @return calling commands in body
azvyagintsev6bda9422018-08-20 11:57:05 +0300565 * @example retry ( 3 , 5 ) { function body }* retry{ function body }
Richard Felkl66a242d2018-01-25 15:27:15 +0100566 */
567
568def retry(int times = 5, int delay = 0, Closure body) {
569 int retries = 0
azvyagintsev6bda9422018-08-20 11:57:05 +0300570 while (retries++ < times) {
Richard Felkl66a242d2018-01-25 15:27:15 +0100571 try {
572 return body.call()
azvyagintsev6bda9422018-08-20 11:57:05 +0300573 } catch (e) {
Denis Egorenko14e39d62019-01-14 12:54:56 +0400574 errorMsg(e.toString())
Richard Felkl66a242d2018-01-25 15:27:15 +0100575 sleep(delay)
576 }
577 }
Richard Felkl66a242d2018-01-25 15:27:15 +0100578 throw new Exception("Failed after $times retries")
579}
Dmitry Pyzhov0d0d8522018-05-15 22:37:37 +0300580
Dmitry Pyzhov0d0d8522018-05-15 22:37:37 +0300581/**
582 * Wait for user input with timeout
583 *
584 * @param timeoutInSeconds Timeout
585 * @param options Options for input widget
586 */
azvyagintsev6bda9422018-08-20 11:57:05 +0300587def waitForInputThenPass(timeoutInSeconds, options = [message: 'Ready to go?']) {
588 def userInput = true
589 try {
590 timeout(time: timeoutInSeconds, unit: 'SECONDS') {
591 userInput = input options
592 }
593 } catch (err) { // timeout reached or input false
594 def user = err.getCauses()[0].getUser()
595 if ('SYSTEM' == user.toString()) { // SYSTEM means timeout.
596 println("Timeout, proceeding")
597 } else {
598 userInput = false
599 println("Aborted by: [${user}]")
600 throw err
601 }
Dmitry Pyzhov0d0d8522018-05-15 22:37:37 +0300602 }
azvyagintsev6bda9422018-08-20 11:57:05 +0300603 return userInput
Dmitry Pyzhov0d0d8522018-05-15 22:37:37 +0300604}
Oleksii Grudev9e1d97a2018-06-29 16:04:30 +0300605
606/**
607 * Function receives Map variable as input and sorts it
608 * by values ascending. Returns sorted Map
609 * @param _map Map variable
610 */
611@NonCPS
612def SortMapByValueAsc(_map) {
azvyagintsev6bda9422018-08-20 11:57:05 +0300613 def sortedMap = _map.sort { it.value }
Oleksii Grudev9e1d97a2018-06-29 16:04:30 +0300614 return sortedMap
615}
azvyagintsev99d35842018-08-17 20:26:34 +0300616
617/**
618 * Compare 'old' and 'new' dir's recursively
619 * @param diffData =' Only in new/XXX/infra: secrets.yml
620 Files old/XXX/init.yml and new/XXX/init.yml differ
621 Only in old/XXX/infra: secrets11.yml '
622 *
623 * @return
624 * - new:
625 - XXX/secrets.yml
626 - diff:
627 - XXX/init.yml
628 - removed:
629 - XXX/secrets11.yml
630
631 */
632def diffCheckMultidir(diffData) {
azvyagintsevab5637b2018-08-20 18:18:15 +0300633 common = new com.mirantis.mk.Common()
634 // Some global constants. Don't change\move them!
635 keyNew = 'new'
636 keyRemoved = 'removed'
637 keyDiff = 'diff'
638 def output = [
639 new : [],
640 removed: [],
641 diff : [],
642 ]
643 String pathSep = '/'
644 diffData.each { line ->
645 def job_file = ''
646 def job_type = ''
647 if (line.startsWith('Files old/')) {
648 job_file = new File(line.replace('Files old/', '').tokenize()[0])
649 job_type = keyDiff
650 } else if (line.startsWith('Only in new/')) {
651 // get clean normalized filepath, under new/
652 job_file = new File(line.replace('Only in new/', '').replace(': ', pathSep)).toString()
653 job_type = keyNew
654 } else if (line.startsWith('Only in old/')) {
655 // get clean normalized filepath, under old/
656 job_file = new File(line.replace('Only in old/', '').replace(': ', pathSep)).toString()
657 job_type = keyRemoved
658 } else {
659 common.warningMsg("Not parsed diff line: ${line}!")
660 }
661 if (job_file != '') {
662 output[job_type].push(job_file)
663 }
azvyagintsev99d35842018-08-17 20:26:34 +0300664 }
azvyagintsevab5637b2018-08-20 18:18:15 +0300665 return output
azvyagintsev99d35842018-08-17 20:26:34 +0300666}
667
668/**
669 * Compare 2 folder, file by file
670 * Structure should be:
671 * ${compRoot}/
672 └── diff - diff results will be save here
673 ├── new - input folder with data
674 ├── old - input folder with data
675 ├── pillar.diff - globall diff will be saved here
676 * b_url - usual env.BUILD_URL, to be add into description
azvyagintsev3bbcafe2018-08-20 19:36:16 +0300677 * grepOpts - General grep cmdline; Could be used to pass some magic
678 * regexp into after-diff listing file(pillar.diff)
679 * Example: '-Ev infra/secrets.yml'
azvyagintsev99d35842018-08-17 20:26:34 +0300680 * return - html-based string
681 * TODO: allow to specify subdir for results?
azvyagintsev99d35842018-08-17 20:26:34 +0300682 **/
Denis Egorenko4551f372018-09-11 16:36:13 +0400683
azvyagintsev3bbcafe2018-08-20 19:36:16 +0300684def comparePillars(compRoot, b_url, grepOpts) {
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000685
azvyagintsevab5637b2018-08-20 18:18:15 +0300686 // Some global constants. Don't change\move them!
687 keyNew = 'new'
688 keyRemoved = 'removed'
689 keyDiff = 'diff'
690 def diff_status = 0
691 // FIXME
692 httpWS = b_url + '/artifact/'
693 dir(compRoot) {
azvyagintsev3bbcafe2018-08-20 19:36:16 +0300694 // If diff empty - exit 0
695 diff_status = sh(script: 'diff -q -r old/ new/ > pillar.diff',
azvyagintsevab5637b2018-08-20 18:18:15 +0300696 returnStatus: true,
697 )
azvyagintsev24d49652018-08-21 19:33:51 +0300698 }
azvyagintsevc0fe1442018-08-21 20:01:34 +0300699 // Unfortunately, diff not able to work with dir-based regexp
700 if (diff_status == 1 && grepOpts) {
701 dir(compRoot) {
702 grep_status = sh(script: """
azvyagintsev3bbcafe2018-08-20 19:36:16 +0300703 cp -v pillar.diff pillar_orig.diff
704 grep ${grepOpts} pillar_orig.diff > pillar.diff
705 """,
azvyagintsevc0fe1442018-08-21 20:01:34 +0300706 returnStatus: true
707 )
azvyagintsevf6e77912018-09-07 15:41:09 +0300708 if (grep_status == 1) {
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000709 warningMsg("Grep regexp ${grepOpts} removed all diff!")
azvyagintsevc0fe1442018-08-21 20:01:34 +0300710 diff_status = 0
azvyagintsevb99f87c2018-08-21 19:43:59 +0300711 }
azvyagintsev3bbcafe2018-08-20 19:36:16 +0300712 }
azvyagintsevc0fe1442018-08-21 20:01:34 +0300713 }
714 // Set job description
Denis Egorenko4551f372018-09-11 16:36:13 +0400715 description = ''
azvyagintsevc0fe1442018-08-21 20:01:34 +0300716 if (diff_status == 1) {
azvyagintsevab5637b2018-08-20 18:18:15 +0300717 // Analyse output file and prepare array with results
718 String data_ = readFile file: "${compRoot}/pillar.diff"
719 def diff_list = diffCheckMultidir(data_.split("\\r?\\n"))
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000720 infoMsg(diff_list)
azvyagintsevab5637b2018-08-20 18:18:15 +0300721 dir(compRoot) {
722 if (diff_list[keyDiff].size() > 0) {
723 if (!fileExists('diff')) {
724 sh('mkdir -p diff')
725 }
726 description += '<b>CHANGED</b><ul>'
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000727 infoMsg('Changed items:')
Denis Egorenko4551f372018-09-11 16:36:13 +0400728 def stepsForParallel = [:]
729 stepsForParallel.failFast = true
730 diff_list[keyDiff].each {
731 stepsForParallel.put("Differ for:${it}",
732 {
733 // We don't want to handle sub-dirs structure. So, simply make diff 'flat'
734 def item_f = it.toString().replace('/', '_')
735 description += "<li><a href=\"${httpWS}/diff/${item_f}/*view*/\">${it}</a></li>"
736 // Generate diff file
737 def diff_exit_code = sh([
738 script : "diff -U 50 old/${it} new/${it} > diff/${item_f}",
739 returnStdout: false,
740 returnStatus: true,
741 ])
742 // catch normal errors, diff should always return 1
743 if (diff_exit_code != 1) {
744 error 'Error with diff file generation'
745 }
746 })
azvyagintsevab5637b2018-08-20 18:18:15 +0300747 }
Denis Egorenko4551f372018-09-11 16:36:13 +0400748
749 parallel stepsForParallel
azvyagintsevab5637b2018-08-20 18:18:15 +0300750 }
751 if (diff_list[keyNew].size() > 0) {
752 description += '<b>ADDED</b><ul>'
753 for (item in diff_list[keyNew]) {
754 description += "<li><a href=\"${httpWS}/new/${item}/*view*/\">${item}</a></li>"
755 }
756 }
757 if (diff_list[keyRemoved].size() > 0) {
758 description += '<b>DELETED</b><ul>'
759 for (item in diff_list[keyRemoved]) {
760 description += "<li><a href=\"${httpWS}/old/${item}/*view*/\">${item}</a></li>"
761 }
762 }
azvyagintsev99d35842018-08-17 20:26:34 +0300763
azvyagintsevab5637b2018-08-20 18:18:15 +0300764 }
azvyagintsev99d35842018-08-17 20:26:34 +0300765 }
azvyagintsevab5637b2018-08-20 18:18:15 +0300766
767 if (description != '') {
768 dir(compRoot) {
769 archiveArtifacts([
770 artifacts : '**',
771 allowEmptyArchive: true,
772 ])
773 }
774 return description.toString()
775 } else {
azvyagintseva57c82a2018-09-20 12:17:24 +0300776 return '<b>No job changes</b>'
azvyagintsevab5637b2018-08-20 18:18:15 +0300777 }
azvyagintsev99d35842018-08-17 20:26:34 +0300778}
azvyagintsevab5637b2018-08-20 18:18:15 +0300779
780/**
781 * Simple function, to get basename from string.
782 * line - path-string
783 * remove_ext - string, optionl. Drop file extenstion.
784 **/
785def GetBaseName(line, remove_ext) {
786 filename = line.toString().split('/').last()
787 if (remove_ext && filename.endsWith(remove_ext.toString())) {
788 filename = filename.take(filename.lastIndexOf(remove_ext.toString()))
789 }
790 return filename
Vasyl Saienko00ef98b2018-09-05 10:34:32 +0300791}
Vasyl Saienko723cbbc2018-09-05 11:08:52 +0300792
793/**
azvyagintsevf6e77912018-09-07 15:41:09 +0300794 * Return colored string of specific stage in stageMap
795 *
796 * @param stageMap LinkedHashMap object.
797 * @param stageName The name of current stage we are going to execute.
798 * @param color Text color
799 * */
800def getColoredStageView(stageMap, stageName, color) {
801 def stage = stageMap[stageName]
802 def banner = []
803 def currentStageIndex = new ArrayList<String>(stageMap.keySet()).indexOf(stageName)
804 def numberOfStages = stageMap.keySet().size() - 1
Vasyl Saienko723cbbc2018-09-05 11:08:52 +0300805
Vasyl Saienko723cbbc2018-09-05 11:08:52 +0300806 banner.add(getColorizedString(
azvyagintsevf6e77912018-09-07 15:41:09 +0300807 "=========== Stage ${currentStageIndex}/${numberOfStages}: ${stageName} ===========", color))
808 for (stage_item in stage.keySet()) {
809 banner.add(getColorizedString(
810 "${stage_item}: ${stage[stage_item]}", color))
Vasyl Saienko723cbbc2018-09-05 11:08:52 +0300811 }
azvyagintsevf6e77912018-09-07 15:41:09 +0300812 banner.add('\n')
813
814 return banner
815}
816
817/**
818 * Pring stageMap to console with specified color
819 *
820 * @param stageMap LinkedHashMap object with stages information.
821 * @param currentStage The name of current stage we are going to execute.
822 *
823 * */
824def printCurrentStage(stageMap, currentStage) {
825 print getColoredStageView(stageMap, currentStage, "cyan").join('\n')
826}
827
828/**
829 * Pring stageMap to console with specified color
830 *
831 * @param stageMap LinkedHashMap object.
832 * @param baseColor Text color (default white)
833 * */
834def printStageMap(stageMap, baseColor = "white") {
835 def banner = []
836 def index = 0
837 for (stage_name in stageMap.keySet()) {
838 banner.addAll(getColoredStageView(stageMap, stage_name, baseColor))
839 }
840 print banner.join('\n')
841}
842
843/**
844 * Wrap provided code in stage, and do interactive retires if needed.
845 *
846 * @param stageMap LinkedHashMap object with stages information.
847 * @param currentStage The name of current stage we are going to execute.
848 * @param target Target host to execute stage on.
849 * @param interactive Boolean flag to specify if interaction with user is enabled.
850 * @param body Command to be in stage block.
851 * */
852def stageWrapper(stageMap, currentStage, target, interactive = true, Closure body) {
853 def common = new com.mirantis.mk.Common()
854 def banner = []
855
856 printCurrentStage(stageMap, currentStage)
857
858 stage(currentStage) {
Vasyl Saienkobc6debb2018-09-10 14:08:09 +0300859 if (interactive){
azvyagintsevf6e77912018-09-07 15:41:09 +0300860 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 +0300861 }
862 try {
Vasyl Saienkobc6debb2018-09-10 14:08:09 +0300863 stageMap[currentStage]['Status'] = "SUCCESS"
Victor Ryzhenkin4a45d732019-01-09 15:28:21 +0400864 return body.call()
Vasyl Saienkobc6debb2018-09-10 14:08:09 +0300865 } catch (Exception err) {
866 def msg = "Stage ${currentStage} failed with the following exception:\n${err}"
867 print getColorizedString(msg, "yellow")
868 common.errorMsg(err)
869 if (interactive) {
870 input message: getColorizedString("Please make sure problem is fixed to proceed with retry. Ready to proceed?", "yellow")
871 stageMap[currentStage]['Status'] = "RETRYING"
872 stageWrapper(stageMap, currentStage, target, interactive, body)
873 } else {
874 error(msg)
azvyagintsevf6e77912018-09-07 15:41:09 +0300875 }
Vasyl Saienkobc6debb2018-09-10 14:08:09 +0300876 }
azvyagintsevf6e77912018-09-07 15:41:09 +0300877 }
878}
879
880/**
881 * Ugly transition solution for internal tests.
882 * 1) Check input => transform to static result, based on runtime and input
883 * 2) Check remote-binary repo for exact resource
884 */
885
886def checkRemoteBinary(LinkedHashMap config, List extraScmExtensions = []) {
887 def common = new com.mirantis.mk.Common()
888 res = [:]
889 res['MirrorRoot'] = config.get('globalMirrorRoot', env["BIN_MIRROR_ROOT"] ? env["BIN_MIRROR_ROOT"] : "http://mirror.mirantis.com/")
890 // Reclass-like format's. To make life eazy!
azvyagintsev603d95b2018-11-09 15:37:10 +0200891 res['mcp_version'] = config.get('mcp_version', env["BIN_APT_MCP_VERSION"] ? env["BIN_APT_MCP_VERSION"] : 'nightly')
892 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']}/")
azvyagintsevf6e77912018-09-07 15:41:09 +0300893
894 if (config.get('verify', true)) {
895 MirrorRootStatus = sh(script: "wget --auth-no-challenge --spider ${res['linux_system_repo_url']} 2>/dev/null", returnStatus: true)
azvyagintsev5141e442018-09-25 17:45:07 +0300896 if (MirrorRootStatus != 0) {
azvyagintsevf6e77912018-09-07 15:41:09 +0300897 common.warningMsg("Resource: ${res['linux_system_repo_url']} not exist")
898 res['linux_system_repo_url'] = false
899 }
900 }
901 return res
Vasyl Saienko723cbbc2018-09-05 11:08:52 +0300902}
Denis Egorenkoeded0d42018-09-26 13:25:49 +0400903
904/**
905 * Workaround to update env properties, like GERRIT_* vars,
906 * which should be passed from upstream job to downstream.
907 * Will not fail entire job in case any issues.
908 * @param envVar - EnvActionImpl env job
909 * @param extraVars - Multiline YAML text with extra vars
910 */
911def mergeEnv(envVar, extraVars) {
Denis Egorenko6d3cc232018-09-27 17:42:58 +0400912 def common = new com.mirantis.mk.Common()
Denis Egorenkoeded0d42018-09-26 13:25:49 +0400913 try {
914 def extraParams = readYaml text: extraVars
915 for(String key in extraParams.keySet()) {
916 envVar[key] = extraParams[key]
917 common.warningMsg("Parameter ${key} is updated from EXTRA vars.")
918 }
919 } catch (Exception e) {
920 common.errorMsg("Can't update env parameteres, because: ${e.toString()}")
921 }
922}
Denis Egorenko599bd632018-09-28 15:24:37 +0400923
924/**
925 * Wrapper around parallel pipeline function
926 * with ability to restrict number of parallel threads
927 * running simultaneously
928 *
929 * @param branches - Map with Clousers to be executed
930 * @param maxParallelJob - Integer number of parallel threads allowed
931 * to run simultaneously
932 */
933def runParallel(branches, maxParallelJob = 10) {
934 def runningSteps = 0
935 branches.each { branchName, branchBody ->
936 if (branchBody instanceof Closure) {
937 branches[branchName] = {
938 while (!(runningSteps < maxParallelJob)) {
939 continue
940 }
941 runningSteps += 1
942 branchBody.call()
943 runningSteps -= 1
944 }
945 }
946 }
947 if (branches) {
948 parallel branches
949 }
950}
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000951
952/**
953 * Ugly processing basic funcs with /etc/apt
Denis Egorenko5cea1412018-10-18 16:40:11 +0400954 * @param repoConfig YAML text or Map
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000955 * Example :
Denis Egorenko5cea1412018-10-18 16:40:11 +0400956 repoConfig = '''
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000957 ---
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000958 aprConfD: |-
Denis Egorenkoe02a1b22018-10-19 17:47:53 +0400959 APT::Get::AllowUnauthenticated 'true';
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000960 repo:
Denis Egorenkoe02a1b22018-10-19 17:47:53 +0400961 mcp_saltstack:
962 source: "deb [arch=amd64] http://mirror.mirantis.com/nightly/saltstack-2017.7/xenial xenial main"
963 pin:
964 - package: "libsodium18"
965 pin: "release o=SaltStack"
966 priority: 50
967 - package: "*"
968 pin: "release o=SaltStack"
969 priority: "1100"
970 repo_key: "http://mirror.mirantis.com/public.gpg"
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000971 '''
972 *
973 */
974
Denis Egorenko5cea1412018-10-18 16:40:11 +0400975def debianExtraRepos(repoConfig) {
976 def config = null
977 if (repoConfig instanceof Map) {
978 config = repoConfig
979 } else {
980 config = readYaml text: repoConfig
981 }
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000982 if (config.get('repo', false)) {
983 for (String repo in config['repo'].keySet()) {
Denis Egorenko395aa212018-10-11 15:11:28 +0400984 source = config['repo'][repo]['source']
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +0000985 warningMsg("Write ${source} > /etc/apt/sources.list.d/${repo}.list")
986 sh("echo '${source}' > /etc/apt/sources.list.d/${repo}.list")
Denis Egorenko395aa212018-10-11 15:11:28 +0400987 if (config['repo'][repo].containsKey('repo_key')) {
988 key = config['repo'][repo]['repo_key']
989 sh("wget -O - '${key}' | apt-key add -")
990 }
Denis Egorenkoe02a1b22018-10-19 17:47:53 +0400991 if (config['repo'][repo]['pin']) {
992 def repoPins = []
993 for (Map pin in config['repo'][repo]['pin']) {
994 repoPins.add("Package: ${pin['package']}")
995 repoPins.add("Pin: ${pin['pin']}")
996 repoPins.add("Pin-Priority: ${pin['priority']}")
Denis Egorenko1c93d122018-11-02 12:14:05 +0400997 // additional empty line between pins
998 repoPins.add('\n')
Denis Egorenkoe02a1b22018-10-19 17:47:53 +0400999 }
1000 if (repoPins) {
1001 repoPins.add(0, "### Extra ${repo} repo pin start ###")
1002 repoPins.add("### Extra ${repo} repo pin end ###")
1003 repoPinning = repoPins.join('\n')
1004 warningMsg("Adding pinning \n${repoPinning}\n => /etc/apt/preferences.d/${repo}")
1005 sh("echo '${repoPinning}' > /etc/apt/preferences.d/${repo}")
1006 }
1007 }
Aleksey Zvyagintsevb20bd262018-10-05 15:09:06 +00001008 }
1009 }
1010 if (config.get('aprConfD', false)) {
1011 for (String pref in config['aprConfD'].tokenize('\n')) {
1012 warningMsg("Adding ${pref} => /etc/apt/apt.conf.d/99setupAndTestNode")
1013 sh("echo '${pref}' >> /etc/apt/apt.conf.d/99setupAndTestNode")
1014 }
1015 sh('cat /etc/apt/apt.conf.d/99setupAndTestNode')
1016 }
1017}