Skip to content

Commit

Permalink
Support cloning from Bitbucket mirror (#796)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
andrey-fomin and lifeofguenter committed Feb 10, 2024
1 parent dba3741 commit 28d74e8
Show file tree
Hide file tree
Showing 48 changed files with 2,120 additions and 1,137 deletions.
21 changes: 19 additions & 2 deletions docs/USER_GUIDE.adoc
Expand Up @@ -90,10 +90,10 @@ service to listen to these webhook requests and acts accordingly by triggering a
triggering builds on matching branches or pull requests.

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

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

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

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

[id=bitbucket-mirror-support]
== Mirror support

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 of the current design limitations.

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.

image::images/screenshot-13.png[scaledwidth=90%]

[id=bitbucket-misc-config]
== Miscellaneous configuration

Expand Down
Binary file added docs/images/screenshot-13.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,93 @@
package com.cloudbees.jenkins.plugins.bitbucket;

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.BitbucketRequestException;
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;
import hudson.Util;
import hudson.model.Item;
import hudson.util.FormFillFailure;
import hudson.util.ListBoxModel;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.authentication.tokens.api.AuthenticationTokens;
import jenkins.model.Jenkins;
import jenkins.scm.api.SCMSourceOwner;
import org.apache.commons.lang.StringUtils;

public class BitbucketApiUtils {

private static final Logger LOGGER = Logger.getLogger(BitbucketApiUtils.class.getName());

public static ListBoxModel getFromBitbucket(SCMSourceOwner context,
String serverUrl,
String credentialsId,
String repoOwner,
String repository,
BitbucketSupplier<ListBoxModel> listBoxModelSupplier)
throws FormFillFailure {
repoOwner = Util.fixEmptyAndTrim(repoOwner);
if (repoOwner == null) {
return new ListBoxModel();
}
if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) ||
context != null && !context.hasPermission(Item.EXTENDED_READ)) {
return new ListBoxModel(); // not supposed to be seeing this form
}
if (context != null && !context.hasPermission(CredentialsProvider.USE_ITEM)) {
return new ListBoxModel(); // not permitted to try connecting with these credentials
}

String serverUrlFallback = BitbucketCloudEndpoint.SERVER_URL;
// if at least one bitbucket server is configured use it instead of bitbucket cloud
if(BitbucketEndpointConfiguration.get().getEndpointItems().size() > 0){
serverUrlFallback = BitbucketEndpointConfiguration.get().getEndpointItems().get(0).value;
}

serverUrl = StringUtils.defaultIfBlank(serverUrl, serverUrlFallback);
StandardCredentials credentials = BitbucketCredentials.lookupCredentials(
serverUrl,
context,
credentialsId,
StandardCredentials.class
);

BitbucketAuthenticator authenticator = AuthenticationTokens.convert(BitbucketAuthenticator.authenticationContext(serverUrl), credentials);

try {
BitbucketApi bitbucket = BitbucketApiFactory.newInstance(serverUrl, authenticator, repoOwner, null, repository);
return listBoxModelSupplier.get(bitbucket);
} catch (FormFillFailure | OutOfMemoryError e) {
throw e;
} catch (IOException e) {
if (e instanceof BitbucketRequestException) {
if (((BitbucketRequestException) e).getHttpCode() == 401) {
throw FormFillFailure.error(credentials == null
? Messages.BitbucketSCMSource_UnauthorizedAnonymous(repoOwner)
: Messages.BitbucketSCMSource_UnauthorizedOwner(repoOwner)).withSelectionCleared();
}
} else if (e.getCause() instanceof BitbucketRequestException) {
if (((BitbucketRequestException) e.getCause()).getHttpCode() == 401) {
throw FormFillFailure.error(credentials == null
? Messages.BitbucketSCMSource_UnauthorizedAnonymous(repoOwner)
: Messages.BitbucketSCMSource_UnauthorizedOwner(repoOwner)).withSelectionCleared();
}
}
LOGGER.log(Level.SEVERE, e.getMessage(), e);
throw FormFillFailure.error(e.getMessage());
} catch (Throwable e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
throw FormFillFailure.error(e.getMessage());
}
}

public interface BitbucketSupplier<T> {
T get(BitbucketApi bitbucketApi) throws IOException, InterruptedException;
}

}

0 comments on commit 28d74e8

Please sign in to comment.