Skip to content

Commit

Permalink
KEYCLOAK-25 Increase keycloak test container to v26.1.0, admin librar…
Browse files Browse the repository at this point in the history
…y to v26.0.4 (#174)

Co-authored-by: Yauhen Vavilkin <[email protected]>

(cherry picked from commit d5485cd)
  • Loading branch information
pfilippov-epam authored and yauhen-vavilkin committed Jan 29, 2025
1 parent 71713fe commit a60752a
Show file tree
Hide file tree
Showing 5 changed files with 380 additions and 24 deletions.
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

0 comments on commit a60752a

Please sign in to comment.