Skip to content

Add API classes to manage bitbucket endpoints and hide the internal implementations #1056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
6 changes: 2 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@

<name>Bitbucket Branch Source Plugin</name>
<url>https://github.com/jenkinsci/bitbucket-branch-source-plugin</url>
<description>Discover and build Bitbucket Cloud and Bitbucket Server pull requests and branches and send status
notifications with the build result.
</description>
<description>Discover and build Bitbucket Cloud and Bitbucket Server pull requests and branches and send status notifications with the build result.</description>
<licenses>
<license>
<name>MIT License</name>
Expand Down Expand Up @@ -64,7 +62,7 @@
<connection>scm:git:https://github.com/${gitHubRepo}.git</connection>
<developerConnection>scm:git:git@github.com:${gitHubRepo}.git</developerConnection>
<url>https://github.com/${gitHubRepo}</url>
<tag>936.3.1</tag>
<tag>${scmTag}</tag>
</scm>

<dependencyManagement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -221,13 +222,14 @@

@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);
Expand Down Expand Up @@ -388,15 +390,15 @@
}

public boolean isServerUrlSelectable() {
return BitbucketEndpointConfiguration.get().isEndpointSelectable();
return !BitbucketEndpointProvider.all().isEmpty();
}

public ListBoxModel doFillServerUrlItems(@AncestorInPath SCMSourceOwner context) {
AccessControlled contextToCheck = context == null ? Jenkins.get() : context;
if (!contextToCheck.hasPermission(Item.CONFIGURE)) {
return new ListBoxModel();
}
return BitbucketEndpointConfiguration.get().getEndpointItems();
return BitbucketEndpointProvider.listEndpoints();

Check warning on line 401 in src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 393-401 are not covered by tests
}

@RequirePOST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -287,11 +288,11 @@

@DataBoundSetter
public void setServerUrl(@CheckForNull String serverUrl) {
String url = BitbucketEndpointConfiguration.normalizeServerURL(serverUrl);
if (url == null) {
url = BitbucketEndpointConfiguration.get().getDefaultEndpoint().getServerUrl();
if (serverUrl == null) {

Check warning on line 291 in src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 291 is only partially covered, one branch is missing
this.serverUrl = BitbucketEndpointConfiguration.get().getDefaultEndpoint().getServerURL();

Check warning on line 292 in src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 292 is not covered by tests
} else {
this.serverUrl = Util.fixNull(URLUtils.normalizeURL(serverUrl));
}
this.serverUrl = url;
}

@NonNull
Expand Down Expand Up @@ -397,6 +398,7 @@
private void retrievePullRequests(final BitbucketSCMSourceRequest request) throws IOException, InterruptedException {
final String fullName = repoOwner + "/" + repository;

@SuppressWarnings("serial")
class Skip extends IOException {
}

Expand Down Expand Up @@ -810,10 +812,10 @@

@NonNull
@Override
protected List<Action> retrieveActions(@CheckForNull SCMSourceEvent event,
protected List<Action> 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

Check warning on line 818 in src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: when we have support for trusted events, use the details from event if event was from trusted source
List<Action> result = new ArrayList<>();
try (BitbucketApi client = buildBitbucketClient()) {
gatherPrimaryCloneLinks(client);
Expand Down Expand Up @@ -846,10 +848,10 @@
@NonNull
@Override
protected List<Action> 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

Check warning on line 854 in src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: when we have support for trusted events, use the details from event if event was from trusted source
List<Action> result = new ArrayList<>();
UriTemplate template;
String title = null;
Expand Down Expand Up @@ -1061,35 +1063,35 @@
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();
}

@RequirePOST
public static FormValidation doCheckMirrorId(@QueryParameter String value,
@QueryParameter(fixEmpty = true, value = "serverUrl") String serverURL) {
if (!value.isEmpty()) {
BitbucketServerWebhookImplementation webhookImplementation =
BitbucketServerEndpoint.findWebhookImplementation(serverURL);
if (webhookImplementation == BitbucketServerWebhookImplementation.PLUGIN) {
return FormValidation.error("Mirror can only be used with native webhooks");
}
}
return FormValidation.ok();
}

public boolean isServerUrlSelectable() {
return BitbucketEndpointConfiguration.get().isEndpointSelectable();
return !BitbucketEndpointProvider.all().isEmpty();
}

public ListBoxModel doFillServerUrlItems(@AncestorInPath SCMSourceOwner context) {
AccessControlled contextToCheck = context == null ? Jenkins.get() : context;
if (!contextToCheck.hasPermission(Item.CONFIGURE)) {
return new ListBoxModel();
}
return BitbucketEndpointConfiguration.get().getEndpointItems();
return BitbucketEndpointProvider.listEndpoints();

Check warning on line 1094 in src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 1066-1094 are not covered by tests
}

public ListBoxModel doFillCredentialsIdItems(@AncestorInPath SCMSourceOwner context, @QueryParameter String serverUrl) {
Expand Down Expand Up @@ -1138,6 +1140,7 @@
};
}

@SuppressWarnings("unchecked")
public List<NamedArrayList<? extends SCMSourceTraitDescriptor>> getTraitsDescriptorLists() {
List<SCMSourceTraitDescriptor> all = new ArrayList<>();
// all that are applicable to our context
Expand All @@ -1158,7 +1161,7 @@
List<NamedArrayList<? extends SCMSourceTraitDescriptor>> 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<BitbucketEndpoint> {

/**
* 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);

Check warning on line 146 in src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/endpoint/BitbucketEndpoint.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 127-146 are not covered by tests
}
}

/**
* 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();
}
Loading
Loading