Skip to content

Commit ef352ec

Browse files
authored
Add Bitbucket Server Version option (#332)
* Add Bitbucket Server Version option This works around incompatibilities between older versions of bitbucket and bitbucket v7.x. Let's users specify their version. * Make enums not use anonymous classes * Add tests and simpler default * Reworded help * Clean up call code There's no reason to have two separate loops over all pull requests.
1 parent a08234e commit ef352ec

File tree

9 files changed

+268
-44
lines changed

9 files changed

+268
-44
lines changed

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*/
2424
package com.cloudbees.jenkins.plugins.bitbucket.endpoints;
2525

26+
import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerVersion;
2627
import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation;
2728
import com.cloudbees.plugins.credentials.common.StandardCredentials;
2829
import com.damnhandy.uri.template.UriTemplate;
@@ -34,6 +35,7 @@
3435
import hudson.util.ListBoxModel;
3536
import java.net.MalformedURLException;
3637
import java.net.URL;
38+
import java.util.Objects;
3739
import javax.annotation.Nonnull;
3840
import jenkins.scm.api.SCMName;
3941
import org.apache.commons.lang.StringUtils;
@@ -80,6 +82,11 @@ public class BitbucketServerEndpoint extends AbstractBitbucketEndpoint {
8082
@NonNull
8183
private BitbucketServerWebhookImplementation webhookImplementation = BitbucketServerWebhookImplementation.PLUGIN;
8284

85+
/**
86+
* The server version for this endpoint.
87+
*/
88+
private BitbucketServerVersion serverVersion = BitbucketServerVersion.VERSION_7;
89+
8390
/**
8491
* Whether to always call the can merge api when retrieving pull requests.
8592
*/
@@ -142,6 +149,26 @@ public void setCallChanges(boolean callChanges) {
142149
this.callChanges = callChanges;
143150
}
144151

152+
@NonNull
153+
public static BitbucketServerVersion findServerVersion(String serverUrl) {
154+
final AbstractBitbucketEndpoint endpoint = BitbucketEndpointConfiguration.get().findEndpoint(serverUrl);
155+
if (endpoint instanceof BitbucketServerEndpoint) {
156+
return ((BitbucketServerEndpoint) endpoint).getServerVersion();
157+
}
158+
159+
return BitbucketServerVersion.VERSION_7;
160+
}
161+
162+
@NonNull
163+
public BitbucketServerVersion getServerVersion() {
164+
return this.serverVersion;
165+
}
166+
167+
@DataBoundSetter
168+
public void setServerVersion(@NonNull BitbucketServerVersion serverVersion) {
169+
this.serverVersion = Objects.requireNonNull(serverVersion);
170+
}
171+
145172
/**
146173
* {@inheritDoc}
147174
*/
@@ -181,6 +208,10 @@ private Object readResolve() {
181208
if (getBitbucketJenkinsRootUrl() != null) {
182209
setBitbucketJenkinsRootUrl(getBitbucketJenkinsRootUrl());
183210
}
211+
if (serverVersion == null) {
212+
serverVersion = BitbucketServerVersion.VERSION_7;
213+
}
214+
184215
return this;
185216
}
186217

@@ -218,6 +249,16 @@ public ListBoxModel doFillWebhookImplementationItems() {
218249
return items;
219250
}
220251

252+
@Restricted(NoExternalUse.class)
253+
public ListBoxModel doFillServerVersionItems() {
254+
ListBoxModel items = new ListBoxModel();
255+
for (BitbucketServerVersion serverVersion : BitbucketServerVersion.values()) {
256+
items.add(serverVersion, serverVersion.name());
257+
}
258+
259+
return items;
260+
}
261+
221262
/**
222263
* Checks that the supplied URL is valid.
223264
*

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookConfiguration.java

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@
2626
import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource;
2727
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketWebHook;
2828
import com.cloudbees.jenkins.plugins.bitbucket.client.repository.BitbucketRepositoryHook;
29+
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint;
2930
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint;
31+
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration;
3032
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint;
33+
import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerVersion;
3134
import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerWebhook;
3235
import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.NativeBitbucketServerWebhook;
3336
import com.damnhandy.uri.template.UriTemplate;
@@ -56,19 +59,25 @@ public class WebhookConfiguration {
5659
));
5760

5861
/**
59-
* The list of events available in Bitbucket Server.
62+
* The list of events available in Bitbucket Server v7.x.
6063
*/
61-
private static final List<String> NATIVE_SERVER_EVENTS = Collections.unmodifiableList(Arrays.asList(
64+
private static final List<String> NATIVE_SERVER_EVENTS_v7 = Collections.unmodifiableList(Arrays.asList(
6265
HookEventType.SERVER_REFS_CHANGED.getKey(),
6366
HookEventType.SERVER_PULL_REQUEST_OPENED.getKey(),
6467
HookEventType.SERVER_PULL_REQUEST_MERGED.getKey(),
6568
HookEventType.SERVER_PULL_REQUEST_DECLINED.getKey(),
6669
HookEventType.SERVER_PULL_REQUEST_DELETED.getKey(),
6770
HookEventType.SERVER_PULL_REQUEST_MODIFIED.getKey(),
6871
HookEventType.SERVER_PULL_REQUEST_REVIEWER_UPDATED.getKey(),
72+
// only on v7.x and above
6973
HookEventType.SERVER_PULL_REQUEST_FROM_REF_UPDATED.getKey()
7074
));
7175

76+
/**
77+
* The list of events available in Bitbucket Server v6.x.
78+
*/
79+
private static final List<String> NATIVE_SERVER_EVENTS_v6 = Collections.unmodifiableList(NATIVE_SERVER_EVENTS_v7.subList(0, 6));
80+
7281
/**
7382
* The title of the webhook.
7483
*/
@@ -131,7 +140,8 @@ boolean updateHook(BitbucketWebHook hook, BitbucketSCMSource owner) {
131140
boolean updated = false;
132141

133142
NativeBitbucketServerWebhook serverHook = (NativeBitbucketServerWebhook) hook;
134-
String url = getNativeServerWebhookUrl(owner.getServerUrl(), owner.getEndpointJenkinsRootUrl());
143+
String serverUrl = owner.getServerUrl();
144+
String url = getNativeServerWebhookUrl(serverUrl, owner.getEndpointJenkinsRootUrl());
135145

136146
if (!url.equals(serverHook.getUrl())) {
137147
serverHook.setUrl(url);
@@ -140,11 +150,11 @@ boolean updateHook(BitbucketWebHook hook, BitbucketSCMSource owner) {
140150

141151
List<String> events = serverHook.getEvents();
142152
if (events == null) {
143-
serverHook.setEvents(NATIVE_SERVER_EVENTS);
153+
serverHook.setEvents(getNativeServerEvents(serverUrl));
144154
updated = true;
145-
} else if (!events.containsAll(NATIVE_SERVER_EVENTS)) {
155+
} else if (!events.containsAll(getNativeServerEvents(serverUrl))) {
146156
Set<String> newEvents = new TreeSet<>(events);
147-
newEvents.addAll(NATIVE_SERVER_EVENTS);
157+
newEvents.addAll(getNativeServerEvents(serverUrl));
148158
serverHook.setEvents(new ArrayList<>(newEvents));
149159
updated = true;
150160
}
@@ -158,6 +168,7 @@ boolean updateHook(BitbucketWebHook hook, BitbucketSCMSource owner) {
158168
public BitbucketWebHook getHook(BitbucketSCMSource owner) {
159169
final String serverUrl = owner.getServerUrl();
160170
final String rootUrl = owner.getEndpointJenkinsRootUrl();
171+
161172
if (BitbucketCloudEndpoint.SERVER_URL.equals(serverUrl)) {
162173
BitbucketRepositoryHook hook = new BitbucketRepositoryHook();
163174
hook.setEvents(CLOUD_EVENTS);
@@ -171,7 +182,7 @@ public BitbucketWebHook getHook(BitbucketSCMSource owner) {
171182
case NATIVE: {
172183
NativeBitbucketServerWebhook hook = new NativeBitbucketServerWebhook();
173184
hook.setActive(true);
174-
hook.setEvents(NATIVE_SERVER_EVENTS);
185+
hook.setEvents(getNativeServerEvents(serverUrl));
175186
hook.setDescription(description);
176187
hook.setUrl(getNativeServerWebhookUrl(serverUrl, rootUrl));
177188
return hook;
@@ -189,6 +200,19 @@ public BitbucketWebHook getHook(BitbucketSCMSource owner) {
189200
}
190201
}
191202

203+
private static List<String> getNativeServerEvents(String serverUrl) {
204+
AbstractBitbucketEndpoint endpoint = BitbucketEndpointConfiguration.get().findEndpoint(serverUrl);
205+
if (endpoint instanceof BitbucketServerEndpoint) {
206+
if (((BitbucketServerEndpoint)endpoint).getServerVersion().equals(BitbucketServerVersion.VERSION_6)) {
207+
return NATIVE_SERVER_EVENTS_v6;
208+
}
209+
}
210+
211+
// Not specifically v6, use v7.
212+
// Better to give an error than quietly not register some events.
213+
return NATIVE_SERVER_EVENTS_v7;
214+
}
215+
192216
private static String getNativeServerWebhookUrl(String serverUrl, String rootUrl) {
193217
return UriTemplate.buildFromTemplate(rootUrl)
194218
.template(BitbucketSCMSourcePushHookReceiver.FULL_PATH)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* The MIT License
3+
*
4+
* Copyright (c) 2016-2020, CloudBees Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
package com.cloudbees.jenkins.plugins.bitbucket.server;
25+
26+
import hudson.model.ModelObject;
27+
28+
public enum BitbucketServerVersion implements ModelObject {
29+
VERSION_7("Bitbucket v7.x (and later)"),
30+
VERSION_6("Bitbucket v6.x (and earlier)");
31+
32+
private final String displayName;
33+
34+
BitbucketServerVersion(String displayName) {
35+
this.displayName = displayName;
36+
}
37+
38+
@Override
39+
public String getDisplayName() {
40+
return displayName;
41+
}
42+
43+
}
44+

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/BitbucketServerWebhookImplementation.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,19 @@
2828
/** The different webhook implementations available for Bitbucket Server. */
2929
public enum BitbucketServerWebhookImplementation implements ModelObject {
3030
/** Plugin-based webhooks. */
31-
PLUGIN {
32-
@Override
33-
public String getDisplayName() {
34-
return "Plugin";
35-
}
36-
},
31+
PLUGIN("Plugin"),
3732

3833
/** Native webhooks, available since Bitbucket Server 5.4. */
39-
NATIVE {
40-
@Override
41-
public String getDisplayName() {
42-
return "Native";
43-
}
34+
NATIVE("Native");
35+
36+
private final String displayName;
37+
38+
BitbucketServerWebhookImplementation(String displayName) {
39+
this.displayName = displayName;
40+
}
41+
42+
@Override
43+
public String getDisplayName() {
44+
return displayName;
4445
}
4546
}

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

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration;
4343
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint;
4444
import com.cloudbees.jenkins.plugins.bitbucket.filesystem.BitbucketSCMFile;
45+
import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerVersion;
4546
import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation;
4647
import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBranch;
4748
import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBranches;
@@ -334,25 +335,21 @@ private List<BitbucketServerPullRequest> getPullRequests(UriTemplate template)
334335
|| pr.getSource().getBranch() == null
335336
|| pr.getDestination().getBranch() == null);
336337

337-
// set commit closure to make commit informations available when need, in a similar way to when request branches
338+
AbstractBitbucketEndpoint endpointConfig = BitbucketEndpointConfiguration.get().findEndpoint(baseURL);
339+
final BitbucketServerEndpoint endpoint = endpointConfig instanceof BitbucketServerEndpoint ?
340+
(BitbucketServerEndpoint) endpointConfig : null;
341+
338342
for (BitbucketServerPullRequest pullRequest : pullRequests) {
343+
// set commit closure to make commit informations available when need, in a similar way to when request branches
339344
setupClosureForPRBranch(pullRequest);
340-
}
341-
342-
AbstractBitbucketEndpoint endpointConfig = BitbucketEndpointConfiguration.get().findEndpoint(baseURL);
343-
if (endpointConfig instanceof BitbucketServerEndpoint) {
344-
final BitbucketServerEndpoint endpoint = (BitbucketServerEndpoint) endpointConfig;
345-
if (!endpoint.isCallCanMerge() && !endpoint.isCallChanges()) {
346-
return pullRequests;
347-
}
348345

349-
// This is required for Bitbucket Server to update the refs/pull-requests/* references
350-
// See https://community.atlassian.com/t5/Bitbucket-questions/Change-pull-request-refs-after-Commit-instead-of-after-Approval/qaq-p/194702#M6829
351-
for (BitbucketServerPullRequest pullRequest : pullRequests) {
346+
if (endpoint != null) {
347+
// This is required for Bitbucket Server to update the refs/pull-requests/* references
348+
// See https://community.atlassian.com/t5/Bitbucket-questions/Change-pull-request-refs-after-Commit-instead-of-after-Approval/qaq-p/194702#M6829
352349
if (endpoint.isCallCanMerge()) {
353350
pullRequest.setCanMerge(getPullRequestCanMergeById(pullRequest.getId()));
354351
}
355-
if (endpoint.isCallChanges()) {
352+
if (endpoint.isCallChanges() && BitbucketServerVersion.VERSION_7.equals(endpoint.getServerVersion())) {
356353
callPullRequestChangesById(pullRequest.getId());
357354
}
358355
}

src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint/config-detail.jelly

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
<f:entry title="${%Server URL}" field="serverUrl">
88
<f:textbox/>
99
</f:entry>
10+
<f:entry title="${%Server Version}" field="serverVersion">
11+
<f:select />
12+
</f:entry>
1013
<f:entry field="callCanMerge">
1114
<f:checkbox title="${%Call Can Merge}" default="true"/>
1215
</f:entry>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div>
2+
Select the version of Bitbucket server for this endpoint. Some features may not work if this setting does not match the version of the referenced server.
3+
</div>

0 commit comments

Comments
 (0)