Skip to content

Commit 1dcce2b

Browse files
authored
Add API classes to manage bitbucket endpoints and hide the internal implementations (#1056)
1 parent 1db6234 commit 1dcce2b

29 files changed

+914
-451
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ This means that when you have a private repository, or a private fork of a publi
3434

3535
### Developers and DevOps notes
3636

37-
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)`.
37+
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)`.
3838

3939
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.
4040

pom.xml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515

1616
<name>Bitbucket Branch Source Plugin</name>
1717
<url>https://github.com/jenkinsci/bitbucket-branch-source-plugin</url>
18-
<description>Discover and build Bitbucket Cloud and Bitbucket Server pull requests and branches and send status
19-
notifications with the build result.
20-
</description>
18+
<description>Discover and build Bitbucket Cloud and Bitbucket Server pull requests and branches and send status notifications with the build result.</description>
2119
<licenses>
2220
<license>
2321
<name>MIT License</name>
@@ -64,7 +62,7 @@
6462
<connection>scm:git:https://github.com/${gitHubRepo}.git</connection>
6563
<developerConnection>scm:git:git@github.com:${gitHubRepo}.git</developerConnection>
6664
<url>https://github.com/${gitHubRepo}</url>
67-
<tag>936.3.1</tag>
65+
<tag>${scmTag}</tag>
6866
</scm>
6967

7068
<dependencyManagement>

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketGitSCMBuilder.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository;
3030
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepositoryProtocol;
3131
import com.cloudbees.jenkins.plugins.bitbucket.api.PullRequestBranchType;
32+
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider;
3233
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint;
33-
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration;
3434
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint;
3535
import com.cloudbees.jenkins.plugins.bitbucket.impl.extension.FallbackToOtherRepositoryGitSCMExtension;
3636
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils;
@@ -112,12 +112,12 @@ public BitbucketGitSCMBuilder(@NonNull BitbucketSCMSource scmSource, @NonNull SC
112112
this.scmSource = scmSource;
113113

114114
String serverURL = scmSource.getServerUrl();
115-
AbstractBitbucketEndpoint endpoint = BitbucketEndpointConfiguration.get()
116-
.findEndpoint(serverURL)
117-
.orElse(new BitbucketServerEndpoint(null, serverURL, false, null));
115+
AbstractBitbucketEndpoint endpoint = (AbstractBitbucketEndpoint) BitbucketEndpointProvider
116+
.lookupEndpoint(serverURL)
117+
.orElse(new BitbucketServerEndpoint(null, serverURL, false, null, false, null));
118118

119119
String repositoryURL = endpoint.getRepositoryUrl(scmSource.getRepoOwner(), scmSource.getRepository());
120-
if (BitbucketApiUtils.isCloud(endpoint.getServerUrl())) {
120+
if (BitbucketApiUtils.isCloud(endpoint.getServerURL())) {
121121
withBrowser(new BitbucketWeb(repositoryURL));
122122
} else {
123123
withBrowser(new BitbucketServer(repositoryURL));

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@
2929
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCloudWorkspace;
3030
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository;
3131
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketTeam;
32+
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider;
3233
import com.cloudbees.jenkins.plugins.bitbucket.client.repository.UserRoleInRepository;
3334
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint;
3435
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint;
35-
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration;
3636
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint;
3737
import com.cloudbees.jenkins.plugins.bitbucket.impl.avatars.BitbucketTeamAvatarMetadataAction;
3838
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils;
3939
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentials;
4040
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.MirrorListSupplier;
41+
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.URLUtils;
4142
import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation;
4243
import com.cloudbees.jenkins.plugins.bitbucket.trait.BranchDiscoveryTrait;
4344
import com.cloudbees.jenkins.plugins.bitbucket.trait.ForkPullRequestDiscoveryTrait;
@@ -221,13 +222,14 @@ public String getServerUrl() {
221222

222223
@DataBoundSetter
223224
public void setServerUrl(@CheckForNull String serverUrl) {
224-
serverUrl = BitbucketEndpointConfiguration.normalizeServerURL(serverUrl);
225+
serverUrl = Util.fixEmpty(URLUtils.normalizeURL(serverUrl));
225226
if (serverUrl != null && !StringUtils.equals(this.serverUrl, serverUrl)) {
226227
this.serverUrl = serverUrl;
227228
resetId();
228229
}
229230
}
230231

232+
@Deprecated(since = "936.4.0", forRemoval = true)
231233
@NonNull
232234
public String getEndpointJenkinsRootUrl() {
233235
return AbstractBitbucketEndpoint.getEndpointJenkinsRootUrl(serverUrl);
@@ -388,15 +390,15 @@ public SCMNavigator newInstance(String name) {
388390
}
389391

390392
public boolean isServerUrlSelectable() {
391-
return BitbucketEndpointConfiguration.get().isEndpointSelectable();
393+
return !BitbucketEndpointProvider.all().isEmpty();
392394
}
393395

394396
public ListBoxModel doFillServerUrlItems(@AncestorInPath SCMSourceOwner context) {
395397
AccessControlled contextToCheck = context == null ? Jenkins.get() : context;
396398
if (!contextToCheck.hasPermission(Item.CONFIGURE)) {
397399
return new ListBoxModel();
398400
}
399-
return BitbucketEndpointConfiguration.get().getEndpointItems();
401+
return BitbucketEndpointProvider.listEndpoints();
400402
}
401403

402404
@RequirePOST

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRequestException;
3939
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketTeam;
4040
import com.cloudbees.jenkins.plugins.bitbucket.api.PullRequestBranchType;
41+
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpointProvider;
4142
import com.cloudbees.jenkins.plugins.bitbucket.client.repository.UserRoleInRepository;
4243
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint;
4344
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint;
@@ -287,11 +288,11 @@ public String getServerUrl() {
287288

288289
@DataBoundSetter
289290
public void setServerUrl(@CheckForNull String serverUrl) {
290-
String url = BitbucketEndpointConfiguration.normalizeServerURL(serverUrl);
291-
if (url == null) {
292-
url = BitbucketEndpointConfiguration.get().getDefaultEndpoint().getServerUrl();
291+
if (serverUrl == null) {
292+
this.serverUrl = BitbucketEndpointConfiguration.get().getDefaultEndpoint().getServerURL();
293+
} else {
294+
this.serverUrl = Util.fixNull(URLUtils.normalizeURL(serverUrl));
293295
}
294-
this.serverUrl = url;
295296
}
296297

297298
@NonNull
@@ -397,6 +398,7 @@ private Iterable<BitbucketPullRequest> getBitbucketPullRequestsFromEvent(@NonNul
397398
private void retrievePullRequests(final BitbucketSCMSourceRequest request) throws IOException, InterruptedException {
398399
final String fullName = repoOwner + "/" + repository;
399400

401+
@SuppressWarnings("serial")
400402
class Skip extends IOException {
401403
}
402404

@@ -810,7 +812,7 @@ public DescriptorImpl getDescriptor() {
810812

811813
@NonNull
812814
@Override
813-
protected List<Action> retrieveActions(@CheckForNull SCMSourceEvent event,
815+
protected List<Action> retrieveActions(@SuppressWarnings("rawtypes") @CheckForNull SCMSourceEvent event,
814816
@NonNull TaskListener listener)
815817
throws IOException, InterruptedException {
816818
// 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() {
846848
@NonNull
847849
@Override
848850
protected List<Action> retrieveActions(@NonNull SCMHead head,
849-
@CheckForNull SCMHeadEvent event,
851+
@SuppressWarnings("rawtypes") @CheckForNull SCMHeadEvent event,
850852
@NonNull TaskListener listener)
851853
throws IOException, InterruptedException {
852854
// 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
10611063
return FormValidation.error(
10621064
"Unauthorized to validate Server URL"); // not supposed to be seeing this form
10631065
}
1064-
if (!BitbucketEndpointConfiguration.get().findEndpoint(value).isPresent()) {
1066+
if (!BitbucketEndpointProvider.lookupEndpoint(value).isPresent()) {
10651067
return FormValidation.error("Unregistered Server: " + value);
10661068
}
10671069
return FormValidation.ok();
@@ -1081,15 +1083,15 @@ public static FormValidation doCheckMirrorId(@QueryParameter String value,
10811083
}
10821084

10831085
public boolean isServerUrlSelectable() {
1084-
return BitbucketEndpointConfiguration.get().isEndpointSelectable();
1086+
return !BitbucketEndpointProvider.all().isEmpty();
10851087
}
10861088

10871089
public ListBoxModel doFillServerUrlItems(@AncestorInPath SCMSourceOwner context) {
10881090
AccessControlled contextToCheck = context == null ? Jenkins.get() : context;
10891091
if (!contextToCheck.hasPermission(Item.CONFIGURE)) {
10901092
return new ListBoxModel();
10911093
}
1092-
return BitbucketEndpointConfiguration.get().getEndpointItems();
1094+
return BitbucketEndpointProvider.listEndpoints();
10931095
}
10941096

10951097
public ListBoxModel doFillCredentialsIdItems(@AncestorInPath SCMSourceOwner context, @QueryParameter String serverUrl) {
@@ -1138,6 +1140,7 @@ protected SCMHeadCategory[] createCategories() {
11381140
};
11391141
}
11401142

1143+
@SuppressWarnings("unchecked")
11411144
public List<NamedArrayList<? extends SCMSourceTraitDescriptor>> getTraitsDescriptorLists() {
11421145
List<SCMSourceTraitDescriptor> all = new ArrayList<>();
11431146
// all that are applicable to our context
@@ -1158,7 +1161,7 @@ public List<NamedArrayList<? extends SCMSourceTraitDescriptor>> getTraitsDescrip
11581161
List<NamedArrayList<? extends SCMSourceTraitDescriptor>> result = new ArrayList<>();
11591162
NamedArrayList.select(all, "Within repository", NamedArrayList
11601163
.anyOf(NamedArrayList.withAnnotation(Discovery.class),
1161-
NamedArrayList.withAnnotation(Selection.class)),
1164+
NamedArrayList.withAnnotation(Selection.class)),
11621165
true, result);
11631166
int insertionPoint = result.size();
11641167
NamedArrayList.select(all, "Git", it -> GitSCM.class.isAssignableFrom(it.getScmClass()), true, result);
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2025, Nikolas Falco
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
package com.cloudbees.jenkins.plugins.bitbucket.api.endpoint;
25+
26+
import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource;
27+
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentials;
28+
import com.cloudbees.plugins.credentials.common.StandardCredentials;
29+
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
30+
import edu.umd.cs.findbugs.annotations.CheckForNull;
31+
import edu.umd.cs.findbugs.annotations.NonNull;
32+
import hudson.Util;
33+
import hudson.model.Describable;
34+
import jenkins.model.Jenkins;
35+
import org.apache.commons.lang3.StringUtils;
36+
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
37+
38+
/**
39+
* The implementation represents an endpoint configuration to be used in
40+
* {@link BitbucketSCMSource}.
41+
*
42+
* @since 936.4.0
43+
*/
44+
public interface BitbucketEndpoint extends Describable<BitbucketEndpoint> {
45+
46+
/**
47+
* Name to use to describe the endpoint.
48+
*
49+
* @return the name to use for the endpoint
50+
*/
51+
@CheckForNull
52+
String getDisplayName();
53+
54+
/**
55+
* The user facing URL of the specified repository.
56+
*
57+
* @param repoOwner the repository owner.
58+
* @param repoSlug the repository name
59+
* @return the user facing URL of the specified repository.
60+
*/
61+
@NonNull
62+
String getRepositoryURL(@NonNull String repoOwner, @NonNull String repoSlug);
63+
64+
/**
65+
* Returns the type of this endpoint.
66+
*
67+
* @return endpoint type.
68+
*/
69+
@NonNull
70+
EndpointType getType();
71+
72+
/**
73+
* The URL of this endpoint.
74+
*
75+
* @return the URL of the endpoint.
76+
*/
77+
@NonNull
78+
String getServerURL();
79+
80+
/**
81+
* Returns {@code true} if and only if Jenkins is supposed to auto-manage
82+
* hooks for this end-point.
83+
*
84+
* @return {@code true} if and only if Jenkins is supposed to auto-manage
85+
* hooks for this end-point.
86+
*/
87+
boolean isManageHooks();
88+
89+
/**
90+
* Returns the {@link StandardUsernamePasswordCredentials#getId()} of the
91+
* credentials to use for auto-management of hooks.
92+
*
93+
* @return the {@link StandardUsernamePasswordCredentials#getId()} of the
94+
* credentials to use for auto-management of hooks.
95+
*/
96+
@CheckForNull
97+
String getCredentialsId();
98+
99+
/**
100+
* Jenkins Server Root URL to be used by this Bitbucket endpoint. The global
101+
* setting from Jenkins.get().getRootUrl() will be used if this field is
102+
* null or equals an empty string.
103+
*
104+
* @return the verbatim setting provided by endpoint configuration
105+
*/
106+
@CheckForNull
107+
String getEndpointJenkinsRootURL();
108+
109+
boolean isEnableHookSignature();
110+
111+
/**
112+
* The {@link StringCredentials#getId()} of the credentials to use to verify
113+
* the signature of hooks.
114+
*
115+
* @return the configured credentials identifier to use
116+
*/
117+
@CheckForNull
118+
String getHookSignatureCredentialsId();
119+
120+
/**
121+
* Looks up the {@link StandardCredentials} to use for auto-management of hooks.
122+
*
123+
* @return the credentials or {@code null}.
124+
*/
125+
@CheckForNull
126+
default StandardCredentials credentials() {
127+
String credentialsId = Util.fixEmptyAndTrim(getCredentialsId());
128+
if (credentialsId == null) {
129+
return null;
130+
} else {
131+
return BitbucketCredentials.lookupCredentials(getServerURL(), Jenkins.get(), credentialsId, StandardCredentials.class);
132+
}
133+
}
134+
135+
/**
136+
* Looks up the {@link StringCredentials} to use to verify the signature of hooks.
137+
*
138+
* @return the credentials or {@code null}.
139+
*/
140+
@CheckForNull
141+
default StringCredentials hookSignatureCredentials() {
142+
String credentialsId = Util.fixEmptyAndTrim(getHookSignatureCredentialsId());
143+
if (credentialsId == null) {
144+
return null;
145+
} else {
146+
return BitbucketCredentials.lookupCredentials(getServerURL(), Jenkins.get(), credentialsId, StringCredentials.class);
147+
}
148+
}
149+
150+
/**
151+
* Returns if two endpoint are the equals.
152+
*
153+
* @param endpoint to compare
154+
* @return {@code true} if endpoint are the same, {@code false} otherwise
155+
*/
156+
default boolean isEquals(BitbucketEndpoint endpoint) {
157+
return StringUtils.equalsIgnoreCase(getServerURL(), endpoint.getServerURL());
158+
}
159+
160+
/**
161+
*
162+
* @see Describable#getDescriptor()
163+
*/
164+
@Override
165+
BitbucketEndpointDescriptor getDescriptor();
166+
}

0 commit comments

Comments
 (0)