blob: fa15b53d0afdee26c93ccc138fa260a7101d6ae4 [file] [log] [blame]
package com.mirantis.mk
import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
import com.cloudbees.groovy.cps.NonCPS
import groovy.json.JsonSlurperClassic
/**
*
* Common functions
*
*/
/**
* Generate current timestamp
*
* @param format Defaults to yyyyMMddHHmmss
*/
def getDatetime(format = "yyyyMMddHHmmss") {
def now = new Date();
return now.format(format, TimeZone.getTimeZone('UTC'));
}
/**
* Return workspace.
* Currently implemented by calling pwd so it won't return relevant result in
* dir context
*/
def getWorkspace(includeBuildNum = false) {
def workspace = sh script: 'pwd', returnStdout: true
workspace = workspace.trim()
if (includeBuildNum) {
if (!workspace.endsWith("/")) {
workspace += "/"
}
workspace += env.BUILD_NUMBER
}
return workspace
}
/**
* Get UID of jenkins user.
* Must be run from context of node
*/
def getJenkinsUid() {
return sh(
script: 'id -u',
returnStdout: true
).trim()
}
/**
* Get GID of jenkins user.
* Must be run from context of node
*/
def getJenkinsGid() {
return sh(
script: 'id -g',
returnStdout: true
).trim()
}
/**
* Returns Jenkins user uid and gid in one list (in that order)
* Must be run from context of node
*/
def getJenkinsUserIds() {
return sh(script: "id -u && id -g", returnStdout: true).tokenize("\n")
}
/**
*
* Find credentials by ID
*
* @param credsId Credentials ID
* @param credsType Credentials type (optional)
*
*/
def getCredentialsById(String credsId, String credsType = 'any') {
def credClasses = [ // ordered by class name
sshKey : com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.class,
cert : com.cloudbees.plugins.credentials.common.CertificateCredentials.class,
password : com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
any : com.cloudbees.plugins.credentials.impl.BaseStandardCredentials.class,
dockerCert: org.jenkinsci.plugins.docker.commons.credentials.DockerServerCredentials.class,
file : org.jenkinsci.plugins.plaincredentials.FileCredentials.class,
string : org.jenkinsci.plugins.plaincredentials.StringCredentials.class,
]
return com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
credClasses[credsType],
jenkins.model.Jenkins.instance
).findAll { cred -> cred.id == credsId }[0]
}
/**
* Get credentials from store
*
* @param id Credentials name
*/
def getCredentials(id, cred_type = "username_password") {
warningMsg('You are using obsolete function. Please switch to use `getCredentialsById()`')
type_map = [
username_password: 'password',
key : 'sshKey',
]
return getCredentialsById(id, type_map[cred_type])
}
/**
* Abort build, wait for some time and ensure we will terminate
*/
def abortBuild() {
currentBuild.build().doStop()
sleep(180)
// just to be sure we will terminate
throw new InterruptedException()
}
/**
* Print pretty-printed string representation of given item
* @param item item to be pretty-printed (list, map, whatever)
*/
def prettyPrint(item) {
println prettify(item)
}
/**
* Return pretty-printed string representation of given item
* @param item item to be pretty-printed (list, map, whatever)
* @return pretty-printed string
*/
def prettify(item) {
return groovy.json.JsonOutput.prettyPrint(toJson(item)).replace('\\n', System.getProperty('line.separator'))
}
/**
* Print informational message
*
* @param msg
* @param color Colorful output or not
*/
def infoMsg(msg, color = true) {
printMsg(msg, "cyan")
}
/**
* Print error message
*
* @param msg
* @param color Colorful output or not
*/
def errorMsg(msg, color = true) {
printMsg(msg, "red")
}
/**
* Print success message
*
* @param msg
* @param color Colorful output or not
*/
def successMsg(msg, color = true) {
printMsg(msg, "green")
}
/**
* Print warning message
*
* @param msg
* @param color Colorful output or not
*/
def warningMsg(msg, color = true) {
printMsg(msg, "yellow")
}
/**
* Print debug message, this message will show only if DEBUG global variable is present
* @param msg
* @param color Colorful output or not
*/
def debugMsg(msg, color = true) {
// if debug property exists on env, debug is enabled
if (env.getEnvironment().containsKey('DEBUG') && env['DEBUG'] == "true") {
printMsg("[DEBUG] ${msg}", "red")
}
}
/**
* Print message
*
* @param msg Message to be printed
* @param level Level of message (default INFO)
* @param color Color to use for output or false (default)
*/
def printMsg(msg, color = false) {
colors = [
'red' : '\u001B[31m',
'black' : '\u001B[30m',
'green' : '\u001B[32m',
'yellow': '\u001B[33m',
'blue' : '\u001B[34m',
'purple': '\u001B[35m',
'cyan' : '\u001B[36m',
'white' : '\u001B[37m',
'reset' : '\u001B[0m'
]
if (color != false) {
print "${colors[color]}${msg}${colors.reset}"
} else {
print "[${level}] ${msg}"
}
}
/**
* Traverse directory structure and return list of files
*
* @param path Path to search
* @param type Type of files to search (groovy.io.FileType.FILES)
*/
@NonCPS
def getFiles(path, type = groovy.io.FileType.FILES) {
files = []
new File(path).eachFile(type) {
files[] = it
}
return files
}
/**
* Helper method to convert map into form of list of [key,value] to avoid
* unserializable exceptions
*
* @param m Map
*/
@NonCPS
def entries(m) {
m.collect { k, v -> [k, v] }
}
/**
* Opposite of build-in parallel, run map of steps in serial
*
* @param steps Map of String<name>: CPSClosure2<step> (or list of closures)
*/
def serial(steps) {
stepsArray = entries(steps)
for (i = 0; i < stepsArray.size; i++) {
def step = stepsArray[i]
def dummySteps = [:]
def stepKey
if (step[1] instanceof List || step[1] instanceof Map) {
for (j = 0; j < step[1].size(); j++) {
if (step[1] instanceof List) {
stepKey = j
} else if (step[1] instanceof Map) {
stepKey = step[1].keySet()[j]
}
dummySteps.put("step-${step[0]}-${stepKey}", step[1][stepKey])
}
} else {
dummySteps.put(step[0], step[1])
}
parallel dummySteps
}
}
/**
* Partition given list to list of small lists
* @param inputList input list
* @param partitionSize (partition size, optional, default 5)
*/
def partitionList(inputList, partitionSize = 5) {
List<List<String>> partitions = new ArrayList<>();
for (int i = 0; i < inputList.size(); i += partitionSize) {
partitions.add(new ArrayList<String>(inputList.subList(i, Math.min(i + partitionSize, inputList.size()))));
}
return partitions
}
/**
* Get password credentials from store
*
* @param id Credentials name
*/
def getPasswordCredentials(id) {
return getCredentialsById(id, 'password')
}
/**
* Get SSH credentials from store
*
* @param id Credentials name
*/
def getSshCredentials(id) {
return getCredentialsById(id, 'sshKey')
}
/**
* Tests Jenkins instance for existence of plugin with given name
* @param pluginName plugin short name to test
* @return boolean result
*/
@NonCPS
def jenkinsHasPlugin(pluginName) {
return Jenkins.instance.pluginManager.plugins.collect { p -> p.shortName }.contains(pluginName)
}
@NonCPS
def _needNotification(notificatedTypes, buildStatus, jobName) {
if (notificatedTypes && notificatedTypes.contains("onchange")) {
if (jobName) {
def job = Jenkins.instance.getItem(jobName)
def numbuilds = job.builds.size()
if (numbuilds > 0) {
//actual build is first for some reasons, so last finished build is second
def lastBuild = job.builds[1]
if (lastBuild) {
if (lastBuild.result.toString().toLowerCase().equals(buildStatus)) {
println("Build status didn't changed since last build, not sending notifications")
return false;
}
}
}
}
} else if (!notificatedTypes.contains(buildStatus)) {
return false;
}
return true;
}
/**
* Send notification to all enabled notifications services
* @param buildStatus message type (success, warning, error), null means SUCCESSFUL
* @param msgText message text
* @param enabledNotifications list of enabled notification types, types: slack, hipchat, email, default empty
* @param notificatedTypes types of notifications will be sent, default onchange - notificate if current build result not equal last result;
* otherwise use - ["success","unstable","failed"]
* @param jobName optional job name param, if empty env.JOB_NAME will be used
* @param buildNumber build number param, if empty env.BUILD_NUM will be used
* @param buildUrl build url param, if empty env.BUILD_URL will be used
* @param mailFrom mail FROM param, if empty "jenkins" will be used, it's mandatory for sending email notifications
* @param mailTo mail TO param, it's mandatory for sending email notifications, this option enable mail notification
*/
def sendNotification(buildStatus, msgText = "", enabledNotifications = [], notificatedTypes = ["onchange"], jobName = null, buildNumber = null, buildUrl = null, mailFrom = "jenkins", mailTo = null) {
// Default values
def colorName = 'blue'
def colorCode = '#0000FF'
def buildStatusParam = buildStatus != null && buildStatus != "" ? buildStatus : "SUCCESS"
def jobNameParam = jobName != null && jobName != "" ? jobName : env.JOB_NAME
def buildNumberParam = buildNumber != null && buildNumber != "" ? buildNumber : env.BUILD_NUMBER
def buildUrlParam = buildUrl != null && buildUrl != "" ? buildUrl : env.BUILD_URL
def subject = "${buildStatusParam}: Job '${jobNameParam} [${buildNumberParam}]'"
def summary = "${subject} (${buildUrlParam})"
if (msgText != null && msgText != "") {
summary += "\n${msgText}"
}
if (buildStatusParam.toLowerCase().equals("success")) {
colorCode = "#00FF00"
colorName = "green"
} else if (buildStatusParam.toLowerCase().equals("unstable")) {
colorCode = "#FFFF00"
colorName = "yellow"
} else if (buildStatusParam.toLowerCase().equals("failure")) {
colorCode = "#FF0000"
colorName = "red"
}
if (_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)) {
if (enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")) {
try {
slackSend color: colorCode, message: summary
} catch (Exception e) {
println("Calling slack plugin failed")
e.printStackTrace()
}
}
if (enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")) {
try {
hipchatSend color: colorName.toUpperCase(), message: summary
} catch (Exception e) {
println("Calling hipchat plugin failed")
e.printStackTrace()
}
}
if (enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != "") {
try {
mail body: summary, from: mailFrom, subject: subject, to: mailTo
} catch (Exception e) {
println("Sending mail plugin failed")
e.printStackTrace()
}
}
}
}
/**
* Execute linux command and catch nth element
* @param cmd command to execute
* @param index index to retrieve
* @return index-th element
*/
def cutOrDie(cmd, index) {
def common = new com.mirantis.mk.Common()
def output
try {
output = sh(script: cmd, returnStdout: true)
def result = output.tokenize(" ")[index]
return result;
} catch (Exception e) {
common.errorMsg("Failed to execute cmd: ${cmd}\n output: ${output}")
}
}
/**
* Check variable contains keyword
* @param variable keywork is searched (contains) here
* @param keyword string to look for
* @return True if variable contains keyword (case insensitive), False if do not contains or any of input isn't a string
*/
def checkContains(variable, keyword) {
if (env.getEnvironment().containsKey(variable)) {
return env[variable] && env[variable].toLowerCase().contains(keyword.toLowerCase())
} else {
return false
}
}
/**
* Parse JSON string to hashmap
* @param jsonString input JSON string
* @return created hashmap
*/
def parseJSON(jsonString) {
def m = [:]
def lazyMap = new JsonSlurperClassic().parseText(jsonString)
m.putAll(lazyMap)
return m
}
/**
* Test pipeline input parameter existence and validity (not null and not empty string)
* @param paramName input parameter name (usually uppercase)
*/
def validInputParam(paramName) {
return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
}
/**
* Take list of hashmaps and count number of hashmaps with parameter equals eq
* @param lm list of hashmaps
* @param param define parameter of hashmap to read and compare
* @param eq desired value of hashmap parameter
* @return count of hashmaps meeting defined condition
*/
@NonCPS
def countHashMapEquals(lm, param, eq) {
return lm.stream().filter { i -> i[param].equals(eq) }.collect(java.util.stream.Collectors.counting())
}
/**
* Execute shell command and return stdout, stderr and status
*
* @param cmd Command to execute
* @return map with stdout, stderr, status keys
*/
def shCmdStatus(cmd) {
def res = [:]
def stderr = sh(script: 'mktemp', returnStdout: true).trim()
def stdout = sh(script: 'mktemp', returnStdout: true).trim()
try {
def status = sh(script: "${cmd} 1>${stdout} 2>${stderr}", returnStatus: true)
res['stderr'] = sh(script: "cat ${stderr}", returnStdout: true)
res['stdout'] = sh(script: "cat ${stdout}", returnStdout: true)
res['status'] = status
} finally {
sh(script: "rm ${stderr}", returnStdout: true)
sh(script: "rm ${stdout}", returnStdout: true)
}
return res
}
/**
* Retry commands passed to body
*
* @param times Number of retries
* @param delay Delay between retries (in seconds)
* @param body Commands to be in retry block
* @return calling commands in body
* @example retry ( 3 , 5 ) { function body }* retry{ function body }
*/
def retry(int times = 5, int delay = 0, Closure body) {
int retries = 0
def exceptions = []
while (retries++ < times) {
try {
return body.call()
} catch (e) {
sleep(delay)
}
}
currentBuild.result = "FAILURE"
throw new Exception("Failed after $times retries")
}
/**
* Wait for user input with timeout
*
* @param timeoutInSeconds Timeout
* @param options Options for input widget
*/
def waitForInputThenPass(timeoutInSeconds, options = [message: 'Ready to go?']) {
def userInput = true
try {
timeout(time: timeoutInSeconds, unit: 'SECONDS') {
userInput = input options
}
} catch (err) { // timeout reached or input false
def user = err.getCauses()[0].getUser()
if ('SYSTEM' == user.toString()) { // SYSTEM means timeout.
println("Timeout, proceeding")
} else {
userInput = false
println("Aborted by: [${user}]")
throw err
}
}
return userInput
}
/**
* Function receives Map variable as input and sorts it
* by values ascending. Returns sorted Map
* @param _map Map variable
*/
@NonCPS
def SortMapByValueAsc(_map) {
def sortedMap = _map.sort { it.value }
return sortedMap
}