Skip to content

Commit

Permalink
RHCLOUD-28942
Browse files Browse the repository at this point in the history
  • Loading branch information
gwenneg committed Oct 28, 2023
1 parent 7d73560 commit 792d7b1
Show file tree
Hide file tree
Showing 25 changed files with 362 additions and 93 deletions.
5 changes: 5 additions & 0 deletions .rhcicd/clowdapp-connector-email.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ objects:
env:
- name: ENV_NAME
value: ${ENV_NAME}
- name: NOTIFICATIONS_CONNECTOR_BOP_SKIP_USERS_RESOLUTION_ENABLED
value: ${NOTIFICATIONS_CONNECTOR_BOP_SKIP_USERS_RESOLUTION_ENABLED}
- name: NOTIFICATIONS_CONNECTOR_ENDPOINT_CACHE_MAX_SIZE
value: ${NOTIFICATIONS_CONNECTOR_ENDPOINT_CACHE_MAX_SIZE}
- name: NOTIFICATIONS_CONNECTOR_FETCH_USERS_RBAC_ENABLED
Expand Down Expand Up @@ -205,6 +207,9 @@ parameters:
- name: MIN_REPLICAS
value: "3"

- name: NOTIFICATIONS_CONNECTOR_BOP_SKIP_USERS_RESOLUTION_ENABLED
description: Should BOP transform usernames from our payload into email addresses using the IT Users Service?
value: "false"
- name: NOTIFICATIONS_CONNECTOR_ENDPOINT_CACHE_MAX_SIZE
description: Maximum size of the Camel endpoints cache
value: "100"
Expand Down
5 changes: 5 additions & 0 deletions .rhcicd/clowdapp-engine.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ objects:
key: client-id
- name: PROCESSOR_EMAIL_BOP_ENV
value: ${BACKOFFICE_CLIENT_ENV}
- name: PROCESSOR_EMAIL_BOP_SKIP_USERS_RESOLUTION_ENABLED
value: ${PROCESSOR_EMAIL_BOP_SKIP_USERS_RESOLUTION_ENABLED}
- name: PROCESSOR_EMAIL_BOP_URL
value: ${BACKOFFICE_SCHEME}://${BACKOFFICE_HOST}:${BACKOFFICE_PORT}/v1/sendEmails
- name: PROCESSOR_EMAIL_NO_REPLY
Expand Down Expand Up @@ -330,6 +332,9 @@ parameters:
- name: NOTIFICATIONS_LOG_LEVEL
description: Log level for com.redhat.cloud.notifications
value: INFO
- name: PROCESSOR_EMAIL_BOP_SKIP_USERS_RESOLUTION_ENABLED
description: Should BOP transform usernames from our payload into email addresses using the IT Users Service?
value: "false"
- name: QUARKUS_HIBERNATE_ORM_LOG_SQL
value: "false"
- name: QUARKUS_LOG_CLOUDWATCH_API_CALL_TIMEOUT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ public class FeatureFlipper {
@ConfigProperty(name = "notifications.async-aggregation.enabled", defaultValue = "true")
boolean asyncAggregation;

@ConfigProperty(name = "notifications.email.send-emails-to-bop", defaultValue = "false")
boolean sendEmailstoBop;

void logFeaturesStatusAtStartup(@Observes StartupEvent event) {
Log.infof("=== %s startup status ===", FeatureFlipper.class.getSimpleName());
Log.infof("The behavior groups unique name constraint is %s", enforceBehaviorGroupNameUnicity ? "enabled" : "disabled");
Expand All @@ -120,6 +123,7 @@ void logFeaturesStatusAtStartup(@Observes StartupEvent event) {
Log.infof("The email connector is %s", emailConnectorEnabled ? "enabled" : "disabled");
Log.infof("The drawer connector is %s", drawerConnectorEnabled ? "enabled" : "disabled");
Log.infof("The async aggregation is %s", asyncAggregation ? "enabled" : "disabled");
Log.infof("Send emails to BOP is %s", sendEmailstoBop ? "enabled" : "disabled");
}

public boolean isEnforceBehaviorGroupNameUnicity() {
Expand Down Expand Up @@ -298,6 +302,15 @@ public void setAsyncAggregation(boolean asyncAggregation) {
this.asyncAggregation = asyncAggregation;
}

public boolean isSendEmailstoBop() {
return sendEmailstoBop;
}

public void setSendEmailstoBop(boolean sendEmailstoBop) {
checkTestLaunchMode();
this.sendEmailstoBop = sendEmailstoBop;
}

/**
* This method throws an {@link IllegalStateException} if it is invoked with a launch mode different from
* {@link io.quarkus.runtime.LaunchMode#TEST TEST}. It should be added to methods that allow overriding a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.redhat.cloud.notifications.connector.email.config.Environment;
import com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty;
import com.redhat.cloud.notifications.connector.email.constants.Routes;
import com.redhat.cloud.notifications.connector.email.model.settings.User;
import com.redhat.cloud.notifications.connector.email.predicates.NotFinishedFetchingAllPages;
import com.redhat.cloud.notifications.connector.email.predicates.rbac.StatusCodeNotFound;
import com.redhat.cloud.notifications.connector.email.processors.bop.BOPRequestPreparer;
Expand Down Expand Up @@ -156,7 +157,7 @@ public void configureRoute() throws Exception {
.routeId(this.connectorConfig.getConnectorName())
// Initialize the usernames hash set, where we will gather the
// fetched users from the user providers.
.process(exchange -> exchange.setProperty(ExchangeProperty.USERNAMES, new HashSet<String>()))
.process(exchange -> exchange.setProperty(ExchangeProperty.USERS, new HashSet<User>()))
// Split each recipient setting and aggregate the usernames to end
// up with a single exchange.
.split(simpleF("${exchangeProperty.%s}", ExchangeProperty.RECIPIENT_SETTINGS), this.userAggregationStrategy).stopOnException()
Expand Down Expand Up @@ -201,7 +202,7 @@ public void configureRoute() throws Exception {
// The cache engine leaves the usernames in the body of the
// exchange, that is why we need to set them back in the
// property that the subsequent processors expect to find them.
.setProperty(ExchangeProperty.USERNAMES, body())
.setProperty(ExchangeProperty.USERS, body())
.otherwise()
// Clear all the headers that may come from the previous route.
.removeHeaders("*")
Expand All @@ -220,7 +221,7 @@ public void configureRoute() throws Exception {
// Store all the received recipients in the cache.
.setHeader(CaffeineConstants.ACTION, constant(CaffeineConstants.ACTION_PUT))
.setHeader(CaffeineConstants.KEY, this.computeCacheKey())
.setHeader(CaffeineConstants.VALUE, exchangeProperty(ExchangeProperty.USERNAMES))
.setHeader(CaffeineConstants.VALUE, exchangeProperty(ExchangeProperty.USERS))
.to(caffeineCache(Routes.FETCH_USERS_RBAC))
.endChoice()
.end()
Expand Down Expand Up @@ -249,7 +250,7 @@ public void configureRoute() throws Exception {
// The cache engine leaves the usernames in the body of the
// exchange, that is why we need to set them back in the
// property that the subsequent processors expect to find them.
.setProperty(ExchangeProperty.USERNAMES, body())
.setProperty(ExchangeProperty.USERS, body())
.otherwise()
// Clear all the headers that may come from the previous route.
.removeHeaders("*")
Expand All @@ -266,7 +267,7 @@ public void configureRoute() throws Exception {
// Store all the received recipients in the cache.
.setHeader(CaffeineConstants.ACTION, constant(CaffeineConstants.ACTION_PUT))
.setHeader(CaffeineConstants.KEY, this.computeCacheKey())
.setHeader(CaffeineConstants.VALUE, exchangeProperty(ExchangeProperty.USERNAMES))
.setHeader(CaffeineConstants.VALUE, exchangeProperty(ExchangeProperty.USERS))
.to(caffeineCache(Routes.FETCH_USERS_IT))
.endChoice()
.end()
Expand Down Expand Up @@ -312,7 +313,7 @@ public void configureRoute() throws Exception {
// The cache engine leaves the usernames in the body of the
// exchange, that is why we need to set them back in the
// property that the subsequent processors expect to find them.
.setProperty(ExchangeProperty.USERNAMES, body())
.setProperty(ExchangeProperty.USERS, body())
.otherwise()
// Clear all the headers that may come from the previous route.
.removeHeaders("*")
Expand All @@ -328,7 +329,7 @@ public void configureRoute() throws Exception {
// Store all the received recipients in the cache.
.setHeader(CaffeineConstants.ACTION, constant(CaffeineConstants.ACTION_PUT))
.setHeader(CaffeineConstants.KEY, this.computeGroupPrincipalsCacheKey())
.setHeader(CaffeineConstants.VALUE, exchangeProperty(ExchangeProperty.USERNAMES))
.setHeader(CaffeineConstants.VALUE, exchangeProperty(ExchangeProperty.USERS))
.to(caffeineCache(Routes.FETCH_GROUP_USERS))
.endChoice()
.end()
Expand Down Expand Up @@ -366,7 +367,7 @@ public void configureRoute() throws Exception {
.routeId(Routes.SEND_EMAIL_BOP_SINGLE_PER_USER)
// Clear all the headers that may come from the previous route.
.removeHeaders("*")
.split(simpleF("${exchangeProperty.%s}", ExchangeProperty.FILTERED_USERNAMES))
.split(simpleF("${exchangeProperty.%s}", ExchangeProperty.FILTERED_USERS))
.setProperty(ExchangeProperty.SINGLE_EMAIL_PER_USER, constant(true))
.to(direct(Routes.SEND_EMAIL_BOP))
.end();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.redhat.cloud.notifications.connector.email.aggregation;

import com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty;
import com.redhat.cloud.notifications.connector.email.model.settings.User;
import jakarta.enterprise.context.ApplicationScoped;
import org.apache.camel.AggregationStrategy;
import org.apache.camel.Exchange;
Expand All @@ -24,8 +25,8 @@ public Exchange aggregate(final Exchange oldExchange, final Exchange newExchange
return newExchange;
}

final Set<String> oldFilteredUsers = oldExchange.getProperty(ExchangeProperty.FILTERED_USERNAMES, Set.class);
final Set<String> newFilteredUsers = newExchange.getProperty(ExchangeProperty.FILTERED_USERNAMES, Set.class);
final Set<User> oldFilteredUsers = oldExchange.getProperty(ExchangeProperty.FILTERED_USERS, Set.class);
final Set<User> newFilteredUsers = newExchange.getProperty(ExchangeProperty.FILTERED_USERS, Set.class);

if (newFilteredUsers != null) {
oldFilteredUsers.addAll(newFilteredUsers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class EmailConnectorConfig extends ConnectorConfig {
private static final String RBAC_APPLICATION_KEY = "notifications.connector.user-provider.rbac.application-key";
private static final String RBAC_ELEMENTS_PAGE = "notifications.connector.user-provider.rbac.elements-per-page";
private static final String RBAC_URL = "notifications.connector.user-provider.rbac.url";
private static final String SKIP_BOP_USERS_RESOLUTION = "notifications.connector.bop.skip-users-resolution.enabled";
@Deprecated(forRemoval = true)
public static final String SINGLE_EMAIL_PER_USER = "notifications.connector.single-email-per-user.enabled";

Expand Down Expand Up @@ -97,6 +98,9 @@ public class EmailConnectorConfig extends ConnectorConfig {
@ConfigProperty(name = USER_PROVIDER_CACHE_EXPIRE_AFTER_WRITE, defaultValue = "600")
int userProviderCacheExpireAfterWrite;

@ConfigProperty(name = SKIP_BOP_USERS_RESOLUTION, defaultValue = "false")
boolean skipBopUsersResolution;

@Override
public void log() {
final Map<String, Object> additionalEntries = new HashMap<>();
Expand All @@ -115,6 +119,7 @@ public void log() {
additionalEntries.put(RBAC_URL, this.rbacURL);
additionalEntries.put(SINGLE_EMAIL_PER_USER, this.singleEmailPerUserEnabled);
additionalEntries.put(USER_PROVIDER_CACHE_EXPIRE_AFTER_WRITE, this.userProviderCacheExpireAfterWrite);
additionalEntries.put(SKIP_BOP_USERS_RESOLUTION, skipBopUsersResolution);

log(additionalEntries);
}
Expand Down Expand Up @@ -219,4 +224,8 @@ public boolean isSingleEmailPerUserEnabled() {
public int getUserProviderCacheExpireAfterWrite() {
return userProviderCacheExpireAfterWrite;
}

public boolean isSkipBopUsersResolution() {
return skipBopUsersResolution;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class ExchangeProperty {
* Holds the filtered usernames. It is used in order to avoid the set of
* cached usernames from being modified.
*/
public static final String FILTERED_USERNAMES = "usernames_filtered";
public static final String FILTERED_USERS = "users_filtered";
/**
* Used to hold the received RBAC group's UUID.
*/
Expand Down Expand Up @@ -55,5 +55,5 @@ public class ExchangeProperty {
* notification through email. Since only usernames are required in order
* to send the emails, we will only grab those.
*/
public static final String USERNAMES = "usernames";
public static final String USERS = "users";
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
/**
* Represents the payload to be sent to BOP/MBOP.
*/
@Deprecated(forRemoval = true)
public class Emails {

@JsonProperty("emails")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.redhat.cloud.notifications.connector.email.model.bop;

import com.fasterxml.jackson.annotation.JsonAutoDetect;

import java.util.HashSet;
import java.util.Set;

import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;

@JsonAutoDetect(fieldVisibility = ANY)
public class SendEmailsRequest {

private final Set<Email> emails = new HashSet<>();
private final boolean skipUsersResolution = true;

public void addEmail(Email email) {
emails.add(email);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.redhat.cloud.notifications.connector.email.model.settings;

import java.util.Objects;

public class User {

private String id;
private String username;
private String email;
private boolean admin;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public boolean isAdmin() {
return admin;
}

public void setAdmin(boolean admin) {
this.admin = admin;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}

if (!(o instanceof User)) {
return false;
}

User user = (User) o;
return Objects.equals(username, user.username);
}

@Override
public int hashCode() {
return Objects.hash(username);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@
import com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty;
import com.redhat.cloud.notifications.connector.email.model.bop.Email;
import com.redhat.cloud.notifications.connector.email.model.bop.Emails;
import com.redhat.cloud.notifications.connector.email.model.bop.SendEmailsRequest;
import com.redhat.cloud.notifications.connector.email.model.settings.User;
import io.vertx.core.json.JsonObject;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.component.http.HttpMethods;

import java.util.HashSet;
import java.util.Collections;
import java.util.Set;

import static java.util.stream.Collectors.toSet;

@ApplicationScoped
public class BOPRequestPreparer implements Processor {
@Inject
Expand All @@ -27,18 +31,28 @@ public class BOPRequestPreparer implements Processor {
public void process(final Exchange exchange) {
final String subject = exchange.getProperty(ExchangeProperty.RENDERED_SUBJECT, String.class);
final String body = exchange.getProperty(ExchangeProperty.RENDERED_BODY, String.class);
final Set<String> recipients = new HashSet<>();
final Set<String> recipients;

// We still need to support sending individual emails per user for a
// while. However, that will go away soon, so we can consider the
// following code block very much deprecated.
final Boolean singleEmailPerUser = exchange.getProperty(ExchangeProperty.SINGLE_EMAIL_PER_USER, Boolean.class);
if (singleEmailPerUser != null && singleEmailPerUser) {
recipients.add(exchange.getMessage().getBody(String.class));
User recipient = exchange.getMessage().getBody(User.class);
if (emailConnectorConfig.isSkipBopUsersResolution()) {
recipients = Collections.singleton(recipient.getEmail());
} else {
recipients = Collections.singleton(recipient.getUsername());
}
} else {
final Set<String> usernames = exchange.getProperty(ExchangeProperty.FILTERED_USERNAMES, Set.class);

recipients.addAll(usernames);
final Set<User> users = exchange.getProperty(ExchangeProperty.FILTERED_USERS, Set.class);
recipients = users.stream().map(user -> {
if (emailConnectorConfig.isSkipBopUsersResolution()) {
return user.getEmail();
} else {
return user.getUsername();
}
}).collect(toSet());
}

final Email email = new Email(
Expand All @@ -47,11 +61,19 @@ public void process(final Exchange exchange) {
recipients
);

final Emails emails = new Emails();
emails.addEmail(email);
JsonObject bopBody;
if (emailConnectorConfig.isSkipBopUsersResolution()) {
final SendEmailsRequest request = new SendEmailsRequest();
request.addEmail(email);
bopBody = JsonObject.mapFrom(request);
} else {
final Emails emails = new Emails();
emails.addEmail(email);
bopBody = JsonObject.mapFrom(emails);
}

// Specify the message's payload in JSON.
exchange.getMessage().setBody(JsonObject.mapFrom(emails).encode());
exchange.getMessage().setBody(bopBody.encode());

// Specify the request's method.
exchange.getMessage().setHeader(Exchange.HTTP_METHOD, HttpMethods.POST);
Expand Down
Loading

0 comments on commit 792d7b1

Please sign in to comment.