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

Merge branch 'gnugomez/main/149' into 'main'

feat: adding email notification when certian revalidation threshold is met

Closes #149

See merge request !221
parents fd35a665 fdecc917
No related branches found
No related tags found
1 merge request!221feat: adding email notification when certian revalidation threshold is met
Pipeline #71719 failed
......@@ -12,4 +12,8 @@ quarkus.datasource.password=
quarkus.datasource.jdbc.url=jdbc:mariadb://mariadb/dev_eclipse_eca
%dev.quarkus.datasource.jdbc.url=jdbc:mariadb://${eclipse.internal-host}:10101/dev_eclipse_eca
eclipse.gitlab.access-token=
\ No newline at end of file
eclipse.gitlab.access-token=
## Used to send mail through the EclipseFdn smtp connection
quarkus.mailer.password=YOURGENERATEDAPPLICATIONPASSWORD
quarkus.mailer.username=YOUREMAIL@gmail.com
\ No newline at end of file
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipsefoundation</groupId>
<artifactId>git-eca</artifactId>
......@@ -25,8 +22,7 @@
<sonar.tests>src/test</sonar.tests>
<sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
<sonar.coverage.jacoco.xmlReportPaths>
${project.basedir}/target/jacoco-report/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
<sonar.coverage.jacoco.xmlReportPaths>${project.basedir}/target/jacoco-report/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
<sonar.junit.reportPath>${project.build.directory}/surefire-reports</sonar.junit.reportPath>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.organization>eclipse-foundation-it</sonar.organization>
......@@ -38,10 +34,8 @@
<id>eclipsefdn</id>
<url>https://repo.eclipse.org/content/repositories/eclipsefdn/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
......@@ -80,6 +74,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mailer</artifactId>
</dependency>
<!-- Annotation preprocessors - reduce all of the boiler plate -->
<dependency>
......@@ -179,8 +177,7 @@
<configuration>
<skipTests>false</skipTests>
<systemPropertyVariables>
<java.util.logging.manager>
org.jboss.logmanager.LogManager</java.util.logging.manager>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
......
/**
* Copyright (c) 2025 Eclipse Foundation
*
* This program and the accompanying materials are made available under the terms of the Eclipse
* Public License 2.0 which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Jordi Gómez <jordi.gomez@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.git.eca.config;
import java.util.List;
import java.util.Optional;
import io.smallrye.config.ConfigMapping;
/**
* Represents configuration for the default mailer service.
*
* @author Martin Lowe
*
*/
@ConfigMapping(prefix = "eclipse.mailer")
public interface MailerConfig {
public RevalidationAlert revalidationAlert();
/**
* This interface defines the contract for specifying recipients and message configuration when sending revalidation alerts within the
* ECA validation process.
*/
public interface RevalidationAlert {
public List<String> to();
public MessageConfiguration authorMessage();
}
public interface MessageConfiguration {
public Optional<String> replyTo();
public Optional<List<String>> bcc();
}
}
/**
* Copyright (c) 2025 Eclipse Foundation
*
* This program and the accompanying materials are made available under the terms of the Eclipse
* Public License 2.0 which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.git.eca.service;
import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking;
/**
* Service responsible for sending email notifications.
*/
public interface MailerService {
/**
* Notifies a configured recipient about an stuck revalidation request.
*
* @param tracking the GithubWebhookTracking object containing information about the request
* @param threshold the used threshold for the revalidation alert
*/
void sendRevalidationAlert(GithubWebhookTracking tracking, Integer threshold);
}
/**
* Copyright (c) 2025 Eclipse Foundation
*
* This program and the accompanying materials are made available under the terms of the Eclipse
* Public License 2.0 which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Jordi Gómez <jordi.gomez@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.git.eca.service.impl;
import org.eclipsefoundation.git.eca.config.MailerConfig;
import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking;
import org.eclipsefoundation.git.eca.service.MailerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.Mailer;
import io.quarkus.qute.Location;
import io.quarkus.qute.Template;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class DefaultMailerService implements MailerService {
public static final Logger LOGGER = LoggerFactory.getLogger(DefaultMailerService.class);
private final MailerConfig config;
private final Mailer mailer;
@Location("emails/revalidation_alert")
Template revalidationAlertTemplate;
public DefaultMailerService(MailerConfig config, Mailer mailer) {
this.config = config;
this.mailer = mailer;
}
@Override
public void sendRevalidationAlert(GithubWebhookTracking tracking, Integer threshold) {
var revalidationAlertConfig = config.revalidationAlert();
if (revalidationAlertConfig.to().isEmpty()) {
LOGGER.warn("No recipients configured for revalidation alert. Skipping email notification.");
return;
}
String subject = "GitHub Webhook Revalidation Alert";
Mail messageBuilder = new Mail();
messageBuilder.setSubject(subject);
messageBuilder
.setText(revalidationAlertTemplate
.data("attempts", threshold)
.data("requestId", tracking.getId())
.data("repository", tracking.getRepositoryFullName())
.data("pullRequest", tracking.getPullRequestNumber())
.data("lastState", tracking.getLastKnownState())
.data("lastUpdated", tracking.getLastUpdated().toString())
.render());
messageBuilder.addTo(revalidationAlertConfig.to().toArray(String[]::new));
revalidationAlertConfig.authorMessage().replyTo().ifPresent(messageBuilder::addReplyTo);
revalidationAlertConfig.authorMessage().bcc().ifPresent(bcc -> messageBuilder.addBcc(bcc.toArray(String[]::new)));
mailer.send(messageBuilder);
LOGGER.info("Revalidation alert sent to: {}", revalidationAlertConfig.to());
}
}
/**
* Copyright (c) 2023 Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
* This program and the accompanying materials are made available under the terms of the Eclipse
* Public License 2.0 which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
......@@ -14,11 +13,13 @@ package org.eclipsefoundation.git.eca.tasks;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking;
import org.eclipsefoundation.git.eca.helper.GithubHelper;
import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames;
import org.eclipsefoundation.git.eca.service.MailerService;
import org.eclipsefoundation.http.model.FlatRequestWrapper;
import org.eclipsefoundation.http.model.RequestWrapper;
import org.eclipsefoundation.http.namespace.DefaultUrlParameterNames;
......@@ -40,11 +41,10 @@ import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
/**
* Scheduled regular task that will interact with the backend persistence to look for requests that are in a
* failed/unvalidated state after an error while processing that could not be recovered. These requests will be
* reprocessed using the same logic as the standard validation, updating the timestamp on completion and either setting
* the revalidation flag to false or incrementing the number of repeated revalidations needed for the request for
* tracking, depending on the succcess of the revalidation.
* Scheduled regular task that will interact with the backend persistence to look for requests that are in a failed/unvalidated state after
* an error while processing that could not be recovered. These requests will be reprocessed using the same logic as the standard
* validation, updating the timestamp on completion and either setting the revalidation flag to false or incrementing the number of repeated
* revalidations needed for the request for tracking, depending on the succcess of the revalidation.
*/
@ApplicationScoped
public class GithubRevalidationQueue {
......@@ -56,13 +56,20 @@ public class GithubRevalidationQueue {
@ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.enabled", defaultValue = "true")
Instance<Boolean> isEnabled;
@ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.notification-enabled", defaultValue = "true")
Instance<Boolean> isRevalidationAlertEnabled;
@ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.notification-threshold", defaultValue = "100")
Instance<Integer> revalidationThreshold;
@Inject
PersistenceDao dao;
@Inject
FilterService filters;
@Inject
GithubHelper validationHelper;
@Inject
MailerService mailerService;
@PostConstruct
void init() {
......@@ -71,8 +78,8 @@ public class GithubRevalidationQueue {
}
/**
* Every 5s, this method will attempt to load a Github webhook validation request that has the needs revalidation flag
* set to true. This will retrieve the oldest request in queue and will attempt to revalidate it.
* Every 5s, this method will attempt to load a Github webhook validation request that has the needs revalidation flag set to true. This
* will retrieve the oldest request in queue and will attempt to revalidate it.
*/
@Scheduled(every = "${eclipse.git-eca.tasks.gh-revalidation.frequency:60s}")
@ActivateRequestContext
......@@ -101,9 +108,9 @@ public class GithubRevalidationQueue {
}
/**
* Reprocess the given record, attempting to run the ECA validation logic again. If it passes, the revalidation flag is
* set to false and the time code is updated. If the processing fails again, the failure count gets incremented and the
* updated time is set so that another entry can be updated, as to not block on potentially broken records.
* Reprocess the given record, attempting to run the ECA validation logic again. If it passes, the revalidation flag is set to false and
* the time code is updated. If the processing fails again, the failure count gets incremented and the updated time is set so that
* another entry can be updated, as to not block on potentially broken records.
*
* @param requestToRevalidate the webhook tracking request to attempt to revalidate
* @param wrap the current stubbed request wrapper used for queries.
......@@ -117,6 +124,10 @@ public class GithubRevalidationQueue {
requestToRevalidate
.setManualRevalidationCount(requestToRevalidate.getManualRevalidationCount() == null ? 1
: requestToRevalidate.getManualRevalidationCount() + 1);
// Check if notification threshold is reached and send email if needed
checkRevalidationThreshold(requestToRevalidate);
// wrap in try-catch to avoid errors from stopping the record updates
try {
// split the full repo name into the org and repo name
......@@ -126,8 +137,7 @@ public class GithubRevalidationQueue {
+ "' is in an invalid state (repository full name is not valid)");
}
// run the validation and then check if it succeeded. Use the forced flag since we want to try even if there are no
// changes
// run the validation and then check if it succeeded. Use the forced flag since we want to try even if there are no changes
validationHelper
.validateIncomingRequest(wrap, repoFullNameParts[0], repoFullNameParts[1], requestToRevalidate.getPullRequestNumber(),
true);
......@@ -151,4 +161,42 @@ public class GithubRevalidationQueue {
dao.add(new RDBMSQuery<>(wrap, filters.get(GithubWebhookTracking.class)), Arrays.asList(requestToRevalidate));
}
}
/**
* Checks if the manual revalidation count has reached the configured threshold and sends an email notification if it has and the
* notification alert is enabled.
*
* @param tracking the webhook tracking object
*/
private void checkRevalidationThreshold(GithubWebhookTracking tracking) {
// If revalidation alert is not enabled, do nothing
if (!Boolean.TRUE.equals(isRevalidationAlertEnabled.get()))
return;
Integer threshold = revalidationThreshold.get();
// If manual revalidation count is null or less than threshold, do nothing
if (Objects.isNull(tracking.getManualRevalidationCount()) || tracking.getManualRevalidationCount() < threshold) {
return;
}
// Check if count equals threshold (to send notification only once when threshold is reached)
if (tracking.getManualRevalidationCount() == threshold) {
sendRevalidationAlert(tracking);
}
}
/**
* Sends an email alert about a webhook that has reached the revalidation threshold.
*
* @param tracking the webhook tracking object
*/
private void sendRevalidationAlert(GithubWebhookTracking tracking) {
try {
mailerService.sendRevalidationAlert(tracking, revalidationThreshold.get());
LOGGER.info("Sent revalidation alert email for request ID {}", tracking.getId());
} catch (RuntimeException e) {
LOGGER.error("Failed to send revalidation alert email for request ID {}", tracking.getId(), e);
}
}
}
......@@ -65,3 +65,15 @@ eclipse.git-eca.reports.allowed-users=mbarbaro,webdev
## Misc
eclipse.system-hook.pool-size=5
## Revalidation alert email config
eclipse.git-eca.tasks.gh-revalidation.notification-threshold=100
eclipse.mailer.revalidation-alert.to=webdev@eclipse-foundation.org
# Quarkus Mailer
quarkus.mailer.auth-methods=DIGEST-MD5 CRAM-SHA256 CRAM-SHA1 CRAM-MD5 PLAIN LOGIN
quarkus.mailer.from=no-reply@eclipse-foundation.org
# Uses gmail by default, can be overridden
quarkus.mailer.host=smtp.gmail.com
quarkus.mailer.port=465
quarkus.mailer.ssl=true
A GitHub webhook has reached the revalidation threshold of {attempts} attempts.
Details:
- Request ID: {requestId}
- Repository: {repository}
- Pull Request: #{pullRequest}
- Last State: {lastState}
- Last Updated: {lastUpdated}
Please investigate this issue as it may indicate a persistent problem.
-------------------------------------------------------------------------------------------
This is an automated email from api.eclipse.org/git/eca. Please do not reply to this email.
\ No newline at end of file
......@@ -23,6 +23,9 @@ quarkus.keycloak.devservices.enabled=false
quarkus.oidc-client.enabled=false
smallrye.jwt.sign.key.location=test.pem
# Quarkus Mailer
quarkus.mailer.mock=true
# hCaptcha test key and secret
eclipse.hcaptcha.site-key=20000000-ffff-ffff-ffff-000000000002
eclipse.hcaptcha.secret=0x0000000000000000000000000000000000000000
......@@ -40,4 +43,4 @@ eclipse.git-eca.tasks.gh-installation.enabled=false
eclipse.gitlab.access-token=token_val
## Disable private project scan in test mode
eclipse.scheduled.private-project.enabled=false
\ No newline at end of file
eclipse.scheduled.private-project.enabled=false
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