Skip to content

Commit 5a91093

Browse files
authored
[JENKINS-72780] The key value for the build notification is an UUID which couldn't be filter in required build feature of Bitbucket (#993)
Add an option in the build status notifications trait to use as id of the notification an human readable format so it is possible configure a filter in the Bitbucket server as described in https://confluence.atlassian.com/bitbucketserver/checks-for-merging-pull-requests-776640039.html
1 parent 94d2883 commit 5a91093

File tree

12 files changed

+137
-41
lines changed

12 files changed

+137
-41
lines changed

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotificationsTrait.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class BitbucketBuildStatusNotificationsTrait extends SCMSourceTrait {
4444
private boolean sendSuccessNotificationForUnstableBuild;
4545
private boolean sendStoppedNotificationForAbortBuild;
4646
private boolean disableNotificationForNotBuildJobs;
47+
private boolean useReadableNotificationIds = false;
4748
// seems that this attribute as been moved out to plugin skip-notifications-trait-plugin
4849
@SuppressFBWarnings("UUF_UNUSED_FIELD")
4950
private transient boolean disableNotifications;
@@ -109,15 +110,31 @@ public boolean getDisableNotificationForNotBuildJobs() {
109110
return this.disableNotificationForNotBuildJobs;
110111
}
111112

113+
/**
114+
* Use a readable id as key for the build notification status.
115+
*
116+
* @return if will not hash the generated key of the build notification
117+
* status.
118+
*/
119+
public boolean getUseReadableNotificationIds() {
120+
return useReadableNotificationIds;
121+
}
122+
123+
@DataBoundSetter
124+
public void setUseReadableNotificationIds(boolean useReadableNotificationIds) {
125+
this.useReadableNotificationIds = useReadableNotificationIds;
126+
}
127+
112128
/**
113129
* {@inheritDoc}
114130
*/
115131
@Override
116132
protected void decorateContext(SCMSourceContext<?, ?> context) {
117133
if (context instanceof BitbucketSCMSourceContext scmContext) {
118-
scmContext.withSendStopNotificationForNotBuildJobs(getDisableNotificationForNotBuildJobs());
119-
scmContext.withSendSuccessNotificationForUnstableBuild(getSendSuccessNotificationForUnstableBuild());
120-
scmContext.withSendStoppedNotificationForAbortBuild(getSendStoppedNotificationForAbortBuild());
134+
scmContext.withSendStopNotificationForNotBuildJobs(getDisableNotificationForNotBuildJobs())
135+
.withSendSuccessNotificationForUnstableBuild(getSendSuccessNotificationForUnstableBuild())
136+
.withSendStoppedNotificationForAbortBuild(getSendStoppedNotificationForAbortBuild())
137+
.withUseReadableNotificationIds(getUseReadableNotificationIds());
121138
}
122139
}
123140

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSourceContext.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ public class BitbucketSCMSourceContext extends SCMSourceContext<BitbucketSCMSour
102102
*/
103103
private boolean sendStopNotificationForNotBuildJobs;
104104

105+
/**
106+
* {@code false} will not hash the generated key of the build notification status.
107+
*/
108+
private boolean useReadableNotificationIds;
109+
105110
/**
106111
* Constructor.
107112
*
@@ -243,6 +248,10 @@ public boolean sendStopNotificationForNotBuildJobs() {
243248
return sendStopNotificationForNotBuildJobs;
244249
}
245250

251+
public boolean useReadableNotificationIds() {
252+
return useReadableNotificationIds;
253+
}
254+
246255
/**
247256
* Adds a requirement for branch details to any {@link BitbucketSCMSourceRequest} for this context.
248257
*
@@ -404,6 +413,11 @@ public final BitbucketSCMSourceContext withSendStopNotificationForNotBuildJobs(b
404413
return this;
405414
}
406415

416+
public final BitbucketSCMSourceContext withUseReadableNotificationIds(boolean useReadableNotificationIds) {
417+
this.useReadableNotificationIds = useReadableNotificationIds;
418+
return this;
419+
}
420+
407421
/**
408422
* {@inheritDoc}
409423
*/
@@ -412,4 +426,5 @@ public final BitbucketSCMSourceContext withSendStopNotificationForNotBuildJobs(b
412426
public BitbucketSCMSourceRequest newRequest(@NonNull SCMSource scmSource, TaskListener taskListener) {
413427
return new BitbucketSCMSourceRequest((BitbucketSCMSource) scmSource, this, taskListener);
414428
}
429+
415430
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketBuildStatus.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import com.fasterxml.jackson.annotation.JsonIgnore;
2727
import edu.umd.cs.findbugs.annotations.NonNull;
2828
import edu.umd.cs.findbugs.annotations.Nullable;
29-
import org.apache.commons.codec.digest.DigestUtils;
3029
import org.kohsuke.accmod.Restricted;
3130
import org.kohsuke.accmod.restrictions.DoNotUse;
3231

@@ -118,7 +117,7 @@ public BitbucketBuildStatus(String hash,
118117
this.description = description;
119118
this.state = state;
120119
this.url = url;
121-
this.key = DigestUtils.md5Hex(key);
120+
this.key = key;
122121
this.name = name;
123122
this.refname = refname;
124123
}
@@ -176,7 +175,7 @@ public String getKey() {
176175
}
177176

178177
public void setKey(String key) {
179-
this.key = DigestUtils.md5Hex(key);
178+
this.key = key;
180179
}
181180

182181
public String getName() {

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484

8585
import static java.util.concurrent.TimeUnit.HOURS;
8686
import static java.util.concurrent.TimeUnit.MINUTES;
87+
import static org.apache.commons.lang.StringUtils.abbreviate;
8788

8889
public class BitbucketCloudApiClient extends AbstractBitbucketApi implements BitbucketApi {
8990

@@ -610,7 +611,7 @@ public List<BitbucketRepositoryHook> getWebHooks() throws IOException, Interrupt
610611
@Override
611612
public void postBuildStatus(@NonNull BitbucketBuildStatus status) throws IOException, InterruptedException {
612613
BitbucketBuildStatus newStatus = new BitbucketBuildStatus(status);
613-
newStatus.setName(truncateMiddle(newStatus.getName(), 255));
614+
newStatus.setName(abbreviate(newStatus.getName(), 255));
614615

615616
String url = UriTemplate.fromTemplate(REPO_URL_TEMPLATE + "/commit/{hash}/statuses/build")
616617
.set("owner", owner)

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/client/AbstractBitbucketApi.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,6 @@ protected AbstractBitbucketApi(BitbucketAuthenticator authenticator) {
8484
this.authenticator = authenticator;
8585
}
8686

87-
protected String truncateMiddle(@CheckForNull String value, int maxLength) {
88-
int length = StringUtils.length(value);
89-
if (length > maxLength) {
90-
int halfLength = (maxLength - 3) / 2;
91-
return StringUtils.left(value, halfLength) + "..." + StringUtils.substring(value, -halfLength);
92-
} else {
93-
return value;
94-
}
95-
}
96-
9787
protected BitbucketRequestException buildResponseException(CloseableHttpResponse response, String errorMessage) {
9888
String headers = StringUtils.join(response.getAllHeaders(), "\n");
9989
String message = String.format("HTTP request error.%nStatus: %s%nResponse: %s%n%s", response.getStatusLine(), errorMessage, headers);

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/notifier/BitbucketBuildStatusNotifications.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import jenkins.scm.api.SCMRevision;
5959
import jenkins.scm.api.SCMRevisionAction;
6060
import jenkins.scm.api.SCMSource;
61+
import org.apache.commons.codec.digest.DigestUtils;
6162
import org.apache.commons.lang.StringUtils;
6263
import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider;
6364

@@ -130,6 +131,7 @@ private static void createStatus(@NonNull Run<?, ?> build,
130131
"IllegalStateException: " + e.getMessage());
131132
return;
132133
}
134+
boolean isCloud = BitbucketApiUtils.isCloud(client);
133135

134136
BitbucketSCMSourceContext context = new BitbucketSCMSourceContext(null, SCMHeadObserver.none())
135137
.withTraits(source.getTraits()); // NOSONAR
@@ -155,15 +157,15 @@ private static void createStatus(@NonNull Run<?, ?> build,
155157
statusDescription = StringUtils.defaultIfBlank(buildDescription, "This commit was not built (probably the build was skipped)");
156158
if (context.sendStopNotificationForNotBuildJobs()) {
157159
// Bitbucket Cloud and Server support different build states.
158-
state = (client instanceof BitbucketCloudApiClient) ? BitbucketBuildStatus.Status.STOPPED : BitbucketBuildStatus.Status.CANCELLED;
160+
state = isCloud ? BitbucketBuildStatus.Status.STOPPED : BitbucketBuildStatus.Status.CANCELLED;
159161
} else {
160162
state = BitbucketBuildStatus.Status.FAILED;
161163
}
162164
} else if (result != null) { // ABORTED etc.
163165
statusDescription = StringUtils.defaultIfBlank(buildDescription, "Something is wrong with the build of this commit.");
164166
if (context.sendStopNotificationForAbortBuild()) {
165167
// Bitbucket Cloud and Server support different build states.
166-
state = (client instanceof BitbucketCloudApiClient) ? BitbucketBuildStatus.Status.STOPPED : BitbucketBuildStatus.Status.CANCELLED;
168+
state = isCloud ? BitbucketBuildStatus.Status.STOPPED : BitbucketBuildStatus.Status.CANCELLED;
167169
} else {
168170
state = BitbucketBuildStatus.Status.FAILED;
169171
}
@@ -174,7 +176,11 @@ private static void createStatus(@NonNull Run<?, ?> build,
174176

175177
if (state != null) {
176178
BitbucketDefaulNotifier notifier = new BitbucketDefaulNotifier(client);
177-
BitbucketBuildStatus buildStatus = new BitbucketBuildStatus(hash, statusDescription, state, url, key, name, refName);
179+
String notificationKey = DigestUtils.md5Hex(key);
180+
if (context.useReadableNotificationIds() && !isCloud) {
181+
notificationKey = key.replace(' ', '_').toUpperCase();
182+
}
183+
BitbucketBuildStatus buildStatus = new BitbucketBuildStatus(hash, statusDescription, state, url, notificationKey, name, refName);
178184
buildStatus.setBuildDuration(build.getDuration());
179185
buildStatus.setBuildNumber(build.getNumber());
180186
// TODO testResults should be provided by an extension point that integrates JUnit or anything else plugin
@@ -266,9 +272,7 @@ private static String getHash(@CheckForNull SCMRevision revision) {
266272
return null;
267273
}
268274

269-
private static String getBuildKey(@NonNull Run<?, ?> build, String branch,
270-
boolean shareBuildKeyBetweenBranchAndPR) {
271-
275+
private static String getBuildKey(@NonNull Run<?, ?> build, String branch, boolean shareBuildKeyBetweenBranchAndPR) {
272276
// When the ExcludeOriginPRBranchesSCMHeadFilter filter is active, we want the
273277
// build status key to be the same between the branch project and the PR project.
274278
// This is to avoid having two build statuses when a branch goes into PR and

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
import jenkins.scm.api.SCMFile;
8888
import jenkins.scm.api.SCMFile.Type;
8989
import jenkins.scm.impl.avatars.AvatarImage;
90+
import org.apache.commons.codec.digest.DigestUtils;
9091
import org.apache.commons.io.IOUtils;
9192
import org.apache.commons.lang.StringUtils;
9293
import org.apache.http.HttpHost;
@@ -99,6 +100,7 @@
99100
import org.apache.http.message.BasicNameValuePair;
100101

101102
import static java.util.Objects.requireNonNull;
103+
import static org.apache.commons.lang.StringUtils.abbreviate;
102104

103105
/**
104106
* Bitbucket API client.
@@ -493,7 +495,12 @@ public void postCommitComment(@NonNull String hash, @NonNull String comment) thr
493495
@Override
494496
public void postBuildStatus(@NonNull BitbucketBuildStatus status) throws IOException, InterruptedException {
495497
BitbucketServerBuildStatus newStatus = new BitbucketServerBuildStatus(status);
496-
newStatus.setName(truncateMiddle(newStatus.getName(), 255));
498+
newStatus.setName(abbreviate(newStatus.getName(), 255));
499+
500+
String key = status.getKey();
501+
if (StringUtils.length(key) > 255) {
502+
newStatus.setKey(abbreviate(key, 255 - 33) + '/' + DigestUtils.md5Hex(key));
503+
}
497504

498505
String url = UriTemplate.fromTemplate(this.baseURL + API_COMMIT_STATUS_PATH)
499506
.set("owner", getUserCentricOwner())
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
<?jelly escape-by-default='true'?>
22
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
3-
<f:entry title="${%Communicate unstable builds to Bitbucket as successful}" field="sendSuccessNotificationForUnstableBuild">
4-
<f:checkbox/>
5-
</f:entry>
6-
<f:entry title="${%Communicate not-built jobs to Bitbucket as stopped}" field="disableNotificationForNotBuildJobs">
7-
<f:checkbox/>
8-
</f:entry>
9-
<f:entry title="${%Communicate abort build to Bitbucket as stopped}" field="sendStoppedNotificationForAbortBuild">
10-
<f:checkbox/>
11-
</f:entry>
3+
<f:entry title="${%Communicate unstable builds to Bitbucket as successful}" field="sendSuccessNotificationForUnstableBuild">
4+
<f:checkbox />
5+
</f:entry>
6+
<f:entry title="${%Communicate not-built jobs to Bitbucket as stopped}" field="disableNotificationForNotBuildJobs">
7+
<f:checkbox />
8+
</f:entry>
9+
<f:entry title="${%Communicate abort build to Bitbucket as stopped}" field="sendStoppedNotificationForAbortBuild">
10+
<f:checkbox />
11+
</f:entry>
12+
<f:entry title="${%Do not hash the build status notification key}" field="useReadableNotificationIds">
13+
<f:checkbox />
14+
</f:entry>
1215
</j:jelly>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<div>
2+
By enabling this feature, the key used to send a build notification status is not hashed. This allow filtering
3+
in Bitbucket Data Center (only) for <a href="https://confluence.atlassian.com/bitbucketserver/checks-for-merging-pull-requests-776640039.html">
4+
Required builds merge check</a>.
5+
<p>
6+
Please note that changing this value will cause all build-time commits (e.g. pull requests) to have a duplicate build notification status due to the key change.
7+
</p>
8+
</div>

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClientTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ void verify_status_notitication_name_max_length() throws Exception {
8282
status.setName(RandomStringUtils.randomAlphanumeric(300));
8383
status.setState(Status.INPROGRESS);
8484
status.setHash("046d9a3c1532acf4cf08fe93235c00e4d673c1d3");
85+
status.setKey("PRJ/REPO");
8586

8687
client.postBuildStatus(status);
8788

0 commit comments

Comments
 (0)