Skip to content

feature/324 | skip non PR notifications #451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,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 +65,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 +161,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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The added md5Hex call looked surprising but I now see you just moved it from the BitbucketBuildStatus constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like it was added to avoid issues with longer names #15

new BitbucketChangesetCommentNotifier(bitbucket).buildStatus(status);
if (result != null) {
listener.getLogger().println("[Bitbucket] Build result notified");
Expand All @@ -184,6 +188,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 +204,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()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this have a special case for reporting the status of a pull request build?

Like, there is a pull request and it's first built once and the build status goes to Bitbucket all right. Then a user of Jenkins replays the build — there is no new commit. When the second build finishes, does the plugin decide not to report the status to Bitbucket because that commit already has one build status from a pull request — even though it's from the same pull request? It seems to me that the previous build status should be overwritten in that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for this case it shouldn't be a problem as notification for PRs being processed in the previous if statement, this is for "branches" only, you could check line 198

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 build status notifications if PR build already exists}" field="doNotOverridePrBuildStatuses">
<f:checkbox/>
</f:entry>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div>
Prevents a prematurely successful state of not fully tested PRs.
<br/>
<strong>"Exclude branches that are also filed as PRs"</strong> strategy implies that branch and PR builds
do share the same key while publishing the build status to Bitbucket, thus can overwrite statuses of each other.
Usually, it happens when the branch build finishes in the middle of a running PR build
so Bitbucket has been already notified about the branch status and
(in case this status was a success) we can merge the PR until the PR build is completed.

<br/>
<strong>NOTE:</strong> this option is only applicable when <strong>"Exclude branches that are also filed as PRs"</strong> strategy in multibranch configuration is used.
</div>