Skip to content

Commit a4caff7

Browse files
committed
[JENKINS-74913] Allow extension point in bitbucket source plugin to provide a implementation for web-hooks management
Add extension point interface to register hook processor provided by other plugins Remove manage of hook events that does ship any king of changes in the source code like reviewer changed or PR approved, they was used only to trigger builds if that kind of changes was enabled bitbucket side. Reindex on empty changes has been turn-off by default, this to avoid scan all repositories if not required. Remove support for version of Bitbucket Server declared in EOS by Atlassian (see https://confluence.atlassian.com/support/atlassian-end-of-support-policy-201851003.html)
1 parent 4b8e01b commit a4caff7

File tree

70 files changed

+2129
-1113
lines changed

Some content is hidden

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

70 files changed

+2129
-1113
lines changed

docs/USER_GUIDE.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,12 +328,12 @@ System.setProperty("http.socket.timeout", "300") // 5 minutes
328328
In case Bitbucket has been configured to expire OAuth2 tokens before 5 minutes, you can configure via a JVM property the release time of the cache where all obtained OAuth2 tokens are stored. This setting is to avoid requests with expired tokens that will produce HTTP 401 responses. link:https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/[Bitbucket Cloud] access tokens expire in two hours.
329329
To change this amount of time (default is 300 seconds), add the system property `bitbucket.oauth2.cache.timeout=60` on Jenkins startup.
330330

331-
=== Disable Branch Indexing on Empty changes
331+
=== Enable Branch Indexing on Empty changes
332332

333-
By default, the plugin triggers *a full branch indexing* when a push event contains *empty* changes. This may happen on various scenario, mainly in Bitbucket Data Center, such as:
333+
By default, the plugin does not triggers *a full branch indexing* when a push event contains *empty* changes. This may happen on various scenario, mainly in Bitbucket Data Center, such as:
334334

335335
* When manually merging remote **Open** pull requests. This particular scenario produces 2 events and cause duplicated builds.
336336
* For a fork, when link:https://confluence.atlassian.com/bitbucketserver/keeping-forks-synchronized-776639961.html[Auto-Sync] is on and a branch cannot be synchronised
337337
* A link:http://confluence.atlassian.com/bitbucketserver/event-payload-938025882.html#Eventpayload-Mirrorsynchronized[mirror:repo_synchronized] event with too many refs
338338

339-
This behaviour can be disabled by adding the system property `bitbucket.hooks.processor.scanOnEmptyChanges=false` on Jenkins startup.
339+
This behaviour can be enabled by adding the system property `bitbucket.hooks.processor.scanOnEmptyChanges=true` on Jenkins startup.

pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@
8383
<groupId>io.jenkins.plugins</groupId>
8484
<artifactId>commons-lang3-api</artifactId>
8585
</dependency>
86+
<dependency>
87+
<groupId>io.jenkins.plugins</groupId>
88+
<artifactId>commons-collections4-api</artifactId>
89+
</dependency>
8690
<dependency>
8791
<groupId>org.jenkins-ci.plugins</groupId>
8892
<artifactId>jackson2-api</artifactId>

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
package com.cloudbees.jenkins.plugins.bitbucket.api;
2525

2626
public class BitbucketException extends RuntimeException {
27+
private static final long serialVersionUID = 1L;
2728

2829
public BitbucketException(String message, Throwable cause) {
2930
super(message, cause);
@@ -33,6 +34,4 @@ public BitbucketException(String message) {
3334
super(message);
3435
}
3536

36-
private static final long serialVersionUID = 1L;
37-
3837
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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.hook;
25+
26+
import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource;
27+
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint;
28+
import edu.umd.cs.findbugs.annotations.NonNull;
29+
import hudson.ExtensionPoint;
30+
import jakarta.servlet.http.HttpServletRequest;
31+
import java.util.Map;
32+
import java.util.concurrent.TimeUnit;
33+
import jenkins.scm.api.SCMEvent;
34+
import jenkins.scm.api.SCMHeadEvent;
35+
import jenkins.util.SystemProperties;
36+
import org.apache.commons.collections4.MultiValuedMap;
37+
import org.kohsuke.accmod.Restricted;
38+
import org.kohsuke.accmod.restrictions.Beta;
39+
40+
/**
41+
* Implementations of this extension point must provide new behaviours to
42+
* accommodate custom event payloads from webhooks sent from Bitbucket Cloud,
43+
* Bitbucket Data Center, or installed plugins.
44+
* <p>
45+
* There cannot be multiple processors processing the same incoming webhook for
46+
* a specific event installed on the system, meaning the processor must fit to
47+
* the incoming request as much as possible or the hook will be rejected.
48+
*/
49+
@Restricted(Beta.class)
50+
public interface BitbucketHookProcessor extends ExtensionPoint {
51+
static final String SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME = "bitbucket.hooks.processor.scanOnEmptyChanges";
52+
53+
/**
54+
* Called by first for this processor that must respond if is able to handle
55+
* this specific request
56+
*
57+
* @param headers request
58+
* @param parameters request
59+
* @return {@code true} if this processor is able to handle this hook
60+
* request, {@code false} otherwise.
61+
*/
62+
boolean canHandle(@NonNull Map<String, String> headers, @NonNull MultiValuedMap<String, String> parameters);
63+
64+
/**
65+
* Extracts the server URL from where this request coming from, the URL must
66+
* match one of the configured {@link BitbucketEndpoint}s.
67+
*
68+
* @param headers request
69+
* @param parameters request
70+
* @return the URL of the server from where this request has been sent.
71+
*/
72+
@NonNull
73+
String getServerURL(@NonNull Map<String, String> headers, @NonNull MultiValuedMap<String, String> parameters);
74+
75+
/**
76+
* Extracts the event type that represent the payload in the request.
77+
*
78+
* @param headers request
79+
* @param parameters request
80+
* @return the event type key.
81+
*/
82+
@NonNull
83+
String getEventType(Map<String, String> headers, MultiValuedMap<String, String> parameters);
84+
85+
/**
86+
* Returns a context for a given request used when process the payload.
87+
*
88+
* @param request hook
89+
* @return a map of information extracted by the given request to be used in
90+
* the {@link #process(String, String, Map, BitbucketEndpoint)}
91+
* method.
92+
*/
93+
@NonNull
94+
default Map<String, Object> buildHookContext(@NonNull HttpServletRequest request) {
95+
return Map.of("origin", SCMEvent.originOf(request));
96+
}
97+
98+
/**
99+
* The implementation must verify if the incoming request is secured or not
100+
* eventually gather some settings from the given {@link BitbucketEndpoint}
101+
* configuration.
102+
*
103+
* @param headers request
104+
* @param payload request
105+
* @param endpoint configured for the given
106+
* {@link #getServerURL(Map, MultiValuedMap)}
107+
* @throws BitbucketHookProcessorException when signature verification fails
108+
*/
109+
void verifyPayload(@NonNull Map<String, String> headers,
110+
@NonNull String payload,
111+
@NonNull BitbucketEndpoint endpoint) throws BitbucketHookProcessorException;
112+
113+
/**
114+
* Settings that will trigger a re-index of the multibranch
115+
* project/organization folder when the request does not ship any source
116+
* changes.
117+
*
118+
* @return if should perform a reindex of the project or not.
119+
*/
120+
default boolean reindexOnEmptyChanges() {
121+
return SystemProperties.getBoolean(SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME, false);
122+
}
123+
124+
/**
125+
* See <a href="https://confluence.atlassian.com/bitbucket/event-payloads-740262817.html">Event
126+
* Payloads</a> for more information about the payload parameter format.
127+
*
128+
* @param eventType the type of hook event.
129+
* @param payload the hook payload
130+
* @param context build from incoming request
131+
* @param endpoint configured in the Jenkins global page
132+
*/
133+
void process(@NonNull String eventType, @NonNull String payload, @NonNull Map<String, Object> context, @NonNull BitbucketEndpoint endpoint);
134+
135+
/**
136+
* Implementations have to call this method when want propagate an
137+
* {@link SCMHeadEvent} to the scm-api.
138+
*
139+
* @param event the to fire
140+
* @param delaySeconds a delay in seconds to wait before propagate the
141+
* event. If the given value is less than 0 than default will be
142+
* used.
143+
*/
144+
default void notifyEvent(SCMHeadEvent<?> event, int delaySeconds) {
145+
if (delaySeconds == 0) {
146+
SCMHeadEvent.fireNow(event);
147+
} else {
148+
SCMHeadEvent.fireLater(event, delaySeconds > 0 ? delaySeconds : BitbucketSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS);
149+
}
150+
}
151+
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* The MIT License
33
*
4-
* Copyright (c) 2016-2018, Yieldlab AG
4+
* Copyright (c) 2025, Falco Nikolas
55
*
66
* Permission is hereby granted, free of charge, to any person obtaining a copy
77
* of this software and associated documentation files (the "Software"), to deal
@@ -21,23 +21,21 @@
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.hooks;
24+
package com.cloudbees.jenkins.plugins.bitbucket.api.hook;
2525

26-
import hudson.RestrictedSince;
27-
import java.util.logging.Level;
28-
import java.util.logging.Logger;
29-
import org.kohsuke.accmod.Restricted;
30-
import org.kohsuke.accmod.restrictions.NoExternalUse;
26+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketException;
3127

32-
@Restricted(NoExternalUse.class)
33-
@RestrictedSince("933.3.0")
34-
public class NativeServerPingHookProcessor extends HookProcessor {
28+
public class BitbucketHookProcessorException extends BitbucketException {
29+
private static final long serialVersionUID = 6682700868741672883L;
30+
private final int httpCode;
3531

36-
private static final Logger LOGGER = Logger.getLogger(NativeServerPingHookProcessor.class.getName());
32+
public BitbucketHookProcessorException(int httpCode, String message) {
33+
super(message);
34+
this.httpCode = httpCode;
35+
}
3736

38-
@Override
39-
public void process(HookEventType hookEvent, String payload, BitbucketType instanceType, String origin) {
40-
LOGGER.log(Level.INFO, "Received webhook ping event from {0}", origin);
37+
public int getHttpCode() {
38+
return httpCode;
4139
}
4240

4341
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/filesystem/BitbucketSCMFileSystem.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,8 @@
3131
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApiFactory;
3232
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
3333
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit;
34-
import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketServerEndpoint;
3534
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils;
3635
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.DateUtils;
37-
import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerVersion;
3836
import com.cloudbees.plugins.credentials.CredentialsMatchers;
3937
import com.cloudbees.plugins.credentials.CredentialsProvider;
4038
import com.cloudbees.plugins.credentials.common.StandardCredentials;
@@ -293,13 +291,7 @@ public SCMFileSystem build(@NonNull SCMSource source, @NonNull SCMHead head, @Ch
293291
// Bitbucket server v7 doesn't have the `merge` ref for PRs
294292
// We don't return `ref` when working with v7
295293
// so that pipeline falls back to heavyweight checkout properly
296-
boolean ligthCheckout = BitbucketServerEndpoint.findServerVersion(serverURL) != BitbucketServerVersion.VERSION_7;
297-
if (ligthCheckout) {
298-
ref = "pull-requests/" + prHead.getId() + "/merge";
299-
} else {
300-
// returning null to fall back to heavyweight checkout
301-
return null;
302-
}
294+
return null;
303295
}
304296
} else if (head instanceof BitbucketTagSCMHead) {
305297
ref = "tags/" + head.getName();

0 commit comments

Comments
 (0)