Skip to content

Commit bd2d653

Browse files
authored
Merge pull request #631 from ryandens/ryandens/configurable-route-path
Allow the webhook listener path to be configurable
2 parents 6d6832e + c09bb16 commit bd2d653

File tree

6 files changed

+83
-14
lines changed

6 files changed

+83
-14
lines changed

docs/modules/ROOT/pages/includes/attributes.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
:quarkus-version: 3.9.2
1+
:quarkus-version: 3.11.2
22
:quarkus-github-app-version: 2.5.1
33

44
:github-api-javadoc-root-url: https://github-api.kohsuke.org/apidocs/org/kohsuke/github

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,25 @@ endif::add-copy-button-to-env-var[]
8585
|
8686

8787

88+
a| [[quarkus-github-app_quarkus-github-app-webhook-url-path]]`link:#quarkus-github-app_quarkus-github-app-webhook-url-path[quarkus.github-app.webhook-url-path]`
89+
90+
91+
[.description]
92+
--
93+
The webhook URL path on which the GitHub App route is mounted.
94+
95+
It defaults to the root `/` but it can be configured to another path such as `/github-events` to enable deployment alongside other HTTP routes.
96+
97+
ifdef::add-copy-button-to-env-var[]
98+
Environment variable: env_var_with_copy_button:+++QUARKUS_GITHUB_APP_WEBHOOK_URL_PATH+++[]
99+
endif::add-copy-button-to-env-var[]
100+
ifndef::add-copy-button-to-env-var[]
101+
Environment variable: `+++QUARKUS_GITHUB_APP_WEBHOOK_URL_PATH+++`
102+
endif::add-copy-button-to-env-var[]
103+
--|string
104+
|`/`
105+
106+
88107
a| [[quarkus-github-app_quarkus-github-app-webhook-secret]]`link:#quarkus-github-app_quarkus-github-app-webhook-secret[quarkus.github-app.webhook-secret]`
89108

90109

runtime/src/main/java/io/quarkiverse/githubapp/runtime/Routes.java

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,13 @@
2828
import io.quarkus.runtime.LaunchMode;
2929
import io.quarkus.runtime.StartupEvent;
3030
import io.quarkus.vertx.http.runtime.HttpConfiguration;
31-
import io.quarkus.vertx.web.Header;
32-
import io.quarkus.vertx.web.Route;
33-
import io.quarkus.vertx.web.Route.HandlerType;
34-
import io.quarkus.vertx.web.Route.HttpMethod;
3531
import io.quarkus.vertx.web.RoutingExchange;
32+
import io.quarkus.vertx.web.runtime.RoutingExchangeImpl;
3633
import io.vertx.core.json.Json;
3734
import io.vertx.core.json.JsonObject;
35+
import io.vertx.ext.web.Router;
3836
import io.vertx.ext.web.RoutingContext;
37+
import io.vertx.ext.web.handler.BodyHandler;
3938

4039
@Singleton
4140
public class Routes {
@@ -72,14 +71,28 @@ public void init(@Observes StartupEvent startupEvent) throws IOException {
7271
}
7372
}
7473

75-
@Route(path = "/", type = HandlerType.BLOCKING, methods = HttpMethod.POST, consumes = "application/json", produces = "application/json")
76-
public void handleRequest(RoutingContext routingContext,
74+
public void init(@Observes Router router) {
75+
router.post(checkedConfigProvider.webhookUrlPath())
76+
.handler(BodyHandler.create()) // this is required so that the body to be read by subsequent handlers
77+
.blockingHandler(routingContext -> {
78+
handleRequest(
79+
routingContext,
80+
new RoutingExchangeImpl(routingContext),
81+
routingContext.request().getHeader(X_REQUEST_ID),
82+
routingContext.request().getHeader(X_HUB_SIGNATURE_256),
83+
routingContext.request().getHeader(X_GITHUB_DELIVERY),
84+
routingContext.request().getHeader(X_GITHUB_EVENT),
85+
routingContext.request().getHeader(X_QUARKIVERSE_GITHUB_APP_REPLAYED));
86+
});
87+
}
88+
89+
private void handleRequest(RoutingContext routingContext,
7790
RoutingExchange routingExchange,
78-
@Header(X_REQUEST_ID) String requestId,
79-
@Header(X_HUB_SIGNATURE_256) String hubSignature,
80-
@Header(X_GITHUB_DELIVERY) String deliveryId,
81-
@Header(X_GITHUB_EVENT) String event,
82-
@Header(X_QUARKIVERSE_GITHUB_APP_REPLAYED) String replayed) throws IOException {
91+
String requestId,
92+
String hubSignature,
93+
String deliveryId,
94+
String event,
95+
String replayed) {
8396

8497
if (!launchMode.isDevOrTest() && (isBlank(deliveryId) || isBlank(hubSignature))) {
8598
routingExchange.response().setStatusCode(400).end();
@@ -118,7 +131,12 @@ public void handleRequest(RoutingContext routingContext,
118131
if (!isBlank(deliveryId) && checkedConfigProvider.debug().payloadDirectory.isPresent()) {
119132
String fileName = DATE_TIME_FORMATTER.format(LocalDateTime.now()) + "-" + event + "-"
120133
+ (!isBlank(action) ? action + "-" : "") + deliveryId + ".json";
121-
Files.write(checkedConfigProvider.debug().payloadDirectory.get().resolve(fileName), bodyBytes);
134+
Path path = checkedConfigProvider.debug().payloadDirectory.get().resolve(fileName);
135+
try {
136+
Files.write(path, bodyBytes);
137+
} catch (Exception e) {
138+
LOG.warnf(e, "Unable to write debug payload: %s", path);
139+
}
122140
}
123141

124142
Long installationId = extractInstallationId(payloadObject);

runtime/src/main/java/io/quarkiverse/githubapp/runtime/config/CheckedConfigProvider.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class CheckedConfigProvider {
3333

3434
private final Optional<PrivateKey> privateKey;
3535
private final Optional<String> webhookSecret;
36+
private final String webhookUrlPath;
3637

3738
private final Set<String> missingPropertyKeys = new TreeSet<>();
3839

@@ -54,6 +55,8 @@ public class CheckedConfigProvider {
5455
} else {
5556
this.webhookSecret = gitHubAppRuntimeConfig.webhookSecret;
5657
}
58+
this.webhookUrlPath = gitHubAppRuntimeConfig.webhookUrlPath.startsWith("/") ? gitHubAppRuntimeConfig.webhookUrlPath
59+
: "/" + gitHubAppRuntimeConfig.webhookUrlPath;
5760

5861
if (gitHubAppRuntimeConfig.appId.isEmpty()) {
5962
missingPropertyKeys.add("quarkus.github-app.app-id (.env: QUARKUS_GITHUB_APP_APP_ID)");
@@ -108,6 +111,10 @@ public String restApiEndpoint() {
108111
return gitHubAppRuntimeConfig.restApiEndpoint;
109112
}
110113

114+
public String webhookUrlPath() {
115+
return webhookUrlPath;
116+
}
117+
111118
public String graphqlApiEndpoint() {
112119
return gitHubAppRuntimeConfig.graphqlApiEndpoint;
113120
}

runtime/src/main/java/io/quarkiverse/githubapp/runtime/config/GitHubAppRuntimeConfig.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class GitHubAppRuntimeConfig {
2121
* Optional for tests, but mandatory in production and dev mode.
2222
*/
2323
@ConfigItem
24+
@ConvertWith(TrimmedStringConverter.class)
2425
Optional<String> appId;
2526

2627
/**
@@ -29,6 +30,7 @@ public class GitHubAppRuntimeConfig {
2930
* Optional, only used for improving the user experience.
3031
*/
3132
@ConfigItem
33+
@ConvertWith(TrimmedStringConverter.class)
3234
Optional<String> appName;
3335

3436
/**
@@ -46,6 +48,16 @@ public class GitHubAppRuntimeConfig {
4648
@ConvertWith(PrivateKeyConverter.class)
4749
Optional<PrivateKey> privateKey;
4850

51+
/**
52+
* The webhook URL path on which the GitHub App route is mounted.
53+
* <p>
54+
* It defaults to the root {@code /} but it can be configured to another path such as {@code /github-events} to enable
55+
* deployment alongside other HTTP routes.
56+
*/
57+
@ConfigItem(defaultValue = "/")
58+
@ConvertWith(TrimmedStringConverter.class)
59+
String webhookUrlPath;
60+
4961
/**
5062
* The webhook secret if defined in the GitHub UI.
5163
*/
@@ -80,6 +92,7 @@ public class GitHubAppRuntimeConfig {
8092
* The Smee.io proxy URL used when testing locally.
8193
*/
8294
@ConfigItem
95+
@ConvertWith(TrimmedStringConverter.class)
8396
Optional<String> webhookProxyUrl;
8497

8598
/**
@@ -88,6 +101,7 @@ public class GitHubAppRuntimeConfig {
88101
* Defaults to the public github.com instance.
89102
*/
90103
@ConfigItem(defaultValue = "https://api.github.com")
104+
@ConvertWith(TrimmedStringConverter.class)
91105
String instanceEndpoint;
92106

93107
/**
@@ -96,6 +110,7 @@ public class GitHubAppRuntimeConfig {
96110
* Defaults to the public github.com instance REST API endpoint.
97111
*/
98112
@ConfigItem(defaultValue = "${quarkus.github-app.instance-endpoint}")
113+
@ConvertWith(TrimmedStringConverter.class)
99114
String restApiEndpoint;
100115

101116
/**
@@ -104,6 +119,7 @@ public class GitHubAppRuntimeConfig {
104119
* Defaults to the public github.com instance GraphQL endpoint.
105120
*/
106121
@ConfigItem(defaultValue = "${quarkus.github-app.instance-endpoint}/graphql")
122+
@ConvertWith(TrimmedStringConverter.class)
107123
String graphqlApiEndpoint;
108124

109125
/**
@@ -119,6 +135,7 @@ public static class Debug {
119135
* A directory in which the payloads are saved.
120136
*/
121137
@ConfigItem
138+
@ConvertWith(TrimmedStringConverter.class)
122139
public Optional<Path> payloadDirectory;
123140
}
124141
}

runtime/src/main/java/io/quarkiverse/githubapp/runtime/smee/SmeeIoForwarder.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static io.quarkiverse.githubapp.runtime.Headers.FORWARDED_HEADERS;
44

5+
import java.io.IOException;
56
import java.net.URI;
67
import java.net.http.HttpClient;
78
import java.net.http.HttpRequest;
@@ -50,7 +51,8 @@ public class SmeeIoForwarder {
5051

5152
LOG.info("Listening to events coming from " + checkedConfigProvider.webhookProxyUrl().get());
5253

53-
URI localUrl = URI.create("http://" + httpConfiguration.host + ":" + httpConfiguration.port + "/");
54+
URI localUrl = URI.create(
55+
"http://" + httpConfiguration.host + ":" + httpConfiguration.port + checkedConfigProvider.webhookUrlPath());
5456

5557
this.replayEventStreamAdapter = new ReplayEventStreamAdapter(checkedConfigProvider.webhookProxyUrl().get(), localUrl,
5658
objectMapper);
@@ -123,6 +125,12 @@ public void onEvent(HttpEventStreamClient client, Event event) {
123125

124126
forwardingHttpClient.send(requestBuilder.build(), BodyHandlers.discarding());
125127
}
128+
} catch (IOException e) {
129+
if (e.getMessage().contains("GOAWAY received")) {
130+
// ignore
131+
} else {
132+
LOG.error("An error occurred while forwarding a payload to the local application running in dev mode", e);
133+
}
126134
} catch (Exception e) {
127135
LOG.error("An error occurred while forwarding a payload to the local application running in dev mode", e);
128136
}

0 commit comments

Comments
 (0)