Skip to content
Snippets Groups Projects
Commit 0bb12164 authored by Martin Lowe's avatar Martin Lowe :flag_ca:
Browse files

Iss #161 - Add merge_group webhook support to the webhook callback

A commit status is required for merge queues as separate branches w/
different SHAs are created, which no longer contain the ECA check
status. As all commits in the queue will have been validated, we can
just send a request to create the missing commit status without checks.
parent 10346317
No related branches found
No related tags found
1 merge request!197Iss #161 - Add merge_group webhook support to the webhook callback
Pipeline #50454 passed
...@@ -15,14 +15,17 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; ...@@ -15,14 +15,17 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import jakarta.annotation.Nullable;
/** /**
* @author martin * @author Martin Lowe
* *
*/ */
@AutoValue @AutoValue
@JsonDeserialize(builder = AutoValue_GithubCommitStatusRequest.Builder.class) @JsonDeserialize(builder = AutoValue_GithubCommitStatusRequest.Builder.class)
public abstract class GithubCommitStatusRequest { public abstract class GithubCommitStatusRequest {
public abstract String getState(); public abstract String getState();
@Nullable
public abstract String getTargetUrl(); public abstract String getTargetUrl();
public abstract String getDescription(); public abstract String getDescription();
public abstract String getContext(); public abstract String getContext();
...@@ -35,7 +38,7 @@ public abstract class GithubCommitStatusRequest { ...@@ -35,7 +38,7 @@ public abstract class GithubCommitStatusRequest {
@JsonPOJOBuilder(withPrefix = "set") @JsonPOJOBuilder(withPrefix = "set")
public abstract static class Builder { public abstract static class Builder {
public abstract Builder setState(String state); public abstract Builder setState(String state);
public abstract Builder setTargetUrl(String targetUrl); public abstract Builder setTargetUrl(@Nullable String targetUrl);
public abstract Builder setDescription(String description); public abstract Builder setDescription(String description);
public abstract Builder setContext(String context); public abstract Builder setContext(String context);
......
...@@ -36,6 +36,9 @@ public abstract class GithubWebhookRequest { ...@@ -36,6 +36,9 @@ public abstract class GithubWebhookRequest {
@Nullable @Nullable
public abstract PullRequest getPullRequest(); public abstract PullRequest getPullRequest();
@Nullable
public abstract MergeGroup getMergeGroup();
/** /**
* Generate basic builder with default properties for constructing a webhook request. * Generate basic builder with default properties for constructing a webhook request.
* *
...@@ -78,6 +81,8 @@ public abstract class GithubWebhookRequest { ...@@ -78,6 +81,8 @@ public abstract class GithubWebhookRequest {
public abstract Builder setPullRequest(@Nullable PullRequest pullRequest); public abstract Builder setPullRequest(@Nullable PullRequest pullRequest);
public abstract Builder setMergeGroup(@Nullable MergeGroup mergeGroup);
public abstract GithubWebhookRequest build(); public abstract GithubWebhookRequest build();
} }
...@@ -150,6 +155,25 @@ public abstract class GithubWebhookRequest { ...@@ -150,6 +155,25 @@ public abstract class GithubWebhookRequest {
} }
} }
@AutoValue
@JsonDeserialize(builder = AutoValue_GithubWebhookRequest_MergeGroup.Builder.class)
public abstract static class MergeGroup {
public abstract String getHeadSha();
public static Builder builder() {
return new AutoValue_GithubWebhookRequest_MergeGroup.Builder();
}
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "set")
public abstract static class Builder {
public abstract Builder setHeadSha(String headSha);
public abstract MergeGroup build();
}
}
@AutoValue @AutoValue
@JsonDeserialize(builder = AutoValue_GithubWebhookRequest_Installation.Builder.class) @JsonDeserialize(builder = AutoValue_GithubWebhookRequest_Installation.Builder.class)
public abstract static class Installation { public abstract static class Installation {
......
...@@ -60,8 +60,8 @@ import jakarta.ws.rs.ServerErrorException; ...@@ -60,8 +60,8 @@ import jakarta.ws.rs.ServerErrorException;
import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.MultivaluedMap;
/** /**
* This class is used to adapt Github requests to the standard validation workflow in a way that could be reused by both * This class is used to adapt Github requests to the standard validation workflow in a way that could be reused by both resource calls and
* resource calls and scheduled tasks for revalidation. * scheduled tasks for revalidation.
*/ */
@ApplicationScoped @ApplicationScoped
public class GithubValidationHelper { public class GithubValidationHelper {
...@@ -95,16 +95,15 @@ public class GithubValidationHelper { ...@@ -95,16 +95,15 @@ public class GithubValidationHelper {
GithubAPI ghApi; GithubAPI ghApi;
/** /**
* Using the wrapper and the passed unique information about a Github validation instance, lookup or create the tracking * Using the wrapper and the passed unique information about a Github validation instance, lookup or create the tracking request and
* request and validate the data. * validate the data.
* *
* @param wrapper the wrapper for the current request * @param wrapper the wrapper for the current request
* @param org the name of the GH organization * @param org the name of the GH organization
* @param repoName the slug of the repo that has the PR to be validated * @param repoName the slug of the repo that has the PR to be validated
* @param prNo the PR number for the current request. * @param prNo the PR number for the current request.
* @param forceRevalidation true if revalidation should be forced when there is no changes, false otherwise. * @param forceRevalidation true if revalidation should be forced when there is no changes, false otherwise.
* @return the validated response if it is a valid request, or throws a web exception if there is a problem validating * @return the validated response if it is a valid request, or throws a web exception if there is a problem validating the request.
* the request.
*/ */
public ValidationResponse validateIncomingRequest(RequestWrapper wrapper, String org, String repoName, Integer prNo, public ValidationResponse validateIncomingRequest(RequestWrapper wrapper, String org, String repoName, Integer prNo,
boolean forceRevalidation) { boolean forceRevalidation) {
...@@ -158,8 +157,8 @@ public class GithubValidationHelper { ...@@ -158,8 +157,8 @@ public class GithubValidationHelper {
} }
/** /**
* Generate a ValidationRequest object based on data pulled from Github, grabbing commits from the noted pull request * Generate a ValidationRequest object based on data pulled from Github, grabbing commits from the noted pull request using the
* using the installation ID for access/authorization. * installation ID for access/authorization.
* *
* @param installationId the ECA app installation ID for the organization * @param installationId the ECA app installation ID for the organization
* @param repositoryFullName the full name of the repository where the PR resides * @param repositoryFullName the full name of the repository where the PR resides
...@@ -212,13 +211,35 @@ public class GithubValidationHelper { ...@@ -212,13 +211,35 @@ public class GithubValidationHelper {
} }
/** /**
* Process the current request and update the checks state to pending then success or failure. Contains verbose TRACE * Process the current 'merge_group' request and create a commit status for the HEAD SHA. As commits need to pass branch rules including
* logging for more info on the states of the validation for more information * the existing ECA check, we don't need to check the commits here.
* *
* @param request information about the request from the GH webhook on what resource requested revalidation. Used to * @param request information about the request from the GH webhook on what resource requested revalidation. Used to target the commit
* target the commit status of the resources * status of the resources
* @param vr the pseudo request generated from the contextual webhook data. Used to make use of existing validation */
* logic. public void sendMergeQueueStatus(GithubWebhookRequest request) {
if (request.getMergeGroup() == null) {
throw new BadRequestException("Merge group object required in webhook request to send status for merge queue");
}
// send the success status for the head SHA
ghApi
.updateStatus(jwtHelper.getGhBearerString(request.getInstallation().getId()), apiVersion,
request.getRepository().getFullName(), request.getMergeGroup().getHeadSha(),
GithubCommitStatusRequest
.builder()
.setDescription("Commits in merge group should be previously validated, auto-passing HEAD commit")
.setState(GithubCommitStatuses.SUCCESS.toString())
.setContext(webhooksConfig.github().context())
.build());
}
/**
* Process the current request and update the checks state to pending then success or failure. Contains verbose TRACE logging for more
* info on the states of the validation for more information
*
* @param request information about the request from the GH webhook on what resource requested revalidation. Used to target the commit
* status of the resources
* @param vr the pseudo request generated from the contextual webhook data. Used to make use of existing validation logic.
* @return true if the validation passed, false otherwise. * @return true if the validation passed, false otherwise.
*/ */
public ValidationResponse handleGithubWebhookValidation(GithubWebhookRequest request, ValidationRequest vr, RequestWrapper wrapper) { public ValidationResponse handleGithubWebhookValidation(GithubWebhookRequest request, ValidationRequest vr, RequestWrapper wrapper) {
...@@ -246,8 +267,8 @@ public class GithubValidationHelper { ...@@ -246,8 +267,8 @@ public class GithubValidationHelper {
} }
/** /**
* Shortcut method that will retrieve existing GH tracking info, create new entries if missing, and will update the * Shortcut method that will retrieve existing GH tracking info, create new entries if missing, and will update the state of existing
* state of existing requests as well. * requests as well.
* *
* @param installationId the installation ID for the ECA app in the given repository * @param installationId the installation ID for the ECA app in the given repository
* @param repositoryFullName the full repository name for the target repo, e.g. eclipse/jetty * @param repositoryFullName the full repository name for the target repo, e.g. eclipse/jetty
...@@ -262,8 +283,7 @@ public class GithubValidationHelper { ...@@ -262,8 +283,7 @@ public class GithubValidationHelper {
} }
/** /**
* Checks if the Github tracking is present for the current request, and if missing will generate a new record and save * Checks if the Github tracking is present for the current request, and if missing will generate a new record and save it.
* it.
* *
* @param tracking the optional tracking entry for the current request * @param tracking the optional tracking entry for the current request
* @param request the pull request that is being validated * @param request the pull request that is being validated
......
...@@ -67,9 +67,9 @@ public class GithubWebhooksResource extends CommonResource { ...@@ -67,9 +67,9 @@ public class GithubWebhooksResource extends CommonResource {
/** /**
* Entry point for processing Github webhook requests. Accepts standard fields as described in the <a href= * Entry point for processing Github webhook requests. Accepts standard fields as described in the <a href=
* "https://docs.github.com/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#webhook-payload-object-common-properties">Webhook * "https://docs.github.com/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#webhook-payload-object-common-properties">Webhook
* properties documentation</a>, as well as the <a href= * properties documentation</a>, as well as the
* "https://docs.github.com/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request">Pull * <a href= "https://docs.github.com/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request">Pull Request
* Request event type documentation.</a> * event type documentation.</a>
* *
* @param deliveryId Github provided unique delivery ID * @param deliveryId Github provided unique delivery ID
* @param eventType the type of webhook event that was fired * @param eventType the type of webhook event that was fired
...@@ -79,32 +79,33 @@ public class GithubWebhooksResource extends CommonResource { ...@@ -79,32 +79,33 @@ public class GithubWebhooksResource extends CommonResource {
@POST @POST
public Response processGithubWebhook(@HeaderParam(WebhookHeaders.GITHUB_DELIVERY) String deliveryId, public Response processGithubWebhook(@HeaderParam(WebhookHeaders.GITHUB_DELIVERY) String deliveryId,
@HeaderParam(WebhookHeaders.GITHUB_EVENT) String eventType, GithubWebhookRequest request) { @HeaderParam(WebhookHeaders.GITHUB_EVENT) String eventType, GithubWebhookRequest request) {
// If event isn't a pr event, drop as we don't process them // If event isn't a pr or merge_group event, drop as we don't process them
if (!"pull_request".equalsIgnoreCase(eventType)) { if ("merge_group".equalsIgnoreCase(eventType)) {
return Response.ok().build(); // if we're in a merge_group, the HEAD SHA should already be validated, so we don't need to check again
} validationHelper.sendMergeQueueStatus(request);
} else if ("pull_request".equalsIgnoreCase(eventType)) {
// check that the pull request isn't somehow missing // check that the pull request isn't somehow missing
if (request.getPullRequest() == null) { if (request.getPullRequest() == null) {
throw new BadRequestException("Pull request event submitted, but pull request information was missing"); throw new BadRequestException("Pull request event submitted, but pull request information was missing");
} }
PullRequest pr = request.getPullRequest(); PullRequest pr = request.getPullRequest();
// track the request before we start processing // track the request before we start processing
GithubWebhookTracking tracking = validationHelper GithubWebhookTracking tracking = validationHelper
.retrieveAndUpdateTrackingInformation(wrapper, request.getInstallation().getId(), request.getRepository().getFullName(), .retrieveAndUpdateTrackingInformation(wrapper, request.getInstallation().getId(), request.getRepository().getFullName(),
pr); pr);
// start processing the request // start processing the request
LOGGER.trace("Processing PR event for install {} with delivery ID of {}", request.getInstallation().getId(), deliveryId); LOGGER.trace("Processing PR event for install {} with delivery ID of {}", request.getInstallation().getId(), deliveryId);
try { try {
// prepare for validation process by pre-processing into standard format // prepare for validation process by pre-processing into standard format
ValidationRequest vr = generateRequest(request, pr); ValidationRequest vr = generateRequest(request, pr);
// process the request // process the request
validationHelper.handleGithubWebhookValidation(request, vr, wrapper); validationHelper.handleGithubWebhookValidation(request, vr, wrapper);
} catch (Exception e) { } catch (Exception e) {
// set the revalidation flag to reprocess if the initial attempt fails before exiting // set the revalidation flag to reprocess if the initial attempt fails before exiting
setRevalidationFlagForTracking(tracking); setRevalidationFlagForTracking(tracking);
}
} }
return Response.ok().build(); return Response.ok().build();
...@@ -118,8 +119,8 @@ public class GithubWebhooksResource extends CommonResource { ...@@ -118,8 +119,8 @@ public class GithubWebhooksResource extends CommonResource {
} }
/** /**
* Endpoint for triggering revalidation of a past request. Uses the fingerprint hash to lookup the unique request and to * Endpoint for triggering revalidation of a past request. Uses the fingerprint hash to lookup the unique request and to rebuild the
* rebuild the request and revalidate if it exists. * request and revalidate if it exists.
* *
* @param fullRepoName the full repository name for the target repo, e.g. eclipse/jetty * @param fullRepoName the full repository name for the target repo, e.g. eclipse/jetty
* @param installationId the installation ID for the ECA app in the given repository * @param installationId the installation ID for the ECA app in the given repository
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment