Merge "Add enforceStateWithTests method"
diff --git a/src/com/mirantis/mk/Common.groovy b/src/com/mirantis/mk/Common.groovy
index 7ad1834..a44bcdf 100644
--- a/src/com/mirantis/mk/Common.groovy
+++ b/src/com/mirantis/mk/Common.groovy
@@ -5,6 +5,7 @@
import com.cloudbees.groovy.cps.NonCPS
import groovy.json.JsonSlurperClassic
+
/**
*
* Common functions
@@ -14,9 +15,9 @@
/**
* Generate current timestamp
*
- * @param format Defaults to yyyyMMddHHmmss
+ * @param format Defaults to yyyyMMddHHmmss
*/
-def getDatetime(format="yyyyMMddHHmmss") {
+def getDatetime(format = "yyyyMMddHHmmss") {
def now = new Date();
return now.format(format, TimeZone.getTimeZone('UTC'));
}
@@ -26,14 +27,14 @@
* Currently implemented by calling pwd so it won't return relevant result in
* dir context
*/
-def getWorkspace(includeBuildNum=false) {
+def getWorkspace(includeBuildNum = false) {
def workspace = sh script: 'pwd', returnStdout: true
workspace = workspace.trim()
- if(includeBuildNum){
- if(!workspace.endsWith("/")){
- workspace += "/"
- }
- workspace += env.BUILD_NUMBER
+ if (includeBuildNum) {
+ if (!workspace.endsWith("/")) {
+ workspace += "/"
+ }
+ workspace += env.BUILD_NUMBER
}
return workspace
}
@@ -43,7 +44,7 @@
* Must be run from context of node
*/
def getJenkinsUid() {
- return sh (
+ return sh(
script: 'id -u',
returnStdout: true
).trim()
@@ -54,7 +55,7 @@
* Must be run from context of node
*/
def getJenkinsGid() {
- return sh (
+ return sh(
script: 'id -g',
returnStdout: true
).trim()
@@ -64,7 +65,7 @@
* Returns Jenkins user uid and gid in one list (in that order)
* Must be run from context of node
*/
-def getJenkinsUserIds(){
+def getJenkinsUserIds() {
return sh(script: "id -u && id -g", returnStdout: true).tokenize("\n")
}
@@ -72,37 +73,37 @@
*
* Find credentials by ID
*
- * @param credsId Credentials ID
- * @param credsType Credentials type (optional)
+ * @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,
+ 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]
+ ).findAll { cred -> cred.id == credsId }[0]
}
/**
* Get credentials from store
*
- * @param id Credentials name
+ * @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',
+ key : 'sshKey',
]
return getCredentialsById(id, type_map[cred_type])
@@ -122,7 +123,7 @@
* Print pretty-printed string representation of given item
* @param item item to be pretty-printed (list, map, whatever)
*/
-def prettyPrint(item){
+def prettyPrint(item) {
println prettify(item)
}
@@ -131,7 +132,7 @@
* @param item item to be pretty-printed (list, map, whatever)
* @return pretty-printed string
*/
-def prettify(item){
+def prettify(item) {
return groovy.json.JsonOutput.prettyPrint(toJson(item)).replace('\\n', System.getProperty('line.separator'))
}
@@ -180,9 +181,9 @@
* @param msg
* @param color Colorful output or not
*/
-def debugMsg(msg, color = true){
+def debugMsg(msg, color = true) {
// if debug property exists on env, debug is enabled
- if(env.getEnvironment().containsKey('DEBUG') && env['DEBUG'] == "true"){
+ if (env.getEnvironment().containsKey('DEBUG') && env['DEBUG'] == "true") {
printMsg("[DEBUG] ${msg}", "red")
}
}
@@ -190,9 +191,9 @@
/**
* 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)
+ * @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 = [
@@ -220,7 +221,7 @@
* @param type Type of files to search (groovy.io.FileType.FILES)
*/
@NonCPS
-def getFiles(path, type=groovy.io.FileType.FILES) {
+def getFiles(path, type = groovy.io.FileType.FILES) {
files = []
new File(path).eachFile(type) {
files[] = it
@@ -236,7 +237,7 @@
*/
@NonCPS
def entries(m) {
- m.collect {k, v -> [k, v]}
+ m.collect { k, v -> [k, v] }
}
/**
@@ -246,20 +247,20 @@
*/
def serial(steps) {
stepsArray = entries(steps)
- for (i=0; i < stepsArray.size; i++) {
+ 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){
+ 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){
+ } else if (step[1] instanceof Map) {
stepKey = step[1].keySet()[j]
}
- dummySteps.put("step-${step[0]}-${stepKey}",step[1][stepKey])
+ dummySteps.put("step-${step[0]}-${stepKey}", step[1][stepKey])
}
- }else{
+ } else {
dummySteps.put(step[0], step[1])
}
parallel dummySteps
@@ -271,18 +272,18 @@
* @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
+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
+ * @param id Credentials name
*/
def getPasswordCredentials(id) {
return getCredentialsById(id, 'password')
@@ -291,7 +292,7 @@
/**
* Get SSH credentials from store
*
- * @param id Credentials name
+ * @param id Credentials name
*/
def getSshCredentials(id) {
return getCredentialsById(id, 'sshKey')
@@ -303,28 +304,28 @@
* @return boolean result
*/
@NonCPS
-def jenkinsHasPlugin(pluginName){
- return Jenkins.instance.pluginManager.plugins.collect{p -> p.shortName}.contains(pluginName)
+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){
+ if (notificatedTypes && notificatedTypes.contains("onchange")) {
+ if (jobName) {
def job = Jenkins.instance.getItem(jobName)
def numbuilds = job.builds.size()
- if (numbuilds > 0){
+ 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)){
+ 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)){
+ } else if (!notificatedTypes.contains(buildStatus)) {
return false;
}
return true;
@@ -343,7 +344,7 @@
* @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){
+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'
@@ -354,40 +355,40 @@
def subject = "${buildStatusParam}: Job '${jobNameParam} [${buildNumberParam}]'"
def summary = "${subject} (${buildUrlParam})"
- if(msgText != null && msgText != ""){
- summary+="\n${msgText}"
+ if (msgText != null && msgText != "") {
+ summary += "\n${msgText}"
}
- if(buildStatusParam.toLowerCase().equals("success")){
+ if (buildStatusParam.toLowerCase().equals("success")) {
colorCode = "#00FF00"
colorName = "green"
- }else if(buildStatusParam.toLowerCase().equals("unstable")){
+ } else if (buildStatusParam.toLowerCase().equals("unstable")) {
colorCode = "#FFFF00"
colorName = "yellow"
- }else if(buildStatusParam.toLowerCase().equals("failure")){
+ } else if (buildStatusParam.toLowerCase().equals("failure")) {
colorCode = "#FF0000"
colorName = "red"
}
- if(_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)){
- if(enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")){
- try{
+ if (_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)) {
+ if (enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")) {
+ try {
slackSend color: colorCode, message: summary
- }catch(Exception e){
+ } catch (Exception e) {
println("Calling slack plugin failed")
e.printStackTrace()
}
}
- if(enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")){
- try{
+ if (enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")) {
+ try {
hipchatSend color: colorName.toUpperCase(), message: summary
- }catch(Exception e){
+ } catch (Exception e) {
println("Calling hipchat plugin failed")
e.printStackTrace()
}
}
- if(enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != ""){
- try{
+ if (enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != "") {
+ try {
mail body: summary, from: mailFrom, subject: subject, to: mailTo
- }catch(Exception e){
+ } catch (Exception e) {
println("Sending mail plugin failed")
e.printStackTrace()
}
@@ -402,16 +403,15 @@
* @return index-th element
*/
-def cutOrDie(cmd, index)
-{
+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;
+ 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}")
+ common.errorMsg("Failed to execute cmd: ${cmd}\n output: ${output}")
}
}
@@ -423,7 +423,7 @@
*/
def checkContains(variable, keyword) {
- if(env.getEnvironment().containsKey(variable)){
+ if (env.getEnvironment().containsKey(variable)) {
return env[variable] && env[variable].toLowerCase().contains(keyword.toLowerCase())
} else {
return false
@@ -435,18 +435,18 @@
* @param jsonString input JSON string
* @return created hashmap
*/
-def parseJSON(jsonString){
- def m = [:]
- def lazyMap = new JsonSlurperClassic().parseText(jsonString)
- m.putAll(lazyMap)
- return m
+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){
+def validInputParam(paramName) {
return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
}
@@ -460,7 +460,7 @@
@NonCPS
def countHashMapEquals(lm, param, eq) {
- return lm.stream().filter{i -> i[param].equals(eq)}.collect(java.util.stream.Collectors.counting())
+ return lm.stream().filter { i -> i[param].equals(eq) }.collect(java.util.stream.Collectors.counting())
}
/**
@@ -476,7 +476,7 @@
def stdout = sh(script: 'mktemp', returnStdout: true).trim()
try {
- def status = sh(script:"${cmd} 1>${stdout} 2>${stderr}", returnStatus: true)
+ 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
@@ -488,7 +488,6 @@
return res
}
-
/**
* Retry commands passed to body
*
@@ -496,17 +495,16 @@
* @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 }
+ * @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) {
+ while (retries++ < times) {
try {
return body.call()
- } catch(e) {
+ } catch (e) {
sleep(delay)
}
}
@@ -514,30 +512,29 @@
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
+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
+ }
}
- } 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
+ return userInput
}
/**
@@ -547,6 +544,174 @@
*/
@NonCPS
def SortMapByValueAsc(_map) {
- def sortedMap = _map.sort {it.value}
+ def sortedMap = _map.sort { it.value }
return sortedMap
}
+
+/**
+ * Compare 'old' and 'new' dir's recursively
+ * @param diffData =' Only in new/XXX/infra: secrets.yml
+ Files old/XXX/init.yml and new/XXX/init.yml differ
+ Only in old/XXX/infra: secrets11.yml '
+ *
+ * @return
+ * - new:
+ - XXX/secrets.yml
+ - diff:
+ - XXX/init.yml
+ - removed:
+ - XXX/secrets11.yml
+
+ */
+def diffCheckMultidir(diffData) {
+ common = new com.mirantis.mk.Common()
+ // Some global constants. Don't change\move them!
+ keyNew = 'new'
+ keyRemoved = 'removed'
+ keyDiff = 'diff'
+ def output = [
+ new : [],
+ removed: [],
+ diff : [],
+ ]
+ String pathSep = '/'
+ diffData.each { line ->
+ def job_file = ''
+ def job_type = ''
+ if (line.startsWith('Files old/')) {
+ job_file = new File(line.replace('Files old/', '').tokenize()[0])
+ job_type = keyDiff
+ } else if (line.startsWith('Only in new/')) {
+ // get clean normalized filepath, under new/
+ job_file = new File(line.replace('Only in new/', '').replace(': ', pathSep)).toString()
+ job_type = keyNew
+ } else if (line.startsWith('Only in old/')) {
+ // get clean normalized filepath, under old/
+ job_file = new File(line.replace('Only in old/', '').replace(': ', pathSep)).toString()
+ job_type = keyRemoved
+ } else {
+ common.warningMsg("Not parsed diff line: ${line}!")
+ }
+ if (job_file != '') {
+ output[job_type].push(job_file)
+ }
+ }
+ return output
+}
+
+/**
+ * Compare 2 folder, file by file
+ * Structure should be:
+ * ${compRoot}/
+ └── diff - diff results will be save here
+ ├── new - input folder with data
+ ├── old - input folder with data
+ ├── pillar.diff - globall diff will be saved here
+ * b_url - usual env.BUILD_URL, to be add into description
+ * grepOpts - General grep cmdline; Could be used to pass some magic
+ * regexp into after-diff listing file(pillar.diff)
+ * Example: '-Ev infra/secrets.yml'
+ * return - html-based string
+ * TODO: allow to specify subdir for results?
+ **/
+def comparePillars(compRoot, b_url, grepOpts) {
+ common = new com.mirantis.mk.Common()
+ // Some global constants. Don't change\move them!
+ keyNew = 'new'
+ keyRemoved = 'removed'
+ keyDiff = 'diff'
+ def diff_status = 0
+ // FIXME
+ httpWS = b_url + '/artifact/'
+ dir(compRoot) {
+ // If diff empty - exit 0
+ diff_status = sh(script: 'diff -q -r old/ new/ > pillar.diff',
+ returnStatus: true,
+ )
+ }
+ // Unfortunately, diff not able to work with dir-based regexp
+ if (diff_status == 1 && grepOpts) {
+ dir(compRoot) {
+ grep_status = sh(script: """
+ cp -v pillar.diff pillar_orig.diff
+ grep ${grepOpts} pillar_orig.diff > pillar.diff
+ """,
+ returnStatus: true
+ )
+ if (grep_status == 1){
+ common.warningMsg("Grep regexp ${grepOpts} removed all diff!")
+ diff_status = 0
+ }
+ }
+ }
+ // Set job description
+ String description = ''
+ if (diff_status == 1) {
+ // Analyse output file and prepare array with results
+ String data_ = readFile file: "${compRoot}/pillar.diff"
+ def diff_list = diffCheckMultidir(data_.split("\\r?\\n"))
+ common.infoMsg(diff_list)
+ dir(compRoot) {
+ if (diff_list[keyDiff].size() > 0) {
+ if (!fileExists('diff')) {
+ sh('mkdir -p diff')
+ }
+ description += '<b>CHANGED</b><ul>'
+ common.infoMsg('Changed items:')
+ for (item in diff_list[keyDiff]) {
+ // We don't want to handle sub-dirs structure. So, simply make diff 'flat'
+ item_f = item.toString().replace('/', '_')
+ description += "<li><a href=\"${httpWS}/diff/${item_f}/*view*/\">${item}</a></li>"
+ // Generate diff file
+ def diff_exit_code = sh([
+ script : "diff -U 50 old/${item} new/${item} > diff/${item_f}",
+ returnStdout: false,
+ returnStatus: true,
+ ])
+ // catch normal errors, diff should always return 1
+ if (diff_exit_code != 1) {
+ error 'Error with diff file generation'
+ }
+ }
+ }
+ if (diff_list[keyNew].size() > 0) {
+ description += '<b>ADDED</b><ul>'
+ for (item in diff_list[keyNew]) {
+ description += "<li><a href=\"${httpWS}/new/${item}/*view*/\">${item}</a></li>"
+ }
+ }
+ if (diff_list[keyRemoved].size() > 0) {
+ description += '<b>DELETED</b><ul>'
+ for (item in diff_list[keyRemoved]) {
+ description += "<li><a href=\"${httpWS}/old/${item}/*view*/\">${item}</a></li>"
+ }
+ }
+
+ }
+ }
+
+ if (description != '') {
+ dir(compRoot) {
+ archiveArtifacts([
+ artifacts : '**',
+ allowEmptyArchive: true,
+ ])
+ }
+ return description.toString()
+ } else {
+ return 'No job changes'
+ }
+}
+
+/**
+ * Simple function, to get basename from string.
+ * line - path-string
+ * remove_ext - string, optionl. Drop file extenstion.
+ **/
+def GetBaseName(line, remove_ext) {
+ filename = line.toString().split('/').last()
+ if (remove_ext && filename.endsWith(remove_ext.toString())) {
+ filename = filename.take(filename.lastIndexOf(remove_ext.toString()))
+ }
+ return filename
+}
\ No newline at end of file
diff --git a/src/com/mirantis/mk/Gerrit.groovy b/src/com/mirantis/mk/Gerrit.groovy
index 99c91b7..d1c0a1c 100644
--- a/src/com/mirantis/mk/Gerrit.groovy
+++ b/src/com/mirantis/mk/Gerrit.groovy
@@ -76,7 +76,7 @@
// if we need to "merge" code from patchset to GERRIT_BRANCH branch
if (merge) {
- scmExtensions.add([$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: 'gerrit', mergeStrategy: 'default', mergeTarget: gerritBranch]])
+ scmExtensions.add([$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: 'gerrit', mergeStrategy: 'DEFAULT', mergeTarget: gerritBranch]])
}
// we need wipe workspace before checkout
if (wipe) {
@@ -246,4 +246,4 @@
def missedParams = requiredParams - config.keySet()
def badParams = config.subMap(requiredParams).findAll{it.value in [null, '']}.keySet()
return badParams + missedParams
-}
\ No newline at end of file
+}