From 3352e675f0f651675b68d7b338372a0f5c266a09 Mon Sep 17 00:00:00 2001 From: Nikolas Falco Date: Mon, 2 Jun 2025 22:26:10 +0200 Subject: [PATCH] Add API classes to manage bitbucket endpoints and hide the internal implementations --- README.md | 2 +- pom.xml | 6 +- .../bitbucket/BitbucketGitSCMBuilder.java | 10 +- .../bitbucket/BitbucketSCMNavigator.java | 10 +- .../plugins/bitbucket/BitbucketSCMSource.java | 23 ++- .../api/endpoint/BitbucketEndpoint.java | 166 +++++++++++++++ .../endpoint/BitbucketEndpointDescriptor.java | 109 ++++++++++ .../endpoint/BitbucketEndpointProvider.java | 165 +++++++++++++++ .../bitbucket/api/endpoint/EndpointType.java | 28 +++ .../client/BitbucketCloudApiFactory.java | 15 +- .../endpoints/AbstractBitbucketEndpoint.java | 64 +++--- .../AbstractBitbucketEndpointDescriptor.java | 87 +------- .../endpoints/BitbucketCloudEndpoint.java | 19 +- .../BitbucketEndpointConfiguration.java | 190 ++++++------------ .../endpoints/BitbucketServerEndpoint.java | 66 +++--- .../filesystem/BitbucketSCMFileSystem.java | 2 +- .../BitbucketSCMSourcePushHookReceiver.java | 13 +- .../hooks/WebhookAutoRegisterListener.java | 18 +- .../bitbucket/hooks/WebhookConfiguration.java | 12 +- .../impl/util/BitbucketApiUtils.java | 15 +- .../impl/util/BitbucketCredentials.java | 30 ++- .../plugins/bitbucket/impl/util/URLUtils.java | 47 +++++ .../client/BitbucketServerAPIClient.java | 10 +- .../bitbucket/BitbucketAuthenticatorTest.java | 18 +- .../bitbucket/BitbucketSCMNavigatorTest.java | 105 +++++----- .../bitbucket/BitbucketSCMSourceTest.java | 4 +- .../BitbucketEndpointConfigurationTest.java | 115 ++++++----- .../endpoints/DummyEndpointConfiguration.java | 15 +- .../trait/BranchDiscoveryTraitTest.java | 1 - 29 files changed, 914 insertions(+), 451 deletions(-) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpoint.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpointDescriptor.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpointProvider.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/EndpointType.java diff --git a/README.md b/README.md index 721777823..ca5f1c156 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ This means that when you have a private repository, or a private fork of a publi ### Developers and DevOps notes -Classes under the packages `com.cloudbees.jenkins.plugins.bitbucket.api` is intended to be public api and can be used to extend functionality in other plugins. Changes in the method signature will be marked with @deprecated providing an alternative new signature or class to use. After a reasonable time (about a year) the method could be removed at all. If some methods are not intended to be used then are marked with `@Restricted(NoExternalUse.class)`. +Classes under packages `com.cloudbees.jenkins.plugins.bitbucket.api` is intended to be public API and can be used to extend functionality in other plugins. Changes in the method signature will be marked with @deprecated providing an alternative new signature or class to use. After a reasonable time (about a year) the method could be removed at all. If some methods are not intended to be used then are marked with `@Restricted(NoExternalUse.class)`. Classes in other packages are not intended to be used outside of this plugin. Signature can be changed in any moment, backward compatibility are no guaranteed. diff --git a/pom.xml b/pom.xml index 529c0e2a2..0445ac47b 100644 --- a/pom.xml +++ b/pom.xml @@ -15,9 +15,7 @@ Bitbucket Branch Source Plugin https://github.com/jenkinsci/bitbucket-branch-source-plugin - Discover and build Bitbucket Cloud and Bitbucket Server pull requests and branches and send status - notifications with the build result. - + Discover and build Bitbucket Cloud and Bitbucket Server pull requests and branches and send status notifications with the build result. MIT License @@ -64,7 +62,7 @@ scm:git:https://github.com/${gitHubRepo}.git scm:git:git@github.com:${gitHubRepo}.git https://github.com/${gitHubRepo} - 936.3.1 + ${scmTag} 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 c0a0323c0..c54368107 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketGitSCMBuilder.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketGitSCMBuilder.java @@ -29,8 +29,8 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepositoryProtocol; import com.cloudbees.jenkins.plugins.bitbucket.api.PullRequestBranchType; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint; -import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.extension.FallbackToOtherRepositoryGitSCMExtension; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; @@ -112,12 +112,12 @@ public BitbucketGitSCMBuilder(@NonNull BitbucketSCMSource scmSource, @NonNull SC this.scmSource = scmSource; String serverURL = scmSource.getServerUrl(); - AbstractBitbucketEndpoint endpoint = BitbucketEndpointConfiguration.get() - .findEndpoint(serverURL) - .orElse(new BitbucketServerEndpoint(null, serverURL, false, null)); + AbstractBitbucketEndpoint endpoint = (AbstractBitbucketEndpoint) BitbucketEndpointProvider + .lookupEndpoint(serverURL) + .orElse(new BitbucketServerEndpoint(null, serverURL, false, null, false, null)); String repositoryURL = endpoint.getRepositoryUrl(scmSource.getRepoOwner(), scmSource.getRepository()); - if (BitbucketApiUtils.isCloud(endpoint.getServerUrl())) { + if (BitbucketApiUtils.isCloud(endpoint.getServerURL())) { withBrowser(new BitbucketWeb(repositoryURL)); } else { withBrowser(new BitbucketServer(repositoryURL)); 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 eee6caf3b..3b6c18ff4 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java @@ -29,15 +29,16 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCloudWorkspace; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketTeam; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; import com.cloudbees.jenkins.plugins.bitbucket.client.repository.UserRoleInRepository; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint; -import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.avatars.BitbucketTeamAvatarMetadataAction; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentials; 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; @@ -221,13 +222,14 @@ public String getServerUrl() { @DataBoundSetter public void setServerUrl(@CheckForNull String serverUrl) { - serverUrl = BitbucketEndpointConfiguration.normalizeServerURL(serverUrl); + serverUrl = Util.fixEmpty(URLUtils.normalizeURL(serverUrl)); if (serverUrl != null && !StringUtils.equals(this.serverUrl, serverUrl)) { this.serverUrl = serverUrl; resetId(); } } + @Deprecated(since = "936.4.0", forRemoval = true) @NonNull public String getEndpointJenkinsRootUrl() { return AbstractBitbucketEndpoint.getEndpointJenkinsRootUrl(serverUrl); @@ -388,7 +390,7 @@ public SCMNavigator newInstance(String name) { } public boolean isServerUrlSelectable() { - return BitbucketEndpointConfiguration.get().isEndpointSelectable(); + return !BitbucketEndpointProvider.all().isEmpty(); } public ListBoxModel doFillServerUrlItems(@AncestorInPath SCMSourceOwner context) { @@ -396,7 +398,7 @@ public ListBoxModel doFillServerUrlItems(@AncestorInPath SCMSourceOwner context) if (!contextToCheck.hasPermission(Item.CONFIGURE)) { return new ListBoxModel(); } - return BitbucketEndpointConfiguration.get().getEndpointItems(); + return BitbucketEndpointProvider.listEndpoints(); } @RequirePOST 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 1878764a5..7c18a86af 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java @@ -38,6 +38,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRequestException; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketTeam; import com.cloudbees.jenkins.plugins.bitbucket.api.PullRequestBranchType; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; import com.cloudbees.jenkins.plugins.bitbucket.client.repository.UserRoleInRepository; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint; @@ -287,11 +288,11 @@ public String getServerUrl() { @DataBoundSetter public void setServerUrl(@CheckForNull String serverUrl) { - String url = BitbucketEndpointConfiguration.normalizeServerURL(serverUrl); - if (url == null) { - url = BitbucketEndpointConfiguration.get().getDefaultEndpoint().getServerUrl(); + if (serverUrl == null) { + this.serverUrl = BitbucketEndpointConfiguration.get().getDefaultEndpoint().getServerURL(); + } else { + this.serverUrl = Util.fixNull(URLUtils.normalizeURL(serverUrl)); } - this.serverUrl = url; } @NonNull @@ -397,6 +398,7 @@ private Iterable getBitbucketPullRequestsFromEvent(@NonNul private void retrievePullRequests(final BitbucketSCMSourceRequest request) throws IOException, InterruptedException { final String fullName = repoOwner + "/" + repository; + @SuppressWarnings("serial") class Skip extends IOException { } @@ -810,7 +812,7 @@ public DescriptorImpl getDescriptor() { @NonNull @Override - protected List retrieveActions(@CheckForNull SCMSourceEvent event, + protected List retrieveActions(@SuppressWarnings("rawtypes") @CheckForNull SCMSourceEvent event, @NonNull TaskListener listener) throws IOException, InterruptedException { // TODO when we have support for trusted events, use the details from event if event was from trusted source @@ -846,7 +848,7 @@ private boolean showAvatar() { @NonNull @Override protected List retrieveActions(@NonNull SCMHead head, - @CheckForNull SCMHeadEvent event, + @SuppressWarnings("rawtypes") @CheckForNull SCMHeadEvent event, @NonNull TaskListener listener) throws IOException, InterruptedException { // TODO when we have support for trusted events, use the details from event if event was from trusted source @@ -1061,7 +1063,7 @@ public static FormValidation doCheckServerUrl(@AncestorInPath SCMSourceOwner con return FormValidation.error( "Unauthorized to validate Server URL"); // not supposed to be seeing this form } - if (!BitbucketEndpointConfiguration.get().findEndpoint(value).isPresent()) { + if (!BitbucketEndpointProvider.lookupEndpoint(value).isPresent()) { return FormValidation.error("Unregistered Server: " + value); } return FormValidation.ok(); @@ -1081,7 +1083,7 @@ public static FormValidation doCheckMirrorId(@QueryParameter String value, } public boolean isServerUrlSelectable() { - return BitbucketEndpointConfiguration.get().isEndpointSelectable(); + return !BitbucketEndpointProvider.all().isEmpty(); } public ListBoxModel doFillServerUrlItems(@AncestorInPath SCMSourceOwner context) { @@ -1089,7 +1091,7 @@ public ListBoxModel doFillServerUrlItems(@AncestorInPath SCMSourceOwner context) if (!contextToCheck.hasPermission(Item.CONFIGURE)) { return new ListBoxModel(); } - return BitbucketEndpointConfiguration.get().getEndpointItems(); + return BitbucketEndpointProvider.listEndpoints(); } public ListBoxModel doFillCredentialsIdItems(@AncestorInPath SCMSourceOwner context, @QueryParameter String serverUrl) { @@ -1138,6 +1140,7 @@ protected SCMHeadCategory[] createCategories() { }; } + @SuppressWarnings("unchecked") public List> getTraitsDescriptorLists() { List all = new ArrayList<>(); // all that are applicable to our context @@ -1158,7 +1161,7 @@ public List> getTraitsDescrip List> result = new ArrayList<>(); NamedArrayList.select(all, "Within repository", NamedArrayList .anyOf(NamedArrayList.withAnnotation(Discovery.class), - NamedArrayList.withAnnotation(Selection.class)), + NamedArrayList.withAnnotation(Selection.class)), true, result); int insertionPoint = result.size(); NamedArrayList.select(all, "Git", it -> GitSCM.class.isAssignableFrom(it.getScmClass()), true, result); diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpoint.java new file mode 100644 index 000000000..a0fe8f6cd --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpoint.java @@ -0,0 +1,166 @@ +/* + * 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.api.endpoint; + +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentials; +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 hudson.Util; +import hudson.model.Describable; +import jenkins.model.Jenkins; +import org.apache.commons.lang3.StringUtils; +import org.jenkinsci.plugins.plaincredentials.StringCredentials; + +/** + * The implementation represents an endpoint configuration to be used in + * {@link BitbucketSCMSource}. + * + * @since 936.4.0 + */ +public interface BitbucketEndpoint extends Describable { + + /** + * Name to use to describe the endpoint. + * + * @return the name to use for the endpoint + */ + @CheckForNull + String getDisplayName(); + + /** + * The user facing URL of the specified repository. + * + * @param repoOwner the repository owner. + * @param repoSlug the repository name + * @return the user facing URL of the specified repository. + */ + @NonNull + String getRepositoryURL(@NonNull String repoOwner, @NonNull String repoSlug); + + /** + * Returns the type of this endpoint. + * + * @return endpoint type. + */ + @NonNull + EndpointType getType(); + + /** + * The URL of this endpoint. + * + * @return the URL of the endpoint. + */ + @NonNull + String getServerURL(); + + /** + * 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. + */ + boolean isManageHooks(); + + /** + * 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 + String getCredentialsId(); + + /** + * Jenkins Server Root URL to be used by this Bitbucket endpoint. The global + * setting from Jenkins.get().getRootUrl() will be used if this field is + * null or equals an empty string. + * + * @return the verbatim setting provided by endpoint configuration + */ + @CheckForNull + String getEndpointJenkinsRootURL(); + + boolean isEnableHookSignature(); + + /** + * The {@link StringCredentials#getId()} of the credentials to use to verify + * the signature of hooks. + * + * @return the configured credentials identifier to use + */ + @CheckForNull + String getHookSignatureCredentialsId(); + + /** + * Looks up the {@link StandardCredentials} to use for auto-management of hooks. + * + * @return the credentials or {@code null}. + */ + @CheckForNull + default StandardCredentials credentials() { + String credentialsId = Util.fixEmptyAndTrim(getCredentialsId()); + if (credentialsId == null) { + return null; + } else { + return BitbucketCredentials.lookupCredentials(getServerURL(), Jenkins.get(), credentialsId, StandardCredentials.class); + } + } + + /** + * Looks up the {@link StringCredentials} to use to verify the signature of hooks. + * + * @return the credentials or {@code null}. + */ + @CheckForNull + default StringCredentials hookSignatureCredentials() { + String credentialsId = Util.fixEmptyAndTrim(getHookSignatureCredentialsId()); + if (credentialsId == null) { + return null; + } else { + return BitbucketCredentials.lookupCredentials(getServerURL(), Jenkins.get(), credentialsId, StringCredentials.class); + } + } + + /** + * Returns if two endpoint are the equals. + * + * @param endpoint to compare + * @return {@code true} if endpoint are the same, {@code false} otherwise + */ + default boolean isEquals(BitbucketEndpoint endpoint) { + return StringUtils.equalsIgnoreCase(getServerURL(), endpoint.getServerURL()); + } + + /** + * + * @see Describable#getDescriptor() + */ + @Override + BitbucketEndpointDescriptor getDescriptor(); +} 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 new file mode 100644 index 000000000..ed0076e0e --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpointDescriptor.java @@ -0,0 +1,109 @@ +/* + * 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.endpoint; + +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentials; +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.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. + * + * @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 BitbucketCredentials.fillCredentialsIdItems(serverURL, jenkins, StandardCredentials.class, 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 new file mode 100644 index 000000000..4bf058508 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpointProvider.java @@ -0,0 +1,165 @@ +/* + * 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.api.endpoint; + +import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; +import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import hudson.util.ListBoxModel; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.UnaryOperator; +import org.apache.commons.lang3.StringUtils; + +/** + * A provider of {@link BitbucketEndpoint}s + * + * @since 936.4.0 + */ +public final class BitbucketEndpointProvider{ + + private BitbucketEndpointProvider() { + } + + /** + * Returns all configured Bitbucket endpoints in the global page. + * + * @return a list of Bitbucket endpoints. + */ + @NonNull + public static List all() { + return List.copyOf(BitbucketEndpointConfiguration.get().getEndpoints()); + } + + /** + * Populates a {@link ListBoxModel} with all Bitbucket endpoints in the + * global page. + * + * @return a {@link ListBoxModel} with all the endpoints + */ + @NonNull + public static ListBoxModel listEndpoints() { + ListBoxModel result = new ListBoxModel(); + for (BitbucketEndpoint endpoint : all()) { + String serverUrl = endpoint.getServerURL(); + String displayName = endpoint.getDisplayName(); + result.add(StringUtils.isBlank(displayName) ? serverUrl : displayName + " (" + serverUrl + ")", serverUrl); + } + return result; + } + + /** + * Checks to see if the supplied server URL is defined in the global + * configuration. + * + * @param serverURL the server url to check. + * @return the global configuration for the specified server URL or + * {@code Optional#empty()} if not found. + */ + @SuppressWarnings("unchecked") + public static Optional lookupEndpoint(@CheckForNull String serverURL) { + String normalizedServerURL = URLUtils.normalizeURL(serverURL); + return BitbucketEndpointConfiguration.get() + .getEndpoints().stream() + .filter(endpoint -> Objects.equals(normalizedServerURL, endpoint.getServerURL())) + .map(endpoint -> (T) endpoint) + .findFirst(); + } + + /** + * Checks to see if the supplied server URL is defined in the global + * configuration. + * + * @param serverURL the server url to check. + * @param clazz the class to check. + * @return the global configuration for the specified server URL or + * {@code Optional#empty()} if not found. + */ + public static Optional lookupEndpoint(@CheckForNull String serverURL, @NonNull Class clazz) { + return lookupEndpoint(serverURL) + .filter(clazz::isInstance) + .map(clazz::cast); + } + + /** + * Checks to see if the supplied server URL is defined in the global + * configuration. + * + * @param type filter of the endpoint to return. + * @return a collection of endpoints of given type. + */ + @NonNull + @SuppressWarnings("unchecked") + public static Collection lookupEndpoint(@NonNull EndpointType type) { + return all().stream() + .filter(endpoint -> endpoint.getType() == type) + .map(endpoint -> (T) endpoint) + .toList(); + } + + /** + * Register a new {@link BitbucketEndpoint} to the global configuration. The + * endpoint is created with default values and could be customised by the + * given endpointCustomiser. + *

+ * The given customiser can also return a different implementation + * + * @param name of the endpoint, alias for label + * @param serverURL the bitbucket endpoint URL + * @param endpointCustomiser an optional customiser for the created endpoint + * @return the registered endpoint instance. + */ + public static BitbucketEndpoint registerEndpoint(@NonNull String name, @NonNull String serverURL, @Nullable UnaryOperator endpointCustomiser) { + BitbucketEndpoint endpoint; + if (BitbucketApiUtils.isCloud(serverURL)) { + endpoint = new BitbucketCloudEndpoint(); + } else { + endpoint = new BitbucketServerEndpoint(name, serverURL, false, null, false, null); + } + if (endpointCustomiser != null) { + endpoint = endpointCustomiser.apply(endpoint); + } + BitbucketEndpointConfiguration.get().addEndpoint(endpoint); + return endpoint; + } + + /** + * Removes a {@link BitbucketEndpoint} that matches the given URl from the + * global configuration. + * + * @param serverURL the bitbucket endpoint URL + * @return {@code true} if the endpoint has been removed from the global + * configuration, {@code false} otherwise + */ + public static boolean unregisterEndpoint(@NonNull String serverURL) { + return BitbucketEndpointConfiguration.get().removeEndpoint(serverURL); + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/EndpointType.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/EndpointType.java new file mode 100644 index 000000000..e5185f3a2 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/EndpointType.java @@ -0,0 +1,28 @@ +/* + * 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.api.endpoint; + +public enum EndpointType { + SERVER, CLOUD +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiFactory.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiFactory.java index 730598d7b..b51c6d25c 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiFactory.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiFactory.java @@ -26,9 +26,8 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApiFactory; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator; -import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint; -import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -45,16 +44,16 @@ protected boolean isMatch(@Nullable String serverUrl) { @Override protected BitbucketApi create(@Nullable String serverUrl, @Nullable BitbucketAuthenticator authenticator, @NonNull String owner, @CheckForNull String projectKey, @CheckForNull String repository) { - AbstractBitbucketEndpoint endpoint = BitbucketEndpointConfiguration.get() - .findEndpoint(BitbucketCloudEndpoint.SERVER_URL) + BitbucketCloudEndpoint endpoint = BitbucketEndpointProvider + .lookupEndpoint(BitbucketCloudEndpoint.SERVER_URL, BitbucketCloudEndpoint.class) .orElse(null); boolean enableCache = false; int teamCacheDuration = 0; int repositoriesCacheDuration = 0; - if (endpoint instanceof BitbucketCloudEndpoint cloudEndpoint) { - enableCache = cloudEndpoint.isEnableCache(); - teamCacheDuration = cloudEndpoint.getTeamCacheDuration(); - repositoriesCacheDuration = cloudEndpoint.getRepositoriesCacheDuration(); + if (endpoint != null) { + enableCache = endpoint.isEnableCache(); + teamCacheDuration = endpoint.getTeamCacheDuration(); + repositoriesCacheDuration = endpoint.getRepositoriesCacheDuration(); } return new BitbucketCloudApiClient( enableCache, teamCacheDuration, repositoriesCacheDuration, diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/AbstractBitbucketEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/AbstractBitbucketEndpoint.java index 9eb4414a6..dc84d3161 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/AbstractBitbucketEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/AbstractBitbucketEndpoint.java @@ -24,13 +24,15 @@ package com.cloudbees.jenkins.plugins.bitbucket.endpoints; 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.BitbucketEndpointProvider; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentials; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils; 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 hudson.Util; -import hudson.model.AbstractDescribableImpl; import jenkins.authentication.tokens.api.AuthenticationTokens; import jenkins.model.Jenkins; import org.apache.commons.lang3.StringUtils; @@ -45,7 +47,7 @@ * * @since 2.2.0 */ -public abstract class AbstractBitbucketEndpoint extends AbstractDescribableImpl { +public abstract class AbstractBitbucketEndpoint implements BitbucketEndpoint { /** * {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point. @@ -93,19 +95,13 @@ public abstract class AbstractBitbucketEndpoint extends AbstractDescribableImpl< this.hookSignatureCredentialsId = enableHookSignature ? fixEmptyAndTrim(hookSignatureCredentialsId) : null; } - /** - * Optional name to use to describe the end-point. - * - * @return the name to use for the end-point - */ - @CheckForNull - public abstract String getDisplayName(); - /** * The URL of this endpoint. * * @return the URL of the endpoint. + * @deprecated Use {@link BitbucketEndpoint#getServerURL()} instead of this. */ + @Deprecated(since = "936.4.0", forRemoval = true) @NonNull public abstract String getServerUrl(); @@ -119,8 +115,7 @@ public abstract class AbstractBitbucketEndpoint extends AbstractDescribableImpl< static String normalizeJenkinsRootUrl(String rootUrl) { // This routine is not really BitbucketEndpointConfiguration // specific, it just works on strings with some defaults: - return Util.ensureEndsWith( - BitbucketEndpointConfiguration.normalizeServerURL(rootUrl),"/"); + return Util.ensureEndsWith(URLUtils.normalizeURL(fixEmptyAndTrim(rootUrl)), "/"); } /** @@ -130,28 +125,39 @@ static String normalizeJenkinsRootUrl(String rootUrl) { * * @return the verbatim setting provided by endpoint configuration */ + @Deprecated(since = "936.4.0", forRemoval = true) @CheckForNull public String getBitbucketJenkinsRootUrl() { return bitbucketJenkinsRootUrl; } + @Override + public String getEndpointJenkinsRootURL() { + return getBitbucketJenkinsRootUrl(); + } + + @NonNull + @Override + public String getRepositoryURL(@NonNull String repoOwner, @NonNull String repoSlug) { + return this.getRepositoryUrl(repoOwner, repoSlug); + } + @DataBoundSetter public void setBitbucketJenkinsRootUrl(String bitbucketJenkinsRootUrl) { if (manageHooks) { - this.bitbucketJenkinsRootUrl = fixEmptyAndTrim(bitbucketJenkinsRootUrl); - if (this.bitbucketJenkinsRootUrl != null) { - this.bitbucketJenkinsRootUrl = normalizeJenkinsRootUrl(this.bitbucketJenkinsRootUrl); - } + this.bitbucketJenkinsRootUrl = normalizeJenkinsRootUrl(bitbucketJenkinsRootUrl); } else { this.bitbucketJenkinsRootUrl = null; } } + @Override @CheckForNull public String getHookSignatureCredentialsId() { return hookSignatureCredentialsId; } + @Override public boolean isEnableHookSignature() { return enableHookSignature; } @@ -164,7 +170,9 @@ public boolean isEnableHookSignature() { * @return the normalized value from setting provided by endpoint * configuration (if not empty), or the global setting of * the Jenkins Root URL + * @deprecated Use {@link BitbucketEndpoint#getEndpointJenkinsRootURL()} instead of this. */ + @Deprecated(since = "936.4.0", forRemoval = true) @NonNull public String getEndpointJenkinsRootUrl() { if (StringUtils.isBlank(bitbucketJenkinsRootUrl)) { @@ -183,12 +191,12 @@ public String getEndpointJenkinsRootUrl() { * This is the routine intended for external consumption when one needs a * Jenkins Root URL to use for webhook configuration. * - * @param serverUrl Bitbucket Server URL for the endpoint config + * @param serverURL Bitbucket Server URL for the endpoint config * * @return the normalized custom or default Jenkins Root URL value */ @NonNull - public static String getEndpointJenkinsRootUrl(String serverUrl) { + public static String getEndpointJenkinsRootUrl(String serverURL) { // If this instance of Bitbucket connection has a custom root URL // configured to have this Jenkins server known by (e.g. when a // private network has different names preferable for different @@ -196,13 +204,17 @@ public static String getEndpointJenkinsRootUrl(String serverUrl) { // Note: do not pre-initialize to the global value, so it can be // reconfigured on the fly. - AbstractBitbucketEndpoint endpoint = BitbucketEndpointConfiguration.get() - .findEndpoint(serverUrl) + String endpointURL = null; + BitbucketEndpoint endpoint = BitbucketEndpointProvider + .lookupEndpoint(serverURL) .orElse(null); if (endpoint != null) { - return endpoint.getEndpointJenkinsRootUrl(); + endpointURL = endpoint.getEndpointJenkinsRootURL(); + } + if (endpointURL == null) { + endpointURL = DisplayURLProvider.get().getRoot(); } - return DisplayURLProvider.get().getRoot(); + return endpointURL; } /** @@ -220,6 +232,7 @@ public static String getEndpointJenkinsRootUrl(String serverUrl) { * * @return {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point. */ + @Override public final boolean isManageHooks() { return manageHooks; } @@ -231,6 +244,7 @@ public final boolean isManageHooks() { * @return the {@link StandardUsernamePasswordCredentials#getId()} of the credentials to use for auto-management * of hooks. */ + @Override @CheckForNull public final String getCredentialsId() { return credentialsId; @@ -241,6 +255,7 @@ public final String getCredentialsId() { * * @return the credentials or {@code null}. */ + @Override @CheckForNull public StandardCredentials credentials() { return BitbucketCredentials.lookupCredentials(getServerUrl(), Jenkins.get(), credentialsId, StandardCredentials.class); @@ -251,6 +266,7 @@ public StandardCredentials credentials() { * * @return the credentials or {@code null}. */ + @Override @CheckForNull public StringCredentials hookSignatureCredentials() { return BitbucketCredentials.lookupCredentials(getServerUrl(), Jenkins.get(), hookSignatureCredentialsId, StringCredentials.class); @@ -263,7 +279,7 @@ public StringCredentials hookSignatureCredentials() { */ @CheckForNull public BitbucketAuthenticator authenticator() { - return AuthenticationTokens.convert(BitbucketAuthenticator.authenticationContext(getServerUrl()), credentials()); + return AuthenticationTokens.convert(BitbucketAuthenticator.authenticationContext(getServerURL()), credentials()); } /** @@ -271,6 +287,6 @@ public BitbucketAuthenticator authenticator() { */ @Override public AbstractBitbucketEndpointDescriptor getDescriptor() { - return (AbstractBitbucketEndpointDescriptor) super.getDescriptor(); + return (AbstractBitbucketEndpointDescriptor) Jenkins.get().getDescriptorOrDie(getClass()); } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/AbstractBitbucketEndpointDescriptor.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/AbstractBitbucketEndpointDescriptor.java index b82be0f24..fec635564 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/AbstractBitbucketEndpointDescriptor.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/AbstractBitbucketEndpointDescriptor.java @@ -23,95 +23,14 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.endpoints; -import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator; -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.domains.URIRequirementBuilder; -import hudson.Util; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointDescriptor; 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.authentication.tokens.api.AuthenticationTokens; -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} base class for {@link AbstractBitbucketEndpoint} subclasses. * * @since 2.2.0 */ -public abstract class AbstractBitbucketEndpointDescriptor extends Descriptor { - /** - * Stapler form completion. - * - * @param credentialsId selected credentials. - * @param serverURL the server URL. - * @return the available credentials. - */ - @Restricted(NoExternalUse.class) // stapler - @RequirePOST - public ListBoxModel doFillCredentialsIdItems(@QueryParameter(fixEmpty = true) String credentialsId, - @QueryParameter(value = "serverUrl", fixEmpty = true) String serverURL) { - Jenkins jenkins = Jenkins.get(); - jenkins.checkPermission(Jenkins.MANAGE); - StandardListBoxModel result = new StandardListBoxModel(); - result.includeMatchingAs( - ACL.SYSTEM2, - jenkins, - StandardCredentials.class, - URIRequirementBuilder.fromUri(serverURL).build(), - AuthenticationTokens.matcher(BitbucketAuthenticator.authenticationContext(serverURL))); - if (credentialsId != null) { - result.includeCurrentValue(credentialsId); - } - return result; - } - - /** - * Stapler form completion. - * - * @param hookSignatureCredentialsId selected hook signature credentials. - * @param serverURL the server URL. - * @return the available credentials. - */ - @Restricted(NoExternalUse.class) // stapler - @RequirePOST - public ListBoxModel doFillHookSignatureCredentialsIdItems(@QueryParameter(fixEmpty = true) String hookSignatureCredentialsId, - @QueryParameter(value = "serverUrl", fixEmpty = true) String serverURL) { - Jenkins jenkins = Jenkins.get(); - jenkins.checkPermission(Jenkins.MANAGE); - 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) { - 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(); - } +@Deprecated(since = "936.4.0") +public abstract class AbstractBitbucketEndpointDescriptor extends BitbucketEndpointDescriptor { } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint.java index fdf087729..ef0610f83 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint.java @@ -23,6 +23,7 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.endpoints; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.EndpointType; import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudApiClient; import com.cloudbees.plugins.credentials.common.StandardCredentials; import com.damnhandy.uri.template.UriTemplate; @@ -47,10 +48,6 @@ public class BitbucketCloudEndpoint extends AbstractBitbucketEndpoint { * The URL of Bitbucket Cloud. */ public static final String SERVER_URL = "https://bitbucket.org"; - /** - * A bad URL of Bitbucket Cloud. - */ - public static final String BAD_SERVER_URL = "http://bitbucket.org"; /** * {@code true} if caching should be used to reduce requests to Bitbucket. @@ -131,12 +128,18 @@ public String getDisplayName() { /** * {@inheritDoc} */ - @NonNull @Override + @NonNull + @Deprecated(since = "936.4.0", forRemoval = true) public String getServerUrl() { return SERVER_URL; } + @Override + public String getServerURL() { + return getServerUrl(); + } + /** * {@inheritDoc} */ @@ -150,6 +153,11 @@ public String getRepositoryUrl(@NonNull String repoOwner, @NonNull String reposi return template.expand(); } + @Override + public EndpointType getType() { + return EndpointType.CLOUD; + } + /** * Our descriptor. */ @@ -188,4 +196,5 @@ private Object readResolve() { } return this; } + } 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 53bf1be71..48d4ef98a 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 @@ -23,6 +23,10 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.endpoints; +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.util.BitbucketApiUtils; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -31,20 +35,18 @@ import hudson.security.ACL; import hudson.security.Permission; import hudson.util.ListBoxModel; -import java.net.URI; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.ListIterator; -import java.util.Locale; import java.util.Objects; -import java.util.Optional; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; import jenkins.model.GlobalConfiguration; import jenkins.model.Jenkins; import net.sf.json.JSONObject; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -59,10 +61,10 @@ public class BitbucketEndpointConfiguration extends GlobalConfiguration { /** - * The list of {@link AbstractBitbucketEndpoint}, this is subject to the constraint that there can only ever be - * one entry for each {@link AbstractBitbucketEndpoint#getServerUrl()}. + * The list of {@link BitbucketEndpoint}, this is subject to the constraint that there can only ever be + * one entry for each {@link BitbucketEndpoint#getServerURL()}. */ - private List endpoints; + private List endpoints = new CopyOnWriteArrayList<>(); /** * Constructor. @@ -91,25 +93,26 @@ public Permission getRequiredGlobalConfigPagePermission() { * {@code serverUrl} field. When called from {@link ACL#SYSTEM} this will update the configuration with the * missing definitions of resolved URLs. * - * @param bitbucketServerUrl the value of the old url field. + * @param serverURL the value of the old url field. * @return the value of the new url field. */ @Restricted(NoExternalUse.class) // only for plugin internal use. @NonNull - public String readResolveServerUrl(@CheckForNull String bitbucketServerUrl) { - String serverURL = normalizeServerURL(bitbucketServerUrl); - serverURL = StringUtils.defaultIfBlank(serverURL, BitbucketCloudEndpoint.SERVER_URL); - AbstractBitbucketEndpoint endpoint = findEndpoint(serverURL).orElse(null); + public String readResolveServerUrl(@CheckForNull String serverURL) { + String normalizedURL = URLUtils.normalizeURL(serverURL); + normalizedURL = StringUtils.defaultIfBlank(normalizedURL, BitbucketCloudEndpoint.SERVER_URL); + BitbucketEndpoint endpoint = BitbucketEndpointProvider.lookupEndpoint(serverURL) + .orElse(null); if (endpoint == null && ACL.SYSTEM2.equals(Jenkins.getAuthentication2())) { - if (BitbucketCloudEndpoint.SERVER_URL.equals(serverURL) - || BitbucketCloudEndpoint.BAD_SERVER_URL.equals(serverURL)) { + if (BitbucketApiUtils.isCloud(normalizedURL)) { // exception case - addEndpoint(new BitbucketCloudEndpoint()); + endpoint = new BitbucketCloudEndpoint(); } else { - addEndpoint(new BitbucketServerEndpoint(serverURL)); + endpoint = new BitbucketServerEndpoint(normalizedURL); } + addEndpoint(endpoint); } - return endpoint == null ? serverURL : endpoint.getServerUrl(); + return endpoint == null ? normalizedURL : endpoint.getServerURL(); } /** @@ -125,11 +128,13 @@ public boolean isEndpointSelectable() { * Populates a {@link ListBoxModel} with the endpoints. * * @return A {@link ListBoxModel} with all the endpoints + * @deprecated Use {@link BitbucketEndpointProvider#listEndpoints()} instead of this. */ + @Deprecated public ListBoxModel getEndpointItems() { ListBoxModel result = new ListBoxModel(); - for (AbstractBitbucketEndpoint endpoint : getEndpoints()) { - String serverUrl = endpoint.getServerUrl(); + for (BitbucketEndpoint endpoint : getEndpoints()) { + String serverUrl = endpoint.getServerURL(); String displayName = endpoint.getDisplayName(); result.add(StringUtils.isBlank(displayName) ? serverUrl : displayName + " (" + serverUrl + ")", serverUrl); } @@ -151,10 +156,12 @@ public boolean configure(StaplerRequest2 req, JSONObject json) throws FormExcept * @return the list of endpoints */ @NonNull - public synchronized List getEndpoints() { - return endpoints == null || endpoints.isEmpty() + public List getEndpoints() { + // make a local copy so if changes in meanwhile you do not get NPE + List/**/ localEndpoints = this.endpoints; + return CollectionUtils.isEmpty(localEndpoints) ? List.of(new BitbucketCloudEndpoint()) - : Collections.unmodifiableList(endpoints); + : Collections.unmodifiableList(localEndpoints); } /** @@ -162,32 +169,32 @@ public synchronized List getEndpoints() { * * @param endpoints the list of endpoints. */ - public synchronized void setEndpoints(@CheckForNull List endpoints) { + public void setEndpoints(@CheckForNull List endpoints) { Jenkins.get().checkPermission(Jenkins.MANAGE); - List eps = new ArrayList<>(Util.fixNull(endpoints)); + + List eps = new ArrayList<>(Util.fixNull(endpoints)); // remove duplicates and empty urls - Set serverUrls = new HashSet<>(); - for (ListIterator iterator = eps.listIterator(); iterator.hasNext(); ) { - AbstractBitbucketEndpoint endpoint = iterator.next(); - String serverUrl = endpoint.getServerUrl(); - if (StringUtils.isBlank(serverUrl) || serverUrls.contains(serverUrl)) { + Set serverURLs = new HashSet<>(); + for (ListIterator iterator = eps.listIterator(); iterator.hasNext(); ) { + BitbucketEndpoint endpoint = iterator.next(); + String serverURL = endpoint.getServerURL(); + if (StringUtils.isBlank(serverURL) || serverURLs.contains(serverURL)) { iterator.remove(); continue; - } else if (!(endpoint instanceof BitbucketCloudEndpoint) - && BitbucketCloudEndpoint.SERVER_URL.equals(serverUrl)) { + } 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(), endpoint.isEnableHookSignature(), endpoint.getHookSignatureCredentialsId()); - cloudEndpoint.setBitbucketJenkinsRootUrl(endpoint.getBitbucketJenkinsRootUrl()); + cloudEndpoint.setBitbucketJenkinsRootUrl(endpoint.getEndpointJenkinsRootURL()); iterator.set(cloudEndpoint); } - serverUrls.add(serverUrl); + serverURLs.add(serverURL); } if (eps.isEmpty()) { eps.add(new BitbucketCloudEndpoint()); } - this.endpoints = eps; + this.endpoints = new CopyOnWriteArrayList<>(eps); save(); } @@ -197,15 +204,14 @@ public synchronized void setEndpoints(@CheckForNull List newEndpoints = new ArrayList<>(getEndpoints()); - for (AbstractBitbucketEndpoint ep : newEndpoints) { - if (ep.getServerUrl().equals(endpoint.getServerUrl())) { + public boolean addEndpoint(@NonNull BitbucketEndpoint endpoint) { + Jenkins.get().checkPermission(Jenkins.MANAGE); + for (BitbucketEndpoint ep : endpoints) { + if (endpoint.isEquals(ep)) { return false; } } - newEndpoints.add(endpoint); - setEndpoints(newEndpoints); + endpoints.add(endpoint); return true; } @@ -214,12 +220,13 @@ public synchronized boolean addEndpoint(@NonNull AbstractBitbucketEndpoint endpo * * @param endpoint the endpoint to update. */ - public synchronized void updateEndpoint(@NonNull AbstractBitbucketEndpoint endpoint) { - List newEndpoints = new ArrayList<>(getEndpoints()); + public void updateEndpoint(@NonNull BitbucketEndpoint endpoint) { + Jenkins.get().checkPermission(Jenkins.MANAGE); + List newEndpoints = endpoints; boolean found = false; for (int i = 0; i < newEndpoints.size(); i++) { - AbstractBitbucketEndpoint ep = newEndpoints.get(i); - if (ep.getServerUrl().equals(endpoint.getServerUrl())) { + BitbucketEndpoint ep = newEndpoints.get(i); + if (endpoint.isEquals(ep)) { newEndpoints.set(i, endpoint); found = true; break; @@ -228,7 +235,6 @@ public synchronized void updateEndpoint(@NonNull AbstractBitbucketEndpoint endpo if (!found) { newEndpoints.add(endpoint); } - setEndpoints(newEndpoints); } /** @@ -237,8 +243,8 @@ public synchronized void updateEndpoint(@NonNull AbstractBitbucketEndpoint endpo * @param endpoint the endpoint to remove. * @return {@code true} if the list of endpoints was modified */ - public boolean removeEndpoint(@NonNull AbstractBitbucketEndpoint endpoint) { - return removeEndpoint(endpoint.getServerUrl()); + public boolean removeEndpoint(@NonNull BitbucketEndpoint endpoint) { + return endpoints.removeIf(e -> e.isEquals(endpoint)); } /** @@ -247,46 +253,13 @@ public boolean removeEndpoint(@NonNull AbstractBitbucketEndpoint endpoint) { * @param serverURL the server URL to remove. * @return {@code true} if the list of endpoints was modified */ - public synchronized boolean removeEndpoint(@CheckForNull String serverURL) { - String fixedServerURL = normalizeServerURL(serverURL); - List newEndpoints = new ArrayList<>(getEndpoints()); - boolean modified = newEndpoints.removeIf(endpoint -> Objects.equals(fixedServerURL, endpoint.getServerUrl())); - setEndpoints(newEndpoints); - return modified; - } - - /** - * Checks to see if the supplied server URL is defined in the global configuration. - * - * @param serverURL the server url to check. - * @return the global configuration for the specified server url or {@code null} if not defined. - */ - public synchronized Optional findEndpoint(@CheckForNull String serverURL) { - serverURL = normalizeServerURL(serverURL); - for (AbstractBitbucketEndpoint endpoint : getEndpoints()) { - if (Objects.equals(serverURL, endpoint.getServerUrl())) { - return Optional.of(endpoint); - } - } - return Optional.empty(); - } - - /** - * Checks to see if the supplied server URL is defined in the global configuration. - * - * @param serverURL the server url to check. - * @param clazz the class to check. - * @return the global configuration for the specified server url or {@code null} if not defined. - */ - public synchronized Optional findEndpoint(@CheckForNull String serverURL, - Class clazz) { - return findEndpoint(serverURL) - .filter(clazz::isInstance) - .map(clazz::cast); + public boolean removeEndpoint(@CheckForNull String serverURL) { + String fixedServerURL = URLUtils.normalizeURL(serverURL); + return endpoints.removeIf(e -> Objects.equals(fixedServerURL, e.getServerURL())); } @NonNull - public synchronized AbstractBitbucketEndpoint getDefaultEndpoint() { + public BitbucketEndpoint getDefaultEndpoint() { return getEndpoints().get(0); } @@ -295,55 +268,12 @@ public synchronized AbstractBitbucketEndpoint getDefaultEndpoint() { * * @param serverURL the server URL. * @return the normalized server URL. - * @deprecated - * @see #normalizeServerURL + * @deprecated Do not use at all, endpoint URL are already managed internally */ @CheckForNull - @Deprecated + @Deprecated(forRemoval = true) public static String normalizeServerUrl(@CheckForNull String serverURL) { - return normalizeServerURL(serverURL); - } - - /** - * Fix a server URL. - * - * @param serverURL the server URL. - * @return the normalized server URL. - */ - @CheckForNull - public static String normalizeServerURL(@CheckForNull String serverURL) { - if (StringUtils.isBlank(serverURL)) { - return null; - } - try { - URI uri = new URI(serverURL).normalize(); - String scheme = uri.getScheme(); - if ("http".equals(scheme) || "https".equals(scheme)) { - // we only expect http / https, but also these are the only ones where we know the authority - // is server based, i.e. [userinfo@]server[:port] - // DNS names must be US-ASCII and are case insensitive, so we force all to lowercase - - String host = uri.getHost() == null ? null : uri.getHost().toLowerCase(Locale.ENGLISH); - int port = uri.getPort(); - if ("http".equals(scheme) && port == 80) { - port = -1; - } else if ("https".equals(scheme) && port == 443) { - port = -1; - } - serverURL = new URI( - scheme, - uri.getUserInfo(), - host, - port, - uri.getPath(), - uri.getQuery(), - uri.getFragment() - ).toASCIIString(); - } - } catch (URISyntaxException e) { - // ignore, this was a best effort tidy-up - } - return serverURL.replaceAll("/$", ""); + return URLUtils.normalizeURL(serverURL); } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint.java index 4133f2936..6e7fe7073 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint.java @@ -23,6 +23,9 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.endpoints; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.EndpointType; +import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils; import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerVersion; import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation; import com.cloudbees.plugins.credentials.common.StandardCredentials; @@ -69,6 +72,21 @@ public class BitbucketServerEndpoint extends AbstractBitbucketEndpoint { "source." }; + @NonNull + public static BitbucketServerWebhookImplementation findWebhookImplementation(String serverURL) { + return BitbucketEndpointProvider.lookupEndpoint(serverURL, BitbucketServerEndpoint.class) + .map(BitbucketServerEndpoint::getWebhookImplementation) + .orElse(BitbucketServerWebhookImplementation.NATIVE); + } + + @NonNull + public static BitbucketServerVersion findServerVersion(String serverURL) { + return BitbucketEndpointProvider + .lookupEndpoint(serverURL, BitbucketServerEndpoint.class) + .map(endpoint -> endpoint.getServerVersion()) + .orElse(BitbucketServerVersion.VERSION_7); + } + /** * Optional name to use to describe the end-point. */ @@ -133,28 +151,22 @@ public BitbucketServerEndpoint(@CheckForNull String displayName, @NonNull String boolean enableHookSignature, @CheckForNull String hookSignatureCredentialsId) { super(manageHooks, credentialsId, enableHookSignature, hookSignatureCredentialsId); // use fixNull to silent nullability check - this.serverUrl = Util.fixNull(BitbucketEndpointConfiguration.normalizeServerURL(serverUrl)); + this.serverUrl = Util.fixNull(URLUtils.normalizeURL(serverUrl)); this.displayName = StringUtils.isBlank(displayName) ? SCMName.fromUrl(this.serverUrl, COMMON_PREFIX_HOSTNAMES) : displayName.trim(); } - @NonNull - public static BitbucketServerWebhookImplementation findWebhookImplementation(String serverUrl) { - final BitbucketServerEndpoint endpoint = BitbucketEndpointConfiguration.get() - .findEndpoint(serverUrl, BitbucketServerEndpoint.class) - .orElse(null); - if (endpoint != null) { - return endpoint.getWebhookImplementation(); - } - - return BitbucketServerWebhookImplementation.PLUGIN; - } - public boolean isCallCanMerge() { return callCanMerge; } + @NonNull + @Override + public EndpointType getType() { + return EndpointType.SERVER; + } + @DataBoundSetter public void setCallCanMerge(boolean callCanMerge) { this.callCanMerge = callCanMerge; @@ -169,14 +181,6 @@ public void setCallChanges(boolean callChanges) { this.callChanges = callChanges; } - @NonNull - public static BitbucketServerVersion findServerVersion(String serverUrl) { - return BitbucketEndpointConfiguration.get() - .findEndpoint(serverUrl, BitbucketServerEndpoint.class) - .map(endpoint -> endpoint.getServerVersion()) - .orElse(BitbucketServerVersion.VERSION_7); - } - @NonNull public BitbucketServerVersion getServerVersion() { return this.serverVersion; @@ -198,24 +202,39 @@ public String getDisplayName() { /** * {@inheritDoc} */ - @NonNull @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); + } + + /** + * {@inheritDoc} + */ + @NonNull + @Override + public String getRepositoryURL(@NonNull String repoOwner, @NonNull String repository) { UriTemplate template = UriTemplate .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(); + : 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!") @@ -304,4 +323,5 @@ public static FormValidation doCheckServerUrl(@QueryParameter String value) { } } + } 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 c0a2a2385..428b63555 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 @@ -214,7 +214,7 @@ public boolean supports(SCMSource source) { } @Override - protected boolean supportsDescriptor(SCMDescriptor scmDescriptor) { + protected boolean supportsDescriptor(@SuppressWarnings("rawtypes") SCMDescriptor scmDescriptor) { return false; } 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 37be59c3c..5bb85421c 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 @@ -23,9 +23,9 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.hooks; -import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint; -import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -113,6 +113,7 @@ public HttpResponse doNotify(StaplerRequest2 req) throws IOException { 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) { @@ -127,8 +128,8 @@ public HttpResponse doNotify(StaplerRequest2 req) throws IOException { serverURL = BitbucketCloudEndpoint.SERVER_URL; } - AbstractBitbucketEndpoint endpoint = BitbucketEndpointConfiguration.get() - .findEndpoint(serverURL) + BitbucketEndpoint endpoint = BitbucketEndpointProvider + .lookupEndpoint(serverURL) .orElse(null); if (endpoint != null) { if (endpoint.isEnableHookSignature()) { @@ -141,7 +142,7 @@ public HttpResponse doNotify(StaplerRequest2 req) throws IOException { 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 endpoint {0}.", endpoint); + 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); @@ -153,7 +154,7 @@ public HttpResponse doNotify(StaplerRequest2 req) throws IOException { } @Nullable - private HttpResponseException checkSignature(@NonNull StaplerRequest2 req, @NonNull String body, @NonNull AbstractBitbucketEndpoint endpoint) { + 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(); diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookAutoRegisterListener.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookAutoRegisterListener.java index fccaf553d..10d45025e 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookAutoRegisterListener.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookAutoRegisterListener.java @@ -27,9 +27,10 @@ import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSourceContext; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApiFactory; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketWebHook; -import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint; -import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -45,6 +46,7 @@ import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; +import jenkins.authentication.tokens.api.AuthenticationTokens; import jenkins.scm.api.SCMHeadObserver; import jenkins.scm.api.SCMSource; import jenkins.scm.api.SCMSourceOwner; @@ -142,8 +144,8 @@ private synchronized void registerHooks(SCMSourceOwner owner) throws IOException case DISABLE: continue; case SYSTEM: - AbstractBitbucketEndpoint endpoint = BitbucketEndpointConfiguration.get() - .findEndpoint(source.getServerUrl()) + BitbucketEndpoint endpoint = BitbucketEndpointProvider + .lookupEndpoint(source.getServerUrl()) .orElse(null); if (endpoint == null || !endpoint.isManageHooks()) { continue; @@ -219,14 +221,14 @@ private BitbucketApi bitbucketApiFor(@NonNull BitbucketSCMSource source) { case DISABLE: return null; case SYSTEM: - AbstractBitbucketEndpoint endpoint = BitbucketEndpointConfiguration.get() - .findEndpoint(source.getServerUrl()) + BitbucketEndpoint endpoint = BitbucketEndpointProvider + .lookupEndpoint(source.getServerUrl()) .orElse(null); return endpoint == null || !endpoint.isManageHooks() ? null : BitbucketApiFactory.newInstance( - endpoint.getServerUrl(), - endpoint.authenticator(), + endpoint.getServerURL(), + AuthenticationTokens.convert(BitbucketAuthenticator.authenticationContext(endpoint.getServerURL()), endpoint.credentials()), source.getRepoOwner(), null, source.getRepository() 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 c5ef3321c..b8a3c521c 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 @@ -25,9 +25,9 @@ import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketWebHook; +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.BitbucketRepositoryHook; -import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint; -import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils; import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerWebhook; @@ -215,8 +215,8 @@ public BitbucketWebHook getHook(BitbucketSCMSource owner) { @Nullable private String getSecret(@NonNull String serverURL) { - AbstractBitbucketEndpoint endpoint = BitbucketEndpointConfiguration.get() - .findEndpoint(serverURL) + BitbucketEndpoint endpoint = BitbucketEndpointProvider + .lookupEndpoint(serverURL) .orElseThrow(); if (endpoint.isEnableHookSignature()) { StringCredentials credentials = endpoint.hookSignatureCredentials(); @@ -230,8 +230,8 @@ private String getSecret(@NonNull String serverURL) { } private static List getNativeServerEvents(String serverUrl) { - BitbucketServerEndpoint endpoint = BitbucketEndpointConfiguration.get() - .findEndpoint(serverUrl, BitbucketServerEndpoint.class) + BitbucketServerEndpoint endpoint = BitbucketEndpointProvider + .lookupEndpoint(serverUrl, BitbucketServerEndpoint.class) .orElse(null); if (endpoint != null) { switch (endpoint.getServerVersion()) { diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/util/BitbucketApiUtils.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/util/BitbucketApiUtils.java index bd7623a6e..3d2711fb2 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/util/BitbucketApiUtils.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/util/BitbucketApiUtils.java @@ -28,8 +28,8 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApiFactory; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRequestException; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudApiClient; -import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.common.StandardCredentials; @@ -47,7 +47,6 @@ import jenkins.authentication.tokens.api.AuthenticationTokens; import jenkins.model.Jenkins; import jenkins.scm.api.SCMSourceOwner; -import org.apache.commons.lang3.StringUtils; import org.apache.hc.core5.http.HttpHost; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -66,7 +65,11 @@ public static boolean isCloud(BitbucketApi client) { } public static boolean isCloud(@NonNull String serverURL) { - return StringUtils.startsWithAny(serverURL, BitbucketCloudEndpoint.SERVER_URL, BitbucketCloudEndpoint.BAD_SERVER_URL); + try { + return "bitbucket.org".equalsIgnoreCase(new URL(serverURL).getHost()); + } catch (MalformedURLException e) { + return false; + } } public static ListBoxModel getFromBitbucket(SCMSourceOwner context, @@ -87,10 +90,10 @@ public static ListBoxModel getFromBitbucket(SCMSourceOwner context, return new ListBoxModel(); // not permitted to try connecting with these credentials } - serverURL = BitbucketEndpointConfiguration.get() - .findEndpoint(serverURL) + serverURL = BitbucketEndpointProvider + .lookupEndpoint(serverURL) .orElse(BitbucketEndpointConfiguration.get().getDefaultEndpoint()) - .getServerUrl(); + .getServerURL(); StandardCredentials credentials = BitbucketCredentials.lookupCredentials( serverURL, context, diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/util/BitbucketCredentials.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/util/BitbucketCredentials.java index b73fa5da4..db101acda 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/util/BitbucketCredentials.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/util/BitbucketCredentials.java @@ -24,6 +24,7 @@ package com.cloudbees.jenkins.plugins.bitbucket.impl.util; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.CredentialsProvider; @@ -113,10 +114,10 @@ public static ListBoxModel fillCredentialsIdItems(SCMSourceOwner context, String ? task.getDefaultAuthentication2() : ACL.SYSTEM2; - serverURL = BitbucketEndpointConfiguration.get() - .findEndpoint(serverURL) + serverURL = BitbucketEndpointProvider + .lookupEndpoint(serverURL) .orElse(BitbucketEndpointConfiguration.get().getDefaultEndpoint()) - .getServerUrl(); + .getServerURL(); result.includeMatchingAs( authentication, @@ -130,10 +131,10 @@ public static ListBoxModel fillCredentialsIdItems(SCMSourceOwner context, String public static FormValidation checkCredentialsId(@CheckForNull SCMSourceOwner context, String value, String serverURL) { if (StringUtils.isNotBlank(value)) { - serverURL = BitbucketEndpointConfiguration.get() - .findEndpoint(serverURL) + serverURL = BitbucketEndpointProvider + .lookupEndpoint(serverURL) .orElse(BitbucketEndpointConfiguration.get().getDefaultEndpoint()) - .getServerUrl(); + .getServerURL(); AccessControlled contextToCheck = context == null ? Jenkins.get() : context; contextToCheck.checkPermission(CredentialsProvider.VIEW); @@ -161,4 +162,21 @@ public static FormValidation checkCredentialsId(@CheckForNull SCMSourceOwner con } } + public static ListBoxModel fillCredentialsIdItems(@CheckForNull String serverURL, + @NonNull ItemGroup context, + @NonNull Class type, + @CheckForNull String credentialsId) { + StandardListBoxModel result = new StandardListBoxModel(); + result.includeMatchingAs( + ACL.SYSTEM2, + context, + type, + URIRequirementBuilder.fromUri(serverURL).build(), + AuthenticationTokens.matcher(BitbucketAuthenticator.authenticationContext(serverURL))); + if (credentialsId != null) { + result.includeCurrentValue(credentialsId); + } + return result; + } + } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/util/URLUtils.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/util/URLUtils.java index 7dd963e33..99bc6e7d6 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/util/URLUtils.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/util/URLUtils.java @@ -26,7 +26,11 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; +import java.util.Locale; +import org.apache.commons.lang3.StringUtils; public final class URLUtils { @@ -46,4 +50,47 @@ public static String removeAuthority(@CheckForNull String url) { } return url; } + + /** + * Fix a server URL. + * + * @param serverURL the server URL. + * @return the normalized server URL. + */ + @CheckForNull + public static String normalizeURL(@CheckForNull String serverURL) { + if (StringUtils.isBlank(serverURL)) { + return null; + } + try { + URI uri = new URI(serverURL).normalize(); + String scheme = uri.getScheme(); + if ("http".equals(scheme) || "https".equals(scheme)) { + // we only expect http / https, but also these are the only ones where we know the authority + // is server based, i.e. [userinfo@]server[:port] + // DNS names must be US-ASCII and are case insensitive, so we force all to lowercase + + String host = uri.getHost() == null ? null : uri.getHost().toLowerCase(Locale.ENGLISH); + int port = uri.getPort(); + if ("http".equals(scheme) && port == 80) { + port = -1; + } else if ("https".equals(scheme) && port == 443) { + port = -1; + } + serverURL = new URI( + scheme, + uri.getUserInfo(), + host, + port, + uri.getPath(), + uri.getQuery(), + uri.getFragment() + ).toASCIIString(); + } + } catch (URISyntaxException e) { + // ignore, this was a best effort tidy-up + } + return serverURL.replaceAll("/$", ""); + } + } 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 54479f559..28e97b9ef 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 @@ -35,8 +35,8 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRequestException; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketTeam; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketWebHook; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; import com.cloudbees.jenkins.plugins.bitbucket.client.repository.UserRoleInRepository; -import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.filesystem.BitbucketSCMFile; import com.cloudbees.jenkins.plugins.bitbucket.impl.client.AbstractBitbucketApi; @@ -261,8 +261,8 @@ private List getPullRequests(UriTemplate template) t pullRequests.removeIf(this::shouldIgnore); - BitbucketServerEndpoint endpoint = BitbucketEndpointConfiguration.get() - .findEndpoint(this.baseURL, BitbucketServerEndpoint.class) + BitbucketServerEndpoint endpoint = BitbucketEndpointProvider + .lookupEndpoint(this.baseURL, BitbucketServerEndpoint.class) .orElse(null); for (BitbucketServerPullRequest pullRequest : pullRequests) { @@ -389,8 +389,8 @@ public BitbucketPullRequest getPullRequestById(@NonNull Integer id) throws IOExc BitbucketServerPullRequest pr = JsonParser.toJava(response, BitbucketServerPullRequest.class); setupClosureForPRBranch(pr); - BitbucketServerEndpoint endpoint = BitbucketEndpointConfiguration.get() - .findEndpoint(this.baseURL, BitbucketServerEndpoint.class) + BitbucketServerEndpoint endpoint = BitbucketEndpointProvider + .lookupEndpoint(this.baseURL, BitbucketServerEndpoint.class) .orElse(null); setupPullRequest(pr, endpoint); return pr; diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketAuthenticatorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketAuthenticatorTest.java index bee103be0..741790582 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketAuthenticatorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketAuthenticatorTest.java @@ -46,6 +46,7 @@ import jenkins.authentication.tokens.api.AuthenticationTokenContext; import jenkins.authentication.tokens.api.AuthenticationTokens; import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.jvnet.hudson.test.JenkinsRule; @@ -58,6 +59,13 @@ class BitbucketAuthenticatorTest { private StandardUsernameCredentials credentials; + static JenkinsRule j; + + @BeforeAll + static void init(JenkinsRule rule) { + j = rule; + } + @BeforeEach void setup() throws Exception { credentials = new UsernamePasswordCredentialsImpl(CredentialsScope.SYSTEM, "credentialsId", "description", "user", "password"); @@ -87,7 +95,7 @@ void test_authenticationContext_builder() { } @Test - void given_UsernamePasswordCredentials_returns_BitbucketOAuthAuthenticator(JenkinsRule r) throws Exception { + void given_UsernamePasswordCredentials_returns_BitbucketOAuthAuthenticator() throws Exception { String clientSecret = insecure().nextAlphabetic(32); String clientId = insecure().nextAlphabetic(18); credentials = new UsernamePasswordCredentialsImpl(CredentialsScope.SYSTEM, @@ -105,9 +113,9 @@ void given_UsernamePasswordCredentials_returns_BitbucketOAuthAuthenticator(Jenki } @Test - void given_UsernamePasswordCredentials_returns_BitbucketUsernamePasswordAuthenticator(JenkinsRule r) { + void given_UsernamePasswordCredentials_returns_BitbucketUsernamePasswordAuthenticator() { // use cases: real username/password or an app password - List list = Collections.singletonList(credentials); + List list = List.of(credentials); AuthenticationTokenContext ctx = BitbucketAuthenticator.authenticationContext(null); Credentials c = CredentialsMatchers.firstOrNull(list, AuthenticationTokens.matcher(ctx)); assertThat(c).isNotNull(); @@ -117,7 +125,7 @@ void given_UsernamePasswordCredentials_returns_BitbucketUsernamePasswordAuthenti } @Test - void given_StringCredentials_returns_BitbucketAccessTokenAuthenticator_cloud(JenkinsRule r) { + void given_StringCredentials_returns_BitbucketAccessTokenAuthenticator_cloud() { // repository Access Token StringCredentialsImpl tokenCredentials = new StringCredentialsImpl(CredentialsScope.SYSTEM, credentials.getId(), @@ -133,7 +141,7 @@ void given_StringCredentials_returns_BitbucketAccessTokenAuthenticator_cloud(Jen } @Test - void given_CertificateCredentials_returns_BitbucketUsernamePasswordAuthenticator(JenkinsRule r) throws Exception { + void given_CertificateCredentials_returns_BitbucketUsernamePasswordAuthenticator() throws Exception { String password = UUID.randomUUID().toString(); StandardCertificateCredentials certCredentials = new CertificateCredentialsImpl(CredentialsScope.SYSTEM, credentials.getId(), diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigatorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigatorTest.java index 022611cad..cc4285d16 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigatorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigatorTest.java @@ -29,28 +29,35 @@ import java.util.Arrays; import java.util.Collections; import jenkins.model.Jenkins; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.junit.jupiter.WithJenkins; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasProperty; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; +import static org.assertj.core.api.Assertions.assertThat; -public class BitbucketSCMNavigatorTest { - @ClassRule - public static JenkinsRule j = new JenkinsRule(); - @Rule - public TestName currentTestName = new TestName(); +@WithJenkins +class BitbucketSCMNavigatorTest { + + static JenkinsRule j; + + @BeforeAll + static void init(JenkinsRule rule) { + j = rule; + } + + private String currentTestName; + + @BeforeEach + void setup(TestInfo testInfo) { + currentTestName = testInfo.getTestMethod().get().getName(); + } private BitbucketSCMNavigator load() { - return load(currentTestName.getMethodName()); + return load(currentTestName); } private BitbucketSCMNavigator load(String dataSet) { @@ -59,68 +66,72 @@ private BitbucketSCMNavigator load(String dataSet) { } @Test - public void modern() throws Exception { + void modern() throws Exception { BitbucketSCMNavigator instance = load(); - assertThat(instance.id(), is("https://bitbucket.org::cloudbeers")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getServerUrl(), is(BitbucketCloudEndpoint.SERVER_URL)); - assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); - assertThat(instance.getTraits(), is(Collections.emptyList())); + assertThat(instance.id()).isEqualTo("https://bitbucket.org::cloudbeers"); + assertThat(instance.getRepoOwner()).isEqualTo("cloudbeers"); + assertThat(instance.getServerUrl()).isEqualTo(BitbucketCloudEndpoint.SERVER_URL); + assertThat(instance.getCredentialsId()).isEqualTo("bcaef157-f105-407f-b150-df7722eab6c1"); + assertThat(instance.getTraits()).isEmpty(); } @Test - public void given__instance__when__setTraits_empty__then__traitsEmpty() { + void given__instance__when__setTraits_empty__then__traitsEmpty() { BitbucketSCMNavigator instance = new BitbucketSCMNavigator("test"); instance.setTraits(Collections.emptyList()); - assertThat(instance.getTraits(), is(Collections.emptyList())); + assertThat(instance.getTraits()).isEmpty(); } @Test - public void given__instance__when__setTraits__then__traitsSet() { + void given__instance__when__setTraits__then__traitsSet() { BitbucketSCMNavigator instance = new BitbucketSCMNavigator("test"); instance.setTraits(Arrays.asList(new BranchDiscoveryTrait(1), new WebhookRegistrationTrait(WebhookRegistration.DISABLE))); - assertThat(instance.getTraits(), - containsInAnyOrder( - allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - allOf( - instanceOf(WebhookRegistrationTrait.class), - hasProperty("mode", is(WebhookRegistration.DISABLE)) - ) - ) - ); + + assertThat(instance.getTraits()) + .anySatisfy(el -> { + assertThat(el).isInstanceOf(BranchDiscoveryTrait.class) + .asInstanceOf(InstanceOfAssertFactories.type(BranchDiscoveryTrait.class)) + .satisfies(trait -> { + assertThat(trait.isBuildBranch()).isTrue(); + assertThat(trait.isBuildBranchesWithPR()).isFalse(); + }); + }) + .anySatisfy(el -> { + assertThat(el).isInstanceOf(WebhookRegistrationTrait.class) + .asInstanceOf(InstanceOfAssertFactories.type(WebhookRegistrationTrait.class)) + .satisfies(trait -> { + assertThat(trait.getMode()).isEqualTo(WebhookRegistration.DISABLE); + }); + }); } @Test - public void given__instance__when__setServerUrl__then__urlNormalized() { + void given__instance__when__setServerUrl__then__urlNormalized() { BitbucketSCMNavigator instance = new BitbucketSCMNavigator("test"); instance.setServerUrl("https://bitbucket.org:443/foo/../bar/../"); - assertThat(instance.getServerUrl(), is("https://bitbucket.org")); + assertThat(instance.getServerUrl()).isEqualTo("https://bitbucket.org"); } @Test - public void given__instance__when__setCredentials_empty__then__credentials_null() { + void given__instance__when__setCredentials_empty__then__credentials_null() { BitbucketSCMNavigator instance = new BitbucketSCMNavigator("test"); instance.setCredentialsId(""); - assertThat(instance.getCredentialsId(), is(nullValue())); + assertThat(instance.getCredentialsId()).isNull(); } @Test - public void given__instance__when__setCredentials_null__then__credentials_null() { + void given__instance__when__setCredentials_null__then__credentials_null() { BitbucketSCMNavigator instance = new BitbucketSCMNavigator("test"); instance.setCredentialsId(""); - assertThat(instance.getCredentialsId(), is(nullValue())); + assertThat(instance.getCredentialsId()).isNull(); } @Test - public void given__instance__when__setCredentials__then__credentials_set() { + void given__instance__when__setCredentials__then__credentials_set() { BitbucketSCMNavigator instance = new BitbucketSCMNavigator("test"); instance.setCredentialsId("test"); - assertThat(instance.getCredentialsId(), is("test")); + assertThat(instance.getCredentialsId()).isEqualTo("test"); } } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSourceTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSourceTest.java index 9ccb442ee..41c2c74c9 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSourceTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSourceTest.java @@ -24,9 +24,9 @@ package com.cloudbees.jenkins.plugins.bitbucket; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketIntegrationClientFactory; import com.cloudbees.jenkins.plugins.bitbucket.client.pullrequest.BitbucketCloudPullRequestCommit; -import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.cloudbees.jenkins.plugins.bitbucket.impl.avatars.BitbucketRepoAvatarMetadataAction; @@ -104,7 +104,7 @@ private void loadBEC(String dataSet) { String path = getClass().getSimpleName() + "/" + BitbucketEndpointConfiguration.class.getSimpleName() + "/" + dataSet + ".xml"; URL url = getClass().getResource(path); BitbucketEndpointConfiguration bec = (BitbucketEndpointConfiguration) Jenkins.XSTREAM2.fromXML(url); - for (AbstractBitbucketEndpoint abe : bec.getEndpoints()) { + for (BitbucketEndpoint abe : bec.getEndpoints()) { if (abe != null) { BitbucketEndpointConfiguration.get().updateEndpoint(abe); } 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 dd3a300dc..263bb9d11 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 @@ -23,6 +23,7 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.endpoints; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider; import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerVersion; import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation; import com.cloudbees.plugins.credentials.Credentials; @@ -45,7 +46,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Optional; import jenkins.model.Jenkins; import org.apache.commons.io.FileUtils; import org.assertj.core.api.InstanceOfAssertFactories; @@ -171,7 +171,7 @@ void given__newInstance__when__configuredWithServer__then__serverPresent() { assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketServerEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { assertThat(endpoint.getDisplayName()).isEqualTo("Example Inc"); - assertThat(endpoint.getServerUrl()).isEqualTo("https://bitbucket.example.com"); + assertThat(endpoint.getServerURL()).isEqualTo("https://bitbucket.example.com"); assertThat(endpoint.isManageHooks()).isTrue(); assertThat(endpoint.getCredentialsId()).isEqualTo("dummy"); }); @@ -188,13 +188,13 @@ void given__newInstance__when__configuredWithTwoServers__then__serversPresent() assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketServerEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { assertThat(endpoint.getDisplayName()).isEqualTo("Example Inc"); - assertThat(endpoint.getServerUrl()).isEqualTo("https://bitbucket.example.com"); + assertThat(endpoint.getServerURL()).isEqualTo("https://bitbucket.example.com"); assertThat(endpoint.isManageHooks()).isTrue(); assertThat(endpoint.getCredentialsId()).isEqualTo("dummy"); }); assertThat(instance.getEndpoints()).element(1).satisfies(endpoint -> { assertThat(endpoint.getDisplayName()).isEqualTo("Example Org"); - assertThat(endpoint.getServerUrl()).isEqualTo("http://example.org:8080/bitbucket"); + assertThat(endpoint.getServerURL()).isEqualTo("http://example.org:8080/bitbucket"); assertThat(endpoint.isManageHooks()).isFalse(); assertThat(endpoint.getCredentialsId()).isNull(); }); @@ -295,10 +295,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"))); + instance.setEndpoints(List.of(new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "dummy", false, null))); assumeTrue("dummy".equals(instance.getEndpoints().get(0).getCredentialsId())); - instance.updateEndpoint(new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "added")); + instance.updateEndpoint(new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "added", false, null)); assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketServerEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { @@ -319,7 +319,7 @@ void given__instanceWithServer__when__updatingSameServer__then__serverUpdated() assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketServerEndpoint.class); assertThat(instance.getEndpoints()).element(0).satisfies(endpoint -> { assertThat(endpoint.getDisplayName()).isEqualTo("Example, Inc."); - assertThat(endpoint.getServerUrl()).isEqualTo("https://bitbucket.example.com"); + assertThat(endpoint.getServerURL()).isEqualTo("https://bitbucket.example.com"); assertThat(endpoint.isManageHooks()).isFalse(); assertThat(endpoint.getCredentialsId()).isNull(); }); @@ -328,8 +328,13 @@ void given__instanceWithServer__when__updatingSameServer__then__serverUpdated() @Test void given__newInstance__when__removingCloud__then__defaultRestored() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - assertThat(instance.getEndpoints().get(0).getCredentialsId()).isNull(); - assertThat(instance.removeEndpoint(buildEndpoint(true, "dummy"))).isTrue(); + // verify there is always a default endpoint + assertThat(instance.getEndpoints()).isNotEmpty() + .element(0) + .satisfies(endpoint -> assertThat(endpoint.getCredentialsId()).isNull()); + // remove default does not really remove it + assertThat(instance.removeEndpoint(buildEndpoint(true, "dummy"))).isFalse(); + // default always exists assertThat(instance.getEndpoints()).hasOnlyElementsOfType(BitbucketCloudEndpoint.class); } @@ -415,33 +420,28 @@ void given__instance__when__multipleEndpoints__then__endpointsSelectable() { @Test void given__instanceWithCloudAndServers__when__findingExistingEndpoint__then__endpointFound() { - BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints( + BitbucketEndpointConfiguration.get().setEndpoints( Arrays.asList( buildEndpoint(true, "first"), new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "second"), new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "third") )); - Optional ep = instance.findEndpoint(BitbucketCloudEndpoint.SERVER_URL); - assertThat(ep).isPresent() + assertThat(BitbucketEndpointProvider.lookupEndpoint(BitbucketCloudEndpoint.SERVER_URL)).isPresent() .hasValueSatisfying(endpoint -> { assertThat(endpoint.getCredentialsId()).isEqualTo("first"); }); - ep = instance.findEndpoint("https://bitbucket.example.com/"); - assertThat(ep).isPresent() + assertThat(BitbucketEndpointProvider.lookupEndpoint("https://bitbucket.example.com/")).isPresent() .hasValueSatisfying(endpoint -> { assertThat(endpoint.getCredentialsId()).isEqualTo("second"); }); - ep = instance.findEndpoint("https://BITBUCKET.EXAMPLE.COM:443/"); - assertThat(ep).isPresent() + assertThat(BitbucketEndpointProvider.lookupEndpoint("https://BITBUCKET.EXAMPLE.COM:443/")).isPresent() .hasValueSatisfying(endpoint -> { assertThat(endpoint.getCredentialsId()).isEqualTo("second"); }); - ep = instance.findEndpoint("http://example.org:8080/bitbucket/../bitbucket/"); - assertThat(ep).isPresent() + assertThat(BitbucketEndpointProvider.lookupEndpoint("http://example.org:8080/bitbucket/../bitbucket/")).isPresent() .hasValueSatisfying(endpoint -> { assertThat(endpoint.getCredentialsId()).isEqualTo("third"); }); @@ -449,34 +449,32 @@ void given__instanceWithCloudAndServers__when__findingExistingEndpoint__then__en @Test void given__instanceWithServers__when__findingNonExistingEndpoint__then__endpointNotFound() { - BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints( + BitbucketEndpointConfiguration.get().setEndpoints( Arrays.asList( new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "dummy"), new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "dummy") )); - assertThat(instance.findEndpoint(BitbucketCloudEndpoint.SERVER_URL)).isEmpty(); - assertThat(instance.findEndpoint("http://bitbucket.example.com/")).isEmpty(); - assertThat(instance.findEndpoint("http://bitbucket.example.com:80/")).isEmpty(); - assertThat(instance.findEndpoint("http://bitbucket.example.com:443")).isEmpty(); - assertThat(instance.findEndpoint("https://BITBUCKET.EXAMPLE.COM:443/bitbucket/")).isEmpty(); - assertThat(instance.findEndpoint("http://example.org/bitbucket/../bitbucket/")).isEmpty(); - assertThat(instance.findEndpoint("bitbucket.org")).isEmpty(); - assertThat(instance.findEndpoint("bitbucket.example.com")).isEmpty(); + assertThat(BitbucketEndpointProvider.lookupEndpoint(BitbucketCloudEndpoint.SERVER_URL)).isEmpty(); + assertThat(BitbucketEndpointProvider.lookupEndpoint("http://bitbucket.example.com/")).isEmpty(); + assertThat(BitbucketEndpointProvider.lookupEndpoint("http://bitbucket.example.com:80/")).isEmpty(); + assertThat(BitbucketEndpointProvider.lookupEndpoint("http://bitbucket.example.com:443")).isEmpty(); + assertThat(BitbucketEndpointProvider.lookupEndpoint("https://BITBUCKET.EXAMPLE.COM:443/bitbucket/")).isEmpty(); + assertThat(BitbucketEndpointProvider.lookupEndpoint("http://example.org/bitbucket/../bitbucket/")).isEmpty(); + assertThat(BitbucketEndpointProvider.lookupEndpoint("bitbucket.org")).isEmpty(); + assertThat(BitbucketEndpointProvider.lookupEndpoint("bitbucket.example.com")).isEmpty(); } @Test void given__instanceWithCloudAndServers__when__findingInvalid__then__endpointNotFound() { - BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints( + BitbucketEndpointConfiguration.get().setEndpoints( Arrays.asList( buildEndpoint(true, "first"), new BitbucketServerEndpoint("Example Inc", "https://bitbucket.example.com/", true, "second"), new BitbucketServerEndpoint("Example Org", "http://example.org:8080/bitbucket/", true, "third") )); - assertThat(instance.findEndpoint("0schemes-start-with+digits:no leading slash")).isEmpty(); - assertThat(instance.findEndpoint("http://host name with spaces:443")).isEmpty(); - assertThat(instance.findEndpoint("http://invalid.port.test:65536/bitbucket/")).isEmpty(); + assertThat(BitbucketEndpointProvider.lookupEndpoint("0schemes-start-with+digits:no leading slash")).isEmpty(); + assertThat(BitbucketEndpointProvider.lookupEndpoint("http://host name with spaces:443")).isEmpty(); + assertThat(BitbucketEndpointProvider.lookupEndpoint("http://invalid.port.test:65536/bitbucket/")).isEmpty(); } @Test @@ -530,7 +528,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))); + instance.setEndpoints(List.of(new BitbucketServerEndpoint("existing", "https://bitbucket.test", false, null, false, null))); assertThat(instance.getEndpointItems()).hasSize(1); assertThat(instance.readResolveServerUrl(null)).isEqualTo(BitbucketCloudEndpoint.SERVER_URL); assertThat(instance.getEndpointItems()).hasSize(2); @@ -547,19 +545,19 @@ void given__instanceWithCloudAndServers__when__resolvingNewEndpointAsSystem__the assertThat(instance.readResolveServerUrl("http://example.org:8080/foo/../bitbucket/.")).isEqualTo("http://example.org:8080/bitbucket"); assertThat(instance.getEndpointItems()).hasSize(4); assertThat(instance.getEndpoints().get(0).getDisplayName()).isEqualTo("existing"); - assertThat(instance.getEndpoints().get(0).getServerUrl()).isEqualTo("https://bitbucket.test"); + assertThat(instance.getEndpoints().get(0).getServerURL()).isEqualTo("https://bitbucket.test"); assertThat(instance.getEndpoints().get(0).isManageHooks()).isFalse(); assertThat(instance.getEndpoints().get(0).getCredentialsId()).isNull(); assertThat(instance.getEndpoints().get(1).getDisplayName()).isEqualTo(Messages.BitbucketCloudEndpoint_displayName()); - assertThat(instance.getEndpoints().get(1).getServerUrl()).isEqualTo("https://bitbucket.org"); + assertThat(instance.getEndpoints().get(1).getServerURL()).isEqualTo("https://bitbucket.org"); assertThat(instance.getEndpoints().get(1).isManageHooks()).isFalse(); assertThat(instance.getEndpoints().get(1).getCredentialsId()).isNull(); assertThat(instance.getEndpoints().get(2).getDisplayName()).isEqualTo("example"); - assertThat(instance.getEndpoints().get(2).getServerUrl()).isEqualTo("https://bitbucket.example.com"); + assertThat(instance.getEndpoints().get(2).getServerURL()).isEqualTo("https://bitbucket.example.com"); assertThat(instance.getEndpoints().get(2).isManageHooks()).isFalse(); assertThat(instance.getEndpoints().get(2).getCredentialsId()).isNull(); assertThat(instance.getEndpoints().get(3).getDisplayName()).isEqualTo("example"); - assertThat(instance.getEndpoints().get(3).getServerUrl()).isEqualTo("http://example.org:8080/bitbucket"); + assertThat(instance.getEndpoints().get(3).getServerURL()).isEqualTo("http://example.org:8080/bitbucket"); assertThat(instance.getEndpoints().get(3).isManageHooks()).isFalse(); assertThat(instance.getEndpoints().get(3).getCredentialsId()).isNull(); } finally { @@ -570,9 +568,8 @@ void given__instanceWithCloudAndServers__when__resolvingNewEndpointAsSystem__the @Test void given__instanceWithCloudAndServers__when__resolvingNewEndpointAsAnon__then__normalizedReturnedNotAdded() { BitbucketEndpointConfiguration instance = new BitbucketEndpointConfiguration(); - instance.setEndpoints(Collections.singletonList( - new BitbucketServerEndpoint("existing", "https://bitbucket.test", false, null))); - try (ACLContext context = ACL.as(Jenkins.ANONYMOUS)) { + instance.setEndpoints(List.of(new BitbucketServerEndpoint("existing", "https://bitbucket.test", false, null, false, null))); + try (ACLContext context = ACL.as2(Jenkins.ANONYMOUS2)) { assertThat(instance.getEndpointItems()).hasSize(1); assertThat(instance.readResolveServerUrl(null)).isEqualTo(BitbucketCloudEndpoint.SERVER_URL); assertThat(instance.getEndpointItems()).hasSize(1); @@ -589,7 +586,7 @@ void given__instanceWithCloudAndServers__when__resolvingNewEndpointAsAnon__then_ assertThat(instance.readResolveServerUrl("http://example.org:8080/foo/../bitbucket/.")).isEqualTo("http://example.org:8080/bitbucket"); assertThat(instance.getEndpointItems()).hasSize(1); assertThat(instance.getEndpoints().get(0).getDisplayName()).isEqualTo("existing"); - assertThat(instance.getEndpoints().get(0).getServerUrl()).isEqualTo("https://bitbucket.test"); + assertThat(instance.getEndpoints().get(0).getServerURL()).isEqualTo("https://bitbucket.test"); assertThat(instance.getEndpoints().get(0).isManageHooks()).isFalse(); assertThat(instance.getEndpoints().get(0).getCredentialsId()).isNull(); } @@ -620,19 +617,19 @@ void given__instanceWithConfig__when__configRoundtrip__then__configRetained() th BitbucketCloudEndpoint endpoint1 = (BitbucketCloudEndpoint) instance.getEndpoints().get(0); assertThat(endpoint1.getDisplayName()).isEqualTo(Messages.BitbucketCloudEndpoint_displayName()); - assertThat(endpoint1.getServerUrl()).isEqualTo("https://bitbucket.org"); + assertThat(endpoint1.getServerURL()).isEqualTo("https://bitbucket.org"); assertThat(endpoint1.isManageHooks()).isTrue(); assertThat(endpoint1.getCredentialsId()).isEqualTo("first"); BitbucketServerEndpoint endpoint2 = (BitbucketServerEndpoint) instance.getEndpoints().get(1); assertThat(endpoint2.getDisplayName()).isEqualTo("Example Inc"); - assertThat(endpoint2.getServerUrl()).isEqualTo("https://bitbucket.example.com"); + assertThat(endpoint2.getServerURL()).isEqualTo("https://bitbucket.example.com"); assertThat(endpoint2.isManageHooks()).isTrue(); assertThat(endpoint2.getCredentialsId()).isEqualTo("second"); BitbucketServerEndpoint endpoint3 = (BitbucketServerEndpoint) instance.getEndpoints().get(2); assertThat(endpoint3.getDisplayName()).isEqualTo("Example Org"); - assertThat(endpoint3.getServerUrl()).isEqualTo("http://example.org:8080/bitbucket"); + assertThat(endpoint3.getServerURL()).isEqualTo("http://example.org:8080/bitbucket"); assertThat(endpoint3.isManageHooks()).isFalse(); assertThat(endpoint3.getCredentialsId()).isNull(); } @@ -674,7 +671,7 @@ void load_serverConfig__with_old_signatures() throws Exception { .satisfies(endpoint -> { assertThat(endpoint.isManageHooks()).isTrue(); assertThat(endpoint.getCredentialsId()).isEqualTo("admin.basic.credentials"); - assertThat(endpoint.getBitbucketJenkinsRootUrl()).isEqualTo("http://host.docker.internal:8090/jenkins/"); + 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(); @@ -694,7 +691,7 @@ void should_support_configuration_as_code() throws Exception { assertThat(instance.getEndpoints()).element(0).isInstanceOf(BitbucketCloudEndpoint.class); BitbucketCloudEndpoint endpoint = (BitbucketCloudEndpoint) instance.getEndpoints().get(0); assertThat(endpoint.getDisplayName()).isEqualTo(Messages.BitbucketCloudEndpoint_displayName()); - assertThat(endpoint.getServerUrl()).isEqualTo("https://bitbucket.org"); + assertThat(endpoint.getServerURL()).isEqualTo("https://bitbucket.org"); assertThat(endpoint.isManageHooks()).isTrue(); assertThat(endpoint.getCredentialsId()).isEqualTo("first"); assertThat(endpoint.isEnableCache()).isTrue(); @@ -709,7 +706,7 @@ void should_support_configuration_as_code() throws Exception { assertThat(instance.getEndpoints()).element(1).isInstanceOf(BitbucketServerEndpoint.class); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(1); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); - assertThat(serverEndpoint.getServerUrl()).isEqualTo("https://bitbucket.example.com"); + assertThat(serverEndpoint.getServerURL()).isEqualTo("https://bitbucket.example.com"); assertThat(serverEndpoint.isManageHooks()).isTrue(); assertThat(serverEndpoint.getCredentialsId()).isEqualTo("second"); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); @@ -720,7 +717,7 @@ void should_support_configuration_as_code() throws Exception { assertThat(instance.getEndpoints()).element(2).isInstanceOf(BitbucketServerEndpoint.class); serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(2); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Org"); - assertThat(serverEndpoint.getServerUrl()).isEqualTo("http://example.org:8080/bitbucket"); + assertThat(serverEndpoint.getServerURL()).isEqualTo("http://example.org:8080/bitbucket"); assertThat(serverEndpoint.isManageHooks()).isFalse(); assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isTrue(); @@ -730,7 +727,7 @@ void should_support_configuration_as_code() throws Exception { serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(3); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); - assertThat(serverEndpoint.getServerUrl()).isEqualTo("http://bitbucket.example.com:8083"); + assertThat(serverEndpoint.getServerURL()).isEqualTo("http://bitbucket.example.com:8083"); assertThat(serverEndpoint.isManageHooks()).isTrue(); assertThat(serverEndpoint.getCredentialsId()).isEqualTo("third"); assertThat(serverEndpoint.isCallCanMerge()).isTrue(); @@ -740,7 +737,7 @@ void should_support_configuration_as_code() throws Exception { serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(4); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); - assertThat(serverEndpoint.getServerUrl()).isEqualTo("http://bitbucket.example.com:8084"); + assertThat(serverEndpoint.getServerURL()).isEqualTo("http://bitbucket.example.com:8084"); assertThat(serverEndpoint.isManageHooks()).isTrue(); assertThat(serverEndpoint.getCredentialsId()).isEqualTo("fourth"); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); @@ -750,7 +747,7 @@ void should_support_configuration_as_code() throws Exception { serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(5); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); - assertThat(serverEndpoint.getServerUrl()).isEqualTo("http://bitbucket.example.com:8085"); + assertThat(serverEndpoint.getServerURL()).isEqualTo("http://bitbucket.example.com:8085"); assertThat(serverEndpoint.isManageHooks()).isTrue(); assertThat(serverEndpoint.getCredentialsId()).isEqualTo("fifth"); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); @@ -760,7 +757,7 @@ void should_support_configuration_as_code() throws Exception { serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(6); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); - assertThat(serverEndpoint.getServerUrl()).isEqualTo("http://bitbucket.example.com:8086"); + assertThat(serverEndpoint.getServerURL()).isEqualTo("http://bitbucket.example.com:8086"); assertThat(serverEndpoint.isManageHooks()).isFalse(); assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); @@ -770,7 +767,7 @@ void should_support_configuration_as_code() throws Exception { serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(7); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); - assertThat(serverEndpoint.getServerUrl()).isEqualTo("http://bitbucket.example.com:8087"); + assertThat(serverEndpoint.getServerURL()).isEqualTo("http://bitbucket.example.com:8087"); assertThat(serverEndpoint.isManageHooks()).isFalse(); assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); @@ -780,7 +777,7 @@ void should_support_configuration_as_code() throws Exception { serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(8); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); - assertThat(serverEndpoint.getServerUrl()).isEqualTo("http://bitbucket.example.com:8088"); + assertThat(serverEndpoint.getServerURL()).isEqualTo("http://bitbucket.example.com:8088"); assertThat(serverEndpoint.isManageHooks()).isFalse(); assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); @@ -790,7 +787,7 @@ void should_support_configuration_as_code() throws Exception { serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(9); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); - assertThat(serverEndpoint.getServerUrl()).isEqualTo("http://bitbucket.example.com:8089"); + assertThat(serverEndpoint.getServerURL()).isEqualTo("http://bitbucket.example.com:8089"); assertThat(serverEndpoint.isManageHooks()).isFalse(); assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); @@ -800,7 +797,7 @@ void should_support_configuration_as_code() throws Exception { serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(10); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); - assertThat(serverEndpoint.getServerUrl()).isEqualTo("http://bitbucket.example.com:8090"); + assertThat(serverEndpoint.getServerURL()).isEqualTo("http://bitbucket.example.com:8090"); assertThat(serverEndpoint.isManageHooks()).isFalse(); assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); @@ -810,7 +807,7 @@ void should_support_configuration_as_code() throws Exception { serverEndpoint = (BitbucketServerEndpoint) instance.getEndpoints().get(11); assertThat(serverEndpoint.getDisplayName()).isEqualTo("Example Inc"); - assertThat(serverEndpoint.getServerUrl()).isEqualTo("http://bitbucket.example.com:8091"); + assertThat(serverEndpoint.getServerURL()).isEqualTo("http://bitbucket.example.com:8091"); assertThat(serverEndpoint.isManageHooks()).isFalse(); assertThat(serverEndpoint.getCredentialsId()).isNull(); assertThat(serverEndpoint.isCallCanMerge()).isFalse(); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/DummyEndpointConfiguration.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/DummyEndpointConfiguration.java index daf832908..df5375a7b 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/DummyEndpointConfiguration.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/DummyEndpointConfiguration.java @@ -23,6 +23,7 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.endpoints; +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.EndpointType; import com.damnhandy.uri.template.UriTemplate; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -41,15 +42,26 @@ public String getDisplayName() { @NonNull @Override public String getServerUrl() { + return getServerURL(); + } + + @NonNull + @Override + public String getServerURL() { return "http://dummy.example.com"; } @NonNull @Override - public String getBitbucketJenkinsRootUrl() { + public String getEndpointJenkinsRootURL() { return "http://master.example.com"; } + @Override + public EndpointType getType() { + return EndpointType.SERVER; + } + @NonNull @Override public String getRepositoryUrl(@NonNull String repoOwner, @NonNull String repository) { @@ -64,4 +76,5 @@ public String getRepositoryUrl(@NonNull String repoOwner, @NonNull String reposi public static class DescriptorImpl extends AbstractBitbucketEndpointDescriptor { } + } \ No newline at end of file diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTraitTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTraitTest.java index 01eef878b..121573024 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTraitTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTraitTest.java @@ -24,7 +24,6 @@ package com.cloudbees.jenkins.plugins.bitbucket.trait; import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSourceContext; -import com.cloudbees.jenkins.plugins.bitbucket.trait.BranchDiscoveryTrait; import hudson.util.ListBoxModel; import java.util.Collections; import jenkins.scm.api.SCMHeadObserver;