Skip to content

Commit 4981e8f

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 b9c6642 commit 4981e8f

File tree

70 files changed

+2060
-1093
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

+2060
-1093
lines changed

docs/USER_GUIDE.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,12 +313,12 @@ System.setProperty("http.socket.timeout", "300") // 5 minutes
313313
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.
314314
To change this amount of time (default is 300 seconds), add the system property `bitbucket.oauth2.cache.timeout=60` on Jenkins startup.
315315

316-
=== Disable Branch Indexing on Empty changes
316+
=== Enable Branch Indexing on Empty changes
317317

318-
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:
318+
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:
319319

320320
* When manually merging remote **Open** pull requests. This particular scenario produces 2 events and cause duplicated builds.
321321
* 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
322322
* A link:http://confluence.atlassian.com/bitbucketserver/event-payload-938025882.html#Eventpayload-Mirrorsynchronized[mirror:repo_synchronized] event with too many refs
323323

324-
This behaviour can be disabled by adding the system property `bitbucket.hooks.processor.scanOnEmptyChanges=false` on Jenkins startup.
324+
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: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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 edu.umd.cs.findbugs.annotations.Nullable;
30+
import hudson.ExtensionPoint;
31+
import java.util.Map;
32+
import java.util.concurrent.TimeUnit;
33+
import jenkins.scm.api.SCMHeadEvent;
34+
import jenkins.util.SystemProperties;
35+
import org.apache.commons.collections4.MultiValuedMap;
36+
import org.kohsuke.accmod.Restricted;
37+
import org.kohsuke.accmod.restrictions.Beta;
38+
39+
/**
40+
* Implementations of this extension point must provide new behaviours to
41+
* accommodate custom event payloads from webhooks sent from Bitbucket Cloud,
42+
* Bitbucket Data Center, or installed plugins.
43+
* <p>
44+
* There cannot be multiple processors processing the same incoming webhook for
45+
* a specific event installed on the system, meaning the processor must fit to
46+
* the incoming request as much as possible or the hook will be rejected.
47+
*/
48+
@Restricted(Beta.class)
49+
public interface BitbucketHookProcessor extends ExtensionPoint {
50+
static final String SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME = "bitbucket.hooks.processor.scanOnEmptyChanges";
51+
52+
/**
53+
* Called by first for this processor that must respond if is able to handle
54+
* this specific request
55+
*
56+
* @param headers request
57+
* @param parameters request
58+
* @return {@code true} if this processor is able to handle this hook
59+
* request, {@code false} otherwise.
60+
*/
61+
boolean canHandle(@NonNull Map<String, String> headers, @NonNull MultiValuedMap<String, String> parameters);
62+
63+
/**
64+
* Extracts the server URL from where this request coming from, the URL must
65+
* match one of the configured {@link BitbucketEndpoint}s.
66+
*
67+
* @param headers request
68+
* @param parameters request
69+
* @return the URL of the server from where this request has been sent.
70+
*/
71+
@NonNull
72+
String getServerURL(@NonNull Map<String, String> headers, @NonNull MultiValuedMap<String, String> parameters);
73+
74+
/**
75+
* Extracts the event type that represent the payload in the request.
76+
*
77+
* @param headers request
78+
* @param parameters request
79+
* @return the event type key.
80+
*/
81+
@NonNull
82+
String getEventType(Map<String, String> headers, MultiValuedMap<String, String> parameters);
83+
84+
/**
85+
* The implementation must verify if the incoming request is secured or not
86+
* eventually gather some settings from the given {@link BitbucketEndpoint}
87+
* configuration.
88+
*
89+
* @param headers request
90+
* @param payload request
91+
* @param endpoint configured for the given
92+
* {@link #getServerURL(Map, MultiValuedMap)}
93+
* @throws BitbucketHookProcessorException when signature verification fails
94+
*/
95+
void verifySignature(@NonNull Map<String, String> headers,
96+
@NonNull String payload,
97+
@NonNull BitbucketEndpoint endpoint) throws BitbucketHookProcessorException;
98+
99+
/**
100+
* Settings that will trigger a re-index of the multibranch
101+
* project/organization folder when the request does not ship any source
102+
* changes.
103+
*
104+
* @return if should perform a reindex of the project or not.
105+
*/
106+
default boolean reindexOnEmptyChanges() {
107+
return SystemProperties.getBoolean(SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME, false);
108+
}
109+
110+
/**
111+
* See <a href="https://confluence.atlassian.com/bitbucket/event-payloads-740262817.html">Event
112+
* Payloads</a> for more information about the payload parameter format.
113+
*
114+
* @param eventType the type of hook event.
115+
* @param payload the hook payload
116+
* @param origin the (optional) origin of the event, e.g. a hostname, etc
117+
* @param endpoint configured in the Jenkins global page
118+
*/
119+
void process(@NonNull String eventType, @NonNull String payload, @Nullable String origin, @NonNull BitbucketEndpoint endpoint);
120+
121+
/**
122+
* Implementations have to call this method when want propagate an
123+
* {@link SCMHeadEvent} to the scm-api.
124+
*
125+
* @param event the to fire
126+
* @param delaySeconds a delay in seconds to wait before propagate the
127+
* event. If the given value is less than 0 than default will be
128+
* used.
129+
*/
130+
default void notifyEvent(SCMHeadEvent<?> event, int delaySeconds) {
131+
if (delaySeconds == 0) {
132+
SCMHeadEvent.fireNow(event);
133+
} else {
134+
SCMHeadEvent.fireLater(event, delaySeconds > 0 ? delaySeconds : BitbucketSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS);
135+
}
136+
}
137+
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPingHookProcessor.java renamed to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessorException.java

Lines changed: 12 additions & 11 deletions
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,24 @@
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;
26+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketException;
2927
import org.kohsuke.accmod.Restricted;
3028
import org.kohsuke.accmod.restrictions.NoExternalUse;
3129

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

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

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);
40+
public int getHttpCode() {
41+
return httpCode;
4142
}
4243

4344
}

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)