Last active
December 16, 2015 18:57
-
-
Save gestj/c6a9b948b422ec82f1c6 to your computer and use it in GitHub Desktop.
Script for jenkins scriptler plugin (works with Jenkins v1.572)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import groovy.json.* | |
import hudson.model.* | |
import static groovy.json.JsonOutput.* | |
import java.util.logging.* | |
import java.security.MessageDigest | |
class Bitbucket { | |
def static BITBUCKET_REST_URL = "https://bitbucket.org" | |
def static BITBUCKET_API_URL = BITBUCKET_REST_URL + "/api/2.0/" | |
def static LOGGER = Logger.getLogger("connect.bitbucket.commit.groovy.Bitbucket") | |
def user, pass | |
Bitbucket(user, pass) { | |
this.user = user | |
this.pass = pass | |
} | |
def post(resource, map) { | |
LOGGER.info("POST -> ${BITBUCKET_API_URL}${resource}\n data: " + map) | |
def conn = connect(resource) | |
conn.setDoOutput(true) | |
conn.setRequestMethod("POST") | |
def out = new OutputStreamWriter(conn.getOutputStream()); | |
out.write(toJson(map)) | |
out.close() | |
return getResponse(conn) | |
} | |
def connect(resource) { | |
def conn = "${BITBUCKET_API_URL}${resource}".toURL().openConnection() | |
def auth = "${user}:${pass}".getBytes().encodeBase64().toString() | |
conn.setRequestProperty("Authorization", "Basic ${auth}") | |
conn.setRequestProperty("content-type", "application/json; charset=utf-8"); | |
return conn | |
} | |
def getResponse(conn) { | |
if (conn.responseCode == 200 || conn.responseCode == 201) { | |
return new JsonSlurper().parseText(conn.content.text) | |
} else if (conn.responseCode == 400) { | |
throw new IllegalArgumentException(formatError("Bad request", conn)) | |
} else if (conn.responseCode == 404) { | |
throw new IllegalArgumentException(formatError("Resource not found", conn)) | |
} else if (conn.responseCode == 401) { | |
throw new SecurityException(formatError("Not authorized", conn)) | |
} else { | |
throw new RuntimeException(formatError("Unexpected error", conn)) | |
} | |
} | |
def formatError(msg, conn) { | |
LOGGER.log(Level.WARNING, "${msg} (${conn.responseCode} - ${conn.responseMessage}):\n${conn.errorStream?.text}") | |
"${msg} (${conn.responseCode} - ${conn.responseMessage})" | |
} | |
// I added retry because sometimes Bitbucket fails to update the build status.. | |
// => I want to be sure that a once started (inprogress) build gets its final result properly updated. | |
/** | |
* Will retry given func up to 3 times (catches thrown exceptions, sleeps and retries). | |
* @param func the function you wanna execute | |
* @param times handled by _retry itself - don't use it | |
* @return the return value of func if it has some and only if successful | |
*/ | |
def _retry(Closure func, times=1) { | |
def response = false | |
try { | |
response = func() | |
} catch(e) { | |
// "exception" is already logged | |
if (times < 3) { | |
LOGGER.fine("Will try again after waiting a little...") | |
sleep(2000 * times) | |
_retry(func, times + 1) | |
} else { | |
LOGGER.log(Level.WARNING, "Tried 3 times to execute your function, but didn't succeed.") | |
} | |
} | |
return response | |
} | |
/** | |
* Updates build status of a commit in Bitbucket. | |
* | |
* If post to Bitbucket fails, it'll be tried again after a short amount of time (up to 3 times). | |
* If still unsuccessful Bitbucket stays untouched. Method then will return false. | |
* | |
* @param account account of the repository in Bitbucket | |
* @param repo the targeted repository | |
* @param revision the revision where the build status shall be set | |
* @param state the status of the corresponding build | |
* @param key the key of the corresponding build | |
* @param name the name of the corresponding build | |
* @param url the url to the corresponding build | |
* @param description this is optional - you can just leave it or put a string there | |
* @return response of Bitbucket | |
* | |
* @see https://confluence.atlassian.com/bitbucket/statuses-build-resource-779295267.html | |
*/ | |
def updateBuildStatus(account, repo, revision, state, key, name, url, description="") { | |
return _retry({ | |
post("repositories/${account}/${repo}/commit/${revision}/statuses/build", [ | |
state: state, | |
key: key, | |
name: name, | |
url: url, | |
description: description | |
]) | |
}) | |
} | |
} | |
def logger = Logger.getLogger("connect.bitbucket.commit.groovy") | |
def thr = Thread.currentThread(); | |
def currentBuild = thr?.executable | |
def env = currentBuild.getEnvironment(listener) | |
def url = currentBuild.url | |
def _id(env) { | |
return MessageDigest.getInstance("MD5").digest(env.BUILD_TAG.bytes).encodeHex().toString() | |
} | |
def _name(env) { | |
def branch = env.GIT_BRANCH | |
if (branch != null) { | |
def name = "unknown" | |
if (branch.startsWith("origin/")) { | |
name = branch.split("origin/")[1] | |
} else if (branch.equals("detached")) { | |
name = "release" | |
} else if (branch.startsWith("refs/tags")) { | |
name = "promote" | |
} | |
return name + " (#" + env.BUILD_NUMBER + ")" | |
} | |
return "#" + env.BUILD_NUMBER | |
} | |
def _repo(env) { | |
if (repo.equals("_env_")) { | |
return env.GIT_REPOSITORY | |
} | |
return repo | |
} | |
def _state(build) { | |
if (build.result == null) { | |
return "INPROGRESS" | |
} else if (build.result.isBetterOrEqualTo(Result.SUCCESS) || build.result == Result.NOT_BUILT) { | |
return "SUCCESSFUL" | |
} | |
return "FAILED" | |
} | |
def _bitbucket(env, repo, sha1, state, id, name, url) { | |
logger.fine("update ${sha1}") | |
Bitbucket(env.BITBUCKET_USER, env.BITBUCKET_PASS).updateBuildStatus( | |
env.BITBUCKET_TEAM, repo, sha1, state, id, name, | |
env.JENKINS_BASE_URL + "/${url}") | |
} | |
// updates for each commit connected to this build or if no change just takes the last commit | |
if (!build.getChangeSet().isEmptySet()) { | |
build.getChangeSet().getLogs().each { | |
def sha = it.id | |
_bitbucket(_repo(env), sha, _state(currentBuild), _id(env), _name(env), url) | |
} | |
} else { | |
def sha = build.getAction(hudson.plugins.git.util.BuildData).getLastBuiltRevision().sha1.name() | |
_bitbucket(_repo(env), sha, _state(currentBuild), _id(env), _name(env), url) | |
} | |
return "connect.bitbucket.commit.groovy done" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Why? For what?
This uses the just updated Bitbucket API to add a "build status" to your commits.
See http://blog.bitbucket.org/2015/11/18/introducing-the-build-status-api-for-bitbucket-cloud/
Usage
repo
as parameter!)env.BITBUCKET_USER
,env.BITBUCKET_PASS
,env.BITBUCKET_TEAM
,env.JENKINS_BASE_URL
to be set.About the code
The class
Bitbucket
comes from a small groovy lib we wrote for our jenkins. It's more complex than needed for this specific purpose, but it works...Interesting part starts at line
115
where build env gets collected, some helper methods are defined and finally theif
else
statement. It intelligently searches for the right commits associated to the current build.(i) Also keep in mind: some parts are specific to the project I am using this. For example:
Check out / change also (in the version posted you need to provide
repo
as parameter):