Skip to content

Commit 3ee7e7f

Browse files
authored
[JENKINS-75804] Webhook auto registration fail with PLUGIN implementation
Add missing logic on when update a plugin hook. Add manage of which events must be subscribed.
1 parent 29da7dd commit 3ee7e7f

File tree

13 files changed

+656
-90
lines changed

13 files changed

+656
-90
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,9 @@ public void setServerUrl(@CheckForNull String serverUrl) {
294294
}
295295
}
296296

297+
@Restricted(NoExternalUse.class)
297298
@Deprecated(since = "936.4.0", forRemoval = true)
299+
// expose if needed in BitbucketEndpointProvider, normally could be get from endpoint if not customized
298300
@NonNull
299301
public String getEndpointJenkinsRootURL() {
300302
return BitbucketEndpointProvider.lookupEndpointJenkinsRootURL(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
@@ -59,7 +59,7 @@
5959
@Extension
6060
public class WebhookAutoRegisterListener extends ItemListener {
6161

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

6565
@Override
@@ -105,7 +105,7 @@ public void doRun() {
105105
try {
106106
registerHooks(owner);
107107
} catch (IOException e) {
108-
LOGGER.log(Level.WARNING, "Could not register hooks for " + owner.getFullName(), e);
108+
logger.log(Level.WARNING, e, () -> "Could not register hooks for " + owner.getFullName());
109109
}
110110
}
111111
});
@@ -118,7 +118,7 @@ public void doRun() {
118118
try {
119119
removeHooks(owner);
120120
} catch (IOException e) {
121-
LOGGER.log(Level.WARNING, "Could not deregister hooks for " + owner.getFullName(), e);
121+
logger.log(Level.WARNING, e, () -> "Could not deregister hooks for " + owner.getFullName());
122122
}
123123
}
124124
});
@@ -153,13 +153,13 @@ private synchronized void registerHooks(SCMSourceOwner owner) throws IOException
153153
case ITEM:
154154
break;
155155
}
156-
LOGGER.log(Level.WARNING, "Can not register hook. Jenkins root URL is not valid: {0}", rootUrl);
156+
logger.log(Level.WARNING, "Can not register hook. Jenkins root URL is not valid: {0}", rootUrl);
157157
// go on to try next source and its rootUrl
158158
}
159159
}
160160
}
161161

162-
private void registerHook(BitbucketSCMSource source) throws IOException {
162+
/* for test purpose */ void registerHook(BitbucketSCMSource source) throws IOException {
163163
BitbucketApi bitbucket = getClientBySource(source);
164164
if (bitbucket == null) {
165165
return;
@@ -178,10 +178,10 @@ private void registerHook(BitbucketSCMSource source) throws IOException {
178178
.withTraits(source.getTraits())
179179
.webhookConfiguration();
180180
if (existingHook == null) {
181-
LOGGER.log(Level.INFO, "Registering hook for {0}/{1}", new Object[]{source.getRepoOwner(), source.getRepository()});
181+
logger.log(Level.INFO, "Registering hook for {0}/{1}", new Object[] { source.getRepoOwner(), source.getRepository() });
182182
bitbucket.registerCommitWebHook(hookConfig.getHook(source));
183183
} else if (hookConfig.updateHook(existingHook, source)) {
184-
LOGGER.log(Level.INFO, "Updating hook for {0}/{1}", new Object[]{source.getRepoOwner(), source.getRepository()});
184+
logger.log(Level.INFO, "Updating hook for {0}/{1}", new Object[] { source.getRepoOwner(), source.getRepository() });
185185
bitbucket.updateCommitWebHook(existingHook);
186186
}
187187
}
@@ -205,12 +205,12 @@ private void removeHooks(SCMSourceOwner owner) throws IOException {
205205
}
206206
}
207207
if (hook != null && !isUsedSomewhereElse(owner, source.getRepoOwner(), source.getRepository())) {
208-
LOGGER.log(Level.INFO, "Removing hook for {0}/{1}",
209-
new Object[]{source.getRepoOwner(), source.getRepository()});
208+
logger.log(Level.INFO, "Removing hook for {0}/{1}",
209+
new Object[] { source.getRepoOwner(), source.getRepository() });
210210
bitbucket.removeCommitWebHook(hook);
211211
} else {
212-
LOGGER.log(Level.FINE, "NOT removing hook for {0}/{1} because does not exists or its used in other project",
213-
new Object[]{source.getRepoOwner(), source.getRepository()});
212+
logger.log(Level.FINE, "NOT removing hook for {0}/{1} because does not exists or its used in other project",
213+
new Object[] { source.getRepoOwner(), source.getRepository() });
214214
}
215215
}
216216
}

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

Lines changed: 90 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,15 @@
4545
import java.util.List;
4646
import java.util.Set;
4747
import java.util.TreeSet;
48+
import java.util.logging.Logger;
49+
import org.apache.commons.collections.CollectionUtils;
4850
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
4951

5052
/**
5153
* Contains the webhook configuration
5254
*/
5355
public class WebhookConfiguration {
56+
private static final Logger logger = Logger.getLogger(WebhookConfiguration.class.getName());
5457

5558
/**
5659
* The list of events available in Bitbucket Cloud.
@@ -81,6 +84,20 @@ public class WebhookConfiguration {
8184
HookEventType.SERVER_PULL_REQUEST_FROM_REF_UPDATED.getKey()
8285
));
8386

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

104121
/**
105122
* The comma separated list of committers to ignore.
@@ -121,43 +138,72 @@ public String getCommittersToIgnore() {
121138
boolean updateHook(BitbucketWebHook hook, BitbucketSCMSource owner) {
122139
boolean updated = false;
123140

141+
final String serverURL = owner.getServerUrl();
142+
final String rootURL = BitbucketEndpointProvider.lookupEndpointJenkinsRootURL(serverURL);
124143
final String signatureSecret = getSecret(owner.getServerUrl());
125144

126145
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));
146+
String url = getCloudWebhookURL(serverURL, rootURL);
147+
if (!Objects.equal(hook.getUrl(), url)) {
148+
cloudHook.setUrl(url);
149+
updated = true;
150+
}
151+
152+
List<String> events = hook.getEvents();
153+
if (!events.containsAll(CLOUD_EVENTS)) {
154+
Set<String> newEvents = new TreeSet<>(events);
155+
newEvents.addAll(CLOUD_EVENTS);
156+
cloudHook.setEvents(new ArrayList<>(newEvents));
157+
logger.info(() -> "Update cloud webhook because the following events was missing: " + CollectionUtils.subtract(CLOUD_EVENTS, events));
131158
updated = true;
132159
}
160+
133161
if (!Objects.equal(hook.getSecret(), signatureSecret)) {
134162
cloudHook.setSecret(signatureSecret);
135163
updated = true;
136164
}
137-
} else if (hook instanceof BitbucketPluginWebhook serverHook) {
138-
String hookCommittersToIgnore = Util.fixEmptyAndTrim(serverHook.getCommittersToIgnore());
165+
} else if (hook instanceof BitbucketPluginWebhook pluginHook) {
166+
String hookCommittersToIgnore = Util.fixEmptyAndTrim(pluginHook.getCommittersToIgnore());
139167
String thisCommittersToIgnore = Util.fixEmptyAndTrim(committersToIgnore);
140168
if (!Objects.equal(thisCommittersToIgnore, hookCommittersToIgnore)) {
141-
serverHook.setCommittersToIgnore(thisCommittersToIgnore);
169+
pluginHook.setCommittersToIgnore(thisCommittersToIgnore);
170+
updated = true;
171+
}
172+
173+
String url = getServerWebhookURL(serverURL, rootURL);
174+
if (!url.equals(pluginHook.getUrl())) {
175+
pluginHook.setUrl(url);
176+
updated = true;
177+
}
178+
179+
if (!pluginHook.isActive()) {
180+
pluginHook.setActive(true);
142181
updated = true;
143182
}
144-
} else if (hook instanceof BitbucketServerWebhook serverHook) {
145-
String serverURL = owner.getServerUrl();
146-
String url = getServerWebhookURL(serverURL, BitbucketEndpointProvider.lookupEndpointJenkinsRootURL(owner.getServerUrl()));
147183

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

200+
List<String> supportedNativeEvents = getNativeServerEvents(serverURL);
153201
List<String> events = serverHook.getEvents();
154-
if (events == null) {
155-
serverHook.setEvents(getNativeServerEvents(serverURL));
156-
updated = true;
157-
} else if (!events.containsAll(getNativeServerEvents(serverURL))) {
202+
if (!events.containsAll(supportedNativeEvents)) {
158203
Set<String> newEvents = new TreeSet<>(events);
159-
newEvents.addAll(getNativeServerEvents(serverURL));
204+
newEvents.addAll(supportedNativeEvents);
160205
serverHook.setEvents(new ArrayList<>(newEvents));
206+
logger.info(() -> "Update native webhook because the following events was missing: " + CollectionUtils.subtract(supportedNativeEvents, events));
161207
updated = true;
162208
}
163209

@@ -170,28 +216,29 @@ boolean updateHook(BitbucketWebHook hook, BitbucketSCMSource owner) {
170216
return updated;
171217
}
172218

219+
@NonNull
173220
public BitbucketWebHook getHook(BitbucketSCMSource owner) {
174-
final String serverUrl = owner.getServerUrl();
175-
final String rootUrl = BitbucketEndpointProvider.lookupEndpointJenkinsRootURL(owner.getServerUrl());
221+
final String serverURL = owner.getServerUrl();
222+
final String rootURL = BitbucketEndpointProvider.lookupEndpointJenkinsRootURL(serverURL);
176223
final String signatureSecret = getSecret(owner.getServerUrl());
177224

178-
if (BitbucketApiUtils.isCloud(serverUrl)) {
225+
if (BitbucketApiUtils.isCloud(serverURL)) {
179226
BitbucketCloudHook hook = new BitbucketCloudHook();
180227
hook.setEvents(CLOUD_EVENTS);
181228
hook.setActive(true);
182-
hook.setDescription(description);
183-
hook.setUrl(rootUrl + BitbucketSCMSourcePushHookReceiver.FULL_PATH);
229+
hook.setDescription(DESCRIPTION);
230+
hook.setUrl(getCloudWebhookURL(serverURL, rootURL));
184231
hook.setSecret(signatureSecret);
185232
return hook;
186233
}
187234

188-
switch (BitbucketServerEndpoint.findWebhookImplementation(serverUrl)) {
235+
switch (BitbucketServerEndpoint.findWebhookImplementation(serverURL)) {
189236
case NATIVE: {
190237
BitbucketServerWebhook hook = new BitbucketServerWebhook();
191238
hook.setActive(true);
192-
hook.setDescription(description);
193-
hook.setEvents(getNativeServerEvents(serverUrl));
194-
hook.setUrl(getServerWebhookURL(serverUrl, rootUrl));
239+
hook.setDescription(DESCRIPTION);
240+
hook.setEvents(getNativeServerEvents(serverURL));
241+
hook.setUrl(getServerWebhookURL(serverURL, rootURL));
195242
hook.setSecret(signatureSecret);
196243
return hook;
197244
}
@@ -200,8 +247,8 @@ public BitbucketWebHook getHook(BitbucketSCMSource owner) {
200247
default: {
201248
BitbucketPluginWebhook hook = new BitbucketPluginWebhook();
202249
hook.setActive(true);
203-
hook.setDescription(description);
204-
hook.setUrl(getServerWebhookURL(serverUrl, rootUrl));
250+
hook.setDescription(DESCRIPTION);
251+
hook.setUrl(getServerWebhookURL(serverURL, rootURL));
205252
hook.setCommittersToIgnore(committersToIgnore);
206253
return hook;
207254
}
@@ -224,9 +271,13 @@ private String getSecret(@NonNull String serverURL) {
224271
return null;
225272
}
226273

227-
private static List<String> getNativeServerEvents(String serverUrl) {
274+
private static List<String> getPluginServerEvents(String serverURL) {
275+
return PLUGIN_SERVER_EVENTS;
276+
}
277+
278+
private static List<String> getNativeServerEvents(String serverURL) {
228279
BitbucketServerEndpoint endpoint = BitbucketEndpointProvider
229-
.lookupEndpoint(serverUrl, BitbucketServerEndpoint.class)
280+
.lookupEndpoint(serverURL, BitbucketServerEndpoint.class)
230281
.orElse(null);
231282
if (endpoint != null) {
232283
switch (endpoint.getServerVersion()) {
@@ -256,12 +307,19 @@ private static List<String> getNativeServerEvents(String serverUrl) {
256307
return NATIVE_SERVER_EVENTS_v7;
257308
}
258309

259-
private static String getServerWebhookURL(String serverUrl, String rootUrl) {
260-
return UriTemplate.buildFromTemplate(rootUrl)
310+
private static String getCloudWebhookURL(String serverURL, String rootURL) {
311+
return UriTemplate.buildFromTemplate(rootURL)
312+
.template(BitbucketSCMSourcePushHookReceiver.FULL_PATH)
313+
.build()
314+
.expand();
315+
}
316+
317+
private static String getServerWebhookURL(String serverURL, String rootURL) {
318+
return UriTemplate.buildFromTemplate(rootURL)
261319
.template(BitbucketSCMSourcePushHookReceiver.FULL_PATH)
262320
.query("server_url")
263321
.build()
264-
.set("server_url", serverUrl)
322+
.set("server_url", serverURL)
265323
.expand();
266324
}
267325
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,7 @@ public void registerCommitWebHook(BitbucketWebHook hook) throws IOException {
675675

676676
@Override
677677
public void updateCommitWebHook(BitbucketWebHook hook) throws IOException {
678+
String payload = JsonParser.toString(hook);
678679
switch (webhookImplementation) {
679680
case PLUGIN:
680681
// API documentation at https://help.moveworkforward.com/BPW/how-to-manage-configurations-using-post-webhooks-f#HowtomanageconfigurationsusingPostWebhooksforBitbucketAPIs?-UpdateapostwebhookbyID
@@ -684,7 +685,7 @@ public void updateCommitWebHook(BitbucketWebHook hook) throws IOException {
684685
.set("owner", getUserCentricOwner())
685686
.set("repo", repositoryName)
686687
.set("id", hook.getUuid())
687-
.expand(), JsonParser.toString(hook)
688+
.expand(), payload
688689
);
689690
break;
690691

@@ -695,7 +696,7 @@ public void updateCommitWebHook(BitbucketWebHook hook) throws IOException {
695696
.set("owner", getUserCentricOwner())
696697
.set("repo", repositoryName)
697698
.set("id", hook.getUuid())
698-
.expand(), JsonParser.toString(hook)
699+
.expand(), payload
699700
);
700701
break;
701702

0 commit comments

Comments
 (0)