-
Notifications
You must be signed in to change notification settings - Fork 374
[JENKINS-62249] Delegate GitHub App token generation from agents and improve caching #326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[JENKINS-62249] Delegate GitHub App token generation from agents and improve caching #326
Conversation
src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java
Outdated
Show resolved
Hide resolved
appInstallationToken = generateAppInstallationToken(appID, privateKey.getPlainText(), apiUri, owner); | ||
cachedToken = appInstallationToken; | ||
tokenCacheTime = now; | ||
synchronized (this) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If multiple requests for a password are made concurrently, we want to just get one refreshed token.
Amends #302. |
src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java
Outdated
Show resolved
Hide resolved
f9e088d
to
f3a2aa1
Compare
…rial form is plaintext)
8244fde
to
1136fca
Compare
1136fca
to
8367aeb
Compare
@@ -104,7 +111,7 @@ public void setOwner(String owner) { | |||
} | |||
|
|||
@SuppressWarnings("deprecation") // preview features are required for GitHub app integration, GitHub api adds deprecated to all preview methods | |||
static String generateAppInstallationToken(String appId, String appPrivateKey, String apiUrl, String owner) { | |||
static AppInstallationToken generateAppInstallationToken(String appId, String appPrivateKey, String apiUrl, String owner) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of returning the bare token, return a serializable instance of the token and when it should be refreshed.
Subsumes #330. |
5aea4eb
to
b76dbdb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks ok to me. Would it be relatively easy to add WireMock tests for this behavior, especially with a token being used on an agent, or not really?
src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java
Show resolved
Hide resolved
…bAppCredentials.java Co-authored-by: Devin Nusbaum <[email protected]>
Not breaking this is pretty important. I'll add a test. |
src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java
Outdated
Show resolved
Hide resolved
34e5409
to
399ee15
Compare
399ee15
to
54e01d5
Compare
Removed sorting that was likely to result in flaky results. There is only one way the agent log makes sense and the output is still checked.
746d626
to
3ac6082
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Certainly do not follow all the twists and turns of the token expiration logic, but overall it looks good, assuming sanity testing passes.
@@ -52,6 +52,10 @@ | |||
<artifactId>github</artifactId> | |||
<version>1.31.0</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.jenkins-ci.plugins.workflow</groupId> | |||
<artifactId>workflow-support</artifactId> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For Timeout
I guess. Wonder if this should be moved to the likes of plugin-util-api
…
src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java
Outdated
Show resolved
Hide resolved
return appInstallationToken.getExpiresAt() | ||
.toInstant() | ||
.getEpochSecond(); | ||
} catch (Exception e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What exception is expected here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was confused about this earlier too. IOException
is declared to be thrown by GHAppInstallationToken.getExpiresAt, but looking at GitHubClient.parseDate I think the throws
clause on GHAppInstallationToken.getExpiresAt
could just be dropped. Date parsing could throw runtime exceptions like DateTimeParseException
(not sure what would cause it in practice, perhaps changes on GitHub's side), so maybe that is the kind of thing this is trying to catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implementation has changed over time, but changing the throws
is an API change that will cause compilation to fail, right?
It is also possible for GitHub to hand us values that don't parse. Regardless of why it fails, we want to recover and continue.
|
||
private static final class DelegatingGitHubAppCredentials extends BaseStandardCredentials implements StandardUsernamePasswordCredentials { | ||
|
||
private static final String SEP = "%%%"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried to pick something unlikely to occur in any of the field values. I guess you could use JSONObject
to be a little safer.
src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsTest.java
Show resolved
Hide resolved
src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsTest.java
Outdated
Show resolved
Hide resolved
src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsTest.java
Outdated
Show resolved
Hide resolved
src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsTest.java
Outdated
Show resolved
Hide resolved
src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsTest.java
Outdated
Show resolved
Hide resolved
return appInstallationToken.getExpiresAt() | ||
.toInstant() | ||
.getEpochSecond(); | ||
} catch (Exception e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was confused about this earlier too. IOException
is declared to be thrown by GHAppInstallationToken.getExpiresAt, but looking at GitHubClient.parseDate I think the throws
clause on GHAppInstallationToken.getExpiresAt
could just be dropped. Date parsing could throw runtime exceptions like DateTimeParseException
(not sure what would cause it in practice, perhaps changes on GitHub's side), so maybe that is the kind of thing this is trying to catch.
src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java
Outdated
Show resolved
Hide resolved
cefdf52
to
d80588e
Compare
now = Instant.now().getEpochSecond(); | ||
token = new GitHubAppCredentials.AppInstallationToken("", now); | ||
assertThat(token.isStale(), is(false)); | ||
assertThat(token.getTokenStaleEpochSeconds(), equalTo(now + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Observed to flake in #527:
java.lang.AssertionError:
Expected: <1648072806L>
but: was <1648072807L>
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:6)
at org.jenkinsci.plugins.github_branch_source.GithubAppCredentialsAppInstallationTokenTest.testAppInstallationTokenStale(GithubAppCredentialsAppInstallationTokenTest.java:23)
I do not think you can make an assertion like this without some grace period.
… flaking; use `closeTo` for actual times
Description
Makes GitHub App credentials more stable by requesting them less often and only requesting them via master.
The code was treating tokens as expired after 4 minutes on the assumption they were only valid for 8 minutes. They are actually valid for 60 minutes. It is safe to continue to return them for at least 15 minutes, leaving 45 minutes of valid use.
(NOTE: this PR does not address cases where individual operations take more than 45 minutes.)
It appears that a number of users have experienced network issues around agents requesting tokens directly from GitHub.
This change makes agents ask the controller to for updated tokens, rather than trying to get them directly.
Requesting new tokens less often and routing the requests through the controller mitigates most cases of checkout failing due to "socket timeout".
See
JENKINS-62249 for further information.
Submitter checklist
Reviewer checklist
Documentation changes
Users/aliases to notify