Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[JENKINS-31574] A customized RateLimitHandler has been added
  • Loading branch information
recena committed Dec 10, 2015
1 parent bd7c160 commit c391fec
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 24 deletions.
Expand Up @@ -198,30 +198,26 @@ public String getRemote() {
@Override protected final void retrieve(SCMHeadObserver observer, final TaskListener listener) throws IOException, InterruptedException {
StandardCredentials credentials = Connector.lookupScanCredentials(getOwner(), apiUri, scanCredentialsId);
GitHub github = Connector.connect(apiUri, credentials);
if (credentials != null && !github.isCredentialValid()) {
listener.getLogger().format("Invalid scan credentials, skipping%n");
return;
}
if (!github.isAnonymous()) {
listener.getLogger().format("Connecting to %s using %s%n", getDescriptor().getDisplayName(),
CredentialsNameProvider.name(credentials));
} else {
listener.getLogger().format("Connecting to %s using anonymous access%n", getDescriptor().getDisplayName());
try {
if (credentials != null && !github.isCredentialValid()) {
listener.getLogger().format("Invalid scan credentials, skipping%n");
return;
}
if (!github.isAnonymous()) {
listener.getLogger().format("Connecting to %s using %s%n", getDescriptor().getDisplayName(),
CredentialsNameProvider.name(credentials));
} else {
listener.getLogger().format("Connecting to %s using anonymous access%n", getDescriptor().getDisplayName());
}
String fullName = repoOwner + "/" + repository;
final GHRepository repo = github.getRepository(fullName);
listener.getLogger().format("Looking up %s%n", HyperlinkNote.encodeTo(repo.getHtmlUrl().toString(), fullName));
doRetrieve(observer, listener, repo);
listener.getLogger().format("%nDone examining %s%n%n", fullName);
} catch (RateLimitExceededException rle) {
listener.getLogger().format("%n%s%n%n", rle.getMessage());
throw new InterruptedException();
}
/* TODO call GitHubBuilder withRateLimitHandler to notify listener so we do not get stuck without messages in something like
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.kohsuke.github.RateLimitHandler$1.onError(RateLimitHandler.java:38)
at org.kohsuke.github.Requester.handleApiError(Requester.java:486)
at org.kohsuke.github.Requester._to(Requester.java:245)
at org.kohsuke.github.Requester.to(Requester.java:191)
at org.kohsuke.github.GitHub.getRepository(GitHub.java:320)
*/
String fullName = repoOwner + "/" + repository;
final GHRepository repo = github.getRepository(fullName);
listener.getLogger().format("Looking up %s%n", HyperlinkNote.encodeTo(repo.getHtmlUrl().toString(), fullName));
doRetrieve(observer, listener, repo);
listener.getLogger().format("%nDone examining %s%n%n", fullName);
}

protected abstract void doRetrieve(SCMHeadObserver observer, TaskListener listener, GHRepository repo) throws IOException, InterruptedException;
Expand Down
Expand Up @@ -36,13 +36,16 @@
import hudson.Util;
import hudson.security.ACL;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.scm.api.SCMSourceOwner;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.kohsuke.github.GitHub;
import org.kohsuke.github.GitHubBuilder;
import org.kohsuke.github.RateLimitHandler;

/**
* Utilities that could perhaps be moved into {@code github-api}.
Expand All @@ -68,7 +71,7 @@ public class Connector {
public static @Nonnull GitHub connect(@CheckForNull String apiUri, @CheckForNull StandardCredentials credentials) throws IOException {
if (Util.fixEmptyAndTrim(apiUri) == null) {
if (credentials == null) {
return GitHub.connectAnonymously();
return new GitHubBuilder().withRateLimitHandler(CUSTOMIZED).build();
} else if (credentials instanceof StandardUsernamePasswordCredentials) {
StandardUsernamePasswordCredentials c = (StandardUsernamePasswordCredentials) credentials;
return GitHub.connectUsingPassword(c.getUsername(), c.getPassword().getPlainText());
Expand Down Expand Up @@ -108,4 +111,25 @@ private static List<DomainRequirement> githubDomainRequirements(String apiUri) {

private Connector() {}

/**
* Fail immediately and throw a customized exception.
*/
public static final RateLimitHandler CUSTOMIZED = new RateLimitHandler() {

@Override
public void onError(IOException e, HttpURLConnection uc) throws IOException {
try {
long limit = Long.parseLong(uc.getHeaderField("X-RateLimit-Limit"));
long remaining = Long.parseLong(uc.getHeaderField("X-RateLimit-Remaining"));
long reset = Long.parseLong(uc.getHeaderField("X-RateLimit-Reset"));

throw new RateLimitExceededException("GitHub API rate limit exceeded", limit, remaining, reset);
} catch (NumberFormatException nfe) {
// Something wrong happened
throw new IOException();
}
}

};

}
Expand Up @@ -117,6 +117,9 @@ public String getApiUri() {
try {
// Requires an authenticated access
myself = github.getMyself();
} catch (RateLimitExceededException rle) {
listener.getLogger().format("%n%s%n%n", rle.getMessage());
throw new InterruptedException();
} catch (IOException e) {
// Something wrong happened, maybe java.net.ConnectException?
}
Expand All @@ -137,6 +140,9 @@ public String getApiUri() {
GHOrganization org = null;
try {
org = github.getOrganization(repoOwner);
} catch (RateLimitExceededException rle) {
listener.getLogger().format("%n%s%n%n", rle.getMessage());
throw new InterruptedException();
} catch (IOException e) {
// may be a user... ok to ignore
}
Expand All @@ -151,6 +157,9 @@ public String getApiUri() {
GHUser user = null;
try {
user = github.getUser(repoOwner);
} catch (RateLimitExceededException rle) {
listener.getLogger().format("%n%s%n%n", rle.getMessage());
throw new InterruptedException();
} catch (IOException e) {
// Something wrong happened, maybe java.net.ConnectException?
}
Expand Down
@@ -0,0 +1,71 @@
/*
* The MIT License
*
* Copyright 2015 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package org.jenkinsci.plugins.github_branch_source;

import java.io.IOException;

public class RateLimitExceededException extends IOException {

private static final long serialVersionUID = 1L;

private long limit;

private long remaining;

private long reset;

public RateLimitExceededException() {
super();
}

public RateLimitExceededException(String msg, long limit, long remaining, long reset) {
super(msg);
this.limit = limit;
this.remaining = remaining;
this.reset = reset;
}

public RateLimitExceededException(Throwable cause) {
initCause(cause);
}

public RateLimitExceededException(String message, Throwable cause) {
super(message);
initCause(cause);
}

public long getReset() {
return reset;
}

public long getRemaining() {
return remaining;
}

public long getLimit() {
return limit;
}

}

0 comments on commit c391fec

Please sign in to comment.