Skip to content

Commit 28d74e8

Browse files
Support cloning from Bitbucket mirror (#796)
A mirrored Git repository can be configured for fetching references. The mirror is not used in the following cases: - If the source branch in a pull request resides in a different repository, the source branch is fetched from the primary repository while the target branch is fetched from the mirror. - During initial pull request scanning, the mirror isn't used because PullRequestSCMHead doesn't contain the hash needed for the build and SCMRevision is null. This is the current design limitation and can be refactored later. Cloning from the mirror can only be used with native web-hooks since plugin web-hooks don't provide a mirror identifier. For branches and tags, the mirror sync event is used. Thus, at cloning time, the mirror is already synchronized. However, in the case of a pull request event, there is no such guarantee. The plugin optimistically assumes that the mirror is synced and the required commit hashes exist in the mirrored repository at cloning time. If the plugin can't find the required hashes, it falls back to the primary repository. Fixes #592 Co-authors: - Andrey Fomin https://github.com/andrey-fomin - Andrei Kouznetchik https://github.com/akouznetchik - Eugene Mercuriev https://github.com/zamonier Co-authored-by: Günter Grodotzki <gunter@grodotzki.com>
1 parent dba3741 commit 28d74e8

File tree

48 files changed

+2120
-1137
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2120
-1137
lines changed

docs/USER_GUIDE.adoc

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,10 @@ service to listen to these webhook requests and acts accordingly by triggering a
9090
triggering builds on matching branches or pull requests.
9191

9292
Go to "Manage Jenkins" / "Configure System" and locate _Bitbucket Endpoints_. For every endpoint where you want webhooks registered automatically,
93-
check "Manage hooks" and select a "Credential" with enough access to add webhooks to repositories. Since the Credential is used at the system level,
93+
check "Manage hooks" and select a "Credential" with enough access to add webhooks to repositories. Since the Credential is used at the system level,
9494
it can be a System scoped credential, which will restrict its usage from Pipelines.
9595

96-
For both Bitbucket _Multibranch Pipelines_ and _Organization folders_ there is an "Override hook management" behavior
96+
For both Bitbucket _Multibranch Pipelines_ and _Organization folders_ there is an "Override hook management" behavior
9797
to opt out or adjust system-wide settings.
9898

9999
image::images/screenshot-4.png[scaledwidth=90%]
@@ -152,6 +152,23 @@ image::images/screenshot-11.png[scaledwidth=90%]
152152

153153
image::images/screenshot-12.png[scaledwidth=90%]
154154

155+
[id=bitbucket-mirror-support]
156+
== Mirror support
157+
158+
A mirrored Git repository can be configured for fetching references.
159+
160+
The mirror is not used in the following cases:
161+
162+
- If the source branch in a pull request resides in a different repository, the source branch is fetched from the primary repository while the target branch is fetched from the mirror.
163+
164+
- During initial pull request scanning, the mirror isn't used because of the current design limitations.
165+
166+
Cloning from the mirror can only be used with native web-hooks since plugin web-hooks don't provide a mirror identifier.
167+
168+
For branches and tags, the mirror sync event is used. Thus, at cloning time, the mirror is already synchronized. However, in the case of a pull request event, there is no such guarantee. The plugin optimistically assumes that the mirror is synced and the required commit hashes exist in the mirrored repository at cloning time. If the plugin can't find the required hashes, it falls back to the primary repository.
169+
170+
image::images/screenshot-13.png[scaledwidth=90%]
171+
155172
[id=bitbucket-misc-config]
156173
== Miscellaneous configuration
157174

docs/images/screenshot-13.png

233 KB
Loading
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.cloudbees.jenkins.plugins.bitbucket;
2+
3+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi;
4+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApiFactory;
5+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
6+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRequestException;
7+
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint;
8+
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration;
9+
import com.cloudbees.plugins.credentials.CredentialsProvider;
10+
import com.cloudbees.plugins.credentials.common.StandardCredentials;
11+
import hudson.Util;
12+
import hudson.model.Item;
13+
import hudson.util.FormFillFailure;
14+
import hudson.util.ListBoxModel;
15+
import java.io.IOException;
16+
import java.util.logging.Level;
17+
import java.util.logging.Logger;
18+
import jenkins.authentication.tokens.api.AuthenticationTokens;
19+
import jenkins.model.Jenkins;
20+
import jenkins.scm.api.SCMSourceOwner;
21+
import org.apache.commons.lang.StringUtils;
22+
23+
public class BitbucketApiUtils {
24+
25+
private static final Logger LOGGER = Logger.getLogger(BitbucketApiUtils.class.getName());
26+
27+
public static ListBoxModel getFromBitbucket(SCMSourceOwner context,
28+
String serverUrl,
29+
String credentialsId,
30+
String repoOwner,
31+
String repository,
32+
BitbucketSupplier<ListBoxModel> listBoxModelSupplier)
33+
throws FormFillFailure {
34+
repoOwner = Util.fixEmptyAndTrim(repoOwner);
35+
if (repoOwner == null) {
36+
return new ListBoxModel();
37+
}
38+
if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) ||
39+
context != null && !context.hasPermission(Item.EXTENDED_READ)) {
40+
return new ListBoxModel(); // not supposed to be seeing this form
41+
}
42+
if (context != null && !context.hasPermission(CredentialsProvider.USE_ITEM)) {
43+
return new ListBoxModel(); // not permitted to try connecting with these credentials
44+
}
45+
46+
String serverUrlFallback = BitbucketCloudEndpoint.SERVER_URL;
47+
// if at least one bitbucket server is configured use it instead of bitbucket cloud
48+
if(BitbucketEndpointConfiguration.get().getEndpointItems().size() > 0){
49+
serverUrlFallback = BitbucketEndpointConfiguration.get().getEndpointItems().get(0).value;
50+
}
51+
52+
serverUrl = StringUtils.defaultIfBlank(serverUrl, serverUrlFallback);
53+
StandardCredentials credentials = BitbucketCredentials.lookupCredentials(
54+
serverUrl,
55+
context,
56+
credentialsId,
57+
StandardCredentials.class
58+
);
59+
60+
BitbucketAuthenticator authenticator = AuthenticationTokens.convert(BitbucketAuthenticator.authenticationContext(serverUrl), credentials);
61+
62+
try {
63+
BitbucketApi bitbucket = BitbucketApiFactory.newInstance(serverUrl, authenticator, repoOwner, null, repository);
64+
return listBoxModelSupplier.get(bitbucket);
65+
} catch (FormFillFailure | OutOfMemoryError e) {
66+
throw e;
67+
} catch (IOException e) {
68+
if (e instanceof BitbucketRequestException) {
69+
if (((BitbucketRequestException) e).getHttpCode() == 401) {
70+
throw FormFillFailure.error(credentials == null
71+
? Messages.BitbucketSCMSource_UnauthorizedAnonymous(repoOwner)
72+
: Messages.BitbucketSCMSource_UnauthorizedOwner(repoOwner)).withSelectionCleared();
73+
}
74+
} else if (e.getCause() instanceof BitbucketRequestException) {
75+
if (((BitbucketRequestException) e.getCause()).getHttpCode() == 401) {
76+
throw FormFillFailure.error(credentials == null
77+
? Messages.BitbucketSCMSource_UnauthorizedAnonymous(repoOwner)
78+
: Messages.BitbucketSCMSource_UnauthorizedOwner(repoOwner)).withSelectionCleared();
79+
}
80+
}
81+
LOGGER.log(Level.SEVERE, e.getMessage(), e);
82+
throw FormFillFailure.error(e.getMessage());
83+
} catch (Throwable e) {
84+
LOGGER.log(Level.SEVERE, e.getMessage(), e);
85+
throw FormFillFailure.error(e.getMessage());
86+
}
87+
}
88+
89+
public interface BitbucketSupplier<T> {
90+
T get(BitbucketApi bitbucketApi) throws IOException, InterruptedException;
91+
}
92+
93+
}

0 commit comments

Comments
 (0)