diff --git a/config/mariadb/initdb.d/init.sql b/config/mariadb/initdb.d/init.sql index a99c760ec6f197cff457e006af6353a91b1d1635..2a6abea6219dc978d7561e8eb845a69a6505154a 100644 --- a/config/mariadb/initdb.d/init.sql +++ b/config/mariadb/initdb.d/init.sql @@ -41,9 +41,8 @@ ALTER TABLE CommitValidationMessage ADD COLUMN IF NOT EXISTS `committerEmail` va CREATE TABLE GithubWebhookTracking ( id SERIAL NOT NULL, - fingerprint varchar(127) NOT NULL, installationId varchar(63) NOT NULL, - deliveryId varchar(63) NOT NULL, + lastKnownState varchar(63) NOT NULL, headSha varchar(63) NOT NULL, repositoryFullName varchar(127) NOT NULL, pullRequestNumber int NOT NULL, diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubWebhookRequest.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubWebhookRequest.java index 5bf0d7dbc72c80ed7eda7d74b7918c7227831f0b..7ec821ac4ff140f110eda9153b24e9a8003fac55 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubWebhookRequest.java +++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubWebhookRequest.java @@ -55,6 +55,7 @@ public abstract class GithubWebhookRequest { .builder() .setNumber(tracking.getPullRequestNumber()) .setHead(PullRequestHead.builder().setSha(tracking.getHeadSha()).build()) + .setState(tracking.getLastKnownState()) .build()) .setRepository(Repository .builder() diff --git a/src/main/java/org/eclipsefoundation/git/eca/dto/GithubWebhookTracking.java b/src/main/java/org/eclipsefoundation/git/eca/dto/GithubWebhookTracking.java index e1c5ebe1280cd2039e473407e4d44db3a778dfa8..72cb98ceac7b3941ca8df976b2ad6262fc067f7d 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/dto/GithubWebhookTracking.java +++ b/src/main/java/org/eclipsefoundation/git/eca/dto/GithubWebhookTracking.java @@ -46,12 +46,11 @@ public class GithubWebhookTracking extends BareNode { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private String fingerprint; private String installationId; - private String deliveryId; private String repositoryFullName; private String headSha; private Integer pullRequestNumber; + private String lastKnownState; private ZonedDateTime lastUpdated; @Override @@ -66,20 +65,6 @@ public class GithubWebhookTracking extends BareNode { this.id = id; } - /** - * @return the fingerprint - */ - public String getFingerprint() { - return fingerprint; - } - - /** - * @param fingerprint the fingerprint to set - */ - public void setFingerprint(String fingerprint) { - this.fingerprint = fingerprint; - } - /** * @return the installationId */ @@ -94,20 +79,6 @@ public class GithubWebhookTracking extends BareNode { this.installationId = installationId; } - /** - * @return the deliveryId - */ - public String getDeliveryId() { - return deliveryId; - } - - /** - * @param deliveryId the deliveryId to set - */ - public void setDeliveryId(String deliveryId) { - this.deliveryId = deliveryId; - } - /** * @return the repositoryFullName */ @@ -150,6 +121,20 @@ public class GithubWebhookTracking extends BareNode { this.pullRequestNumber = pullRequestNumber; } + /** + * @return the lastKnownState + */ + public String getLastKnownState() { + return lastKnownState; + } + + /** + * @param lastKnownState the lastKnownState to set + */ + public void setLastKnownState(String lastKnownState) { + this.lastKnownState = lastKnownState; + } + /** * @return the lastUpdated */ @@ -169,7 +154,7 @@ public class GithubWebhookTracking extends BareNode { final int prime = 31; int result = super.hashCode(); result = prime * result - + Objects.hash(deliveryId, fingerprint, headSha, id, installationId, lastUpdated, pullRequestNumber, repositoryFullName); + + Objects.hash(headSha, id, installationId, lastKnownState, lastUpdated, pullRequestNumber, repositoryFullName); return result; } @@ -185,10 +170,9 @@ public class GithubWebhookTracking extends BareNode { return false; } GithubWebhookTracking other = (GithubWebhookTracking) obj; - return Objects.equals(deliveryId, other.deliveryId) && Objects.equals(fingerprint, other.fingerprint) - && Objects.equals(headSha, other.headSha) && Objects.equals(id, other.id) - && Objects.equals(installationId, other.installationId) && Objects.equals(lastUpdated, other.lastUpdated) - && Objects.equals(pullRequestNumber, other.pullRequestNumber) + return Objects.equals(headSha, other.headSha) && Objects.equals(id, other.id) + && Objects.equals(installationId, other.installationId) && Objects.equals(lastKnownState, other.lastKnownState) + && Objects.equals(lastUpdated, other.lastUpdated) && Objects.equals(pullRequestNumber, other.pullRequestNumber) && Objects.equals(repositoryFullName, other.repositoryFullName); } @@ -202,13 +186,6 @@ public class GithubWebhookTracking extends BareNode { public ParameterizedSQLStatement getFilters(MultivaluedMap<String, String> params, boolean isRoot) { ParameterizedSQLStatement statement = builder.build(TABLE); - // fingerprint check - String fingerprint = params.getFirst(GitEcaParameterNames.FINGERPRINT.getName()); - if (StringUtils.isNotBlank(fingerprint)) { - statement - .addClause( - new ParameterizedSQLStatement.Clause(TABLE.getAlias() + ".fingerprint = ?", new Object[] { fingerprint })); - } String installationId = params.getFirst(GitEcaParameterNames.INSTALLATION_ID_RAW); if (StringUtils.isNotBlank(installationId)) { statement diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/GithubAdjacentResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/GithubAdjacentResource.java index 8393c154aaf1154fb0e2b868bc17bf701ad916b0..e048f31a53332ef30483e3c6d864c18f1a1fcd33 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/GithubAdjacentResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/GithubAdjacentResource.java @@ -13,6 +13,7 @@ package org.eclipsefoundation.git.eca.resource; import java.net.URI; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -24,12 +25,14 @@ import javax.ws.rs.core.MultivaluedMap; import org.apache.commons.lang3.StringUtils; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.eclipsefoundation.core.helper.DateTimeHelper; import org.eclipsefoundation.core.model.RequestWrapper; import org.eclipsefoundation.core.service.APIMiddleware; import org.eclipsefoundation.git.eca.api.GithubAPI; import org.eclipsefoundation.git.eca.api.models.GithubCommit; import org.eclipsefoundation.git.eca.api.models.GithubCommitStatusRequest; import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest; +import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.PullRequest; import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking; import org.eclipsefoundation.git.eca.helper.JwtHelper; import org.eclipsefoundation.git.eca.model.Commit; @@ -39,6 +42,7 @@ import org.eclipsefoundation.git.eca.model.ValidationResponse; import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames; import org.eclipsefoundation.git.eca.namespace.GithubCommitStatuses; import org.eclipsefoundation.git.eca.namespace.ProviderType; +import org.eclipsefoundation.git.eca.service.GithubApplicationService; import org.eclipsefoundation.git.eca.service.ValidationService; import org.eclipsefoundation.persistence.dao.PersistenceDao; import org.eclipsefoundation.persistence.model.RDBMSQuery; @@ -75,7 +79,9 @@ public abstract class GithubAdjacentResource { FilterService filters; @Inject - ValidationService validationService; + ValidationService validation; + @Inject + GithubApplicationService ghAppService; @Inject RequestWrapper wrapper; @@ -127,6 +133,20 @@ public abstract class GithubAdjacentResource { .build(); } + /** + * 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 + * @param pr the pull request targeted by the validation request. + * @return a new or updated tracking object, or null if there was an error in saving the information + */ + GithubWebhookTracking retrieveAndUpdateTrackingInformation(String installationId, String repositoryFullName, PullRequest pr) { + return updateGithubTrackingIfMissing(getExistingRequestInformation(installationId, repositoryFullName, pr.getNumber()), pr, + installationId, repositoryFullName); + } + /** * Attempts to retrieve a webhook tracking record given the installation, repository, and pull request number. * @@ -144,6 +164,50 @@ public abstract class GithubAdjacentResource { return dao.get(new RDBMSQuery<>(wrapper, filters.get(GithubWebhookTracking.class), params)).stream().findFirst(); } + /** + * 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 + * @param installationId the ECA app installation ID for the current request + * @param fullRepoName the full repo name for the validation request + */ + GithubWebhookTracking updateGithubTrackingIfMissing(Optional<GithubWebhookTracking> tracking, PullRequest request, + String installationId, String fullRepoName) { + // if there is no tracking present, create the missing tracking and persist it + GithubWebhookTracking updatedTracking; + if (tracking.isEmpty()) { + updatedTracking = new GithubWebhookTracking(); + updatedTracking.setHeadSha(request.getHead().getSha()); + updatedTracking.setInstallationId(installationId); + updatedTracking.setLastUpdated(DateTimeHelper.now()); + updatedTracking.setPullRequestNumber(request.getNumber()); + updatedTracking.setRepositoryFullName(fullRepoName); + updatedTracking.setLastKnownState(request.getState()); + if (!"open".equalsIgnoreCase(request.getState())) { + LOGGER + .warn("The PR {} in {} is not in an open state, and will not be validated to follow our validation practice", + updatedTracking.getPullRequestNumber(), fullRepoName); + } + } else { + // at least update the state on every run + updatedTracking = tracking.get(); + updatedTracking.setLastKnownState(request.getState()); + } + + // save the data, and log on its success or failure + List<GithubWebhookTracking> savedTracking = dao + .add(new RDBMSQuery<>(wrapper, filters.get(GithubWebhookTracking.class)), Arrays.asList(updatedTracking)); + if (savedTracking.isEmpty()) { + LOGGER.warn("Unable to create new GH tracking record for request to validate {}#{}", fullRepoName, request.getNumber()); + return null; + } + // return the updated tracking when successful + LOGGER.debug("Created new GH tracking record for request to validate {}#{}", fullRepoName, request.getNumber()); + return savedTracking.get(0); + } + /** * 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 @@ -165,7 +229,7 @@ public abstract class GithubAdjacentResource { LOGGER .trace("Begining validation of request for {}/#{}", request.getRepository().getFullName(), request.getPullRequest().getNumber()); - ValidationResponse r = validationService.validateIncomingRequest(vr, wrapper); + ValidationResponse r = validation.validateIncomingRequest(vr, wrapper); if (r.getPassed()) { LOGGER .trace(VALIDATION_LOGGING_MESSAGE, request.getRepository().getFullName(), request.getPullRequest().getNumber(), @@ -198,7 +262,7 @@ public abstract class GithubAdjacentResource { .builder() .setDescription(state.getMessage()) .setState(state.toString()) - .setTargetUrl(serverTarget + "/git/eca/status/gh" + request.getRepository().getFullName() + '/' + .setTargetUrl(serverTarget + "/git/eca/status/gh/" + request.getRepository().getFullName() + '/' + request.getPullRequest().getNumber()) .setContext(context) .build()); diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java index feb89850b30231d105dccb111d598ead6be19f81..a4569876f97c4d9078eb8f8c56c626d248117d66 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java @@ -12,7 +12,6 @@ package org.eclipsefoundation.git.eca.resource; import java.net.URI; -import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -23,18 +22,17 @@ import javax.ws.rs.NotFoundException; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; +import javax.ws.rs.ServerErrorException; import javax.ws.rs.core.Response; -import org.eclipsefoundation.core.helper.DateTimeHelper; -import org.eclipsefoundation.core.service.CachingService; import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest; +import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.PullRequest; import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking; import org.eclipsefoundation.git.eca.helper.CaptchaHelper; import org.eclipsefoundation.git.eca.model.RevalidationResponse; import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames; import org.eclipsefoundation.git.eca.namespace.HCaptchaErrorCodes; -import org.eclipsefoundation.persistence.model.RDBMSQuery; import org.jboss.resteasy.annotations.jaxrs.HeaderParam; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,9 +50,6 @@ public class GithubWebhooksResource extends GithubAdjacentResource { @Inject CaptchaHelper captchaHelper; - @Inject - CachingService cache; - /** * 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 @@ -78,7 +73,8 @@ public class GithubWebhooksResource extends GithubAdjacentResource { // prepare for validation process by pre-processing into standard format ValidationRequest vr = generateRequest(request); // track the request before we start processing - trackWebhookRequest(deliveryId, request); + retrieveAndUpdateTrackingInformation(request.getInstallation().getId(), request.getRepository().getFullName(), + request.getPullRequest()); // process the request handleGithubWebhookValidation(request, vr); @@ -89,7 +85,9 @@ public class GithubWebhooksResource extends GithubAdjacentResource { * 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 fingerprint sha hash of unique identifiers for the request. + * @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 prNo the pull request number to be revalidated * @param captchaResponse the passed captcha challenge response * @return redirect to the pull request once done processing */ @@ -99,11 +97,21 @@ public class GithubWebhooksResource extends GithubAdjacentResource { @QueryParam(GitEcaParameterNames.INSTALLATION_ID_RAW) String installationId, @QueryParam(GitEcaParameterNames.PULL_REQUEST_NUMBER_RAW) Integer prNo, @FormParam("h-captcha-response") String captchaResponse) { - // get the tracking if it exists - Optional<GithubWebhookTracking> optTracking = getExistingRequestInformation(installationId, fullRepoName, prNo); - if (optTracking.isEmpty()) { - throw new NotFoundException( - String.format("Cannot find a tracked pull request with for repo '%s', pull request number '%d'", fullRepoName, prNo)); + // retrieve and check that the PR exists + Optional<PullRequest> prResponse = ghAppService.getPullRequest(installationId, fullRepoName, prNo); + if (prResponse.isEmpty()) { + throw new NotFoundException(String.format("Cannot find Github PR for repo '%s', pull request number '%d'", fullRepoName, prNo)); + } + + // get the tracking if it exists, create it if it doesn't, and fail out if there is an issue + GithubWebhookTracking tracking = retrieveAndUpdateTrackingInformation(installationId, fullRepoName, prResponse.get()); + if (tracking == null) { + throw new ServerErrorException( + String.format("Cannot find a tracked pull request with for repo '%s', pull request number '%d'", fullRepoName, prNo), + 500); + } else if (!"open".equalsIgnoreCase(tracking.getLastKnownState())) { + // we do not want to reprocess non-open pull requests + throw new BadRequestException("Cannot revalidate a non-open pull request"); } // check the captcha challenge response @@ -117,13 +125,10 @@ public class GithubWebhooksResource extends GithubAdjacentResource { } // get the tracking class, convert back to a GH webhook request, and validate the request - GithubWebhookTracking tracking = optTracking.get(); GithubWebhookRequest request = GithubWebhookRequest.buildFromTracking(tracking); boolean isSuccessful = handleGithubWebhookValidation(request, generateRequest(request)); LOGGER.debug("Revalidation for request for '{}#{}' was {}successful.", fullRepoName, prNo, isSuccessful ? "" : " not"); - // update the tracking for the update time - trackWebhookRequest(tracking.getDeliveryId(), request); // build the url for pull request page StringBuilder sb = new StringBuilder(); sb.append("https://github.com/"); @@ -134,40 +139,6 @@ public class GithubWebhooksResource extends GithubAdjacentResource { return Response.ok(RevalidationResponse.builder().setLocation(URI.create(sb.toString())).build()).build(); } - /** - * Create a new entry in the webhook request tracking table to facilitate revalidation requests from the ECA system - * rather than just from Github. - * - * @param fingerprint the unique hash for the request - * @param deliveryId unique ID of the Github Webhook Delivery object - * @param request the webhook event payload from Github - * @return the persisted webhook tracking data, or null if there was an error in creating the tracking - */ - private GithubWebhookTracking trackWebhookRequest(String deliveryId, GithubWebhookRequest request) { - Optional<GithubWebhookTracking> existingTracker = getExistingRequestInformation(request.getInstallation().getId(), - request.getRepository().getFullName(), request.getPullRequest().getNumber()); - GithubWebhookTracking calculatedTracker = new GithubWebhookTracking(); - calculatedTracker.setInstallationId(request.getInstallation().getId()); - calculatedTracker.setDeliveryId(deliveryId); - calculatedTracker.setPullRequestNumber(request.getPullRequest().getNumber()); - calculatedTracker.setRepositoryFullName(request.getRepository().getFullName()); - calculatedTracker.setHeadSha(request.getPullRequest().getHead().getSha()); - calculatedTracker.setLastUpdated(DateTimeHelper.now()); - // check if we already have an ID for the current request to prevent duplicates - existingTracker.ifPresent(gwt -> calculatedTracker.setId(gwt.getId())); - - // push the new/updated webhook request to the DB for future usage in revalidation - List<GithubWebhookTracking> results = dao - .add(new RDBMSQuery<>(wrapper, filters.get(GithubWebhookTracking.class)), Arrays.asList(calculatedTracker)); - if (results.isEmpty()) { - LOGGER - .error("Could not save the webhook metadata, functionality will be restricted for request in repo '{}', pull request #{}", - calculatedTracker.getRepositoryFullName(), calculatedTracker.getPullRequestNumber()); - return null; - } - return results.get(0); - } - /** * Generate the validation request for the current GH Webhook request. * diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java index f71fdb02867ac5d41d7b525602d300b8921a14b1..b2bd837117ac7fcebcb08dd14666957095215c24 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java @@ -11,7 +11,6 @@ */ package org.eclipsefoundation.git.eca.resource; -import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -28,8 +27,6 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; -import org.eclipsefoundation.core.helper.DateTimeHelper; -import org.eclipsefoundation.core.service.CachingService; import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest; import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.PullRequest; import org.eclipsefoundation.git.eca.api.models.Project; @@ -37,10 +34,7 @@ import org.eclipsefoundation.git.eca.dto.CommitValidationStatus; import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking; import org.eclipsefoundation.git.eca.model.Commit; import org.eclipsefoundation.git.eca.model.ValidationRequest; -import org.eclipsefoundation.git.eca.service.GithubApplicationService; import org.eclipsefoundation.git.eca.service.ProjectsService; -import org.eclipsefoundation.git.eca.service.ValidationService; -import org.eclipsefoundation.persistence.model.RDBMSQuery; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,26 +51,32 @@ import io.quarkus.qute.Template; public class StatusResource extends GithubAdjacentResource { private static final Logger LOGGER = LoggerFactory.getLogger(StatusResource.class); - @Inject - CachingService cache; - @Inject ProjectsService projects; - @Inject - ValidationService validation; - @Inject - GithubApplicationService ghAppService; // Qute templates, generates UI status page @Location("simple_fingerprint_ui") Template statusUiTemplate; + /** + * Standard endpoint for retrieving raw validation information on a historic request, using the fingerprint for lookups. + * + * @param fingerprint the associated fingerprint with the request that was validated. + * @return list of commits that exist for the given fingerprint, or empty if there are none that match + */ @GET @Path("{fingerprint}") public Response getCommitValidation(@PathParam("fingerprint") String fingerprint) { return Response.ok(validation.getHistoricValidationStatus(wrapper, fingerprint)).build(); } + /** + * Retrieves commit status information based on the fingerprint and builds a UI around the results for easier + * consumption. + * + * @param fingerprint the string associated with the request for looking up related commit statuses. + * @return the HTML UI for the status of the fingerprint request + */ @GET @Produces(MediaType.TEXT_HTML) @Path("{fingerprint}/ui") @@ -99,6 +99,15 @@ public class StatusResource extends GithubAdjacentResource { .build(); } + /** + * Retrieves and checks the validity of the commit statuses for a Github pull request, and if out of date will + * revalidate the request automatically on load. + * + * @param org the organization in Github that contains the target repo + * @param repoName the name of the repo in Github containing the pull request + * @param prNo the number of the pull request being targeted for the validation lookup + * @return the status UI for the Github status lookup, or an error if there was a problem + */ @GET @Produces(MediaType.TEXT_HTML) @Path("gh/{org}/{repoName}/{prNo}") @@ -110,13 +119,14 @@ public class StatusResource extends GithubAdjacentResource { Optional<PullRequest> prResponse = ghAppService.getPullRequest(installationId, fullRepoName, prNo); if (StringUtils.isBlank(installationId)) { throw new BadRequestException("Could not find an ECA app installation for repo name: " + fullRepoName); - } else if (prResponse.isEmpty() || !"open".equalsIgnoreCase(prResponse.get().getState())) { - throw new NotFoundException(String.format("Could not find open PR '%d' in repo name '%s'", prNo, fullRepoName)); + } else if (prResponse.isEmpty()) { + throw new NotFoundException(String.format("Could not find PR '%d' in repo name '%s'", prNo, fullRepoName)); } // prepare the request for consumption String repoUrl = "https://github.com/" + fullRepoName; ValidationRequest vr = generateRequest(installationId, fullRepoName, prNo, repoUrl); + // build the commit sha list based on the prepared request List<String> commitShas = vr.getCommits().stream().map(Commit::getHash).collect(Collectors.toList()); // there should always be commits for a PR, but in case, lets check @@ -125,17 +135,21 @@ public class StatusResource extends GithubAdjacentResource { } LOGGER.debug("Found {} commits for '{}#{}'", commitShas.size(), fullRepoName, prNo); - // get the commit status of commits to use + // retrieve the webhook tracking info, or generate an entry to track this PR if it's missing. + GithubWebhookTracking updatedTracking = retrieveAndUpdateTrackingInformation(installationId, fullRepoName, prResponse.get()); + if (updatedTracking == null) { + throw new ServerErrorException("Error while attempting to revalidate request, try again later.", 500); + } + + // get the commit status of commits to use for checking historic validation List<CommitValidationStatus> statuses = validation.getHistoricValidationStatusByShas(wrapper, commitShas); - // check if this request has been validated in the past, and if not, run validation - Optional<GithubWebhookTracking> tracking = getExistingRequestInformation(installationId, fullRepoName, prNo); - if (tracking.isEmpty() || commitShas.size() != statuses.size()) { + if (!"open".equalsIgnoreCase(prResponse.get().getState()) && statuses.isEmpty()) { + throw new BadRequestException("Cannot find validation history for current non-open PR, cannot provide validation status"); + } + + // we only want to update/revalidate for open PRs, so don't do this check if the PR is merged/closed + if ("open".equalsIgnoreCase(prResponse.get().getState()) && commitShas.size() != statuses.size()) { LOGGER.debug("Validation for {}#{} does not seem to be current, revalidating commits", fullRepoName, prNo); - // update the tracking before revalidating the request - GithubWebhookTracking updatedTracking = updateGithubTrackingIfMissing(tracking, prResponse.get(), installationId, fullRepoName); - if (updatedTracking == null) { - throw new ServerErrorException("Error while attempting to revalidate request, try again later.", 500); - } // using the updated tracking, perform the validation handleGithubWebhookValidation(GithubWebhookRequest.buildFromTracking(updatedTracking), vr); // call to retrieve the statuses once again since they will have changed at this point @@ -158,39 +172,4 @@ public class StatusResource extends GithubAdjacentResource { .build(); } - /** - * 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 installationId the ECA app installation ID for the current request - * @param request the pull request that is being validated - * @param fullRepoName the full repo name for the validation request - */ - private GithubWebhookTracking updateGithubTrackingIfMissing(Optional<GithubWebhookTracking> tracking, PullRequest request, - String installationId, String fullRepoName) { - // if there is no tracking present, create the missing tracking and persist it - if (tracking.isEmpty()) { - GithubWebhookTracking newTracking = new GithubWebhookTracking(); - newTracking.setDeliveryId(""); - newTracking.setFingerprint(""); - newTracking.setHeadSha(request.getHead().getSha()); - newTracking.setInstallationId(installationId); - newTracking.setLastUpdated(DateTimeHelper.now()); - newTracking.setPullRequestNumber(request.getNumber()); - newTracking.setRepositoryFullName(fullRepoName); - - // save the data, and log on its success or failure - List<GithubWebhookTracking> savedTracking = dao - .add(new RDBMSQuery<>(wrapper, filters.get(GithubWebhookTracking.class)), Arrays.asList(newTracking)); - if (savedTracking.isEmpty()) { - LOGGER.warn("Unable to create new GH tracking record for request to validate {}#{}", fullRepoName, request.getNumber()); - return null; - } - // return the updated tracking when successful - LOGGER.debug("Created new GH tracking record for request to validate {}#{}", fullRepoName, request.getNumber()); - return savedTracking.get(0); - } - return tracking.get(); - } } diff --git a/src/test/resources/database/default/V1.0.0__default.sql b/src/test/resources/database/default/V1.0.0__default.sql index ff4c33f79679e8b5cf09f6bfaa19a91d37d6827a..09fa5c3dffe1f162ea9e86bf5c352a5055a9e41f 100644 --- a/src/test/resources/database/default/V1.0.0__default.sql +++ b/src/test/resources/database/default/V1.0.0__default.sql @@ -50,8 +50,7 @@ INSERT INTO PrivateProjectEvent(userId, projectId, projectPath, ef_username, par CREATE TABLE GithubWebhookTracking ( id SERIAL NOT NULL, - fingerprint varchar(127) NOT NULL, - installationId varchar(63) NOT NULL, + lastKnownState varchar(63) NOT NULL, deliveryId varchar(63) NOT NULL, headSha varchar(63) NOT NULL, repositoryFullName varchar(127) NOT NULL,