Skip to content

Commit 5344d31

Browse files
committed
Allow to define a personal access token and inject TokenGitHubClients
While in mosts cases, you shouldn't need it, it could be useful when receiving webhook events not containing an installation id. Note that when defined, we try our best to push it in the injection when the installation is not present.
1 parent 4f9a652 commit 5344d31

File tree

15 files changed

+311
-5
lines changed

15 files changed

+311
-5
lines changed

deployment/src/main/java/io/quarkiverse/githubapp/deployment/GitHubAppProcessor.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858

5959
import io.quarkiverse.githubapp.ConfigFile;
6060
import io.quarkiverse.githubapp.GitHubEvent;
61+
import io.quarkiverse.githubapp.TokenGitHubClients;
6162
import io.quarkiverse.githubapp.deployment.DispatchingConfiguration.EventAnnotation;
6263
import io.quarkiverse.githubapp.deployment.DispatchingConfiguration.EventAnnotationLiteral;
6364
import io.quarkiverse.githubapp.deployment.DispatchingConfiguration.EventDispatchingConfiguration;
@@ -223,7 +224,8 @@ void additionalBeans(CombinedIndexBuildItem index, BuildProducer<AdditionalBeanB
223224
DefaultErrorHandler.class,
224225
GitHubFileDownloader.class,
225226
GitHubConfigFileProviderImpl.class,
226-
CheckedConfigProvider.class)
227+
CheckedConfigProvider.class,
228+
TokenGitHubClients.class)
227229
.setUnremovable();
228230

229231
for (ClassInfo errorHandler : index.getIndex().getAllKnownImplementors(GitHubAppDotNames.ERROR_HANDLER)) {
@@ -582,11 +584,20 @@ private static void generateDispatcher(ClassOutput beanClassOutput,
582584
}
583585
BytecodeCreator installationIdNull = testInstallationId.falseBranch();
584586
installationIdNull.assign(gitHubRh, installationIdNull.invokeVirtualMethod(
585-
MethodDescriptor.ofMethod(GitHubService.class, "getApplicationClient", GitHub.class),
587+
MethodDescriptor.ofMethod(GitHubService.class, "getTokenOrApplicationClient", GitHub.class),
586588
installationIdNull.readInstanceField(
587589
FieldDescriptor.of(dispatcherClassCreator.getClassName(), GITHUB_SERVICE_FIELD, GitHubService.class),
588590
installationIdNull.getThis())));
589-
installationIdNull.assign(gitHubGraphQLClientRh, installationIdNull.loadNull());
591+
if (dispatchingConfiguration.requiresGraphQLClient()) {
592+
installationIdNull.assign(gitHubGraphQLClientRh, installationIdNull.invokeVirtualMethod(
593+
MethodDescriptor.ofMethod(GitHubService.class, "getTokenGraphQLClientOrNull", DynamicGraphQLClient.class),
594+
installationIdNull.readInstanceField(
595+
FieldDescriptor.of(dispatcherClassCreator.getClassName(), GITHUB_SERVICE_FIELD,
596+
GitHubService.class),
597+
installationIdNull.getThis())));
598+
} else {
599+
installationIdNull.assign(gitHubGraphQLClientRh, installationIdNull.loadNull());
600+
}
590601

591602
for (EventDispatchingConfiguration eventDispatchingConfiguration : dispatchingConfiguration.getEventConfigurations()
592603
.values()) {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.quarkiverse.githubapp.deployment;
2+
3+
import jakarta.inject.Inject;
4+
5+
import org.jboss.shrinkwrap.api.ShrinkWrap;
6+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
7+
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.api.extension.RegisterExtension;
9+
import org.kohsuke.github.GHEventPayload;
10+
11+
import io.quarkiverse.githubapp.TokenGitHubClients;
12+
import io.quarkiverse.githubapp.event.Label;
13+
import io.quarkus.test.QuarkusUnitTest;
14+
15+
public class TokenGitHubClientsInjectionTest {
16+
17+
@RegisterExtension
18+
static final QuarkusUnitTest config = new QuarkusUnitTest()
19+
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
20+
.addClass(ListeningClass.class).addClass(ListeningClass2.class))
21+
.withConfigurationResource("application-token.properties");
22+
23+
@Test
24+
public void testGitHubGraphQLClientInjection() {
25+
}
26+
27+
static class ListeningClass {
28+
29+
@Inject
30+
TokenGitHubClients tokenGitHubClients;
31+
32+
void createLabel(@Label.Created GHEventPayload.Label labelPayload) {
33+
}
34+
}
35+
36+
static class ListeningClass2 {
37+
38+
void createLabel(@Label.Created GHEventPayload.Label labelPayload, TokenGitHubClients tokenGitHubClients) {
39+
}
40+
}
41+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
quarkus.github-app.app-name=quarkus-github-app-test
2+
quarkus.github-app.personal-access-token=ghp_mytoken

docs/modules/ROOT/pages/includes/quarkus-github-app.adoc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,29 @@ endif::add-copy-button-to-env-var[]
241241
|`${quarkus.github-app.instance-endpoint}/graphql`
242242

243243

244+
a| [[quarkus-github-app_quarkus-github-app-personal-access-token]]`link:#quarkus-github-app_quarkus-github-app-personal-access-token[quarkus.github-app.personal-access-token]`
245+
246+
247+
[.description]
248+
--
249+
A personal access token for use with `TokenGitHubClients`.
250+
251+
For standard use cases, you will use the installation client which comes with the installation permissions. It can be injected directly in your method.
252+
253+
However, if your payload comes from a webhook and doesn't have an installation id, it's handy to be able to use a client authenticated with a personal access token as the application client permissions are very limited.
254+
255+
This token will be used to authenticate the clients provided by `TokenGitHubClients`.
256+
257+
ifdef::add-copy-button-to-env-var[]
258+
Environment variable: env_var_with_copy_button:+++QUARKUS_GITHUB_APP_PERSONAL_ACCESS_TOKEN+++[]
259+
endif::add-copy-button-to-env-var[]
260+
ifndef::add-copy-button-to-env-var[]
261+
Environment variable: `+++QUARKUS_GITHUB_APP_PERSONAL_ACCESS_TOKEN+++`
262+
endif::add-copy-button-to-env-var[]
263+
--|string
264+
|
265+
266+
244267
a| [[quarkus-github-app_quarkus-github-app-debug-payload-directory]]`link:#quarkus-github-app_quarkus-github-app-debug-payload-directory[quarkus.github-app.debug.payload-directory]`
245268

246269

integration-tests/app/src/main/java/io/quarkiverse/githubapp/it/app/RawEventListener.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ void testRawEventCatchAllAction(@RawEvent(event = "issues") GitHubEvent gitHubEv
3636
}
3737

3838
void testRawEventCatchAllEventAction(@RawEvent GitHubEvent gitHubEvent, GitHub gitHub) throws IOException {
39-
assert gitHubEvent.getEvent().equals("issues");
40-
assert gitHubEvent.getAction().equals("opened");
39+
assert gitHubEvent.getEvent() != null;
40+
assert gitHubEvent.getAction() != null;
4141

4242
gitHub.getRepository("test/test").getIssue(1).addLabels("testRawEventCatchAllEventAction");
4343
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.quarkiverse.githubapp.it.app;
2+
3+
import java.io.IOException;
4+
5+
import org.kohsuke.github.GitHub;
6+
7+
import io.quarkiverse.githubapp.GitHubEvent;
8+
import io.quarkiverse.githubapp.event.RawEvent;
9+
import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient;
10+
11+
public class RawEventListenerWithoutInstallation {
12+
13+
public static final String EVENT_TYPE = "sponsor";
14+
15+
void testRawEventListenerWithoutInstallation(@RawEvent(event = EVENT_TYPE, action = "sponsored") GitHubEvent gitHubEvent,
16+
GitHub gitHub,
17+
DynamicGraphQLClient graphQLClient) throws IOException {
18+
assert gitHubEvent.getEvent().equals(EVENT_TYPE);
19+
assert gitHubEvent.getAction().equals("sponsored");
20+
21+
assert graphQLClient != null;
22+
assert gitHub != null;
23+
24+
gitHub.getRepository("test/test").getIssue(1).addLabels("testRawEventListenerWithoutInstallation");
25+
}
26+
}

integration-tests/app/src/main/resources/application.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ E0/FAoGATJvuAfgy9uiKR7za7MigYVacE0u4aD1sF7v6D4AFqBOGquPQQhePSdz9\
2828
G/UUwySoo+AQ+rd2EPhyexjqXBhRGe+EDGFVFivaQzTT8/5bt/VddbTcw2IpmXYj\
2929
LW6V8BbcP5MRhd2JQSRh16nWwSQJ2BdpUZFwayEEQ6UcrMfqvA0=\
3030
-----END RSA PRIVATE KEY-----
31+
%test.quarkus.github-app.personal-access-token=ghp_mytoken
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.quarkiverse.githubapp.it.app;
2+
3+
import static io.quarkiverse.githubapp.testing.GitHubAppTesting.given;
4+
import static org.mockito.Mockito.verify;
5+
import static org.mockito.Mockito.when;
6+
7+
import java.io.IOException;
8+
9+
import org.junit.jupiter.api.Test;
10+
11+
import io.quarkiverse.githubapp.testing.GitHubAppTest;
12+
import io.quarkus.test.junit.QuarkusTest;
13+
14+
@QuarkusTest
15+
@GitHubAppTest
16+
public class RawEventWithoutInstallationTest {
17+
18+
@Test
19+
void testRawEventWithoutInstallation() throws IOException {
20+
given().github(mocks -> {
21+
when(mocks.repository("test/test").getIssue(1))
22+
.thenReturn(mocks.issue(1L));
23+
})
24+
.when().payloadFromClasspath("/event-without-installation.json")
25+
.rawEvent(RawEventListenerWithoutInstallation.EVENT_TYPE)
26+
.then().github(mocks -> {
27+
verify(mocks.issue(1))
28+
.addLabels("testRawEventListenerWithoutInstallation");
29+
verify(mocks.issue(1))
30+
.addLabels("testRawEventCatchAllEventAction");
31+
});
32+
}
33+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"action": "sponsored",
3+
"sender": {
4+
"login": "yrodiere",
5+
"id": 412878,
6+
"node_id": "MDQ6VXNlcjQxMjg3OA==",
7+
"avatar_url": "https://avatars1.githubusercontent.com/u/412878?v=4",
8+
"gravatar_id": "",
9+
"url": "https://api.github.com/users/yrodiere",
10+
"html_url": "https://github.com/yrodiere",
11+
"followers_url": "https://api.github.com/users/yrodiere/followers",
12+
"following_url": "https://api.github.com/users/yrodiere/following{/other_user}",
13+
"gists_url": "https://api.github.com/users/yrodiere/gists{/gist_id}",
14+
"starred_url": "https://api.github.com/users/yrodiere/starred{/owner}{/repo}",
15+
"subscriptions_url": "https://api.github.com/users/yrodiere/subscriptions",
16+
"organizations_url": "https://api.github.com/users/yrodiere/orgs",
17+
"repos_url": "https://api.github.com/users/yrodiere/repos",
18+
"events_url": "https://api.github.com/users/yrodiere/events{/privacy}",
19+
"received_events_url": "https://api.github.com/users/yrodiere/received_events",
20+
"type": "User",
21+
"site_admin": false
22+
}
23+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.quarkiverse.githubapp;
2+
3+
import jakarta.inject.Singleton;
4+
5+
import org.kohsuke.github.GitHub;
6+
7+
import io.quarkiverse.githubapp.runtime.github.GitHubService;
8+
import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient;
9+
10+
@Singleton
11+
public class TokenGitHubClients {
12+
13+
private final GitHubService gitHubService;
14+
15+
TokenGitHubClients(GitHubService gitHubService) {
16+
this.gitHubService = gitHubService;
17+
}
18+
19+
public GitHub getRestClient() {
20+
return gitHubService.getTokenRestClient();
21+
}
22+
23+
public DynamicGraphQLClient getGraphQLClient() {
24+
return gitHubService.getTokenGraphQLClient();
25+
}
26+
}

0 commit comments

Comments
 (0)