Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KEYCLOAK-25 Increase keycloak test container to v26.1.0, admin library to v26.0.4 #174

Merged
merged 7 commits into from
Jan 28, 2025
10 changes: 9 additions & 1 deletion folio-backend-testing/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
<sonar.exclusions>src/main/**/*</sonar.exclusions>
<jsonassert.version>1.5.3</jsonassert.version>
<jjwt.version>0.12.6</jjwt.version>
<testcontainers-keycloak.version>3.4.0</testcontainers-keycloak.version>
<testcontainers-keycloak.version>3.5.1</testcontainers-keycloak.version>
<keycloak-admin-client.version>26.0.4</keycloak-admin-client.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -101,6 +102,13 @@
<scope>compile</scope>
</dependency>

<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>${keycloak-admin-client.version}</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
@Log4j2
public class KeycloakContainerExtension implements BeforeAllCallback, AfterAllCallback {

private static final String KEYCLOAK_IMAGE = "quay.io/keycloak/keycloak:25.0.6";
private static final String KEYCLOAK_IMAGE = "quay.io/keycloak/keycloak:26.1.0";
private static final String REALM_JSON = "json/keycloak/master-realm.json";
private static final String IMPORTED_CLIENT_ID = "folio-backend-admin-client";
private static final String IMPORTED_CLIENT_SECRET = "supersecret";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import static org.folio.common.utils.CollectionUtils.mapItems;
import static org.folio.common.utils.CollectionUtils.toStream;
import static org.folio.common.utils.OkapiHeaders.MODULE_ID;
import static org.folio.common.utils.OkapiHeaders.TENANT;
import static org.folio.tools.kong.model.expression.RouteExpressions.combineUsingAnd;
import static org.folio.tools.kong.model.expression.RouteExpressions.combineUsingOr;
import static org.folio.tools.kong.model.expression.RouteExpressions.httpHeader;
Expand All @@ -28,6 +29,7 @@
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Stream;
Expand Down Expand Up @@ -67,6 +69,19 @@ public void addRoutes(Collection<ModuleDescriptor> moduleDescriptors) {
performOperation(moduleDescriptors, "create", this::addRoutesForModule);
}

/**
* Adds tenant routes for API Gateway.
*
* @param tenant - tenant name as {@link String}, nullable
* @param moduleDescriptors - {@link List} with {@link ModuleDescriptor} objects to be processed
* @throws KongIntegrationException if any of route create requests failed
* @deprecated use {@link #addRoutes(Collection)} instead
*/
@Deprecated(since = "2.3.0", forRemoval = true)
public void addRoutes(String tenant, Collection<ModuleDescriptor> moduleDescriptors) {
performOperation(tenant, moduleDescriptors, "create", (t, md) -> addTenantRoutesForModule(md, t));
}

/**
* Updates routes for API Gateway.
*
Expand All @@ -77,6 +92,19 @@ public void updateRoutes(Collection<ModuleDescriptor> moduleDescriptors) {
performOperation(moduleDescriptors, "update", this::updateRoutesForModule);
}

/**
* Updates tenant routes for API Gateway.
*
* @param tenant - tenant name as {@link String}, nullable
* @param moduleDescriptors - {@link List} with {@link ModuleDescriptor} objects to be processed
* @throws KongIntegrationException if any of route update requests failed
* @deprecated use {@link #updateRoutes(Collection)} instead
*/
@Deprecated(since = "2.3.0", forRemoval = true)
public void updateRoutes(String tenant, Collection<ModuleDescriptor> moduleDescriptors) {
performOperation(tenant, moduleDescriptors, "update", (t, md) -> updateTenantRoutesForModule(md, t));
}

/**
* Removes routes from API Gateway.
*
Expand All @@ -87,6 +115,19 @@ public void removeRoutes(Collection<ModuleDescriptor> moduleDescriptors) {
performOperation(moduleDescriptors, "remove", md -> removeKongRoutes(md.getId()));
}

/**
* Removes routes for tenant from API Gateway.
*
* @param tenant - tenant name as {@link String}, nullable
* @param moduleDescriptors - {@link List} with {@link ModuleDescriptor} objects to be processed
* @throws KongIntegrationException if any of route delete requests failed
* @deprecated use {@link #removeRoutes(Collection)} instead
*/
@Deprecated(since = "2.3.0", forRemoval = true)
public void removeRoutes(String tenant, Collection<ModuleDescriptor> moduleDescriptors) {
performOperation(tenant, moduleDescriptors, "remove", (t, md) -> removeKongTenantRoutes(t, md.getId()));
}

/**
* Creates a new {@link Service} in Kong if it does not exist.
*
Expand Down Expand Up @@ -158,14 +199,30 @@ private void performOperation(Collection<ModuleDescriptor> moduleDescriptors, St
}
}

private void performOperation(String tenant, Collection<ModuleDescriptor> moduleDescriptors, String operation,
BiFunction<String, ModuleDescriptor, Collection<Parameter>> moduleOperation) {
var allErrors = new ArrayList<Parameter>();
for (var moduleDescriptor : emptyIfNull(moduleDescriptors)) {
allErrors.addAll(moduleOperation.apply(tenant, moduleDescriptor));
}

if (isNotEmpty(allErrors)) {
throw new KongIntegrationException(String.format("Failed to %s routes", operation), allErrors);
}
}

private List<Parameter> updateRoutesForModule(ModuleDescriptor moduleDescriptor) {
return updateTenantRoutesForModule(moduleDescriptor, null);
}

private List<Parameter> updateTenantRoutesForModule(ModuleDescriptor moduleDescriptor, String tenant) {
var moduleId = moduleDescriptor.getId();
var serviceId = getExistingServiceId(moduleId);
var existingRouteNames = getKongRoutes(moduleId).stream()
var existingRouteNames = getKongRoutes(tenant, moduleId).stream()
.map(Route::getName)
.collect(toCollection(LinkedHashSet::new));

var routes = prepareRoutes(moduleDescriptor, moduleId);
var routes = prepareRoutes(moduleDescriptor, moduleId, tenant);
var newRoutesCreationErrors = toStream(routes)
.filter(not(pair -> existingRouteNames.contains(pair.getLeft().getName())))
.map(pair -> createKongRoute(serviceId, pair.getLeft(), pair.getRight()))
Expand All @@ -189,32 +246,36 @@ private List<Parameter> updateRoutesForModule(ModuleDescriptor moduleDescriptor)
}

private List<Parameter> addRoutesForModule(ModuleDescriptor moduleDescriptor) {
return addTenantRoutesForModule(moduleDescriptor, null);
}

private List<Parameter> addTenantRoutesForModule(ModuleDescriptor moduleDescriptor, String tenant) {
var moduleId = moduleDescriptor.getId();
var serviceId = getExistingServiceId(moduleId);
return prepareRoutes(moduleDescriptor, moduleId).stream()
return prepareRoutes(moduleDescriptor, moduleId, tenant).stream()
.map(kongRoutePair -> createKongRoute(serviceId, kongRoutePair.getLeft(), kongRoutePair.getRight()))
.flatMap(Optional::stream)
.toList();
}

private List<Pair<Route, RoutingEntry>> prepareRoutes(ModuleDescriptor desc, String moduleId) {
private List<Pair<Route, RoutingEntry>> prepareRoutes(ModuleDescriptor desc, String moduleId, String tenant) {
return toStream(desc.getProvides())
.map(interfaceDescriptor -> prepareRoutes(interfaceDescriptor, moduleId))
.map(interfaceDescriptor -> prepareRoutes(interfaceDescriptor, moduleId, tenant))
.flatMap(Collection::stream)
.toList();
}

private List<Pair<Route, RoutingEntry>> prepareRoutes(InterfaceDescriptor desc, String moduleId) {
private List<Pair<Route, RoutingEntry>> prepareRoutes(InterfaceDescriptor desc, String moduleId, String tenant) {
var interfaceId = desc.getId() + "-" + desc.getVersion();
if (desc.isSystem() && !desc.isTimer()) {
log.debug("System interface is ignored: moduleId={}, interfaceId={}]", moduleId, interfaceId);
log.debug("System interface is ignored: tenant={}, moduleId={}, interfaceId={}]", tenant, moduleId, interfaceId);
return emptyList();
}

var interfaceType = desc.getInterfaceType();
var isMultiple = StringUtils.equals(interfaceType, MULTIPLE_INTERFACE_TYPE);
return toStream(desc.getHandlers())
.map(routingEntry -> getRoute(moduleId, interfaceId, routingEntry, isMultiple)
.map(routingEntry -> getRoute(tenant, moduleId, interfaceId, routingEntry, isMultiple)
.map(route -> Pair.of(route, routingEntry)))
.flatMap(Optional::stream)
.toList();
Expand All @@ -238,13 +299,13 @@ private Optional<Parameter> createKongRoute(String serviceId, Route route, Routi
}
}

private List<Route> getKongRoutes(String moduleId) {
private List<Route> getKongRoutes(String tenantName, String moduleId) {
var routes = new ArrayList<Route>();
var errorParameters = new ArrayList<Parameter>();
String offset = null;
do {
try {
var routePage = kongAdminClient.getRoutesByTag(getTagsToSearch(moduleId), offset);
var routePage = kongAdminClient.getRoutesByTag(getTagsToSearch(tenantName, moduleId), offset);
routes.addAll(routePage.getData());
offset = routePage.getOffset();
} catch (Exception exception) {
Expand All @@ -261,8 +322,12 @@ private List<Route> getKongRoutes(String moduleId) {
}

private List<Parameter> removeKongRoutes(String moduleId) {
return removeKongTenantRoutes(null, moduleId);
}

private List<Parameter> removeKongTenantRoutes(String tenant, String moduleId) {
var serviceId = getExistingServiceId(moduleId);
return getKongRoutes(moduleId).stream()
return getKongRoutes(tenant, moduleId).stream()
.map(kongRoute -> deleteRoute(serviceId, kongRoute.getId()))
.flatMap(Optional::stream)
.toList();
Expand All @@ -278,38 +343,45 @@ private Optional<Parameter> deleteRoute(String serviceId, String routeIdOrName)
}

@SuppressWarnings("java:S4790")
private static Optional<Route> getRoute(String moduleId, String interfaceId, RoutingEntry re, boolean isMultiple) {
private static Optional<Route> getRoute(
String tenant, String moduleId, String interfaceId, RoutingEntry re, boolean isMultiple) {

var staticPath = re.getStaticPath();
var httpMethods = getMethods(re);
if (StringUtils.isEmpty(staticPath) || CollectionUtils.isEmpty(httpMethods)) {
log.debug("Route cannot be created: moduleId={}, interfaceId={}, routingEntry={}",
() -> moduleId, () -> interfaceId, () -> asString(re));
log.debug("Route cannot be created: moduleId={}, tenant={}, interfaceId={}, routingEntry={}",
() -> moduleId, () -> tenant, () -> interfaceId, () -> asString(re));
return Optional.empty();
}

var kongPathPair = updatePathPatternForKongGateway(staticPath);
var path = kongPathPair.getLeft();

var routeName = Stream.of(path, String.join(",", httpMethods), moduleId, interfaceId)
var routeName = Stream.of(path, String.join(",", httpMethods), tenant, moduleId, interfaceId)
.filter(StringUtils::isNotBlank)
.collect(joining("|"));

var pathExpression = path.endsWith("$") ? httpPath().regexMatching(path) : httpPath().equalsTo(path);
var methodsExpression = combineUsingOr(mapItems(httpMethods, method -> httpMethod().equalsTo(method)));
var headersExpression = getHeadersExpression(moduleId, isMultiple);
var headersExpression = getHeadersExpression(tenant, moduleId, isMultiple);
return Optional.of(
new Route()
.priority(kongPathPair.getRight())
.name(sha1Hex(routeName))
.expression(combineUsingAnd(getNonNullValues(pathExpression, methodsExpression, headersExpression)))
.tags(getNonNullValues(moduleId, interfaceId))
.tags(getNonNullValues(tenant, moduleId, interfaceId))
.stripPath(false)
);
}

private static RouteExpression getHeadersExpression(String moduleId, boolean isMultiple) {
return isMultiple ? httpHeader(MODULE_ID).equalsTo(moduleId) : null;
private static RouteExpression getHeadersExpression(String tenantId, String moduleId, boolean isMultiple) {
if (tenantId == null) {
return isMultiple ? httpHeader(MODULE_ID).equalsTo(moduleId) : null;
}

return isMultiple
? combineUsingAnd(httpHeader(TENANT).equalsTo(tenantId), httpHeader(MODULE_ID).equalsTo(moduleId))
: httpHeader(TENANT).equalsTo(tenantId);
}

/**
Expand Down
Loading
Loading