diff --git a/pom.xml b/pom.xml index e3e846e34..515b630a4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.jenkins-ci.plugins plugin - 4.88 + 5.19 @@ -33,8 +33,9 @@ 999999-SNAPSHOT jenkinsci/${project.artifactId}-plugin 2.2.0 - 2.440.3 - true + + 2.504 + ${jenkins.baseline}.1 false @@ -42,8 +43,8 @@ io.jenkins.tools.bom - bom-2.440.x - 3234.v5ca_5154341ef + bom-${jenkins.baseline}.x + 4948.vcf1d17350668 pom import @@ -59,6 +60,10 @@ io.jenkins.plugins caffeine-api + + io.jenkins.plugins + commons-lang3-api + io.jenkins.plugins ionicons-api @@ -83,12 +88,6 @@ org.jenkins-ci.plugins.workflow workflow-support - - com.github.tomakehurst - wiremock-jre8-standalone - 2.35.2 - test - io.jenkins configuration-as-code @@ -107,7 +106,7 @@ org.awaitility awaitility - 4.2.2 + 4.3.0 test @@ -137,6 +136,12 @@ mockito-core test + + org.wiremock + wiremock-standalone + 3.13.1 + test + diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/ApiRateLimitChecker.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/ApiRateLimitChecker.java index 57ce199ad..41f0deb8f 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/ApiRateLimitChecker.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/ApiRateLimitChecker.java @@ -1,7 +1,6 @@ package org.jenkinsci.plugins.github_branch_source; import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Util; import hudson.model.TaskListener; import hudson.util.LogTaskListener; @@ -12,13 +11,12 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.jenkinsci.plugins.github.config.GitHubServerConfig; import org.kohsuke.github.GHRateLimit; import org.kohsuke.github.GitHub; import org.kohsuke.github.RateLimitChecker; -@SuppressFBWarnings("DMI_RANDOM_USED_ONLY_ONCE") // https://github.com/spotbugs/spotbugs/issues/1539 public enum ApiRateLimitChecker { /** Attempt to evenly distribute GitHub API requests. */ diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/Connector.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/Connector.java index 0973951c5..d6a30fc14 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/Connector.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/Connector.java @@ -75,7 +75,7 @@ import jenkins.util.SystemProperties; import okhttp3.Cache; import okhttp3.OkHttpClient; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.jenkinsci.plugins.gitclient.GitClient; import org.jenkinsci.plugins.github.config.GitHubServerConfig; import org.kohsuke.github.GHAppInstallationToken; diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/Endpoint.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/Endpoint.java index 8c6539002..0669a5444 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/Endpoint.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/Endpoint.java @@ -42,7 +42,7 @@ import java.util.logging.Logger; import jenkins.model.Jenkins; import jenkins.scm.api.SCMName; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.github.GitHub; diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/FillErrorResponse.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/FillErrorResponse.java index c2513a629..3d030fbf6 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/FillErrorResponse.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/FillErrorResponse.java @@ -1,13 +1,12 @@ package org.jenkinsci.plugins.github_branch_source; import hudson.Util; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletResponse; import jenkins.model.Jenkins; import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; +import org.kohsuke.stapler.StaplerRequest2; +import org.kohsuke.stapler.StaplerResponse2; // TODO replace with corresponding core functionality once Jenkins core has JENKINS-42443 class FillErrorResponse extends IOException implements HttpResponse { @@ -20,8 +19,7 @@ public FillErrorResponse(String message, boolean clearList) { } @Override - public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) - throws IOException, ServletException { + public void generateResponse(StaplerRequest2 req, StaplerResponse2 rsp, Object node) throws IOException { rsp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); rsp.setContentType("text/html;charset=UTF-8"); rsp.setHeader("X-Jenkins-Select-Error", clearList ? "clear" : "retain"); diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java index 4dc65d3f8..d2a2886e3 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java @@ -103,7 +103,6 @@ public class GitHubAppCredentials extends BaseStandardCredentials implements Sta private String apiUri; - @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC", justification = "#withOwner locking only for #byOwner") @Deprecated private String owner; diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubConfiguration.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubConfiguration.java index 5360b3d8e..f06c109b2 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubConfiguration.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubConfiguration.java @@ -41,8 +41,8 @@ import jenkins.model.GlobalConfiguration; import jenkins.model.Jenkins; import net.sf.json.JSONObject; -import org.apache.commons.lang.StringUtils; -import org.kohsuke.stapler.StaplerRequest; +import org.apache.commons.lang3.StringUtils; +import org.kohsuke.stapler.StaplerRequest2; @Extension public class GitHubConfiguration extends GlobalConfiguration { @@ -60,7 +60,7 @@ public GitHubConfiguration() { } @Override - public boolean configure(StaplerRequest req, JSONObject json) throws FormException { + public boolean configure(StaplerRequest2 req, JSONObject json) throws FormException { req.bindJSON(this, json); return true; } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgMetadataAction.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgMetadataAction.java index 37c170ecf..284f00f69 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgMetadataAction.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgMetadataAction.java @@ -32,7 +32,7 @@ import java.io.ObjectStreamException; import java.util.Objects; import jenkins.scm.api.metadata.AvatarMetadataAction; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.kohsuke.github.GHUser; import org.kohsuke.stapler.Stapler; @@ -76,7 +76,7 @@ public String getAvatarImageOf(String size) { String image = avatarIconClassNameImageOf(getAvatarIconClassName(), size); return image != null ? image - : (Stapler.getCurrentRequest().getContextPath() + : (Stapler.getCurrentRequest2().getContextPath() + Hudson.RESOURCE_PATH + "/plugin/github-branch-source/images/" + "/github-logo.svg"); diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepositoryInfo.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepositoryInfo.java index db74b0720..9302e863f 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepositoryInfo.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepositoryInfo.java @@ -24,13 +24,13 @@ package org.jenkinsci.plugins.github_branch_source; -import static org.apache.commons.lang.StringUtils.removeEnd; +import static org.apache.commons.lang3.StringUtils.removeEnd; import static org.jenkinsci.plugins.github_branch_source.GitHubSCMSource.GITHUB_COM; import edu.umd.cs.findbugs.annotations.NonNull; import java.net.MalformedURLException; import java.net.URL; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; /** * Used to compute values for GitHubSCMSource from a user-specified repository URL. diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilder.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilder.java index 44bd2ee37..962b9707e 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilder.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilder.java @@ -32,7 +32,6 @@ import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.model.Item; import hudson.model.Queue; import hudson.plugins.git.GitSCM; @@ -49,7 +48,7 @@ import jenkins.scm.api.SCMRevision; import jenkins.scm.api.SCMSourceOwner; import jenkins.scm.api.mixin.TagSCMHead; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.transport.RefSpec; import org.jenkinsci.plugins.github.config.GitHubServerConfig; @@ -59,7 +58,6 @@ * * @since 2.2.0 */ -@SuppressFBWarnings("DMI_RANDOM_USED_ONLY_ONCE") // https://github.com/spotbugs/spotbugs/issues/1539 public class GitHubSCMBuilder extends GitSCMBuilder { private static final Random ENTROPY = new Random(); diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFileSystem.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFileSystem.java index cdecf79ea..e3bd1a01e 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFileSystem.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFileSystem.java @@ -46,7 +46,7 @@ import jenkins.scm.api.SCMRevision; import jenkins.scm.api.SCMSource; import jenkins.scm.api.SCMSourceDescriptor; -import org.apache.commons.lang.time.FastDateFormat; +import org.apache.commons.lang3.time.FastDateFormat; import org.kohsuke.github.GHCommit; import org.kohsuke.github.GHRef; import org.kohsuke.github.GHRepository; diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigator.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigator.java index 4e35ad913..d188b2af1 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigator.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigator.java @@ -39,6 +39,7 @@ import hudson.AbortException; import hudson.Extension; import hudson.ExtensionList; +import hudson.Functions; import hudson.RestrictedSince; import hudson.Util; import hudson.console.HyperlinkNote; @@ -90,7 +91,7 @@ import jenkins.scm.impl.trait.WildcardSCMHeadFilterTrait; import jenkins.util.SystemProperties; import net.jcip.annotations.GuardedBy; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.jenkins.ui.icon.Icon; import org.jenkins.ui.icon.IconFormat; import org.jenkins.ui.icon.IconSet; @@ -1041,50 +1042,60 @@ public void visitSources(SCMSourceObserver observer) throws IOException, Interru continue; // ignore repos in other orgs when using GHMyself } - if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is archived", - repo.getName()))); - - } else if (!topicMatches(gitHubSCMNavigatorContext, repo, listener.getLogger())) { - // exclude repositories which are missing one or more of the specified topics - witness.record(repo.getName(), false); - } else if (!repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is public", - repo.getName()))); - } else if (repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is private", - repo.getName()))); - } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() - && repo.getSource() != null) { - witness.record(repo.getName(), false); + try { + if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is archived", + repo.getName()))); + + } else if (!topicMatches(gitHubSCMNavigatorContext, repo, listener.getLogger())) { + // exclude repositories which are missing one or more of the specified topics + witness.record(repo.getName(), false); + } else if (!repo.isPrivate() + && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is public", + repo.getName()))); + } else if (repo.isPrivate() + && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is private", + repo.getName()))); + } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() + && repo.getSource() != null) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is a fork", + repo.getName()))); + } else if (request.process(repo.getName(), sourceFactory, null, witness)) { + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "%d repositories were processed (query completed)", + witness.getCount()))); + } + } catch (IOException e) { listener.getLogger() .println(GitHubConsoleNote.create( System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is a fork", - repo.getName()))); - } else if (request.process(repo.getName(), sourceFactory, null, witness)) { - listener.getLogger() - .println(GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "%d repositories were processed (query completed)", - witness.getCount()))); + String.format("Error while processing repository %s", repo.getName()))); + Functions.printStackTrace(e, listener.getLogger()); } } listener.getLogger() @@ -1124,48 +1135,60 @@ public void visitSources(SCMSourceObserver observer) throws IOException, Interru repositories = org.listRepositories(100); } for (GHRepository repo : repositories) { - if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { - // exclude archived repositories - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is archived", repo.getName()))); - } else if (!topicMatches(gitHubSCMNavigatorContext, repo, listener.getLogger())) { - // exclude repositories which are missing one or more of the specified topics - witness.record(repo.getName(), false); - } else if (!repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is public", repo.getName()))); + try { + if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { + // exclude archived repositories + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is archived", + repo.getName()))); + } else if (!topicMatches(gitHubSCMNavigatorContext, repo, listener.getLogger())) { + // exclude repositories which are missing one or more of the specified topics + witness.record(repo.getName(), false); + } else if (!repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is public", + repo.getName()))); - } else if (repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is private", repo.getName()))); + } else if (repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is private", + repo.getName()))); - } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() - && repo.getSource() != null) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is a fork", repo.getName()))); - } else if (request.process(repo.getName(), sourceFactory, null, witness)) { + } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() + && repo.getSource() != null) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is a fork", + repo.getName()))); + } else if (request.process(repo.getName(), sourceFactory, null, witness)) { + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "%d repositories were processed (query completed)", + witness.getCount()))); + } + } catch (IOException e) { listener.getLogger() .println(GitHubConsoleNote.create( System.currentTimeMillis(), - String.format( - "%d repositories were processed (query completed)", - witness.getCount()))); + String.format("Error while processing repository %s", repo.getName()))); + Functions.printStackTrace(e, listener.getLogger()); } } listener.getLogger() @@ -1753,7 +1776,7 @@ public String getIconFilePathPattern() { /** {@inheritDoc} */ @Override public String getIconClassName() { - return "icon-github-scm-navigator"; + return "symbol-logo-github plugin-ionicons-api"; } /** {@inheritDoc} */ diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java index 6b1583df9..ac1c480fc 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java @@ -26,8 +26,8 @@ import static hudson.Functions.isWindows; import static hudson.model.Items.XSTREAM2; -import static org.apache.commons.lang.StringUtils.isBlank; -import static org.apache.commons.lang.StringUtils.removeEnd; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.removeEnd; import static org.jenkinsci.plugins.github_branch_source.Connector.isCredentialValid; import static org.jenkinsci.plugins.github_branch_source.GitHubSCMBuilder.API_V3; import static org.jenkinsci.plugins.github_branch_source.GitHubSCMBuilder.HTTPS; @@ -58,6 +58,7 @@ import hudson.util.FormValidation; import hudson.util.ListBoxModel; import hudson.util.LogTaskListener; +import jakarta.servlet.http.HttpServletResponse; import java.io.Closeable; import java.io.FileNotFoundException; import java.io.IOException; @@ -85,7 +86,6 @@ import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletResponse; import jenkins.model.Jenkins; import jenkins.plugins.git.AbstractGitSCMSource; import jenkins.plugins.git.GitTagSCMRevision; @@ -117,7 +117,7 @@ import jenkins.scm.impl.trait.Selection; import jenkins.scm.impl.trait.WildcardSCMHeadFilterTrait; import jenkins.util.SystemProperties; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.eclipse.jgit.lib.Constants; import org.jenkinsci.Symbol; import org.jenkinsci.plugins.github.config.GitHubServerConfig; @@ -1053,6 +1053,19 @@ public GHPermissionType fetch(String username) throws IOException, InterruptedEx } }); + if (request.isFetchPRs()) { + // JENKINS-56996 / JENKINS-73791 + // PRs are one the most error prone areas for scans + // Branches and tags are contained only the current repo, PRs go across forks + // FileNotFoundException can occur in a number of situations + // When this happens, it is not ideal behavior but it is better to let the PR be + // orphaned + // and the orphan strategy control the result than for this error to stop scanning + // (For Org scanning this is particularly important.) + // If some more general IO exception is thrown, we will still fail. + validatePullRequests(request); + } + if (request.isFetchBranches() && !request.isComplete() && this.shouldRetrieve(observer, event, BranchSCMHead.class)) { @@ -1067,6 +1080,7 @@ public GHPermissionType fetch(String username) throws IOException, InterruptedEx HyperlinkNote.encodeTo( resolvedRepositoryUrl + "/tree/" + branchName, branchName)); BranchSCMHead head = new BranchSCMHead(branchName); + if (request.process( head, new SCMRevisionImpl(head, branch.getSHA1()), @@ -1081,8 +1095,6 @@ public SCMSourceCriteria.Probe create( } }, new CriteriaWitness(listener))) { - listener.getLogger() - .format("%n %d branches were processed (query completed)%n", count); break; } } @@ -1095,18 +1107,6 @@ public SCMSourceCriteria.Probe create( int count = 0; int errorCount = 0; Map> strategies = request.getPRStrategies(); - - // JENKINS-56996 - // PRs are one the most error prone areas for scans - // Branches and tags are contained only the current repo, PRs go across forks - // FileNotFoundException can occur in a number of situations - // When this happens, it is not ideal behavior but it is better to let the PR be - // orphaned - // and the orphan strategy control the result than for this error to stop scanning - // (For Org scanning this is particularly important.) - // If some more general IO exception is thrown, we will still fail. - - validatePullRequests(request); for (final GHPullRequest pr : request.getPullRequests()) { int number = pr.getNumber(); try { @@ -2134,7 +2134,6 @@ public FormValidation doValidateRepositoryUrlAndCredentials( } catch (IllegalArgumentException e) { return FormValidation.error(e, e.getMessage()); } - StandardCredentials credentials = Connector.lookupScanCredentials(context, info.getApiUri(), credentialsId, info.getRepoOwner()); StringBuilder sb = new StringBuilder(); @@ -2571,17 +2570,24 @@ public void observe(GHPullRequest pr) { GHUser user = null; try { user = pr.getUser(); - if (users.containsKey(user.getLogin())) { - // looked up this user already - user = users.get(user.getLogin()); + String login = user.getLogin(); + if ("copilot".equalsIgnoreCase(login)) { + ContributorMetadataAction contributor = + new ContributorMetadataAction("copilot", "copilot", "copilot@unknown.user"); + pullRequestContributorCache.put(number, contributor); + users.put("copilot", user); + } else { + if (users.containsKey(login)) { + // looked up this user already + user = users.get(login); + } + ContributorMetadataAction contributor = + new ContributorMetadataAction(login, user.getName(), user.getEmail()); + // store the populated user record now that we have it + pullRequestContributorCache.put(number, contributor); + users.put(login, user); } - ContributorMetadataAction contributor = - new ContributorMetadataAction(user.getLogin(), user.getName(), user.getEmail()); - pullRequestContributorCache.put(number, contributor); - // store the populated user record now that we have it - users.put(user.getLogin(), user); } catch (FileNotFoundException e) { - // If file not found for user, warn but keep going request.listener() .getLogger() .format( diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMRevision.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMRevision.java index 9931447a9..afce906e5 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMRevision.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMRevision.java @@ -32,7 +32,7 @@ import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; import jenkins.scm.api.mixin.ChangeRequestSCMHead2; import jenkins.scm.api.mixin.ChangeRequestSCMRevision; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.kohsuke.stapler.export.Exported; /** Revision of a pull request. */ @@ -58,7 +58,7 @@ public PullRequestSCMRevision( this.mergeHash = mergeHash; } - @SuppressFBWarnings({"SE_PRIVATE_READ_RESOLVE_NOT_INHERITED", "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"}) + @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE") private Object readResolve() { if (getTarget() == null) { // fix an instance prior to the type migration, thankfully we have all the required info diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/SSHCheckoutTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/SSHCheckoutTrait.java index 67ecdad4b..de6a6d00f 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/SSHCheckoutTrait.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/SSHCheckoutTrait.java @@ -47,7 +47,7 @@ import jenkins.scm.api.trait.SCMSourceContext; import jenkins.scm.api.trait.SCMSourceTrait; import jenkins.scm.api.trait.SCMSourceTraitDescriptor; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.jenkinsci.Symbol; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/EndpointTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/EndpointTest.java index b470e6fc3..9693adbfb 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/EndpointTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/EndpointTest.java @@ -10,13 +10,13 @@ import hudson.Util; import hudson.model.UnprotectedRootAction; import hudson.security.csrf.CrumbExclusion; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URL; import java.util.Arrays; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import jenkins.model.Jenkins; import org.htmlunit.FailingHttpStatusCodeException; import org.htmlunit.HttpMethod; @@ -31,8 +31,8 @@ import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.MockAuthorizationStrategy; import org.jvnet.hudson.test.TestExtension; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; +import org.kohsuke.stapler.StaplerRequest2; +import org.kohsuke.stapler.StaplerResponse2; import org.xml.sax.SAXException; public class EndpointTest { @@ -103,7 +103,7 @@ private String appendCrumb(String url) { } private String getCrumb() { - return Functions.getCrumbRequestField() + "=" + Functions.getCrumb(null); + return Functions.getCrumbRequestField() + "=" + Functions.getCrumb((StaplerRequest2) null); } private Page post(String relative, String userName) throws Exception { @@ -116,8 +116,8 @@ private Page post(String relative, String userName) throws Exception { final WebRequest request = new WebRequest(new URL(client.getContextPath() + relative), HttpMethod.POST); request.setAdditionalHeader("Accept", client.getBrowserVersion().getHtmlAcceptHeader()); - request.setRequestParameters( - Arrays.asList(new NameValuePair(Functions.getCrumbRequestField(), Functions.getCrumb(null)))); + request.setRequestParameters(Arrays.asList( + new NameValuePair(Functions.getCrumbRequestField(), Functions.getCrumb((StaplerRequest2) null)))); return client.getPage(request); } @@ -141,7 +141,7 @@ public String getUrlName() { return "testroot"; } - public void doIndex(StaplerRequest request, StaplerResponse response) throws IOException { + public void doIndex(StaplerRequest2 request, StaplerResponse2 response) throws IOException { visited = true; response.getWriter().println("OK"); } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilderTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilderTest.java index 1f1ef4d95..1b39e0f4c 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilderTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilderTest.java @@ -20,6 +20,7 @@ import com.cloudbees.plugins.credentials.SystemCredentialsProvider; import com.cloudbees.plugins.credentials.domains.Domain; import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; +import hudson.model.Descriptor; import hudson.plugins.git.GitSCM; import hudson.plugins.git.Revision; import hudson.plugins.git.UserRemoteConfig; @@ -82,7 +83,7 @@ public void createGitHubSCMSourceForTest(boolean configuredByUrl, String repoUrl } @Before - public void setUp() throws IOException { + public void setUp() throws IOException, Descriptor.FormException { owner = j.createProject(WorkflowMultiBranchProject.class); Credentials userPasswordCredential = new UsernamePasswordCredentialsImpl( CredentialsScope.GLOBAL, "user-pass", null, "git-user", "git-secret"); diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorTest.java index 4a501b6a0..f2b8d395a 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorTest.java @@ -37,6 +37,7 @@ import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import hudson.model.Descriptor; import hudson.model.Item; import hudson.model.TaskListener; import hudson.model.User; @@ -46,6 +47,7 @@ import hudson.security.SecurityRealm; import hudson.util.ListBoxModel; import hudson.util.LogTaskListener; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -75,8 +77,16 @@ public class GitHubSCMNavigatorTest extends AbstractGitHubWireMockTest { @Mock private SCMSourceOwner scmSourceOwner; - private BaseStandardCredentials credentials = new UsernamePasswordCredentialsImpl( - CredentialsScope.GLOBAL, "authenticated-user", null, "git-user", "git-secret"); + private BaseStandardCredentials credentials; + + { + try { + credentials = new UsernamePasswordCredentialsImpl( + CredentialsScope.GLOBAL, "authenticated-user", null, "git-user", "git-secret"); + } catch (Descriptor.FormException e) { + throw new RuntimeException(e); + } + } private GitHubSCMNavigator navigator; @@ -451,6 +461,42 @@ public void appliesFilters() throws Exception { assertEquals(projectNames, Collections.singleton("yolo")); } + @Test + public void fetchBadRepo() throws Exception { + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = new SCMSourceObserver() { + @NonNull + @Override + public SCMSourceOwner getContext() { + return scmSourceOwner; + } + + @NonNull + @Override + public TaskListener getListener() { + return new LogTaskListener(Logger.getAnonymousLogger(), Level.INFO); + } + + @NonNull + @Override + public ProjectObserver observe(@NonNull String projectName) throws IllegalArgumentException, IOException { + if ("basic".equalsIgnoreCase(projectName)) { + throw new IOException("Failed to get repo basic"); + } + projectNames.add(projectName); + return new NoOpProjectObserver(); + } + + @Override + public void addAttribute(@NonNull String key, @Nullable Object value) + throws IllegalArgumentException, ClassCastException {} + }; + + navigator.visitSources(SCMSourceObserver.filter(observer, "basic", "yolo")); + + assertThat(projectNames, containsInAnyOrder("yolo")); + } + @Test public void fetchActions() throws Exception { assertThat( diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbeTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbeTest.java index 63917dacd..545600ddc 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbeTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbeTest.java @@ -4,7 +4,6 @@ import static org.junit.Assert.*; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; -import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; import com.github.tomakehurst.wiremock.http.RequestMethod; import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; @@ -32,10 +31,8 @@ public class GitHubSCMProbeTest { public WireMockRule githubApi = factory.getRule(WireMockConfiguration.options() .dynamicPort() .usingFilesUnderClasspath("cache_failure") - .extensions(ResponseTemplateTransformer.builder() - .global(true) - .maxCacheEntries(0L) - .build())); + .globalTemplating(true) + .withMaxTemplateCacheEntries(0L)); private GitHubSCMProbe probe; diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java index 1a58db43e..77666a076 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java @@ -375,6 +375,11 @@ public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) thro @Test public void fetchSmokes_badUser() throws Exception { + source.setTraits(Arrays.asList( + new BranchDiscoveryTrait(true, false), + new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), + new ForkPullRequestDiscoveryTrait.TrustContributors()))); // make it so PR-2 returns a file not found for user githubApi.stubFor(get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/pulls/2")) .inScenario("Pull Request Merge Hash")