Skip to content

Commit f16c41e

Browse files
committed
[JENKINS-74913] Allow extension point in bitbucket source plugin to provide a implementation for web-hooks management
Add webhook extensible interface to provide custom implementation of wehbook from configuration to the APIs to register it on Bitbucket
1 parent a4caff7 commit f16c41e

File tree

76 files changed

+943
-185
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+943
-185
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2025, Falco Nikolas
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.api.webhook;
25+
26+
import edu.umd.cs.findbugs.annotations.CheckForNull;
27+
import edu.umd.cs.findbugs.annotations.NonNull;
28+
import hudson.model.Describable;
29+
import org.kohsuke.accmod.Restricted;
30+
import org.kohsuke.accmod.restrictions.Beta;
31+
32+
@Restricted(Beta.class)
33+
public interface BitbucketWebhook extends Describable<BitbucketWebhook> {
34+
35+
/**
36+
* Name to use to describe the hook implementation.
37+
*
38+
* @return the name to use for the implementation
39+
*/
40+
@CheckForNull
41+
String getDisplayName();
42+
43+
/**
44+
* The hook implementation identifier.
45+
*
46+
* @return the unique identifier for this implementation
47+
*/
48+
@NonNull
49+
String getId();
50+
51+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2017, 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.api.webhook;
25+
26+
import hudson.model.Descriptor;
27+
28+
/**
29+
* {@link Descriptor} for {@link BitbucketWebhook}s.
30+
*
31+
* @since 936.4.0
32+
*/
33+
public class BitbucketWebhookDescriptor extends Descriptor<BitbucketWebhook> {
34+
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessor.java renamed to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookProcessor.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2222
* THE SOFTWARE.
2323
*/
24-
package com.cloudbees.jenkins.plugins.bitbucket.api.hook;
24+
package com.cloudbees.jenkins.plugins.bitbucket.api.webhook;
2525

2626
import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource;
2727
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint;
@@ -47,7 +47,7 @@
4747
* the incoming request as much as possible or the hook will be rejected.
4848
*/
4949
@Restricted(Beta.class)
50-
public interface BitbucketHookProcessor extends ExtensionPoint {
50+
public interface BitbucketWebhookProcessor extends ExtensionPoint {
5151
static final String SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME = "bitbucket.hooks.processor.scanOnEmptyChanges";
5252

5353
/**
@@ -104,11 +104,11 @@ default Map<String, Object> buildHookContext(@NonNull HttpServletRequest request
104104
* @param payload request
105105
* @param endpoint configured for the given
106106
* {@link #getServerURL(Map, MultiValuedMap)}
107-
* @throws BitbucketHookProcessorException when signature verification fails
107+
* @throws BitbucketWebhookProcessorException when signature verification fails
108108
*/
109109
void verifyPayload(@NonNull Map<String, String> headers,
110110
@NonNull String payload,
111-
@NonNull BitbucketEndpoint endpoint) throws BitbucketHookProcessorException;
111+
@NonNull BitbucketEndpoint endpoint) throws BitbucketWebhookProcessorException;
112112

113113
/**
114114
* Settings that will trigger a re-index of the multibranch

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessorException.java renamed to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookProcessorException.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@
2121
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2222
* THE SOFTWARE.
2323
*/
24-
package com.cloudbees.jenkins.plugins.bitbucket.api.hook;
24+
package com.cloudbees.jenkins.plugins.bitbucket.api.webhook;
2525

2626
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketException;
2727

28-
public class BitbucketHookProcessorException extends BitbucketException {
28+
public class BitbucketWebhookProcessorException extends BitbucketException {
2929
private static final long serialVersionUID = 6682700868741672883L;
3030
private final int httpCode;
3131

32-
public BitbucketHookProcessorException(int httpCode, String message) {
32+
public BitbucketWebhookProcessorException(int httpCode, String message) {
3333
super(message);
3434
this.httpCode = httpCode;
3535
}

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525

2626
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint;
2727
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider;
28-
import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessor;
29-
import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessorException;
28+
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookProcessor;
29+
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookProcessorException;
3030
import hudson.Extension;
3131
import hudson.ExtensionList;
3232
import hudson.model.UnprotectedRootAction;
@@ -93,7 +93,7 @@ public HttpResponse doNotify(StaplerRequest2 req) throws IOException {
9393
try {
9494
Map<String, String> reqHeaders = getHeaders(req);
9595
MultiValuedMap<String, String> reqParameters = getParameters(req);
96-
BitbucketHookProcessor hookProcessor = getHookProcessor(reqHeaders, reqParameters);
96+
BitbucketWebhookProcessor hookProcessor = getHookProcessor(reqHeaders, reqParameters);
9797

9898
String body = IOUtils.toString(req.getInputStream(), StandardCharsets.UTF_8);
9999
if (StringUtils.isEmpty(body)) {
@@ -120,37 +120,37 @@ public HttpResponse doNotify(StaplerRequest2 req) throws IOException {
120120
String eventType = hookProcessor.getEventType(Collections.unmodifiableMap(reqHeaders), MultiMapUtils.unmodifiableMultiValuedMap(reqParameters));
121121

122122
hookProcessor.process(eventType, body, context, endpoint);
123-
} catch(BitbucketHookProcessorException e) {
123+
} catch(BitbucketWebhookProcessorException e) {
124124
return HttpResponses.error(e.getHttpCode(), e.getMessage());
125125
}
126126
return HttpResponses.ok();
127127
}
128128

129-
private BitbucketHookProcessor getHookProcessor(Map<String, String> reqHeaders,
129+
private BitbucketWebhookProcessor getHookProcessor(Map<String, String> reqHeaders,
130130
MultiValuedMap<String, String> reqParameters) {
131-
BitbucketHookProcessor hookProcessor;
131+
BitbucketWebhookProcessor hookProcessor;
132132

133-
List<BitbucketHookProcessor> matchingProcessors = getHookProcessors()
133+
List<BitbucketWebhookProcessor> matchingProcessors = getHookProcessors()
134134
.filter(processor -> processor.canHandle(Collections.unmodifiableMap(reqHeaders), MultiMapUtils.unmodifiableMultiValuedMap(reqParameters)))
135135
.toList();
136136
if (matchingProcessors.isEmpty()) {
137137
logger.warning(() -> "No processor found for the incoming Bitbucket hook. Skipping.");
138-
throw new BitbucketHookProcessorException(HttpServletResponse.SC_BAD_REQUEST, "No processor found that supports this event. Refer to the user documentation on how configure the webHook in Bitbucket at https://github.com/jenkinsci/bitbucket-branch-source-plugin/blob/master/docs/USER_GUIDE.adoc#webhooks-registering");
138+
throw new BitbucketWebhookProcessorException(HttpServletResponse.SC_BAD_REQUEST, "No processor found that supports this event. Refer to the user documentation on how configure the webHook in Bitbucket at https://github.com/jenkinsci/bitbucket-branch-source-plugin/blob/master/docs/USER_GUIDE.adoc#webhooks-registering");
139139
} else if (matchingProcessors.size() > 1) {
140140
String processors = StringUtils.joinWith("\n- ", matchingProcessors.stream()
141141
.map(p -> p.getClass().getName())
142142
.toList());
143143
logger.severe(() -> "More processors found that handle the incoming Bitbucket hook:\n" + processors);
144-
throw new BitbucketHookProcessorException(HttpServletResponse.SC_CONFLICT, "More processors found that handle the incoming Bitbucket hook.");
144+
throw new BitbucketWebhookProcessorException(HttpServletResponse.SC_CONFLICT, "More processors found that handle the incoming Bitbucket hook.");
145145
} else {
146146
hookProcessor = matchingProcessors.get(0);
147147
logger.fine(() -> "Hook processor " + hookProcessor.getClass().getName() + " found.");
148148
}
149149
return hookProcessor;
150150
}
151151

152-
/*test*/ Stream<BitbucketHookProcessor> getHookProcessors() {
153-
return ExtensionList.lookup(BitbucketHookProcessor.class).stream();
152+
/*test*/ Stream<BitbucketWebhookProcessor> getHookProcessors() {
153+
return ExtensionList.lookup(BitbucketWebhookProcessor.class).stream();
154154
}
155155

156156
private MultiValuedMap<String, String> getParameters(StaplerRequest2 req) {

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@
2626
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
2727
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint;
2828
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointDescriptor;
29+
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhook;
2930
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils;
3031
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils;
3132
import com.cloudbees.plugins.credentials.common.StandardCredentials;
3233
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
3334
import edu.umd.cs.findbugs.annotations.CheckForNull;
3435
import edu.umd.cs.findbugs.annotations.NonNull;
3536
import hudson.Util;
37+
import java.util.Objects;
3638
import jenkins.authentication.tokens.api.AuthenticationTokens;
3739
import jenkins.model.Jenkins;
3840
import org.apache.commons.lang3.StringUtils;
@@ -60,6 +62,9 @@ public abstract class AbstractBitbucketEndpoint implements BitbucketEndpoint {
6062
@CheckForNull
6163
private String credentialsId;
6264

65+
@NonNull
66+
private BitbucketWebhook webhook;
67+
6368
/**
6469
* {@code true} if and only if Jenkins have to verify the signature of all incoming hooks.
6570
*/
@@ -267,4 +272,13 @@ public BitbucketAuthenticator authenticator() {
267272
public BitbucketEndpointDescriptor getDescriptor() {
268273
return (BitbucketEndpointDescriptor) Jenkins.get().getDescriptorOrDie(getClass());
269274
}
275+
276+
public @NonNull BitbucketWebhook getWebhook() {
277+
return webhook;
278+
}
279+
280+
@DataBoundSetter
281+
public void setWebhook(@NonNull BitbucketWebhook webhook) {
282+
this.webhook = Objects.requireNonNull(webhook);
283+
}
270284
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint.java

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@
5050
import org.kohsuke.stapler.QueryParameter;
5151
import org.kohsuke.stapler.interceptor.RequirePOST;
5252

53-
import static java.util.Objects.requireNonNull;
54-
5553
/**
5654
* Represents a Bitbucket Server instance.
5755
*
@@ -74,9 +72,9 @@ public class BitbucketServerEndpoint extends AbstractBitbucketEndpoint {
7472

7573
@NonNull
7674
public static BitbucketServerWebhookImplementation findWebhookImplementation(String serverURL) {
77-
return BitbucketEndpointProvider.lookupEndpoint(serverURL, BitbucketServerEndpoint.class)
75+
return /*BitbucketEndpointProvider.lookupEndpoint(serverURL, BitbucketServerEndpoint.class)
7876
.map(BitbucketServerEndpoint::getWebhookImplementation)
79-
.orElse(BitbucketServerWebhookImplementation.NATIVE);
77+
.orElse(BitbucketServerWebhookImplementation.NATIVE);*/ null;
8078
}
8179

8280
@NonNull
@@ -99,8 +97,8 @@ public static BitbucketServerVersion findServerVersion(String serverURL) {
9997
@NonNull
10098
private final String serverUrl;
10199

102-
@NonNull
103-
private BitbucketServerWebhookImplementation webhookImplementation = BitbucketServerWebhookImplementation.NATIVE;
100+
// @NonNull
101+
// private BitbucketServerWebhookImplementation webhookImplementation = BitbucketServerWebhookImplementation.NATIVE;
104102

105103
/**
106104
* The server version for this endpoint.
@@ -243,9 +241,9 @@ public String getRepositoryURL(@NonNull String repoOwner, @NonNull String reposi
243241

244242
@SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "Only non-null after we set them here!")
245243
private Object readResolve() {
246-
if (webhookImplementation == null) {
247-
webhookImplementation = BitbucketServerWebhookImplementation.NATIVE;
248-
}
244+
// if (webhookImplementation == null) {
245+
// webhookImplementation = BitbucketServerWebhookImplementation.NATIVE;
246+
// }
249247
if (getBitbucketJenkinsRootUrl() != null) {
250248
setBitbucketJenkinsRootUrl(getBitbucketJenkinsRootUrl());
251249
}
@@ -256,15 +254,15 @@ private Object readResolve() {
256254
return this;
257255
}
258256

259-
@NonNull
260-
public BitbucketServerWebhookImplementation getWebhookImplementation() {
261-
return webhookImplementation;
262-
}
257+
// @NonNull
258+
// public BitbucketServerWebhookImplementation getWebhookImplementation() {
259+
// return webhookImplementation;
260+
// }
263261

264-
@DataBoundSetter
265-
public void setWebhookImplementation(@NonNull BitbucketServerWebhookImplementation webhookImplementation) {
266-
this.webhookImplementation = requireNonNull(webhookImplementation);
267-
}
262+
// @DataBoundSetter
263+
// public void setWebhookImplementation(@NonNull BitbucketServerWebhookImplementation webhookImplementation) {
264+
// this.webhookImplementation = requireNonNull(webhookImplementation);
265+
// }
268266

269267
/**
270268
* Our descriptor.

0 commit comments

Comments
 (0)