From 620a2215e275e124836327ba1e6a153d07615af2 Mon Sep 17 00:00:00 2001 From: Björn Dahlgren Date: Thu, 26 Nov 2015 17:27:10 +0100 Subject: Use new Build Status instead of commenting on pull requests News: http://blog.bitbucket.org/2015/11/18/introducing-the-build-status-api-for-bitbucket-cloud/ API: https://confluence.atlassian.com/bitbucket/buildstatus-resource-779295267.html --- .../BitbucketBuildTrigger.java | 14 +++ .../BitbucketBuilds.java | 11 ++- .../BitbucketCause.java | 7 +- .../BitbucketRepository.java | 109 +++++++-------------- .../bitbucket/ApiClient.java | 48 ++++----- .../bitbucket/BuildState.java | 10 ++ .../BitbucketBuildTrigger/config.jelly | 6 ++ .../BitbucketBuildTrigger/help-ciKey.html | 4 + .../BitbucketBuildTrigger/help-ciName.html | 1 + 9 files changed, 102 insertions(+), 108 deletions(-) create mode 100644 src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/BuildState.java create mode 100644 src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciKey.html create mode 100644 src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciName.html (limited to 'src') diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java index 879facb..247bd67 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java @@ -28,6 +28,8 @@ public class BitbucketBuildTrigger extends Trigger> { private final String password; private final String repositoryOwner; private final String repositoryName; + private final String ciKey; + private final String ciName; private final String ciSkipPhrases; private final boolean checkDestinationCommit; private final boolean approveIfSuccess; @@ -45,6 +47,8 @@ public class BitbucketBuildTrigger extends Trigger> { String password, String repositoryOwner, String repositoryName, + String ciKey, + String ciName, String ciSkipPhrases, boolean checkDestinationCommit, boolean approveIfSuccess @@ -56,6 +60,8 @@ public class BitbucketBuildTrigger extends Trigger> { this.password = password; this.repositoryOwner = repositoryOwner; this.repositoryName = repositoryName; + this.ciKey = ciKey; + this.ciName = ciName; this.ciSkipPhrases = ciSkipPhrases; this.checkDestinationCommit = checkDestinationCommit; this.approveIfSuccess = approveIfSuccess; @@ -85,6 +91,14 @@ public class BitbucketBuildTrigger extends Trigger> { return repositoryName; } + public String getCiKey() { + return ciKey; + } + + public String getCiName() { + return ciName; + } + public String getCiSkipPhrases() { return ciSkipPhrases; } diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuilds.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuilds.java index 45e1873..3e8e84b 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuilds.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuilds.java @@ -1,5 +1,6 @@ package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder; +import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.BuildState; import hudson.model.AbstractBuild; import hudson.model.Cause; import hudson.model.Result; @@ -51,13 +52,13 @@ public class BitbucketBuilds { String rootUrl = Jenkins.getInstance().getRootUrl(); String buildUrl = ""; if (rootUrl == null) { - buildUrl = " PLEASE SET JENKINS ROOT URL FROM GLOBAL CONFIGURATION " + build.getUrl(); - } - else { + logger.warning("PLEASE SET JENKINS ROOT URL IN GLOBAL CONFIGURATION FOR BUILD STATE REPORTING"); + } else { buildUrl = rootUrl + build.getUrl(); + BuildState state = result == Result.SUCCESS ? BuildState.SUCCESSFUL : BuildState.FAILED; + repository.setBuildStatus(cause, state, buildUrl); } - repository.deletePullRequestComment(cause.getPullRequestId(), cause.getBuildStartCommentId()); - repository.postFinishedComment(cause.getPullRequestId(), cause.getSourceCommitHash(), cause.getDestinationCommitHash(), result == Result.SUCCESS, buildUrl); + if ( this.trigger.getApproveIfSuccess() && result == Result.SUCCESS ) { this.repository.postPullRequestApproval(cause.getPullRequestId()); } diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketCause.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketCause.java index 01e6cd1..e233371 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketCause.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketCause.java @@ -16,7 +16,6 @@ public class BitbucketCause extends Cause { private final String pullRequestTitle; private final String sourceCommitHash; private final String destinationCommitHash; - private final String buildStartCommentId; public static final String BITBUCKET_URL = "https://bitbucket.org/"; public BitbucketCause(String sourceBranch, @@ -28,8 +27,7 @@ public class BitbucketCause extends Cause { String destinationRepositoryName, String pullRequestTitle, String sourceCommitHash, - String destinationCommitHash, - String buildStartCommentId) { + String destinationCommitHash) { this.sourceBranch = sourceBranch; this.targetBranch = targetBranch; this.repositoryOwner = repositoryOwner; @@ -40,7 +38,6 @@ public class BitbucketCause extends Cause { this.pullRequestTitle = pullRequestTitle; this.sourceCommitHash = sourceCommitHash; this.destinationCommitHash = destinationCommitHash; - this.buildStartCommentId = buildStartCommentId; } public String getSourceBranch() { @@ -79,8 +76,6 @@ public class BitbucketCause extends Cause { public String getDestinationCommitHash() { return destinationCommitHash; } - public String getBuildStartCommentId() { return buildStartCommentId; } - @Override public String getShortDescription() { String description = " getTargetPullRequests() { @@ -58,17 +53,8 @@ public class BitbucketRepository { return targetPullRequests; } - public String postBuildStartCommentTo(Pullrequest pullRequest) { - String sourceCommit = pullRequest.getSource().getCommit().getHash(); - String destinationCommit = pullRequest.getDestination().getCommit().getHash(); - String comment = String.format(BUILD_START_MARKER, builder.getProject().getDisplayName(), sourceCommit, destinationCommit); - Pullrequest.Comment commentResponse = this.client.postPullRequestComment(pullRequest.getId(), comment); - return commentResponse.getId().toString(); - } - public void addFutureBuildTasks(Collection pullRequests) { for(Pullrequest pullRequest : pullRequests) { - String commentId = postBuildStartCommentTo(pullRequest); if ( this.trigger.getApproveIfSuccess() ) { deletePullRequestApproval(pullRequest.getId()); } @@ -82,24 +68,26 @@ public class BitbucketRepository { pullRequest.getDestination().getRepository().getRepositoryName(), pullRequest.getTitle(), pullRequest.getSource().getCommit().getHash(), - pullRequest.getDestination().getCommit().getHash(), - commentId); + pullRequest.getDestination().getCommit().getHash()); + setBuildStatus(cause, BuildState.INPROGRESS, Jenkins.getInstance().getRootUrl()); this.builder.getTrigger().startJob(cause); } } - public void deletePullRequestComment(String pullRequestId, String commentId) { - this.client.deletePullRequestComment(pullRequestId,commentId); - } + public void setBuildStatus(BitbucketCause cause, BuildState state, String buildUrl) { + String comment = null; + String sourceCommit = cause.getSourceCommitHash(); + String owner = cause.getRepositoryOwner(); + String repository = cause.getRepositoryName(); + String destinationBranch = cause.getTargetBranch(); - public void postFinishedComment(String pullRequestId, String sourceCommit, String destinationCommit, boolean success, String buildUrl) { - String message = BUILD_FAILURE_COMMENT; - if (success){ - message = BUILD_SUCCESS_COMMENT; + logger.info("setBuildStatus " + state + " for commit: " + sourceCommit + " with url " + buildUrl); + + if (state == BuildState.FAILED || state == BuildState.SUCCESSFUL) { + comment = String.format(BUILD_DESCRIPTION, builder.getProject().getDisplayName(), sourceCommit, destinationBranch); } - String comment = String.format(BUILD_FINISH_SENTENCE, builder.getProject().getDisplayName(), sourceCommit, destinationCommit, message, buildUrl); - this.client.postPullRequestComment(pullRequestId, comment); + this.client.setBuildStatus(owner, repository, sourceCommit, state, buildUrl, comment); } public void deletePullRequestApproval(String pullRequestId) { @@ -111,19 +99,16 @@ public class BitbucketRepository { } private boolean isBuildTarget(Pullrequest pullRequest) { - - boolean shouldBuild = true; if (pullRequest.getState() != null && pullRequest.getState().equals("OPEN")) { if (isSkipBuild(pullRequest.getTitle())) { return false; } - String sourceCommit = pullRequest.getSource().getCommit().getHash(); - + Pullrequest.Revision source = pullRequest.getSource(); + String sourceCommit = source.getCommit().getHash(); Pullrequest.Revision destination = pullRequest.getDestination(); String owner = destination.getRepository().getOwnerName(); String repositoryName = destination.getRepository().getRepositoryName(); - String destinationCommit = destination.getCommit().getHash(); String id = pullRequest.getId(); List comments = client.getPullRequestComments(owner, repositoryName, id); @@ -137,47 +122,23 @@ public class BitbucketRepository { continue; } - //These will match any start or finish message -- need to check commits - String project_build_start = String.format(BUILD_START_REGEX, builder.getProject().getDisplayName()); - String project_build_finished = String.format(BUILD_FINISH_REGEX, builder.getProject().getDisplayName()); - Matcher startMatcher = Pattern.compile(project_build_start, Pattern.CASE_INSENSITIVE).matcher(content); - Matcher finishMatcher = Pattern.compile(project_build_finished, Pattern.CASE_INSENSITIVE).matcher(content); - - if (startMatcher.find() || - finishMatcher.find()) { - - String sourceCommitMatch; - String destinationCommitMatch; - - if (startMatcher.find(0)) { - sourceCommitMatch = startMatcher.group(1); - destinationCommitMatch = startMatcher.group(2); - } else { - sourceCommitMatch = finishMatcher.group(1); - destinationCommitMatch = finishMatcher.group(2); - } - - //first check source commit -- if it doesn't match, just move on. If it does, investigate further. - if (sourceCommitMatch.equalsIgnoreCase(sourceCommit)) { - // if we're checking destination commits, and if this doesn't match, then move on. - if (this.trigger.getCheckDestinationCommit() - && (!destinationCommitMatch.equalsIgnoreCase(destinationCommit))) { - continue; - } - - shouldBuild = false; - break; - } - } - if (content.contains(BUILD_REQUEST_MARKER.toLowerCase())) { - shouldBuild = true; - break; + return true; } } } + + Pullrequest.Repository sourceRepository = source.getRepository(); + + if (this.client.hasBuildStatus(sourceRepository.getOwnerName(), sourceRepository.getRepositoryName(), sourceCommit)) { + logger.info("Commit " + sourceCommit + " has already been processed"); + return false; + } + + return true; } - return shouldBuild; + + return false; } private boolean isSkipBuild(String pullRequestTitle) { diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java index 713a061..c8f1818 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java @@ -9,7 +9,6 @@ import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.type.TypeReference; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.util.Collections; import java.util.List; import java.util.logging.Level; @@ -23,17 +22,20 @@ import hudson.ProxyConfiguration; */ public class ApiClient { private static final Logger logger = Logger.getLogger(ApiClient.class.getName()); - private static final String BITBUCKET_HOST = "bitbucket.org"; private static final String V1_API_BASE_URL = "https://bitbucket.org/api/1.0/repositories/"; private static final String V2_API_BASE_URL = "https://bitbucket.org/api/2.0/repositories/"; private String owner; private String repositoryName; private Credentials credentials; + private String key; + private String name; - public ApiClient(String username, String password, String owner, String repositoryName) { + public ApiClient(String username, String password, String owner, String repositoryName, String key, String name) { this.credentials = new UsernamePasswordCredentials(username, password); this.owner = owner; this.repositoryName = repositoryName; + this.key = key; + this.name = name; } public List getPullRequests() { @@ -54,26 +56,22 @@ public class ApiClient { return Collections.EMPTY_LIST; } - public void deletePullRequestComment(String pullRequestId, String commentId) { - String path = V1_API_BASE_URL + this.owner + "/" + this.repositoryName + "/pullrequests/" + pullRequestId + "/comments/" + commentId; - //https://bitbucket.org/api/1.0/repositories/{accountname}/{repo_slug}/pullrequests/{pull_request_id}/comments/{comment_id} - delete(path); + public boolean hasBuildStatus(String owner, String repositoryName, String revision) { + String url = v2(owner, repositoryName, "/commit/" + revision + "/statuses/build/" + this.key); + return get(url).contains("\"state\""); } - - public Pullrequest.Comment postPullRequestComment(String pullRequestId, String comment) { - try { - String response = post( - v1("/pullrequests/" + pullRequestId + "/comments"), - new NameValuePair[]{ new NameValuePair("content", comment) }); - return parse(response, Pullrequest.Comment.class); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - catch (IOException e) { - e.printStackTrace(); - } - return null; + public void setBuildStatus(String owner, String repositoryName, String revision, BuildState state, String buildUrl, String comment) { + String url = v2(owner, repositoryName, "/commit/" + revision + "/statuses/build"); + NameValuePair[] data = new NameValuePair[]{ + new NameValuePair("description", comment), + new NameValuePair("key", this.key), + new NameValuePair("name", this.name), + new NameValuePair("state", state.toString()), + new NameValuePair("url", buildUrl), + }; + logger.info("POST state " + state + " to " + url); + post(url, data); } public void deletePullRequestApproval(String pullRequestId) { @@ -115,8 +113,12 @@ public class ApiClient { return V1_API_BASE_URL + this.owner + "/" + this.repositoryName + url; } - private String v2(String url) { - return V2_API_BASE_URL + this.owner + "/" + this.repositoryName + url; + private String v2(String path) { + return v2(this.owner, this.repositoryName, path); + } + + private String v2(String owner, String repositoryName, String path) { + return V2_API_BASE_URL + owner + "/" + repositoryName + path; } private String get(String path) { diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/BuildState.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/BuildState.java new file mode 100644 index 0000000..e5c15ef --- /dev/null +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/BuildState.java @@ -0,0 +1,10 @@ +package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket; + +/** + * Valid build states for a pull request + * + * @see https://confluence.atlassian.com/bitbucket/buildstatus-resource-779295267.html + */ +public enum BuildState { + FAILED, INPROGRESS, SUCCESSFUL +} diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly index 0eee781..82ab08c 100644 --- a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly +++ b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly @@ -14,6 +14,12 @@ + + + + + + diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciKey.html b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciKey.html new file mode 100644 index 0000000..97696b6 --- /dev/null +++ b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciKey.html @@ -0,0 +1,4 @@ +The identifier needs to be unique among your Jenkins jobs related to this repo. +This identifier is used to decide whether a commit is already built by this job and to set status for a newly built commit. +If the value is changed rebuilds may occur and multiple statuses might show on an existing pull request. +The value is not shown for end users of Bitbucket. diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciName.html b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciName.html new file mode 100644 index 0000000..32f248d --- /dev/null +++ b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciName.html @@ -0,0 +1 @@ +This value is the name of the current job when showing build statuses for a pull request. -- cgit v1.2.3