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

Merge branch 'malowe/main/161' into 'main'

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

See merge request !197
parents 10346317 14bcb056
No related branches found
No related tags found
1 merge request!197Iss #161 - Add merge_group webhook support to the webhook callback
Pipeline #50616 passed
......@@ -15,14 +15,17 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.google.auto.value.AutoValue;
import jakarta.annotation.Nullable;
/**
* @author martin
* @author Martin Lowe
*
*/
@AutoValue
@JsonDeserialize(builder = AutoValue_GithubCommitStatusRequest.Builder.class)
public abstract class GithubCommitStatusRequest {
public abstract String getState();
@Nullable
public abstract String getTargetUrl();
public abstract String getDescription();
public abstract String getContext();
......@@ -35,7 +38,7 @@ public abstract class GithubCommitStatusRequest {
@JsonPOJOBuilder(withPrefix = "set")
public abstract static class Builder {
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 setContext(String context);
......
......@@ -36,6 +36,9 @@ public abstract class GithubWebhookRequest {
@Nullable
public abstract PullRequest getPullRequest();
@Nullable
public abstract MergeGroup getMergeGroup();
/**
* Generate basic builder with default properties for constructing a webhook request.
*
......@@ -78,6 +81,8 @@ public abstract class GithubWebhookRequest {
public abstract Builder setPullRequest(@Nullable PullRequest pullRequest);
public abstract Builder setMergeGroup(@Nullable MergeGroup mergeGroup);
public abstract GithubWebhookRequest build();
}
......@@ -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
@JsonDeserialize(builder = AutoValue_GithubWebhookRequest_Installation.Builder.class)
public abstract static class Installation {
......
......@@ -60,8 +60,8 @@ import jakarta.ws.rs.ServerErrorException;
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
* resource calls and scheduled tasks for revalidation.
* 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
* scheduled tasks for revalidation.
*/
@ApplicationScoped
public class GithubValidationHelper {
......@@ -95,16 +95,15 @@ public class GithubValidationHelper {
GithubAPI ghApi;
/**
* Using the wrapper and the passed unique information about a Github validation instance, lookup or create the tracking
* request and validate the data.
* Using the wrapper and the passed unique information about a Github validation instance, lookup or create the tracking request and
* validate the data.
*
* @param wrapper the wrapper for the current request
* @param org the name of the GH organization
* @param repoName the slug of the repo that has the PR to be validated
* @param prNo the PR number for the current request.
* @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
* the request.
* @return the validated response if it is a valid request, or throws a web exception if there is a problem validating the request.
*/
public ValidationResponse validateIncomingRequest(RequestWrapper wrapper, String org, String repoName, Integer prNo,
boolean forceRevalidation) {
......@@ -158,8 +157,8 @@ public class GithubValidationHelper {
}
/**
* Generate a ValidationRequest object based on data pulled from Github, grabbing commits from the noted pull request
* using the installation ID for access/authorization.
* Generate a ValidationRequest object based on data pulled from Github, grabbing commits from the noted pull request using the
* installation ID for access/authorization.
*
* @param installationId the ECA app installation ID for the organization
* @param repositoryFullName the full name of the repository where the PR resides
......@@ -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
* logging for more info on the states of the validation for more information
* Process the current 'merge_group' request and create a commit status for the HEAD SHA. As commits need to pass branch rules including
* 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
* 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.
* @param request information about the request from the GH webhook on what resource requested revalidation. Used to target the commit
* status of the resources
*/
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.
*/
public ValidationResponse handleGithubWebhookValidation(GithubWebhookRequest request, ValidationRequest vr, RequestWrapper wrapper) {
......@@ -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
* state of existing requests as well.
* Shortcut method that will retrieve existing GH tracking info, create new entries if missing, and will update the state of existing
* requests as well.
*
* @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
......@@ -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
* it.
* Checks if the Github tracking is present for the current request, and if missing will generate a new record and save it.
*
* @param tracking the optional tracking entry for the current request
* @param request the pull request that is being validated
......
......@@ -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=
* "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=
* "https://docs.github.com/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request">Pull
* Request event type documentation.</a>
* properties documentation</a>, as well as the
* <a href= "https://docs.github.com/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request">Pull Request
* event type documentation.</a>
*
* @param deliveryId Github provided unique delivery ID
* @param eventType the type of webhook event that was fired
......@@ -79,32 +79,33 @@ public class GithubWebhooksResource extends CommonResource {
@POST
public Response processGithubWebhook(@HeaderParam(WebhookHeaders.GITHUB_DELIVERY) String deliveryId,
@HeaderParam(WebhookHeaders.GITHUB_EVENT) String eventType, GithubWebhookRequest request) {
// If event isn't a pr event, drop as we don't process them
if (!"pull_request".equalsIgnoreCase(eventType)) {
return Response.ok().build();
}
// check that the pull request isn't somehow missing
if (request.getPullRequest() == null) {
throw new BadRequestException("Pull request event submitted, but pull request information was missing");
}
PullRequest pr = request.getPullRequest();
// track the request before we start processing
GithubWebhookTracking tracking = validationHelper
.retrieveAndUpdateTrackingInformation(wrapper, request.getInstallation().getId(), request.getRepository().getFullName(),
pr);
// start processing the request
LOGGER.trace("Processing PR event for install {} with delivery ID of {}", request.getInstallation().getId(), deliveryId);
try {
// prepare for validation process by pre-processing into standard format
ValidationRequest vr = generateRequest(request, pr);
// process the request
validationHelper.handleGithubWebhookValidation(request, vr, wrapper);
} catch (Exception e) {
// set the revalidation flag to reprocess if the initial attempt fails before exiting
setRevalidationFlagForTracking(tracking);
// If event isn't a pr or merge_group event, drop as we don't process them
if ("merge_group".equalsIgnoreCase(eventType)) {
// 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
if (request.getPullRequest() == null) {
throw new BadRequestException("Pull request event submitted, but pull request information was missing");
}
PullRequest pr = request.getPullRequest();
// track the request before we start processing
GithubWebhookTracking tracking = validationHelper
.retrieveAndUpdateTrackingInformation(wrapper, request.getInstallation().getId(), request.getRepository().getFullName(),
pr);
// start processing the request
LOGGER.trace("Processing PR event for install {} with delivery ID of {}", request.getInstallation().getId(), deliveryId);
try {
// prepare for validation process by pre-processing into standard format
ValidationRequest vr = generateRequest(request, pr);
// process the request
validationHelper.handleGithubWebhookValidation(request, vr, wrapper);
} catch (Exception e) {
// set the revalidation flag to reprocess if the initial attempt fails before exiting
setRevalidationFlagForTracking(tracking);
}
}
return Response.ok().build();
......@@ -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
* rebuild the request and revalidate if it exists.
* Endpoint for triggering revalidation of a past request. Uses the fingerprint hash to lookup the unique request and to rebuild the
* request and revalidate if it exists.
*
* @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
......
......@@ -16,6 +16,7 @@ import java.util.Optional;
import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest;
import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.Installation;
import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.MergeGroup;
import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.PullRequest;
import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.PullRequestHead;
import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.Repository;
......@@ -55,6 +56,17 @@ class GithubWebhooksResourceTest {
.run();
}
@Test
void testGHWebhook_success_mergeGroup() {
EndpointTestBuilder
.from(TestCaseHelper
.prepareTestCase(GH_WEBHOOK_BASE_URL, new String[] {}, null)
.setHeaderParams(Optional.of(Map.of(WebhookHeaders.GITHUB_DELIVERY, "id-1", WebhookHeaders.GITHUB_EVENT, "merge_group")))
.build())
.doPost(createMergeGroupWebhookBody())
.run();
}
private String createGHWebhook() {
try {
return om.writeValueAsString(GithubWebhookRequest.builder()
......@@ -75,4 +87,23 @@ class GithubWebhooksResourceTest {
throw new ApplicationException("Error converting Hook to JSON");
}
}
private String createMergeGroupWebhookBody() {
try {
return om.writeValueAsString(GithubWebhookRequest.builder()
.setInstallation(Installation.builder().setId("install-id").build())
.setMergeGroup(MergeGroup
.builder()
.setHeadSha("headsha-123")
.build())
.setRepository(Repository
.builder()
.setFullName("eclipsefdn/sample")
.setHtmlUrl("http://www.github.com/eclipsefdn/sample")
.build())
.build());
} catch (Exception e) {
throw new ApplicationException("Error converting Hook to JSON");
}
}
}
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