From a4caff7f65075fb283dc0541cb50b951e649d095 Mon Sep 17 00:00:00 2001 From: Nikolas Falco Date: Sun, 22 Jun 2025 15:28:29 +0200 Subject: [PATCH 1/3] [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) --- docs/USER_GUIDE.adoc | 6 +- pom.xml | 4 + .../bitbucket/api/BitbucketException.java | 3 +- .../api/hook/BitbucketHookProcessor.java | 151 +++++++ .../BitbucketHookProcessorException.java} | 26 +- .../filesystem/BitbucketSCMFileSystem.java | 10 +- .../BitbucketSCMSourcePushHookReceiver.java | 197 ++++----- .../bitbucket/hooks/HookEventType.java | 54 +-- .../bitbucket/hooks/HookProcessor.java | 124 ------ .../hooks/PullRequestHookProcessor.java | 68 --- .../bitbucket/hooks/PushHookProcessor.java | 84 ---- .../bitbucket/hooks/WebhookConfiguration.java | 87 +--- .../endpoint/AbstractBitbucketEndpoint.java | 21 +- .../impl/endpoint/BitbucketCloudEndpoint.java | 2 +- .../endpoint/BitbucketServerEndpoint.java | 19 +- .../impl/hook/AbstractHookProcessor.java | 184 ++++++++ .../AbstractNativeServerSCMHeadEvent.java | 2 +- .../hook}/AbstractSCMHeadEvent.java | 8 +- .../hook/CloudPullRequestHookProcessor.java | 89 ++++ .../impl/hook/CloudPushHookProcessor.java | 88 ++++ .../hook/NativeServerPingHookProcessor.java | 64 +++ .../NativeServerPullRequestHookProcessor.java | 33 +- .../hook}/NativeServerPushHookProcessor.java | 48 ++- .../{hooks => impl/hook}/PREvent.java | 4 +- .../hook/PluginPullRequestHookProcessor.java | 83 ++++ .../impl/hook/PluginPushHookProcessor.java | 93 ++++ .../{hooks => impl/hook}/PushEvent.java | 2 +- .../{hooks => impl/hook}/ServerHeadEvent.java | 3 +- .../{hooks => impl/hook}/ServerPushEvent.java | 3 +- .../server/BitbucketServerVersion.java | 19 +- .../client/BitbucketServerAPIClient.java | 13 +- .../BitbucketEndpointConfigurationTest.java | 42 +- ...itbucketSCMSourcePushHookReceiverTest.java | 396 ++++-------------- .../hooks/WebhookConfigurationTest.java | 44 -- .../endpoint/DummyEndpointConfiguration.java | 8 +- .../impl/hook/AbstractHookProcessorTest.java | 135 ++++++ .../CloudPullRequestHookProcessorTest.java} | 73 +++- .../hook/CloudPushHookProcessorTest.java} | 99 +++-- .../NativeServerPingHookProcessorTest.java | 104 +++++ ...iveServerPullRequestHookProcessorTest.java | 59 ++- .../NativeServerPushHookProcessorTest.java | 59 ++- .../PluginPullRequestHookProcessorTest.java | 205 +++++++++ .../hook/PluginPushHookProcessorTest.java | 237 +++++++++++ .../test/util/HookProcessorTestUtil.java | 62 +++ .../bitbucket/test/util/MockRequest.java | 61 +++ .../bitbucket/hooks/server/pushPayload.json | 66 --- .../hook}/cloud/annotated_tag_created.json | 0 .../hook}/cloud/commit_created.json | 0 .../hook}/cloud/pullrequest_created.json | 0 .../hook}/cloud/pullrequest_rejected.json | 0 .../hook}/cloud/signed_payload.json | 0 .../hook}/cloud/tag_created.json | 0 .../hook}/native/annotated_tag_created.json | 0 .../hook}/native/emptyPayload.json | 0 .../hook}/native/mirrorSynchronized.json | 0 .../mirrorSynchronized_refLimitExceeded.json | 0 .../hook/native/ping.json} | 0 .../hook}/native/prOpenFromTagPayload.json | 0 .../hook}/native/pushPayload.json | 0 .../hook}/native/signed_payload.json | 0 .../hook}/native/tag_created.json | 0 .../hook}/native/tag_deleted.json | 0 .../hook}/plugin/branch_created.json | 0 .../hook}/plugin/branch_deleted.json | 0 .../hook}/plugin/commit_update.json | 0 .../hook}/plugin/commit_update2.json | 0 .../hook/plugin}/emptyPayload.json | 0 .../hook}/plugin/pullrequest_created.json | 0 .../hook}/plugin/pullrequest_merged.json | 0 .../hook}/plugin/pullrequest_updated.json | 0 70 files changed, 2129 insertions(+), 1113 deletions(-) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessor.java rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/{hooks/NativeServerPingHookProcessor.java => api/hook/BitbucketHookProcessorException.java} (60%) delete mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/HookProcessor.java delete mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PullRequestHookProcessor.java delete mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PushHookProcessor.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessor.java rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/AbstractNativeServerSCMHeadEvent.java (98%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/AbstractSCMHeadEvent.java (92%) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPullRequestHookProcessor.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPushHookProcessor.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessor.java rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/NativeServerPullRequestHookProcessor.java (62%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/NativeServerPushHookProcessor.java (76%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/PREvent.java (97%) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessor.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessor.java rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/PushEvent.java (98%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/ServerHeadEvent.java (97%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/ServerPushEvent.java (99%) create mode 100644 src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessorTest.java rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/{hooks/PullRequestHookProcessorTest.java => impl/hook/CloudPullRequestHookProcessorTest.java} (62%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/{hooks/PushHookProcessorTest.java => impl/hook/CloudPushHookProcessorTest.java} (61%) create mode 100644 src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessorTest.java rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/NativeServerPullRequestHookProcessorTest.java (61%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/NativeServerPushHookProcessorTest.java (72%) create mode 100644 src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessorTest.java create mode 100644 src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessorTest.java create mode 100644 src/test/java/com/cloudbees/jenkins/plugins/bitbucket/test/util/HookProcessorTestUtil.java create mode 100644 src/test/java/com/cloudbees/jenkins/plugins/bitbucket/test/util/MockRequest.java delete mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/server/pushPayload.json rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/cloud/annotated_tag_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/cloud/commit_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/cloud/pullrequest_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/cloud/pullrequest_rejected.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/cloud/signed_payload.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/cloud/tag_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/native/annotated_tag_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/native/emptyPayload.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/native/mirrorSynchronized.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/native/mirrorSynchronized_refLimitExceeded.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks/native/ping_payload.json => impl/hook/native/ping.json} (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/native/prOpenFromTagPayload.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/native/pushPayload.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/native/signed_payload.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/native/tag_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/native/tag_deleted.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/plugin/branch_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/plugin/branch_deleted.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/plugin/commit_update.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/plugin/commit_update2.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks/server => impl/hook/plugin}/emptyPayload.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/plugin/pullrequest_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/plugin/pullrequest_merged.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/{hooks => impl/hook}/plugin/pullrequest_updated.json (100%) diff --git a/docs/USER_GUIDE.adoc b/docs/USER_GUIDE.adoc index a1c938ca2..c4cc37eae 100644 --- a/docs/USER_GUIDE.adoc +++ b/docs/USER_GUIDE.adoc @@ -328,12 +328,12 @@ System.setProperty("http.socket.timeout", "300") // 5 minutes 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. To change this amount of time (default is 300 seconds), add the system property `bitbucket.oauth2.cache.timeout=60` on Jenkins startup. -=== Disable Branch Indexing on Empty changes +=== Enable Branch Indexing on Empty changes -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: +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: * When manually merging remote **Open** pull requests. This particular scenario produces 2 events and cause duplicated builds. * 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 * A link:http://confluence.atlassian.com/bitbucketserver/event-payload-938025882.html#Eventpayload-Mirrorsynchronized[mirror:repo_synchronized] event with too many refs -This behaviour can be disabled by adding the system property `bitbucket.hooks.processor.scanOnEmptyChanges=false` on Jenkins startup. +This behaviour can be enabled by adding the system property `bitbucket.hooks.processor.scanOnEmptyChanges=true` on Jenkins startup. diff --git a/pom.xml b/pom.xml index f4eae59a2..b80c764a8 100644 --- a/pom.xml +++ b/pom.xml @@ -83,6 +83,10 @@ io.jenkins.plugins commons-lang3-api + + io.jenkins.plugins + commons-collections4-api + org.jenkins-ci.plugins jackson2-api diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketException.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketException.java index 90e856cdf..2d62f2aef 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketException.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketException.java @@ -24,6 +24,7 @@ package com.cloudbees.jenkins.plugins.bitbucket.api; public class BitbucketException extends RuntimeException { + private static final long serialVersionUID = 1L; public BitbucketException(String message, Throwable cause) { super(message, cause); @@ -33,6 +34,4 @@ public BitbucketException(String message) { super(message); } - private static final long serialVersionUID = 1L; - } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessor.java new file mode 100644 index 000000000..6889920bd --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessor.java @@ -0,0 +1,151 @@ +/* + * The MIT License + * + * Copyright (c) 2025, Falco Nikolas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.api.hook; + +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.ExtensionPoint; +import jakarta.servlet.http.HttpServletRequest; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import jenkins.scm.api.SCMEvent; +import jenkins.scm.api.SCMHeadEvent; +import jenkins.util.SystemProperties; +import org.apache.commons.collections4.MultiValuedMap; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.Beta; + +/** + * Implementations of this extension point must provide new behaviours to + * accommodate custom event payloads from webhooks sent from Bitbucket Cloud, + * Bitbucket Data Center, or installed plugins. + *

+ * There cannot be multiple processors processing the same incoming webhook for + * a specific event installed on the system, meaning the processor must fit to + * the incoming request as much as possible or the hook will be rejected. + */ +@Restricted(Beta.class) +public interface BitbucketHookProcessor extends ExtensionPoint { + static final String SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME = "bitbucket.hooks.processor.scanOnEmptyChanges"; + + /** + * Called by first for this processor that must respond if is able to handle + * this specific request + * + * @param headers request + * @param parameters request + * @return {@code true} if this processor is able to handle this hook + * request, {@code false} otherwise. + */ + boolean canHandle(@NonNull Map headers, @NonNull MultiValuedMap parameters); + + /** + * Extracts the server URL from where this request coming from, the URL must + * match one of the configured {@link BitbucketEndpoint}s. + * + * @param headers request + * @param parameters request + * @return the URL of the server from where this request has been sent. + */ + @NonNull + String getServerURL(@NonNull Map headers, @NonNull MultiValuedMap parameters); + + /** + * Extracts the event type that represent the payload in the request. + * + * @param headers request + * @param parameters request + * @return the event type key. + */ + @NonNull + String getEventType(Map headers, MultiValuedMap parameters); + + /** + * Returns a context for a given request used when process the payload. + * + * @param request hook + * @return a map of information extracted by the given request to be used in + * the {@link #process(String, String, Map, BitbucketEndpoint)} + * method. + */ + @NonNull + default Map buildHookContext(@NonNull HttpServletRequest request) { + return Map.of("origin", SCMEvent.originOf(request)); + } + + /** + * The implementation must verify if the incoming request is secured or not + * eventually gather some settings from the given {@link BitbucketEndpoint} + * configuration. + * + * @param headers request + * @param payload request + * @param endpoint configured for the given + * {@link #getServerURL(Map, MultiValuedMap)} + * @throws BitbucketHookProcessorException when signature verification fails + */ + void verifyPayload(@NonNull Map headers, + @NonNull String payload, + @NonNull BitbucketEndpoint endpoint) throws BitbucketHookProcessorException; + + /** + * Settings that will trigger a re-index of the multibranch + * project/organization folder when the request does not ship any source + * changes. + * + * @return if should perform a reindex of the project or not. + */ + default boolean reindexOnEmptyChanges() { + return SystemProperties.getBoolean(SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME, false); + } + + /** + * See Event + * Payloads for more information about the payload parameter format. + * + * @param eventType the type of hook event. + * @param payload the hook payload + * @param context build from incoming request + * @param endpoint configured in the Jenkins global page + */ + void process(@NonNull String eventType, @NonNull String payload, @NonNull Map context, @NonNull BitbucketEndpoint endpoint); + + /** + * Implementations have to call this method when want propagate an + * {@link SCMHeadEvent} to the scm-api. + * + * @param event the to fire + * @param delaySeconds a delay in seconds to wait before propagate the + * event. If the given value is less than 0 than default will be + * used. + */ + default void notifyEvent(SCMHeadEvent event, int delaySeconds) { + if (delaySeconds == 0) { + SCMHeadEvent.fireNow(event); + } else { + SCMHeadEvent.fireLater(event, delaySeconds > 0 ? delaySeconds : BitbucketSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS); + } + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPingHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessorException.java similarity index 60% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPingHookProcessor.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessorException.java index 45c5196ef..36978c39b 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPingHookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessorException.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016-2018, Yieldlab AG + * Copyright (c) 2025, Falco Nikolas * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,23 +21,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; +package com.cloudbees.jenkins.plugins.bitbucket.api.hook; -import hudson.RestrictedSince; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketException; -@Restricted(NoExternalUse.class) -@RestrictedSince("933.3.0") -public class NativeServerPingHookProcessor extends HookProcessor { +public class BitbucketHookProcessorException extends BitbucketException { + private static final long serialVersionUID = 6682700868741672883L; + private final int httpCode; - private static final Logger LOGGER = Logger.getLogger(NativeServerPingHookProcessor.class.getName()); + public BitbucketHookProcessorException(int httpCode, String message) { + super(message); + this.httpCode = httpCode; + } - @Override - public void process(HookEventType hookEvent, String payload, BitbucketType instanceType, String origin) { - LOGGER.log(Level.INFO, "Received webhook ping event from {0}", origin); + public int getHttpCode() { + return httpCode; } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/filesystem/BitbucketSCMFileSystem.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/filesystem/BitbucketSCMFileSystem.java index d9b8a8805..cca38fd20 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/filesystem/BitbucketSCMFileSystem.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/filesystem/BitbucketSCMFileSystem.java @@ -31,10 +31,8 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApiFactory; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit; -import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.DateUtils; -import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerVersion; import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.common.StandardCredentials; @@ -293,13 +291,7 @@ public SCMFileSystem build(@NonNull SCMSource source, @NonNull SCMHead head, @Ch // Bitbucket server v7 doesn't have the `merge` ref for PRs // We don't return `ref` when working with v7 // so that pipeline falls back to heavyweight checkout properly - boolean ligthCheckout = BitbucketServerEndpoint.findServerVersion(serverURL) != BitbucketServerVersion.VERSION_7; - if (ligthCheckout) { - ref = "pull-requests/" + prHead.getId() + "/merge"; - } else { - // returning null to fall back to heavyweight checkout - return null; - } + return null; } } else if (head instanceof BitbucketTagSCMHead) { ref = "tags/" + head.getName(); diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiver.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiver.java index 68b012fd3..b5bc8deec 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiver.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiver.java @@ -25,49 +25,45 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; -import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; -import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; +import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessor; +import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessorException; import hudson.Extension; +import hudson.ExtensionList; import hudson.model.UnprotectedRootAction; import hudson.security.csrf.CrumbExclusion; -import hudson.util.HttpResponses; -import hudson.util.Secret; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; -import jenkins.scm.api.SCMEvent; -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.codec.digest.HmacAlgorithms; -import org.apache.commons.codec.digest.HmacUtils; +import java.util.stream.Stream; +import org.apache.commons.collections4.EnumerationUtils; +import org.apache.commons.collections4.MultiMapUtils; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang3.ObjectUtils; -import org.jenkinsci.plugins.plaincredentials.StringCredentials; +import org.apache.commons.lang3.StringUtils; import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.HttpResponses.HttpResponseException; +import org.kohsuke.stapler.HttpResponses; import org.kohsuke.stapler.StaplerRequest2; -import static org.apache.commons.lang.StringUtils.trimToNull; - /** * Process Bitbucket push and pull requests creations/updates hooks. */ @Extension public class BitbucketSCMSourcePushHookReceiver extends CrumbExclusion implements UnprotectedRootAction { - private static final Logger LOGGER = Logger.getLogger(BitbucketSCMSourcePushHookReceiver.class.getName()); - + private static final Logger logger = Logger.getLogger(BitbucketSCMSourcePushHookReceiver.class.getName()); private static final String PATH = "bitbucket-scmsource-hook"; - public static final String FULL_PATH = PATH + "/notify"; @Override @@ -94,120 +90,83 @@ public String getUrlName() { * @throws IOException if there is any issue reading the HTTP content payload. */ public HttpResponse doNotify(StaplerRequest2 req) throws IOException { - String origin = SCMEvent.originOf(req); - String body = IOUtils.toString(req.getInputStream(), StandardCharsets.UTF_8); - - String eventKey = req.getHeader("X-Event-Key"); - if (eventKey == null) { - return HttpResponses.error(HttpServletResponse.SC_BAD_REQUEST, "X-Event-Key HTTP header not found"); - } - HookEventType type = HookEventType.fromString(eventKey); - if (type == null) { - LOGGER.info(() -> "Received unknown Bitbucket hook: " + eventKey + ". Skipping."); - return HttpResponses.error(HttpServletResponse.SC_BAD_REQUEST, "X-Event-Key HTTP header invalid: " + eventKey); - } - - String bitbucketKey = req.getHeader("X-Bitbucket-Type"); // specific header from Plugin implementation - String serverURL = req.getParameter("server_url"); + try { + Map reqHeaders = getHeaders(req); + MultiValuedMap reqParameters = getParameters(req); + BitbucketHookProcessor hookProcessor = getHookProcessor(reqHeaders, reqParameters); + + String body = IOUtils.toString(req.getInputStream(), StandardCharsets.UTF_8); + if (StringUtils.isEmpty(body)) { + return HttpResponses.error(HttpServletResponse.SC_BAD_REQUEST, "Payload is empty."); + } - BitbucketType instanceType = null; - if (bitbucketKey != null) { - instanceType = BitbucketType.fromString(bitbucketKey); - LOGGER.log(Level.FINE, "X-Bitbucket-Type header found {0}.", instanceType); - } - if (serverURL != null) { - if (instanceType == null) { - LOGGER.log(Level.FINE, "server_url request parameter found. Bitbucket Native Server webhook incoming."); - instanceType = BitbucketType.SERVER; - } else { - LOGGER.log(Level.FINE, "X-Bitbucket-Type header / server_url request parameter found. Bitbucket Plugin Server webhook incoming."); + String serverURL = hookProcessor.getServerURL(Collections.unmodifiableMap(reqHeaders), MultiMapUtils.unmodifiableMultiValuedMap(reqParameters)); + BitbucketEndpoint endpoint = BitbucketEndpointProvider + .lookupEndpoint(serverURL) + .orElse(null); + if (endpoint == null) { + logger.log(Level.SEVERE, "No configured bitbucket endpoint found for {0}.", serverURL); + return HttpResponses.error(HttpServletResponse.SC_BAD_REQUEST, "No bitbucket endpoint found for " + serverURL); } - } else { - LOGGER.log(Level.FINE, "X-Bitbucket-Type header / server_url request parameter not found. Bitbucket Cloud webhook incoming."); - instanceType = BitbucketType.CLOUD; - serverURL = BitbucketCloudEndpoint.SERVER_URL; - } - BitbucketEndpoint endpoint = BitbucketEndpointProvider - .lookupEndpoint(serverURL) - .orElse(null); - if (endpoint != null) { if (endpoint.isEnableHookSignature()) { - if (req.getHeader("X-Hub-Signature") != null) { - HttpResponseException error = checkSignature(req, body, endpoint); - if (error != null) { - return error; - } - } else { - return HttpResponses.error(HttpServletResponse.SC_FORBIDDEN, "Payload has not be signed, configure the webHook secret in Bitbucket as documented at https://github.com/jenkinsci/bitbucket-branch-source-plugin/blob/master/docs/USER_GUIDE.adoc#webhooks-registering"); - } - } else if (req.getHeader("X-Hub-Signature") == null) { - LOGGER.log(Level.FINER, "Signature not configured for bitbucket endpoint {0}.", serverURL); + logger.log(Level.FINE, "Payload endpoint host {0}, request endpoint host {1}", new Object[] { endpoint, req.getRemoteAddr() }); + hookProcessor.verifyPayload(reqHeaders, body, endpoint); + } else { + logger.log(Level.FINER, "Signature not configured for bitbucket endpoint {0}.", serverURL); } - } else { - LOGGER.log(Level.INFO, "No bitbucket endpoint found for {0} to verify the signature of incoming webhook.", serverURL); - } - HookProcessor hookProcessor = getHookProcessor(type); - hookProcessor.process(type, body, instanceType, origin, serverURL); + Map context = hookProcessor.buildHookContext(req); + String eventType = hookProcessor.getEventType(Collections.unmodifiableMap(reqHeaders), MultiMapUtils.unmodifiableMultiValuedMap(reqParameters)); + + hookProcessor.process(eventType, body, context, endpoint); + } catch(BitbucketHookProcessorException e) { + return HttpResponses.error(e.getHttpCode(), e.getMessage()); + } return HttpResponses.ok(); } - @Nullable - private HttpResponseException checkSignature(@NonNull StaplerRequest2 req, @NonNull String body, @NonNull BitbucketEndpoint endpoint) { - LOGGER.log(Level.FINE, "Payload endpoint host {0}, request endpoint host {1}", new Object[] { endpoint, req.getRemoteAddr() }); - - StringCredentials signatureCredentials = endpoint.hookSignatureCredentials(); - if (signatureCredentials != null) { - String signatureHeader = req.getHeader("X-Hub-Signature"); - String bitbucketAlgorithm = trimToNull(StringUtils.substringBefore(signatureHeader, "=")); - String bitbucketSignature = trimToNull(StringUtils.substringAfter(signatureHeader, "=")); - HmacAlgorithms algorithm = getAlgorithm(bitbucketAlgorithm); - if (algorithm == null) { - return HttpResponses.error(HttpServletResponse.SC_FORBIDDEN, "Signature " + bitbucketAlgorithm + " not supported"); - } - HmacUtils util; - try { - String key = Secret.toString(signatureCredentials.getSecret()); - util = new HmacUtils(algorithm, key.getBytes(StandardCharsets.UTF_8)); - byte[] digest = util.hmac(body); - if (!MessageDigest.isEqual(Hex.decodeHex(bitbucketSignature), digest)) { - return HttpResponses.error(HttpServletResponse.SC_FORBIDDEN, "Signature verification failed"); - } - } catch (IllegalArgumentException e) { - return HttpResponses.error(HttpServletResponse.SC_BAD_REQUEST, "Signature method not supported: " + algorithm); - } catch (DecoderException e) { - return HttpResponses.error(HttpServletResponse.SC_BAD_REQUEST, "Hex signature can not be decoded: " + bitbucketSignature); - } + private BitbucketHookProcessor getHookProcessor(Map reqHeaders, + MultiValuedMap reqParameters) { + BitbucketHookProcessor hookProcessor; + + List matchingProcessors = getHookProcessors() + .filter(processor -> processor.canHandle(Collections.unmodifiableMap(reqHeaders), MultiMapUtils.unmodifiableMultiValuedMap(reqParameters))) + .toList(); + if (matchingProcessors.isEmpty()) { + logger.warning(() -> "No processor found for the incoming Bitbucket hook. Skipping."); + 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"); + } else if (matchingProcessors.size() > 1) { + String processors = StringUtils.joinWith("\n- ", matchingProcessors.stream() + .map(p -> p.getClass().getName()) + .toList()); + logger.severe(() -> "More processors found that handle the incoming Bitbucket hook:\n" + processors); + throw new BitbucketHookProcessorException(HttpServletResponse.SC_CONFLICT, "More processors found that handle the incoming Bitbucket hook."); } else { - String hookId = req.getHeader("X-Hook-UUID"); - String requestId = ObjectUtils.firstNonNull(req.getHeader("X-Request-UUID"), req.getHeader("X-Request-Id")); - String hookSignatureCredentialsId = endpoint.getHookSignatureCredentialsId(); - LOGGER.log(Level.WARNING, "No credentials {0} found to verify the signature of incoming webhook {1} request {2}", new Object[] { hookSignatureCredentialsId, hookId, requestId }); - return HttpResponses.error(HttpServletResponse.SC_FORBIDDEN, "No credentials " + hookSignatureCredentialsId + " found in Jenkins to verify the signature"); + hookProcessor = matchingProcessors.get(0); + logger.fine(() -> "Hook processor " + hookProcessor.getClass().getName() + " found."); } - return null; + return hookProcessor; } - @CheckForNull - private HmacAlgorithms getAlgorithm(String algorithm) { - switch (StringUtils.lowerCase(algorithm)) { - case "sha1": - return HmacAlgorithms.HMAC_SHA_1; - case "sha256": - return HmacAlgorithms.HMAC_SHA_256; - case "sha384": - return HmacAlgorithms.HMAC_SHA_384; - case "sha512": - return HmacAlgorithms.HMAC_SHA_512; - default: - return null; + /*test*/ Stream getHookProcessors() { + return ExtensionList.lookup(BitbucketHookProcessor.class).stream(); + } + + private MultiValuedMap getParameters(StaplerRequest2 req) { + MultiValuedMap reqParameters = new ArrayListValuedHashMap<>(); + for (Entry entry : req.getParameterMap().entrySet()) { + reqParameters.putAll(entry.getKey(), Arrays.asList(entry.getValue())); } + return reqParameters; } - /* For test purpose */ - HookProcessor getHookProcessor(HookEventType type) { - return type.getProcessor(); + private Map getHeaders(StaplerRequest2 req) { + Map reqHeaders = new HashMap<>(); + for (String headerName : EnumerationUtils.asIterable(req.getHeaderNames())) { + reqHeaders.put(headerName, req.getHeader(headerName)); + } + return reqHeaders; } @Override diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/HookEventType.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/HookEventType.java index 926dfff42..2f0d5d4d5 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/HookEventType.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/HookEventType.java @@ -23,7 +23,6 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.hooks; -import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; /** @@ -34,125 +33,118 @@ public enum HookEventType { /** * See EventPayloads-Push */ - PUSH("repo:push", PushHookProcessor.class), + PUSH("repo:push"), /** * See EventPayloads-Created */ - PULL_REQUEST_CREATED("pullrequest:created", PullRequestHookProcessor.class), + PULL_REQUEST_CREATED("pullrequest:created"), /** * See EventPayloads-Updated */ - PULL_REQUEST_UPDATED("pullrequest:updated", PullRequestHookProcessor.class), + PULL_REQUEST_UPDATED("pullrequest:updated"), /** * See EventPayloads-Merged */ - PULL_REQUEST_MERGED("pullrequest:fulfilled", PullRequestHookProcessor.class), + PULL_REQUEST_MERGED("pullrequest:fulfilled"), /** * See EventPayloads-Declined */ - PULL_REQUEST_DECLINED("pullrequest:rejected", PullRequestHookProcessor.class), + PULL_REQUEST_DECLINED("pullrequest:rejected"), /** * See EventPayloads-Approved */ - PULL_REQUEST_APPROVED("pullrequest:approved", PullRequestHookProcessor.class), + @Deprecated + PULL_REQUEST_APPROVED("pullrequest:approved"), /** * @see Eventpayload-Push * @since Bitbucket Server 5.4 */ - SERVER_REFS_CHANGED("repo:refs_changed", NativeServerPushHookProcessor.class), + SERVER_REFS_CHANGED("repo:refs_changed"), /** * @see Eventpayload-repo-mirr-syn * @since Bitbucket Server 6.5 */ - SERVER_MIRROR_REPO_SYNCHRONIZED("mirror:repo_synchronized", NativeServerPushHookProcessor.class), + SERVER_MIRROR_REPO_SYNCHRONIZED("mirror:repo_synchronized"), /** * @see Eventpayload-Opened * @since Bitbucket Server 5.4 */ - SERVER_PULL_REQUEST_OPENED("pr:opened", NativeServerPullRequestHookProcessor.class), + SERVER_PULL_REQUEST_OPENED("pr:opened"), /** * @see Eventpayload-Merged * @since Bitbucket Server 5.4 */ - SERVER_PULL_REQUEST_MERGED("pr:merged", NativeServerPullRequestHookProcessor.class), + SERVER_PULL_REQUEST_MERGED("pr:merged"), /** * See Eventpayload-Declined * @since Bitbucket Server 5.4 */ - SERVER_PULL_REQUEST_DECLINED("pr:declined", NativeServerPullRequestHookProcessor.class), + SERVER_PULL_REQUEST_DECLINED("pr:declined"), /** * See Eventpayload-Deleted * * @since Bitbucket Server 5.4 */ - SERVER_PULL_REQUEST_DELETED("pr:deleted", NativeServerPullRequestHookProcessor.class), + SERVER_PULL_REQUEST_DELETED("pr:deleted"), /** * See Eventpayload-Approved * * @since Bitbucket Server 5.4 */ - SERVER_PULL_REQUEST_APPROVED("pr:reviewer:approved", NativeServerPullRequestHookProcessor.class), + @Deprecated + SERVER_PULL_REQUEST_APPROVED("pr:reviewer:approved"), /** * @see Eventpayload: Pull Request - Modified * @since Bitbucket Server 5.10 */ - SERVER_PULL_REQUEST_MODIFIED("pr:modified", NativeServerPullRequestHookProcessor.class), + SERVER_PULL_REQUEST_MODIFIED("pr:modified"), /** * @see Eventpayload: Pull Request - Reviewers Updated * @since Bitbucket Server 5.10 */ - SERVER_PULL_REQUEST_REVIEWER_UPDATED("pr:reviewer:updated", NativeServerPullRequestHookProcessor.class), + @Deprecated + SERVER_PULL_REQUEST_REVIEWER_UPDATED("pr:reviewer:updated"), /** * @see Eventpayload-Sourcebranchupdated * @since Bitbucket Server 7.0 */ - SERVER_PULL_REQUEST_FROM_REF_UPDATED("pr:from_ref_updated", NativeServerPullRequestHookProcessor.class), + SERVER_PULL_REQUEST_FROM_REF_UPDATED("pr:from_ref_updated"), /** * Sent when hitting the {@literal "Test connection"} button in Bitbucket Server. Apparently undocumented. */ - SERVER_PING("diagnostics:ping", NativeServerPingHookProcessor.class); + SERVER_PING("diagnostics:ping"); private final String key; - private final Class clazz; -

HookEventType(@NonNull String key, Class

clazz) { + HookEventType(@NonNull String key) { this.key = key; - this.clazz = clazz; } - @CheckForNull + @NonNull public static HookEventType fromString(String key) { for (HookEventType value : HookEventType.values()) { if (value.getKey().equals(key)) { return value; } } - return null; - } - - public HookProcessor getProcessor() { - try { - return (HookProcessor) clazz.getDeclaredConstructor().newInstance(); - } catch (ReflectiveOperationException e) { - throw new AssertionError("Can not instantiate hook payload processor: " + e.getMessage()); - } + throw new IllegalArgumentException("No enum constant " + HookEventType.class.getCanonicalName() + " have key " + key); } public String getKey() { diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/HookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/HookProcessor.java deleted file mode 100644 index 0bcc1682e..000000000 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/HookProcessor.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2016, CloudBees, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; - -import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; -import hudson.security.ACL; -import hudson.security.ACLContext; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; -import jenkins.scm.api.SCMHeadEvent; -import jenkins.scm.api.SCMSource; -import jenkins.scm.api.SCMSourceOwner; -import jenkins.scm.api.SCMSourceOwners; -import jenkins.util.SystemProperties; -import org.apache.commons.lang3.StringUtils; - -/** - * Abstract hook processor. - * - * Add new hook processors by extending this class and implement {@link #process(HookEventType, String, BitbucketType, String)}, - * extract details from the hook payload and then fire an {@link jenkins.scm.api.SCMEvent} to dispatch it to the SCM API. - */ -public abstract class HookProcessor { - - protected static final String SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME = "bitbucket.hooks.processor.scanOnEmptyChanges"; - protected static final boolean SCAN_ON_EMPTY_CHANGES = SystemProperties.getBoolean(SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME, true); - - private static final Logger LOGGER = Logger.getLogger(HookProcessor.class.getName()); - - /** - * See Event Payloads for more - * information about the payload parameter format. - * @param type the type of hook. - * @param payload the hook payload - * @param instanceType the Bitbucket type that called the hook - * @param origin the origin of the event. - */ - public abstract void process(HookEventType type, String payload, BitbucketType instanceType, String origin); - - /** - * See Event Payloads for more - * information about the payload parameter format. - * @param type the type of hook. - * @param payload the hook payload - * @param instanceType the Bitbucket type that called the hook - * @param origin the origin of the event. - * @param serverURL special value for native Bitbucket Server hooks which don't expose the server URL in the payload. - */ - public void process(HookEventType type, String payload, BitbucketType instanceType, String origin, String serverURL) { - process(type, payload, instanceType, origin); - } - - /** - * To be called by implementations once the owner and the repository have been extracted from the payload. - * - * @param owner the repository owner as configured in the SCMSource - * @param repository the repository name as configured in the SCMSource - * @param mirrorId the mirror id if applicable, may be null - */ - protected void scmSourceReIndex(final String owner, final String repository, final String mirrorId) { - try (ACLContext context = ACL.as2(ACL.SYSTEM2)) { - boolean reindexed = false; - for (SCMSourceOwner scmOwner : SCMSourceOwners.all()) { - List sources = scmOwner.getSCMSources(); - for (SCMSource source : sources) { - // Search for the correct SCM source - if (source instanceof BitbucketSCMSource scmSource - && StringUtils.equalsIgnoreCase(scmSource.getRepoOwner(), owner) - && scmSource.getRepository().equals(repository) - && (mirrorId == null || StringUtils.equalsIgnoreCase(mirrorId, scmSource.getMirrorId()))) { - LOGGER.log(Level.INFO, "Multibranch project found, reindexing " + scmOwner.getName()); - // TODO: SCMSourceOwner.onSCMSourceUpdated is deprecated. We may explore options with an - // SCMEventListener extension and firing SCMSourceEvents. - scmOwner.onSCMSourceUpdated(source); - reindexed = true; - } - } - } - if (!reindexed) { - LOGGER.log(Level.INFO, "No multibranch project matching for reindex on {0}/{1}", new Object[] {owner, repository}); - } - } - } - - /** - * Implementations have to call this method when want propagate an - * {@link SCMHeadEvent} to the scm-api. - * - * @param event the to fire - * @param delaySeconds a delay in seconds to wait before propagate the - * event. If the given value is less than 0 than default will be - * used. - */ - protected void notifyEvent(SCMHeadEvent event, int delaySeconds) { - if (delaySeconds == 0) { - SCMHeadEvent.fireNow(event); - } else { - SCMHeadEvent.fireLater(event, delaySeconds > 0 ? delaySeconds : BitbucketSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS); - } - } -} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PullRequestHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PullRequestHookProcessor.java deleted file mode 100644 index 893738275..000000000 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PullRequestHookProcessor.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2016, CloudBees, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; - -import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; -import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestEvent; -import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudWebhookPayload; -import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerWebhookPayload; -import hudson.RestrictedSince; -import jenkins.scm.api.SCMEvent; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; - -@Restricted(NoExternalUse.class) -@RestrictedSince("933.3.0") -public class PullRequestHookProcessor extends HookProcessor { - - @Override - public void process(final HookEventType hookEvent, String payload, final BitbucketType instanceType, String origin) { - if (payload != null) { - BitbucketPullRequestEvent pull; - if (instanceType == BitbucketType.SERVER) { - // plugin webhook case - pull = BitbucketServerWebhookPayload.pullRequestEventFromPayload(payload); - } else { - pull = BitbucketCloudWebhookPayload.pullRequestEventFromPayload(payload); - } - if (pull != null) { - SCMEvent.Type eventType; - switch (hookEvent) { - case PULL_REQUEST_CREATED: - eventType = SCMEvent.Type.CREATED; - break; - case PULL_REQUEST_DECLINED, - PULL_REQUEST_MERGED: - eventType = SCMEvent.Type.REMOVED; - break; - default: - eventType = SCMEvent.Type.UPDATED; - break; - } - // assume updated as a catch-all type - notifyEvent(new PREvent(eventType, pull, origin, hookEvent), BitbucketSCMSource.getEventDelaySeconds()); - } - } - } -} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PushHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PushHookProcessor.java deleted file mode 100644 index 6cb45ea5f..000000000 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PushHookProcessor.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2016, CloudBees, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; - -import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; -import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPushEvent; -import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudWebhookPayload; -import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerWebhookPayload; -import hudson.RestrictedSince; -import java.util.logging.Level; -import java.util.logging.Logger; -import jenkins.scm.api.SCMEvent; -import jenkins.scm.api.SCMHeadEvent; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; - -@Restricted(NoExternalUse.class) -@RestrictedSince("933.3.0") -public class PushHookProcessor extends HookProcessor { - - private static final Logger LOGGER = Logger.getLogger(PushHookProcessor.class.getName()); - - @Override - public void process(HookEventType hookEvent, String payload, BitbucketType instanceType, String origin) { - if (payload != null) { - BitbucketPushEvent push; - if (instanceType == BitbucketType.SERVER) { - // plugin webhook case - push = BitbucketServerWebhookPayload.pushEventFromPayload(payload); - } else { - push = BitbucketCloudWebhookPayload.pushEventFromPayload(payload); - } - if (push != null) { - if (push.getChanges().isEmpty()) { - final String owner = push.getRepository().getOwnerName(); - final String repository = push.getRepository().getRepositoryName(); - if (instanceType == BitbucketType.CLOUD || SCAN_ON_EMPTY_CHANGES) { - LOGGER.log(Level.INFO, "Received push hook with empty changes from Bitbucket. Processing indexing on {0}/{1}. " + - "You may skip this scan by adding the system property -D{2}=false on startup.", - new Object[]{owner, repository, SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME}); - scmSourceReIndex(owner, repository, null); - } else { - LOGGER.log(Level.INFO, "Received push hook with empty changes from Bitbucket for {0}/{1}. Skipping.", - new Object[]{owner, repository}); - } - } else { - SCMHeadEvent.Type type = null; - for (BitbucketPushEvent.Change change : push.getChanges()) { - if ((type == null || type == SCMEvent.Type.CREATED) && change.isCreated()) { - type = SCMEvent.Type.CREATED; - } else if ((type == null || type == SCMEvent.Type.REMOVED) && change.isClosed()) { - type = SCMEvent.Type.REMOVED; - } else { - type = SCMEvent.Type.UPDATED; - } - } - notifyEvent(new PushEvent(type, push, origin), BitbucketSCMSource.getEventDelaySeconds()); - } - } - } - } - -} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookConfiguration.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookConfiguration.java index 8cde8d2ba..085c657c9 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookConfiguration.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookConfiguration.java @@ -64,38 +64,19 @@ public class WebhookConfiguration { )); /** - * The list of events available in Bitbucket Server v7.x. + * The list of events available in Bitbucket Data Center for the minimum supported version. */ - private static final List NATIVE_SERVER_EVENTS_v7 = Collections.unmodifiableList(Arrays.asList( + private static final List NATIVE_SERVER_EVENTS = Collections.unmodifiableList(Arrays.asList( HookEventType.SERVER_REFS_CHANGED.getKey(), HookEventType.SERVER_PULL_REQUEST_OPENED.getKey(), HookEventType.SERVER_PULL_REQUEST_MERGED.getKey(), HookEventType.SERVER_PULL_REQUEST_DECLINED.getKey(), HookEventType.SERVER_PULL_REQUEST_DELETED.getKey(), - // only on v5.10 and above HookEventType.SERVER_PULL_REQUEST_MODIFIED.getKey(), - HookEventType.SERVER_PULL_REQUEST_REVIEWER_UPDATED.getKey(), - // only on v6.5 and above HookEventType.SERVER_MIRROR_REPO_SYNCHRONIZED.getKey(), - // only on v7.x and above HookEventType.SERVER_PULL_REQUEST_FROM_REF_UPDATED.getKey() )); - /** - * The list of events available in Bitbucket Server v6.5+. - */ - private static final List NATIVE_SERVER_EVENTS_v6_5 = Collections.unmodifiableList(NATIVE_SERVER_EVENTS_v7.subList(0, 8)); - - /** - * The list of events available in Bitbucket Server v6.x. Applies to v5.10+. - */ - private static final List NATIVE_SERVER_EVENTS_v6 = Collections.unmodifiableList(NATIVE_SERVER_EVENTS_v7.subList(0, 7)); - - /** - * The list of events available in Bitbucket Server v5.9-. - */ - private static final List NATIVE_SERVER_EVENTS_v5 = Collections.unmodifiableList(NATIVE_SERVER_EVENTS_v7.subList(0, 5)); - /** * The title of the webhook. */ @@ -143,7 +124,8 @@ boolean updateHook(BitbucketWebHook hook, BitbucketSCMSource owner) { } } else if (hook instanceof BitbucketServerWebhook serverHook) { String serverURL = owner.getServerUrl(); - String url = getServerWebhookURL(serverURL, BitbucketEndpointProvider.lookupEndpointJenkinsRootURL(owner.getServerUrl())); + BitbucketEndpoint endpoint = BitbucketEndpointProvider.lookupEndpoint(serverURL).orElseThrow(); + String url = getServerWebhookURL(serverURL, endpoint.getEndpointJenkinsRootURL()); if (!url.equals(serverHook.getUrl())) { serverHook.setUrl(url); @@ -152,11 +134,11 @@ boolean updateHook(BitbucketWebHook hook, BitbucketSCMSource owner) { List events = serverHook.getEvents(); if (events == null) { - serverHook.setEvents(getNativeServerEvents(serverURL)); + serverHook.setEvents(getNativeServerEvents(endpoint)); updated = true; - } else if (!events.containsAll(getNativeServerEvents(serverURL))) { + } else if (!events.containsAll(getNativeServerEvents(endpoint))) { Set newEvents = new TreeSet<>(events); - newEvents.addAll(getNativeServerEvents(serverURL)); + newEvents.addAll(getNativeServerEvents(endpoint)); serverHook.setEvents(new ArrayList<>(newEvents)); updated = true; } @@ -171,27 +153,28 @@ boolean updateHook(BitbucketWebHook hook, BitbucketSCMSource owner) { } public BitbucketWebHook getHook(BitbucketSCMSource owner) { - final String serverUrl = owner.getServerUrl(); - final String rootUrl = BitbucketEndpointProvider.lookupEndpointJenkinsRootURL(owner.getServerUrl()); + final String serverURL = owner.getServerUrl(); + BitbucketEndpoint endpoint = BitbucketEndpointProvider.lookupEndpoint(serverURL).orElseThrow(); + final String rootURL = endpoint.getEndpointJenkinsRootURL(); final String signatureSecret = getSecret(owner.getServerUrl()); - if (BitbucketApiUtils.isCloud(serverUrl)) { + if (BitbucketApiUtils.isCloud(serverURL)) { BitbucketCloudHook hook = new BitbucketCloudHook(); hook.setEvents(CLOUD_EVENTS); hook.setActive(true); hook.setDescription(description); - hook.setUrl(rootUrl + BitbucketSCMSourcePushHookReceiver.FULL_PATH); + hook.setUrl(rootURL + BitbucketSCMSourcePushHookReceiver.FULL_PATH); hook.setSecret(signatureSecret); return hook; } - switch (BitbucketServerEndpoint.findWebhookImplementation(serverUrl)) { + switch (BitbucketServerEndpoint.findWebhookImplementation(serverURL)) { case NATIVE: { BitbucketServerWebhook hook = new BitbucketServerWebhook(); hook.setActive(true); hook.setDescription(description); - hook.setEvents(getNativeServerEvents(serverUrl)); - hook.setUrl(getServerWebhookURL(serverUrl, rootUrl)); + hook.setEvents(getNativeServerEvents(endpoint)); + hook.setUrl(getServerWebhookURL(serverURL, rootURL)); hook.setSecret(signatureSecret); return hook; } @@ -201,7 +184,7 @@ public BitbucketWebHook getHook(BitbucketSCMSource owner) { BitbucketPluginWebhook hook = new BitbucketPluginWebhook(); hook.setActive(true); hook.setDescription(description); - hook.setUrl(getServerWebhookURL(serverUrl, rootUrl)); + hook.setUrl(getServerWebhookURL(serverURL, rootURL)); hook.setCommittersToIgnore(committersToIgnore); return hook; } @@ -224,44 +207,16 @@ private String getSecret(@NonNull String serverURL) { return null; } - private static List getNativeServerEvents(String serverUrl) { - BitbucketServerEndpoint endpoint = BitbucketEndpointProvider - .lookupEndpoint(serverUrl, BitbucketServerEndpoint.class) - .orElse(null); - if (endpoint != null) { - switch (endpoint.getServerVersion()) { - case VERSION_5: - return NATIVE_SERVER_EVENTS_v5; - case VERSION_5_10: - return NATIVE_SERVER_EVENTS_v6; - case VERSION_6: - // plugin version 2.9.1 introduced VERSION_6 setting for Bitbucket but it - // actually applies - // to Version 5.10+. In order to preserve backwards compatibility, rather than - // remove - // VERSION_6, it will use the same list as 5.10 until such time a need arises - // for it to have its - // own list - return NATIVE_SERVER_EVENTS_v6; - case VERSION_6_5: - return NATIVE_SERVER_EVENTS_v6_5; - case VERSION_7: - default: - return NATIVE_SERVER_EVENTS_v7; - } - } - - // Not specifically v6, use v7. - // Better to give an error than quietly not register some events. - return NATIVE_SERVER_EVENTS_v7; + private static List getNativeServerEvents(BitbucketEndpoint endpoint) { + return NATIVE_SERVER_EVENTS; } - private static String getServerWebhookURL(String serverUrl, String rootUrl) { - return UriTemplate.buildFromTemplate(rootUrl) + private static String getServerWebhookURL(String serverURL, String rootURL) { + return UriTemplate.buildFromTemplate(rootURL) .template(BitbucketSCMSourcePushHookReceiver.FULL_PATH) .query("server_url") .build() - .set("server_url", serverUrl) + .set("server_url", serverURL) .expand(); } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint.java index d11f4136b..ff02b7c55 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint.java @@ -109,7 +109,9 @@ public void setManageHooks(boolean manageHooks, String credentialsId) { */ @Deprecated(since = "936.4.0", forRemoval = true) @NonNull - public abstract String getServerUrl(); + public String getServerUrl() { + return this.getServerURL(); + } /** * A Jenkins Server Root URL should end with a slash to use with webhooks. @@ -154,12 +156,6 @@ public String getEndpointJenkinsRootURL() { return normalizeJenkinsRootURL(endpointURL); } - @NonNull - @Override - public String getRepositoryURL(@NonNull String repoOwner, @NonNull String repoSlug) { - return this.getRepositoryUrl(repoOwner, repoSlug); - } - @DataBoundSetter public void setBitbucketJenkinsRootUrl(String bitbucketJenkinsRootUrl) { if (manageHooks) { @@ -193,11 +189,7 @@ public boolean isEnableHookSignature() { @Deprecated(since = "936.4.0", forRemoval = true) @NonNull public String getEndpointJenkinsRootUrl() { - if (StringUtils.isBlank(bitbucketJenkinsRootUrl)) { - return DisplayURLProvider.get().getRoot(); - } else { - return bitbucketJenkinsRootUrl; - } + return getEndpointJenkinsRootURL(); } /** @@ -207,8 +199,11 @@ public String getEndpointJenkinsRootUrl() { * @param repository the repository. * @return the user facing URL of the specified repository. */ + @Deprecated(since = "937.0.0", forRemoval = true) @NonNull - public abstract String getRepositoryUrl(@NonNull String repoOwner, @NonNull String repository); + public String getRepositoryUrl(@NonNull String repoOwner, @NonNull String repository) { + return this.getRepositoryURL(repoOwner, repository); + } /** * Returns {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point. diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpoint.java index 78934ab1b..9ce04c07e 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpoint.java @@ -146,7 +146,7 @@ public String getServerURL() { */ @NonNull @Override - public String getRepositoryUrl(@NonNull String repoOwner, @NonNull String repository) { + public String getRepositoryURL(@NonNull String repoOwner, @NonNull String repository) { UriTemplate template = UriTemplate .fromTemplate(SERVER_URL + "{/owner,repo}") .set("owner", repoOwner) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint.java index 03998889a..f8e5b809c 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint.java @@ -40,7 +40,6 @@ import hudson.util.ListBoxModel; import java.net.MalformedURLException; import java.net.URL; -import java.util.Objects; import jenkins.scm.api.SCMName; import org.apache.commons.lang3.StringUtils; import org.jenkinsci.plugins.plaincredentials.StringCredentials; @@ -85,7 +84,7 @@ public static BitbucketServerVersion findServerVersion(String serverURL) { return BitbucketEndpointProvider .lookupEndpoint(serverURL, BitbucketServerEndpoint.class) .map(endpoint -> endpoint.getServerVersion()) - .orElse(BitbucketServerVersion.VERSION_7); + .orElse(BitbucketServerVersion.getMinSupportedVersion()); } /** @@ -101,12 +100,12 @@ public static BitbucketServerVersion findServerVersion(String serverURL) { private final String serverUrl; @NonNull - private BitbucketServerWebhookImplementation webhookImplementation = BitbucketServerWebhookImplementation.PLUGIN; + private BitbucketServerWebhookImplementation webhookImplementation = BitbucketServerWebhookImplementation.NATIVE; /** * The server version for this endpoint. */ - private BitbucketServerVersion serverVersion = BitbucketServerVersion.VERSION_7; + private BitbucketServerVersion serverVersion = BitbucketServerVersion.getMinSupportedVersion(); /** * Whether to always call the can merge api when retrieving pull requests. @@ -188,8 +187,12 @@ public BitbucketServerVersion getServerVersion() { } @DataBoundSetter - public void setServerVersion(@NonNull BitbucketServerVersion serverVersion) { - this.serverVersion = Objects.requireNonNull(serverVersion); + public void setServerVersion(@CheckForNull BitbucketServerVersion serverVersion) { + this.serverVersion = serverVersion; + if (serverVersion == null || BitbucketServerVersion.getMinSupportedVersion().compareTo(this.serverVersion) < 0) { + // force value to the minimum supported version + this.serverVersion = BitbucketServerVersion.getMinSupportedVersion(); + } } /** @@ -241,13 +244,13 @@ public String getRepositoryURL(@NonNull String repoOwner, @NonNull String reposi @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "Only non-null after we set them here!") private Object readResolve() { if (webhookImplementation == null) { - webhookImplementation = BitbucketServerWebhookImplementation.PLUGIN; + webhookImplementation = BitbucketServerWebhookImplementation.NATIVE; } if (getBitbucketJenkinsRootUrl() != null) { setBitbucketJenkinsRootUrl(getBitbucketJenkinsRootUrl()); } if (serverVersion == null) { - serverVersion = BitbucketServerVersion.VERSION_7; + serverVersion = BitbucketServerVersion.getMinSupportedVersion(); } return this; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessor.java new file mode 100644 index 000000000..a463785ef --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessor.java @@ -0,0 +1,184 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; + +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessor; +import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessorException; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.BitbucketType; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.security.ACL; +import hudson.security.ACLContext; +import hudson.util.Secret; +import jakarta.servlet.http.HttpServletResponse; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import jenkins.scm.api.SCMSource; +import jenkins.scm.api.SCMSourceOwner; +import jenkins.scm.api.SCMSourceOwners; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.HmacAlgorithms; +import org.apache.commons.codec.digest.HmacUtils; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.jenkinsci.plugins.plaincredentials.StringCredentials; + +import static org.apache.commons.lang.StringUtils.trimToNull; + +/** + * Abstract hook processor. + * + * Add new hook processors by extending this class and implement {@link #process(HookEventType, String, BitbucketType, String)}, + * extract details from the hook payload and then fire an {@link jenkins.scm.api.SCMEvent} to dispatch it to the SCM API. + */ +abstract class AbstractHookProcessor implements BitbucketHookProcessor { + + protected static final String REQUEST_ID_CLOUD_HEADER = "X-Request-UUID"; + protected static final String REQUEST_ID_SERVER_HEADER = "X-Request-Id"; + protected static final String SIGNATURE_HEADER = "X-Hub-Signature"; + protected static final String EVENT_TYPE_HEADER = "X-Event-Key"; + protected static final String SERVER_URL_PARAMETER = "server_url"; + + private static final Logger LOGGER = Logger.getLogger(AbstractHookProcessor.class.getName()); + + /** + * To be called by implementations once the owner and the repository have been extracted from the payload. + * + * @param owner the repository owner as configured in the SCMSource + * @param repository the repository name as configured in the SCMSource + * @param mirrorId the mirror id if applicable, may be null + */ + protected void scmSourceReIndex(final String owner, final String repository, final String mirrorId) { + try (ACLContext context = ACL.as2(ACL.SYSTEM2)) { + boolean reindexed = false; + for (SCMSourceOwner scmOwner : SCMSourceOwners.all()) { + List sources = scmOwner.getSCMSources(); + for (SCMSource source : sources) { + // Search for the correct SCM source + if (source instanceof BitbucketSCMSource scmSource + && StringUtils.equalsIgnoreCase(scmSource.getRepoOwner(), owner) + && scmSource.getRepository().equals(repository) + && (mirrorId == null || StringUtils.equalsIgnoreCase(mirrorId, scmSource.getMirrorId()))) { + LOGGER.log(Level.INFO, "Multibranch project found, reindexing " + scmOwner.getName()); + // TODO: SCMSourceOwner.onSCMSourceUpdated is deprecated. We may explore options with an + // SCMEventListener extension and firing SCMSourceEvents. + scmOwner.onSCMSourceUpdated(source); + reindexed = true; + } + } + } + if (!reindexed) { + LOGGER.log(Level.INFO, "No multibranch project matching for reindex on {0}/{1}", new Object[] {owner, repository}); + } + } + } + + @NonNull + @Override + public String getServerURL(@NonNull Map headers, @NonNull MultiValuedMap parameters) { + String serverURL = parameters.get(SERVER_URL_PARAMETER).stream() + .findFirst() + .orElse(null); + if (StringUtils.isBlank(serverURL)) { + throw new BitbucketHookProcessorException(HttpServletResponse.SC_BAD_REQUEST, SERVER_URL_PARAMETER + " query parameter not found or empty. 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"); + } + return serverURL; + } + + @NonNull + @Override + public String getEventType(@NonNull Map headers, @NonNull MultiValuedMap parameters) { + String eventType = headers.get(EVENT_TYPE_HEADER); + if (StringUtils.isEmpty(eventType)) { + throw new IllegalStateException(EVENT_TYPE_HEADER + " is missing or empty, this processor should not proceed after canHandle method. Please fill an issue at https://issues.jenkins.io reporting this stacktrace."); + } + return eventType; + } + + @Override + public void verifyPayload(@NonNull Map headers, @NonNull String body, BitbucketEndpoint endpoint) { + if (headers.containsKey(SIGNATURE_HEADER)) { + StringCredentials signatureCredentials = endpoint.hookSignatureCredentials(); + if (signatureCredentials != null) { + String signatureHeader = headers.get(SIGNATURE_HEADER); + String bitbucketAlgorithm = trimToNull(StringUtils.substringBefore(signatureHeader, "=")); + String bitbucketSignature = trimToNull(StringUtils.substringAfter(signatureHeader, "=")); + HmacAlgorithms algorithm = getAlgorithm(bitbucketAlgorithm); + if (algorithm == null) { + throw new BitbucketHookProcessorException(HttpServletResponse.SC_FORBIDDEN, "Signature " + bitbucketAlgorithm + " not supported"); + } + HmacUtils util; + try { + String key = Secret.toString(signatureCredentials.getSecret()); + util = new HmacUtils(algorithm, key.getBytes(StandardCharsets.UTF_8)); + byte[] digest = util.hmac(body); + if (!MessageDigest.isEqual(Hex.decodeHex(bitbucketSignature), digest)) { + throw new BitbucketHookProcessorException(HttpServletResponse.SC_FORBIDDEN, "Signature verification failed"); + } + } catch (IllegalArgumentException e) { + throw new BitbucketHookProcessorException(HttpServletResponse.SC_BAD_REQUEST, "Signature method not supported: " + algorithm); + } catch (DecoderException e) { + throw new BitbucketHookProcessorException(HttpServletResponse.SC_BAD_REQUEST, "Hex signature can not be decoded: " + bitbucketSignature); + } + } else { + String hookId = headers.get("X-Hook-UUID"); + String requestId = ObjectUtils.firstNonNull(headers.get("X-Request-UUID"), headers.get("X-Request-Id")); + String hookSignatureCredentialsId = endpoint.getHookSignatureCredentialsId(); + LOGGER.log(Level.WARNING, "No credentials {0} found to verify the signature of incoming webhook {1} request {2}", new Object[] { hookSignatureCredentialsId, hookId, requestId }); + throw new BitbucketHookProcessorException(HttpServletResponse.SC_FORBIDDEN, "No credentials " + hookSignatureCredentialsId + " found in Jenkins to verify the signature"); + } + } else { + throw new BitbucketHookProcessorException(HttpServletResponse.SC_FORBIDDEN, "Payload has not be signed, configure the webHook secret in Bitbucket as documented at https://github.com/jenkinsci/bitbucket-branch-source-plugin/blob/master/docs/USER_GUIDE.adoc#webhooks-registering"); + } + } + + protected String getOrigin(Map context) { + return StringUtils.firstNonBlank((String) context.get("origin"), "unknow"); + } + + @CheckForNull + private HmacAlgorithms getAlgorithm(String algorithm) { + switch (StringUtils.lowerCase(algorithm)) { + case "sha1": + return HmacAlgorithms.HMAC_SHA_1; + case "sha256": + return HmacAlgorithms.HMAC_SHA_256; + case "sha384": + return HmacAlgorithms.HMAC_SHA_384; + case "sha512": + return HmacAlgorithms.HMAC_SHA_512; + default: + return null; + } + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/AbstractNativeServerSCMHeadEvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractNativeServerSCMHeadEvent.java similarity index 98% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/AbstractNativeServerSCMHeadEvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractNativeServerSCMHeadEvent.java index b3340566d..bdb77be89 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/AbstractNativeServerSCMHeadEvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractNativeServerSCMHeadEvent.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/AbstractSCMHeadEvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractSCMHeadEvent.java similarity index 92% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/AbstractSCMHeadEvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractSCMHeadEvent.java index 47777cd75..177e14868 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/AbstractSCMHeadEvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractSCMHeadEvent.java @@ -21,14 +21,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketHref; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; import com.cloudbees.jenkins.plugins.bitbucket.client.events.BitbucketCloudPullRequestEvent; +import com.cloudbees.jenkins.plugins.bitbucket.client.events.BitbucketCloudPushEvent; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; import com.cloudbees.jenkins.plugins.bitbucket.server.events.BitbucketServerPullRequestEvent; +import com.cloudbees.jenkins.plugins.bitbucket.server.events.BitbucketServerPushEvent; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.scm.SCM; import java.net.URI; @@ -77,12 +79,12 @@ private boolean isProjectKeyMatch(String projectKey) { protected boolean isServerURLMatch(String serverURL) { if (serverURL == null || BitbucketApiUtils.isCloud(serverURL)) { // this is a Bitbucket cloud navigator - if (getPayload() instanceof BitbucketServerPullRequestEvent) { + if (getPayload() instanceof BitbucketServerPullRequestEvent || getPayload() instanceof BitbucketServerPushEvent) { return false; } } else { // this is a Bitbucket server navigator - if (getPayload() instanceof BitbucketCloudPullRequestEvent) { + if (getPayload() instanceof BitbucketCloudPullRequestEvent || getPayload() instanceof BitbucketCloudPushEvent) { return false; } Map> links = getRepository().getLinks(); diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPullRequestHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPullRequestHookProcessor.java new file mode 100644 index 000000000..ff3c96674 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPullRequestHookProcessor.java @@ -0,0 +1,89 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; + +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestEvent; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudWebhookPayload; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import hudson.RestrictedSince; +import java.util.List; +import java.util.Map; +import jenkins.scm.api.SCMEvent; +import org.apache.commons.collections4.MultiValuedMap; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +@Extension +@Restricted(NoExternalUse.class) +@RestrictedSince("933.3.0") +public class CloudPullRequestHookProcessor extends AbstractHookProcessor { + + private static final List supportedEvents = List.of( + HookEventType.PULL_REQUEST_CREATED.getKey(), // needed to create job + HookEventType.PULL_REQUEST_DECLINED.getKey(), // needed to remove job + HookEventType.PULL_REQUEST_MERGED.getKey(), // needed to remove job + HookEventType.PULL_REQUEST_UPDATED.getKey()); // needed to update git content and trigger build job + + @Override + public boolean canHandle(@NonNull Map headers, @NonNull MultiValuedMap parameters) { + return headers.containsKey(EVENT_TYPE_HEADER) + && headers.containsKey(REQUEST_ID_CLOUD_HEADER) + && supportedEvents.contains(headers.get(EVENT_TYPE_HEADER)) + && !parameters.containsKey(SERVER_URL_PARAMETER); + } + + @NonNull + @Override + public String getServerURL(@NonNull Map headers, @NonNull MultiValuedMap parameters) { + return BitbucketCloudEndpoint.SERVER_URL; + } + + @Override + public void process(@NonNull String hookEventType, @NonNull String payload, @NonNull Map context, @NonNull BitbucketEndpoint endpoint) { + HookEventType hookEvent = HookEventType.fromString(hookEventType); + BitbucketPullRequestEvent pull = BitbucketCloudWebhookPayload.pullRequestEventFromPayload(payload); + if (pull != null) { + SCMEvent.Type eventType; + switch (hookEvent) { + case PULL_REQUEST_CREATED: + eventType = SCMEvent.Type.CREATED; + break; + case PULL_REQUEST_DECLINED, + PULL_REQUEST_MERGED: + eventType = SCMEvent.Type.REMOVED; + break; + default: + eventType = SCMEvent.Type.UPDATED; + break; + } + // assume updated as a catch-all type + notifyEvent(new PREvent(eventType, pull, getOrigin(context), hookEvent), BitbucketSCMSource.getEventDelaySeconds()); + } + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPushHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPushHookProcessor.java new file mode 100644 index 000000000..9c7437cc0 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPushHookProcessor.java @@ -0,0 +1,88 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; + +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPushEvent; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudWebhookPayload; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import hudson.RestrictedSince; +import java.util.List; +import java.util.Map; +import jenkins.scm.api.SCMEvent; +import org.apache.commons.collections4.MultiValuedMap; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +@Extension +@Restricted(NoExternalUse.class) +@RestrictedSince("933.3.0") +public class CloudPushHookProcessor extends AbstractHookProcessor { + + private static final List supportedEvents = List.of( + HookEventType.PUSH.getKey()); + + @Override + public boolean canHandle(Map headers, MultiValuedMap parameters) { + return headers.containsKey(EVENT_TYPE_HEADER) + && headers.containsKey(REQUEST_ID_CLOUD_HEADER) + && supportedEvents.contains(headers.get(EVENT_TYPE_HEADER)) + && !parameters.containsKey(SERVER_URL_PARAMETER); + } + + @NonNull + @Override + public String getServerURL(@NonNull Map headers, @NonNull MultiValuedMap parameters) { + return BitbucketCloudEndpoint.SERVER_URL; + } + + @Override + public void process(@NonNull String hookEventType, @NonNull String payload, @NonNull Map context, @NonNull BitbucketEndpoint endpoint) { + BitbucketPushEvent push = BitbucketCloudWebhookPayload.pushEventFromPayload(payload); + if (push != null) { + if (push.getChanges().isEmpty()) { + final String owner = push.getRepository().getOwnerName(); + final String repository = push.getRepository().getRepositoryName(); + scmSourceReIndex(owner, repository, null); + } else { + SCMEvent.Type type = null; + for (BitbucketPushEvent.Change change : push.getChanges()) { + if ((type == null || type == SCMEvent.Type.CREATED) && change.isCreated()) { + type = SCMEvent.Type.CREATED; + } else if ((type == null || type == SCMEvent.Type.REMOVED) && change.isClosed()) { + type = SCMEvent.Type.REMOVED; + } else { + type = SCMEvent.Type.UPDATED; + } + } + notifyEvent(new PushEvent(type, push, getOrigin(context)), BitbucketSCMSource.getEventDelaySeconds()); + } + } + } + +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessor.java new file mode 100644 index 000000000..9bbabc6dd --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessor.java @@ -0,0 +1,64 @@ +/* + * The MIT License + * + * Copyright (c) 2016-2018, Yieldlab AG + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; + +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import hudson.RestrictedSince; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.commons.collections4.MultiValuedMap; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +@Extension +@Restricted(NoExternalUse.class) +@RestrictedSince("933.3.0") +public class NativeServerPingHookProcessor extends AbstractHookProcessor { + + private static final Logger logger = Logger.getLogger(NativeServerPingHookProcessor.class.getName()); + private static final List supportedEvents = List.of(HookEventType.SERVER_PING.getKey()); + + @Override + public boolean canHandle(Map headers, MultiValuedMap parameters) { + return headers.containsKey(EVENT_TYPE_HEADER) + && supportedEvents.contains(headers.get(EVENT_TYPE_HEADER)) + && parameters.containsKey(SERVER_URL_PARAMETER); + } + + @Override + public void verifyPayload(Map headers, String body, BitbucketEndpoint endpoint) { + // ping hook is not signed + } + + @Override + public void process(@NonNull String eventType, @NonNull String payload, @NonNull Map context, @NonNull BitbucketEndpoint endpoint) { + logger.log(Level.INFO, "Received webhook ping event from {0}", getOrigin(context)); + } + +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPullRequestHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPullRequestHookProcessor.java similarity index 62% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPullRequestHookProcessor.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPullRequestHookProcessor.java index 4f59394c3..b9bd22f99 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPullRequestHookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPullRequestHookProcessor.java @@ -21,33 +21,50 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.JsonParser; import com.cloudbees.jenkins.plugins.bitbucket.server.events.NativeServerPullRequestEvent; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; import hudson.RestrictedSince; import java.io.IOException; +import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.scm.api.SCMEvent; +import org.apache.commons.collections4.MultiValuedMap; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; +@Extension @Restricted(NoExternalUse.class) @RestrictedSince("933.3.0") -public class NativeServerPullRequestHookProcessor extends HookProcessor { +public class NativeServerPullRequestHookProcessor extends AbstractHookProcessor { private static final Logger LOGGER = Logger.getLogger(NativeServerPullRequestHookProcessor.class.getName()); + private static final List supportedEvents = List.of( + HookEventType.SERVER_PULL_REQUEST_OPENED.getKey(), + HookEventType.SERVER_PULL_REQUEST_MERGED.getKey(), + HookEventType.SERVER_PULL_REQUEST_DECLINED.getKey(), + HookEventType.SERVER_PULL_REQUEST_DELETED.getKey(), + HookEventType.SERVER_PULL_REQUEST_MODIFIED.getKey(), + HookEventType.SERVER_PULL_REQUEST_FROM_REF_UPDATED.getKey()); @Override - public void process(HookEventType hookEvent, String payload, BitbucketType instanceType, String origin) { - // without a server URL, the event wouldn't match anything + public boolean canHandle(@NonNull Map headers, @NonNull MultiValuedMap parameters) { + return headers.containsKey(EVENT_TYPE_HEADER) + && headers.containsKey(REQUEST_ID_SERVER_HEADER) + && supportedEvents.contains(headers.get(EVENT_TYPE_HEADER)) + && parameters.containsKey(SERVER_URL_PARAMETER); } @Override - public void process(HookEventType hookEvent, String payload, BitbucketType instanceType, String origin, String serverUrl) { - + public void process(@NonNull String hookEventType, @NonNull String payload, @NonNull Map context, @NonNull BitbucketEndpoint endpoint) { final NativeServerPullRequestEvent pullRequestEvent; try { pullRequestEvent = JsonParser.toJava(payload, NativeServerPullRequestEvent.class); @@ -56,6 +73,7 @@ public void process(HookEventType hookEvent, String payload, BitbucketType insta return; } + HookEventType hookEvent = HookEventType.fromString(hookEventType); final SCMEvent.Type eventType; switch (hookEvent) { case SERVER_PULL_REQUEST_OPENED: @@ -67,7 +85,6 @@ public void process(HookEventType hookEvent, String payload, BitbucketType insta eventType = SCMEvent.Type.REMOVED; break; case SERVER_PULL_REQUEST_MODIFIED, - SERVER_PULL_REQUEST_REVIEWER_UPDATED, SERVER_PULL_REQUEST_FROM_REF_UPDATED: eventType = SCMEvent.Type.UPDATED; break; @@ -76,6 +93,6 @@ public void process(HookEventType hookEvent, String payload, BitbucketType insta return; } - notifyEvent(new ServerHeadEvent(serverUrl, eventType, pullRequestEvent, origin), BitbucketSCMSource.getEventDelaySeconds()); + notifyEvent(new ServerHeadEvent(endpoint.getServerURL(), eventType, pullRequestEvent, getOrigin(context)), BitbucketSCMSource.getEventDelaySeconds()); } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPushHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPushHookProcessor.java similarity index 76% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPushHookProcessor.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPushHookProcessor.java index d3bd48158..fe66cfac2 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPushHookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPushHookProcessor.java @@ -21,9 +21,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.JsonParser; import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerCommit; import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerRepository; @@ -32,37 +34,45 @@ import com.cloudbees.jenkins.plugins.bitbucket.server.events.NativeServerRefsChangedEvent; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; import hudson.RestrictedSince; import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.scm.api.SCMEvent; +import org.apache.commons.collections4.MultiValuedMap; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; +@Extension @Restricted(NoExternalUse.class) @RestrictedSince("933.3.0") -public class NativeServerPushHookProcessor extends HookProcessor { +public class NativeServerPushHookProcessor extends AbstractHookProcessor { - private static final Logger LOGGER = Logger.getLogger(NativeServerPushHookProcessor.class.getName()); + private static final Logger logger = Logger.getLogger(NativeServerPushHookProcessor.class.getName()); + private static final List supportedEvents = List.of( + HookEventType.SERVER_REFS_CHANGED.getKey(), + HookEventType.SERVER_MIRROR_REPO_SYNCHRONIZED.getKey()); @Override - public void process(HookEventType hookEvent, String payload, BitbucketType instanceType, String origin) { - return; // without a server URL, the event wouldn't match anything + public boolean canHandle(Map headers, MultiValuedMap parameters) { + return headers.containsKey(EVENT_TYPE_HEADER) + && headers.containsKey(REQUEST_ID_SERVER_HEADER) + && supportedEvents.contains(headers.get(EVENT_TYPE_HEADER)) + && parameters.containsKey(SERVER_URL_PARAMETER); } @Override - public void process(HookEventType hookEvent, String payload, BitbucketType instanceType, String origin, String serverUrl) { - if (payload == null) { - return; - } - + public void process(@NonNull String hookEventType, @NonNull String payload, @NonNull Map context, @NonNull BitbucketEndpoint endpoint) { final BitbucketServerRepository repository; final BitbucketServerCommit refCommit; final List changes; final String mirrorId; try { + HookEventType hookEvent = HookEventType.fromString(hookEventType); if (hookEvent == HookEventType.SERVER_REFS_CHANGED) { final NativeServerRefsChangedEvent event = JsonParser.toJava(payload, NativeServerRefsChangedEvent.class); repository = event.getRepository(); @@ -80,31 +90,31 @@ public void process(HookEventType hookEvent, String payload, BitbucketType insta if (event.getRefLimitExceeded()) { final String owner = repository.getOwnerName(); final String repositoryName = repository.getRepositoryName(); - LOGGER.log(Level.INFO, "Received mirror synchronized event with refLimitExceeded from Bitbucket. Processing with indexing on {0}/{1}. " + + logger.log(Level.INFO, "Received mirror synchronized event with refLimitExceeded from Bitbucket. Processing with indexing on {0}/{1}. " + "You may skip this scan by adding the system property -D{2}=false on startup.", new Object[]{owner, repositoryName, SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME}); scmSourceReIndex(owner, repositoryName, mirrorId); return; } } else { - throw new UnsupportedOperationException("Hook event of type " + hookEvent + " is not supported.\n" + throw new UnsupportedOperationException("Hook event of type " + hookEventType + " is not supported.\n" + "Please fill an issue at https://issues.jenkins.io to the bitbucket-branch-source-plugin component."); } } catch (final IOException e) { - LOGGER.log(Level.SEVERE, "Can not read hook payload", e); + logger.log(Level.SEVERE, "Can not read hook payload", e); return; } if (changes.isEmpty()) { final String owner = repository.getOwnerName(); final String repositoryName = repository.getRepositoryName(); - if (SCAN_ON_EMPTY_CHANGES) { - LOGGER.log(Level.INFO, "Received push hook with empty changes from Bitbucket. Processing indexing on {0}/{1}. " + + if (reindexOnEmptyChanges()) { + logger.log(Level.INFO, "Received push hook with empty changes from Bitbucket. Processing indexing on {0}/{1}. " + "You may skip this scan by adding the system property -D{2}=false on startup.", new Object[]{owner, repositoryName, SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME}); scmSourceReIndex(owner, repositoryName, mirrorId); } else { - LOGGER.log(Level.INFO, "Received push hook with empty changes from Bitbucket for {0}/{1}. Skipping.", + logger.log(Level.INFO, "Received push hook with empty changes from Bitbucket for {0}/{1}. Skipping.", new Object[]{owner, repositoryName}); } } else { @@ -118,15 +128,15 @@ public void process(HookEventType hookEvent, String payload, BitbucketType insta } else if ("ADD".equals(type)) { events.put(SCMEvent.Type.CREATED, change); } else { - LOGGER.log(Level.INFO, "Unknown change event type of {0} received from Bitbucket Server", type); + logger.log(Level.INFO, "Unknown change event type of {0} received from Bitbucket Server", type); } } for (final SCMEvent.Type type : events.keySet()) { - ServerPushEvent headEvent = new ServerPushEvent(serverUrl, type, events.get(type), origin, repository, refCommit, mirrorId); + ServerPushEvent headEvent = new ServerPushEvent(endpoint.getServerURL(), type, events.get(type), getOrigin(context), repository, refCommit, mirrorId); notifyEvent(headEvent, BitbucketSCMSource.getEventDelaySeconds()); } } - } + } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PREvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PREvent.java similarity index 97% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PREvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PREvent.java index 4a1906e58..05d23ff54 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PREvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PREvent.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSourceContext; @@ -30,6 +30,8 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestEvent; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HasPullRequests; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.Collections; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessor.java new file mode 100644 index 000000000..8d19de9c6 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessor.java @@ -0,0 +1,83 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; + +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestEvent; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerWebhookPayload; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import hudson.RestrictedSince; +import java.util.List; +import java.util.Map; +import jenkins.scm.api.SCMEvent; +import org.apache.commons.collections4.MultiValuedMap; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +@Extension +@Deprecated(since = "937.0.0") +@Restricted(NoExternalUse.class) +@RestrictedSince("933.3.0") +public class PluginPullRequestHookProcessor extends AbstractHookProcessor { + + private static final List supportedEvents = List.of( + HookEventType.PULL_REQUEST_CREATED.getKey(), // needed to create job + HookEventType.PULL_REQUEST_DECLINED.getKey(), // needed to remove job + HookEventType.PULL_REQUEST_MERGED.getKey(), // needed to remove job + HookEventType.PULL_REQUEST_UPDATED.getKey()); // needed to update git content and trigger build job + + @Override + public boolean canHandle(@NonNull Map headers, @NonNull MultiValuedMap parameters) { + return headers.containsKey(EVENT_TYPE_HEADER) + && headers.containsKey("X-Bitbucket-Type") + && supportedEvents.contains(headers.get(EVENT_TYPE_HEADER)) + && parameters.containsKey(SERVER_URL_PARAMETER); + } + + @Override + public void process(@NonNull String hookEventType, @NonNull String payload, @NonNull Map context, @NonNull BitbucketEndpoint endpoint) { + HookEventType hookEvent = HookEventType.fromString(hookEventType); + BitbucketPullRequestEvent pull = BitbucketServerWebhookPayload.pullRequestEventFromPayload(payload); + if (pull != null) { + SCMEvent.Type eventType; + switch (hookEvent) { + case PULL_REQUEST_CREATED: + eventType = SCMEvent.Type.CREATED; + break; + case PULL_REQUEST_DECLINED, + PULL_REQUEST_MERGED: + eventType = SCMEvent.Type.REMOVED; + break; + default: + eventType = SCMEvent.Type.UPDATED; + break; + } + // assume updated as a catch-all type + notifyEvent(new PREvent(eventType, pull, getOrigin(context), hookEvent), BitbucketSCMSource.getEventDelaySeconds()); + } + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessor.java new file mode 100644 index 000000000..57d5c665e --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessor.java @@ -0,0 +1,93 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; + +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPushEvent; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerWebhookPayload; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import hudson.RestrictedSince; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import jenkins.scm.api.SCMEvent; +import org.apache.commons.collections4.MultiValuedMap; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +@Extension +@Deprecated(since = "937.0.0") +@Restricted(NoExternalUse.class) +@RestrictedSince("933.3.0") +public class PluginPushHookProcessor extends AbstractHookProcessor { + + private static final Logger logger = Logger.getLogger(PluginPushHookProcessor.class.getName()); + private static final List supportedEvents = List.of( + HookEventType.PUSH.getKey(), + HookEventType.PULL_REQUEST_UPDATED.getKey()); + + @Override + public boolean canHandle(Map headers, MultiValuedMap parameters) { + return headers.containsKey(EVENT_TYPE_HEADER) + && headers.containsKey("X-Bitbucket-Type") + && supportedEvents.contains(headers.get(EVENT_TYPE_HEADER)) + && parameters.containsKey(SERVER_URL_PARAMETER); + } + + @Override + public void process(@NonNull String eventType, @NonNull String payload, @NonNull Map context, @NonNull BitbucketEndpoint endpoint) { + BitbucketPushEvent push = BitbucketServerWebhookPayload.pushEventFromPayload(payload); + if (push != null) { + if (push.getChanges().isEmpty()) { + final String owner = push.getRepository().getOwnerName(); + final String repository = push.getRepository().getRepositoryName(); + if (!reindexOnEmptyChanges()) { + logger.log(Level.INFO, "Received push hook with empty changes from Bitbucket. Processing indexing on {0}/{1}. " + + "You may skip this scan by adding the system property -D{2}=false on startup.", new Object[]{owner, repository, SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME}); + scmSourceReIndex(owner, repository, null); + } else { + logger.log(Level.INFO, "Received push hook with empty changes from Bitbucket for {0}/{1}. Skipping.", + new Object[]{owner, repository}); + } + } else { + SCMEvent.Type type = null; + for (BitbucketPushEvent.Change change : push.getChanges()) { + if ((type == null || type == SCMEvent.Type.CREATED) && change.isCreated()) { + type = SCMEvent.Type.CREATED; + } else if ((type == null || type == SCMEvent.Type.REMOVED) && change.isClosed()) { + type = SCMEvent.Type.REMOVED; + } else { + type = SCMEvent.Type.UPDATED; + } + } + notifyEvent(new PushEvent(type, push, getOrigin(context)), BitbucketSCMSource.getEventDelaySeconds()); + } + } + } + +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PushEvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PushEvent.java similarity index 98% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PushEvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PushEvent.java index cacdd6389..00f2d96b4 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PushEvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PushEvent.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketTagSCMHead; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/ServerHeadEvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/ServerHeadEvent.java similarity index 97% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/ServerHeadEvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/ServerHeadEvent.java index c3535b906..a7aa91091 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/ServerHeadEvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/ServerHeadEvent.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSourceContext; @@ -29,6 +29,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.PullRequestSCMRevision; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HasPullRequests; import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerRepository; import com.cloudbees.jenkins.plugins.bitbucket.server.events.NativeServerPullRequestEvent; import com.google.common.base.Ascii; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/ServerPushEvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/ServerPushEvent.java similarity index 99% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/ServerPushEvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/ServerPushEvent.java index c0e897f7f..758c63ff0 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/ServerPushEvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/ServerPushEvent.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSourceContext; @@ -34,6 +34,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HasPullRequests; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils; import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerAPIClient; import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerCommit; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/BitbucketServerVersion.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/BitbucketServerVersion.java index 98190276c..a304c2002 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/BitbucketServerVersion.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/BitbucketServerVersion.java @@ -26,11 +26,12 @@ import hudson.model.ModelObject; public enum BitbucketServerVersion implements ModelObject { - VERSION_7("Bitbucket Data Center v8.x (and later)"), - VERSION_6_5("Bitbucket Server v6.5 to v6.10 - EOL reached, any support DISMISSED"), - VERSION_6("Bitbucket Server v6.0 to v6.4 - EOL reached, any support DISMISSED"), - VERSION_5_10("Bitbucket Server v5.10 to v5.16 - EOL reached, any support DISMISSED"), - VERSION_5("Bitbucket Server v5.9 (and earlier) - EOL reached, any support DISMISSED"); + VERSION_8("Bitbucket Data Center v8.x (and later)"), + VERSION_7("Bitbucket Server v7 - EOS reached, any support DISMISSED"), + VERSION_6_5("Bitbucket Server v6.5 to v6.10 - EOS reached, any support DISMISSED"), + VERSION_6("Bitbucket Server v6.0 to v6.4 - EOS reached, any support DISMISSED"), + VERSION_5_10("Bitbucket Server v5.10 to v5.16 - EOS reached, any support DISMISSED"), + VERSION_5("Bitbucket Server v5.9 (and earlier) - EOS reached, any support DISMISSED"); private final String displayName; @@ -43,4 +44,12 @@ public String getDisplayName() { return displayName; } + /** + * The minimal supported version. + *

+ * If configured less than this it will be changed to the minimal. + */ + public static BitbucketServerVersion getMinSupportedVersion() { + return BitbucketServerVersion.VERSION_8; + } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java index ff0e93971..0690379e3 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java @@ -46,7 +46,6 @@ import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.JsonParser; -import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerVersion; import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation; import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBranch; import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBranches; @@ -272,12 +271,10 @@ private List getPullRequests(UriTemplate template) t setupPullRequest(pullRequest, endpoint); } - if (endpoint != null) { - // Get PRs again as revisions could be changed by other events during setupPullRequest - if (endpoint.isCallChanges() && BitbucketServerVersion.VERSION_7.equals(endpoint.getServerVersion())) { - pullRequests = getPagedRequest(template, BitbucketServerPullRequest.class); - pullRequests.removeIf(this::shouldIgnore); - } + // Get PRs again as revisions could be changed by other events during setupPullRequest + if (endpoint != null && endpoint.isCallChanges()) { + pullRequests = getPagedRequest(template, BitbucketServerPullRequest.class); + pullRequests.removeIf(this::shouldIgnore); } return pullRequests; @@ -303,7 +300,7 @@ private void setupPullRequest(@NonNull BitbucketServerPullRequest pullRequest, @ } } } - if (endpoint.isCallChanges() && BitbucketServerVersion.VERSION_7.equals(endpoint.getServerVersion())) { + if (endpoint.isCallChanges()) { callPullRequestChangesById(pullRequest.getId()); } } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfigurationTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfigurationTest.java index c3a500ae4..c72213785 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfigurationTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfigurationTest.java @@ -637,7 +637,7 @@ void given__instanceWithConfig__when__configRoundtrip__then__configRetained() th } @Test - void given__serverConfig__without__webhookImplementation__then__usePlugin() throws Exception { + void given__serverConfig__without__webhookImplementation__then__useNative() throws Exception { final URL configWithoutWebhookImpl = Resources.getResource(getClass(), "config-without-webhook-impl.xml"); final File configFile = new File(Jenkins.get().getRootDir(), BitbucketEndpointConfiguration.class.getName() + ".xml"); FileUtils.copyURLToFile(configWithoutWebhookImpl, configFile); @@ -646,7 +646,7 @@ void given__serverConfig__without__webhookImplementation__then__usePlugin() thro assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketServerEndpoint.class); final BitbucketServerEndpoint endpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(0); - assertThat(endpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.PLUGIN); + assertThat(endpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); } @Test @@ -713,8 +713,8 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isEqualTo("second"); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.PLUGIN); - assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.VERSION_7); + assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); + assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); assertThat(instance.getEndpoints()).element(2).isInstanceOf(BitbucketServerEndpoint.class); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(2); @@ -724,8 +724,8 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isTrue(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.PLUGIN); - assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.VERSION_7); + assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); + assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(3); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); @@ -735,7 +735,7 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.isCallCanMerge()).isTrue(); assertThat(serverEndpoint.isCallChanges()).isTrue(); assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); - assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.VERSION_7); + assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(4); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); @@ -745,7 +745,7 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isFalse(); assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.PLUGIN); - assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.VERSION_7); + assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(5); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); @@ -755,7 +755,7 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isTrue(); assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.PLUGIN); - assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.VERSION_7); + assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(6); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); @@ -764,8 +764,8 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isFalse(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.PLUGIN); - assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.VERSION_7); + assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); + assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(7); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); @@ -774,8 +774,8 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isFalse(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.PLUGIN); - assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.VERSION_7); + assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); + assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(8); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); @@ -784,8 +784,8 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.PLUGIN); - assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.VERSION_6_5); + assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); + assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(9); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); @@ -794,8 +794,8 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.PLUGIN); - assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.VERSION_6); + assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); + assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(10); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); @@ -804,8 +804,8 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.PLUGIN); - assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.VERSION_5_10); + assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); + assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(11); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); @@ -814,8 +814,8 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.PLUGIN); - assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.VERSION_5); + assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); + assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); } private BitbucketCloudEndpoint buildEndpoint(boolean manageHook, String credentials) { diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiverTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiverTest.java index ceb0c0c10..ccff9e588 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiverTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiverTest.java @@ -23,28 +23,34 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.hooks; +import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; -import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketServerEndpoint; -import com.cloudbees.jenkins.plugins.bitbucket.test.util.BitbucketTestUtil; -import jakarta.servlet.ReadListener; -import jakarta.servlet.ServletInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.UUID; +import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; +import com.cloudbees.jenkins.plugins.bitbucket.test.util.MockRequest; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; +import org.apache.commons.collections4.MultiValuedMap; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.junit.jupiter.WithJenkins; +import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.StaplerRequest2; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -58,357 +64,111 @@ static void init(JenkinsRule rule) { j = rule; } - private BitbucketSCMSourcePushHookReceiver sut; private StaplerRequest2 req; - private HookProcessor hookProcessor; - private String credentialsId; + private Map headers; + private BitbucketSCMSourcePushHookReceiver sut; @BeforeEach void setup() throws Exception { req = mock(StaplerRequest2.class); - - hookProcessor = mock(HookProcessor.class); - sut = new BitbucketSCMSourcePushHookReceiver() { + headers = new HashMap<>(); + when(req.getHeader(anyString())).thenAnswer(new Answer() { @Override - HookProcessor getHookProcessor(HookEventType type) { - return hookProcessor; + public String answer(InvocationOnMock invocation) throws Throwable { + return headers.get(invocation.getArgument(0)); } - }; - - credentialsId = BitbucketTestUtil.registerHookCredentials("Gkvl$k$wyNpQAF42", j).getId(); + }); + when(req.getHeaderNames()).thenAnswer(new Answer>() { + @Override + public Enumeration answer(InvocationOnMock invocation) throws Throwable { + return Collections.enumeration(headers.keySet()); + } + }); + when(req.getInputStream()).thenReturn(new MockRequest("{}")); } - private void mockCloudRequest() { + private void mockRequest() { when(req.getRemoteHost()).thenReturn("https://bitbucket.org"); when(req.getRemoteAddr()).thenReturn("185.166.143.48"); when(req.getScheme()).thenReturn("https"); when(req.getServerName()).thenReturn("jenkins.example.com"); when(req.getLocalPort()).thenReturn(80); when(req.getRequestURI()).thenReturn("/bitbucket-scmsource-hook/notify"); - when(req.getHeader("User-Agent")).thenReturn("Bitbucket-Webhooks/2.0"); - when(req.getHeader("X-Attempt-Number")).thenReturn("1"); - when(req.getHeader("Content-Type")).thenReturn("application/json"); - when(req.getHeader("X-Hook-UUID")).thenReturn(UUID.randomUUID().toString()); - when(req.getHeader("X-Request-UUID")).thenReturn(UUID.randomUUID().toString()); - when(req.getHeader("traceparent")).thenReturn(UUID.randomUUID().toString()); - when(req.getHeader("User-Agent")).thenReturn("Bitbucket-Webhooks/2.0"); - } - - private void mockServerRequest(String serverURL) { - when(req.getRemoteHost()).thenReturn("http://localhost:7990"); - when(req.getParameter("server_url")).thenReturn(serverURL); - when(req.getRemoteAddr()).thenReturn("127.0.0.1"); - when(req.getScheme()).thenReturn("https"); - when(req.getServerName()).thenReturn("jenkins.example.com"); - when(req.getLocalPort()).thenReturn(80); - when(req.getRequestURI()).thenReturn("/bitbucket-scmsource-hook/notify"); - when(req.getHeader("Content-Type")).thenReturn("application/json; charset=utf-8"); - when(req.getHeader("X-Request-Id")).thenReturn(UUID.randomUUID().toString()); - when(req.getHeader("User-Agent")).thenReturn("Atlassian HttpClient 4.2.0 / Bitbucket-9.5.2 (9005002) / Default"); - } - - private void mockPluginRequest(String serverURL) { - when(req.getRemoteHost()).thenReturn("http://localhost:7990"); - when(req.getParameter("server_url")).thenReturn(serverURL); - when(req.getRemoteAddr()).thenReturn("127.0.0.1"); - when(req.getScheme()).thenReturn("https"); - when(req.getServerName()).thenReturn("jenkins.example.com"); - when(req.getLocalPort()).thenReturn(80); - when(req.getRequestURI()).thenReturn("/bitbucket-scmsource-hook/notify"); - when(req.getHeader("Content-Type")).thenReturn("application/json; charset=utf-8"); - when(req.getHeader("X-Bitbucket-Type")).thenReturn("server"); - when(req.getHeader("User-Agent")).thenReturn("Bitbucket version: 8.18.0, Post webhook plugin version: 7.13.41-SNAPSHOT"); - } - - @Test - void test_cloud_signature_is_missing() throws Exception { - BitbucketCloudEndpoint endpoint = new BitbucketCloudEndpoint(false, 0, 0, false, null , true, credentialsId); - endpoint.setBitbucketJenkinsRootUrl("http://jenkins.acme.com:8080/jenkins"); - BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); - - try { - when(req.getHeader("X-Event-Key")).thenReturn("repo:push"); - when(req.getInputStream()).thenReturn(loadResource("cloud/signed_payload.json")); - - /*HttpResponse response = */sut.doNotify(req); - // really hard to verify if response contains a status 400 - verify(hookProcessor, never()).process(any(), anyString(), any(), anyString(), anyString()); - } finally { - BitbucketEndpointConfiguration.get().removeEndpoint(endpoint.getServerUrl()); - } } @Test - void test_cloud_signature() throws Exception { - mockCloudRequest(); - BitbucketCloudEndpoint endpoint = new BitbucketCloudEndpoint(false, 0, 0, false, null , true, credentialsId); - endpoint.setBitbucketJenkinsRootUrl("http://jenkins.acme.com:8080/jenkins"); - BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); - - try { - when(req.getHeader("X-Event-Key")).thenReturn("repo:push"); - when(req.getHeader("X-Hub-Signature")).thenReturn("sha256=f205c729821c6954aff2afe72b965c34015b4baf96ea8ddc2cc44999c014a035"); - when(req.getInputStream()).thenReturn(loadResource("cloud/signed_payload.json")); - - sut.doNotify(req); - verify(hookProcessor).process( - eq(HookEventType.PUSH), - anyString(), - eq(BitbucketType.CLOUD), - eq("https://bitbucket.org/185.166.143.48 ⇒ https://jenkins.example.com:80/bitbucket-scmsource-hook/notify"), - eq("https://bitbucket.org")); - } finally { - BitbucketEndpointConfiguration.get().removeEndpoint(endpoint.getServerUrl()); - } - } - - @Test - void test_cloud_bad_signature() throws Exception { - BitbucketCloudEndpoint endpoint = new BitbucketCloudEndpoint(false, 0, 0, false, null , true, credentialsId); - endpoint.setBitbucketJenkinsRootUrl("http://jenkins.acme.com:8080/jenkins"); - BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); - - try { - when(req.getHeader("X-Event-Key")).thenReturn("repo:push"); - when(req.getHeader("X-Hub-Signature")).thenReturn("sha256=f205c729821c6954aff2afe72b965c34015b4baf96ea8ddc2cc44999c014a036"); - when(req.getInputStream()).thenReturn(loadResource("cloud/signed_payload.json")); - - /*HttpResponse response = */sut.doNotify(req); - // really hard to verify if response contains a status 400 - verify(hookProcessor, never()).process(any(), anyString(), any(), anyString(), anyString()); - } finally { - BitbucketEndpointConfiguration.get().removeEndpoint(endpoint.getServerUrl()); - } - } - - @Test - void test_native_signature() throws Exception { - BitbucketServerEndpoint endpoint = new BitbucketServerEndpoint("datacenter", "http://localhost:7990/bitbucket", false, null, true, credentialsId); - endpoint.setBitbucketJenkinsRootUrl("https://jenkins.example.com"); - BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); - - try { - mockServerRequest(endpoint.getServerUrl()); - when(req.getHeader("X-Event-Key")).thenReturn("repo:refs_changed"); - when(req.getHeader("X-Hub-Signature")).thenReturn("sha256=4ffba9e7b58ea3d7e1a230446e8c92baea0aeec89b73f598932387254f0de13e"); - when(req.getInputStream()).thenReturn(loadResource("native/signed_payload.json")); - - sut.doNotify(req); - // really hard to verify if response contains a status 400 - verify(hookProcessor).process( - eq(HookEventType.SERVER_REFS_CHANGED), - anyString(), - eq(BitbucketType.SERVER), - eq("http://localhost:7990/127.0.0.1 ⇒ https://jenkins.example.com:80/bitbucket-scmsource-hook/notify"), - eq(endpoint.getServerUrl())); - - // verify bad signature - reset(hookProcessor); - when(req.getHeader("X-Hub-Signature")).thenReturn("sha256=4ffba9e7b58ea3d7e1a230446e8c92baea0aeec89b73f598932387254f0de13f"); - when(req.getInputStream()).thenReturn(loadResource("native/signed_payload.json")); - /*HttpResponse response = */ sut.doNotify(req); - // really hard to verify if response contains a status 400 - verify(hookProcessor, never()).process(any(), anyString(), any(), anyString(), anyString()); - } finally { - BitbucketEndpointConfiguration.get().removeEndpoint(endpoint.getServerUrl()); - } - } - - @Test - void test_native_ping() throws Exception { - BitbucketServerEndpoint endpoint = new BitbucketServerEndpoint("datacenter", "http://localhost:7990/bitbucket", false, null, false, null); + void test_roundtrip() throws Exception { + BitbucketCloudEndpoint endpoint = new BitbucketCloudEndpoint(false, 0, 0, false, null, true, "hmac"); endpoint.setBitbucketJenkinsRootUrl("https://jenkins.example.com"); BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); try { - mockServerRequest(endpoint.getServerUrl()); - when(req.getHeader("X-Event-Key")).thenReturn("diagnostics:ping"); - when(req.getInputStream()).thenReturn(loadResource("native/ping_payload.json")); - - sut.doNotify(req); - verify(hookProcessor).process( - eq(HookEventType.SERVER_PING), - anyString(), - eq(BitbucketType.SERVER), - eq("http://localhost:7990/127.0.0.1 ⇒ https://jenkins.example.com:80/bitbucket-scmsource-hook/notify"), - eq(endpoint.getServerUrl())); - } finally { - BitbucketEndpointConfiguration.get().removeEndpoint(endpoint.getServerUrl()); - } - } - - @Test - void test_plugin_pullrequest_created() throws Exception { - BitbucketServerEndpoint endpoint = new BitbucketServerEndpoint("datacenter", "http://localhost:7990/bitbucket", false, null, false, null); - endpoint.setBitbucketJenkinsRootUrl("https://jenkins.example.com"); - BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); - - try { - mockPluginRequest(endpoint.getServerUrl()); - when(req.getHeader("X-Event-Key")).thenReturn("pullrequest:created"); - when(req.getInputStream()).thenReturn(loadResource("plugin/pullrequest_created.json")); + BitbucketHookProcessor hookProcessor = mock(BitbucketHookProcessor.class); + sut = new BitbucketSCMSourcePushHookReceiver() { + @Override + Stream getHookProcessors() { + return Stream.of(hookProcessor); + } + }; + mockRequest(); + headers.putAll(HookProcessorTestUtil.getCloudHeaders()); + when(hookProcessor.canHandle(any(), any())).thenReturn(true); + when(hookProcessor.getServerURL(any(), any())).thenReturn(endpoint.getServerURL()); + when(hookProcessor.getEventType(any(), any())).thenReturn("event:X"); sut.doNotify(req); - verify(hookProcessor).process( - eq(HookEventType.PULL_REQUEST_CREATED), - anyString(), - eq(BitbucketType.SERVER), - eq("http://localhost:7990/127.0.0.1 ⇒ https://jenkins.example.com:80/bitbucket-scmsource-hook/notify"), - eq(endpoint.getServerUrl())); - } finally { - BitbucketEndpointConfiguration.get().removeEndpoint(endpoint.getServerUrl()); - } - } - - @Test - void test_plugin_pullrequest_updated() throws Exception { - BitbucketServerEndpoint endpoint = new BitbucketServerEndpoint("datacenter", "http://localhost:7990/bitbucket", false, null, false, null); - endpoint.setBitbucketJenkinsRootUrl("https://jenkins.example.com"); - BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); + ArgumentCaptor> headersCaptor = ArgumentCaptor.forClass(Map.class); + ArgumentCaptor> parametersCaptor = ArgumentCaptor.forClass(MultiValuedMap.class); - try { - mockPluginRequest(endpoint.getServerUrl()); - when(req.getHeader("X-Event-Key")).thenReturn("pullrequest:updated"); - when(req.getInputStream()).thenReturn(loadResource("plugin/pullrequest_updated.json")); - - sut.doNotify(req); - verify(hookProcessor).process( - eq(HookEventType.PULL_REQUEST_UPDATED), - anyString(), - eq(BitbucketType.SERVER), - eq("http://localhost:7990/127.0.0.1 ⇒ https://jenkins.example.com:80/bitbucket-scmsource-hook/notify"), - eq(endpoint.getServerUrl())); - } finally { - BitbucketEndpointConfiguration.get().removeEndpoint(endpoint.getServerUrl()); - } - } + verify(hookProcessor).canHandle(headersCaptor.capture(), parametersCaptor.capture()); + assertThat(headersCaptor.getValue()).containsAllEntriesOf(headers); + assertThat(parametersCaptor.getValue().entries()).isEmpty(); - @Test - void test_plugin_pullrequest_merged() throws Exception { - BitbucketServerEndpoint endpoint = new BitbucketServerEndpoint("datacenter", "http://localhost:7990/bitbucket", false, null, false, null); - endpoint.setBitbucketJenkinsRootUrl("https://jenkins.example.com"); - BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); + verify(hookProcessor).getServerURL(headersCaptor.capture(), parametersCaptor.capture()); + assertThat(headersCaptor.getValue()).containsAllEntriesOf(headers); + assertThat(parametersCaptor.getValue().entries()).isEmpty(); - try { - mockPluginRequest(endpoint.getServerUrl()); - when(req.getHeader("X-Event-Key")).thenReturn("pullrequest:fulfilled"); - when(req.getInputStream()).thenReturn(loadResource("plugin/pullrequest_merged.json")); + verify(hookProcessor).getEventType(headersCaptor.capture(), parametersCaptor.capture()); + assertThat(headersCaptor.getValue()).containsAllEntriesOf(headers); + assertThat(parametersCaptor.getValue().entries()).isEmpty(); - sut.doNotify(req); + verify(hookProcessor).verifyPayload(headersCaptor.capture(), eq("{}"), eq(endpoint)); verify(hookProcessor).process( - eq(HookEventType.PULL_REQUEST_MERGED), - anyString(), - eq(BitbucketType.SERVER), - eq("http://localhost:7990/127.0.0.1 ⇒ https://jenkins.example.com:80/bitbucket-scmsource-hook/notify"), - eq(endpoint.getServerUrl())); + eq("event:X"), + eq("{}"), + eq(Map.of("origin", "https://bitbucket.org/185.166.143.48 ⇒ https://jenkins.example.com:80/bitbucket-scmsource-hook/notify")), + eq(endpoint)); } finally { - BitbucketEndpointConfiguration.get().removeEndpoint(endpoint.getServerUrl()); + BitbucketEndpointConfiguration.get().removeEndpoint(endpoint.getServerURL()); } } @Test - void test_plugin_create_branch() throws Exception { - BitbucketServerEndpoint endpoint = new BitbucketServerEndpoint("datacenter", "http://localhost:7990/bitbucket", false, null, false, null); + void stop_process_when_multiple_processors_canHandle_incoming_webhook() throws Exception { + BitbucketCloudEndpoint endpoint = new BitbucketCloudEndpoint(false, 0, 0, false, null, true, "hmac"); endpoint.setBitbucketJenkinsRootUrl("https://jenkins.example.com"); BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); try { - mockPluginRequest(endpoint.getServerUrl()); - when(req.getHeader("X-Event-Key")).thenReturn("repo:push"); - when(req.getInputStream()).thenReturn(loadResource("plugin/branch_created.json")); - // when(req.getInputStream()).thenReturn(loadResource("plugin/branch_deleted.json")); - // when(req.getInputStream()).thenReturn(loadResource("plugin/commit_update.json")); - // when(req.getInputStream()).thenReturn(loadResource("plugin/commit_update2.json")); - - sut.doNotify(req); - verify(hookProcessor).process( - eq(HookEventType.PUSH), - anyString(), - eq(BitbucketType.SERVER), - eq("http://localhost:7990/127.0.0.1 ⇒ https://jenkins.example.com:80/bitbucket-scmsource-hook/notify"), - eq(endpoint.getServerUrl())); - } finally { - BitbucketEndpointConfiguration.get().removeEndpoint(endpoint.getServerUrl()); - } - } - - @Test - void test_native_bad_signature() throws Exception { - BitbucketServerEndpoint endpoint = new BitbucketServerEndpoint("datacenter", "http://localhost:7990/bitbucket", false, null, true, credentialsId); - endpoint.setBitbucketJenkinsRootUrl("https://jenkins.example.com"); - BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); + BitbucketHookProcessor hookProcessor = mock(BitbucketHookProcessor.class); + when(hookProcessor.canHandle(any(), any())).thenReturn(true); + sut = new BitbucketSCMSourcePushHookReceiver() { + @Override + Stream getHookProcessors() { + BitbucketHookProcessor otherHookProcessor = mock(BitbucketHookProcessor.class); + when(otherHookProcessor.canHandle(any(), any())).thenReturn(true); + return Stream.of(hookProcessor, otherHookProcessor); + } + }; + mockRequest(); + headers.putAll(HookProcessorTestUtil.getCloudHeaders()); - try { - mockServerRequest(endpoint.getServerUrl()); - when(req.getHeader("X-Event-Key")).thenReturn("repo:refs_changed"); - when(req.getHeader("X-Hub-Signature")).thenReturn("sha256=4ffba9e7b58ea3d7e1a230446e8c92baea0aeec89b73f598932387254f0de13f"); - when(req.getInputStream()).thenReturn(loadResource("native/signed_payload.json")); - /*HttpResponse response = */ sut.doNotify(req); - // really hard to verify if response contains a status 400 - verify(hookProcessor, never()).process(any(), anyString(), any(), anyString(), anyString()); + HttpResponse response = sut.doNotify(req); + assertThat(response).asString().contains("More processors found that handle the incoming Bitbucket hook."); + verify(hookProcessor, never()).getServerURL(any(), any()); } finally { - BitbucketEndpointConfiguration.get().removeEndpoint(endpoint.getServerUrl()); + BitbucketEndpointConfiguration.get().removeEndpoint(endpoint.getServerURL()); } } - @Test - void test_cloud_pullrequest_created() throws Exception { - mockCloudRequest(); - when(req.getHeader("X-Event-Key")).thenReturn("pullrequest:created"); - when(req.getInputStream()).thenReturn(loadResource("cloud/pullrequest_created.json")); - - sut.doNotify(req); - - verify(hookProcessor).process( - eq(HookEventType.PULL_REQUEST_CREATED), - anyString(), - eq(BitbucketType.CLOUD), - eq("https://bitbucket.org/185.166.143.48 ⇒ https://jenkins.example.com:80/bitbucket-scmsource-hook/notify"), - eq("https://bitbucket.org")); - } - - @Test - void test_cloud_pullrequest_declined() throws Exception { - mockCloudRequest(); - when(req.getHeader("X-Event-Key")).thenReturn("pullrequest:rejected"); - when(req.getInputStream()).thenReturn(loadResource("cloud/pullrequest_rejected.json")); - - sut.doNotify(req); - - verify(hookProcessor).process( - eq(HookEventType.PULL_REQUEST_DECLINED), - anyString(), - eq(BitbucketType.CLOUD), - eq("https://bitbucket.org/185.166.143.48 ⇒ https://jenkins.example.com:80/bitbucket-scmsource-hook/notify"), - eq("https://bitbucket.org")); - } - - private ServletInputStream loadResource(String resource) { - final InputStream delegate = this.getClass().getResourceAsStream(resource); - return new ServletInputStream() { - - @Override - public int read() throws IOException { - return delegate.read(); - } - - @Override - public void setReadListener(ReadListener readListener) { - } - - @Override - public boolean isReady() { - return !isFinished(); - } - - @Override - public boolean isFinished() { - try { - return delegate.available() == 0; - } catch (IOException e) { - return false; - } - } - }; - } } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookConfigurationTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookConfigurationTest.java index 406d55143..00df0332a 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookConfigurationTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookConfigurationTest.java @@ -48,17 +48,6 @@ static void init(JenkinsRule rule) { ConfigurationAsCode.get().configure(jcacURL); } - @Test - void given_instanceWithServerVersion6_when_getHooks_SERVER_PR_RVWR_UPDATE_EVENT_exists() { - WebhookConfiguration whc = new WebhookConfiguration(); - BitbucketSCMSource owner = mock(BitbucketSCMSource.class); - final String server = "http://bitbucket.example.com:8088"; - when(owner.getServerUrl()).thenReturn(server); - when(owner.getEndpointJenkinsRootURL()).thenReturn(server); - BitbucketWebHook hook = whc.getHook(owner); - assertThat(hook.getEvents()).contains(HookEventType.SERVER_PULL_REQUEST_REVIEWER_UPDATED.getKey()); - } - @Test void given_instanceWithServerVersion6_5_when_getHooks_SERVER_MIRROR_REPO_SYNC_EVENT_exists() { WebhookConfiguration whc = new WebhookConfiguration(); @@ -72,39 +61,6 @@ void given_instanceWithServerVersion6_5_when_getHooks_SERVER_MIRROR_REPO_SYNC_EV assertThat(hook.getEvents()).contains(HookEventType.SERVER_MIRROR_REPO_SYNCHRONIZED.getKey()); } - @Test - void given_instanceWithServerVersion510_when_getHooks_SERVER_PR_RVWR_UPDATE_EVENT_exists() { - WebhookConfiguration whc = new WebhookConfiguration(); - BitbucketSCMSource owner = mock(BitbucketSCMSource.class); - final String server = "http://bitbucket.example.com:8089"; - when(owner.getServerUrl()).thenReturn(server); - when(owner.getEndpointJenkinsRootURL()).thenReturn(server); - BitbucketWebHook hook = whc.getHook(owner); - assertThat(hook.getEvents()).contains(HookEventType.SERVER_PULL_REQUEST_REVIEWER_UPDATED.getKey()); - } - - @Test - void given_instanceWithServerVersion59_when_getHooks_SERVER_PR_RVWR_UPDATE_EVENT_not_exists() { - WebhookConfiguration whc = new WebhookConfiguration(); - BitbucketSCMSource owner = mock(BitbucketSCMSource.class); - final String server = "http://bitbucket.example.com:8090"; - when(owner.getServerUrl()).thenReturn(server); - when(owner.getEndpointJenkinsRootURL()).thenReturn(server); - BitbucketWebHook hook = whc.getHook(owner); - assertThat(hook.getEvents()).doesNotContain(HookEventType.SERVER_PULL_REQUEST_REVIEWER_UPDATED.getKey()); - } - - @Test - void given_instanceWithServerVersion59_when_getHooks_SERVER_PR_MOD_EVENT_not_exists() { - WebhookConfiguration whc = new WebhookConfiguration(); - BitbucketSCMSource owner = mock(BitbucketSCMSource.class); - final String server = "http://bitbucket.example.com:8090"; - when(owner.getServerUrl()).thenReturn(server); - when(owner.getEndpointJenkinsRootURL()).thenReturn(server); - BitbucketWebHook hook = whc.getHook(owner); - assertThat(hook.getEvents()).doesNotContain(HookEventType.SERVER_PULL_REQUEST_MODIFIED.getKey()); - } - @Test void given_instanceWithServerVersion7_when_getHooks_SERVER_PR_FROM_REF_UPDATED_EVENT_exists() { WebhookConfiguration whc = new WebhookConfiguration(); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/DummyEndpointConfiguration.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/DummyEndpointConfiguration.java index 0432ca7a9..795d192b4 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/DummyEndpointConfiguration.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/DummyEndpointConfiguration.java @@ -40,12 +40,6 @@ public String getDisplayName() { return "Dummy"; } - @NonNull - @Override - public String getServerUrl() { - return getServerURL(); - } - @NonNull @Override public String getServerURL() { @@ -65,7 +59,7 @@ public EndpointType getType() { @NonNull @Override - public String getRepositoryUrl(@NonNull String repoOwner, @NonNull String repository) { + public String getRepositoryURL(@NonNull String repoOwner, @NonNull String repository) { return UriTemplate .fromTemplate("http://dummy.example.com{/owner,repo}") .set("owner", repoOwner) diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessorTest.java new file mode 100644 index 000000000..1914de5ce --- /dev/null +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessorTest.java @@ -0,0 +1,135 @@ +/* + * The MIT License + * + * Copyright (c) 2025, Nikolas Falco + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; + +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessorException; +import com.cloudbees.plugins.credentials.CredentialsScope; +import hudson.util.Secret; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import jenkins.scm.api.SCMHeadEvent; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; +import org.apache.commons.io.IOUtils; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class AbstractHookProcessorTest { + + private AbstractHookProcessor sut; + protected SCMHeadEvent scmEvent; + + @BeforeEach + void setup() { + sut = new AbstractHookProcessor() { + + @Override + public boolean canHandle(Map headers, MultiValuedMap parameters) { + return true; + } + + @Override + public void process(String eventType, String payload, Map origin, BitbucketEndpoint endpoint) { + throw new UnsupportedOperationException(); + } + }; + } + + @Test + void test_getEventType() throws Exception { + Map headers = new HashMap<>(); + MultiValuedMap parameters = new ArrayListValuedHashMap<>(); + + headers.put("X-Event-Key", "pullrequest:created"); + assertThat(sut.getEventType(headers, parameters)).isEqualTo("pullrequest:created"); + } + + void test_getServerURL() throws Exception { + Map headers = new HashMap<>(); + MultiValuedMap parameters = new ArrayListValuedHashMap<>(); + parameters.put("server_url", "https://localhost:8080"); + + assertThat(sut.getServerURL(headers, parameters)).isEqualTo("https://localhost:8080"); + } + + @Test + void test_signature() throws Exception { + BitbucketEndpoint endpoint = getEndpoint(); + Map headers = new HashMap<>(); + headers.put("X-Hub-Signature", "sha256=f205c729821c6954aff2afe72b965c34015b4baf96ea8ddc2cc44999c014a035"); + + assertDoesNotThrow(() -> sut.verifyPayload(headers, loadResource("signed_payload.json"), endpoint)); + } + + @Test + void test_bad_signature() throws Exception { + BitbucketEndpoint endpoint = getEndpoint(); + Map headers = new HashMap<>(); + headers.put("X-Hub-Signature", "sha256=f205c729821c6954aff2afe72b965c34015b4baf96ea8ddc2cc44999c014a036"); + + assertThatThrownBy(() -> sut.verifyPayload(headers, loadResource("signed_payload.json"), endpoint)) + .isInstanceOf(BitbucketHookProcessorException.class) + .hasMessage("Signature verification failed") + .asInstanceOf(InstanceOfAssertFactories.type(BitbucketHookProcessorException.class)) + .satisfies(ex -> assertThat(ex.getHttpCode()).isEqualTo(403)); + } + + @Test + void test_signature_is_missing() throws Exception { + BitbucketEndpoint endpoint = getEndpoint(); + + assertThatThrownBy(() -> sut.verifyPayload(Collections.emptyMap(), loadResource("signed_payload.json"), endpoint)) + .isInstanceOf(BitbucketHookProcessorException.class) + .hasMessage("Payload has not be signed, configure the webHook secret in Bitbucket as documented at https://github.com/jenkinsci/bitbucket-branch-source-plugin/blob/master/docs/USER_GUIDE.adoc#webhooks-registering") + .asInstanceOf(InstanceOfAssertFactories.type(BitbucketHookProcessorException.class)) + .satisfies(ex -> assertThat(ex.getHttpCode()).isEqualTo(403)); + } + + private String loadResource(String resource) throws IOException { + try (InputStream stream = this.getClass().getResourceAsStream("cloud/" + resource)) { + return IOUtils.toString(stream, StandardCharsets.UTF_8); + } + } + + private BitbucketEndpoint getEndpoint() { + String credentialsId = "hmac"; + BitbucketEndpoint endpoint = mock(BitbucketEndpoint.class); + when(endpoint.hookSignatureCredentials()).thenReturn(new StringCredentialsImpl(CredentialsScope.GLOBAL, credentialsId, null, Secret.fromString("Gkvl$k$wyNpQAF42"))); + return endpoint; + } + +} diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PullRequestHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPullRequestHookProcessorTest.java similarity index 62% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PullRequestHookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPullRequestHookProcessorTest.java index d1fbfabd2..04d1e3a1b 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PullRequestHookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPullRequestHookProcessorTest.java @@ -21,20 +21,29 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import com.cloudbees.jenkins.plugins.bitbucket.trait.OriginPullRequestDiscoveryTrait; import hudson.scm.SCM; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import jenkins.scm.api.SCMEvent.Type; import jenkins.scm.api.SCMHeadEvent; import jenkins.scm.api.SCMNavigator; import jenkins.scm.api.SCMSource; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -44,24 +53,63 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -class PullRequestHookProcessorTest { +class CloudPullRequestHookProcessorTest { - private PullRequestHookProcessor sut; - protected SCMHeadEvent scmEvent; + private CloudPullRequestHookProcessor sut; + private SCMHeadEvent scmEvent; @BeforeEach void setup() { - sut = new PullRequestHookProcessor() { + sut = new CloudPullRequestHookProcessor() { @Override - protected void notifyEvent(SCMHeadEvent event, int delaySeconds) { - PullRequestHookProcessorTest.this.scmEvent = event; + public void notifyEvent(SCMHeadEvent event, int delaySeconds) { + CloudPullRequestHookProcessorTest.this.scmEvent = event; } }; } + @Test + void test_getServerURL_return_always_cloud_URL() throws Exception { + Map headers = new HashMap<>(); + MultiValuedMap parameters = new ArrayListValuedHashMap<>(); + parameters.put("server_url", "https://localhost:8080"); + + assertThat(sut.getServerURL(headers, parameters)).isEqualTo(BitbucketCloudEndpoint.SERVER_URL); + } + + @Test + void test_reindexOnEmptyChanges_is_disable_by_default() throws Exception { + assertThat(sut.reindexOnEmptyChanges()).isFalse(); + } + + @Test + void test_canHandle_only_pass_specific_cloud_hook() throws Exception { + MultiValuedMap parameters = new ArrayListValuedHashMap<>(); + + assertThat(sut.canHandle(new HashMap<>(), parameters)).isFalse(); + + Map headers = HookProcessorTestUtil.getCloudHeaders(); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pullrequest:created"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "pullrequest:rejected"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "pullrequest:fulfilled"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "pullrequest:updated"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "repo:push"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + } + @Test void test_pullrequest_created() throws Exception { - sut.process(HookEventType.PULL_REQUEST_CREATED, loadResource("pullrequest_created.json"), BitbucketType.CLOUD, "origin"); + sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); PREvent event = (PREvent) scmEvent; assertThat(event).isNotNull(); @@ -72,7 +120,7 @@ void test_pullrequest_created() throws Exception { @Test void test_pullrequest_rejected() throws Exception { - sut.process(HookEventType.PULL_REQUEST_DECLINED, loadResource("pullrequest_rejected.json"), BitbucketType.CLOUD, "origin"); + sut.process(HookEventType.PULL_REQUEST_DECLINED.getKey(), loadResource("pullrequest_rejected.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); PREvent event = (PREvent) scmEvent; assertThat(event).isNotNull(); @@ -83,7 +131,7 @@ void test_pullrequest_rejected() throws Exception { @Test void test_pullrequest_created_when_event_match_SCMNavigator() throws Exception { - sut.process(HookEventType.PULL_REQUEST_CREATED, loadResource("pullrequest_created.json"), BitbucketType.CLOUD, "origin"); + sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); PREvent event = (PREvent) scmEvent; // discard any scm navigator than bitbucket @@ -107,7 +155,7 @@ void test_pullrequest_created_when_event_match_SCMNavigator() throws Exception { @WithJenkins @Test void test_pullrequest_created_when_event_match_SCMSource(JenkinsRule r) throws Exception { - sut.process(HookEventType.PULL_REQUEST_CREATED, loadResource("pullrequest_created.json"), BitbucketType.CLOUD, "origin"); + sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); PREvent event = (PREvent) scmEvent; // discard any scm navigator than bitbucket @@ -133,7 +181,7 @@ void test_pullrequest_created_when_event_match_SCMSource(JenkinsRule r) throws E @WithJenkins @Test void test_pullrequest_rejected_returns_empty_pullrequests_when_event_match_SCMSource(JenkinsRule r) throws Exception { - sut.process(HookEventType.PULL_REQUEST_DECLINED, loadResource("pullrequest_rejected.json"), BitbucketType.CLOUD, "origin"); + sut.process(HookEventType.PULL_REQUEST_DECLINED.getKey(), loadResource("pullrequest_rejected.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); PREvent event = (PREvent) scmEvent; @@ -148,4 +196,5 @@ private String loadResource(String resource) throws IOException { return IOUtils.toString(stream, StandardCharsets.UTF_8); } } + } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PushHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPushHookProcessorTest.java similarity index 61% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PushHookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPushHookProcessorTest.java index ef1d66166..e137462e5 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PushHookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPushHookProcessorTest.java @@ -21,48 +21,93 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketTagSCMHead; import com.cloudbees.jenkins.plugins.bitbucket.BranchSCMHead; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import hudson.scm.SCM; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import jenkins.plugins.git.AbstractGitSCMSource.SCMRevisionImpl; -import jenkins.scm.api.SCMEvent; import jenkins.scm.api.SCMEvent.Type; import jenkins.scm.api.SCMHead; import jenkins.scm.api.SCMHeadEvent; import jenkins.scm.api.SCMRevision; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.jvnet.hudson.test.Issue; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -class PushHookProcessorTest { +class CloudPushHookProcessorTest { - private PushHookProcessor sut; + private CloudPushHookProcessor sut; private SCMHeadEvent scmEvent; @BeforeEach void setup() { - sut = new PushHookProcessor() { + sut = new CloudPushHookProcessor() { @Override - protected void notifyEvent(SCMHeadEvent event, int delaySeconds) { - PushHookProcessorTest.this.scmEvent = event; + public void notifyEvent(SCMHeadEvent event, int delaySeconds) { + CloudPushHookProcessorTest.this.scmEvent = event; } }; } + @Test + void test_getServerURL_return_always_cloud_URL() throws Exception { + Map headers = new HashMap<>(); + MultiValuedMap parameters = new ArrayListValuedHashMap<>(); + parameters.put("server_url", "https://localhost:8080"); + + assertThat(sut.getServerURL(headers, parameters)).isEqualTo(BitbucketCloudEndpoint.SERVER_URL); + } + + @Test + void test_reindexOnEmptyChanges_is_disable_by_default() throws Exception { + assertThat(sut.reindexOnEmptyChanges()).isFalse(); + } + + @Test + void test_canHandle_only_pass_specific_cloud_hook() throws Exception { + MultiValuedMap parameters = new ArrayListValuedHashMap<>(); + + assertThat(sut.canHandle(new HashMap<>(), parameters)).isFalse(); + + Map headers = HookProcessorTestUtil.getCloudHeaders(); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pullrequest:created"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pullrequest:rejected"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pullrequest:fulfilled"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pullrequest:updated"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "repo:push"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + } + @Test void test_tag_created() throws Exception { - sut.process(HookEventType.PUSH, loadResource("cloud/tag_created.json"), BitbucketType.CLOUD, "origin"); + sut.process(HookEventType.PUSH.getKey(), loadResource("tag_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); PushEvent event = (PushEvent) scmEvent; assertThat(event).isNotNull(); @@ -80,7 +125,7 @@ void test_tag_created() throws Exception { @Test void test_annotated_tag_created() throws Exception { - sut.process(HookEventType.PUSH, loadResource("cloud/annotated_tag_created.json"), BitbucketType.CLOUD, "origin"); + sut.process(HookEventType.PUSH.getKey(), loadResource("annotated_tag_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); PushEvent event = (PushEvent) scmEvent; assertThat(event).isNotNull(); @@ -98,7 +143,7 @@ void test_annotated_tag_created() throws Exception { @Test void test_commmit_created() throws Exception { - sut.process(HookEventType.PUSH, loadResource("cloud/commit_created.json"), BitbucketType.CLOUD, "origin"); + sut.process(HookEventType.PUSH.getKey(), loadResource("commit_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); PushEvent event = (PushEvent) scmEvent; assertThat(event).isNotNull(); @@ -118,38 +163,10 @@ void test_commmit_created() throws Exception { .isEqualTo(new SCMRevisionImpl(new BranchSCMHead("feature/issue-819"), "5ecffa3874e96920f24a2b3c0d0038e47d5cd1a4")); } - @Test - void test_push_server() throws Exception { - sut.process(HookEventType.SERVER_REFS_CHANGED, loadResource("server/pushPayload.json"), BitbucketType.SERVER, "origin"); - - PushEvent event = (PushEvent) scmEvent; - assertThat(event).isNotNull(); - assertThat(event.getSourceName()).isEqualTo("test-repos"); - assertThat(event.getType()).isEqualTo(SCMEvent.Type.UPDATED); - assertThat(event.isMatch(mock(SCM.class))).isFalse(); - - BitbucketSCMSource scmSource = new BitbucketSCMSource("aMUNIZ", "test-repos"); - Map heads = event.heads(scmSource); - assertThat(heads.keySet()) - .first() - .usingRecursiveComparison() - .isEqualTo(new BranchSCMHead("main")); - assertThat(heads.values()) - .first() - .usingRecursiveComparison() - .isEqualTo(new SCMRevisionImpl(new BranchSCMHead("main"), "9fdd7b96d3f5c276d0b9e0bf38c879eb112d889a")); - } - - @Test - @Issue("JENKINS-55927") - void test_push_server_empty_changes() throws Exception { - sut.process(HookEventType.SERVER_REFS_CHANGED, loadResource("server/emptyPayload.json"), BitbucketType.SERVER, "origin"); - assertThat(scmEvent).isNull(); - } - private String loadResource(String resource) throws IOException { - try (InputStream stream = this.getClass().getResourceAsStream(resource)) { + try (InputStream stream = this.getClass().getResourceAsStream("cloud/" + resource)) { return IOUtils.toString(stream, StandardCharsets.UTF_8); } } + } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessorTest.java new file mode 100644 index 000000000..9f0f40262 --- /dev/null +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessorTest.java @@ -0,0 +1,104 @@ +/* + * The MIT License + * + * Copyright (c) 2025, Allan Burdajewicz + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; + +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import jenkins.scm.api.SCMHeadEvent; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.jvnet.hudson.test.junit.jupiter.WithJenkins; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@WithJenkins +class NativeServerPingHookProcessorTest { + + private static final String SERVER_URL = "http://localhost:7990"; + private NativeServerPingHookProcessor sut; + private SCMHeadEvent scmEvent; + private BitbucketEndpoint endpoint; + + @BeforeEach + void setup() { + sut = new NativeServerPingHookProcessor() { + @Override + public void notifyEvent(SCMHeadEvent event, int delaySeconds) { + NativeServerPingHookProcessorTest.this.scmEvent = event; + } + }; + endpoint = mock(BitbucketEndpoint.class); + when(endpoint.getServerURL()).thenReturn(SERVER_URL); + } + + @Test + void test_reindexOnEmptyChanges_is_disable_by_default() throws Exception { + assertThat(sut.reindexOnEmptyChanges()).isFalse(); + } + + @Test + void test_canHandle_only_pass_specific_native_hook() throws Exception { + MultiValuedMap parameters = new ArrayListValuedHashMap<>(); + parameters.put("server_url", SERVER_URL); + + assertThat(sut.canHandle(new HashMap<>(), parameters)).isFalse(); + + Map headers = HookProcessorTestUtil.getNativeHeaders(); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pr:opened"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pr:merged"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "diagnostics:ping"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + } + + @Test + void test_ping() throws Exception { + sut.process(HookEventType.SERVER_PING.getKey(), loadResource("ping.json"), Collections.emptyMap(), endpoint); + + assertThat(scmEvent).isNull(); + } + + private String loadResource(String resource) throws IOException { + try (InputStream stream = this.getClass().getResourceAsStream("native/" + resource)) { + return IOUtils.toString(stream, StandardCharsets.UTF_8); + } + } +} diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPullRequestHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPullRequestHookProcessorTest.java similarity index 61% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPullRequestHookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPullRequestHookProcessorTest.java index 6473f116d..27a383dea 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPullRequestHookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPullRequestHookProcessorTest.java @@ -21,17 +21,22 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BranchSCMHead; import com.cloudbees.jenkins.plugins.bitbucket.PullRequestSCMHead; import com.cloudbees.jenkins.plugins.bitbucket.api.PullRequestBranchType; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import com.cloudbees.jenkins.plugins.bitbucket.trait.OriginPullRequestDiscoveryTrait; import hudson.scm.SCM; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -41,6 +46,8 @@ import jenkins.scm.api.SCMHeadOrigin; import jenkins.scm.api.SCMRevision; import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -50,28 +57,72 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class NativeServerPullRequestHookProcessorTest { private static final String SERVER_URL = "http://localhost:7990"; private NativeServerPullRequestHookProcessor sut; private SCMHeadEvent scmEvent; + private BitbucketEndpoint endpoint; @BeforeEach void setup() { sut = new NativeServerPullRequestHookProcessor() { @Override - protected void notifyEvent(SCMHeadEvent event, int delaySeconds) { + public void notifyEvent(SCMHeadEvent event, int delaySeconds) { NativeServerPullRequestHookProcessorTest.this.scmEvent = event; } }; + endpoint = mock(BitbucketEndpoint.class); + when(endpoint.getServerURL()).thenReturn(SERVER_URL); + } + + @Test + void test_reindexOnEmptyChanges_is_disable_by_default() throws Exception { + assertThat(sut.reindexOnEmptyChanges()).isFalse(); + } + + @Test + void test_canHandle_only_pass_specific_native_hook() throws Exception { + MultiValuedMap parameters = new ArrayListValuedHashMap<>(); + parameters.put("server_url", SERVER_URL); + + assertThat(sut.canHandle(new HashMap<>(), parameters)).isFalse(); + + Map headers = HookProcessorTestUtil.getNativeHeaders(); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pr:opened"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "pr:merged"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "pr:declined"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "pr:deleted"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "pr:modified"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "pr:from_ref_updated"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "pr:reviewer:updated"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pr:reviewer:approved"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); } @WithJenkins @Test @Issue("JENKINS-75523") void test_pr_where_source_is_tag(JenkinsRule rule) throws Exception { - sut.process(HookEventType.SERVER_PULL_REQUEST_OPENED, loadResource("native/prOpenFromTagPayload.json"), BitbucketType.SERVER, "origin", SERVER_URL); + sut.process(HookEventType.SERVER_PULL_REQUEST_OPENED.getKey(), loadResource("prOpenFromTagPayload.json"), Collections.emptyMap(), endpoint); ServerHeadEvent event = (ServerHeadEvent) scmEvent; assertThat(event).isNotNull(); @@ -90,7 +141,7 @@ void test_pr_where_source_is_tag(JenkinsRule rule) throws Exception { } private String loadResource(String resource) throws IOException { - try (InputStream stream = this.getClass().getResourceAsStream(resource)) { + try (InputStream stream = this.getClass().getResourceAsStream("native/" + resource)) { return IOUtils.toString(stream, StandardCharsets.UTF_8); } } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPushHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPushHookProcessorTest.java similarity index 72% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPushHookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPushHookProcessorTest.java index e69516295..09d9a267f 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPushHookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPushHookProcessorTest.java @@ -21,23 +21,30 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.hooks; +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketTagSCMHead; import com.cloudbees.jenkins.plugins.bitbucket.BranchSCMHead; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketMockApiFactory; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketIntegrationClientFactory; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import hudson.scm.SCM; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import jenkins.plugins.git.AbstractGitSCMSource; import jenkins.scm.api.SCMEvent; import jenkins.scm.api.SCMHead; import jenkins.scm.api.SCMHeadEvent; import jenkins.scm.api.SCMRevision; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -48,6 +55,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @WithJenkins class NativeServerPushHookProcessorTest { @@ -56,6 +64,7 @@ class NativeServerPushHookProcessorTest { private static final String MIRROR_ID = "ABCD-1234-EFGH-5678"; private NativeServerPushHookProcessor sut; private SCMHeadEvent scmEvent; + private BitbucketEndpoint endpoint; static JenkinsRule rule; @@ -68,16 +77,46 @@ static void init(JenkinsRule r) { void setup() { sut = new NativeServerPushHookProcessor() { @Override - protected void notifyEvent(SCMHeadEvent event, int delaySeconds) { + public void notifyEvent(SCMHeadEvent event, int delaySeconds) { NativeServerPushHookProcessorTest.this.scmEvent = event; } }; + endpoint = mock(BitbucketEndpoint.class); + when(endpoint.getServerURL()).thenReturn(SERVER_URL); + } + + @Test + void test_reindexOnEmptyChanges_is_disable_by_default() throws Exception { + assertThat(sut.reindexOnEmptyChanges()).isFalse(); + } + + @Test + void test_canHandle_only_pass_specific_native_hook() throws Exception { + MultiValuedMap parameters = new ArrayListValuedHashMap<>(); + parameters.put("server_url", SERVER_URL); + + assertThat(sut.canHandle(new HashMap<>(), parameters)).isFalse(); + + Map headers = HookProcessorTestUtil.getNativeHeaders(); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pr:opened"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pr:merged"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "repo:refs_changed"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "mirror:repo_synchronized"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); } @Test @Issue("JENKINS-55927") void test_mirror_sync_changes() throws Exception { - sut.process(HookEventType.SERVER_MIRROR_REPO_SYNCHRONIZED, loadResource("native/mirrorSynchronized.json"), BitbucketType.SERVER, "origin", SERVER_URL); + sut.process(HookEventType.SERVER_MIRROR_REPO_SYNCHRONIZED.getKey(), loadResource("mirrorSynchronized.json"), Collections.emptyMap(), endpoint); ServerPushEvent event = (ServerPushEvent) scmEvent; assertThat(event).isNotNull(); @@ -97,7 +136,7 @@ void test_mirror_sync_changes() throws Exception { @Test @Issue("JENKINS-75604") void test_annotated_tag_create_event() throws Exception { - sut.process(HookEventType.SERVER_REFS_CHANGED, loadResource("native/annotated_tag_created.json"), BitbucketType.SERVER, "origin", SERVER_URL); + sut.process(HookEventType.SERVER_REFS_CHANGED.getKey(), loadResource("annotated_tag_created.json"), Collections.emptyMap(), endpoint); assertThat(scmEvent) .isInstanceOf(ServerPushEvent.class) .isNotNull(); @@ -118,7 +157,7 @@ void test_annotated_tag_create_event() throws Exception { @Test @Issue("JENKINS-75604") void test_tag_created_event() throws Exception { - sut.process(HookEventType.SERVER_REFS_CHANGED, loadResource("native/tag_created.json"), BitbucketType.SERVER, "origin", SERVER_URL); + sut.process(HookEventType.SERVER_REFS_CHANGED.getKey(), loadResource("tag_created.json"), Collections.emptyMap(), endpoint); assertThat(scmEvent) .isInstanceOf(ServerPushEvent.class) .isNotNull(); @@ -139,7 +178,7 @@ void test_tag_created_event() throws Exception { @Test @Issue("JENKINS-75604") void test_tag_deleted_event() throws Exception { - sut.process(HookEventType.SERVER_REFS_CHANGED, loadResource("native/tag_deleted.json"), BitbucketType.SERVER, "origin", SERVER_URL); + sut.process(HookEventType.SERVER_REFS_CHANGED.getKey(), loadResource("tag_deleted.json"), Collections.emptyMap(), endpoint); assertThat(scmEvent) .isInstanceOf(ServerPushEvent.class) .isNotNull(); @@ -160,14 +199,14 @@ void test_tag_deleted_event() throws Exception { @Test @Issue("JENKINS-55927") void test_mirror_sync_reflimitexceeed() throws Exception { - sut.process(HookEventType.SERVER_MIRROR_REPO_SYNCHRONIZED, loadResource("native/mirrorSynchronized_refLimitExceeded.json"), BitbucketType.SERVER, "origin", SERVER_URL); + sut.process(HookEventType.SERVER_MIRROR_REPO_SYNCHRONIZED.getKey(), loadResource("mirrorSynchronized_refLimitExceeded.json"), Collections.emptyMap(), endpoint); ServerPushEvent event = (ServerPushEvent) scmEvent; assertThat(event).isNull(); } @Test void test_push() throws Exception { - sut.process(HookEventType.SERVER_REFS_CHANGED, loadResource("native/pushPayload.json"), BitbucketType.SERVER, "origin", SERVER_URL); + sut.process(HookEventType.SERVER_REFS_CHANGED.getKey(), loadResource("pushPayload.json"), Collections.emptyMap(), endpoint); ServerPushEvent event = (ServerPushEvent) scmEvent; assertThat(event).isNotNull(); @@ -190,13 +229,13 @@ void test_push() throws Exception { @Test @Issue("JENKINS-55927") void test_push_empty_changes() throws Exception { - sut.process(HookEventType.SERVER_REFS_CHANGED, loadResource("native/emptyPayload.json"), BitbucketType.SERVER, "origin", SERVER_URL); + sut.process(HookEventType.SERVER_REFS_CHANGED.getKey(), loadResource("emptyPayload.json"), Collections.emptyMap(), endpoint); ServerPushEvent event = (ServerPushEvent) scmEvent; assertThat(event).isNull(); } private String loadResource(String resource) throws IOException { - try (InputStream stream = this.getClass().getResourceAsStream(resource)) { + try (InputStream stream = this.getClass().getResourceAsStream("native/" + resource)) { return IOUtils.toString(stream, StandardCharsets.UTF_8); } } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessorTest.java new file mode 100644 index 000000000..213a36c73 --- /dev/null +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessorTest.java @@ -0,0 +1,205 @@ +/* + * The MIT License + * + * Copyright (c) 2025, Nikolas Falco + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; + +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; +import com.cloudbees.jenkins.plugins.bitbucket.trait.OriginPullRequestDiscoveryTrait; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import jenkins.scm.api.SCMEvent.Type; +import jenkins.scm.api.SCMHeadEvent; +import jenkins.scm.api.SCMNavigator; +import jenkins.scm.api.SCMSource; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.junit.jupiter.WithJenkins; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@SuppressWarnings("deprecation") +class PluginPullRequestHookProcessorTest { + private static final String SERVER_URL = "http://localhost:7990"; + + private PluginPullRequestHookProcessor sut; + private SCMHeadEvent scmEvent; + + @BeforeEach + void setup() { + sut = new PluginPullRequestHookProcessor() { + @Override + public void notifyEvent(SCMHeadEvent event, int delaySeconds) { + PluginPullRequestHookProcessorTest.this.scmEvent = event; + } + }; + } + + @Test + void test_getServerURL_return_always_cloud_URL() throws Exception { + Map headers = new HashMap<>(); + MultiValuedMap parameters = new ArrayListValuedHashMap<>(); + parameters.put("server_url", SERVER_URL); + + assertThat(sut.getServerURL(headers, parameters)).isEqualTo(SERVER_URL); + } + + @Test + void test_reindexOnEmptyChanges_is_disable_by_default() throws Exception { + assertThat(sut.reindexOnEmptyChanges()).isFalse(); + } + + @Test + void test_canHandle_only_pass_specific_cloud_hook() throws Exception { + MultiValuedMap parameters = new ArrayListValuedHashMap<>(); + parameters.put("server_url", SERVER_URL); + + assertThat(sut.canHandle(new HashMap<>(), parameters)).isFalse(); + + Map headers = HookProcessorTestUtil.getPluginHeaders(); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pullrequest:created"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "pullrequest:rejected"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "pullrequest:fulfilled"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "pullrequest:updated"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "repo:push"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + } + + @Test + void test_pullrequest_created() throws Exception { + sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); + + assertThat(scmEvent).isNotNull().isInstanceOf(PREvent.class); + + PREvent event = (PREvent) scmEvent; + assertThat(event.getSourceName()).isEqualTo("rep_1"); + assertThat(event.getType()).isEqualTo(Type.CREATED); + } + + @Test + void test_pullrequest_merged() throws Exception { + sut.process(HookEventType.PULL_REQUEST_MERGED.getKey(), loadResource("pullrequest_merged.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); + + assertThat(scmEvent).isNotNull().isInstanceOf(PREvent.class); + + PREvent event = (PREvent) scmEvent; + assertThat(event.getSourceName()).isEqualTo("rep_1"); + assertThat(event.getType()).isEqualTo(Type.REMOVED); + } + + @Test + void test_pullrequest_updated() throws Exception { + sut.process(HookEventType.PULL_REQUEST_UPDATED.getKey(), loadResource("pullrequest_updated.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); + + assertThat(scmEvent).isNotNull().isInstanceOf(PREvent.class); + + PREvent event = (PREvent) scmEvent; + assertThat(event.getSourceName()).isEqualTo("rep_1"); + assertThat(event.getType()).isEqualTo(Type.UPDATED); + } + + @Test + void test_PREvent_match_SCMNavigator() throws Exception { + sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); + + PREvent event = (PREvent) scmEvent; + assertThat(event.getType()).isEqualTo(Type.CREATED); + // discard any scm navigator than bitbucket + assertThat(event.isMatch(mock(SCMNavigator.class))).isFalse(); + + BitbucketSCMNavigator scmNavigator = new BitbucketSCMNavigator("PROJECT_1"); + assertThat(event.isMatch(scmNavigator)).isFalse(); + // match only if projectKey and serverURL matches + scmNavigator.setServerUrl(SERVER_URL); + assertThat(event.isMatch(scmNavigator)).isTrue(); + // if set must match the project of repository from which the hook is generated + scmNavigator.setProjectKey("PROJECT_1"); + assertThat(event.isMatch(scmNavigator)).isTrue(); + // project key is case sensitive + scmNavigator.setProjectKey("project_1"); + assertThat(event.isMatch(scmNavigator)).isFalse(); + + // workspace/owner is case insensitive + scmNavigator = new BitbucketSCMNavigator("project_1"); + scmNavigator.setServerUrl(SERVER_URL); + assertThat(event.isMatch(scmNavigator)).isTrue(); + } + + @WithJenkins + @Test + void test_PREvent_match_SCMSource(JenkinsRule r) throws Exception { + sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); + + PREvent event = (PREvent) scmEvent; + // discard any scm navigator than bitbucket + assertThat(event.isMatch(mock(SCMSource.class))).isFalse(); + + BitbucketSCMSource scmSource = new BitbucketSCMSource("PROJECT_1", "rep_1"); + scmSource.setServerUrl(SERVER_URL); + // skip scm source that has not been configured to discover PRs + assertThat(event.isMatch(scmSource)).isFalse(); + + scmSource.setTraits(List.of(new OriginPullRequestDiscoveryTrait(2))); + assertThat(event.isMatch(scmSource)).isTrue(); + + // workspace/owner is case insensitive + scmSource = new BitbucketSCMSource("project_1", "rep_1"); + scmSource.setServerUrl(SERVER_URL); + scmSource.setTraits(List.of(new OriginPullRequestDiscoveryTrait(1))); + assertThat(event.isMatch(scmSource)).isTrue(); + + assertThat(event.getPullRequests(scmSource)) + .isNotEmpty() + .hasSize(1); + } + + private String loadResource(String resource) throws IOException { + try (InputStream stream = this.getClass().getResourceAsStream("plugin/" + resource)) { + return IOUtils.toString(stream, StandardCharsets.UTF_8); + } + } + +} diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessorTest.java new file mode 100644 index 000000000..6b3447a06 --- /dev/null +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessorTest.java @@ -0,0 +1,237 @@ +/* + * The MIT License + * + * Copyright (c) 2025, Nikolas Falco + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; + +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.BranchSCMHead; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import jenkins.plugins.git.AbstractGitSCMSource.SCMRevisionImpl; +import jenkins.scm.api.SCMEvent; +import jenkins.scm.api.SCMEvent.Type; +import jenkins.scm.api.SCMHead; +import jenkins.scm.api.SCMHeadEvent; +import jenkins.scm.api.SCMNavigator; +import jenkins.scm.api.SCMRevision; +import jenkins.scm.api.SCMSource; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.junit.jupiter.WithJenkins; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@SuppressWarnings("deprecation") +class PluginPushHookProcessorTest { + + private static final String SERVER_URL = "http://localhost:7990"; + private PluginPushHookProcessor sut; + private SCMHeadEvent scmEvent; + + @BeforeEach + void setup() { + sut = new PluginPushHookProcessor() { + @Override + public void notifyEvent(SCMHeadEvent event, int delaySeconds) { + PluginPushHookProcessorTest.this.scmEvent = event; + } + }; + } + + @Test + void test_getServerURL_return_always_cloud_URL() throws Exception { + Map headers = new HashMap<>(); + MultiValuedMap parameters = new ArrayListValuedHashMap<>(); + parameters.put("server_url", SERVER_URL); + + assertThat(sut.getServerURL(headers, parameters)).isEqualTo(SERVER_URL); + } + + @Test + void test_reindexOnEmptyChanges_is_disable_by_default() throws Exception { + assertThat(sut.reindexOnEmptyChanges()).isFalse(); + } + + @Test + void test_canHandle_only_pass_specific_native_hook() throws Exception { + MultiValuedMap parameters = new ArrayListValuedHashMap<>(); + parameters.put("server_url", SERVER_URL); + + assertThat(sut.canHandle(new HashMap<>(), parameters)).isFalse(); + + Map headers = HookProcessorTestUtil.getPluginHeaders(); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pr:opened"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "pr:merged"); + assertThat(sut.canHandle(headers, parameters)).isFalse(); + + headers.put("X-Event-Key", "repo:push"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + + headers.put("X-Event-Key", "pullrequest:updated"); + assertThat(sut.canHandle(headers, parameters)).isTrue(); + } + + @Test + void test_push_server_UPDATE_2() throws Exception { + sut.process(HookEventType.PUSH.getKey(), loadResource("commit_update2.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); + + PushEvent event = (PushEvent) scmEvent; + assertThat(event).isNotNull(); + assertThat(event.getSourceName()).isEqualTo("rep_1"); + assertThat(event.getType()).isEqualTo(SCMEvent.Type.UPDATED); + + BitbucketSCMSource scmSource = new BitbucketSCMSource("PROJECT_1", "rep_1"); + scmSource.setServerUrl(SERVER_URL); + Map heads = event.heads(scmSource); + assertThat(heads) + .containsKey(new BranchSCMHead("master")) + .containsValue(new SCMRevisionImpl(new BranchSCMHead("master"), "500cf91e7b4b7d9f995cdb6e81cb5538216ac02e")); + } + + @Test + void test_push_server_UPDATE() throws Exception { + sut.process(HookEventType.PUSH.getKey(), loadResource("commit_update.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); + + PushEvent event = (PushEvent) scmEvent; + assertThat(event).isNotNull(); + assertThat(event.getSourceName()).isEqualTo("rep_1"); + assertThat(event.getType()).isEqualTo(SCMEvent.Type.UPDATED); + + BitbucketSCMSource scmSource = new BitbucketSCMSource("PROJEct_1", "rep_1"); + scmSource.setServerUrl(SERVER_URL); + Map heads = event.heads(scmSource); + assertThat(heads) + .containsKey(new BranchSCMHead("test-webhook")) + .containsValue(new SCMRevisionImpl(new BranchSCMHead("test-webhook"), "c0158b3e6c8cecf3bddc39d20957a98660cd23fd")); + } + + @Test + void test_push_server_CREATED() throws Exception { + sut.process(HookEventType.PUSH.getKey(), loadResource("branch_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); + + PushEvent event = (PushEvent) scmEvent; + assertThat(event).isNotNull(); + assertThat(event.getSourceName()).isEqualTo("rep_1"); + assertThat(event.getType()).isEqualTo(SCMEvent.Type.CREATED); + + BitbucketSCMSource scmSource = new BitbucketSCMSource("pROJECT_1", "rep_1"); + scmSource.setServerUrl(SERVER_URL); + Map heads = event.heads(scmSource); + assertThat(heads) + .containsKey(new BranchSCMHead("test-webhook")) + .containsValue(new SCMRevisionImpl(new BranchSCMHead("test-webhook"), "417b2f673581ee6000e260a5fa65e62b56c7a3cd")); + } + + @Test + void test_push_server_REMOVED() throws Exception { + sut.process(HookEventType.PUSH.getKey(), loadResource("branch_deleted.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); + + PushEvent event = (PushEvent) scmEvent; + assertThat(event).isNotNull(); + assertThat(event.getSourceName()).isEqualTo("rep_1"); + assertThat(event.getType()).isEqualTo(SCMEvent.Type.REMOVED); + + BitbucketSCMSource scmSource = new BitbucketSCMSource("pROJECT_1", "rep_1"); + scmSource.setServerUrl(SERVER_URL); + Map heads = event.heads(scmSource); + assertThat(heads).containsKey(new BranchSCMHead("test-webhook")); + assertThat(heads.values()).containsNull(); + } + + @Test + void test_PushEvent_match_SCMNavigator() throws Exception { + sut.process(HookEventType.PUSH.getKey(), loadResource("branch_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); + + PushEvent event = (PushEvent) scmEvent; + assertThat(event.getType()).isEqualTo(Type.CREATED); + // discard any scm navigator than bitbucket + assertThat(event.isMatch(mock(SCMNavigator.class))).isFalse(); + + BitbucketSCMNavigator scmNavigator = new BitbucketSCMNavigator("PROJECT_1"); + assertThat(event.isMatch(scmNavigator)).isFalse(); + // match only if projectKey and serverURL matches + scmNavigator.setServerUrl(SERVER_URL); + assertThat(event.isMatch(scmNavigator)).isTrue(); + // if set must match the project of repository from which the hook is generated + scmNavigator.setProjectKey("PROJECT_1"); + assertThat(event.isMatch(scmNavigator)).isTrue(); + // project key is case sensitive + scmNavigator.setProjectKey("project_1"); + assertThat(event.isMatch(scmNavigator)).isFalse(); + + // workspace/owner is case insensitive + scmNavigator = new BitbucketSCMNavigator("project_1"); + scmNavigator.setServerUrl(SERVER_URL); + assertThat(event.isMatch(scmNavigator)).isTrue(); + } + + @WithJenkins + @Test + void test_PushEvent_match_SCMSource(JenkinsRule r) throws Exception { + sut.process(HookEventType.PUSH.getKey(), loadResource("branch_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); + + PushEvent event = (PushEvent) scmEvent; + // discard any scm navigator than bitbucket + assertThat(event.isMatch(mock(SCMSource.class))).isFalse(); + + BitbucketSCMSource scmSource = new BitbucketSCMSource("PROJECT_1", "rep_1"); + scmSource.setServerUrl(SERVER_URL); + assertThat(event.isMatch(scmSource)).isTrue(); + + // workspace/owner is case insensitive + scmSource = new BitbucketSCMSource("project_1", "rep_1"); + scmSource.setServerUrl(SERVER_URL); + assertThat(event.isMatch(scmSource)).isTrue(); + } + + @Test + @Issue("JENKINS-55927") + void test_push_server_empty_changes() throws Exception { + sut.process(HookEventType.SERVER_REFS_CHANGED.getKey(), loadResource("emptyPayload.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); + assertThat(scmEvent).isNull(); + } + + private String loadResource(String resource) throws IOException { + try (InputStream stream = this.getClass().getResourceAsStream("plugin/" + resource)) { + return IOUtils.toString(stream, StandardCharsets.UTF_8); + } + } +} diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/test/util/HookProcessorTestUtil.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/test/util/HookProcessorTestUtil.java new file mode 100644 index 000000000..017f3d031 --- /dev/null +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/test/util/HookProcessorTestUtil.java @@ -0,0 +1,62 @@ +/* + * The MIT License + * + * Copyright (c) 2025, Nikolas Falco + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.test.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public final class HookProcessorTestUtil { + + private HookProcessorTestUtil() { + } + + public static Map getCloudHeaders() { + Map headers = new HashMap<>(); + headers.put("User-Agent", "Bitbucket-Webhooks/2.0"); + headers.put("X-Attempt-Number", "1"); + headers.put("Content-Type", "application/json"); + headers.put("X-Hook-UUID", UUID.randomUUID().toString()); + headers.put("X-Request-UUID", UUID.randomUUID().toString()); + headers.put("traceparent", UUID.randomUUID().toString()); + headers.put("User-Agent", "Bitbucket-Webhooks/2.0"); + return headers; + } + + public static Map getNativeHeaders() { + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json; charset=utf-8"); + headers.put("X-Request-Id", UUID.randomUUID().toString()); + headers.put("User-Agent", "Atlassian HttpClient 4.2.0 / Bitbucket-9.5.2 (9005002) / Default"); + return headers; + } + + public static Map getPluginHeaders() { + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json; charset=utf-8"); + headers.put("X-Bitbucket-Type", "server"); + headers.put("User-Agent", "Bitbucket version: 8.18.0, Post webhook plugin version: 7.13.41-SNAPSHOT"); + return headers; + } +} diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/test/util/MockRequest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/test/util/MockRequest.java new file mode 100644 index 000000000..3e07424c4 --- /dev/null +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/test/util/MockRequest.java @@ -0,0 +1,61 @@ +/* + * The MIT License + * + * Copyright (c) 2025, Nikolas Falco + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.test.util; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import java.io.IOException; +import java.io.InputStream; +import org.apache.tools.ant.filters.StringInputStream; + +public class MockRequest extends ServletInputStream { + InputStream delegate; + + public MockRequest(String payload) { + this.delegate = new StringInputStream(payload); + } + + @Override + public int read() throws IOException { + return delegate.read(); + } + + @Override + public void setReadListener(ReadListener readListener) { + } + + @Override + public boolean isReady() { + return !isFinished(); + } + + @Override + public boolean isFinished() { + try { + return delegate.available() == 0; + } catch (IOException e) { + return false; + } + } +} \ No newline at end of file diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/server/pushPayload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/server/pushPayload.json deleted file mode 100644 index 8fb0aa90d..000000000 --- a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/server/pushPayload.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "actor": { - "active": true, - "displayName": "Antonio Muniz", - "emailAddress": "amuniz@acme.com", - "id": 2, - "links": { - "self": [ - { - "href": "http://localhost:7990/users/amuniz" - } - ] - }, - "name": "amuniz", - "slug": "amuniz", - "type": "NORMAL" - }, - "push": { - "changes": [ - { - "created": false, - "closed": false, - "new": { - "type": "branch", - "name": "main", - "target": { - "type": "commit", - "hash": "9fdd7b96d3f5c276d0b9e0bf38c879eb112d889a" - } - }, - "old": { - "type": "branch", - "name": "main", - "target": { - "type": "commit", - "hash": "a8c13e8850dc60300be720019d1ffc1aa2fded87" - } - } - } - ] - }, - "date": "2024-11-27T01:34:45+0000", - "eventKey": "repo:push", - "repository": { - "scmId": "git", - "project": { - "key": "AMUNIZ", - "name": "prj" - }, - "slug": "test-repos", - "links": { - "self": [ - { - "href": "http://localhost:7990/projects/AMUNIZ/repos/test-repos/browse" - } - ] - }, - "public": false, - "owner": { - "username": "amuniz", - "displayName": "Antonio Muniz" - }, - "fullName": "amuniz/test-repos", - "ownerName": "amuniz" - } -} diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/cloud/annotated_tag_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/annotated_tag_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/cloud/annotated_tag_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/annotated_tag_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/cloud/commit_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/commit_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/cloud/commit_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/commit_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/cloud/pullrequest_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/pullrequest_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/cloud/pullrequest_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/pullrequest_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/cloud/pullrequest_rejected.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/pullrequest_rejected.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/cloud/pullrequest_rejected.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/pullrequest_rejected.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/cloud/signed_payload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/signed_payload.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/cloud/signed_payload.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/signed_payload.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/cloud/tag_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/tag_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/cloud/tag_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/tag_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/annotated_tag_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/annotated_tag_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/annotated_tag_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/annotated_tag_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/emptyPayload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/emptyPayload.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/emptyPayload.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/emptyPayload.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/mirrorSynchronized.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/mirrorSynchronized.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/mirrorSynchronized.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/mirrorSynchronized.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/mirrorSynchronized_refLimitExceeded.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/mirrorSynchronized_refLimitExceeded.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/mirrorSynchronized_refLimitExceeded.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/mirrorSynchronized_refLimitExceeded.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/ping_payload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/ping.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/ping_payload.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/ping.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/prOpenFromTagPayload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/prOpenFromTagPayload.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/prOpenFromTagPayload.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/prOpenFromTagPayload.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/pushPayload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/pushPayload.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/pushPayload.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/pushPayload.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/signed_payload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/signed_payload.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/signed_payload.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/signed_payload.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/tag_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/tag_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/tag_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/tag_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/tag_deleted.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/tag_deleted.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/native/tag_deleted.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/tag_deleted.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/branch_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/branch_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/branch_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/branch_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/branch_deleted.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/branch_deleted.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/branch_deleted.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/branch_deleted.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/commit_update.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/commit_update.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/commit_update.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/commit_update.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/commit_update2.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/commit_update2.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/commit_update2.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/commit_update2.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/server/emptyPayload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/emptyPayload.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/server/emptyPayload.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/emptyPayload.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/pullrequest_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/pullrequest_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/pullrequest_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/pullrequest_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/pullrequest_merged.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/pullrequest_merged.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/pullrequest_merged.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/pullrequest_merged.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/pullrequest_updated.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/pullrequest_updated.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/hooks/plugin/pullrequest_updated.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/pullrequest_updated.json From 78adcd1bcdc688e6807b8b29a89f1a226f551a5c Mon Sep 17 00:00:00 2001 From: Nikolas Falco Date: Sun, 6 Jul 2025 23:30:10 +0200 Subject: [PATCH 2/3] [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 --- .../api/webhook/BitbucketWebhook.java | 51 +++++ .../webhook/BitbucketWebhookDescriptor.java | 34 +++ .../BitbucketWebhookProcessor.java} | 8 +- .../BitbucketWebhookProcessorException.java} | 6 +- .../BitbucketSCMSourcePushHookReceiver.java | 22 +- .../endpoint/AbstractBitbucketEndpoint.java | 14 ++ .../endpoint/BitbucketServerEndpoint.java | 32 ++- .../webhook/AbstractBitbucketWebhook.java | 212 ++++++++++++++++++ .../AbstractWebhookProcessor.java} | 27 ++- .../webhook/cloud/AbstractSCMHeadEvent.java | 115 ++++++++++ .../CloudPullRequestWebhookProcessor.java} | 5 +- .../cloud/CloudPushWebhookProcessor.java} | 5 +- .../impl/webhook/cloud/CloudWebhook.java | 59 +++++ .../impl/{hook => webhook/cloud}/PREvent.java | 2 +- .../{hook => webhook/cloud}/PushEvent.java | 2 +- .../plugin}/AbstractSCMHeadEvent.java | 5 +- .../impl/webhook/plugin/PluginPREvent.java | 148 ++++++++++++ .../PluginPullRequestWebhookProcessor.java} | 7 +- .../impl/webhook/plugin/PluginPushEvent.java | 106 +++++++++ .../plugin/PluginPushWebhookProcessor.java} | 9 +- .../impl/webhook/plugin/PluginWebhook.java | 178 +++++++++++++++ .../AbstractNativeServerSCMHeadEvent.java | 2 +- .../server}/ServerHeadEvent.java | 2 +- .../server/ServerPingWebhookProcessor.java} | 7 +- .../ServerPullRequestWebhookProcessor.java} | 8 +- .../server}/ServerPushEvent.java | 4 +- .../server/ServerPushWebhookProcessor.java} | 7 +- .../impl/webhook/server/ServerWebhook.java | 60 +++++ .../client/BitbucketServerAPIClient.java | 3 +- .../AbstractBitbucketEndpoint/config.jelly | 16 +- .../config-detail.jelly | 29 ++- .../config-detail.jelly | 33 ++- .../manage-hooks-detail.jelly | 2 +- .../AbstractBitbucketWebhook/config.jelly | 41 ++++ .../help-credentialsId.html | 7 + .../help-enableHookSignature.html | 4 + .../help-endpointJenkinsRootURL.html | 6 + .../help-hookSignatureCredentialsId.html | 9 + .../help-manageHooks.html | 6 + .../impl/webhook/Messages.properties | 24 ++ .../webhook/plugin/PluginWebhook/config.jelly | 34 +++ .../PluginWebhook/help-credentialsId.html | 7 + .../help-endpointJenkinsRootURL.html | 6 + .../PluginWebhook/help-manageHooks.html | 6 + .../BitbucketEndpointConfigurationTest.java | 14 -- ...itbucketSCMSourcePushHookReceiverTest.java | 12 +- .../AbstractWebhookProcessorTest.java} | 19 +- ...CloudPullRequestWebhookProcessorTest.java} | 22 +- .../CloudPushWebhookProcessorTest.java} | 18 +- ...luginPullRequestWebhookProcessorTest.java} | 28 +-- .../PluginPushWebhookProcessorTest.java} | 24 +- .../ServerPingWebhookProcessorTest.java} | 11 +- ...erverPullRequestWebhookProcessorTest.java} | 12 +- .../ServerPushWebhookProcessorTest.java} | 12 +- ...dpoints.BitbucketEndpointConfiguration.xml | 37 +++ .../cloud/annotated_tag_created.json | 0 .../cloud/commit_created.json | 0 .../cloud/pullrequest_created.json | 0 .../cloud/pullrequest_rejected.json | 0 .../cloud/signed_payload.json | 0 .../{hook => webhook}/cloud/tag_created.json | 0 .../native/annotated_tag_created.json | 0 .../native/emptyPayload.json | 0 .../native/mirrorSynchronized.json | 0 .../mirrorSynchronized_refLimitExceeded.json | 0 .../impl/{hook => webhook}/native/ping.json | 0 .../native/prOpenFromTagPayload.json | 0 .../{hook => webhook}/native/pushPayload.json | 0 .../native/signed_payload.json | 0 .../{hook => webhook}/native/tag_created.json | 0 .../{hook => webhook}/native/tag_deleted.json | 0 .../plugin/branch_created.json | 0 .../plugin/branch_deleted.json | 0 .../plugin/commit_update.json | 0 .../plugin/commit_update2.json | 0 .../plugin/emptyPayload.json | 0 .../plugin/pullrequest_created.json | 0 .../plugin/pullrequest_merged.json | 0 .../plugin/pullrequest_updated.json | 0 79 files changed, 1369 insertions(+), 210 deletions(-) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhook.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookDescriptor.java rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/{hook/BitbucketHookProcessor.java => webhook/BitbucketWebhookProcessor.java} (96%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/{hook/BitbucketHookProcessorException.java => webhook/BitbucketWebhookProcessorException.java} (87%) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook.java rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/AbstractHookProcessor.java => webhook/AbstractWebhookProcessor.java} (81%) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/AbstractSCMHeadEvent.java rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/CloudPullRequestHookProcessor.java => webhook/cloud/CloudPullRequestWebhookProcessor.java} (94%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/CloudPushHookProcessor.java => webhook/cloud/CloudPushWebhookProcessor.java} (94%) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudWebhook.java rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook/cloud}/PREvent.java (98%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook/cloud}/PushEvent.java (98%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook/plugin}/AbstractSCMHeadEvent.java (96%) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPREvent.java rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/PluginPullRequestHookProcessor.java => webhook/plugin/PluginPullRequestWebhookProcessor.java} (90%) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPushEvent.java rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/PluginPushHookProcessor.java => webhook/plugin/PluginPushWebhookProcessor.java} (91%) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook.java rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook/server}/AbstractNativeServerSCMHeadEvent.java (98%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook/server}/ServerHeadEvent.java (98%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/NativeServerPingHookProcessor.java => webhook/server/ServerPingWebhookProcessor.java} (88%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/NativeServerPullRequestHookProcessor.java => webhook/server/ServerPullRequestWebhookProcessor.java} (92%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook/server}/ServerPushEvent.java (98%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/NativeServerPushHookProcessor.java => webhook/server/ServerPushWebhookProcessor.java} (95%) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerWebhook.java create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/config.jelly create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-credentialsId.html create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-enableHookSignature.html create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-endpointJenkinsRootURL.html create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-hookSignatureCredentialsId.html create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-manageHooks.html create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/Messages.properties create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/config.jelly create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/help-credentialsId.html create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/help-endpointJenkinsRootURL.html create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/help-manageHooks.html rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/AbstractHookProcessorTest.java => webhook/AbstractWebhookProcessorTest.java} (90%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/CloudPullRequestHookProcessorTest.java => webhook/CloudPullRequestWebhookProcessorTest.java} (91%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/CloudPushHookProcessorTest.java => webhook/CloudPushWebhookProcessorTest.java} (91%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/PluginPullRequestHookProcessorTest.java => webhook/PluginPullRequestWebhookProcessorTest.java} (89%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/PluginPushHookProcessorTest.java => webhook/PluginPushWebhookProcessorTest.java} (92%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/NativeServerPingHookProcessorTest.java => webhook/ServerPingWebhookProcessorTest.java} (91%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/NativeServerPullRequestHookProcessorTest.java => webhook/ServerPullRequestWebhookProcessorTest.java} (92%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook/NativeServerPushHookProcessorTest.java => webhook/ServerPushWebhookProcessorTest.java} (95%) create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration.xml rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/cloud/annotated_tag_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/cloud/commit_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/cloud/pullrequest_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/cloud/pullrequest_rejected.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/cloud/signed_payload.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/cloud/tag_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/native/annotated_tag_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/native/emptyPayload.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/native/mirrorSynchronized.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/native/mirrorSynchronized_refLimitExceeded.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/native/ping.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/native/prOpenFromTagPayload.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/native/pushPayload.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/native/signed_payload.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/native/tag_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/native/tag_deleted.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/plugin/branch_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/plugin/branch_deleted.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/plugin/commit_update.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/plugin/commit_update2.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/plugin/emptyPayload.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/plugin/pullrequest_created.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/plugin/pullrequest_merged.json (100%) rename src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{hook => webhook}/plugin/pullrequest_updated.json (100%) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhook.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhook.java new file mode 100644 index 000000000..1e1d28358 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhook.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * + * Copyright (c) 2025, Falco Nikolas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.api.webhook; + +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.model.Describable; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.Beta; + +@Restricted(Beta.class) +public interface BitbucketWebhook extends Describable { + + /** + * Name to use to describe the hook implementation. + * + * @return the name to use for the implementation + */ + @CheckForNull + String getDisplayName(); + + /** + * The hook implementation identifier. + * + * @return the unique identifier for this implementation + */ + @NonNull + String getId(); + +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookDescriptor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookDescriptor.java new file mode 100644 index 000000000..a72d44610 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookDescriptor.java @@ -0,0 +1,34 @@ +/* + * The MIT License + * + * Copyright (c) 2017, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.api.webhook; + +import hudson.model.Descriptor; + +/** + * {@link Descriptor} for {@link BitbucketWebhook}s. + * + * @since 936.4.0 + */ +public class BitbucketWebhookDescriptor extends Descriptor { +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookProcessor.java similarity index 96% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessor.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookProcessor.java index 6889920bd..4c187e258 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookProcessor.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.api.hook; +package com.cloudbees.jenkins.plugins.bitbucket.api.webhook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; @@ -47,7 +47,7 @@ * the incoming request as much as possible or the hook will be rejected. */ @Restricted(Beta.class) -public interface BitbucketHookProcessor extends ExtensionPoint { +public interface BitbucketWebhookProcessor extends ExtensionPoint { static final String SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME = "bitbucket.hooks.processor.scanOnEmptyChanges"; /** @@ -104,11 +104,11 @@ default Map buildHookContext(@NonNull HttpServletRequest request * @param payload request * @param endpoint configured for the given * {@link #getServerURL(Map, MultiValuedMap)} - * @throws BitbucketHookProcessorException when signature verification fails + * @throws BitbucketWebhookProcessorException when signature verification fails */ void verifyPayload(@NonNull Map headers, @NonNull String payload, - @NonNull BitbucketEndpoint endpoint) throws BitbucketHookProcessorException; + @NonNull BitbucketEndpoint endpoint) throws BitbucketWebhookProcessorException; /** * Settings that will trigger a re-index of the multibranch diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessorException.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookProcessorException.java similarity index 87% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessorException.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookProcessorException.java index 36978c39b..cd20d4125 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/hook/BitbucketHookProcessorException.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookProcessorException.java @@ -21,15 +21,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.api.hook; +package com.cloudbees.jenkins.plugins.bitbucket.api.webhook; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketException; -public class BitbucketHookProcessorException extends BitbucketException { +public class BitbucketWebhookProcessorException extends BitbucketException { private static final long serialVersionUID = 6682700868741672883L; private final int httpCode; - public BitbucketHookProcessorException(int httpCode, String message) { + public BitbucketWebhookProcessorException(int httpCode, String message) { super(message); this.httpCode = httpCode; } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiver.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiver.java index b5bc8deec..6951441ac 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiver.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiver.java @@ -25,8 +25,8 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; -import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessor; -import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessorException; +import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookProcessor; +import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookProcessorException; import hudson.Extension; import hudson.ExtensionList; import hudson.model.UnprotectedRootAction; @@ -93,7 +93,7 @@ public HttpResponse doNotify(StaplerRequest2 req) throws IOException { try { Map reqHeaders = getHeaders(req); MultiValuedMap reqParameters = getParameters(req); - BitbucketHookProcessor hookProcessor = getHookProcessor(reqHeaders, reqParameters); + BitbucketWebhookProcessor hookProcessor = getHookProcessor(reqHeaders, reqParameters); String body = IOUtils.toString(req.getInputStream(), StandardCharsets.UTF_8); if (StringUtils.isEmpty(body)) { @@ -120,28 +120,28 @@ public HttpResponse doNotify(StaplerRequest2 req) throws IOException { String eventType = hookProcessor.getEventType(Collections.unmodifiableMap(reqHeaders), MultiMapUtils.unmodifiableMultiValuedMap(reqParameters)); hookProcessor.process(eventType, body, context, endpoint); - } catch(BitbucketHookProcessorException e) { + } catch(BitbucketWebhookProcessorException e) { return HttpResponses.error(e.getHttpCode(), e.getMessage()); } return HttpResponses.ok(); } - private BitbucketHookProcessor getHookProcessor(Map reqHeaders, + private BitbucketWebhookProcessor getHookProcessor(Map reqHeaders, MultiValuedMap reqParameters) { - BitbucketHookProcessor hookProcessor; + BitbucketWebhookProcessor hookProcessor; - List matchingProcessors = getHookProcessors() + List matchingProcessors = getHookProcessors() .filter(processor -> processor.canHandle(Collections.unmodifiableMap(reqHeaders), MultiMapUtils.unmodifiableMultiValuedMap(reqParameters))) .toList(); if (matchingProcessors.isEmpty()) { logger.warning(() -> "No processor found for the incoming Bitbucket hook. Skipping."); - 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"); + 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"); } else if (matchingProcessors.size() > 1) { String processors = StringUtils.joinWith("\n- ", matchingProcessors.stream() .map(p -> p.getClass().getName()) .toList()); logger.severe(() -> "More processors found that handle the incoming Bitbucket hook:\n" + processors); - throw new BitbucketHookProcessorException(HttpServletResponse.SC_CONFLICT, "More processors found that handle the incoming Bitbucket hook."); + throw new BitbucketWebhookProcessorException(HttpServletResponse.SC_CONFLICT, "More processors found that handle the incoming Bitbucket hook."); } else { hookProcessor = matchingProcessors.get(0); logger.fine(() -> "Hook processor " + hookProcessor.getClass().getName() + " found."); @@ -149,8 +149,8 @@ private BitbucketHookProcessor getHookProcessor(Map reqHeaders, return hookProcessor; } - /*test*/ Stream getHookProcessors() { - return ExtensionList.lookup(BitbucketHookProcessor.class).stream(); + /*test*/ Stream getHookProcessors() { + return ExtensionList.lookup(BitbucketWebhookProcessor.class).stream(); } private MultiValuedMap getParameters(StaplerRequest2 req) { diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint.java index ff02b7c55..fdb5b4652 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint.java @@ -26,6 +26,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointDescriptor; +import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhook; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils; import com.cloudbees.plugins.credentials.common.StandardCredentials; @@ -33,6 +34,7 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Util; +import java.util.Objects; import jenkins.authentication.tokens.api.AuthenticationTokens; import jenkins.model.Jenkins; import org.apache.commons.lang3.StringUtils; @@ -60,6 +62,9 @@ public abstract class AbstractBitbucketEndpoint implements BitbucketEndpoint { @CheckForNull private String credentialsId; + @NonNull + private BitbucketWebhook webhook; + /** * {@code true} if and only if Jenkins have to verify the signature of all incoming hooks. */ @@ -267,4 +272,13 @@ public BitbucketAuthenticator authenticator() { public BitbucketEndpointDescriptor getDescriptor() { return (BitbucketEndpointDescriptor) Jenkins.get().getDescriptorOrDie(getClass()); } + + public @NonNull BitbucketWebhook getWebhook() { + return webhook; + } + + @DataBoundSetter + public void setWebhook(@NonNull BitbucketWebhook webhook) { + this.webhook = Objects.requireNonNull(webhook); + } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint.java index f8e5b809c..6050497a1 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint.java @@ -50,8 +50,6 @@ import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.interceptor.RequirePOST; -import static java.util.Objects.requireNonNull; - /** * Represents a Bitbucket Server instance. * @@ -74,9 +72,9 @@ public class BitbucketServerEndpoint extends AbstractBitbucketEndpoint { @NonNull public static BitbucketServerWebhookImplementation findWebhookImplementation(String serverURL) { - return BitbucketEndpointProvider.lookupEndpoint(serverURL, BitbucketServerEndpoint.class) + return /*BitbucketEndpointProvider.lookupEndpoint(serverURL, BitbucketServerEndpoint.class) .map(BitbucketServerEndpoint::getWebhookImplementation) - .orElse(BitbucketServerWebhookImplementation.NATIVE); + .orElse(BitbucketServerWebhookImplementation.NATIVE);*/ null; } @NonNull @@ -99,8 +97,8 @@ public static BitbucketServerVersion findServerVersion(String serverURL) { @NonNull private final String serverUrl; - @NonNull - private BitbucketServerWebhookImplementation webhookImplementation = BitbucketServerWebhookImplementation.NATIVE; +// @NonNull +// private BitbucketServerWebhookImplementation webhookImplementation = BitbucketServerWebhookImplementation.NATIVE; /** * The server version for this endpoint. @@ -243,9 +241,9 @@ public String getRepositoryURL(@NonNull String repoOwner, @NonNull String reposi @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "Only non-null after we set them here!") private Object readResolve() { - if (webhookImplementation == null) { - webhookImplementation = BitbucketServerWebhookImplementation.NATIVE; - } +// if (webhookImplementation == null) { +// webhookImplementation = BitbucketServerWebhookImplementation.NATIVE; +// } if (getBitbucketJenkinsRootUrl() != null) { setBitbucketJenkinsRootUrl(getBitbucketJenkinsRootUrl()); } @@ -256,15 +254,15 @@ private Object readResolve() { return this; } - @NonNull - public BitbucketServerWebhookImplementation getWebhookImplementation() { - return webhookImplementation; - } +// @NonNull +// public BitbucketServerWebhookImplementation getWebhookImplementation() { +// return webhookImplementation; +// } - @DataBoundSetter - public void setWebhookImplementation(@NonNull BitbucketServerWebhookImplementation webhookImplementation) { - this.webhookImplementation = requireNonNull(webhookImplementation); - } +// @DataBoundSetter +// public void setWebhookImplementation(@NonNull BitbucketServerWebhookImplementation webhookImplementation) { +// this.webhookImplementation = requireNonNull(webhookImplementation); +// } /** * Our descriptor. diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook.java new file mode 100644 index 000000000..b52491346 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook.java @@ -0,0 +1,212 @@ +/* + * The MIT License + * + * Copyright (c) 2025, Falco Nikolas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; + +import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhook; +import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookDescriptor; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils; +import com.cloudbees.plugins.credentials.CredentialsMatchers; +import com.cloudbees.plugins.credentials.common.StandardCredentials; +import com.cloudbees.plugins.credentials.common.StandardListBoxModel; +import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; +import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import hudson.Util; +import hudson.model.Descriptor; +import hudson.security.ACL; +import hudson.util.FormValidation; +import hudson.util.ListBoxModel; +import java.net.MalformedURLException; +import java.net.URL; +import jenkins.model.Jenkins; +import org.apache.commons.lang3.StringUtils; +import org.jenkinsci.plugins.plaincredentials.StringCredentials; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; + +import static hudson.Util.fixEmptyAndTrim; + +@Restricted(NoExternalUse.class) +public abstract class AbstractBitbucketWebhook implements BitbucketWebhook { + + /** + * {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point. + */ + private boolean manageHooks; + + /** + * The {@link StandardCredentials#getId()} of the credentials to use for auto-management of hooks. + */ + @CheckForNull + private String credentialsId; + + /** + * {@code true} if and only if Jenkins have to verify the signature of all incoming hooks. + */ + private final boolean enableHookSignature; + + /** + * The {@link StringCredentials#getId()} of the credentials to use to verify the signature of hooks. + */ + @CheckForNull + private final String hookSignatureCredentialsId; + + /** + * Jenkins Server Root URL to be used by that Bitbucket endpoint. + * The global setting from Jenkins.get().getRootUrl() + * will be used if this field is null or equals an empty string. + * This variable is bound to the UI, so an empty value is saved + * and returned by getter as such. + */ + private String endpointJenkinsRootURL; + + protected AbstractBitbucketWebhook(boolean manageHooks, @CheckForNull String credentialsId, + boolean enableHookSignature, @CheckForNull String hookSignatureCredentialsId) { + this.manageHooks = manageHooks && StringUtils.isNotBlank(credentialsId); + this.credentialsId = manageHooks ? fixEmptyAndTrim(credentialsId) : null; + this.enableHookSignature = enableHookSignature && StringUtils.isNotBlank(hookSignatureCredentialsId); + this.hookSignatureCredentialsId = enableHookSignature ? fixEmptyAndTrim(hookSignatureCredentialsId) : null; + } + + /** + * Returns {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point. + * + * @return {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point. + */ + public final boolean isManageHooks() { + return manageHooks; + } + + /** + * Returns the {@link StandardUsernamePasswordCredentials#getId()} of the credentials to use for auto-management + * of hooks. + * + * @return the {@link StandardUsernamePasswordCredentials#getId()} of the credentials to use for auto-management + * of hooks. + */ + @CheckForNull + public final String getCredentialsId() { + return credentialsId; + } + + @CheckForNull + public String getHookSignatureCredentialsId() { + return hookSignatureCredentialsId; + } + + public boolean isEnableHookSignature() { + return enableHookSignature; + } + + public String getEndpointJenkinsRootURL() { + return endpointJenkinsRootURL; + } + + @DataBoundSetter + public void setEndpointJenkinsRootURL(@CheckForNull String endpointJenkinsRootURL) { + this.endpointJenkinsRootURL = fixEmptyAndTrim(endpointJenkinsRootURL); + } + + @Override + public String getDisplayName() { + return Messages.ServerWebhookImplementation_displayName(); + } + + @Override + public String getId() { + return "SERVER"; + } + + @SuppressWarnings("unchecked") + @Override + public Descriptor getDescriptor() { + return Jenkins.get().getDescriptorOrDie(getClass()); + } + + + public abstract static class AbstractBitbucketWebhookDescriptorImpl extends BitbucketWebhookDescriptor { + + /** + * Stapler form completion. + * + * @param credentialsId selected credentials. + * @param serverURL the server URL. + * @return the available credentials. + */ + @RequirePOST + public ListBoxModel doFillCredentialsIdItems(@QueryParameter(fixEmpty = true) String credentialsId, + @QueryParameter(value = "serverUrl", fixEmpty = true) String serverURL) { + Jenkins jenkins = checkPermission(); + return BitbucketCredentialsUtils.listCredentials(jenkins, serverURL, credentialsId); + } + + /** + * Stapler form completion. + * + * @param hookSignatureCredentialsId selected hook signature credentials. + * @param serverURL the server URL. + * @return the available credentials. + */ + @RequirePOST + public ListBoxModel doFillHookSignatureCredentialsIdItems(@QueryParameter(fixEmpty = true) String hookSignatureCredentialsId, + @QueryParameter(value = "serverUrl", fixEmpty = true) String serverURL) { + Jenkins jenkins = checkPermission(); + StandardListBoxModel result = new StandardListBoxModel(); + result.includeMatchingAs(ACL.SYSTEM2, + jenkins, + StringCredentials.class, + URIRequirementBuilder.fromUri(serverURL).build(), + CredentialsMatchers.always()); + if (hookSignatureCredentialsId != null) { + result.includeCurrentValue(hookSignatureCredentialsId); + } + return result; + } + + @Restricted(NoExternalUse.class) + @RequirePOST + public static FormValidation doCheckEndpointJenkinsRootURL(@QueryParameter String value) { + checkPermission(); + String url = Util.fixEmptyAndTrim(value); + if (url == null) { + return FormValidation.ok(); + } + try { + new URL(url); + } catch (MalformedURLException e) { + return FormValidation.error("Invalid URL: " + e.getMessage()); + } + return FormValidation.ok(); + } + + private static Jenkins checkPermission() { + Jenkins jenkins = Jenkins.get(); + jenkins.checkPermission(Jenkins.MANAGE); + return jenkins; + } + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhookProcessor.java similarity index 81% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessor.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhookProcessor.java index a463785ef..02886939d 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhookProcessor.java @@ -21,12 +21,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; -import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessor; -import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessorException; +import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookProcessor; +import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookProcessorException; import com.cloudbees.jenkins.plugins.bitbucket.hooks.BitbucketType; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; import edu.umd.cs.findbugs.annotations.CheckForNull; @@ -52,6 +52,8 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.jenkinsci.plugins.plaincredentials.StringCredentials; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; import static org.apache.commons.lang.StringUtils.trimToNull; @@ -61,7 +63,8 @@ * Add new hook processors by extending this class and implement {@link #process(HookEventType, String, BitbucketType, String)}, * extract details from the hook payload and then fire an {@link jenkins.scm.api.SCMEvent} to dispatch it to the SCM API. */ -abstract class AbstractHookProcessor implements BitbucketHookProcessor { +@Restricted(NoExternalUse.class) +public abstract class AbstractWebhookProcessor implements BitbucketWebhookProcessor { protected static final String REQUEST_ID_CLOUD_HEADER = "X-Request-UUID"; protected static final String REQUEST_ID_SERVER_HEADER = "X-Request-Id"; @@ -69,7 +72,7 @@ abstract class AbstractHookProcessor implements BitbucketHookProcessor { protected static final String EVENT_TYPE_HEADER = "X-Event-Key"; protected static final String SERVER_URL_PARAMETER = "server_url"; - private static final Logger LOGGER = Logger.getLogger(AbstractHookProcessor.class.getName()); + private static final Logger LOGGER = Logger.getLogger(AbstractWebhookProcessor.class.getName()); /** * To be called by implementations once the owner and the repository have been extracted from the payload. @@ -110,7 +113,7 @@ public String getServerURL(@NonNull Map headers, @NonNull MultiV .findFirst() .orElse(null); if (StringUtils.isBlank(serverURL)) { - throw new BitbucketHookProcessorException(HttpServletResponse.SC_BAD_REQUEST, SERVER_URL_PARAMETER + " query parameter not found or empty. 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"); + throw new BitbucketWebhookProcessorException(HttpServletResponse.SC_BAD_REQUEST, SERVER_URL_PARAMETER + " query parameter not found or empty. 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"); } return serverURL; } @@ -135,7 +138,7 @@ public void verifyPayload(@NonNull Map headers, @NonNull String String bitbucketSignature = trimToNull(StringUtils.substringAfter(signatureHeader, "=")); HmacAlgorithms algorithm = getAlgorithm(bitbucketAlgorithm); if (algorithm == null) { - throw new BitbucketHookProcessorException(HttpServletResponse.SC_FORBIDDEN, "Signature " + bitbucketAlgorithm + " not supported"); + throw new BitbucketWebhookProcessorException(HttpServletResponse.SC_FORBIDDEN, "Signature " + bitbucketAlgorithm + " not supported"); } HmacUtils util; try { @@ -143,22 +146,22 @@ public void verifyPayload(@NonNull Map headers, @NonNull String util = new HmacUtils(algorithm, key.getBytes(StandardCharsets.UTF_8)); byte[] digest = util.hmac(body); if (!MessageDigest.isEqual(Hex.decodeHex(bitbucketSignature), digest)) { - throw new BitbucketHookProcessorException(HttpServletResponse.SC_FORBIDDEN, "Signature verification failed"); + throw new BitbucketWebhookProcessorException(HttpServletResponse.SC_FORBIDDEN, "Signature verification failed"); } } catch (IllegalArgumentException e) { - throw new BitbucketHookProcessorException(HttpServletResponse.SC_BAD_REQUEST, "Signature method not supported: " + algorithm); + throw new BitbucketWebhookProcessorException(HttpServletResponse.SC_BAD_REQUEST, "Signature method not supported: " + algorithm); } catch (DecoderException e) { - throw new BitbucketHookProcessorException(HttpServletResponse.SC_BAD_REQUEST, "Hex signature can not be decoded: " + bitbucketSignature); + throw new BitbucketWebhookProcessorException(HttpServletResponse.SC_BAD_REQUEST, "Hex signature can not be decoded: " + bitbucketSignature); } } else { String hookId = headers.get("X-Hook-UUID"); String requestId = ObjectUtils.firstNonNull(headers.get("X-Request-UUID"), headers.get("X-Request-Id")); String hookSignatureCredentialsId = endpoint.getHookSignatureCredentialsId(); LOGGER.log(Level.WARNING, "No credentials {0} found to verify the signature of incoming webhook {1} request {2}", new Object[] { hookSignatureCredentialsId, hookId, requestId }); - throw new BitbucketHookProcessorException(HttpServletResponse.SC_FORBIDDEN, "No credentials " + hookSignatureCredentialsId + " found in Jenkins to verify the signature"); + throw new BitbucketWebhookProcessorException(HttpServletResponse.SC_FORBIDDEN, "No credentials " + hookSignatureCredentialsId + " found in Jenkins to verify the signature"); } } else { - throw new BitbucketHookProcessorException(HttpServletResponse.SC_FORBIDDEN, "Payload has not be signed, configure the webHook secret in Bitbucket as documented at https://github.com/jenkinsci/bitbucket-branch-source-plugin/blob/master/docs/USER_GUIDE.adoc#webhooks-registering"); + throw new BitbucketWebhookProcessorException(HttpServletResponse.SC_FORBIDDEN, "Payload has not be signed, configure the webHook secret in Bitbucket as documented at https://github.com/jenkinsci/bitbucket-branch-source-plugin/blob/master/docs/USER_GUIDE.adoc#webhooks-registering"); } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/AbstractSCMHeadEvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/AbstractSCMHeadEvent.java new file mode 100644 index 000000000..fa6cbbe8d --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/AbstractSCMHeadEvent.java @@ -0,0 +1,115 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud; + +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketHref; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; +import com.cloudbees.jenkins.plugins.bitbucket.client.events.BitbucketCloudPullRequestEvent; +import com.cloudbees.jenkins.plugins.bitbucket.client.events.BitbucketCloudPushEvent; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; +import com.cloudbees.jenkins.plugins.bitbucket.server.events.BitbucketServerPullRequestEvent; +import com.cloudbees.jenkins.plugins.bitbucket.server.events.BitbucketServerPushEvent; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.scm.SCM; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Map; +import jenkins.scm.api.SCMHeadEvent; +import jenkins.scm.api.SCMNavigator; +import org.apache.commons.lang3.StringUtils; + +public abstract class AbstractSCMHeadEvent

extends SCMHeadEvent

{ + + AbstractSCMHeadEvent(Type type, P payload, String origin) { + super(type, payload, origin); + } + + @Override + public boolean isMatch(@NonNull SCMNavigator navigator) { + if (!(navigator instanceof BitbucketSCMNavigator)) { + return false; + } + BitbucketSCMNavigator bbNav = (BitbucketSCMNavigator) navigator; + if (!isProjectKeyMatch(bbNav.getProjectKey())) { + return false; + } + + if (!isServerURLMatch(bbNav.getServerUrl())) { + return false; + } + return StringUtils.equalsIgnoreCase(bbNav.getRepoOwner(), getRepository().getOwnerName()); + } + + protected abstract BitbucketRepository getRepository(); + + private boolean isProjectKeyMatch(String projectKey) { + if (StringUtils.isBlank(projectKey)) { + return true; + } + BitbucketRepository repository = getRepository(); + if (repository.getProject() != null) { + return projectKey.equals(repository.getProject().getKey()); + } + return true; + } + + protected boolean isServerURLMatch(String serverURL) { + if (serverURL == null || BitbucketApiUtils.isCloud(serverURL)) { + // this is a Bitbucket cloud navigator + if (getPayload() instanceof BitbucketServerPullRequestEvent || getPayload() instanceof BitbucketServerPushEvent) { + return false; + } + } else { + // this is a Bitbucket server navigator + if (getPayload() instanceof BitbucketCloudPullRequestEvent || getPayload() instanceof BitbucketCloudPushEvent) { + return false; + } + Map> links = getRepository().getLinks(); + if (links != null && links.containsKey("self")) { + boolean matches = false; + for (BitbucketHref link: links.get("self")) { + try { + URI navUri = new URI(serverURL); + URI evtUri = new URI(link.getHref()); + if (navUri.getHost().equalsIgnoreCase(evtUri.getHost())) { + matches = true; + break; + } + } catch (URISyntaxException e) { + // ignore + } + } + return matches; + } + } + return true; + } + + @Override + public boolean isMatch(@NonNull SCM scm) { + return false; + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPullRequestHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPullRequestWebhookProcessor.java similarity index 94% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPullRequestHookProcessor.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPullRequestWebhookProcessor.java index ff3c96674..30cf6de1f 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPullRequestHookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPullRequestWebhookProcessor.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestEvent; @@ -29,6 +29,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudWebhookPayload; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractWebhookProcessor; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.RestrictedSince; @@ -42,7 +43,7 @@ @Extension @Restricted(NoExternalUse.class) @RestrictedSince("933.3.0") -public class CloudPullRequestHookProcessor extends AbstractHookProcessor { +public class CloudPullRequestWebhookProcessor extends AbstractWebhookProcessor { private static final List supportedEvents = List.of( HookEventType.PULL_REQUEST_CREATED.getKey(), // needed to create job diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPushHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPushWebhookProcessor.java similarity index 94% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPushHookProcessor.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPushWebhookProcessor.java index 9c7437cc0..14cd0bed8 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPushHookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPushWebhookProcessor.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPushEvent; @@ -29,6 +29,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudWebhookPayload; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractWebhookProcessor; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.RestrictedSince; @@ -42,7 +43,7 @@ @Extension @Restricted(NoExternalUse.class) @RestrictedSince("933.3.0") -public class CloudPushHookProcessor extends AbstractHookProcessor { +public class CloudPushWebhookProcessor extends AbstractWebhookProcessor { private static final List supportedEvents = List.of( HookEventType.PUSH.getKey()); diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudWebhook.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudWebhook.java new file mode 100644 index 000000000..fa9a9a644 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudWebhook.java @@ -0,0 +1,59 @@ +/* + * The MIT License + * + * Copyright (c) 2025, Falco Nikolas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud; + +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractBitbucketWebhook; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.Messages; +import hudson.Extension; +import org.jenkinsci.Symbol; +import org.kohsuke.stapler.DataBoundConstructor; + +public class CloudWebhook extends AbstractBitbucketWebhook { + + @DataBoundConstructor + public CloudWebhook(boolean manageHooks, String credentialsId, boolean enableHookSignature, String hookSignatureCredentialsId) { + super(manageHooks, credentialsId, enableHookSignature, hookSignatureCredentialsId); + } + + @Override + public String getDisplayName() { + return Messages.CloudWebhookImplementation_displayName(); + } + + @Override + public String getId() { + return "CLOUD"; + } + + @Symbol("cloudWebhook") + @Extension + public static class DescriptorImpl extends AbstractBitbucketWebhookDescriptorImpl { + + @Override + public String getDisplayName() { + return "Native Cloud"; + } + + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PREvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/PREvent.java similarity index 98% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PREvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/PREvent.java index 05d23ff54..78845050e 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PREvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/PREvent.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSourceContext; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PushEvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/PushEvent.java similarity index 98% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PushEvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/PushEvent.java index 00f2d96b4..e6c29c857 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PushEvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/PushEvent.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketTagSCMHead; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractSCMHeadEvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/AbstractSCMHeadEvent.java similarity index 96% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractSCMHeadEvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/AbstractSCMHeadEvent.java index 177e14868..3bf0f5c44 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractSCMHeadEvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/AbstractSCMHeadEvent.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketHref; @@ -41,9 +41,10 @@ import jenkins.scm.api.SCMNavigator; import org.apache.commons.lang3.StringUtils; +@Deprecated(since = "937.0.0") abstract class AbstractSCMHeadEvent

extends SCMHeadEvent

{ - protected AbstractSCMHeadEvent(Type type, P payload, String origin) { + AbstractSCMHeadEvent(Type type, P payload, String origin) { super(type, payload, origin); } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPREvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPREvent.java new file mode 100644 index 000000000..16d60702a --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPREvent.java @@ -0,0 +1,148 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin; + +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSourceContext; +import com.cloudbees.jenkins.plugins.bitbucket.PullRequestSCMHead; +import com.cloudbees.jenkins.plugins.bitbucket.PullRequestSCMRevision; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestEvent; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HasPullRequests; +import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import jenkins.plugins.git.AbstractGitSCMSource; +import jenkins.scm.api.SCMHead; +import jenkins.scm.api.SCMHeadObserver; +import jenkins.scm.api.SCMHeadOrigin; +import jenkins.scm.api.SCMRevision; +import jenkins.scm.api.SCMSource; +import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; + +import static com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType.PULL_REQUEST_DECLINED; +import static com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType.PULL_REQUEST_MERGED; + +@Deprecated(since = "937.0.0") +final class PluginPREvent extends AbstractSCMHeadEvent implements HasPullRequests { + private final HookEventType hookEvent; + + PluginPREvent(Type type, BitbucketPullRequestEvent payload, + String origin, + HookEventType hookEvent) { + super(type, payload, origin); + this.hookEvent = hookEvent; + } + + @Override + protected BitbucketRepository getRepository() { + return getPayload().getRepository(); + } + + @NonNull + @Override + public String getSourceName() { + return getRepository().getRepositoryName(); + } + + @NonNull + @Override + @SuppressFBWarnings(value = "SBSC_USE_STRINGBUFFER_CONCATENATION", justification = "false positive, the scope of branchName variable is inside the for cycle, no string contatenation happens into a loop") + public Map heads(@NonNull SCMSource source) { + if (!(source instanceof BitbucketSCMSource)) { + return Collections.emptyMap(); + } + BitbucketSCMSource src = (BitbucketSCMSource) source; + if (!isServerURLMatch(src.getServerUrl())) { + return Collections.emptyMap(); + } + BitbucketRepository repository = getRepository(); + if (!src.getRepoOwner().equalsIgnoreCase(repository.getOwnerName())) { + return Collections.emptyMap(); + } + if (!src.getRepository().equalsIgnoreCase(repository.getRepositoryName())) { + return Collections.emptyMap(); + } + + BitbucketSCMSourceContext ctx = new BitbucketSCMSourceContext(null, SCMHeadObserver.none()) + .withTraits(src.getTraits()); + if (!ctx.wantPRs()) { + // doesn't want PRs, let the push event handle origin branches + return Collections.emptyMap(); + } + BitbucketPullRequest pull = getPayload().getPullRequest(); + String pullRepoOwner = pull.getSource().getRepository().getOwnerName(); + String pullRepository = pull.getSource().getRepository().getRepositoryName(); + SCMHeadOrigin headOrigin = src.originOf(pullRepoOwner, pullRepository); + Set strategies = + headOrigin == SCMHeadOrigin.DEFAULT + ? ctx.originPRStrategies() + : ctx.forkPRStrategies(); + Map result = new HashMap<>(strategies.size()); + for (ChangeRequestCheckoutStrategy strategy : strategies) { + String branchName = "PR-" + pull.getId(); + if (strategies.size() > 1) { + branchName = branchName + "-" + strategy.name().toLowerCase(Locale.ENGLISH); + } + String originalBranchName = pull.getSource().getBranch().getName(); + PullRequestSCMHead head = new PullRequestSCMHead( + branchName, + pullRepoOwner, + pullRepository, + originalBranchName, + pull, + headOrigin, + strategy + ); + if (hookEvent == PULL_REQUEST_DECLINED || hookEvent == PULL_REQUEST_MERGED) { + // special case for repo being deleted + result.put(head, null); + } else { + String targetHash = pull.getDestination().getCommit().getHash(); + String pullHash = pull.getSource().getCommit().getHash(); + + SCMRevision revision = new PullRequestSCMRevision(head, + new AbstractGitSCMSource.SCMRevisionImpl(head.getTarget(), targetHash), + new AbstractGitSCMSource.SCMRevisionImpl(head, pullHash) + ); + result.put(head, revision); + } + } + return result; + } + + @Override + public Iterable getPullRequests(BitbucketSCMSource src) throws InterruptedException { + if (hookEvent == PULL_REQUEST_DECLINED || hookEvent == PULL_REQUEST_MERGED) { + return Collections.emptyList(); + } + return Collections.singleton(getPayload().getPullRequest()); + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPullRequestWebhookProcessor.java similarity index 90% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessor.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPullRequestWebhookProcessor.java index 8d19de9c6..2d8038a5c 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPullRequestWebhookProcessor.java @@ -21,12 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestEvent; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractWebhookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerWebhookPayload; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -42,7 +43,7 @@ @Deprecated(since = "937.0.0") @Restricted(NoExternalUse.class) @RestrictedSince("933.3.0") -public class PluginPullRequestHookProcessor extends AbstractHookProcessor { +public class PluginPullRequestWebhookProcessor extends AbstractWebhookProcessor { private static final List supportedEvents = List.of( HookEventType.PULL_REQUEST_CREATED.getKey(), // needed to create job @@ -77,7 +78,7 @@ public void process(@NonNull String hookEventType, @NonNull String payload, @Non break; } // assume updated as a catch-all type - notifyEvent(new PREvent(eventType, pull, getOrigin(context), hookEvent), BitbucketSCMSource.getEventDelaySeconds()); + notifyEvent(new PluginPREvent(eventType, pull, getOrigin(context), hookEvent), BitbucketSCMSource.getEventDelaySeconds()); } } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPushEvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPushEvent.java new file mode 100644 index 000000000..7d66195a7 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPushEvent.java @@ -0,0 +1,106 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin; + +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketTagSCMHead; +import com.cloudbees.jenkins.plugins.bitbucket.BranchSCMHead; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPushEvent; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPushEvent.Reference; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPushEvent.Target; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import jenkins.plugins.git.AbstractGitSCMSource; +import jenkins.scm.api.SCMHead; +import jenkins.scm.api.SCMRevision; +import jenkins.scm.api.SCMSource; +import org.apache.commons.lang3.StringUtils; + +@Deprecated(since = "937.0.0") +final class PluginPushEvent extends AbstractSCMHeadEvent { + + PluginPushEvent(Type type, BitbucketPushEvent payload, String origin) { + super(type, payload, origin); + } + + @NonNull + @Override + public String getSourceName() { + return getRepository().getRepositoryName(); + } + + @NonNull + @Override + public Map heads(@NonNull SCMSource source) { + if (!(source instanceof BitbucketSCMSource)) { + return Collections.emptyMap(); + } + BitbucketSCMSource src = (BitbucketSCMSource) source; + if (!isServerURLMatch(src.getServerUrl())) { + return Collections.emptyMap(); + } + if (!StringUtils.equalsIgnoreCase(src.getRepoOwner(), getPayload().getRepository().getOwnerName())) { + return Collections.emptyMap(); + } + if (!src.getRepository().equalsIgnoreCase(getPayload().getRepository().getRepositoryName())) { + return Collections.emptyMap(); + } + + Map result = new HashMap<>(); + for (BitbucketPushEvent.Change change: getPayload().getChanges()) { + if (change.isClosed()) { + result.put(new BranchSCMHead(change.getOld().getName()), null); + } else { + // created is true + Reference newChange = change.getNew(); + Target target = newChange.getTarget(); + + SCMHead head = null; + String eventType = newChange.getType(); + if ("tag".equals(eventType)) { + // for BB Cloud date is valued only in case of annotated tag + Date tagDate = newChange.getDate() != null ? newChange.getDate() : target.getDate(); + if (tagDate == null) { + // fall back to the jenkins time when the request is processed + tagDate = new Date(); + } + head = new BitbucketTagSCMHead(newChange.getName(), tagDate.getTime()); + } else { + head = new BranchSCMHead(newChange.getName()); + } + result.put(head, new AbstractGitSCMSource.SCMRevisionImpl(head, target.getHash())); + } + } + return result; + } + + @Override + protected BitbucketRepository getRepository() { + return getPayload().getRepository(); + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPushWebhookProcessor.java similarity index 91% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessor.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPushWebhookProcessor.java index 57d5c665e..d99a79928 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPushWebhookProcessor.java @@ -21,12 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPushEvent; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractWebhookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerWebhookPayload; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -44,9 +45,9 @@ @Deprecated(since = "937.0.0") @Restricted(NoExternalUse.class) @RestrictedSince("933.3.0") -public class PluginPushHookProcessor extends AbstractHookProcessor { +public class PluginPushWebhookProcessor extends AbstractWebhookProcessor { - private static final Logger logger = Logger.getLogger(PluginPushHookProcessor.class.getName()); + private static final Logger logger = Logger.getLogger(PluginPushWebhookProcessor.class.getName()); private static final List supportedEvents = List.of( HookEventType.PUSH.getKey(), HookEventType.PULL_REQUEST_UPDATED.getKey()); @@ -85,7 +86,7 @@ public void process(@NonNull String eventType, @NonNull String payload, @NonNull type = SCMEvent.Type.UPDATED; } } - notifyEvent(new PushEvent(type, push, getOrigin(context)), BitbucketSCMSource.getEventDelaySeconds()); + notifyEvent(new PluginPushEvent(type, push, getOrigin(context)), BitbucketSCMSource.getEventDelaySeconds()); } } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook.java new file mode 100644 index 000000000..9bfca9119 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook.java @@ -0,0 +1,178 @@ +/* + * The MIT License + * + * Copyright (c) 2025, Falco Nikolas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin; + +import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhook; +import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookDescriptor; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.Messages; +import com.cloudbees.plugins.credentials.common.StandardCredentials; +import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import hudson.Extension; +import hudson.Util; +import hudson.model.Descriptor; +import hudson.util.FormValidation; +import hudson.util.ListBoxModel; +import java.net.MalformedURLException; +import java.net.URL; +import jenkins.model.Jenkins; +import org.apache.commons.lang3.StringUtils; +import org.jenkinsci.Symbol; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; + +import static hudson.Util.fixEmptyAndTrim; + +@Deprecated(since = "937.0.0") +// https://help.moveworkforward.com/BPW/atlassian-bitbucket-post-webhook-api +// https://help.moveworkforward.com/BPW/how-to-get-configurations-using-post-webhooks-for- +public class PluginWebhook implements BitbucketWebhook { + + /** + * {@code true} if and only if Jenkins is supposed to auto-manage hooks for + * this end-point. + */ + private boolean manageHooks; + + /** + * The {@link StandardCredentials#getId()} of the credentials to use for + * auto-management of hooks. + */ + @CheckForNull + private String credentialsId; + + /** + * Jenkins Server Root URL to be used by that Bitbucket endpoint. + * The global setting from Jenkins.get().getRootUrl() + * will be used if this field is null or equals an empty string. + * This variable is bound to the UI, so an empty value is saved + * and returned by getter as such. + */ + private String endpointJenkinsRootURL; + + @DataBoundConstructor + public PluginWebhook(boolean manageHooks, @CheckForNull String credentialsId) { + this.manageHooks = manageHooks && StringUtils.isNotBlank(credentialsId); + this.credentialsId = manageHooks ? fixEmptyAndTrim(credentialsId) : null; + } + + /** + * Returns {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point. + * + * @return {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point. + */ + public final boolean isManageHooks() { + return manageHooks; + } + + /** + * Returns the {@link StandardUsernamePasswordCredentials#getId()} of the credentials to use for auto-management + * of hooks. + * + * @return the {@link StandardUsernamePasswordCredentials#getId()} of the credentials to use for auto-management + * of hooks. + */ + @CheckForNull + public final String getCredentialsId() { + return credentialsId; + } + + public String getEndpointJenkinsRootURL() { + return endpointJenkinsRootURL; + } + + @DataBoundSetter + public void setEndpointJenkinsRootURL(@CheckForNull String endpointJenkinsRootURL) { + this.endpointJenkinsRootURL = fixEmptyAndTrim(endpointJenkinsRootURL); + } + + @Override + public String getDisplayName() { + return Messages.ServerWebhookImplementation_displayName(); + } + + @Override + public String getId() { + return "PLUGIN"; + } + + @SuppressWarnings("unchecked") + @Override + public Descriptor getDescriptor() { + return Jenkins.get().getDescriptorOrDie(getClass()); + } + + @Symbol("pluginWebhook") + @Extension + public static class DescriptorImpl extends BitbucketWebhookDescriptor { + + @Override + public String getDisplayName() { + return "Post Webhooks for Bitbucket"; + } + + /** + * Stapler form completion. + * + * @param credentialsId selected credentials. + * @param serverURL the server URL. + * @return the available credentials. + */ + @RequirePOST + public ListBoxModel doFillCredentialsIdItems(@QueryParameter(fixEmpty = true) String credentialsId, + @QueryParameter(value = "serverUrl", fixEmpty = true) String serverURL) { + Jenkins jenkins = checkPermission(); + return BitbucketCredentialsUtils.listCredentials(jenkins, serverURL, credentialsId); + } + + @Restricted(NoExternalUse.class) + @RequirePOST + public static FormValidation doCheckEndpointJenkinsRootURL(@QueryParameter String value) { + checkPermission(); + String url = Util.fixEmptyAndTrim(value); + if (url == null) { + return FormValidation.ok(); + } + try { + new URL(url); + } catch (MalformedURLException e) { + return FormValidation.error("Invalid URL: " + e.getMessage()); + } + return FormValidation.ok(); + } + + private static Jenkins checkPermission() { + Jenkins jenkins = Jenkins.get(); + jenkins.checkPermission(Jenkins.MANAGE); + return jenkins; + } + + } + +} \ No newline at end of file diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractNativeServerSCMHeadEvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/AbstractNativeServerSCMHeadEvent.java similarity index 98% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractNativeServerSCMHeadEvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/AbstractNativeServerSCMHeadEvent.java index bdb77be89..c27f5a6fb 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractNativeServerSCMHeadEvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/AbstractNativeServerSCMHeadEvent.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/ServerHeadEvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerHeadEvent.java similarity index 98% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/ServerHeadEvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerHeadEvent.java index a7aa91091..75f5b5140 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/ServerHeadEvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerHeadEvent.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSourceContext; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPingWebhookProcessor.java similarity index 88% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessor.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPingWebhookProcessor.java index 9bbabc6dd..7808e0760 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPingWebhookProcessor.java @@ -21,10 +21,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractWebhookProcessor; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.RestrictedSince; @@ -39,9 +40,9 @@ @Extension @Restricted(NoExternalUse.class) @RestrictedSince("933.3.0") -public class NativeServerPingHookProcessor extends AbstractHookProcessor { +public class ServerPingWebhookProcessor extends AbstractWebhookProcessor { - private static final Logger logger = Logger.getLogger(NativeServerPingHookProcessor.class.getName()); + private static final Logger logger = Logger.getLogger(ServerPingWebhookProcessor.class.getName()); private static final List supportedEvents = List.of(HookEventType.SERVER_PING.getKey()); @Override diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPullRequestHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPullRequestWebhookProcessor.java similarity index 92% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPullRequestHookProcessor.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPullRequestWebhookProcessor.java index b9bd22f99..5ee1c2b1d 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPullRequestHookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPullRequestWebhookProcessor.java @@ -21,12 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.JsonParser; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractWebhookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.server.events.NativeServerPullRequestEvent; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -44,9 +45,10 @@ @Extension @Restricted(NoExternalUse.class) @RestrictedSince("933.3.0") -public class NativeServerPullRequestHookProcessor extends AbstractHookProcessor { +public class ServerPullRequestWebhookProcessor extends AbstractWebhookProcessor { + + private static final Logger LOGGER = Logger.getLogger(ServerPullRequestWebhookProcessor.class.getName()); - private static final Logger LOGGER = Logger.getLogger(NativeServerPullRequestHookProcessor.class.getName()); private static final List supportedEvents = List.of( HookEventType.SERVER_PULL_REQUEST_OPENED.getKey(), HookEventType.SERVER_PULL_REQUEST_MERGED.getKey(), diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/ServerPushEvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPushEvent.java similarity index 98% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/ServerPushEvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPushEvent.java index 758c63ff0..824121cf5 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/ServerPushEvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPushEvent.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSourceContext; @@ -102,7 +102,7 @@ public boolean equals(Object obj) { } // event logs with the name of the processor - private static final Logger LOGGER = Logger.getLogger(NativeServerPushHookProcessor.class.getName()); + private static final Logger LOGGER = Logger.getLogger(ServerPushWebhookProcessor.class.getName()); private final BitbucketServerRepository repository; private final BitbucketServerCommit refCommit; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPushHookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPushWebhookProcessor.java similarity index 95% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPushHookProcessor.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPushWebhookProcessor.java index fe66cfac2..72e908b0e 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPushHookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPushWebhookProcessor.java @@ -21,12 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.JsonParser; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractWebhookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerCommit; import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerRepository; import com.cloudbees.jenkins.plugins.bitbucket.server.events.NativeServerChange; @@ -50,9 +51,9 @@ @Extension @Restricted(NoExternalUse.class) @RestrictedSince("933.3.0") -public class NativeServerPushHookProcessor extends AbstractHookProcessor { +public class ServerPushWebhookProcessor extends AbstractWebhookProcessor { - private static final Logger logger = Logger.getLogger(NativeServerPushHookProcessor.class.getName()); + private static final Logger logger = Logger.getLogger(ServerPushWebhookProcessor.class.getName()); private static final List supportedEvents = List.of( HookEventType.SERVER_REFS_CHANGED.getKey(), HookEventType.SERVER_MIRROR_REPO_SYNCHRONIZED.getKey()); diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerWebhook.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerWebhook.java new file mode 100644 index 000000000..aaef5a889 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerWebhook.java @@ -0,0 +1,60 @@ +/* + * The MIT License + * + * Copyright (c) 2025, Falco Nikolas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server; + +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractBitbucketWebhook; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.Messages; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import hudson.Extension; +import org.jenkinsci.Symbol; +import org.kohsuke.stapler.DataBoundConstructor; + +public class ServerWebhook extends AbstractBitbucketWebhook { + + @DataBoundConstructor + public ServerWebhook(boolean manageHooks, @CheckForNull String credentialsId, + boolean enableHookSignature, @CheckForNull String hookSignatureCredentialsId) { + super(manageHooks, credentialsId, enableHookSignature, hookSignatureCredentialsId); + } + + @Override + public String getDisplayName() { + return Messages.ServerWebhookImplementation_displayName(); + } + + @Override + public String getId() { + return "NATIVE"; + } + + @Symbol("serverWebhook") + @Extension + public static class DescriptorImpl extends AbstractBitbucketWebhookDescriptorImpl { + + @Override + public String getDisplayName() { + return "Native Data Center"; + } + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java index 0690379e3..0c9f972c9 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java @@ -96,7 +96,6 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.message.BasicNameValuePair; -import static java.util.Objects.requireNonNull; import static org.apache.commons.lang3.StringUtils.abbreviate; import static org.apache.commons.lang3.StringUtils.substring; @@ -177,7 +176,7 @@ public BitbucketServerAPIClient(@NonNull String baseURL, @NonNull String owner, } this.repositoryName = repositoryName; this.baseURL = Util.removeTrailingSlash(baseURL); - this.webhookImplementation = requireNonNull(webhookImplementation); + this.webhookImplementation = BitbucketServerWebhookImplementation.NATIVE; this.client = setupClientBuilder().build(); } diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/config.jelly index 4b869b907..b16e91d46 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/config.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/config.jelly @@ -24,18 +24,6 @@ THE SOFTWARE. - - - - - - - - - - - - - - + + diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpoint/config-detail.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpoint/config-detail.jelly index ab1a32045..8ff5f86b2 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpoint/config-detail.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpoint/config-detail.jelly @@ -22,19 +22,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/config-detail.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/config-detail.jelly index ff192cc92..cdb430942 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/config-detail.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/config-detail.jelly @@ -22,21 +22,20 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/manage-hooks-detail.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/manage-hooks-detail.jelly index 109342fce..a6c1e0e39 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/manage-hooks-detail.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/manage-hooks-detail.jelly @@ -1,6 +1,6 @@ - + diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/config.jelly new file mode 100644 index 000000000..592f4af80 --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/config.jelly @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-credentialsId.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-credentialsId.html new file mode 100644 index 000000000..b7061f123 --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-credentialsId.html @@ -0,0 +1,7 @@ +

+ Select the credentials to use for managing hooks. Both GLOBAL and SYSTEM scoped credentials are eligible as the + management of hooks is run in the context of Jenkins itself and not in the context of the individual items. +

+ For security reasons most credentials are only available when HTTPS is used. +

+
diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-enableHookSignature.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-enableHookSignature.html new file mode 100644 index 000000000..0c9ca8c6f --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-enableHookSignature.html @@ -0,0 +1,4 @@ +
+ Verify the signature of any incoming hook payload from this endpoint. This means that any payload must be signed, + if not payload will be rejected. +
\ No newline at end of file diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-endpointJenkinsRootURL.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-endpointJenkinsRootURL.html new file mode 100644 index 000000000..ff9bbb027 --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-endpointJenkinsRootURL.html @@ -0,0 +1,6 @@ +
+ You can customize the Jenkins Server Root URL to be used by this Bitbucket endpoint, + instead of the one set in Jenkins global configuration. This may be useful in local + networks with different DNS views for different clients (such as a Bitbucket server) + or to receive webhooks through some tunneling or relaying service hosted elsewhere. +
diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-hookSignatureCredentialsId.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-hookSignatureCredentialsId.html new file mode 100644 index 000000000..0af1f6438 --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-hookSignatureCredentialsId.html @@ -0,0 +1,9 @@ +
+ Select the credentials to use to verify the signature of incoming hooks. Both GLOBAL and SYSTEM scoped credentials + are eligible as the management of hooks is run in the context of Jenkins itself and not in the context of the + individual items. +

+ If the automatic management of web hooks is disabled than you have to setup manually in each repository selected + credentials or any untrusted hook will be discarded. +

+
\ No newline at end of file diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-manageHooks.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-manageHooks.html new file mode 100644 index 000000000..391b54782 --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-manageHooks.html @@ -0,0 +1,6 @@ +
+ Selecting this option will enable the automatic management of web hooks for all items that use this + endpoint, except those items that have explicitly opted out of hook management. + When this option is not selected, individual items can still opt in to hook management provided the credentials + those items have been configured with have permission to manage the required hooks. +
diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/Messages.properties b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/Messages.properties new file mode 100644 index 000000000..b1b25770f --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/Messages.properties @@ -0,0 +1,24 @@ +# +# The MIT License +# +# Copyright (c) 2025, Falco Nikolas +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +CloudWebhookImplementation.displayName=Native Cloud Implementation +ServerWebhookImplementation.displayName=Native Server Implementation diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/config.jelly new file mode 100644 index 000000000..6779be8fe --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/config.jelly @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/help-credentialsId.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/help-credentialsId.html new file mode 100644 index 000000000..b7061f123 --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/help-credentialsId.html @@ -0,0 +1,7 @@ +
+ Select the credentials to use for managing hooks. Both GLOBAL and SYSTEM scoped credentials are eligible as the + management of hooks is run in the context of Jenkins itself and not in the context of the individual items. +

+ For security reasons most credentials are only available when HTTPS is used. +

+
diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/help-endpointJenkinsRootURL.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/help-endpointJenkinsRootURL.html new file mode 100644 index 000000000..ff9bbb027 --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/help-endpointJenkinsRootURL.html @@ -0,0 +1,6 @@ +
+ You can customize the Jenkins Server Root URL to be used by this Bitbucket endpoint, + instead of the one set in Jenkins global configuration. This may be useful in local + networks with different DNS views for different clients (such as a Bitbucket server) + or to receive webhooks through some tunneling or relaying service hosted elsewhere. +
diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/help-manageHooks.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/help-manageHooks.html new file mode 100644 index 000000000..391b54782 --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook/help-manageHooks.html @@ -0,0 +1,6 @@ +
+ Selecting this option will enable the automatic management of web hooks for all items that use this + endpoint, except those items that have explicitly opted out of hook management. + When this option is not selected, individual items can still opt in to hook management provided the credentials + those items have been configured with have permission to manage the required hooks. +
diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfigurationTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfigurationTest.java index c72213785..11be8f02f 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfigurationTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfigurationTest.java @@ -29,7 +29,6 @@ import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.Messages; import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerVersion; -import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation; import com.cloudbees.plugins.credentials.Credentials; import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.SystemCredentialsProvider; @@ -646,7 +645,6 @@ void given__serverConfig__without__webhookImplementation__then__useNative() thro assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketServerEndpoint.class); final BitbucketServerEndpoint endpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(0); - assertThat(endpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); } @Test @@ -675,7 +673,6 @@ void load_serverConfig__with_old_signatures() throws Exception { assertThat(endpoint.getCredentialsId()).isEqualTo("admin.basic.credentials"); assertThat(endpoint.getEndpointJenkinsRootURL()).isEqualTo("http://host.docker.internal:8090/jenkins/"); assertThat(endpoint.getDisplayName()).isEqualTo("server"); - assertThat(endpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); assertThat(endpoint.isCallCanMerge()).isTrue(); assertThat(endpoint.isCallChanges()).isTrue(); }); @@ -713,7 +710,6 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isEqualTo("second"); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); assertThat(instance.getEndpoints()).element(2).isInstanceOf(BitbucketServerEndpoint.class); @@ -724,7 +720,6 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isTrue(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(3); @@ -734,7 +729,6 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isEqualTo("third"); assertThat(serverEndpoint.isCallCanMerge()).isTrue(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(4); @@ -744,7 +738,6 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isEqualTo("fourth"); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isFalse(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.PLUGIN); assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(5); @@ -754,7 +747,6 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isEqualTo("fifth"); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.PLUGIN); assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(6); @@ -764,7 +756,6 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isFalse(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(7); @@ -774,7 +765,6 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isFalse(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(8); @@ -784,7 +774,6 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(9); @@ -794,7 +783,6 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(10); @@ -804,7 +792,6 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(11); @@ -814,7 +801,6 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); assertThat(serverEndpoint.isCallChanges()).isTrue(); - assertThat(serverEndpoint.getWebhookImplementation()).isEqualTo(BitbucketServerWebhookImplementation.NATIVE); assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiverTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiverTest.java index ccff9e588..60de6da4d 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiverTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiverTest.java @@ -23,7 +23,7 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.hooks; -import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessor; +import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; @@ -103,10 +103,10 @@ void test_roundtrip() throws Exception { BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); try { - BitbucketHookProcessor hookProcessor = mock(BitbucketHookProcessor.class); + BitbucketWebhookProcessor hookProcessor = mock(BitbucketWebhookProcessor.class); sut = new BitbucketSCMSourcePushHookReceiver() { @Override - Stream getHookProcessors() { + Stream getHookProcessors() { return Stream.of(hookProcessor); } }; @@ -150,12 +150,12 @@ void stop_process_when_multiple_processors_canHandle_incoming_webhook() throws E BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); try { - BitbucketHookProcessor hookProcessor = mock(BitbucketHookProcessor.class); + BitbucketWebhookProcessor hookProcessor = mock(BitbucketWebhookProcessor.class); when(hookProcessor.canHandle(any(), any())).thenReturn(true); sut = new BitbucketSCMSourcePushHookReceiver() { @Override - Stream getHookProcessors() { - BitbucketHookProcessor otherHookProcessor = mock(BitbucketHookProcessor.class); + Stream getHookProcessors() { + BitbucketWebhookProcessor otherHookProcessor = mock(BitbucketWebhookProcessor.class); when(otherHookProcessor.canHandle(any(), any())).thenReturn(true); return Stream.of(hookProcessor, otherHookProcessor); } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhookProcessorTest.java similarity index 90% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhookProcessorTest.java index 1914de5ce..d8597516c 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/AbstractHookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhookProcessorTest.java @@ -21,10 +21,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; -import com.cloudbees.jenkins.plugins.bitbucket.api.hook.BitbucketHookProcessorException; +import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookProcessorException; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractWebhookProcessor; import com.cloudbees.plugins.credentials.CredentialsScope; import hudson.util.Secret; import java.io.IOException; @@ -48,14 +49,14 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class AbstractHookProcessorTest { +class AbstractWebhookProcessorTest { - private AbstractHookProcessor sut; + private AbstractWebhookProcessor sut; protected SCMHeadEvent scmEvent; @BeforeEach void setup() { - sut = new AbstractHookProcessor() { + sut = new AbstractWebhookProcessor() { @Override public boolean canHandle(Map headers, MultiValuedMap parameters) { @@ -102,9 +103,9 @@ void test_bad_signature() throws Exception { headers.put("X-Hub-Signature", "sha256=f205c729821c6954aff2afe72b965c34015b4baf96ea8ddc2cc44999c014a036"); assertThatThrownBy(() -> sut.verifyPayload(headers, loadResource("signed_payload.json"), endpoint)) - .isInstanceOf(BitbucketHookProcessorException.class) + .isInstanceOf(BitbucketWebhookProcessorException.class) .hasMessage("Signature verification failed") - .asInstanceOf(InstanceOfAssertFactories.type(BitbucketHookProcessorException.class)) + .asInstanceOf(InstanceOfAssertFactories.type(BitbucketWebhookProcessorException.class)) .satisfies(ex -> assertThat(ex.getHttpCode()).isEqualTo(403)); } @@ -113,9 +114,9 @@ void test_signature_is_missing() throws Exception { BitbucketEndpoint endpoint = getEndpoint(); assertThatThrownBy(() -> sut.verifyPayload(Collections.emptyMap(), loadResource("signed_payload.json"), endpoint)) - .isInstanceOf(BitbucketHookProcessorException.class) + .isInstanceOf(BitbucketWebhookProcessorException.class) .hasMessage("Payload has not be signed, configure the webHook secret in Bitbucket as documented at https://github.com/jenkinsci/bitbucket-branch-source-plugin/blob/master/docs/USER_GUIDE.adoc#webhooks-registering") - .asInstanceOf(InstanceOfAssertFactories.type(BitbucketHookProcessorException.class)) + .asInstanceOf(InstanceOfAssertFactories.type(BitbucketWebhookProcessorException.class)) .satisfies(ex -> assertThat(ex.getHttpCode()).isEqualTo(403)); } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPullRequestHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/CloudPullRequestWebhookProcessorTest.java similarity index 91% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPullRequestHookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/CloudPullRequestWebhookProcessorTest.java index 04d1e3a1b..3b7bc0853 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPullRequestHookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/CloudPullRequestWebhookProcessorTest.java @@ -21,13 +21,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.CloudPullRequestWebhookProcessor; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.PREvent; import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import com.cloudbees.jenkins.plugins.bitbucket.trait.OriginPullRequestDiscoveryTrait; import hudson.scm.SCM; @@ -53,17 +55,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -class CloudPullRequestHookProcessorTest { +class CloudPullRequestWebhookProcessorTest { - private CloudPullRequestHookProcessor sut; + private CloudPullRequestWebhookProcessor sut; private SCMHeadEvent scmEvent; @BeforeEach void setup() { - sut = new CloudPullRequestHookProcessor() { + sut = new CloudPullRequestWebhookProcessor() { @Override public void notifyEvent(SCMHeadEvent event, int delaySeconds) { - CloudPullRequestHookProcessorTest.this.scmEvent = event; + CloudPullRequestWebhookProcessorTest.this.scmEvent = event; } }; } @@ -111,7 +113,7 @@ void test_canHandle_only_pass_specific_cloud_hook() throws Exception { void test_pullrequest_created() throws Exception { sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PREvent event = (PREvent) scmEvent; + PluginPREvent event = (PluginPREvent) scmEvent; assertThat(event).isNotNull(); assertThat(event.getSourceName()).isEqualTo("test-repos"); assertThat(event.getType()).isEqualTo(Type.CREATED); @@ -122,7 +124,7 @@ void test_pullrequest_created() throws Exception { void test_pullrequest_rejected() throws Exception { sut.process(HookEventType.PULL_REQUEST_DECLINED.getKey(), loadResource("pullrequest_rejected.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PREvent event = (PREvent) scmEvent; + PluginPREvent event = (PluginPREvent) scmEvent; assertThat(event).isNotNull(); assertThat(event.getSourceName()).isEqualTo("test-repos"); assertThat(event.getType()).isEqualTo(Type.REMOVED); @@ -133,7 +135,7 @@ void test_pullrequest_rejected() throws Exception { void test_pullrequest_created_when_event_match_SCMNavigator() throws Exception { sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PREvent event = (PREvent) scmEvent; + PluginPREvent event = (PluginPREvent) scmEvent; // discard any scm navigator than bitbucket assertThat(event.isMatch(mock(SCMNavigator.class))).isFalse(); @@ -157,7 +159,7 @@ void test_pullrequest_created_when_event_match_SCMNavigator() throws Exception { void test_pullrequest_created_when_event_match_SCMSource(JenkinsRule r) throws Exception { sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PREvent event = (PREvent) scmEvent; + PluginPREvent event = (PluginPREvent) scmEvent; // discard any scm navigator than bitbucket assertThat(event.isMatch(mock(SCMSource.class))).isFalse(); @@ -183,7 +185,7 @@ void test_pullrequest_created_when_event_match_SCMSource(JenkinsRule r) throws E void test_pullrequest_rejected_returns_empty_pullrequests_when_event_match_SCMSource(JenkinsRule r) throws Exception { sut.process(HookEventType.PULL_REQUEST_DECLINED.getKey(), loadResource("pullrequest_rejected.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PREvent event = (PREvent) scmEvent; + PluginPREvent event = (PluginPREvent) scmEvent; BitbucketSCMSource scmSource = new BitbucketSCMSource("aMUNIZ", "test-repos"); scmSource.setTraits(List.of(new OriginPullRequestDiscoveryTrait(2))); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPushHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/CloudPushWebhookProcessorTest.java similarity index 91% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPushHookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/CloudPushWebhookProcessorTest.java index e137462e5..07bfcc943 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/CloudPushHookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/CloudPushWebhookProcessorTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketTagSCMHead; @@ -29,6 +29,8 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.CloudPushWebhookProcessor; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.PushEvent; import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import hudson.scm.SCM; import java.io.IOException; @@ -51,17 +53,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -class CloudPushHookProcessorTest { +class CloudPushWebhookProcessorTest { - private CloudPushHookProcessor sut; + private CloudPushWebhookProcessor sut; private SCMHeadEvent scmEvent; @BeforeEach void setup() { - sut = new CloudPushHookProcessor() { + sut = new CloudPushWebhookProcessor() { @Override public void notifyEvent(SCMHeadEvent event, int delaySeconds) { - CloudPushHookProcessorTest.this.scmEvent = event; + CloudPushWebhookProcessorTest.this.scmEvent = event; } }; } @@ -109,7 +111,7 @@ void test_canHandle_only_pass_specific_cloud_hook() throws Exception { void test_tag_created() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("tag_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PushEvent event = (PushEvent) scmEvent; + PluginPushEvent event = (PluginPushEvent) scmEvent; assertThat(event).isNotNull(); assertThat(event.getSourceName()).isEqualTo("test-repos"); assertThat(event.getType()).isEqualTo(Type.CREATED); @@ -127,7 +129,7 @@ void test_tag_created() throws Exception { void test_annotated_tag_created() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("annotated_tag_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PushEvent event = (PushEvent) scmEvent; + PluginPushEvent event = (PluginPushEvent) scmEvent; assertThat(event).isNotNull(); assertThat(event.getSourceName()).isEqualTo("test-repos"); assertThat(event.getType()).isEqualTo(Type.CREATED); @@ -145,7 +147,7 @@ void test_annotated_tag_created() throws Exception { void test_commmit_created() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("commit_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PushEvent event = (PushEvent) scmEvent; + PluginPushEvent event = (PluginPushEvent) scmEvent; assertThat(event).isNotNull(); assertThat(event.getSourceName()).isEqualTo("test-repos"); assertThat(event.getType()).isEqualTo(Type.UPDATED); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/PluginPullRequestWebhookProcessorTest.java similarity index 89% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/PluginPullRequestWebhookProcessorTest.java index 213a36c73..2241b6caf 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPullRequestHookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/PluginPullRequestWebhookProcessorTest.java @@ -21,12 +21,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.PREvent; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin.PluginPullRequestWebhookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import com.cloudbees.jenkins.plugins.bitbucket.trait.OriginPullRequestDiscoveryTrait; import java.io.IOException; @@ -52,18 +54,18 @@ import static org.mockito.Mockito.mock; @SuppressWarnings("deprecation") -class PluginPullRequestHookProcessorTest { +class PluginPullRequestWebhookProcessorTest { private static final String SERVER_URL = "http://localhost:7990"; - private PluginPullRequestHookProcessor sut; + private PluginPullRequestWebhookProcessor sut; private SCMHeadEvent scmEvent; @BeforeEach void setup() { - sut = new PluginPullRequestHookProcessor() { + sut = new PluginPullRequestWebhookProcessor() { @Override public void notifyEvent(SCMHeadEvent event, int delaySeconds) { - PluginPullRequestHookProcessorTest.this.scmEvent = event; + PluginPullRequestWebhookProcessorTest.this.scmEvent = event; } }; } @@ -112,9 +114,9 @@ void test_canHandle_only_pass_specific_cloud_hook() throws Exception { void test_pullrequest_created() throws Exception { sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - assertThat(scmEvent).isNotNull().isInstanceOf(PREvent.class); + assertThat(scmEvent).isNotNull().isInstanceOf(PluginPREvent.class); - PREvent event = (PREvent) scmEvent; + PluginPREvent event = (PluginPREvent) scmEvent; assertThat(event.getSourceName()).isEqualTo("rep_1"); assertThat(event.getType()).isEqualTo(Type.CREATED); } @@ -123,9 +125,9 @@ void test_pullrequest_created() throws Exception { void test_pullrequest_merged() throws Exception { sut.process(HookEventType.PULL_REQUEST_MERGED.getKey(), loadResource("pullrequest_merged.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - assertThat(scmEvent).isNotNull().isInstanceOf(PREvent.class); + assertThat(scmEvent).isNotNull().isInstanceOf(PluginPREvent.class); - PREvent event = (PREvent) scmEvent; + PluginPREvent event = (PluginPREvent) scmEvent; assertThat(event.getSourceName()).isEqualTo("rep_1"); assertThat(event.getType()).isEqualTo(Type.REMOVED); } @@ -134,9 +136,9 @@ void test_pullrequest_merged() throws Exception { void test_pullrequest_updated() throws Exception { sut.process(HookEventType.PULL_REQUEST_UPDATED.getKey(), loadResource("pullrequest_updated.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - assertThat(scmEvent).isNotNull().isInstanceOf(PREvent.class); + assertThat(scmEvent).isNotNull().isInstanceOf(PluginPREvent.class); - PREvent event = (PREvent) scmEvent; + PluginPREvent event = (PluginPREvent) scmEvent; assertThat(event.getSourceName()).isEqualTo("rep_1"); assertThat(event.getType()).isEqualTo(Type.UPDATED); } @@ -145,7 +147,7 @@ void test_pullrequest_updated() throws Exception { void test_PREvent_match_SCMNavigator() throws Exception { sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PREvent event = (PREvent) scmEvent; + PluginPREvent event = (PluginPREvent) scmEvent; assertThat(event.getType()).isEqualTo(Type.CREATED); // discard any scm navigator than bitbucket assertThat(event.isMatch(mock(SCMNavigator.class))).isFalse(); @@ -173,7 +175,7 @@ void test_PREvent_match_SCMNavigator() throws Exception { void test_PREvent_match_SCMSource(JenkinsRule r) throws Exception { sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PREvent event = (PREvent) scmEvent; + PluginPREvent event = (PluginPREvent) scmEvent; // discard any scm navigator than bitbucket assertThat(event.isMatch(mock(SCMSource.class))).isFalse(); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/PluginPushWebhookProcessorTest.java similarity index 92% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/PluginPushWebhookProcessorTest.java index 6b3447a06..6b3632c7a 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/PluginPushHookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/PluginPushWebhookProcessorTest.java @@ -21,13 +21,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BranchSCMHead; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.PushEvent; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin.PluginPushWebhookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import java.io.IOException; import java.io.InputStream; @@ -56,18 +58,18 @@ import static org.mockito.Mockito.mock; @SuppressWarnings("deprecation") -class PluginPushHookProcessorTest { +class PluginPushWebhookProcessorTest { private static final String SERVER_URL = "http://localhost:7990"; - private PluginPushHookProcessor sut; + private PluginPushWebhookProcessor sut; private SCMHeadEvent scmEvent; @BeforeEach void setup() { - sut = new PluginPushHookProcessor() { + sut = new PluginPushWebhookProcessor() { @Override public void notifyEvent(SCMHeadEvent event, int delaySeconds) { - PluginPushHookProcessorTest.this.scmEvent = event; + PluginPushWebhookProcessorTest.this.scmEvent = event; } }; } @@ -113,7 +115,7 @@ void test_canHandle_only_pass_specific_native_hook() throws Exception { void test_push_server_UPDATE_2() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("commit_update2.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PushEvent event = (PushEvent) scmEvent; + PluginPushEvent event = (PluginPushEvent) scmEvent; assertThat(event).isNotNull(); assertThat(event.getSourceName()).isEqualTo("rep_1"); assertThat(event.getType()).isEqualTo(SCMEvent.Type.UPDATED); @@ -130,7 +132,7 @@ void test_push_server_UPDATE_2() throws Exception { void test_push_server_UPDATE() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("commit_update.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PushEvent event = (PushEvent) scmEvent; + PluginPushEvent event = (PluginPushEvent) scmEvent; assertThat(event).isNotNull(); assertThat(event.getSourceName()).isEqualTo("rep_1"); assertThat(event.getType()).isEqualTo(SCMEvent.Type.UPDATED); @@ -147,7 +149,7 @@ void test_push_server_UPDATE() throws Exception { void test_push_server_CREATED() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("branch_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PushEvent event = (PushEvent) scmEvent; + PluginPushEvent event = (PluginPushEvent) scmEvent; assertThat(event).isNotNull(); assertThat(event.getSourceName()).isEqualTo("rep_1"); assertThat(event.getType()).isEqualTo(SCMEvent.Type.CREATED); @@ -164,7 +166,7 @@ void test_push_server_CREATED() throws Exception { void test_push_server_REMOVED() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("branch_deleted.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PushEvent event = (PushEvent) scmEvent; + PluginPushEvent event = (PluginPushEvent) scmEvent; assertThat(event).isNotNull(); assertThat(event.getSourceName()).isEqualTo("rep_1"); assertThat(event.getType()).isEqualTo(SCMEvent.Type.REMOVED); @@ -180,7 +182,7 @@ void test_push_server_REMOVED() throws Exception { void test_PushEvent_match_SCMNavigator() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("branch_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PushEvent event = (PushEvent) scmEvent; + PluginPushEvent event = (PluginPushEvent) scmEvent; assertThat(event.getType()).isEqualTo(Type.CREATED); // discard any scm navigator than bitbucket assertThat(event.isMatch(mock(SCMNavigator.class))).isFalse(); @@ -208,7 +210,7 @@ void test_PushEvent_match_SCMNavigator() throws Exception { void test_PushEvent_match_SCMSource(JenkinsRule r) throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("branch_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PushEvent event = (PushEvent) scmEvent; + PluginPushEvent event = (PluginPushEvent) scmEvent; // discard any scm navigator than bitbucket assertThat(event.isMatch(mock(SCMSource.class))).isFalse(); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPingWebhookProcessorTest.java similarity index 91% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPingWebhookProcessorTest.java index 9f0f40262..897b4cbd2 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPingHookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPingWebhookProcessorTest.java @@ -21,10 +21,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerPingWebhookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import java.io.IOException; import java.io.InputStream; @@ -45,19 +46,19 @@ import static org.mockito.Mockito.when; @WithJenkins -class NativeServerPingHookProcessorTest { +class ServerPingWebhookProcessorTest { private static final String SERVER_URL = "http://localhost:7990"; - private NativeServerPingHookProcessor sut; + private ServerPingWebhookProcessor sut; private SCMHeadEvent scmEvent; private BitbucketEndpoint endpoint; @BeforeEach void setup() { - sut = new NativeServerPingHookProcessor() { + sut = new ServerPingWebhookProcessor() { @Override public void notifyEvent(SCMHeadEvent event, int delaySeconds) { - NativeServerPingHookProcessorTest.this.scmEvent = event; + ServerPingWebhookProcessorTest.this.scmEvent = event; } }; endpoint = mock(BitbucketEndpoint.class); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPullRequestHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPullRequestWebhookProcessorTest.java similarity index 92% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPullRequestHookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPullRequestWebhookProcessorTest.java index 27a383dea..8af6c413a 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPullRequestHookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPullRequestWebhookProcessorTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BranchSCMHead; @@ -29,6 +29,8 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.PullRequestBranchType; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerHeadEvent; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerPullRequestWebhookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import com.cloudbees.jenkins.plugins.bitbucket.trait.OriginPullRequestDiscoveryTrait; import hudson.scm.SCM; @@ -59,19 +61,19 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class NativeServerPullRequestHookProcessorTest { +class ServerPullRequestWebhookProcessorTest { private static final String SERVER_URL = "http://localhost:7990"; - private NativeServerPullRequestHookProcessor sut; + private ServerPullRequestWebhookProcessor sut; private SCMHeadEvent scmEvent; private BitbucketEndpoint endpoint; @BeforeEach void setup() { - sut = new NativeServerPullRequestHookProcessor() { + sut = new ServerPullRequestWebhookProcessor() { @Override public void notifyEvent(SCMHeadEvent event, int delaySeconds) { - NativeServerPullRequestHookProcessorTest.this.scmEvent = event; + ServerPullRequestWebhookProcessorTest.this.scmEvent = event; } }; endpoint = mock(BitbucketEndpoint.class); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPushHookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPushWebhookProcessorTest.java similarity index 95% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPushHookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPushWebhookProcessorTest.java index 09d9a267f..8618d6e60 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/NativeServerPushHookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPushWebhookProcessorTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.hook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketTagSCMHead; @@ -30,6 +30,8 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketIntegrationClientFactory; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerPushEvent; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerPushWebhookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import hudson.scm.SCM; import java.io.IOException; @@ -58,11 +60,11 @@ import static org.mockito.Mockito.when; @WithJenkins -class NativeServerPushHookProcessorTest { +class ServerPushWebhookProcessorTest { private static final String SERVER_URL = "http://localhost:7990"; private static final String MIRROR_ID = "ABCD-1234-EFGH-5678"; - private NativeServerPushHookProcessor sut; + private ServerPushWebhookProcessor sut; private SCMHeadEvent scmEvent; private BitbucketEndpoint endpoint; @@ -75,10 +77,10 @@ static void init(JenkinsRule r) { @BeforeEach void setup() { - sut = new NativeServerPushHookProcessor() { + sut = new ServerPushWebhookProcessor() { @Override public void notifyEvent(SCMHeadEvent event, int delaySeconds) { - NativeServerPushHookProcessorTest.this.scmEvent = event; + ServerPushWebhookProcessorTest.this.scmEvent = event; } }; endpoint = mock(BitbucketEndpoint.class); diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration.xml b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration.xml new file mode 100644 index 000000000..679be55b4 --- /dev/null +++ b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration.xml @@ -0,0 +1,37 @@ + + + + + false + false + false + 360 + 180 + + + true + admin.basic.credentials + true + datacenter.hook.signature + http://host.docker.internal:8090/jenkins/ + server + http://localhost:7990/bitbucket + NATIVE + VERSION_7 + true + true + + + true + datacenter.certificate.credentials + false + http://host.docker.internal:8090/jenkins/ + server ngix + https://localhost:1443/bitbucket + PLUGIN + VERSION_7 + true + true + + + \ No newline at end of file diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/annotated_tag_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/annotated_tag_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/annotated_tag_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/annotated_tag_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/commit_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/commit_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/commit_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/commit_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/pullrequest_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/pullrequest_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/pullrequest_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/pullrequest_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/pullrequest_rejected.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/pullrequest_rejected.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/pullrequest_rejected.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/pullrequest_rejected.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/signed_payload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/signed_payload.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/signed_payload.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/signed_payload.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/tag_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/tag_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/cloud/tag_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/tag_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/annotated_tag_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/annotated_tag_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/annotated_tag_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/annotated_tag_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/emptyPayload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/emptyPayload.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/emptyPayload.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/emptyPayload.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/mirrorSynchronized.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/mirrorSynchronized.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/mirrorSynchronized.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/mirrorSynchronized.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/mirrorSynchronized_refLimitExceeded.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/mirrorSynchronized_refLimitExceeded.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/mirrorSynchronized_refLimitExceeded.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/mirrorSynchronized_refLimitExceeded.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/ping.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/ping.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/ping.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/ping.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/prOpenFromTagPayload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/prOpenFromTagPayload.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/prOpenFromTagPayload.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/prOpenFromTagPayload.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/pushPayload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/pushPayload.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/pushPayload.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/pushPayload.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/signed_payload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/signed_payload.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/signed_payload.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/signed_payload.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/tag_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/tag_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/tag_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/tag_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/tag_deleted.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/tag_deleted.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/native/tag_deleted.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/native/tag_deleted.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/branch_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/branch_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/branch_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/branch_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/branch_deleted.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/branch_deleted.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/branch_deleted.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/branch_deleted.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/commit_update.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/commit_update.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/commit_update.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/commit_update.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/commit_update2.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/commit_update2.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/commit_update2.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/commit_update2.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/emptyPayload.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/emptyPayload.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/emptyPayload.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/emptyPayload.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/pullrequest_created.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/pullrequest_created.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/pullrequest_created.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/pullrequest_created.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/pullrequest_merged.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/pullrequest_merged.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/pullrequest_merged.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/pullrequest_merged.json diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/pullrequest_updated.json b/src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/pullrequest_updated.json similarity index 100% rename from src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/hook/plugin/pullrequest_updated.json rename to src/test/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/pullrequest_updated.json From ef896c001ed075cb13a443ade636477ecc598e00 Mon Sep 17 00:00:00 2001 From: Nikolas Falco Date: Mon, 7 Jul 2025 11:49:11 +0200 Subject: [PATCH 3/3] [JENKINS-74913] Allow extension point in bitbucket source plugin to provide a implementation for web-hooks management make readResolve compatible with old format --- .../bitbucket/BitbucketGitSCMBuilder.java | 3 +- .../bitbucket/BitbucketSCMNavigator.java | 7 +- .../plugins/bitbucket/BitbucketSCMSource.java | 5 +- .../endpoint/BitbucketEndpointDescriptor.java | 74 ----------- .../endpoint/BitbucketEndpointProvider.java | 2 +- .../webhook/BitbucketWebhookDescriptor.java | 3 +- .../BitbucketEndpointConfiguration.java | 10 +- .../bitbucket/hooks/WebhookConfiguration.java | 5 +- .../endpoint/AbstractBitbucketEndpoint.java | 88 ++++++------- .../impl/endpoint/BitbucketCloudEndpoint.java | 56 +------- .../endpoint/BitbucketServerEndpoint.java | 120 +++-------------- ...ucketWebhook.java => AbstractWebhook.java} | 50 +------ .../cloud/{PREvent.java => CloudPREvent.java} | 4 +- .../CloudPullRequestWebhookProcessor.java | 2 +- .../{PushEvent.java => CloudPushEvent.java} | 4 +- .../cloud/CloudPushWebhookProcessor.java | 2 +- .../impl/webhook/cloud/CloudWebhook.java | 59 ++++++++- .../impl/webhook/plugin/PluginWebhook.java | 8 +- .../impl/webhook/server/ServerWebhook.java | 61 ++++++++- .../BitbucketServerWebhookImplementation.java | 46 ------- .../client/BitbucketServerAPIClient.java | 21 ++- .../help-bitbucketJenkinsRootUrl.html | 6 - ...elp-serverUrl.html => help-serverURL.html} | 0 .../help-webhookImplementation.html | 11 -- .../manage-hooks-detail.jelly | 6 - .../help-credentialsId.html | 7 - .../help-enableHookSignature.html | 4 - .../help-hookSignatureCredentialsId.html | 9 -- .../help-manageHooks.html | 6 - .../config.jelly | 0 .../AbstractWebhook}/help-credentialsId.html | 0 .../help-enableHookSignature.html | 0 .../help-endpointJenkinsRootURL.html | 0 .../help-hookSignatureCredentialsId.html | 0 .../AbstractWebhook}/help-manageHooks.html | 0 .../BranchScanningIntegrationTest.java | 2 +- .../bitbucket/WebhooksAutoregisterTest.java | 3 +- .../BitbucketIntegrationClientFactory.java | 3 +- .../BitbucketEndpointConfigurationTest.java | 124 +++++++++--------- ...itbucketSCMSourcePushHookReceiverTest.java | 5 +- .../WebhookAutoRegisterListenerTest.java | 3 +- .../ExponentialBackOffRetryStrategyTest.java | 5 +- .../endpoint/BitbucketCloudEndpointTest.java | 5 +- .../endpoint/BitbucketServerEndpointTest.java | 7 +- .../endpoint/DummyEndpointConfiguration.java | 3 +- ...ketBuildStatusNotificationsJUnit5Test.java | 11 +- .../webhook/AbstractWebhookProcessorTest.java | 3 +- .../CloudPullRequestWebhookProcessorTest.java | 54 ++++---- .../CloudPushWebhookProcessorTest.java | 41 +++--- ...PluginPullRequestWebhookProcessorTest.java | 53 +++----- .../PluginPushWebhookProcessorTest.java | 66 ++++------ .../ServerPingWebhookProcessorTest.java | 2 +- ...ServerPullRequestWebhookProcessorTest.java | 2 +- .../ServerPushWebhookProcessorTest.java | 2 +- .../client/BitbucketServerAPIClientTest.java | 3 +- .../trait/SCMNavigatorIntegrationTest.java | 2 +- 56 files changed, 409 insertions(+), 669 deletions(-) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/{AbstractBitbucketWebhook.java => AbstractWebhook.java} (72%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/{PREvent.java => CloudPREvent.java} (97%) rename src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/{PushEvent.java => CloudPushEvent.java} (96%) delete mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/BitbucketServerWebhookImplementation.java delete mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/help-bitbucketJenkinsRootUrl.html rename src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/{help-serverUrl.html => help-serverURL.html} (100%) delete mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/help-webhookImplementation.html delete mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/manage-hooks-detail.jelly delete mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-credentialsId.html delete mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-enableHookSignature.html delete mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-hookSignatureCredentialsId.html delete mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-manageHooks.html rename src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/{AbstractBitbucketWebhook => AbstractWebhook}/config.jelly (100%) rename src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{endpoint/AbstractBitbucketEndpoint => webhook/AbstractWebhook}/help-credentialsId.html (100%) rename src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{endpoint/AbstractBitbucketEndpoint => webhook/AbstractWebhook}/help-enableHookSignature.html (100%) rename src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/{AbstractBitbucketWebhook => AbstractWebhook}/help-endpointJenkinsRootURL.html (100%) rename src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{endpoint/AbstractBitbucketEndpoint => webhook/AbstractWebhook}/help-hookSignatureCredentialsId.html (100%) rename src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/{endpoint/AbstractBitbucketEndpoint => webhook/AbstractWebhook}/help-manageHooks.html (100%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/{ => cloud}/CloudPullRequestWebhookProcessorTest.java (80%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/{ => cloud}/CloudPushWebhookProcessorTest.java (81%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/{ => plugin}/PluginPullRequestWebhookProcessorTest.java (80%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/{ => plugin}/PluginPushWebhookProcessorTest.java (79%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/{ => server}/ServerPingWebhookProcessorTest.java (98%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/{ => server}/ServerPullRequestWebhookProcessorTest.java (98%) rename src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/{ => server}/ServerPushWebhookProcessorTest.java (99%) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketGitSCMBuilder.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketGitSCMBuilder.java index 16e7291a0..cf4e6812a 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketGitSCMBuilder.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketGitSCMBuilder.java @@ -31,6 +31,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.PullRequestBranchType; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; +import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.extension.FallbackToOtherRepositoryGitSCMExtension; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; @@ -113,7 +114,7 @@ public BitbucketGitSCMBuilder(@NonNull BitbucketSCMSource scmSource, @NonNull SC String serverURL = scmSource.getServerUrl(); BitbucketEndpoint endpoint = BitbucketEndpointProvider.lookupEndpoint(serverURL) - .orElse(new BitbucketServerEndpoint(null, serverURL, false, null, false, null)); + .orElse(BitbucketApiUtils.isCloud(serverURL) ? new BitbucketCloudEndpoint() : new BitbucketServerEndpoint("tmp", serverURL)); String repositoryURL = endpoint.getRepositoryURL(scmSource.getRepoOwner(), scmSource.getRepository()); if (BitbucketApiUtils.isCloud(endpoint.getServerURL())) { diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java index 5be3d4a5d..20a89efd6 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java @@ -38,7 +38,6 @@ import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.MirrorListSupplier; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils; -import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation; import com.cloudbees.jenkins.plugins.bitbucket.trait.BranchDiscoveryTrait; import com.cloudbees.jenkins.plugins.bitbucket.trait.ForkPullRequestDiscoveryTrait; import com.cloudbees.jenkins.plugins.bitbucket.trait.OriginPullRequestDiscoveryTrait; @@ -412,10 +411,8 @@ public static FormValidation doCheckCredentialsId(@AncestorInPath SCMSourceOwner public static FormValidation doCheckMirrorId(@QueryParameter String value, @QueryParameter(fixEmpty = true, value = "serverUrl") String serverURL) { if (!value.isEmpty()) { - BitbucketServerWebhookImplementation webhookImplementation = - BitbucketServerEndpoint.findWebhookImplementation(serverURL); - if (webhookImplementation == BitbucketServerWebhookImplementation.PLUGIN) { - return FormValidation.error("Mirror can only be used with native webhooks"); + if (BitbucketServerEndpoint.supportsMirror(serverURL)) { + return FormValidation.error("Mirror is not supported by the choosen webhooks"); } } return FormValidation.ok(); diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java index 16850eb6f..e13257f13 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java @@ -53,7 +53,6 @@ import com.cloudbees.jenkins.plugins.bitbucket.impl.util.DateUtils; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.MirrorListSupplier; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils; -import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation; import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerAPIClient; import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerRepository; import com.cloudbees.jenkins.plugins.bitbucket.trait.BranchDiscoveryTrait; @@ -1073,9 +1072,7 @@ public static FormValidation doCheckServerUrl(@AncestorInPath SCMSourceOwner con public static FormValidation doCheckMirrorId(@QueryParameter String value, @QueryParameter(fixEmpty = true, value = "serverUrl") String serverURL) { if (!value.isEmpty()) { - BitbucketServerWebhookImplementation webhookImplementation = - BitbucketServerEndpoint.findWebhookImplementation(serverURL); - if (webhookImplementation == BitbucketServerWebhookImplementation.PLUGIN) { + if (BitbucketServerEndpoint.supportsMirror(serverURL)) { return FormValidation.error("Mirror can only be used with native webhooks"); } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpointDescriptor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpointDescriptor.java index b235b7121..c1ba41e24 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpointDescriptor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpointDescriptor.java @@ -23,23 +23,7 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.api.endpoint; -import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils; -import com.cloudbees.plugins.credentials.CredentialsMatchers; -import com.cloudbees.plugins.credentials.common.StandardListBoxModel; -import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder; -import hudson.Util; import hudson.model.Descriptor; -import hudson.security.ACL; -import hudson.util.FormValidation; -import hudson.util.ListBoxModel; -import java.net.MalformedURLException; -import java.net.URL; -import jenkins.model.Jenkins; -import org.jenkinsci.plugins.plaincredentials.StringCredentials; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.interceptor.RequirePOST; /** * {@link Descriptor} for {@link BitbucketEndpoint}s. @@ -47,62 +31,4 @@ * @since 936.4.0 */ public class BitbucketEndpointDescriptor extends Descriptor { - /** - * Stapler form completion. - * - * @param credentialsId selected credentials. - * @param serverURL the server URL. - * @return the available credentials. - */ - @RequirePOST - public ListBoxModel doFillCredentialsIdItems(@QueryParameter(fixEmpty = true) String credentialsId, - @QueryParameter(value = "serverUrl", fixEmpty = true) String serverURL) { - Jenkins jenkins = checkPermission(); - return BitbucketCredentialsUtils.listCredentials(jenkins, serverURL, credentialsId); - } - - private static Jenkins checkPermission() { - Jenkins jenkins = Jenkins.get(); - jenkins.checkPermission(Jenkins.MANAGE); - return jenkins; - } - - /** - * Stapler form completion. - * - * @param hookSignatureCredentialsId selected hook signature credentials. - * @param serverURL the server URL. - * @return the available credentials. - */ - @RequirePOST - public ListBoxModel doFillHookSignatureCredentialsIdItems(@QueryParameter(fixEmpty = true) String hookSignatureCredentialsId, - @QueryParameter(value = "serverUrl", fixEmpty = true) String serverURL) { - Jenkins jenkins = checkPermission(); - StandardListBoxModel result = new StandardListBoxModel(); - result.includeMatchingAs(ACL.SYSTEM2, - jenkins, - StringCredentials.class, - URIRequirementBuilder.fromUri(serverURL).build(), - CredentialsMatchers.always()); - if (hookSignatureCredentialsId != null) { - result.includeCurrentValue(hookSignatureCredentialsId); - } - return result; - } - - @Restricted(NoExternalUse.class) - @RequirePOST - public static FormValidation doCheckBitbucketJenkinsRootUrl(@QueryParameter String value) { - checkPermission(); - String url = Util.fixEmptyAndTrim(value); - if (url == null) { - return FormValidation.ok(); - } - try { - new URL(url); - } catch (MalformedURLException e) { - return FormValidation.error("Invalid URL: " + e.getMessage()); - } - return FormValidation.ok(); - } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpointProvider.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpointProvider.java index fb6027934..8095a5169 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpointProvider.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpointProvider.java @@ -159,7 +159,7 @@ public static BitbucketEndpoint registerEndpoint(@NonNull String name, @NonNull if (BitbucketApiUtils.isCloud(serverURL)) { endpoint = new BitbucketCloudEndpoint(); } else { - endpoint = new BitbucketServerEndpoint(name, serverURL, false, null, false, null); + endpoint = new BitbucketServerEndpoint(name, serverURL); } if (endpointCustomiser != null) { endpoint = endpointCustomiser.apply(endpoint); diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookDescriptor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookDescriptor.java index a72d44610..a45687820 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookDescriptor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookDescriptor.java @@ -30,5 +30,6 @@ * * @since 936.4.0 */ -public class BitbucketWebhookDescriptor extends Descriptor { +public abstract class BitbucketWebhookDescriptor extends Descriptor { + public abstract boolean isApplicable(String serverURL); } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfiguration.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfiguration.java index 9a773dee4..18a8f4b3d 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfiguration.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfiguration.java @@ -29,6 +29,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.CloudWebhook; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -86,6 +87,7 @@ public XmlFile getConfigFile() { XStream2 xs = new XStream2(XStream2.getDefaultDriver()); xs.alias("com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint", BitbucketCloudEndpoint.class); xs.alias("com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint", BitbucketServerEndpoint.class); + xs.aliasAttribute(BitbucketServerEndpoint.class, "serverURL", "serverUrl"); return new XmlFile(xs, cfgFile); } @@ -124,7 +126,7 @@ public String readResolveServerUrl(@CheckForNull String serverURL) { // exception case endpoint = new BitbucketCloudEndpoint(); } else { - endpoint = new BitbucketServerEndpoint(normalizedURL); + endpoint = new BitbucketServerEndpoint(null, normalizedURL); } addEndpoint(endpoint); } @@ -199,10 +201,10 @@ public void setEndpoints(@CheckForNull List endpoin continue; } else if (!(endpoint instanceof BitbucketCloudEndpoint) && BitbucketApiUtils.isCloud(serverURL)) { // fix type for the special case - BitbucketCloudEndpoint cloudEndpoint = new BitbucketCloudEndpoint(false, 0, 0, - endpoint.isManageHooks(), endpoint.getCredentialsId(), + CloudWebhook webhook = new CloudWebhook(endpoint.isManageHooks(), endpoint.getCredentialsId(), endpoint.isEnableHookSignature(), endpoint.getHookSignatureCredentialsId()); - cloudEndpoint.setBitbucketJenkinsRootUrl(endpoint.getEndpointJenkinsRootURL()); + webhook.setEndpointJenkinsRootURL(endpoint.getEndpointJenkinsRootURL()); + BitbucketCloudEndpoint cloudEndpoint = new BitbucketCloudEndpoint(false, 0, 0, webhook); iterator.set(cloudEndpoint); } serverURLs.add(serverURL); diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookConfiguration.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookConfiguration.java index 085c657c9..203acc539 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookConfiguration.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookConfiguration.java @@ -28,7 +28,6 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; import com.cloudbees.jenkins.plugins.bitbucket.client.repository.BitbucketCloudHook; -import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketPluginWebhook; import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerWebhook; @@ -167,7 +166,8 @@ public BitbucketWebHook getHook(BitbucketSCMSource owner) { hook.setSecret(signatureSecret); return hook; } - + return null; +/* switch (BitbucketServerEndpoint.findWebhookImplementation(serverURL)) { case NATIVE: { BitbucketServerWebhook hook = new BitbucketServerWebhook(); @@ -189,6 +189,7 @@ public BitbucketWebHook getHook(BitbucketSCMSource owner) { return hook; } } +*/ } @Nullable diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint.java index fdb5b4652..16c27b882 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint.java @@ -29,10 +29,14 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhook; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.CloudWebhook; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin.PluginWebhook; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerWebhook; import com.cloudbees.plugins.credentials.common.StandardCredentials; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Util; import java.util.Objects; import jenkins.authentication.tokens.api.AuthenticationTokens; @@ -51,53 +55,33 @@ */ public abstract class AbstractBitbucketEndpoint implements BitbucketEndpoint { - /** - * {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point. - */ + // keept for backward XStream compatibility + @Deprecated private boolean manageHooks; - - /** - * The {@link StandardCredentials#getId()} of the credentials to use for auto-management of hooks. - */ - @CheckForNull + @Deprecated private String credentialsId; + @Deprecated + private boolean enableHookSignature; + @Deprecated + private String hookSignatureCredentialsId; + @Deprecated + private String bitbucketJenkinsRootUrl; + @Deprecated + private String webhookImplementation; @NonNull private BitbucketWebhook webhook; - /** - * {@code true} if and only if Jenkins have to verify the signature of all incoming hooks. - */ - private boolean enableHookSignature; - - /** - * The {@link StringCredentials#getId()} of the credentials to use to verify the signature of hooks. - */ - @CheckForNull - private String hookSignatureCredentialsId; + AbstractBitbucketEndpoint(@NonNull BitbucketWebhook webhook) { + this.webhook = Objects.requireNonNull(webhook); + } - /** - * Jenkins Server Root URL to be used by that Bitbucket endpoint. - * The global setting from Jenkins.get().getRootUrl() - * will be used if this field is null or equals an empty string. - * This variable is bound to the UI, so an empty value is saved - * and returned by getter as such. - */ - private String bitbucketJenkinsRootUrl; + public @NonNull BitbucketWebhook getWebhook() { + return webhook; + } - /** - * Constructor. - * - * @param manageHooks {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point. - * @param credentialsId The {@link StandardCredentials#getId()} of the credentials to use for - * auto-management of hooks. - */ - AbstractBitbucketEndpoint(boolean manageHooks, @CheckForNull String credentialsId, - boolean enableHookSignature, @CheckForNull String hookSignatureCredentialsId) { - this.manageHooks = manageHooks && StringUtils.isNotBlank(credentialsId); - this.credentialsId = manageHooks ? fixEmptyAndTrim(credentialsId) : null; - this.enableHookSignature = enableHookSignature && StringUtils.isNotBlank(hookSignatureCredentialsId); - this.hookSignatureCredentialsId = enableHookSignature ? fixEmptyAndTrim(hookSignatureCredentialsId) : null; + protected void setWebhook(@NonNull BitbucketWebhook webhook) { + this.webhook = webhook; } @Override @@ -265,6 +249,23 @@ public BitbucketAuthenticator authenticator() { return AuthenticationTokens.convert(BitbucketAuthenticator.authenticationContext(getServerURL()), credentials()); } + @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "Only non-null after we set them here!") + protected Object readResolve() { + if (webhook == null) { + if ("NATIVE".equals(webhookImplementation)) { + webhook = new ServerWebhook(manageHooks, credentialsId, enableHookSignature, hookSignatureCredentialsId); + ((ServerWebhook) webhook).setEndpointJenkinsRootURL(bitbucketJenkinsRootUrl); + } else if ("PLUGIN".equals(webhookImplementation)) { + webhook = new PluginWebhook(manageHooks, credentialsId); + ((PluginWebhook) webhook).setEndpointJenkinsRootURL(bitbucketJenkinsRootUrl); + } else { + webhook = new CloudWebhook(manageHooks, credentialsId, enableHookSignature, hookSignatureCredentialsId); + ((CloudWebhook) webhook).setEndpointJenkinsRootURL(bitbucketJenkinsRootUrl); + } + } + return this; + } + /** * {@inheritDoc} */ @@ -272,13 +273,4 @@ public BitbucketAuthenticator authenticator() { public BitbucketEndpointDescriptor getDescriptor() { return (BitbucketEndpointDescriptor) Jenkins.get().getDescriptorOrDie(getClass()); } - - public @NonNull BitbucketWebhook getWebhook() { - return webhook; - } - - @DataBoundSetter - public void setWebhook(@NonNull BitbucketWebhook webhook) { - this.webhook = Objects.requireNonNull(webhook); - } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpoint.java index 9ce04c07e..aa7dee26f 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpoint.java @@ -25,16 +25,15 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointDescriptor; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.EndpointType; +import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhook; import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudApiClient; -import com.cloudbees.plugins.credentials.common.StandardCredentials; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.CloudWebhook; import com.damnhandy.uri.template.UriTemplate; -import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.util.FormValidation; import java.util.List; import jenkins.model.Jenkins; -import org.jenkinsci.plugins.plaincredentials.StringCredentials; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.verb.POST; @@ -69,38 +68,12 @@ public class BitbucketCloudEndpoint extends AbstractBitbucketEndpoint { * Default constructor. */ public BitbucketCloudEndpoint() { - this(false, 0, 0, false, null, false, null); + this(false, 0, 0, new CloudWebhook(false, null, false, null)); } - @Deprecated(since = "936.3.1") - public BitbucketCloudEndpoint(boolean enableCache, int teamCacheDuration, int repositoriesCacheDuration, - boolean manageHooks, @CheckForNull String credentialsId) { - this(enableCache, teamCacheDuration, repositoriesCacheDuration, manageHooks, credentialsId, false, null); - } - - /** - * Constructor. - * - * @param enableCache {@code true} if caching should be used to reduce - * requests to Bitbucket. - * @param teamCacheDuration How long, in minutes, to cache the team - * response. - * @param repositoriesCacheDuration How long, in minutes, to cache the - * repositories response. - * @param manageHooks {@code true} if and only if Jenkins is supposed to - * auto-manage hooks for this end-point. - * @param credentialsId The {@link StandardCredentials#getId()} of the - * credentials to use for auto-management of hooks. - * @param enableHookSignature {@code true} hooks that comes Bitbucket Data - * Center are signed. - * @param hookSignatureCredentialsId The {@link StringCredentials#getId()} of the - * credentials to use for verify the signature of payload. - */ @DataBoundConstructor - public BitbucketCloudEndpoint(boolean enableCache, int teamCacheDuration, int repositoriesCacheDuration, - boolean manageHooks, @CheckForNull String credentialsId, - boolean enableHookSignature, @CheckForNull String hookSignatureCredentialsId) { - super(manageHooks, credentialsId, enableHookSignature, hookSignatureCredentialsId); + public BitbucketCloudEndpoint(boolean enableCache, int teamCacheDuration, int repositoriesCacheDuration, @NonNull BitbucketWebhook webhook) { + super(webhook); this.enableCache = enableCache; this.teamCacheDuration = teamCacheDuration; this.repositoriesCacheDuration = repositoriesCacheDuration; @@ -126,19 +99,9 @@ public String getDisplayName() { return Messages.BitbucketCloudEndpoint_displayName(); } - /** - * {@inheritDoc} - */ - @Override - @NonNull - @Deprecated(since = "936.4.0", forRemoval = true) - public String getServerUrl() { - return SERVER_URL; - } - @Override public String getServerURL() { - return getServerUrl(); + return SERVER_URL; } /** @@ -191,11 +154,4 @@ public FormValidation doClear() { } } - private Object readResolve() { - if (getBitbucketJenkinsRootUrl() != null) { - setBitbucketJenkinsRootUrl(getBitbucketJenkinsRootUrl()); - } - return this; - } - } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint.java index 6050497a1..c4856b8ee 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint.java @@ -26,14 +26,13 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointDescriptor; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.EndpointType; +import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhook; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerWebhook; import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerVersion; -import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation; -import com.cloudbees.plugins.credentials.common.StandardCredentials; import com.damnhandy.uri.template.UriTemplate; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Extension; import hudson.Util; import hudson.util.FormValidation; @@ -42,13 +41,11 @@ import java.net.URL; import jenkins.scm.api.SCMName; import org.apache.commons.lang3.StringUtils; -import org.jenkinsci.plugins.plaincredentials.StringCredentials; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.interceptor.RequirePOST; /** * Represents a Bitbucket Server instance. @@ -71,10 +68,8 @@ public class BitbucketServerEndpoint extends AbstractBitbucketEndpoint { }; @NonNull - public static BitbucketServerWebhookImplementation findWebhookImplementation(String serverURL) { - return /*BitbucketEndpointProvider.lookupEndpoint(serverURL, BitbucketServerEndpoint.class) - .map(BitbucketServerEndpoint::getWebhookImplementation) - .orElse(BitbucketServerWebhookImplementation.NATIVE);*/ null; + public static boolean supportsMirror(String serverURL) { + return false; // depends on webhook type } @NonNull @@ -95,10 +90,7 @@ public static BitbucketServerVersion findServerVersion(String serverURL) { * The URL of this Bitbucket Server. */ @NonNull - private final String serverUrl; - -// @NonNull -// private BitbucketServerWebhookImplementation webhookImplementation = BitbucketServerWebhookImplementation.NATIVE; + private final String serverURL; /** * The server version for this endpoint. @@ -119,40 +111,25 @@ public static BitbucketServerVersion findServerVersion(String serverURL) { * Default constructor. * @param serverURL */ - public BitbucketServerEndpoint(@NonNull String serverURL) { - this(null, serverURL, false, null, false, null); - } - - @Deprecated(since = "936.3.1") - public BitbucketServerEndpoint(@CheckForNull String displayName, @NonNull String serverUrl, - boolean manageHooks, @CheckForNull String credentialsId) { - this(displayName, serverUrl, manageHooks, credentialsId, false, null); + public BitbucketServerEndpoint(@CheckForNull String displayName, @NonNull String serverURL) { + this(displayName, serverURL, new ServerWebhook(false, null, false, null)); } /** * Constructor. * * @param displayName Optional name to use to describe the end-point. - * @param serverUrl The URL of this Bitbucket Server - * @param manageHooks {@code true} if and only if Jenkins is supposed to - * auto-manage hooks for this end-point. - * @param credentialsId The {@link StandardCredentials#getId()} of the - * credentials to use for auto-management of hooks. - * @param enableHookSignature {@code true} hooks that comes Bitbucket Data - * Center are signed. - * @param hookSignatureCredentialsId The {@link StringCredentials#getId()} of the - * credentials to use for verify the signature of payload. + * @param serverURL The URL of this Bitbucket Server + * @param webhook implementation to work for this end-point. */ @DataBoundConstructor - public BitbucketServerEndpoint(@CheckForNull String displayName, @NonNull String serverUrl, - boolean manageHooks, @CheckForNull String credentialsId, - boolean enableHookSignature, @CheckForNull String hookSignatureCredentialsId) { - super(manageHooks, credentialsId, enableHookSignature, hookSignatureCredentialsId); + public BitbucketServerEndpoint(@CheckForNull String displayName, @NonNull String serverURL, @NonNull BitbucketWebhook webhook) { + super(webhook); // use fixNull to silent nullability check - this.serverUrl = Util.fixNull(URLUtils.normalizeURL(serverUrl)); + this.serverURL = Util.fixNull(URLUtils.normalizeURL(serverURL)); this.displayName = StringUtils.isBlank(displayName) - ? SCMName.fromUrl(this.serverUrl, COMMON_PREFIX_HOSTNAMES) - : displayName.trim(); + ? SCMName.fromUrl(this.serverURL, COMMON_PREFIX_HOSTNAMES) + : displayName.trim(); } public boolean isCallCanMerge() { @@ -201,28 +178,9 @@ public String getDisplayName() { return displayName; } - /** - * {@inheritDoc} - */ - @Override - @NonNull - @Deprecated(since = "936.4.0", forRemoval = true) - public String getServerUrl() { - return serverUrl; - } - @Override public String getServerURL() { - return getServerUrl(); - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public String getRepositoryUrl(@NonNull String repoOwner, @NonNull String repository) { - return getRepositoryURL(repoOwner, repository); + return serverURL; } /** @@ -232,54 +190,28 @@ public String getRepositoryUrl(@NonNull String repoOwner, @NonNull String reposi @Override public String getRepositoryURL(@NonNull String repoOwner, @NonNull String repository) { UriTemplate template = UriTemplate - .fromTemplate(serverUrl + "/{userOrProject}/{owner}/repos/{repo}") + .fromTemplate(serverURL + "/{userOrProject}/{owner}/repos/{repo}") .set("repo", repository); return repoOwner.startsWith("~") ? template.set("userOrProject", "users").set("owner", repoOwner.substring(1)).expand() : template.set("userOrProject", "projects").set("owner", repoOwner).expand(); } - @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "Only non-null after we set them here!") - private Object readResolve() { -// if (webhookImplementation == null) { -// webhookImplementation = BitbucketServerWebhookImplementation.NATIVE; -// } - if (getBitbucketJenkinsRootUrl() != null) { - setBitbucketJenkinsRootUrl(getBitbucketJenkinsRootUrl()); - } + @Override +// @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "Only non-null after we set them here!") + protected Object readResolve() { if (serverVersion == null) { serverVersion = BitbucketServerVersion.getMinSupportedVersion(); } - - return this; + return super.readResolve(); } -// @NonNull -// public BitbucketServerWebhookImplementation getWebhookImplementation() { -// return webhookImplementation; -// } - -// @DataBoundSetter -// public void setWebhookImplementation(@NonNull BitbucketServerWebhookImplementation webhookImplementation) { -// this.webhookImplementation = requireNonNull(webhookImplementation); -// } - /** * Our descriptor. */ @Extension public static class DescriptorImpl extends BitbucketEndpointDescriptor { - @Restricted(NoExternalUse.class) // stapler - @RequirePOST - public FormValidation doCheckEnableHookSignature(@QueryParameter BitbucketServerWebhookImplementation webhookImplementation, - @QueryParameter boolean enableHookSignature) { - if (enableHookSignature && webhookImplementation == BitbucketServerWebhookImplementation.PLUGIN) { - return FormValidation.error("Signature verification not supported for PLUGIN webhook"); - } - return FormValidation.ok(); - } - /** * {@inheritDoc} */ @@ -289,16 +221,6 @@ public String getDisplayName() { return Messages.BitbucketServerEndpoint_displayName(); } - @Restricted(NoExternalUse.class) - public ListBoxModel doFillWebhookImplementationItems() { - ListBoxModel items = new ListBoxModel(); - for (BitbucketServerWebhookImplementation webhookImplementation : BitbucketServerWebhookImplementation.values()) { - items.add(webhookImplementation, webhookImplementation.name()); - } - - return items; - } - @Restricted(NoExternalUse.class) public ListBoxModel doFillServerVersionItems() { ListBoxModel items = new ListBoxModel(); @@ -315,7 +237,7 @@ public ListBoxModel doFillServerVersionItems() { * @param value the URL to check. * @return the validation results. */ - public static FormValidation doCheckServerUrl(@QueryParameter String value) { + public static FormValidation doCheckServerURL(@QueryParameter String value) { try { new URL(value); } catch (MalformedURLException e) { diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook.java similarity index 72% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook.java index b52491346..75e86cf46 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook.java @@ -25,18 +25,12 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhook; import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookDescriptor; -import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils; -import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.common.StandardCredentials; -import com.cloudbees.plugins.credentials.common.StandardListBoxModel; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; -import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder; import edu.umd.cs.findbugs.annotations.CheckForNull; import hudson.Util; import hudson.model.Descriptor; -import hudson.security.ACL; import hudson.util.FormValidation; -import hudson.util.ListBoxModel; import java.net.MalformedURLException; import java.net.URL; import jenkins.model.Jenkins; @@ -51,7 +45,7 @@ import static hudson.Util.fixEmptyAndTrim; @Restricted(NoExternalUse.class) -public abstract class AbstractBitbucketWebhook implements BitbucketWebhook { +public abstract class AbstractWebhook implements BitbucketWebhook { /** * {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point. @@ -84,7 +78,7 @@ public abstract class AbstractBitbucketWebhook implements BitbucketWebhook { */ private String endpointJenkinsRootURL; - protected AbstractBitbucketWebhook(boolean manageHooks, @CheckForNull String credentialsId, + protected AbstractWebhook(boolean manageHooks, @CheckForNull String credentialsId, boolean enableHookSignature, @CheckForNull String hookSignatureCredentialsId) { this.manageHooks = manageHooks && StringUtils.isNotBlank(credentialsId); this.credentialsId = manageHooks ? fixEmptyAndTrim(credentialsId) : null; @@ -150,44 +144,6 @@ public Descriptor getDescriptor() { public abstract static class AbstractBitbucketWebhookDescriptorImpl extends BitbucketWebhookDescriptor { - /** - * Stapler form completion. - * - * @param credentialsId selected credentials. - * @param serverURL the server URL. - * @return the available credentials. - */ - @RequirePOST - public ListBoxModel doFillCredentialsIdItems(@QueryParameter(fixEmpty = true) String credentialsId, - @QueryParameter(value = "serverUrl", fixEmpty = true) String serverURL) { - Jenkins jenkins = checkPermission(); - return BitbucketCredentialsUtils.listCredentials(jenkins, serverURL, credentialsId); - } - - /** - * Stapler form completion. - * - * @param hookSignatureCredentialsId selected hook signature credentials. - * @param serverURL the server URL. - * @return the available credentials. - */ - @RequirePOST - public ListBoxModel doFillHookSignatureCredentialsIdItems(@QueryParameter(fixEmpty = true) String hookSignatureCredentialsId, - @QueryParameter(value = "serverUrl", fixEmpty = true) String serverURL) { - Jenkins jenkins = checkPermission(); - StandardListBoxModel result = new StandardListBoxModel(); - result.includeMatchingAs(ACL.SYSTEM2, - jenkins, - StringCredentials.class, - URIRequirementBuilder.fromUri(serverURL).build(), - CredentialsMatchers.always()); - if (hookSignatureCredentialsId != null) { - result.includeCurrentValue(hookSignatureCredentialsId); - } - return result; - } - - @Restricted(NoExternalUse.class) @RequirePOST public static FormValidation doCheckEndpointJenkinsRootURL(@QueryParameter String value) { checkPermission(); @@ -203,7 +159,7 @@ public static FormValidation doCheckEndpointJenkinsRootURL(@QueryParameter Strin return FormValidation.ok(); } - private static Jenkins checkPermission() { + protected static Jenkins checkPermission() { Jenkins jenkins = Jenkins.get(); jenkins.checkPermission(Jenkins.MANAGE); return jenkins; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/PREvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPREvent.java similarity index 97% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/PREvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPREvent.java index 78845050e..96b520346 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/PREvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPREvent.java @@ -50,10 +50,10 @@ import static com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType.PULL_REQUEST_DECLINED; import static com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType.PULL_REQUEST_MERGED; -final class PREvent extends AbstractSCMHeadEvent implements HasPullRequests { +final class CloudPREvent extends AbstractSCMHeadEvent implements HasPullRequests { private final HookEventType hookEvent; - PREvent(Type type, BitbucketPullRequestEvent payload, + CloudPREvent(Type type, BitbucketPullRequestEvent payload, String origin, HookEventType hookEvent) { super(type, payload, origin); diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPullRequestWebhookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPullRequestWebhookProcessor.java index 30cf6de1f..781742fe7 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPullRequestWebhookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPullRequestWebhookProcessor.java @@ -84,7 +84,7 @@ public void process(@NonNull String hookEventType, @NonNull String payload, @Non break; } // assume updated as a catch-all type - notifyEvent(new PREvent(eventType, pull, getOrigin(context), hookEvent), BitbucketSCMSource.getEventDelaySeconds()); + notifyEvent(new CloudPREvent(eventType, pull, getOrigin(context), hookEvent), BitbucketSCMSource.getEventDelaySeconds()); } } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/PushEvent.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPushEvent.java similarity index 96% rename from src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/PushEvent.java rename to src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPushEvent.java index e6c29c857..d1ef7f136 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/PushEvent.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPushEvent.java @@ -41,9 +41,9 @@ import jenkins.scm.api.SCMSource; import org.apache.commons.lang3.StringUtils; -final class PushEvent extends AbstractSCMHeadEvent { +final class CloudPushEvent extends AbstractSCMHeadEvent { - PushEvent(Type type, BitbucketPushEvent payload, String origin) { + CloudPushEvent(Type type, BitbucketPushEvent payload, String origin) { super(type, payload, origin); } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPushWebhookProcessor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPushWebhookProcessor.java index 14cd0bed8..78669ccb9 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPushWebhookProcessor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPushWebhookProcessor.java @@ -81,7 +81,7 @@ public void process(@NonNull String hookEventType, @NonNull String payload, @Non type = SCMEvent.Type.UPDATED; } } - notifyEvent(new PushEvent(type, push, getOrigin(context)), BitbucketSCMSource.getEventDelaySeconds()); + notifyEvent(new CloudPushEvent(type, push, getOrigin(context)), BitbucketSCMSource.getEventDelaySeconds()); } } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudWebhook.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudWebhook.java index fa9a9a644..7e30543c1 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudWebhook.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudWebhook.java @@ -23,13 +23,29 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud; -import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractBitbucketWebhook; +import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractWebhook; import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.Messages; +import com.cloudbees.plugins.credentials.CredentialsMatchers; +import com.cloudbees.plugins.credentials.common.StandardListBoxModel; +import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder; import hudson.Extension; +import hudson.security.ACL; +import hudson.util.ListBoxModel; +import jenkins.model.Jenkins; import org.jenkinsci.Symbol; +import org.jenkinsci.plugins.plaincredentials.StringCredentials; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; -public class CloudWebhook extends AbstractBitbucketWebhook { +public class CloudWebhook extends AbstractWebhook { + + public CloudWebhook(boolean manageHooks, String credentialsId) { + this(manageHooks, credentialsId, false, null); + } @DataBoundConstructor public CloudWebhook(boolean manageHooks, String credentialsId, boolean enableHookSignature, String hookSignatureCredentialsId) { @@ -55,5 +71,44 @@ public String getDisplayName() { return "Native Cloud"; } + @Override + public boolean isApplicable(String serverURL) { + return BitbucketApiUtils.isCloud(serverURL); + } + + /** + * Stapler form completion. + * + * @param credentialsId selected credentials. + * @param serverURL the server URL. + * @return the available credentials. + */ + @RequirePOST + public ListBoxModel doFillCredentialsIdItems(@QueryParameter(fixEmpty = true) String credentialsId) { + Jenkins jenkins = checkPermission(); + return BitbucketCredentialsUtils.listCredentials(jenkins, BitbucketCloudEndpoint.SERVER_URL, credentialsId); + } + + /** + * Stapler form completion. + * + * @param hookSignatureCredentialsId selected hook signature credentials. + * @param serverURL the server URL. + * @return the available credentials. + */ + @RequirePOST + public ListBoxModel doFillHookSignatureCredentialsIdItems(@QueryParameter(fixEmpty = true) String hookSignatureCredentialsId) { + Jenkins jenkins = checkPermission(); + StandardListBoxModel result = new StandardListBoxModel(); + result.includeMatchingAs(ACL.SYSTEM2, + jenkins, + StringCredentials.class, + URIRequirementBuilder.fromUri(BitbucketCloudEndpoint.SERVER_URL).build(), + CredentialsMatchers.always()); + if (hookSignatureCredentialsId != null) { + result.includeCurrentValue(hookSignatureCredentialsId); + } + return result; + } } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook.java index 9bfca9119..7c6fe54bb 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhook.java @@ -25,6 +25,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhook; import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookDescriptor; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils; import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.Messages; import com.cloudbees.plugins.credentials.common.StandardCredentials; @@ -137,6 +138,11 @@ public String getDisplayName() { return "Post Webhooks for Bitbucket"; } + @Override + public boolean isApplicable(String serverURL) { + return !BitbucketApiUtils.isCloud(serverURL); + } + /** * Stapler form completion. * @@ -146,7 +152,7 @@ public String getDisplayName() { */ @RequirePOST public ListBoxModel doFillCredentialsIdItems(@QueryParameter(fixEmpty = true) String credentialsId, - @QueryParameter(value = "serverUrl", fixEmpty = true) String serverURL) { + @QueryParameter(value = "serverURL", fixEmpty = true) String serverURL) { Jenkins jenkins = checkPermission(); return BitbucketCredentialsUtils.listCredentials(jenkins, serverURL, credentialsId); } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerWebhook.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerWebhook.java index aaef5a889..97f187296 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerWebhook.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerWebhook.java @@ -23,14 +23,29 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server; -import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractBitbucketWebhook; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractWebhook; import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.Messages; +import com.cloudbees.plugins.credentials.CredentialsMatchers; +import com.cloudbees.plugins.credentials.common.StandardListBoxModel; +import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder; import edu.umd.cs.findbugs.annotations.CheckForNull; import hudson.Extension; +import hudson.security.ACL; +import hudson.util.ListBoxModel; +import jenkins.model.Jenkins; import org.jenkinsci.Symbol; +import org.jenkinsci.plugins.plaincredentials.StringCredentials; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; -public class ServerWebhook extends AbstractBitbucketWebhook { +public class ServerWebhook extends AbstractWebhook { + + public ServerWebhook(boolean manageHooks, @CheckForNull String credentialsId) { + super(manageHooks, credentialsId, false, null); + } @DataBoundConstructor public ServerWebhook(boolean manageHooks, @CheckForNull String credentialsId, @@ -56,5 +71,47 @@ public static class DescriptorImpl extends AbstractBitbucketWebhookDescriptorImp public String getDisplayName() { return "Native Data Center"; } + + @Override + public boolean isApplicable(String serverURL) { + return !BitbucketApiUtils.isCloud(serverURL); + } + + /** + * Stapler form completion. + * + * @param credentialsId selected credentials. + * @param serverURL the server URL. + * @return the available credentials. + */ + @RequirePOST + public ListBoxModel doFillCredentialsIdItems(@QueryParameter(fixEmpty = true) String credentialsId, + @QueryParameter(value = "serverURL", fixEmpty = true) String serverURL) { + Jenkins jenkins = checkPermission(); + return BitbucketCredentialsUtils.listCredentials(jenkins, serverURL, credentialsId); + } + + /** + * Stapler form completion. + * + * @param hookSignatureCredentialsId selected hook signature credentials. + * @param serverURL the server URL. + * @return the available credentials. + */ + @RequirePOST + public ListBoxModel doFillHookSignatureCredentialsIdItems(@QueryParameter(fixEmpty = true) String hookSignatureCredentialsId, + @QueryParameter(value = "serverURL", fixEmpty = true) String serverURL) { + Jenkins jenkins = checkPermission(); + StandardListBoxModel result = new StandardListBoxModel(); + result.includeMatchingAs(ACL.SYSTEM2, + jenkins, + StringCredentials.class, + URIRequirementBuilder.fromUri(serverURL).build(), + CredentialsMatchers.always()); + if (hookSignatureCredentialsId != null) { + result.includeCurrentValue(hookSignatureCredentialsId); + } + return result; + } } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/BitbucketServerWebhookImplementation.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/BitbucketServerWebhookImplementation.java deleted file mode 100644 index 445fc5cae..000000000 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/BitbucketServerWebhookImplementation.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2016-2018, Yieldlab AG - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.cloudbees.jenkins.plugins.bitbucket.server; - -import hudson.model.ModelObject; - -/** The different webhook implementations available for Bitbucket Server. */ -public enum BitbucketServerWebhookImplementation implements ModelObject { - /** Plugin-based webhooks. */ - PLUGIN("Plugin"), - - /** Native webhooks, available since Bitbucket Server 5.4. */ - NATIVE("Native"); - - private final String displayName; - - BitbucketServerWebhookImplementation(String displayName) { - this.displayName = displayName; - } - - @Override - public String getDisplayName() { - return displayName; - } -} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java index 0c9f972c9..7d05f8fb2 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java @@ -46,17 +46,14 @@ import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.JsonParser; -import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation; import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBranch; import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBranches; import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBuildStatus; import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerCommit; import com.cloudbees.jenkins.plugins.bitbucket.server.client.pullrequest.BitbucketServerPullRequest; import com.cloudbees.jenkins.plugins.bitbucket.server.client.pullrequest.BitbucketServerPullRequestCanMerge; -import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketPluginWebhook; import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerProject; import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerRepository; -import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerWebhook; import com.damnhandy.uri.template.UriTemplate; import com.damnhandy.uri.template.impl.Operator; import com.fasterxml.jackson.core.JacksonException; @@ -75,7 +72,6 @@ import java.lang.reflect.ParameterizedType; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -157,17 +153,10 @@ public class BitbucketServerAPIClient extends AbstractBitbucketApi implements Bi */ private final boolean userCentric; private final String baseURL; - private final BitbucketServerWebhookImplementation webhookImplementation; private final CloseableHttpClient client; public BitbucketServerAPIClient(@NonNull String baseURL, @NonNull String owner, @CheckForNull String repositoryName, @CheckForNull BitbucketAuthenticator authenticator, boolean userCentric) { - this(baseURL, owner, repositoryName, authenticator, userCentric, BitbucketServerEndpoint.findWebhookImplementation(baseURL)); - } - - public BitbucketServerAPIClient(@NonNull String baseURL, @NonNull String owner, @CheckForNull String repositoryName, - @CheckForNull BitbucketAuthenticator authenticator, boolean userCentric, - @NonNull BitbucketServerWebhookImplementation webhookImplementation) { super(authenticator); this.userCentric = userCentric; this.owner = Util.fixEmptyAndTrim(owner); @@ -176,7 +165,6 @@ public BitbucketServerAPIClient(@NonNull String baseURL, @NonNull String owner, } this.repositoryName = repositoryName; this.baseURL = Util.removeTrailingSlash(baseURL); - this.webhookImplementation = BitbucketServerWebhookImplementation.NATIVE; this.client = setupClientBuilder().build(); } @@ -639,6 +627,7 @@ public BitbucketCommit resolveCommit(@NonNull BitbucketPullRequest pull) throws @Override public void registerCommitWebHook(BitbucketWebHook hook) throws IOException { + /* switch (webhookImplementation) { case PLUGIN: // API documentation at https://help.moveworkforward.com/BPW/how-to-manage-configurations-using-post-webhooks-f#HowtomanageconfigurationsusingPostWebhooksforBitbucketAPIs?-Createpostwebhook @@ -667,10 +656,12 @@ public void registerCommitWebHook(BitbucketWebHook hook) throws IOException { logger.log(Level.WARNING, "Cannot register {0} webhook.", webhookImplementation); break; } + */ } @Override public void updateCommitWebHook(BitbucketWebHook hook) throws IOException { + /* switch (webhookImplementation) { case PLUGIN: // API documentation at https://help.moveworkforward.com/BPW/how-to-manage-configurations-using-post-webhooks-f#HowtomanageconfigurationsusingPostWebhooksforBitbucketAPIs?-UpdateapostwebhookbyID @@ -699,10 +690,12 @@ public void updateCommitWebHook(BitbucketWebHook hook) throws IOException { logger.log(Level.WARNING, "Cannot update {0} webhook.", webhookImplementation); break; } + */ } @Override public void removeCommitWebHook(BitbucketWebHook hook) throws IOException { + /* switch (webhookImplementation) { case PLUGIN: deleteRequest( @@ -730,11 +723,13 @@ public void removeCommitWebHook(BitbucketWebHook hook) throws IOException { logger.log(Level.WARNING, "Cannot remove {0} webhook.", webhookImplementation); break; } + */ } @NonNull @Override public List getWebHooks() throws IOException { + /* switch (webhookImplementation) { case PLUGIN: String url = UriTemplate @@ -750,7 +745,7 @@ public List getWebHooks() throws IOException { .set("repo", repositoryName); return getPagedRequest(uriTemplate, BitbucketServerWebhook.class); } - +*/ return Collections.emptyList(); } diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/help-bitbucketJenkinsRootUrl.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/help-bitbucketJenkinsRootUrl.html deleted file mode 100644 index ff9bbb027..000000000 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/help-bitbucketJenkinsRootUrl.html +++ /dev/null @@ -1,6 +0,0 @@ -
- You can customize the Jenkins Server Root URL to be used by this Bitbucket endpoint, - instead of the one set in Jenkins global configuration. This may be useful in local - networks with different DNS views for different clients (such as a Bitbucket server) - or to receive webhooks through some tunneling or relaying service hosted elsewhere. -
diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/help-serverUrl.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/help-serverURL.html similarity index 100% rename from src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/help-serverUrl.html rename to src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/help-serverURL.html diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/help-webhookImplementation.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/help-webhookImplementation.html deleted file mode 100644 index 5386867c9..000000000 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/help-webhookImplementation.html +++ /dev/null @@ -1,11 +0,0 @@ -
- Determines which Webhook implementation is to be used: -
-
Plugin
-
The third party Webhook implementation provided by the Post Webhooks for Bitbucket plugin.
-
Please note cloning from mirror is not supported with this implementation.
- -
Native
-
The native Webhook implementation available since Bitbucket Server 5.4.
-
-
diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/manage-hooks-detail.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/manage-hooks-detail.jelly deleted file mode 100644 index a6c1e0e39..000000000 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpoint/manage-hooks-detail.jelly +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-credentialsId.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-credentialsId.html deleted file mode 100644 index b7061f123..000000000 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-credentialsId.html +++ /dev/null @@ -1,7 +0,0 @@ -
- Select the credentials to use for managing hooks. Both GLOBAL and SYSTEM scoped credentials are eligible as the - management of hooks is run in the context of Jenkins itself and not in the context of the individual items. -

- For security reasons most credentials are only available when HTTPS is used. -

-
diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-enableHookSignature.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-enableHookSignature.html deleted file mode 100644 index 0c9ca8c6f..000000000 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-enableHookSignature.html +++ /dev/null @@ -1,4 +0,0 @@ -
- Verify the signature of any incoming hook payload from this endpoint. This means that any payload must be signed, - if not payload will be rejected. -
\ No newline at end of file diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-hookSignatureCredentialsId.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-hookSignatureCredentialsId.html deleted file mode 100644 index 0af1f6438..000000000 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-hookSignatureCredentialsId.html +++ /dev/null @@ -1,9 +0,0 @@ -
- Select the credentials to use to verify the signature of incoming hooks. Both GLOBAL and SYSTEM scoped credentials - are eligible as the management of hooks is run in the context of Jenkins itself and not in the context of the - individual items. -

- If the automatic management of web hooks is disabled than you have to setup manually in each repository selected - credentials or any untrusted hook will be discarded. -

-
\ No newline at end of file diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-manageHooks.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-manageHooks.html deleted file mode 100644 index 391b54782..000000000 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-manageHooks.html +++ /dev/null @@ -1,6 +0,0 @@ -
- Selecting this option will enable the automatic management of web hooks for all items that use this - endpoint, except those items that have explicitly opted out of hook management. - When this option is not selected, individual items can still opt in to hook management provided the credentials - those items have been configured with have permission to manage the required hooks. -
diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook/config.jelly similarity index 100% rename from src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/config.jelly rename to src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook/config.jelly diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/help-credentialsId.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook/help-credentialsId.html similarity index 100% rename from src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/help-credentialsId.html rename to src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook/help-credentialsId.html diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/help-enableHookSignature.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook/help-enableHookSignature.html similarity index 100% rename from src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/help-enableHookSignature.html rename to src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook/help-enableHookSignature.html diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-endpointJenkinsRootURL.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook/help-endpointJenkinsRootURL.html similarity index 100% rename from src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhook/help-endpointJenkinsRootURL.html rename to src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook/help-endpointJenkinsRootURL.html diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/help-hookSignatureCredentialsId.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook/help-hookSignatureCredentialsId.html similarity index 100% rename from src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/help-hookSignatureCredentialsId.html rename to src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook/help-hookSignatureCredentialsId.html diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/help-manageHooks.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook/help-manageHooks.html similarity index 100% rename from src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/AbstractBitbucketEndpoint/help-manageHooks.html rename to src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhook/help-manageHooks.html diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningIntegrationTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningIntegrationTest.java index 685ef34a6..22e2a9582 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningIntegrationTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningIntegrationTest.java @@ -64,7 +64,7 @@ public class BranchScanningIntegrationTest { @Test public void indexingTest() throws Exception { BitbucketEndpointConfiguration.get() - .addEndpoint(new BitbucketServerEndpoint("test", "http://bitbucket.test", false, null, false, null)); + .addEndpoint(new BitbucketServerEndpoint("test", "http://bitbucket.test")); BitbucketMockApiFactory.add("http://bitbucket.test", BitbucketClientMockUtils.getAPIClientMock(false, false)); MockMultiBranchProjectImpl p = j.jenkins.createProject(MockMultiBranchProjectImpl.class, "test"); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/WebhooksAutoregisterTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/WebhooksAutoregisterTest.java index 3f3944cf1..b82d9cadd 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/WebhooksAutoregisterTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/WebhooksAutoregisterTest.java @@ -28,6 +28,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.cloudbees.jenkins.plugins.bitbucket.hooks.WebhookAutoRegisterListener; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.CloudWebhook; import com.cloudbees.jenkins.plugins.bitbucket.trait.WebhookRegistrationTrait; import hudson.model.listeners.ItemListener; import hudson.util.RingBufferLogHandler; @@ -82,7 +83,7 @@ void registerHookTest() throws Exception { @Test void registerHookTest2() throws Exception { - BitbucketEndpointConfiguration.get().setEndpoints(List.of(new BitbucketCloudEndpoint(false, 0, 0, true, "dummy", false, null))); + BitbucketEndpointConfiguration.get().setEndpoints(List.of(new BitbucketCloudEndpoint(false, 0, 0, new CloudWebhook(true, "dummy")))); BitbucketApi mock = Mockito.mock(BitbucketApi.class); BitbucketMockApiFactory.add(BitbucketCloudEndpoint.SERVER_URL, mock); RingBufferLogHandler log = createJULTestHandler(); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketIntegrationClientFactory.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketIntegrationClientFactory.java index 9b1f08ea3..03f257a31 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketIntegrationClientFactory.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketIntegrationClientFactory.java @@ -26,7 +26,6 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; -import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation; import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerAPIClient; import java.io.FileNotFoundException; import java.io.IOException; @@ -143,7 +142,7 @@ private static class BitbucketServerIntegrationClient extends BitbucketServerAPI private final IRequestAudit audit; private BitbucketServerIntegrationClient(String payloadRootPath, String baseURL, String owner, String repositoryName) { - super(baseURL, owner, repositoryName, mock(BitbucketAuthenticator.class), false, BitbucketServerWebhookImplementation.NATIVE); + super(baseURL, owner, repositoryName, mock(BitbucketAuthenticator.class), false); if (payloadRootPath == null) { this.payloadRootPath = PAYLOAD_RESOURCE_ROOTPATH; diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfigurationTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfigurationTest.java index 11be8f02f..a2385bc5c 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfigurationTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfigurationTest.java @@ -28,6 +28,8 @@ import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.Messages; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.CloudWebhook; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerWebhook; import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerVersion; import com.cloudbees.plugins.credentials.Credentials; import com.cloudbees.plugins.credentials.CredentialsScope; @@ -102,7 +104,7 @@ void given__newInstance__when__configuredWithEmpty__then__cloudPresent() { void given__newInstance__when__configuredWithCloud__then__cloudPresent() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); assumeFalse("dummy".equals(instance.getEndpoints().get(0).getCredentialsId())); - instance.setEndpoints(List.of(buildEndpoint(true, "dummy"))); + instance.setEndpoints(List.of(buildCloudEndpoint(true, "dummy"))); assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketCloudEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { assertThat(endpoint.getCredentialsId()).isEqualTo("dummy"); @@ -113,7 +115,7 @@ void given__newInstance__when__configuredWithCloud__then__cloudPresent() { void given__newInstance__when__configuredWithMultipleCloud__then__onlyFirstCloudPresent() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); assumeFalse("first".equals(instance.getEndpoints().get(0).getCredentialsId())); - instance.setEndpoints(List.of(buildEndpoint(true, "first"), buildEndpoint(true, "second"), buildEndpoint(true, "third"))); + instance.setEndpoints(List.of(buildCloudEndpoint(true, "first"), buildCloudEndpoint(true, "second"), buildCloudEndpoint(true, "third"))); assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketCloudEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { assertThat(endpoint.getCredentialsId()).isEqualTo("first"); @@ -126,7 +128,7 @@ void given__newInstance__when__configuredAsAnon__then__permissionError() { r.jenkins.setAuthorizationStrategy(new FullControlOnceLoggedInAuthorizationStrategy()); try (ACLContext context = ACL.as2(Jenkins.ANONYMOUS2)) { assertThatThrownBy(() -> - instance.setEndpoints(List.of(buildEndpoint(true, "first"), buildEndpoint(true, "second"), buildEndpoint(true, "third"))) + instance.setEndpoints(List.of(buildCloudEndpoint(true, "first"), buildCloudEndpoint(true, "second"), buildCloudEndpoint(true, "third"))) ) .hasMessage(hudson.security.Messages.AccessDeniedException2_MissingPermission("anonymous", "Overall/Administer")); } finally { @@ -142,7 +144,7 @@ void given__newInstance__when__configuredAsManage__then__OK() { mockStrategy.grant(Jenkins.MANAGE).onRoot().to("admin"); r.jenkins.setAuthorizationStrategy(mockStrategy); try (ACLContext context = ACL.as(User.get("admin"))) { - instance.setEndpoints(List.of(buildEndpoint(true, "first"), buildEndpoint(true, "second"), buildEndpoint(true, "third"))); + instance.setEndpoints(List.of(buildCloudEndpoint(true, "first"), buildCloudEndpoint(true, "second"), buildCloudEndpoint(true, "third"))); assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketCloudEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { assertThat(endpoint.getCredentialsId()).isEqualTo("first"); @@ -157,9 +159,9 @@ void given__newInstance__when__configuredWithServerUsingCloudUrl__then__converte BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); assumeFalse("dummy".equals(instance.getEndpoints().get(0).getCredentialsId())); instance.setEndpoints(List.of( - new BitbucketServerEndpoint("I am silly", BitbucketCloudEndpoint.SERVER_URL, true, "dummy"), - buildEndpoint(true, "second"), - buildEndpoint(true, "third"))); + new BitbucketServerEndpoint("I am silly", BitbucketCloudEndpoint.SERVER_URL, new ServerWebhook(true, "dummy")), + buildCloudEndpoint(true, "second"), + buildCloudEndpoint(true, "third"))); assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketCloudEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { assertThat(endpoint.getCredentialsId()).isEqualTo("dummy"); @@ -170,7 +172,7 @@ void given__newInstance__when__configuredWithServerUsingCloudUrl__then__converte void given__newInstance__when__configuredWithServer__then__serverPresent() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); assumeFalse("dummy".equals(instance.getEndpoints().get(0).getCredentialsId())); - instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "dummy"))); + instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "dummy")))); assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketServerEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { @@ -186,8 +188,8 @@ void given__newInstance__when__configuredWithTwoServers__then__serversPresent() BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); assumeFalse("dummy".equals(instance.getEndpoints().get(0).getCredentialsId())); instance.setEndpoints(List.of( - new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "dummy"), - new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", false, null))); + new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "dummy")), + new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/"))); assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketServerEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { @@ -207,7 +209,7 @@ void given__newInstance__when__configuredWithTwoServers__then__serversPresent() @Test void given__instanceWithCloud__when__addingAnotherCloud__then__onlyFirstCloudRetained() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints(List.of(buildEndpoint(true, "dummy"))); + instance.setEndpoints(List.of(buildCloudEndpoint(true, "dummy"))); assumeTrue("dummy".equals(instance.getEndpoints().get(0).getCredentialsId())); assertThat(instance.addEndpoint(new BitbucketCloudEndpoint())).isFalse(); @@ -220,10 +222,10 @@ void given__instanceWithCloud__when__addingAnotherCloud__then__onlyFirstCloudRet @Test void given__instanceWithServer__when__addingCloud__then__cloudAdded() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "dummy"))); + instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "dummy")))); assumeTrue("dummy".equals(instance.getEndpoints().get(0).getCredentialsId())); - assertThat(instance.addEndpoint(buildEndpoint(true, "added"))).isTrue(); + assertThat(instance.addEndpoint(buildCloudEndpoint(true, "added"))).isTrue(); assertThat(instance.getEndpoints()).hasExactlyElementsOfTypes(BitbucketServerEndpoint.class, BitbucketCloudEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { assertThat(endpoint.getCredentialsId()).isEqualTo("dummy"); @@ -240,10 +242,10 @@ void given__instanceWithServer__when__addingCloud__then__cloudAdded() { @Test void given__instanceWithServer__when__addingDifferentServer__then__serverAdded() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "dummy"))); + instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "dummy")))); assumeTrue("dummy".equals(instance.getEndpoints().get(0).getCredentialsId())); - assertThat(instance.addEndpoint(new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "added"))).isTrue(); + assertThat(instance.addEndpoint(new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", new ServerWebhook(true, "added")))).isTrue(); assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketServerEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { assertThat(endpoint.getCredentialsId()).isEqualTo("dummy"); @@ -256,10 +258,10 @@ void given__instanceWithServer__when__addingDifferentServer__then__serverAdded() @Test void given__instanceWithServer__when__addingSameServer__then__onlyFirstServerRetained() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "dummy"))); + instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "dummy")))); assumeTrue("dummy".equals(instance.getEndpoints().get(0).getCredentialsId())); - assertThat(instance.addEndpoint(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", false, null))).isFalse(); + assertThat(instance.addEndpoint(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/"))).isFalse(); assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketServerEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { assertThat(endpoint.getCredentialsId()).isEqualTo("dummy"); @@ -269,7 +271,7 @@ void given__instanceWithServer__when__addingSameServer__then__onlyFirstServerRet @Test void given__instanceWithCloud__when__updatingCloud__then__cloudUpdated() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints(List.of(buildEndpoint(true, "dummy"))); + instance.setEndpoints(List.of(buildCloudEndpoint(true, "dummy"))); assumeTrue("dummy".equals(instance.getEndpoints().get(0).getCredentialsId())); instance.updateEndpoint(new BitbucketCloudEndpoint()); @@ -283,9 +285,9 @@ void given__instanceWithCloud__when__updatingCloud__then__cloudUpdated() { @Test void given__instanceWithServer__when__updatingCloud__then__cloudAdded() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "dummy"))); + instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "dummy")))); assumeTrue("dummy".equals(instance.getEndpoints().get(0).getCredentialsId())); - instance.updateEndpoint(buildEndpoint(true, "added")); + instance.updateEndpoint(buildCloudEndpoint(true, "added")); assertThat(instance.getEndpoints()).hasExactlyElementsOfTypes(BitbucketServerEndpoint.class, BitbucketCloudEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { @@ -299,10 +301,10 @@ void given__instanceWithServer__when__updatingCloud__then__cloudAdded() { @Test void given__instanceWithServer__when__updatingDifferentServer__then__serverAdded() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "dummy", false, null))); + instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "dummy")))); assumeTrue("dummy".equals(instance.getEndpoints().get(0).getCredentialsId())); - instance.updateEndpoint(new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "added", false, null)); + instance.updateEndpoint(new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", new ServerWebhook(true, "added"))); assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketServerEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { @@ -316,9 +318,9 @@ void given__instanceWithServer__when__updatingDifferentServer__then__serverAdded @Test void given__instanceWithServer__when__updatingSameServer__then__serverUpdated() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "dummy"))); + instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "dummy")))); - instance.updateEndpoint(new BitbucketServerEndpoint("Example, Inc.", "https://bitbucket.example.com/", false, null)); + instance.updateEndpoint(new BitbucketServerEndpoint("Example, Inc.", "https://bitbucket.example.com/")); assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketServerEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { @@ -337,7 +339,7 @@ void given__newInstance__when__removingCloud__then__defaultRestored() { .element(0) .satisfies(endpoint -> assertThat(endpoint.getCredentialsId()).isNull()); // remove default does not really remove it - assertThat(instance.removeEndpoint(buildEndpoint(true, "dummy"))).isFalse(); + assertThat(instance.removeEndpoint(buildCloudEndpoint(true, "dummy"))).isFalse(); // default always exists assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketCloudEndpoint.class); } @@ -347,15 +349,15 @@ void given__instanceWithCloudAndServers__when__removingServer__then__matchingSer BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); instance.setEndpoints( List.of( - buildEndpoint(true, "first"), - new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "second"), - new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "third") + buildCloudEndpoint(true, "first"), + new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "second")), + new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", new ServerWebhook(true, "third")) )); assumeTrue("first".equals(instance.getEndpoints().get(0).getCredentialsId())); assumeTrue("second".equals(instance.getEndpoints().get(1).getCredentialsId())); assumeTrue("third".equals(instance.getEndpoints().get(2).getCredentialsId())); - assertThat(instance.removeEndpoint(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", false, null))).isTrue(); + assertThat(instance.removeEndpoint(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/"))).isTrue(); assertThat(instance.getEndpoints()).hasExactlyElementsOfTypes(BitbucketCloudEndpoint.class, BitbucketServerEndpoint.class); assertThat(instance.getEndpoints().get(0).getCredentialsId()).isEqualTo("first"); assertThat(instance.getEndpoints().get(1).getCredentialsId()).isEqualTo("third"); @@ -366,9 +368,9 @@ void given__instanceWithCloudAndServers__when__removingCloud__then__cloudRemoved BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); instance.setEndpoints( List.of( - buildEndpoint(true, "first"), - new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "second"), - new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "third") + buildCloudEndpoint(true, "first"), + new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "second")), + new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", new ServerWebhook(true, "third")) )); assumeTrue("first".equals(instance.getEndpoints().get(0).getCredentialsId())); assumeTrue("second".equals(instance.getEndpoints().get(1).getCredentialsId())); @@ -385,15 +387,15 @@ void given__instanceWithCloudAndServers__when__removingNonExisting__then__noChan BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); instance.setEndpoints( List.of( - buildEndpoint(true, "first"), - new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "second"), - new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "third") + buildCloudEndpoint(true, "first"), + new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "second")), + new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", new ServerWebhook(true, "third")) )); assumeTrue("first".equals(instance.getEndpoints().get(0).getCredentialsId())); assumeTrue("second".equals(instance.getEndpoints().get(1).getCredentialsId())); assumeTrue("third".equals(instance.getEndpoints().get(2).getCredentialsId())); - assertThat(instance.removeEndpoint(new BitbucketServerEndpoint("Test", "http://bitbucket.test", true, "fourth"))).isFalse(); + assertThat(instance.removeEndpoint(new BitbucketServerEndpoint("Test", "http://bitbucket.test", new ServerWebhook(true, "third")))).isFalse(); assertThat(instance.getEndpoints()).hasExactlyElementsOfTypes(BitbucketCloudEndpoint.class, BitbucketServerEndpoint.class, BitbucketServerEndpoint.class); assertThat(instance.getEndpoints().get(0).getCredentialsId()).isEqualTo("first"); assertThat(instance.getEndpoints().get(1).getCredentialsId()).isEqualTo("second"); @@ -403,7 +405,7 @@ void given__instanceWithCloudAndServers__when__removingNonExisting__then__noChan @Test void given__instance__when__onlyOneEndpoint__then__endpointsNotSelectable() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "dummy", false, null))); + instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", new ServerWebhook(true, "dummy")))); assertThat(instance.isEndpointSelectable()).isFalse(); } @@ -412,9 +414,9 @@ void given__instance__when__multipleEndpoints__then__endpointsSelectable() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); instance.setEndpoints( List.of( - buildEndpoint(true, "first"), - new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "second"), - new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "third") + buildCloudEndpoint(true, "first"), + new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "second")), + new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", new ServerWebhook(true, "third")) )); assertThat(instance.isEndpointSelectable()).isTrue(); } @@ -423,9 +425,9 @@ void given__instance__when__multipleEndpoints__then__endpointsSelectable() { void given__instanceWithCloudAndServers__when__findingExistingEndpoint__then__endpointFound() { BitbucketEndpointConfiguration.get().setEndpoints( List.of( - buildEndpoint(true, "first"), - new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "second"), - new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "third") + buildCloudEndpoint(true, "first"), + new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "second")), + new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", new ServerWebhook(true, "third")) )); assertThat(BitbucketEndpointProvider.lookupEndpoint(BitbucketCloudEndpoint.SERVER_URL)).isPresent() .hasValueSatisfying(endpoint -> { @@ -452,8 +454,8 @@ void given__instanceWithCloudAndServers__when__findingExistingEndpoint__then__en void given__instanceWithServers__when__findingNonExistingEndpoint__then__endpointNotFound() { BitbucketEndpointConfiguration.get().setEndpoints( List.of( - new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "dummy"), - new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "dummy") + new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "dummy")), + new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", new ServerWebhook(true, "dummy")) )); assertThat(BitbucketEndpointProvider.lookupEndpoint(BitbucketCloudEndpoint.SERVER_URL)).isEmpty(); assertThat(BitbucketEndpointProvider.lookupEndpoint("http://bitbucket.example.com/")).isEmpty(); @@ -469,9 +471,9 @@ void given__instanceWithServers__when__findingNonExistingEndpoint__then__endpoin void given__instanceWithCloudAndServers__when__findingInvalid__then__endpointNotFound() { BitbucketEndpointConfiguration.get().setEndpoints( List.of( - buildEndpoint(true, "first"), - new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "second"), - new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "third") + buildCloudEndpoint(true, "first"), + new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "second")), + new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", new ServerWebhook(true, "third")) )); assertThat(BitbucketEndpointProvider.lookupEndpoint("0schemes-start-with+digits:no leading slash")).isEmpty(); assertThat(BitbucketEndpointProvider.lookupEndpoint("http://host name with spaces:443")).isEmpty(); @@ -483,9 +485,9 @@ void given__instanceWithCloudAndServers__when__populatingDropBox__then__endpoint BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); instance.setEndpoints( List.of( - buildEndpoint(true, "first"), - new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "second"), - new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "third") + buildCloudEndpoint(true, "first"), + new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "second")), + new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", new ServerWebhook(true, "third")) )); ListBoxModel items = instance.getEndpointItems(); assertThat(items).hasSize(3); @@ -502,9 +504,9 @@ void given__instanceWithCloudAndServers__when__resolvingExistingEndpoint__then__ BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); instance.setEndpoints( List.of( - buildEndpoint(true, "first"), - new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "second"), - new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "third") + buildCloudEndpoint(true, "first"), + new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "second")), + new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", new ServerWebhook(true, "third")) )); assertThat(instance.getEndpointItems()).hasSize(3); assertThat(instance.readResolveServerUrl(null)).isEqualTo(BitbucketCloudEndpoint.SERVER_URL); @@ -529,7 +531,7 @@ void given__instanceWithCloudAndServers__when__resolvingNewEndpointAsSystem__the r.jenkins.setAuthorizationStrategy(mockStrategy); try (ACLContext context = ACL.as2(ACL.SYSTEM2)) { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints(List.of(new BitbucketServerEndpoint("existing", "https://bitbucket.test", false, null, false, null))); + instance.setEndpoints(List.of(new BitbucketServerEndpoint("existing", "https://bitbucket.test"))); assertThat(instance.getEndpointItems()).hasSize(1); assertThat(instance.readResolveServerUrl(null)).isEqualTo(BitbucketCloudEndpoint.SERVER_URL); assertThat(instance.getEndpointItems()).hasSize(2); @@ -569,7 +571,7 @@ void given__instanceWithCloudAndServers__when__resolvingNewEndpointAsSystem__the @Test void given__instanceWithCloudAndServers__when__resolvingNewEndpointAsAnon__then__normalizedReturnedNotAdded() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints(List.of(new BitbucketServerEndpoint("existing", "https://bitbucket.test", false, null, false, null))); + instance.setEndpoints(List.of(new BitbucketServerEndpoint("existing", "https://bitbucket.test"))); try (ACLContext context = ACL.as2(Jenkins.ANONYMOUS2)) { assertThat(instance.getEndpointItems()).hasSize(1); assertThat(instance.readResolveServerUrl(null)).isEqualTo(BitbucketCloudEndpoint.SERVER_URL); @@ -598,9 +600,9 @@ void given__instanceWithConfig__when__configRoundtrip__then__configRetained() th BitbucketEndpointConfiguration instance = BitbucketEndpointConfiguration.get(); instance.setEndpoints( List.of( - buildEndpoint(true, "first"), - new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "second", false, null), - new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", false, null, false, null) + buildCloudEndpoint(true, "first"), + new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", new ServerWebhook(true, "second")), + new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/") )); SystemCredentialsProvider.getInstance().setDomainCredentialsMap( Collections.singletonMap(Domain.global(), Arrays.asList( @@ -804,8 +806,8 @@ void should_support_configuration_as_code() throws Exception { assertThat(serverEndpoint.getServerVersion()).isEqualTo(BitbucketServerVersion.getMinSupportedVersion()); } - private BitbucketCloudEndpoint buildEndpoint(boolean manageHook, String credentials) { - return new BitbucketCloudEndpoint(false, 0, 0, manageHook, credentials, false, null); + private BitbucketCloudEndpoint buildCloudEndpoint(boolean manageHook, String credentials) { + return new BitbucketCloudEndpoint(false, 0, 0, new CloudWebhook(manageHook, credentials, false, null)); } } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiverTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiverTest.java index 60de6da4d..88c31a2e2 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiverTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiverTest.java @@ -26,6 +26,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.CloudWebhook; import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import com.cloudbees.jenkins.plugins.bitbucket.test.util.MockRequest; import java.util.Collections; @@ -98,7 +99,7 @@ private void mockRequest() { @Test void test_roundtrip() throws Exception { - BitbucketCloudEndpoint endpoint = new BitbucketCloudEndpoint(false, 0, 0, false, null, true, "hmac"); + BitbucketCloudEndpoint endpoint = new BitbucketCloudEndpoint(false, 0, 0, new CloudWebhook(false, null, true, "hmac")); endpoint.setBitbucketJenkinsRootUrl("https://jenkins.example.com"); BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); @@ -145,7 +146,7 @@ Stream getHookProcessors() { @Test void stop_process_when_multiple_processors_canHandle_incoming_webhook() throws Exception { - BitbucketCloudEndpoint endpoint = new BitbucketCloudEndpoint(false, 0, 0, false, null, true, "hmac"); + BitbucketCloudEndpoint endpoint = new BitbucketCloudEndpoint(false, 0, 0, new CloudWebhook(false, null, true, "hmac")); endpoint.setBitbucketJenkinsRootUrl("https://jenkins.example.com"); BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookAutoRegisterListenerTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookAutoRegisterListenerTest.java index bb359bd85..92fe7a760 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookAutoRegisterListenerTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookAutoRegisterListenerTest.java @@ -31,6 +31,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.JsonParser; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerWebhook; import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerWebhook; import com.cloudbees.jenkins.plugins.bitbucket.test.util.BitbucketTestUtil; import hudson.model.TaskListener; @@ -81,7 +82,7 @@ void test_register() throws Exception { StringCredentials credentials = BitbucketTestUtil.registerHookCredentials("password", rule); - BitbucketEndpoint endpoint = new BitbucketServerEndpoint("datacenter", serverURL, true, "dummyId", true, credentials.getId()); + BitbucketEndpoint endpoint = new BitbucketServerEndpoint("datacenter", serverURL, new ServerWebhook(true, "dummyId", true, credentials.getId())); ((BitbucketServerEndpoint) endpoint).setBitbucketJenkinsRootUrl("https://jenkins.example.com/"); BitbucketEndpointConfiguration.get().updateEndpoint(endpoint); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/client/ExponentialBackOffRetryStrategyTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/client/ExponentialBackOffRetryStrategyTest.java index 9200df35f..dce7cf9a6 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/client/ExponentialBackOffRetryStrategyTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/client/ExponentialBackOffRetryStrategyTest.java @@ -25,7 +25,6 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator; -import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation; import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerAPIClient; import java.io.IOException; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; @@ -42,7 +41,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIOException; -import static org.mockito.Mockito.mock; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; @@ -65,8 +63,7 @@ void test_retry(ClientAndServer mockServer) throws Exception { "test", "testRepos", (BitbucketAuthenticator) null, - false, - mock(BitbucketServerWebhookImplementation.class)) { + false) { @Override protected HttpClientBuilder setupClientBuilder() { return super.setupClientBuilder() diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpointTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpointTest.java index 2dd6f8540..282aead6d 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpointTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketCloudEndpointTest.java @@ -24,6 +24,7 @@ package com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.CloudWebhook; import com.damnhandy.uri.template.UriTemplate; import hudson.Util; import jenkins.model.Jenkins; @@ -42,7 +43,7 @@ void smokes() { BitbucketCloudEndpoint endpoint = new BitbucketCloudEndpoint(); assertThat(endpoint.getDisplayName()).isNotNull(); - assertThat(endpoint.getServerUrl()).isEqualTo(BitbucketCloudEndpoint.SERVER_URL); + assertThat(endpoint.getServerURL()).isEqualTo(BitbucketCloudEndpoint.SERVER_URL); /* The endpoints should set (literally, not normalized) and return * the bitbucketJenkinsRootUrl if the management of hooks is enabled */ @@ -77,7 +78,7 @@ void getUnmanagedDefaultRootUrl(JenkinsRule rule) { String jenkinsRootURL = Util.ensureEndsWith(URLUtils.normalizeURL(Jenkins.get().getRootUrl()), "/"); assertThat(new BitbucketCloudEndpoint().getEndpointJenkinsRootUrl()) .isEqualTo(jenkinsRootURL); - assertThat(new BitbucketCloudEndpoint(false, 0, 0, false, "{cred}", false, null).getEndpointJenkinsRootURL()) + assertThat(new BitbucketCloudEndpoint(false, 0, 0, new CloudWebhook(false, "{cred}", false, null)).getEndpointJenkinsRootURL()) .isEqualTo(jenkinsRootURL); } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpointTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpointTest.java index c42197d15..159a94310 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpointTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/BitbucketServerEndpointTest.java @@ -24,6 +24,7 @@ package com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerWebhook; import hudson.Util; import hudson.util.FormValidation; import jenkins.model.Jenkins; @@ -74,15 +75,15 @@ void smokes() { @Test void getUnmanagedDefaultRootUrl(JenkinsRule rule) { String jenkinsRootURL = Util.ensureEndsWith(URLUtils.normalizeURL(Jenkins.get().getRootUrl()), "/"); - assertThat(new BitbucketServerEndpoint("Dummy", "http://dummy.example.com", true, null, false, null).getEndpointJenkinsRootUrl()) + assertThat(new BitbucketServerEndpoint("Dummy", "http://dummy.example.com", new ServerWebhook(true, null, false, null)).getEndpointJenkinsRootUrl()) .isEqualTo(jenkinsRootURL); - assertThat(new BitbucketServerEndpoint("Dummy", "http://dummy.example.com", false, "{cred}", false, null).getEndpointJenkinsRootURL()) + assertThat(new BitbucketServerEndpoint("Dummy", "http://dummy.example.com", new ServerWebhook(false, "{cred}", false, null)).getEndpointJenkinsRootURL()) .isEqualTo(jenkinsRootURL); } @Test void getRepositoryUrl() { - BitbucketServerEndpoint endpoint = new BitbucketServerEndpoint("Dummy", "http://dummy.example.com", false, null, false, null); + BitbucketServerEndpoint endpoint = new BitbucketServerEndpoint("Dummy", "http://dummy.example.com"); assertThat(endpoint.getRepositoryUrl("TST", "test-repo")).isEqualTo("http://dummy.example.com/projects/TST/repos/test-repo"); assertThat(endpoint.getRepositoryUrl("~tester", "test-repo")).isEqualTo("http://dummy.example.com/users/tester/repos/test-repo"); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/DummyEndpointConfiguration.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/DummyEndpointConfiguration.java index 795d192b4..75a9da1f1 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/DummyEndpointConfiguration.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/endpoint/DummyEndpointConfiguration.java @@ -25,6 +25,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointDescriptor; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.EndpointType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerWebhook; import com.damnhandy.uri.template.UriTemplate; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -32,7 +33,7 @@ public class DummyEndpointConfiguration extends AbstractBitbucketEndpoint { DummyEndpointConfiguration(boolean manageHooks, String credentialsId) { - super(manageHooks, credentialsId, false, null); + super(new ServerWebhook(manageHooks, credentialsId, false, null)); } @Override diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/notifier/BitbucketBuildStatusNotificationsJUnit5Test.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/notifier/BitbucketBuildStatusNotificationsJUnit5Test.java index e81112523..c3f9e0199 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/notifier/BitbucketBuildStatusNotificationsJUnit5Test.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/notifier/BitbucketBuildStatusNotificationsJUnit5Test.java @@ -37,6 +37,8 @@ import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.notifier.BitbucketBuildStatusNotifications.JobCheckoutListener; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.CloudWebhook; +import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerWebhook; import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerAPIClient; import com.cloudbees.jenkins.plugins.bitbucket.trait.BitbucketBuildStatusNotificationsTrait; import com.cloudbees.jenkins.plugins.bitbucket.trait.ForkPullRequestDiscoveryTrait; @@ -279,10 +281,10 @@ public static Stream buildServerURLsProvider() { @ParameterizedTest(name = "checkURL {0} against Bitbucket Server") @MethodSource("buildServerURLsProvider") void test_checkURL_for_Bitbucket_server(String jenkinsURL, String expectedExceptionMsg, @NonNull JenkinsRule r) { - BitbucketServerEndpoint endpoint = new BitbucketServerEndpoint("Bitbucket Server", "https://bitbucket.server", true, "dummy"); + BitbucketServerEndpoint endpoint = new BitbucketServerEndpoint("Bitbucket Server", "https://bitbucket.server", new ServerWebhook(true, "dummy")); BitbucketEndpointConfiguration.get().setEndpoints(List.of(endpoint)); - BitbucketApi client = getApiMockClient(endpoint.getServerUrl()); + BitbucketApi client = getApiMockClient(endpoint.getServerURL()); if (expectedExceptionMsg != null) { assertThatIllegalStateException() .isThrownBy(() -> BitbucketBuildStatusNotifications.checkURL("http://" + jenkinsURL + "/build/sample", client)) @@ -314,11 +316,10 @@ public static Stream buildCloudURLsProvider() { @ParameterizedTest(name = "checkURL {0} against Bitbucket Cloud") @MethodSource("buildCloudURLsProvider") void test_checkURL_for_Bitbucket_cloud(String jenkinsURL, String expectedExceptionMsg, @NonNull JenkinsRule r) { - @SuppressWarnings("deprecation") - BitbucketCloudEndpoint endpoint = new BitbucketCloudEndpoint(false, 0, 0, true, "second"); + BitbucketCloudEndpoint endpoint = new BitbucketCloudEndpoint(false, 0, 0, new CloudWebhook(true, "second")); BitbucketEndpointConfiguration.get().setEndpoints(List.of(endpoint)); - BitbucketApi client = getApiMockClient(endpoint.getServerUrl()); + BitbucketApi client = getApiMockClient(endpoint.getServerURL()); if (expectedExceptionMsg != null) { assertThatIllegalStateException() .isThrownBy(() -> BitbucketBuildStatusNotifications.checkURL("http://" + jenkinsURL + "/build/sample", client)) diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhookProcessorTest.java index d8597516c..354e9be4d 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractWebhookProcessorTest.java @@ -25,7 +25,6 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookProcessorException; -import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractWebhookProcessor; import com.cloudbees.plugins.credentials.CredentialsScope; import hudson.util.Secret; import java.io.IOException; @@ -49,7 +48,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class AbstractWebhookProcessorTest { +public class AbstractWebhookProcessorTest { private AbstractWebhookProcessor sut; protected SCMHeadEvent scmEvent; diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/CloudPullRequestWebhookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPullRequestWebhookProcessorTest.java similarity index 80% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/CloudPullRequestWebhookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPullRequestWebhookProcessorTest.java index 3b7bc0853..ec55dd9d8 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/CloudPullRequestWebhookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPullRequestWebhookProcessorTest.java @@ -21,15 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; -import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.CloudPullRequestWebhookProcessor; -import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.PREvent; import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import com.cloudbees.jenkins.plugins.bitbucket.trait.OriginPullRequestDiscoveryTrait; import hudson.scm.SCM; @@ -58,14 +56,14 @@ class CloudPullRequestWebhookProcessorTest { private CloudPullRequestWebhookProcessor sut; - private SCMHeadEvent scmEvent; + private CloudPREvent scmEvent; @BeforeEach void setup() { sut = new CloudPullRequestWebhookProcessor() { @Override public void notifyEvent(SCMHeadEvent event, int delaySeconds) { - CloudPullRequestWebhookProcessorTest.this.scmEvent = event; + CloudPullRequestWebhookProcessorTest.this.scmEvent = (CloudPREvent) event; } }; } @@ -113,45 +111,42 @@ void test_canHandle_only_pass_specific_cloud_hook() throws Exception { void test_pullrequest_created() throws Exception { sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPREvent event = (PluginPREvent) scmEvent; - assertThat(event).isNotNull(); - assertThat(event.getSourceName()).isEqualTo("test-repos"); - assertThat(event.getType()).isEqualTo(Type.CREATED); - assertThat(event.isMatch(mock(SCM.class))).isFalse(); + assertThat(scmEvent).isNotNull(); + assertThat(scmEvent.getSourceName()).isEqualTo("test-repos"); + assertThat(scmEvent.getType()).isEqualTo(Type.CREATED); + assertThat(scmEvent.isMatch(mock(SCM.class))).isFalse(); } @Test void test_pullrequest_rejected() throws Exception { sut.process(HookEventType.PULL_REQUEST_DECLINED.getKey(), loadResource("pullrequest_rejected.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPREvent event = (PluginPREvent) scmEvent; - assertThat(event).isNotNull(); - assertThat(event.getSourceName()).isEqualTo("test-repos"); - assertThat(event.getType()).isEqualTo(Type.REMOVED); - assertThat(event.isMatch(mock(SCM.class))).isFalse(); + assertThat(scmEvent).isNotNull(); + assertThat(scmEvent.getSourceName()).isEqualTo("test-repos"); + assertThat(scmEvent.getType()).isEqualTo(Type.REMOVED); + assertThat(scmEvent.isMatch(mock(SCM.class))).isFalse(); } @Test void test_pullrequest_created_when_event_match_SCMNavigator() throws Exception { sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPREvent event = (PluginPREvent) scmEvent; // discard any scm navigator than bitbucket - assertThat(event.isMatch(mock(SCMNavigator.class))).isFalse(); + assertThat(scmEvent.isMatch(mock(SCMNavigator.class))).isFalse(); BitbucketSCMNavigator scmNavigator = new BitbucketSCMNavigator("amuniz"); // cloud could not filter by ProjectKey - assertThat(event.isMatch(scmNavigator)).isTrue(); + assertThat(scmEvent.isMatch(scmNavigator)).isTrue(); // if set must match the project of repository from which the hook is generated scmNavigator.setProjectKey("PRJKEY"); - assertThat(event.isMatch(scmNavigator)).isTrue(); + assertThat(scmEvent.isMatch(scmNavigator)).isTrue(); // project key is case sensitive scmNavigator.setProjectKey("prjkey"); - assertThat(event.isMatch(scmNavigator)).isFalse(); + assertThat(scmEvent.isMatch(scmNavigator)).isFalse(); // workspace/owner is case insensitive scmNavigator = new BitbucketSCMNavigator("AMUNIZ"); - assertThat(event.isMatch(scmNavigator)).isTrue(); + assertThat(scmEvent.isMatch(scmNavigator)).isTrue(); } @WithJenkins @@ -159,23 +154,22 @@ void test_pullrequest_created_when_event_match_SCMNavigator() throws Exception { void test_pullrequest_created_when_event_match_SCMSource(JenkinsRule r) throws Exception { sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPREvent event = (PluginPREvent) scmEvent; // discard any scm navigator than bitbucket - assertThat(event.isMatch(mock(SCMSource.class))).isFalse(); + assertThat(scmEvent.isMatch(mock(SCMSource.class))).isFalse(); BitbucketSCMSource scmSource = new BitbucketSCMSource("amuniz", "test-repos"); // skip scm source that has not been configured to discover PRs - assertThat(event.isMatch(scmSource)).isFalse(); + assertThat(scmEvent.isMatch(scmSource)).isFalse(); scmSource.setTraits(List.of(new OriginPullRequestDiscoveryTrait(2))); - assertThat(event.isMatch(scmSource)).isTrue(); + assertThat(scmEvent.isMatch(scmSource)).isTrue(); // workspace/owner is case insensitive scmSource = new BitbucketSCMSource("AMUNIZ", "TEST-REPOS"); scmSource.setTraits(List.of(new OriginPullRequestDiscoveryTrait(1))); - assertThat(event.isMatch(scmSource)).isTrue(); + assertThat(scmEvent.isMatch(scmSource)).isTrue(); - assertThat(event.getPullRequests(scmSource)) + assertThat(scmEvent.getPullRequests(scmSource)) .isNotEmpty() .hasSize(1); } @@ -185,12 +179,10 @@ void test_pullrequest_created_when_event_match_SCMSource(JenkinsRule r) throws E void test_pullrequest_rejected_returns_empty_pullrequests_when_event_match_SCMSource(JenkinsRule r) throws Exception { sut.process(HookEventType.PULL_REQUEST_DECLINED.getKey(), loadResource("pullrequest_rejected.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPREvent event = (PluginPREvent) scmEvent; - BitbucketSCMSource scmSource = new BitbucketSCMSource("aMUNIZ", "test-repos"); scmSource.setTraits(List.of(new OriginPullRequestDiscoveryTrait(2))); - assertThat(event.isMatch(scmSource)).isTrue(); - assertThat(event.getPullRequests(scmSource)).isEmpty(); + assertThat(scmEvent.isMatch(scmSource)).isTrue(); + assertThat(scmEvent.getPullRequests(scmSource)).isEmpty(); } private String loadResource(String resource) throws IOException { diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/CloudPushWebhookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPushWebhookProcessorTest.java similarity index 81% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/CloudPushWebhookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPushWebhookProcessorTest.java index 07bfcc943..b2b217271 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/CloudPushWebhookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudPushWebhookProcessorTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketTagSCMHead; @@ -29,8 +29,6 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint; -import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.CloudPushWebhookProcessor; -import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.PushEvent; import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import hudson.scm.SCM; import java.io.IOException; @@ -56,14 +54,14 @@ class CloudPushWebhookProcessorTest { private CloudPushWebhookProcessor sut; - private SCMHeadEvent scmEvent; + private CloudPushEvent scmEvent; @BeforeEach void setup() { sut = new CloudPushWebhookProcessor() { @Override public void notifyEvent(SCMHeadEvent event, int delaySeconds) { - CloudPushWebhookProcessorTest.this.scmEvent = event; + CloudPushWebhookProcessorTest.this.scmEvent = (CloudPushEvent) event; } }; } @@ -111,14 +109,13 @@ void test_canHandle_only_pass_specific_cloud_hook() throws Exception { void test_tag_created() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("tag_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPushEvent event = (PluginPushEvent) scmEvent; - assertThat(event).isNotNull(); - assertThat(event.getSourceName()).isEqualTo("test-repos"); - assertThat(event.getType()).isEqualTo(Type.CREATED); - assertThat(event.isMatch(mock(SCM.class))).isFalse(); + assertThat(scmEvent).isNotNull(); + assertThat(scmEvent.getSourceName()).isEqualTo("test-repos"); + assertThat(scmEvent.getType()).isEqualTo(Type.CREATED); + assertThat(scmEvent.isMatch(mock(SCM.class))).isFalse(); BitbucketSCMSource scmSource = new BitbucketSCMSource("AMUNIZ", "test-repos"); - Map heads = event.heads(scmSource); + Map heads = scmEvent.heads(scmSource); assertThat(heads.keySet()) .first() .usingRecursiveComparison() @@ -129,14 +126,13 @@ void test_tag_created() throws Exception { void test_annotated_tag_created() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("annotated_tag_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPushEvent event = (PluginPushEvent) scmEvent; - assertThat(event).isNotNull(); - assertThat(event.getSourceName()).isEqualTo("test-repos"); - assertThat(event.getType()).isEqualTo(Type.CREATED); - assertThat(event.isMatch(mock(SCM.class))).isFalse(); + assertThat(scmEvent).isNotNull(); + assertThat(scmEvent.getSourceName()).isEqualTo("test-repos"); + assertThat(scmEvent.getType()).isEqualTo(Type.CREATED); + assertThat(scmEvent.isMatch(mock(SCM.class))).isFalse(); BitbucketSCMSource scmSource = new BitbucketSCMSource("AMUNIz", "test-repos"); - Map heads = event.heads(scmSource); + Map heads = scmEvent.heads(scmSource); assertThat(heads.keySet()) .first() .usingRecursiveComparison() @@ -147,14 +143,13 @@ void test_annotated_tag_created() throws Exception { void test_commmit_created() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("commit_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPushEvent event = (PluginPushEvent) scmEvent; - assertThat(event).isNotNull(); - assertThat(event.getSourceName()).isEqualTo("test-repos"); - assertThat(event.getType()).isEqualTo(Type.UPDATED); - assertThat(event.isMatch(mock(SCM.class))).isFalse(); + assertThat(scmEvent).isNotNull(); + assertThat(scmEvent.getSourceName()).isEqualTo("test-repos"); + assertThat(scmEvent.getType()).isEqualTo(Type.UPDATED); + assertThat(scmEvent.isMatch(mock(SCM.class))).isFalse(); BitbucketSCMSource scmSource = new BitbucketSCMSource("aMUNIZ", "test-repos"); - Map heads = event.heads(scmSource); + Map heads = scmEvent.heads(scmSource); assertThat(heads.keySet()) .first() .usingRecursiveComparison() diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/PluginPullRequestWebhookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPullRequestWebhookProcessorTest.java similarity index 80% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/PluginPullRequestWebhookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPullRequestWebhookProcessorTest.java index 2241b6caf..7ba9efe12 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/PluginPullRequestWebhookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPullRequestWebhookProcessorTest.java @@ -21,14 +21,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; -import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.PREvent; -import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin.PluginPullRequestWebhookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import com.cloudbees.jenkins.plugins.bitbucket.trait.OriginPullRequestDiscoveryTrait; import java.io.IOException; @@ -58,14 +56,14 @@ class PluginPullRequestWebhookProcessorTest { private static final String SERVER_URL = "http://localhost:7990"; private PluginPullRequestWebhookProcessor sut; - private SCMHeadEvent scmEvent; + private PluginPREvent scmEvent; @BeforeEach void setup() { sut = new PluginPullRequestWebhookProcessor() { @Override public void notifyEvent(SCMHeadEvent event, int delaySeconds) { - PluginPullRequestWebhookProcessorTest.this.scmEvent = event; + PluginPullRequestWebhookProcessorTest.this.scmEvent = (PluginPREvent) event; } }; } @@ -114,22 +112,16 @@ void test_canHandle_only_pass_specific_cloud_hook() throws Exception { void test_pullrequest_created() throws Exception { sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - assertThat(scmEvent).isNotNull().isInstanceOf(PluginPREvent.class); - - PluginPREvent event = (PluginPREvent) scmEvent; - assertThat(event.getSourceName()).isEqualTo("rep_1"); - assertThat(event.getType()).isEqualTo(Type.CREATED); + assertThat(scmEvent.getSourceName()).isEqualTo("rep_1"); + assertThat(scmEvent.getType()).isEqualTo(Type.CREATED); } @Test void test_pullrequest_merged() throws Exception { sut.process(HookEventType.PULL_REQUEST_MERGED.getKey(), loadResource("pullrequest_merged.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - assertThat(scmEvent).isNotNull().isInstanceOf(PluginPREvent.class); - - PluginPREvent event = (PluginPREvent) scmEvent; - assertThat(event.getSourceName()).isEqualTo("rep_1"); - assertThat(event.getType()).isEqualTo(Type.REMOVED); + assertThat(scmEvent.getSourceName()).isEqualTo("rep_1"); + assertThat(scmEvent.getType()).isEqualTo(Type.REMOVED); } @Test @@ -138,36 +130,34 @@ void test_pullrequest_updated() throws Exception { assertThat(scmEvent).isNotNull().isInstanceOf(PluginPREvent.class); - PluginPREvent event = (PluginPREvent) scmEvent; - assertThat(event.getSourceName()).isEqualTo("rep_1"); - assertThat(event.getType()).isEqualTo(Type.UPDATED); + assertThat(scmEvent.getSourceName()).isEqualTo("rep_1"); + assertThat(scmEvent.getType()).isEqualTo(Type.UPDATED); } @Test void test_PREvent_match_SCMNavigator() throws Exception { sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPREvent event = (PluginPREvent) scmEvent; - assertThat(event.getType()).isEqualTo(Type.CREATED); + assertThat(scmEvent.getType()).isEqualTo(Type.CREATED); // discard any scm navigator than bitbucket - assertThat(event.isMatch(mock(SCMNavigator.class))).isFalse(); + assertThat(scmEvent.isMatch(mock(SCMNavigator.class))).isFalse(); BitbucketSCMNavigator scmNavigator = new BitbucketSCMNavigator("PROJECT_1"); - assertThat(event.isMatch(scmNavigator)).isFalse(); + assertThat(scmEvent.isMatch(scmNavigator)).isFalse(); // match only if projectKey and serverURL matches scmNavigator.setServerUrl(SERVER_URL); - assertThat(event.isMatch(scmNavigator)).isTrue(); + assertThat(scmEvent.isMatch(scmNavigator)).isTrue(); // if set must match the project of repository from which the hook is generated scmNavigator.setProjectKey("PROJECT_1"); - assertThat(event.isMatch(scmNavigator)).isTrue(); + assertThat(scmEvent.isMatch(scmNavigator)).isTrue(); // project key is case sensitive scmNavigator.setProjectKey("project_1"); - assertThat(event.isMatch(scmNavigator)).isFalse(); + assertThat(scmEvent.isMatch(scmNavigator)).isFalse(); // workspace/owner is case insensitive scmNavigator = new BitbucketSCMNavigator("project_1"); scmNavigator.setServerUrl(SERVER_URL); - assertThat(event.isMatch(scmNavigator)).isTrue(); + assertThat(scmEvent.isMatch(scmNavigator)).isTrue(); } @WithJenkins @@ -175,25 +165,24 @@ void test_PREvent_match_SCMNavigator() throws Exception { void test_PREvent_match_SCMSource(JenkinsRule r) throws Exception { sut.process(HookEventType.PULL_REQUEST_CREATED.getKey(), loadResource("pullrequest_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPREvent event = (PluginPREvent) scmEvent; // discard any scm navigator than bitbucket - assertThat(event.isMatch(mock(SCMSource.class))).isFalse(); + assertThat(scmEvent.isMatch(mock(SCMSource.class))).isFalse(); BitbucketSCMSource scmSource = new BitbucketSCMSource("PROJECT_1", "rep_1"); scmSource.setServerUrl(SERVER_URL); // skip scm source that has not been configured to discover PRs - assertThat(event.isMatch(scmSource)).isFalse(); + assertThat(scmEvent.isMatch(scmSource)).isFalse(); scmSource.setTraits(List.of(new OriginPullRequestDiscoveryTrait(2))); - assertThat(event.isMatch(scmSource)).isTrue(); + assertThat(scmEvent.isMatch(scmSource)).isTrue(); // workspace/owner is case insensitive scmSource = new BitbucketSCMSource("project_1", "rep_1"); scmSource.setServerUrl(SERVER_URL); scmSource.setTraits(List.of(new OriginPullRequestDiscoveryTrait(1))); - assertThat(event.isMatch(scmSource)).isTrue(); + assertThat(scmEvent.isMatch(scmSource)).isTrue(); - assertThat(event.getPullRequests(scmSource)) + assertThat(scmEvent.getPullRequests(scmSource)) .isNotEmpty() .hasSize(1); } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/PluginPushWebhookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPushWebhookProcessorTest.java similarity index 79% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/PluginPushWebhookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPushWebhookProcessorTest.java index 6b3632c7a..2e658c125 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/PluginPushWebhookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginPushWebhookProcessorTest.java @@ -21,15 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMNavigator; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BranchSCMHead; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; -import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.PushEvent; -import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin.PluginPushWebhookProcessor; import com.cloudbees.jenkins.plugins.bitbucket.test.util.HookProcessorTestUtil; import java.io.IOException; import java.io.InputStream; @@ -62,14 +60,14 @@ class PluginPushWebhookProcessorTest { private static final String SERVER_URL = "http://localhost:7990"; private PluginPushWebhookProcessor sut; - private SCMHeadEvent scmEvent; + private PluginPushEvent scmEvent; @BeforeEach void setup() { sut = new PluginPushWebhookProcessor() { @Override public void notifyEvent(SCMHeadEvent event, int delaySeconds) { - PluginPushWebhookProcessorTest.this.scmEvent = event; + PluginPushWebhookProcessorTest.this.scmEvent = (PluginPushEvent) event; } }; } @@ -115,14 +113,13 @@ void test_canHandle_only_pass_specific_native_hook() throws Exception { void test_push_server_UPDATE_2() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("commit_update2.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPushEvent event = (PluginPushEvent) scmEvent; - assertThat(event).isNotNull(); - assertThat(event.getSourceName()).isEqualTo("rep_1"); - assertThat(event.getType()).isEqualTo(SCMEvent.Type.UPDATED); + assertThat(scmEvent).isNotNull(); + assertThat(scmEvent.getSourceName()).isEqualTo("rep_1"); + assertThat(scmEvent.getType()).isEqualTo(SCMEvent.Type.UPDATED); BitbucketSCMSource scmSource = new BitbucketSCMSource("PROJECT_1", "rep_1"); scmSource.setServerUrl(SERVER_URL); - Map heads = event.heads(scmSource); + Map heads = scmEvent.heads(scmSource); assertThat(heads) .containsKey(new BranchSCMHead("master")) .containsValue(new SCMRevisionImpl(new BranchSCMHead("master"), "500cf91e7b4b7d9f995cdb6e81cb5538216ac02e")); @@ -132,14 +129,13 @@ void test_push_server_UPDATE_2() throws Exception { void test_push_server_UPDATE() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("commit_update.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPushEvent event = (PluginPushEvent) scmEvent; - assertThat(event).isNotNull(); - assertThat(event.getSourceName()).isEqualTo("rep_1"); - assertThat(event.getType()).isEqualTo(SCMEvent.Type.UPDATED); + assertThat(scmEvent).isNotNull(); + assertThat(scmEvent.getSourceName()).isEqualTo("rep_1"); + assertThat(scmEvent.getType()).isEqualTo(SCMEvent.Type.UPDATED); BitbucketSCMSource scmSource = new BitbucketSCMSource("PROJEct_1", "rep_1"); scmSource.setServerUrl(SERVER_URL); - Map heads = event.heads(scmSource); + Map heads = scmEvent.heads(scmSource); assertThat(heads) .containsKey(new BranchSCMHead("test-webhook")) .containsValue(new SCMRevisionImpl(new BranchSCMHead("test-webhook"), "c0158b3e6c8cecf3bddc39d20957a98660cd23fd")); @@ -149,14 +145,13 @@ void test_push_server_UPDATE() throws Exception { void test_push_server_CREATED() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("branch_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPushEvent event = (PluginPushEvent) scmEvent; - assertThat(event).isNotNull(); - assertThat(event.getSourceName()).isEqualTo("rep_1"); - assertThat(event.getType()).isEqualTo(SCMEvent.Type.CREATED); + assertThat(scmEvent).isNotNull(); + assertThat(scmEvent.getSourceName()).isEqualTo("rep_1"); + assertThat(scmEvent.getType()).isEqualTo(SCMEvent.Type.CREATED); BitbucketSCMSource scmSource = new BitbucketSCMSource("pROJECT_1", "rep_1"); scmSource.setServerUrl(SERVER_URL); - Map heads = event.heads(scmSource); + Map heads = scmEvent.heads(scmSource); assertThat(heads) .containsKey(new BranchSCMHead("test-webhook")) .containsValue(new SCMRevisionImpl(new BranchSCMHead("test-webhook"), "417b2f673581ee6000e260a5fa65e62b56c7a3cd")); @@ -166,14 +161,13 @@ void test_push_server_CREATED() throws Exception { void test_push_server_REMOVED() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("branch_deleted.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPushEvent event = (PluginPushEvent) scmEvent; - assertThat(event).isNotNull(); - assertThat(event.getSourceName()).isEqualTo("rep_1"); - assertThat(event.getType()).isEqualTo(SCMEvent.Type.REMOVED); + assertThat(scmEvent).isNotNull(); + assertThat(scmEvent.getSourceName()).isEqualTo("rep_1"); + assertThat(scmEvent.getType()).isEqualTo(SCMEvent.Type.REMOVED); BitbucketSCMSource scmSource = new BitbucketSCMSource("pROJECT_1", "rep_1"); scmSource.setServerUrl(SERVER_URL); - Map heads = event.heads(scmSource); + Map heads = scmEvent.heads(scmSource); assertThat(heads).containsKey(new BranchSCMHead("test-webhook")); assertThat(heads.values()).containsNull(); } @@ -182,27 +176,26 @@ void test_push_server_REMOVED() throws Exception { void test_PushEvent_match_SCMNavigator() throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("branch_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPushEvent event = (PluginPushEvent) scmEvent; - assertThat(event.getType()).isEqualTo(Type.CREATED); + assertThat(scmEvent.getType()).isEqualTo(Type.CREATED); // discard any scm navigator than bitbucket - assertThat(event.isMatch(mock(SCMNavigator.class))).isFalse(); + assertThat(scmEvent.isMatch(mock(SCMNavigator.class))).isFalse(); BitbucketSCMNavigator scmNavigator = new BitbucketSCMNavigator("PROJECT_1"); - assertThat(event.isMatch(scmNavigator)).isFalse(); + assertThat(scmEvent.isMatch(scmNavigator)).isFalse(); // match only if projectKey and serverURL matches scmNavigator.setServerUrl(SERVER_URL); - assertThat(event.isMatch(scmNavigator)).isTrue(); + assertThat(scmEvent.isMatch(scmNavigator)).isTrue(); // if set must match the project of repository from which the hook is generated scmNavigator.setProjectKey("PROJECT_1"); - assertThat(event.isMatch(scmNavigator)).isTrue(); + assertThat(scmEvent.isMatch(scmNavigator)).isTrue(); // project key is case sensitive scmNavigator.setProjectKey("project_1"); - assertThat(event.isMatch(scmNavigator)).isFalse(); + assertThat(scmEvent.isMatch(scmNavigator)).isFalse(); // workspace/owner is case insensitive scmNavigator = new BitbucketSCMNavigator("project_1"); scmNavigator.setServerUrl(SERVER_URL); - assertThat(event.isMatch(scmNavigator)).isTrue(); + assertThat(scmEvent.isMatch(scmNavigator)).isTrue(); } @WithJenkins @@ -210,18 +203,17 @@ void test_PushEvent_match_SCMNavigator() throws Exception { void test_PushEvent_match_SCMSource(JenkinsRule r) throws Exception { sut.process(HookEventType.PUSH.getKey(), loadResource("branch_created.json"), Collections.emptyMap(), mock(BitbucketEndpoint.class)); - PluginPushEvent event = (PluginPushEvent) scmEvent; // discard any scm navigator than bitbucket - assertThat(event.isMatch(mock(SCMSource.class))).isFalse(); + assertThat(scmEvent.isMatch(mock(SCMSource.class))).isFalse(); BitbucketSCMSource scmSource = new BitbucketSCMSource("PROJECT_1", "rep_1"); scmSource.setServerUrl(SERVER_URL); - assertThat(event.isMatch(scmSource)).isTrue(); + assertThat(scmEvent.isMatch(scmSource)).isTrue(); // workspace/owner is case insensitive scmSource = new BitbucketSCMSource("project_1", "rep_1"); scmSource.setServerUrl(SERVER_URL); - assertThat(event.isMatch(scmSource)).isTrue(); + assertThat(scmEvent.isMatch(scmSource)).isTrue(); } @Test diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPingWebhookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPingWebhookProcessorTest.java similarity index 98% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPingWebhookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPingWebhookProcessorTest.java index 897b4cbd2..412877f49 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPingWebhookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPingWebhookProcessorTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server; import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.hooks.HookEventType; diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPullRequestWebhookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPullRequestWebhookProcessorTest.java similarity index 98% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPullRequestWebhookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPullRequestWebhookProcessorTest.java index 8af6c413a..f6ef1b498 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPullRequestWebhookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPullRequestWebhookProcessorTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BranchSCMHead; diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPushWebhookProcessorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPushWebhookProcessorTest.java similarity index 99% rename from src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPushWebhookProcessorTest.java rename to src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPushWebhookProcessorTest.java index 8618d6e60..45f8630cb 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/ServerPushWebhookProcessorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerPushWebhookProcessorTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook; +package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketTagSCMHead; diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClientTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClientTest.java index 2ed509d2a..1d66565e3 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClientTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClientTest.java @@ -35,7 +35,6 @@ import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketClientCertificateAuthenticator; import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketOAuthAuthenticator; import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketUsernamePasswordAuthenticator; -import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation; import com.cloudbees.jenkins.plugins.bitbucket.test.util.BitbucketTestUtil; import hudson.ProxyConfiguration; import java.io.InputStream; @@ -269,7 +268,7 @@ void test_no_proxy_configurations() throws Exception { j.jenkins.setProxy(proxyConfiguration); AtomicReference builderReference = new AtomicReference<>(); - try(BitbucketApi client = new BitbucketServerAPIClient(serverURL, "amuniz", "test-repos", mock(BitbucketUsernamePasswordAuthenticator.class), false, BitbucketServerWebhookImplementation.NATIVE) { + try(BitbucketApi client = new BitbucketServerAPIClient(serverURL, "amuniz", "test-repos", mock(BitbucketUsernamePasswordAuthenticator.class), false) { @Override protected void setClientProxyParams(HttpClientBuilder builder) { builderReference.set(spy(builder)); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/trait/SCMNavigatorIntegrationTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/trait/SCMNavigatorIntegrationTest.java index 910cf2481..453078018 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/trait/SCMNavigatorIntegrationTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/trait/SCMNavigatorIntegrationTest.java @@ -55,7 +55,7 @@ class SCMNavigatorIntegrationTest { @Test void teamDiscoveringTest(JenkinsRule j) throws Exception { BitbucketEndpointConfiguration - .get().addEndpoint(new BitbucketServerEndpoint("test", "http://bitbucket.test", false, null)); + .get().addEndpoint(new BitbucketServerEndpoint("test", "http://bitbucket.test")); BitbucketMockApiFactory.add("http://bitbucket.test", BitbucketClientMockUtils.getAPIClientMock(true, false)); OrganizationFolder teamFolder = j.jenkins.createProject(OrganizationFolder.class, "test");