Skip to content

Commit

Permalink
Replace the avatar cache beta implementation with default one provide…
Browse files Browse the repository at this point in the history
…d by the scm-api plugin.

There are two action that store the AvatarImage, BitbucketTeamAvatarMetadataAction and BitbucketRepoAvatarMetadataAction.
The BitbucketTeamAvatarMetadataAction holds the metadata to retrieve the team/project avatar of an organization folder.
The BitbucketRepoAvatarMetadataAction holds the metadata to retrieve the repository avatar of single multi branch repository.
The SVG images generated by bitbucket are not supported by ImageIO, those images are replaced with Jenkins random images.
  • Loading branch information
nfalco79 committed Feb 7, 2025
1 parent d5bdec0 commit 8a6d076
Show file tree
Hide file tree
Showing 31 changed files with 708 additions and 1,450 deletions.
4 changes: 3 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
<gitHubRepo>jenkinsci/bitbucket-branch-source-plugin</gitHubRepo>
<jenkins.baseline>2.479</jenkins.baseline>
<jenkins.version>${jenkins.baseline}.1</jenkins.version>
<hpi.compatibleSinceVersion>2.0</hpi.compatibleSinceVersion>
<hpi.compatibleSinceVersion>934.0</hpi.compatibleSinceVersion>
<!-- Jenkins.MANAGE is still in Beta -->
<useBeta>true</useBeta>
<tagNameFormat>@{project.version}</tagNameFormat>
<!-- because eclipse generate a JUnit5 run configuration with placeholder not replaced in the jvm argument section! -->
<surefire.forkNumber>1</surefire.forkNumber>
</properties>

<developers>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,18 @@
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.BitbucketCloudWorkspace;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketTeam;
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.BitbucketCredentials;
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.MirrorListSupplier;
import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation;
import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerProject;
import com.cloudbees.plugins.credentials.CredentialsNameProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import edu.umd.cs.findbugs.annotations.CheckForNull;
Expand Down Expand Up @@ -323,7 +324,7 @@ public void setPattern(String pattern) {
@RestrictedSince("2.2.0")
@DataBoundSetter
public void setAutoRegisterHooks(boolean autoRegisterHook) {
traits.removeIf(trait -> trait instanceof WebhookRegistrationTrait);
traits.removeIf(WebhookRegistrationTrait.class::isInstance);
traits.add(new WebhookRegistrationTrait(
autoRegisterHook ? WebhookRegistration.ITEM : WebhookRegistration.DISABLE
));
Expand All @@ -334,8 +335,8 @@ public void setAutoRegisterHooks(boolean autoRegisterHook) {
@RestrictedSince("2.2.0")
public boolean isAutoRegisterHooks() {
for (SCMTrait<? extends SCMTrait<?>> t : traits) {
if (t instanceof WebhookRegistrationTrait) {
return ((WebhookRegistrationTrait) t).getMode() != WebhookRegistration.DISABLE;
if (t instanceof WebhookRegistrationTrait hookTrait) {
return hookTrait.getMode() != WebhookRegistration.DISABLE;
}
}
return true;
Expand All @@ -348,8 +349,8 @@ public boolean isAutoRegisterHooks() {
@NonNull
public String getCheckoutCredentialsId() {
for (SCMTrait<?> t : traits) {
if (t instanceof SSHCheckoutTrait) {
return StringUtils.defaultString(((SSHCheckoutTrait) t).getCredentialsId(), BitbucketSCMSource
if (t instanceof SSHCheckoutTrait sshTrait) {
return StringUtils.defaultString(sshTrait.getCredentialsId(), BitbucketSCMSource
.DescriptorImpl.ANONYMOUS);
}
}
Expand All @@ -361,7 +362,7 @@ public String getCheckoutCredentialsId() {
@RestrictedSince("2.2.0")
@DataBoundSetter
public void setCheckoutCredentialsId(String checkoutCredentialsId) {
traits.removeIf(trait -> trait instanceof SSHCheckoutTrait);
traits.removeIf(SSHCheckoutTrait.class::isInstance);
if (checkoutCredentialsId != null && !BitbucketSCMSource.DescriptorImpl.SAME.equals(checkoutCredentialsId)) {
traits.add(new SSHCheckoutTrait(checkoutCredentialsId));
}
Expand All @@ -372,8 +373,8 @@ public void setCheckoutCredentialsId(String checkoutCredentialsId) {
@RestrictedSince("2.2.0")
public String getPattern() {
for (SCMTrait<?> trait : traits) {
if (trait instanceof RegexSCMSourceFilterTrait) {
return ((RegexSCMSourceFilterTrait) trait).getRegex();
if (trait instanceof RegexSCMSourceFilterTrait regexTrait) {
return regexTrait.getRegex();
}
}
return ".*";
Expand Down Expand Up @@ -421,8 +422,8 @@ public String getEndpointJenkinsRootUrl() {
@NonNull
public String getIncludes() {
for (SCMTrait<?> trait : traits) {
if (trait instanceof WildcardSCMHeadFilterTrait) {
return ((WildcardSCMHeadFilterTrait) trait).getIncludes();
if (trait instanceof WildcardSCMHeadFilterTrait wildcardTrait) {
return wildcardTrait.getIncludes();
}
}
return "*";
Expand All @@ -435,12 +436,11 @@ public String getIncludes() {
public void setIncludes(@NonNull String includes) {
for (int i = 0; i < traits.size(); i++) {
SCMTrait<?> trait = traits.get(i);
if (trait instanceof WildcardSCMHeadFilterTrait) {
WildcardSCMHeadFilterTrait existing = (WildcardSCMHeadFilterTrait) trait;
if ("*".equals(includes) && "".equals(existing.getExcludes())) {
if (trait instanceof WildcardSCMHeadFilterTrait wildcardTrait) {
if ("*".equals(includes) && "".equals(wildcardTrait.getExcludes())) {

Check notice

Code scanning / CodeQL

Inefficient empty string test Note

Inefficient comparison to empty string, check for zero length instead.
traits.remove(i);
} else {
traits.set(i, new WildcardSCMHeadFilterTrait(includes, existing.getExcludes()));
traits.set(i, new WildcardSCMHeadFilterTrait(includes, wildcardTrait.getExcludes()));
}
return;
}
Expand All @@ -456,8 +456,8 @@ public void setIncludes(@NonNull String includes) {
@NonNull
public String getExcludes() {
for (SCMTrait<?> trait : traits) {
if (trait instanceof WildcardSCMHeadFilterTrait) {
return ((WildcardSCMHeadFilterTrait) trait).getExcludes();
if (trait instanceof WildcardSCMHeadFilterTrait wildcardTrait) {
return wildcardTrait.getExcludes();
}
}
return "";
Expand All @@ -470,12 +470,11 @@ public String getExcludes() {
public void setExcludes(@NonNull String excludes) {
for (int i = 0; i < traits.size(); i++) {
SCMTrait<?> trait = traits.get(i);
if (trait instanceof WildcardSCMHeadFilterTrait) {
WildcardSCMHeadFilterTrait existing = (WildcardSCMHeadFilterTrait) trait;
if ("*".equals(existing.getIncludes()) && "".equals(excludes)) {
if (trait instanceof WildcardSCMHeadFilterTrait wildcardTrait) {
if ("*".equals(wildcardTrait.getIncludes()) && "".equals(excludes)) {

Check notice

Code scanning / CodeQL

Inefficient empty string test Note

Inefficient comparison to empty string, check for zero length instead.
traits.remove(i);
} else {
traits.set(i, new WildcardSCMHeadFilterTrait(existing.getIncludes(), excludes));
traits.set(i, new WildcardSCMHeadFilterTrait(wildcardTrait.getIncludes(), excludes));
}
return;
}
Expand Down Expand Up @@ -569,37 +568,33 @@ public List<Action> retrieveActions(@NonNull SCMNavigatorOwner owner,

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

BitbucketApi bitbucket = BitbucketApiFactory.newInstance(serverUrl, authenticator, repoOwner, null, null);
BitbucketTeam team = bitbucket.getTeam();
if (team != null) {
String defaultTeamUrl;
if (team instanceof BitbucketServerProject) {
defaultTeamUrl = serverUrl + "/projects/" + team.getName();
try (BitbucketApi client = BitbucketApiFactory.newInstance(serverUrl, authenticator, repoOwner, projectKey, null)) {
BitbucketTeam team = client.getTeam();
String avatarURL = null;
String teamURL;
String teamDisplayName;
if (team != null) {
avatarURL = team.getAvatar();
teamURL = team.getLink("html");
teamDisplayName = StringUtils.defaultIfBlank(team.getDisplayName(), team.getName());
if (StringUtils.isNotBlank(teamURL)) {
if (team instanceof BitbucketCloudWorkspace wks) {
teamURL = serverUrl + "/" + wks.getSlug();
} else {
teamURL = serverUrl + "/projects/" + team.getName();
}
}
listener.getLogger().printf("Team: %s%n", HyperlinkNote.encodeTo(teamURL, teamDisplayName));
} else {
defaultTeamUrl = serverUrl + "/" + team.getName();
teamURL = serverUrl + "/" + repoOwner;
teamDisplayName = repoOwner;
listener.getLogger().println("Could not resolve team details");
}
String teamUrl = StringUtils.defaultIfBlank(team.getLink("html"), defaultTeamUrl);
String teamDisplayName = StringUtils.defaultIfBlank(team.getDisplayName(), team.getName());
result.add(new ObjectMetadataAction(
teamDisplayName,
null,
teamUrl
));
result.add(new BitbucketTeamMetadataAction(serverUrl, credentials, team.getName()));
result.add(new BitbucketLink("icon-bitbucket-logo", teamUrl));
listener.getLogger().printf("Team: %s%n", HyperlinkNote.encodeTo(teamUrl, teamDisplayName));
} else {
String teamUrl = serverUrl + "/" + repoOwner;
result.add(new ObjectMetadataAction(
repoOwner,
null,
teamUrl
));
result.add(new BitbucketTeamMetadataAction(null, null, null));
result.add(new BitbucketLink("icon-bitbucket-logo", teamUrl));
listener.getLogger().println("Could not resolve team details");
result.add(new ObjectMetadataAction(teamDisplayName, null, teamURL));
result.add(new BitbucketTeamAvatarMetadataAction(avatarURL, serverUrl, owner.getFullName(), credentialsId));
result.add(new BitbucketLink("icon-bitbucket-logo", teamURL));
return result;
}
return result;
}

@Symbol("bitbucket")
Expand Down Expand Up @@ -629,7 +624,6 @@ public String getIconClassName() {
return "icon-bitbucket-scm-navigator";
}

@SuppressWarnings("unchecked")
@Override
public SCMNavigator newInstance(String name) {
BitbucketSCMNavigator instance = new BitbucketSCMNavigator(StringUtils.defaultString(name));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration;
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint;
import com.cloudbees.jenkins.plugins.bitbucket.hooks.HasPullRequests;
import com.cloudbees.jenkins.plugins.bitbucket.impl.avatars.BitbucketRepoAvatarMetadataAction;
import com.cloudbees.jenkins.plugins.bitbucket.impl.extension.BitbucketEnvVarExtension;
import com.cloudbees.jenkins.plugins.bitbucket.impl.extension.GitClientAuthenticatorExtension;
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils;
Expand All @@ -65,12 +66,9 @@
import hudson.RestrictedSince;
import hudson.Util;
import hudson.console.HyperlinkNote;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.model.Action;
import hudson.model.Actionable;
import hudson.model.Item;
import hudson.model.Items;
import hudson.model.TaskListener;
import hudson.plugins.git.GitSCM;
import hudson.scm.SCM;
Expand All @@ -97,7 +95,6 @@
import java.util.logging.Logger;
import jenkins.authentication.tokens.api.AuthenticationTokens;
import jenkins.model.Jenkins;
import jenkins.plugins.git.MergeWithGitSCMExtension;
import jenkins.plugins.git.traits.GitBrowserSCMSourceTrait;
import jenkins.scm.api.SCMHead;
import jenkins.scm.api.SCMHeadCategory;
Expand Down Expand Up @@ -152,14 +149,6 @@ public class BitbucketSCMSource extends SCMSource {
private static final String CLOUD_REPO_TEMPLATE = "{/owner,repo}";
private static final String SERVER_REPO_TEMPLATE = "/projects{/owner}/repos{/repo}";

/**
* Mapping classes after refactoring for backward compatibility.
*/
@Initializer(before = InitMilestone.PLUGINS_STARTED)
public static void aliases() {
Items.XSTREAM2.addCompatibilityAlias("com.cloudbees.jenkins.plugins.bitbucket.MergeWithGitSCMExtension", MergeWithGitSCMExtension.class);
}

/** How long to delay events received from Bitbucket in order to allow the API caches to sync. */
private static /*mostly final*/ int eventDelaySeconds =
Math.min(
Expand Down Expand Up @@ -1107,26 +1096,27 @@ protected List<Action> retrieveActions(@CheckForNull SCMSourceEvent event,
throws IOException, InterruptedException {
// TODO when we have support for trusted events, use the details from event if event was from trusted source
List<Action> result = new ArrayList<>();
final BitbucketApi bitbucket = buildBitbucketClient();
gatherPrimaryCloneLinks(bitbucket);
BitbucketRepository r = bitbucket.getRepository();
result.add(new BitbucketRepoMetadataAction(r));
String defaultBranch = bitbucket.getDefaultBranch();
if (StringUtils.isNotBlank(defaultBranch)) {
result.add(new BitbucketDefaultBranch(repoOwner, repository, defaultBranch));
try (BitbucketApi bitbucket = buildBitbucketClient()) {
gatherPrimaryCloneLinks(bitbucket);
BitbucketRepository r = bitbucket.getRepository();
result.add(new BitbucketRepoAvatarMetadataAction(r));
String defaultBranch = bitbucket.getDefaultBranch();
if (StringUtils.isNotBlank(defaultBranch)) {
result.add(new BitbucketDefaultBranch(repoOwner, repository, defaultBranch));
}
UriTemplate template;
if (BitbucketApiUtils.isCloud(getServerUrl())) {
template = UriTemplate.fromTemplate(getServerUrl() + CLOUD_REPO_TEMPLATE);
} else {
template = UriTemplate.fromTemplate(getServerUrl() + SERVER_REPO_TEMPLATE);
}
String url = template
.set("owner", repoOwner)
.set("repo", repository)
.expand();
result.add(new BitbucketLink("icon-bitbucket-repo", url));
result.add(new ObjectMetadataAction(r.getRepositoryName(), null, url));
}
UriTemplate template;
if (BitbucketApiUtils.isCloud(getServerUrl())) {
template = UriTemplate.fromTemplate(getServerUrl() + CLOUD_REPO_TEMPLATE);
} else {
template = UriTemplate.fromTemplate(getServerUrl() + SERVER_REPO_TEMPLATE);
}
String url = template
.set("owner", repoOwner)
.set("repo", repository)
.expand();
result.add(new BitbucketLink("icon-bitbucket-repo", url));
result.add(new ObjectMetadataAction(r.getRepositoryName(), null, url));
return result;
}

Expand Down
Loading

0 comments on commit 8a6d076

Please sign in to comment.