Skip to content

Commit

Permalink
feature/324 | Skip branch notifications once PR build has started
Browse files Browse the repository at this point in the history
  • Loading branch information
sams-gleb committed Nov 30, 2021
1 parent 64d8355 commit d2999e0
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.cloudbees.jenkins.plugins.bitbucket.BranchDiscoveryTrait.ExcludeOriginPRBranchesSCMHeadFilter;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatuses;
import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudApiClient;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
Expand All @@ -42,12 +43,15 @@
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.regex.Pattern;
import jenkins.model.JenkinsLocationConfiguration;
import jenkins.plugins.git.AbstractGitSCMSource;
import jenkins.scm.api.SCMHeadObserver;
import jenkins.scm.api.SCMRevision;
import jenkins.scm.api.SCMRevisionAction;
import jenkins.scm.api.SCMSource;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider;

Expand All @@ -62,6 +66,7 @@ public class BitbucketBuildStatusNotifications {
private static final String FAILED_STATE = "FAILED";
private static final String STOPPED_STATE = "STOPPED";
private static final String INPROGRESS_STATE = "INPROGRESS";
private static final Pattern PR_BUILD_PATTERN = Pattern.compile("^.*/job/PR-(\\d+)/(\\d+)/.*$");

private static String getRootURL(@NonNull Run<?, ?> build) {
JenkinsLocationConfiguration cfg = JenkinsLocationConfiguration.get();
Expand Down Expand Up @@ -157,7 +162,7 @@ private static void createStatus(@NonNull Run<?, ?> build, @NonNull TaskListener
statusDescription = StringUtils.defaultIfBlank(buildDescription, "The build is in progress...");
state = INPROGRESS_STATE;
}
status = new BitbucketBuildStatus(hash, statusDescription, state, url, key, name);
status = new BitbucketBuildStatus(hash, statusDescription, state, url, DigestUtils.md5Hex(key), name);
new BitbucketChangesetCommentNotifier(bitbucket).buildStatus(status);
if (result != null) {
listener.getLogger().println("[Bitbucket] Build result notified");
Expand All @@ -184,6 +189,7 @@ private static void sendNotifications(BitbucketSCMSource source, Run<?, ?> build
if (hash == null) {
return;
}

boolean shareBuildKeyBetweenBranchAndPR = sourceContext
.filters().stream()
.anyMatch(filter -> filter instanceof ExcludeOriginPRBranchesSCMHeadFilter);
Expand All @@ -199,6 +205,15 @@ private static void sendNotifications(BitbucketSCMSource source, Run<?, ?> build
listener.getLogger().println("[Bitbucket] Notifying commit build result");
key = getBuildKey(build, r.getHead().getName(), shareBuildKeyBetweenBranchAndPR);
bitbucket = source.buildBitbucketClient();
if (sourceContext.doNotOverridePrBuildStatuses()) {
List<BitbucketBuildStatus> buildStatuses = bitbucket.getBuildStatus(hash).getValues();
for (final BitbucketBuildStatus bs : buildStatuses) {
if (DigestUtils.md5Hex(key).equals(bs.getKey()) && PR_BUILD_PATTERN.matcher(bs.getUrl()).matches()) {
listener.getLogger().println("[Bitbucket] Skip branch notification as this revision has PR build status");
return;
}
}
}
}
createStatus(build, listener, bitbucket, key, hash);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public class BitbucketBuildStatusNotificationsTrait extends SCMSourceTrait {
*/
private boolean sendSuccessNotificationForUnstableBuild;


private boolean doNotOverridePrBuildStatuses;

/**
* Constructor.
*
Expand All @@ -61,19 +64,29 @@ public void setSendSuccessNotificationForUnstableBuild(boolean isSendSuccess) {
sendSuccessNotificationForUnstableBuild = isSendSuccess;
}

@DataBoundSetter
public void setDoNotOverridePrBuildStatuses(boolean isDoNotOverridePrBuildStatuses) {
doNotOverridePrBuildStatuses = isDoNotOverridePrBuildStatuses;
}

/**
* @return if unstable builds will be communicated as successful
*/
public boolean getSendSuccessNotificationForUnstableBuild() {
return this.sendSuccessNotificationForUnstableBuild;
}

public boolean getDoNotOverridePrBuildStatuses() {
return this.doNotOverridePrBuildStatuses;
}

/**
* {@inheritDoc}
*/
@Override
protected void decorateContext(SCMSourceContext<?, ?> context) {
((BitbucketSCMSourceContext) context).withSendSuccessNotificationForUnstableBuild(getSendSuccessNotificationForUnstableBuild());
((BitbucketSCMSourceContext) context).withDoNotOverridePrBuildStatuses(getDoNotOverridePrBuildStatuses());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ public class BitbucketSCMSourceContext extends SCMSourceContext<BitbucketSCMSour
*/
private boolean sendSuccessNotificationForUnstableBuild;

private boolean doNotOverridePrBuildStatuses;

/**
* Constructor.
*
Expand Down Expand Up @@ -215,6 +217,10 @@ public final boolean sendSuccessNotificationForUnstableBuild() {
return sendSuccessNotificationForUnstableBuild;
}

public final boolean doNotOverridePrBuildStatuses() {
return doNotOverridePrBuildStatuses;
}

/**
* Adds a requirement for branch details to any {@link BitbucketSCMSourceRequest} for this context.
*
Expand Down Expand Up @@ -352,6 +358,12 @@ public final BitbucketSCMSourceContext withSendSuccessNotificationForUnstableBui
return this;
}

@NonNull
public final BitbucketSCMSourceContext withDoNotOverridePrBuildStatuses(boolean doNotOverwritePrBuildStatus) {
this.doNotOverridePrBuildStatuses = doNotOverwritePrBuildStatus;
return this;
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,15 @@ List<? extends BitbucketRepository> getRepositories(@CheckForNull UserRoleInRepo
*/
void postBuildStatus(@NonNull BitbucketBuildStatus status) throws IOException, InterruptedException;

/**
* Set the build status for the given commit hash.
*
* @param hash get the status object for specified key
* @throws IOException if there was a network communications error.
* @throws InterruptedException if interrupted while waiting on remote communications.
*/
BitbucketBuildStatuses getBuildStatus(@NonNull String hash) throws IOException, InterruptedException;

/**
* Returns {@code true} if and only if the repository is private.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
package com.cloudbees.jenkins.plugins.bitbucket.api;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.apache.commons.codec.digest.DigestUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;

Expand Down Expand Up @@ -71,7 +70,7 @@ public BitbucketBuildStatus(String hash, String description, String state, Strin
this.description = description;
this.state = state;
this.url = url;
this.key = DigestUtils.md5Hex(key);
this.key = key;
this.name = name;
}

Expand Down Expand Up @@ -112,7 +111,7 @@ public String getKey() {
}

public void setKey(String key) {
this.key = DigestUtils.md5Hex(key);
this.key = key;
}

public String getName() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.cloudbees.jenkins.plugins.bitbucket.api;

import java.util.Collections;
import java.util.List;

public class BitbucketBuildStatuses {
private List<BitbucketBuildStatus> values;

public List<BitbucketBuildStatus> getValues() {
return values == null ? Collections.<BitbucketBuildStatus>emptyList() : Collections.unmodifiableList(values);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatuses;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCloudWorkspace;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketException;
Expand Down Expand Up @@ -648,6 +649,21 @@ public void postBuildStatus(@NonNull BitbucketBuildStatus status) throws IOExcep
postRequest(url, JsonParser.toJson(status));
}

@Override
public BitbucketBuildStatuses getBuildStatus(@NonNull String hash) throws IOException, InterruptedException {
String url = UriTemplate.fromTemplate(REPO_URL_TEMPLATE + "/commit/{hash}/statuses/build")
.set("owner", owner)
.set("repo", repositoryName)
.set("hash", hash)
.expand();
String response = getRequest(url);
try {
return JsonParser.toJava(response, BitbucketBuildStatuses.class);
} catch (IOException e) {
throw new IOException("I/O error when accessing URL: " + url, e);
}
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatuses;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository;
Expand Down Expand Up @@ -510,6 +511,22 @@ public void postBuildStatus(@NonNull BitbucketBuildStatus status) throws IOExcep
);
}

/**
* {@inheritDoc}
*/
public BitbucketBuildStatuses getBuildStatus(@NonNull String hash) throws IOException {
String url = UriTemplate
.fromTemplate(API_COMMIT_STATUS_PATH)
.set("hash", hash)
.expand();
String response = getRequest(url);
try {
return JsonParser.toJava(response, BitbucketBuildStatuses.class);
} catch (IOException e) {
throw new IOException("I/O error when accessing URL: " + url, e);
}
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
<f:entry title="${%Communicate Unstable builds to Bitbucket as Successful}" field="sendSuccessNotificationForUnstableBuild">
<f:checkbox/>
</f:entry>
</j:jelly>
<f:entry title="${%Skip branch notifications once PR build has started to prevent preliminary successful state of not fully tested PR}" field="doNotOverridePrBuildStatuses">
<f:checkbox/>
</f:entry>
</j:jelly>

0 comments on commit d2999e0

Please sign in to comment.