Skip to content

Commit 31a1789

Browse files
committed
Add API classes to manage bitbucket endpoints and hide the internal implementations
1 parent 44b1a90 commit 31a1789

23 files changed

+496
-273
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

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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()
115+
AbstractBitbucketEndpoint endpoint = (AbstractBitbucketEndpoint) BitbucketEndpointConfiguration.get()
116116
.findEndpoint(serverURL)
117-
.orElse(new BitbucketServerEndpoint(null, serverURL, false, null));
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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,14 @@ public String getServerUrl() {
221221

222222
@DataBoundSetter
223223
public void setServerUrl(@CheckForNull String serverUrl) {
224-
serverUrl = BitbucketEndpointConfiguration.normalizeServerURL(serverUrl);
224+
serverUrl = Util.fixEmpty(serverUrl);
225225
if (serverUrl != null && !StringUtils.equals(this.serverUrl, serverUrl)) {
226226
this.serverUrl = serverUrl;
227227
resetId();
228228
}
229229
}
230230

231+
@Deprecated(since = "936.4.0", forRemoval = true)
231232
@NonNull
232233
public String getEndpointJenkinsRootUrl() {
233234
return AbstractBitbucketEndpoint.getEndpointJenkinsRootUrl(serverUrl);

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,11 @@ public String getServerUrl() {
287287

288288
@DataBoundSetter
289289
public void setServerUrl(@CheckForNull String serverUrl) {
290-
String url = BitbucketEndpointConfiguration.normalizeServerURL(serverUrl);
291-
if (url == null) {
292-
url = BitbucketEndpointConfiguration.get().getDefaultEndpoint().getServerUrl();
290+
if (serverUrl == null) {
291+
this.serverUrl = BitbucketEndpointConfiguration.get().getDefaultEndpoint().getServerURL();
292+
} else {
293+
this.serverUrl = serverUrl;
293294
}
294-
this.serverUrl = url;
295295
}
296296

297297
@NonNull
@@ -397,6 +397,7 @@ private Iterable<BitbucketPullRequest> getBitbucketPullRequestsFromEvent(@NonNul
397397
private void retrievePullRequests(final BitbucketSCMSourceRequest request) throws IOException, InterruptedException {
398398
final String fullName = repoOwner + "/" + repository;
399399

400+
@SuppressWarnings("serial")
400401
class Skip extends IOException {
401402
}
402403

@@ -810,7 +811,7 @@ public DescriptorImpl getDescriptor() {
810811

811812
@NonNull
812813
@Override
813-
protected List<Action> retrieveActions(@CheckForNull SCMSourceEvent event,
814+
protected List<Action> retrieveActions(@SuppressWarnings("rawtypes") @CheckForNull SCMSourceEvent event,
814815
@NonNull TaskListener listener)
815816
throws IOException, InterruptedException {
816817
// TODO when we have support for trusted events, use the details from event if event was from trusted source
@@ -846,7 +847,7 @@ private boolean showAvatar() {
846847
@NonNull
847848
@Override
848849
protected List<Action> retrieveActions(@NonNull SCMHead head,
849-
@CheckForNull SCMHeadEvent event,
850+
@SuppressWarnings("rawtypes") @CheckForNull SCMHeadEvent event,
850851
@NonNull TaskListener listener)
851852
throws IOException, InterruptedException {
852853
// TODO when we have support for trusted events, use the details from event if event was from trusted source
@@ -1138,6 +1139,7 @@ protected SCMHeadCategory[] createCategories() {
11381139
};
11391140
}
11401141

1142+
@SuppressWarnings("unchecked")
11411143
public List<NamedArrayList<? extends SCMSourceTraitDescriptor>> getTraitsDescriptorLists() {
11421144
List<SCMSourceTraitDescriptor> all = new ArrayList<>();
11431145
// all that are applicable to our context
@@ -1158,7 +1160,7 @@ public List<NamedArrayList<? extends SCMSourceTraitDescriptor>> getTraitsDescrip
11581160
List<NamedArrayList<? extends SCMSourceTraitDescriptor>> result = new ArrayList<>();
11591161
NamedArrayList.select(all, "Within repository", NamedArrayList
11601162
.anyOf(NamedArrayList.withAnnotation(Discovery.class),
1161-
NamedArrayList.withAnnotation(Selection.class)),
1163+
NamedArrayList.withAnnotation(Selection.class)),
11621164
true, result);
11631165
int insertionPoint = result.size();
11641166
NamedArrayList.select(all, "Git", it -> GitSCM.class.isAssignableFrom(it.getScmClass()), true, result);
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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 URL of this endpoint.
56+
*
57+
* @return the URL of the endpoint.
58+
*/
59+
@NonNull
60+
String getServerURL();
61+
62+
/**
63+
* Returns {@code true} if and only if Jenkins is supposed to auto-manage
64+
* hooks for this end-point.
65+
*
66+
* @return {@code true} if and only if Jenkins is supposed to auto-manage
67+
* hooks for this end-point.
68+
*/
69+
boolean isManageHooks();
70+
71+
/**
72+
* Returns the {@link StandardUsernamePasswordCredentials#getId()} of the
73+
* credentials to use for auto-management of hooks.
74+
*
75+
* @return the {@link StandardUsernamePasswordCredentials#getId()} of the
76+
* credentials to use for auto-management of hooks.
77+
*/
78+
@CheckForNull
79+
String getCredentialsId();
80+
81+
/**
82+
* Jenkins Server Root URL to be used by this Bitbucket endpoint. The global
83+
* setting from Jenkins.get().getRootUrl() will be used if this field is
84+
* null or equals an empty string.
85+
*
86+
* @return the verbatim setting provided by endpoint configuration
87+
*/
88+
@CheckForNull
89+
String getEndpointJenkinsRootURL();
90+
91+
boolean isEnableHookSignature();
92+
93+
/**
94+
* The {@link StringCredentials#getId()} of the credentials to use to verify
95+
* the signature of hooks.
96+
*
97+
* @return the configured credentials identifier to use
98+
*/
99+
@CheckForNull
100+
String getHookSignatureCredentialsId();
101+
102+
/**
103+
* Looks up the {@link StandardCredentials} to use for auto-management of hooks.
104+
*
105+
* @return the credentials or {@code null}.
106+
*/
107+
@CheckForNull
108+
default StandardCredentials credentials() {
109+
String credentialsId = Util.fixEmptyAndTrim(getCredentialsId());
110+
if (credentialsId == null) {
111+
return null;
112+
} else {
113+
return BitbucketCredentials.lookupCredentials(getServerURL(), Jenkins.get(), credentialsId, StandardCredentials.class);
114+
}
115+
}
116+
117+
/**
118+
* Looks up the {@link StringCredentials} to use to verify the signature of hooks.
119+
*
120+
* @return the credentials or {@code null}.
121+
*/
122+
@CheckForNull
123+
default StringCredentials hookSignatureCredentials() {
124+
String credentialsId = Util.fixEmptyAndTrim(getHookSignatureCredentialsId());
125+
if (credentialsId == null) {
126+
return null;
127+
} else {
128+
return BitbucketCredentials.lookupCredentials(getServerURL(), Jenkins.get(), credentialsId, StringCredentials.class);
129+
}
130+
}
131+
132+
/**
133+
* Returns if two endpoint are the equals.
134+
*
135+
* @param endpoint to compare
136+
* @return {@code true} if endpoint are the same, {@code false} otherwise
137+
*/
138+
default boolean isEquals(BitbucketEndpoint endpoint) {
139+
return StringUtils.equalsIgnoreCase(getServerURL(), endpoint.getServerURL());
140+
}
141+
142+
/**
143+
*
144+
* @see Describable#getDescriptor()
145+
*/
146+
@Override
147+
BitbucketEndpointDescriptor getDescriptor();
148+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2017, CloudBees, Inc.
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.impl.util.BitbucketCredentials;
27+
import com.cloudbees.plugins.credentials.CredentialsMatchers;
28+
import com.cloudbees.plugins.credentials.common.StandardCredentials;
29+
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
30+
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
31+
import hudson.Util;
32+
import hudson.model.Descriptor;
33+
import hudson.security.ACL;
34+
import hudson.util.FormValidation;
35+
import hudson.util.ListBoxModel;
36+
import java.net.MalformedURLException;
37+
import java.net.URL;
38+
import jenkins.model.Jenkins;
39+
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
40+
import org.kohsuke.accmod.Restricted;
41+
import org.kohsuke.accmod.restrictions.NoExternalUse;
42+
import org.kohsuke.stapler.QueryParameter;
43+
import org.kohsuke.stapler.interceptor.RequirePOST;
44+
45+
/**
46+
* {@link Descriptor} for {@link BitbucketEndpoint}s.
47+
*
48+
* @since 936.4.0
49+
*/
50+
public class BitbucketEndpointDescriptor extends Descriptor<BitbucketEndpoint> {
51+
/**
52+
* Stapler form completion.
53+
*
54+
* @param credentialsId selected credentials.
55+
* @param serverURL the server URL.
56+
* @return the available credentials.
57+
*/
58+
@RequirePOST
59+
public ListBoxModel doFillCredentialsIdItems(@QueryParameter(fixEmpty = true) String credentialsId,
60+
@QueryParameter(value = "serverUrl", fixEmpty = true) String serverURL) {
61+
Jenkins jenkins = Jenkins.get();
62+
jenkins.checkPermission(Jenkins.MANAGE);
63+
return BitbucketCredentials.fillCredentialsIdItems(serverURL, jenkins, StandardCredentials.class, credentialsId);
64+
}
65+
66+
/**
67+
* Stapler form completion.
68+
*
69+
* @param hookSignatureCredentialsId selected hook signature credentials.
70+
* @param serverURL the server URL.
71+
* @return the available credentials.
72+
*/
73+
@RequirePOST
74+
public ListBoxModel doFillHookSignatureCredentialsIdItems(@QueryParameter(fixEmpty = true) String hookSignatureCredentialsId,
75+
@QueryParameter(value = "serverUrl", fixEmpty = true) String serverURL) {
76+
Jenkins jenkins = Jenkins.get();
77+
jenkins.checkPermission(Jenkins.MANAGE);
78+
StandardListBoxModel result = new StandardListBoxModel();
79+
result.includeMatchingAs(ACL.SYSTEM2,
80+
jenkins,
81+
StringCredentials.class,
82+
URIRequirementBuilder.fromUri(serverURL).build(),
83+
CredentialsMatchers.always());
84+
if (hookSignatureCredentialsId != null) {
85+
result.includeCurrentValue(hookSignatureCredentialsId);
86+
}
87+
return result;
88+
}
89+
90+
@Restricted(NoExternalUse.class)
91+
@RequirePOST
92+
public static FormValidation doCheckBitbucketJenkinsRootUrl(@QueryParameter String value) {
93+
String url = Util.fixEmptyAndTrim(value);
94+
if (url == null) {
95+
return FormValidation.ok();
96+
}
97+
try {
98+
new URL(url);
99+
} catch (MalformedURLException e) {
100+
return FormValidation.error("Invalid URL: " + e.getMessage());
101+
}
102+
return FormValidation.ok();
103+
}
104+
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi;
2727
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApiFactory;
2828
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
29-
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint;
29+
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint;
3030
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint;
3131
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration;
3232
import edu.umd.cs.findbugs.annotations.CheckForNull;
@@ -45,7 +45,7 @@ protected boolean isMatch(@Nullable String serverUrl) {
4545
@Override
4646
protected BitbucketApi create(@Nullable String serverUrl, @Nullable BitbucketAuthenticator authenticator,
4747
@NonNull String owner, @CheckForNull String projectKey, @CheckForNull String repository) {
48-
AbstractBitbucketEndpoint endpoint = BitbucketEndpointConfiguration.get()
48+
BitbucketEndpoint endpoint = BitbucketEndpointConfiguration.get()
4949
.findEndpoint(BitbucketCloudEndpoint.SERVER_URL)
5050
.orElse(null);
5151
boolean enableCache = false;

0 commit comments

Comments
 (0)