blob: 02fcadaf1f3da0699e30f87543a2dfe1b5b40ae7 [file] [log] [blame]
Jakub Josef79ecec32017-02-17 14:36:28 +01001package com.mirantis.mk
2
3/**
4 *
5 * Common functions
6 *
7 */
8
9/**
10 * Generate current timestamp
11 *
12 * @param format Defaults to yyyyMMddHHmmss
13 */
14def getDatetime(format="yyyyMMddHHmmss") {
15 def now = new Date();
16 return now.format(format, TimeZone.getTimeZone('UTC'));
17}
18
19/**
20 * Parse HEAD of current directory and return commit hash
21 */
22def getGitCommit() {
23 git_commit = sh (
24 script: 'git rev-parse HEAD',
25 returnStdout: true
26 ).trim()
27 return git_commit
28}
29
30/**
31 * Return workspace.
32 * Currently implemented by calling pwd so it won't return relevant result in
33 * dir context
34 */
35def getWorkspace() {
36 def workspace = sh script: 'pwd', returnStdout: true
37 workspace = workspace.trim()
38 return workspace
39}
40
41/**
42 * Get credentials from store
43 *
44 * @param id Credentials name
45 */
46def getCredentials(id) {
47 def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
48 com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
49 jenkins.model.Jenkins.instance
50 )
51
52 for (Iterator<String> credsIter = creds.iterator(); credsIter.hasNext();) {
53 c = credsIter.next();
54 if ( c.id == id ) {
55 return c;
56 }
57 }
58
59 throw new Exception("Could not find credentials for ID ${id}")
60}
61
62/**
63 * Abort build, wait for some time and ensure we will terminate
64 */
65def abortBuild() {
66 currentBuild.build().doStop()
67 sleep(180)
68 // just to be sure we will terminate
69 throw new InterruptedException()
70}
71
72/**
73 * Print informational message
74 *
75 * @param msg
76 * @param color Colorful output or not
77 */
78def infoMsg(msg, color = true) {
79 printMsg(msg, "cyan")
80}
81
82/**
83 * Print error message
84 *
85 * @param msg
86 * @param color Colorful output or not
87 */
88def errorMsg(msg, color = true) {
89 printMsg(msg, "red")
90}
91
92/**
93 * Print success message
94 *
95 * @param msg
96 * @param color Colorful output or not
97 */
98def successMsg(msg, color = true) {
99 printMsg(msg, "green")
100}
101
102/**
103 * Print warning message
104 *
105 * @param msg
106 * @param color Colorful output or not
107 */
108def warningMsg(msg, color = true) {
109 printMsg(msg, "blue")
110}
111
112/**
113 * Print message
114 *
115 * @param msg Message to be printed
116 * @param level Level of message (default INFO)
117 * @param color Color to use for output or false (default)
118 */
119def printMsg(msg, color = false) {
120 colors = [
121 'red' : '\u001B[31m',
122 'black' : '\u001B[30m',
123 'green' : '\u001B[32m',
124 'yellow': '\u001B[33m',
125 'blue' : '\u001B[34m',
126 'purple': '\u001B[35m',
127 'cyan' : '\u001B[36m',
128 'white' : '\u001B[37m',
129 'reset' : '\u001B[0m'
130 ]
131 if (color != false) {
132 wrap([$class: 'AnsiColorBuildWrapper']) {
133 print "${colors[color]}${msg}${colors.reset}"
134 }
135 } else {
136 print "[${level}] ${msg}"
137 }
138}
139
140/**
141 * Traverse directory structure and return list of files
142 *
143 * @param path Path to search
144 * @param type Type of files to search (groovy.io.FileType.FILES)
145 */
146@NonCPS
147def getFiles(path, type=groovy.io.FileType.FILES) {
148 files = []
149 new File(path).eachFile(type) {
150 files[] = it
151 }
152 return files
153}
154
155/**
156 * Helper method to convert map into form of list of [key,value] to avoid
157 * unserializable exceptions
158 *
159 * @param m Map
160 */
161@NonCPS
162def entries(m) {
163 m.collect {k, v -> [k, v]}
164}
165
166/**
167 * Opposite of build-in parallel, run map of steps in serial
168 *
169 * @param steps Map of String<name>: CPSClosure2<step>
170 */
171def serial(steps) {
172 stepsArray = entries(steps)
173 for (i=0; i < stepsArray.size; i++) {
174 s = stepsArray[i]
175 dummySteps = ["${s[0]}": s[1]]
176 parallel dummySteps
177 }
178}
179
180/**
181 * Get password credentials from store
182 *
183 * @param id Credentials name
184 */
185def getPasswordCredentials(id) {
186 def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
187 com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
188 jenkins.model.Jenkins.instance
189 )
190
191 for (Iterator<String> credsIter = creds.iterator(); credsIter.hasNext();) {
192 c = credsIter.next();
193 if ( c.id == id ) {
194 return c;
195 }
196 }
197
198 throw new Exception("Could not find credentials for ID ${id}")
199}
200
201/**
202 * Get SSH credentials from store
203 *
204 * @param id Credentials name
205 */
206def getSshCredentials(id) {
207 def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
208 com.cloudbees.plugins.credentials.common.StandardUsernameCredentials.class,
209 jenkins.model.Jenkins.instance
210 )
211
212 for (Iterator<String> credsIter = creds.iterator(); credsIter.hasNext();) {
213 c = credsIter.next();
214 if ( c.id == id ) {
215 return c;
216 }
217 }
218
219 throw new Exception("Could not find credentials for ID ${id}")
220}
221/**
222 * Setup ssh agent and add private key
223 *
224 * @param credentialsId Jenkins credentials name to lookup private key
225 */
226def prepareSshAgentKey(credentialsId) {
227 c = getSshCredentials(credentialsId)
228 sh("test -d ~/.ssh || mkdir -m 700 ~/.ssh")
229 sh('pgrep -l -u $USER -f | grep -e ssh-agent\$ >/dev/null || ssh-agent|grep -v "Agent pid" > ~/.ssh/ssh-agent.sh')
230 sh("set +x; echo '${c.getPrivateKey()}' > ~/.ssh/id_rsa_${credentialsId} && chmod 600 ~/.ssh/id_rsa_${credentialsId}; set -x")
231 agentSh("ssh-add ~/.ssh/id_rsa_${credentialsId}")
232}
233
234/**
235 * Execute command with ssh-agent
236 *
237 * @param cmd Command to execute
238 */
239def agentSh(cmd) {
240 sh(". ~/.ssh/ssh-agent.sh && ${cmd}")
241}
242
243/**
244 * Ensure entry in SSH known hosts
245 *
246 * @param url url of remote host
247 */
248def ensureKnownHosts(url) {
249 def hostArray = getKnownHost(url)
250 sh "test -f ~/.ssh/known_hosts && grep ${hostArray[0]} ~/.ssh/known_hosts || ssh-keyscan -p ${hostArray[1]} ${hostArray[0]} >> ~/.ssh/known_hosts"
251}
252
253@NonCPS
254def getKnownHost(url){
255 // test for git@github.com:organization/repository like URLs
256 def p = ~/.+@(.+\..+)\:{1}.*/
257 def result = p.matcher(url)
258 def host = ""
259 if (result.matches()) {
260 host = result.group(1)
261 port = 22
262 } else {
263 parsed = new URI(url)
264 host = parsed.host
265 port = parsed.port && parsed.port > 0 ? parsed.port: 22
266 }
267 return [host,port]
268}
269
270/**
271 * Mirror git repository, merge target changes (downstream) on top of source
272 * (upstream) and push target or both if pushSource is true
273 *
274 * @param sourceUrl Source git repository
275 * @param targetUrl Target git repository
276 * @param credentialsId Credentials id to use for accessing source/target
277 * repositories
278 * @param branches List or comma-separated string of branches to sync
279 * @param followTags Mirror tags
280 * @param pushSource Push back into source branch, resulting in 2-way sync
281 * @param pushSourceTags Push target tags into source or skip pushing tags
282 * @param gitEmail Email for creation of merge commits
283 * @param gitName Name for creation of merge commits
284 */
285def mirrorGit(sourceUrl, targetUrl, credentialsId, branches, followTags = false, pushSource = false, pushSourceTags = false, gitEmail = 'jenkins@localhost', gitName = 'Jenkins') {
286 if (branches instanceof String) {
287 branches = branches.tokenize(',')
288 }
289
290 prepareSshAgentKey(credentialsId)
291 ensureKnownHosts(targetUrl)
292 sh "git config user.email '${gitEmail}'"
293 sh "git config user.name '${gitName}'"
294
295 sh "git remote | grep target || git remote add target ${TARGET_URL}"
296 agentSh "git remote update --prune"
297
298 for (i=0; i < branches.size; i++) {
299 branch = branches[i]
300 sh "git branch | grep ${branch} || git checkout -b ${branch} origin/${branch}"
301 sh "git branch | grep ${branch} && git checkout ${branch} && git reset --hard origin/${branch}"
302
303 sh "git ls-tree target/${branch} && git merge --no-edit --ff target/${branch} || echo 'Target repository is empty, skipping merge'"
304 followTagsArg = followTags ? "--follow-tags" : ""
305 agentSh "git push ${followTagsArg} target HEAD:${branch}"
306
307 if (pushSource == true) {
308 followTagsArg = followTags && pushSourceTags ? "--follow-tags" : ""
309 agentSh "git push ${followTagsArg} origin HEAD:${branch}"
310 }
311 }
312
313 if (followTags == true) {
314 agentSh "git push target --tags"
315
316 if (pushSourceTags == true) {
317 agentSh "git push origin --tags"
318 }
319 }
320}
321
322/**
323 * Tests Jenkins instance for existence of plugin with given name
324 * @param pluginName plugin short name to test
325 * @return boolean result
326 */
327@NonCPS
328def jenkinsHasPlugin(pluginName){
329 return Jenkins.instance.pluginManager.plugins.collect{p -> p.shortName}.contains(pluginName)
330}
331
332@NonCPS
333def _needNotification(notificatedTypes, buildStatus, jobName) {
334 if(notificatedTypes && notificatedTypes.contains("onchange")){
335 if(jobName){
336 def job = Jenkins.instance.getItem(jobName)
337 def numbuilds = job.builds.size()
338 if (numbuilds > 0){
339 //actual build is first for some reasons, so last finished build is second
340 def lastBuild = job.builds[1]
341 if(lastBuild){
342 if(lastBuild.result.toString().toLowerCase().equals(buildStatus)){
343 println("Build status didn't changed since last build, not sending notifications")
344 return false;
345 }
346 }
347 }
348 }
349 }else if(!notificatedTypes.contains(buildStatus)){
350 return false;
351 }
352 return true;
353}
354
355/**
356 * Send notification to all enabled notifications services
357 * @param buildStatus message type (success, warning, error), null means SUCCESSFUL
358 * @param msgText message text
359 * @param enabledNotifications list of enabled notification types, types: slack, hipchat, email, default empty
360 * @param notificatedTypes types of notifications will be sent, default onchange - notificate if current build result not equal last result;
361 * otherwise use - ["success","unstable","failed"]
362 * @param jobName optional job name param, if empty env.JOB_NAME will be used
363 * @param buildNumber build number param, if empty env.JOB_NAME will be used
364 * @param buildUrl build url param, if empty env.JOB_NAME will be used
365 * @param mailFrom mail FROM param, if empty "jenkins" will be used, it's mandatory for sending email notifications
366 * @param mailTo mail TO param, it's mandatory for sending email notifications
367 */
368def sendNotification(buildStatus, msgText="", enabledNotifications = [], notificatedTypes=["onchange"], jobName=null, buildNumber=null, buildUrl=null, mailFrom="jenkins", mailTo=null){
369 // Default values
370 def colorName = 'blue'
371 def colorCode = '#0000FF'
372 def buildStatusParam = buildStatus != null && buildStatus != "" ? buildStatus : "SUCCESS"
373 def jobNameParam = jobName != null && jobName != "" ? jobName : env.JOB_NAME
374 def buildNumberParam = buildNumber != null && buildNumber != "" ? buildNumber : env.BUILD_NUMBER
375 def buildUrlParam = buildUrl != null && buildUrl != "" ? buildUrl : env.BUILD_URL
376 def subject = "${buildStatusParam}: Job '${jobNameParam} [${buildNumberParam}]'"
377 def summary = "${subject} (${buildUrlParam})"
378
379 if(msgText != null && msgText != ""){
380 summary+="\n${msgText}"
381 }
382 if(buildStatusParam.toLowerCase().equals("success")){
383 colorCode = "#00FF00"
384 colorName = "green"
385 }else if(buildStatusParam.toLowerCase().equals("unstable")){
386 colorCode = "#FFFF00"
387 colorName = "yellow"
388 }else if(buildStatusParam.toLowerCase().equals("failure")){
389 colorCode = "#FF0000"
390 colorName = "red"
391 }
392 if(_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)){
393 if(enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")){
394 try{
395 slackSend color: colorCode, message: summary
396 }catch(Exception e){
397 println("Calling slack plugin failed")
398 e.printStackTrace()
399 }
400 }
401 if(enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")){
402 try{
403 hipchatSend color: colorName.toUpperCase(), message: summary
404 }catch(Exception e){
405 println("Calling hipchat plugin failed")
406 e.printStackTrace()
407 }
408 }
409 if(enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != ""){
410 try{
411 mail body: summary, from: mailFrom, subject: subject, to: mailTo
412 }catch(Exception e){
413 println("Sending mail plugin failed")
414 e.printStackTrace()
415 }
416 }
417 }
418}
419
420/**
421 * Execute git clone and checkout stage from gerrit review
422 *
423 * @param config LinkedHashMap
424 * config includes next parameters:
425 * - credentialsId, id of user which should make checkout
426 * - withMerge, prevent detached mode in repo
427 * - withWipeOut, wipe repository and force clone
428 *
429 * Usage example:
430 * //anonymous gerrit checkout
431 * def gitFunc = new com.mirantis.mcp.Git()
432 * gitFunc.gerritPatchsetCheckout([
433 * withMerge : true
434 * ])
435 *
436 * def gitFunc = new com.mirantis.mcp.Git()
437 * gitFunc.gerritPatchsetCheckout([
438 * credentialsId : 'mcp-ci-gerrit',
439 * withMerge : true
440 * ])
441 */
442def gerritPatchsetCheckout(LinkedHashMap config) {
443 def merge = config.get('withMerge', false)
444 def wipe = config.get('withWipeOut', false)
445 def credentials = config.get('credentialsId','')
446
447 // default parameters
448 def scmExtensions = [
449 [$class: 'CleanCheckout'],
450 [$class: 'BuildChooserSetting', buildChooser: [$class: 'GerritTriggerBuildChooser']]
451 ]
452 def scmUserRemoteConfigs = [
453 name: 'gerrit',
454 refspec: "${GERRIT_REFSPEC}"
455 ]
456
457 if (credentials == '') {
458 // then try to checkout in anonymous mode
459 scmUserRemoteConfigs.put('url',"https://${GERRIT_HOST}/${GERRIT_PROJECT}")
460 } else {
461 // else use ssh checkout
462 scmUserRemoteConfigs.put('url',"ssh://${GERRIT_NAME}@${GERRIT_HOST}:${GERRIT_PORT}/${GERRIT_PROJECT}.git")
463 scmUserRemoteConfigs.put('credentialsId',credentials)
464 }
465
466 // if we need to "merge" code from patchset to GERRIT_BRANCH branch
467 if (merge) {
468 scmExtensions.add([$class: 'LocalBranch', localBranch: "${GERRIT_BRANCH}"])
469 }
470 // we need wipe workspace before checkout
471 if (wipe) {
472 scmExtensions.add([$class: 'WipeWorkspace'])
473 }
474
475 checkout(
476 scm: [
477 $class: 'GitSCM',
478 branches: [[name: "${GERRIT_BRANCH}"]],
479 extensions: scmExtensions,
480 userRemoteConfigs: [scmUserRemoteConfigs]
481 ]
482 )
483}