Skip to content

Commit e6d9e7a

Browse files
authored
[JENKINS-75804] Webhook auto registration fail with PLUGIN implementation (#1079)
Add missing logic on when update a plugin hook. Add manage of which events must be subscribed.
1 parent 53206c2 commit e6d9e7a

File tree

14 files changed

+659
-92
lines changed

14 files changed

+659
-92
lines changed

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
<revision>936.4.4</revision>
2828
<changelist>-SNAPSHOT</changelist>
2929
<gitHubRepo>jenkinsci/bitbucket-branch-source-plugin</gitHubRepo>
30-
<jenkins.baseline>2.479</jenkins.baseline>
31-
<jenkins.version>${jenkins.baseline}.3</jenkins.version>
30+
<jenkins.baseline>2.504</jenkins.baseline>
31+
<jenkins.version>${jenkins.baseline}.1</jenkins.version>
3232
<hpi.compatibleSinceVersion>936.0.0</hpi.compatibleSinceVersion>
3333
<!-- Jenkins.MANAGE is still in Beta -->
3434
<useBeta>true</useBeta>

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ public void setServerUrl(@CheckForNull String serverUrl) {
295295
}
296296
}
297297

298+
@Restricted(NoExternalUse.class)
299+
@Deprecated(forRemoval = true)
300+
// expose if needed in BitbucketEndpointProvider, normally could be get from endpoint if not customized
298301
@NonNull
299302
public String getEndpointJenkinsRootURL() {
300303
return AbstractBitbucketEndpoint.getEndpointJenkinsRootUrl(serverUrl);

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

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

26+
import edu.umd.cs.findbugs.annotations.NonNull;
2627
import edu.umd.cs.findbugs.annotations.Nullable;
2728
import java.util.List;
2829

@@ -49,6 +50,7 @@ public interface BitbucketWebHook {
4950
/**
5051
* @return the list of events this webhook is notifying
5152
*/
53+
@NonNull
5254
List<String> getEvents();
5355

5456
/**

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/repository/BitbucketCloudHook.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
package com.cloudbees.jenkins.plugins.bitbucket.client.repository;
2525

2626
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketWebHook;
27+
import java.util.ArrayList;
2728
import java.util.List;
29+
import org.apache.commons.lang3.ObjectUtils;
2830

2931
public class BitbucketCloudHook implements BitbucketWebHook {
3032

@@ -38,7 +40,7 @@ public class BitbucketCloudHook implements BitbucketWebHook {
3840

3941
private boolean active;
4042

41-
private List<String> events;
43+
private List<String> events = new ArrayList<>();
4244

4345
@Override
4446
public String getDescription() {
@@ -73,7 +75,7 @@ public List<String> getEvents() {
7375
}
7476

7577
public void setEvents(List<String> events) {
76-
this.events = events;
78+
this.events = ObjectUtils.firstNonNull(events,new ArrayList<>());
7779
}
7880

7981
@Override
@@ -85,6 +87,7 @@ public void setUuid(String uuid) {
8587
this.uuid = uuid;
8688
}
8789

90+
@Override
8891
public String getSecret() {
8992
return secret;
9093
}

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
@Extension
6161
public class WebhookAutoRegisterListener extends ItemListener {
6262

63-
private static final Logger LOGGER = Logger.getLogger(WebhookAutoRegisterListener.class.getName());
63+
private static final Logger logger = Logger.getLogger(WebhookAutoRegisterListener.class.getName());
6464
private static ExecutorService executorService;
6565

6666
@Override
@@ -106,7 +106,7 @@ public void doRun() {
106106
try {
107107
registerHooks(owner);
108108
} catch (IOException e) {
109-
LOGGER.log(Level.WARNING, "Could not register hooks for " + owner.getFullName(), e);
109+
logger.log(Level.WARNING, e, () -> "Could not register hooks for " + owner.getFullName());
110110
}
111111
}
112112
});
@@ -119,7 +119,7 @@ public void doRun() {
119119
try {
120120
removeHooks(owner);
121121
} catch (IOException e) {
122-
LOGGER.log(Level.WARNING, "Could not deregister hooks for " + owner.getFullName(), e);
122+
logger.log(Level.WARNING, e, () -> "Could not deregister hooks for " + owner.getFullName());
123123
}
124124
}
125125
});
@@ -154,13 +154,13 @@ private synchronized void registerHooks(SCMSourceOwner owner) throws IOException
154154
case ITEM:
155155
break;
156156
}
157-
LOGGER.log(Level.WARNING, "Can not register hook. Jenkins root URL is not valid: {0}", rootUrl);
157+
logger.log(Level.WARNING, "Can not register hook. Jenkins root URL is not valid: {0}", rootUrl);
158158
// go on to try next source and its rootUrl
159159
}
160160
}
161161
}
162162

163-
private void registerHook(BitbucketSCMSource source) throws IOException {
163+
/* for test purpose */ void registerHook(BitbucketSCMSource source) throws IOException {
164164
BitbucketApi bitbucket = bitbucketApiFor(source);
165165
if (bitbucket == null) {
166166
return;
@@ -179,10 +179,10 @@ private void registerHook(BitbucketSCMSource source) throws IOException {
179179
.withTraits(source.getTraits())
180180
.webhookConfiguration();
181181
if (existingHook == null) {
182-
LOGGER.log(Level.INFO, "Registering hook for {0}/{1}", new Object[]{source.getRepoOwner(), source.getRepository()});
182+
logger.log(Level.INFO, "Registering hook for {0}/{1}", new Object[]{source.getRepoOwner(), source.getRepository()});
183183
bitbucket.registerCommitWebHook(hookConfig.getHook(source));
184184
} else if (hookConfig.updateHook(existingHook, source)) {
185-
LOGGER.log(Level.INFO, "Updating hook for {0}/{1}", new Object[]{source.getRepoOwner(), source.getRepository()});
185+
logger.log(Level.INFO, "Updating hook for {0}/{1}", new Object[]{source.getRepoOwner(), source.getRepository()});
186186
bitbucket.updateCommitWebHook(existingHook);
187187
}
188188
}
@@ -202,11 +202,11 @@ private void removeHooks(SCMSourceOwner owner) throws IOException {
202202
}
203203
}
204204
if (hook != null && !isUsedSomewhereElse(owner, source.getRepoOwner(), source.getRepository())) {
205-
LOGGER.log(Level.INFO, "Removing hook for {0}/{1}",
205+
logger.log(Level.INFO, "Removing hook for {0}/{1}",
206206
new Object[]{source.getRepoOwner(), source.getRepository()});
207207
bitbucket.removeCommitWebHook(hook);
208208
} else {
209-
LOGGER.log(Level.FINE, "NOT removing hook for {0}/{1} because does not exists or its used in other project",
209+
logger.log(Level.FINE, "NOT removing hook for {0}/{1} because does not exists or its used in other project",
210210
new Object[]{source.getRepoOwner(), source.getRepository()});
211211
}
212212
}
@@ -259,8 +259,8 @@ private boolean isUsedSomewhereElse(SCMSourceOwner owner, String repoOwner, Stri
259259
private List<BitbucketSCMSource> getBitbucketSCMSources(SCMSourceOwner owner) {
260260
List<BitbucketSCMSource> sources = new ArrayList<>();
261261
for (SCMSource source : owner.getSCMSources()) {
262-
if (source instanceof BitbucketSCMSource) {
263-
sources.add((BitbucketSCMSource) source);
262+
if (source instanceof BitbucketSCMSource scmSource) {
263+
sources.add(scmSource);
264264
}
265265
}
266266
return sources;

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

Lines changed: 93 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint;
2929
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider;
3030
import com.cloudbees.jenkins.plugins.bitbucket.client.repository.BitbucketCloudHook;
31+
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint;
3132
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint;
3233
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils;
3334
import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketPluginWebhook;
@@ -45,12 +46,15 @@
4546
import java.util.List;
4647
import java.util.Set;
4748
import java.util.TreeSet;
49+
import java.util.logging.Logger;
50+
import org.apache.commons.collections.CollectionUtils;
4851
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
4952

5053
/**
5154
* Contains the webhook configuration
5255
*/
5356
public class WebhookConfiguration {
57+
private static final Logger logger = Logger.getLogger(WebhookConfiguration.class.getName());
5458

5559
/**
5660
* The list of events available in Bitbucket Cloud.
@@ -81,6 +85,20 @@ public class WebhookConfiguration {
8185
HookEventType.SERVER_PULL_REQUEST_FROM_REF_UPDATED.getKey()
8286
));
8387

88+
// See https://help.moveworkforward.com/BPW/how-to-manage-configurations-using-post-webhooks-f#HowtomanageconfigurationsusingPostWebhooksforBitbucketAPIs?-Possibleeventtypes
89+
private static final List<String> PLUGIN_SERVER_EVENTS = Collections.unmodifiableList(Arrays.asList(
90+
"ABSTRACT_REPOSITORY_REFS_CHANGED", // push event
91+
"BRANCH_CREATED",
92+
"BRANCH_DELETED",
93+
"PULL_REQUEST_DECLINED",
94+
"PULL_REQUEST_DELETED",
95+
"PULL_REQUEST_MERGED",
96+
"PULL_REQUEST_OPENED",
97+
"PULL_REQUEST_REOPENED",
98+
"PULL_REQUEST_UPDATED",
99+
"REPOSITORY_MIRROR_SYNCHRONIZED", // not supported by the hookprocessor
100+
"TAG_CREATED"));
101+
84102
/**
85103
* The list of events available in Bitbucket Server v6.5+.
86104
*/
@@ -99,7 +117,7 @@ public class WebhookConfiguration {
99117
/**
100118
* The title of the webhook.
101119
*/
102-
private static final String description = "Jenkins hook";
120+
private static final String DESCRIPTION = "Jenkins hook";
103121

104122
/**
105123
* The comma separated list of committers to ignore.
@@ -121,43 +139,72 @@ public String getCommittersToIgnore() {
121139
boolean updateHook(BitbucketWebHook hook, BitbucketSCMSource owner) {
122140
boolean updated = false;
123141

142+
final String serverURL = owner.getServerUrl();
143+
final String rootURL = getEndpointJenkinsRootURL(serverURL);
124144
final String signatureSecret = getSecret(owner.getServerUrl());
125145

126146
if (hook instanceof BitbucketCloudHook cloudHook) {
127-
if (!hook.getEvents().containsAll(CLOUD_EVENTS)) {
128-
Set<String> events = new TreeSet<>(hook.getEvents());
129-
events.addAll(CLOUD_EVENTS);
130-
cloudHook.setEvents(new ArrayList<>(events));
147+
String url = getCloudWebhookURL(serverURL, rootURL);
148+
if (!Objects.equal(hook.getUrl(), url)) {
149+
cloudHook.setUrl(url);
131150
updated = true;
132151
}
152+
153+
List<String> events = hook.getEvents();
154+
if (!events.containsAll(CLOUD_EVENTS)) {
155+
Set<String> newEvents = new TreeSet<>(events);
156+
newEvents.addAll(CLOUD_EVENTS);
157+
cloudHook.setEvents(new ArrayList<>(newEvents));
158+
logger.info(() -> "Update cloud webhook because the following events was missing: " + CollectionUtils.subtract(CLOUD_EVENTS, events));
159+
updated = true;
160+
}
161+
133162
if (!Objects.equal(hook.getSecret(), signatureSecret)) {
134163
cloudHook.setSecret(signatureSecret);
135164
updated = true;
136165
}
137-
} else if (hook instanceof BitbucketPluginWebhook serverHook) {
138-
String hookCommittersToIgnore = Util.fixEmptyAndTrim(serverHook.getCommittersToIgnore());
166+
} else if (hook instanceof BitbucketPluginWebhook pluginHook) {
167+
String hookCommittersToIgnore = Util.fixEmptyAndTrim(pluginHook.getCommittersToIgnore());
139168
String thisCommittersToIgnore = Util.fixEmptyAndTrim(committersToIgnore);
140169
if (!Objects.equal(thisCommittersToIgnore, hookCommittersToIgnore)) {
141-
serverHook.setCommittersToIgnore(thisCommittersToIgnore);
170+
pluginHook.setCommittersToIgnore(thisCommittersToIgnore);
171+
updated = true;
172+
}
173+
174+
String url = getServerWebhookURL(serverURL, rootURL);
175+
if (!url.equals(pluginHook.getUrl())) {
176+
pluginHook.setUrl(url);
177+
updated = true;
178+
}
179+
180+
if (!pluginHook.isActive()) {
181+
pluginHook.setActive(true);
142182
updated = true;
143183
}
144-
} else if (hook instanceof BitbucketServerWebhook serverHook) {
145-
String serverURL = owner.getServerUrl();
146-
String url = getServerWebhookURL(serverURL, owner.getEndpointJenkinsRootURL());
147184

185+
List<String> supportedPluginEvents = getPluginServerEvents(serverURL);
186+
List<String> events = pluginHook.getEvents();
187+
if (!events.containsAll(supportedPluginEvents)) {
188+
Set<String> newEvents = new TreeSet<>(events);
189+
newEvents.addAll(supportedPluginEvents);
190+
pluginHook.setEvents(new ArrayList<>(newEvents));
191+
logger.info(() -> "Update plugin webhook because the following events was missing: " + CollectionUtils.subtract(supportedPluginEvents, events));
192+
updated = true;
193+
}
194+
} else if (hook instanceof BitbucketServerWebhook serverHook) {
195+
String url = getServerWebhookURL(serverURL, rootURL);
148196
if (!url.equals(serverHook.getUrl())) {
149197
serverHook.setUrl(url);
150198
updated = true;
151199
}
152200

201+
List<String> supportedNativeEvents = getNativeServerEvents(serverURL);
153202
List<String> events = serverHook.getEvents();
154-
if (events == null) {
155-
serverHook.setEvents(getNativeServerEvents(serverURL));
156-
updated = true;
157-
} else if (!events.containsAll(getNativeServerEvents(serverURL))) {
203+
if (!events.containsAll(supportedNativeEvents)) {
158204
Set<String> newEvents = new TreeSet<>(events);
159-
newEvents.addAll(getNativeServerEvents(serverURL));
205+
newEvents.addAll(supportedNativeEvents);
160206
serverHook.setEvents(new ArrayList<>(newEvents));
207+
logger.info(() -> "Update native webhook because the following events was missing: " + CollectionUtils.subtract(supportedNativeEvents, events));
161208
updated = true;
162209
}
163210

@@ -170,28 +217,34 @@ boolean updateHook(BitbucketWebHook hook, BitbucketSCMSource owner) {
170217
return updated;
171218
}
172219

220+
@NonNull
221+
private String getEndpointJenkinsRootURL(@NonNull String serverURL) {
222+
return AbstractBitbucketEndpoint.getEndpointJenkinsRootUrl(serverURL);
223+
}
224+
225+
@NonNull
173226
public BitbucketWebHook getHook(BitbucketSCMSource owner) {
174-
final String serverUrl = owner.getServerUrl();
175-
final String rootUrl = owner.getEndpointJenkinsRootURL();
227+
final String serverURL = owner.getServerUrl();
228+
final String rootURL = getEndpointJenkinsRootURL(serverURL);
176229
final String signatureSecret = getSecret(owner.getServerUrl());
177230

178-
if (BitbucketApiUtils.isCloud(serverUrl)) {
231+
if (BitbucketApiUtils.isCloud(serverURL)) {
179232
BitbucketCloudHook hook = new BitbucketCloudHook();
180233
hook.setEvents(CLOUD_EVENTS);
181234
hook.setActive(true);
182-
hook.setDescription(description);
183-
hook.setUrl(rootUrl + BitbucketSCMSourcePushHookReceiver.FULL_PATH);
235+
hook.setDescription(DESCRIPTION);
236+
hook.setUrl(getCloudWebhookURL(serverURL, rootURL));
184237
hook.setSecret(signatureSecret);
185238
return hook;
186239
}
187240

188-
switch (BitbucketServerEndpoint.findWebhookImplementation(serverUrl)) {
241+
switch (BitbucketServerEndpoint.findWebhookImplementation(serverURL)) {
189242
case NATIVE: {
190243
BitbucketServerWebhook hook = new BitbucketServerWebhook();
191244
hook.setActive(true);
192-
hook.setDescription(description);
193-
hook.setEvents(getNativeServerEvents(serverUrl));
194-
hook.setUrl(getServerWebhookURL(serverUrl, rootUrl));
245+
hook.setDescription(DESCRIPTION);
246+
hook.setEvents(getNativeServerEvents(serverURL));
247+
hook.setUrl(getServerWebhookURL(serverURL, rootURL));
195248
hook.setSecret(signatureSecret);
196249
return hook;
197250
}
@@ -200,8 +253,8 @@ public BitbucketWebHook getHook(BitbucketSCMSource owner) {
200253
default: {
201254
BitbucketPluginWebhook hook = new BitbucketPluginWebhook();
202255
hook.setActive(true);
203-
hook.setDescription(description);
204-
hook.setUrl(getServerWebhookURL(serverUrl, rootUrl));
256+
hook.setDescription(DESCRIPTION);
257+
hook.setUrl(getServerWebhookURL(serverURL, rootURL));
205258
hook.setCommittersToIgnore(committersToIgnore);
206259
return hook;
207260
}
@@ -224,9 +277,13 @@ private String getSecret(@NonNull String serverURL) {
224277
return null;
225278
}
226279

227-
private static List<String> getNativeServerEvents(String serverUrl) {
280+
private static List<String> getPluginServerEvents(String serverURL) {
281+
return PLUGIN_SERVER_EVENTS;
282+
}
283+
284+
private static List<String> getNativeServerEvents(String serverURL) {
228285
BitbucketServerEndpoint endpoint = BitbucketEndpointProvider
229-
.lookupEndpoint(serverUrl, BitbucketServerEndpoint.class)
286+
.lookupEndpoint(serverURL, BitbucketServerEndpoint.class)
230287
.orElse(null);
231288
if (endpoint != null) {
232289
switch (endpoint.getServerVersion()) {
@@ -256,6 +313,13 @@ private static List<String> getNativeServerEvents(String serverUrl) {
256313
return NATIVE_SERVER_EVENTS_v7;
257314
}
258315

316+
private static String getCloudWebhookURL(String serverURL, String rootURL) {
317+
return UriTemplate.buildFromTemplate(rootURL)
318+
.template(BitbucketSCMSourcePushHookReceiver.FULL_PATH)
319+
.build()
320+
.expand();
321+
}
322+
259323
private static String getServerWebhookURL(String serverURL, String rootURL) {
260324
return UriTemplate.buildFromTemplate(rootURL)
261325
.template(BitbucketSCMSourcePushHookReceiver.FULL_PATH)

0 commit comments

Comments
 (0)