blob: 7ad183427f064c9364aa69c41c0b2ba446619fab [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
}