Support for script providers when running in embedded mode

Closes #27574

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2024-03-05 15:34:34 -03:00
parent ba7e27a105
commit d5a613cd6b
2 changed files with 89 additions and 33 deletions

View file

@ -126,6 +126,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
@ -694,12 +695,12 @@ class KeycloakProcessor {
}
}
Map<String, ProviderFactory> deployedScriptProviders = loadDeployedScriptProviders(classLoader, spi);
Map<String, ProviderFactory<?>> deployedScriptProviders = loadDeployedScriptProviders(classLoader, spi);
loadedFactories.addAll(deployedScriptProviders.values());
preConfiguredProviders.putAll(deployedScriptProviders);
for (ProviderFactory factory : loadedFactories) {
for (ProviderFactory<?> factory : loadedFactories) {
if (IGNORED_PROVIDER_FACTORY.contains(factory.getClass())) {
continue;
}
@ -724,31 +725,29 @@ class KeycloakProcessor {
return factories;
}
private Map<String, ProviderFactory> loadDeployedScriptProviders(ClassLoader classLoader, Spi spi) {
Map<String, ProviderFactory> providers = new HashMap<>();
private Map<String, ProviderFactory<?>> loadDeployedScriptProviders(ClassLoader classLoader, Spi spi) {
Map<String, ProviderFactory<?>> providers = new HashMap<>();
if (supportsDeployeableScripts(spi)) {
try {
Enumeration<URL> urls = classLoader.getResources(KEYCLOAK_SCRIPTS_JSON_PATH);
Enumeration<URL> descriptorsUrls = classLoader.getResources(KEYCLOAK_SCRIPTS_JSON_PATH);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
int fileSeparator = url.getFile().indexOf(JAR_FILE_SEPARATOR);
while (descriptorsUrls.hasMoreElements()) {
URL url = descriptorsUrls.nextElement();
List<ScriptProviderDescriptor> descriptors = getScriptProviderDescriptorsFromJarFile(url);
if (fileSeparator != -1) {
JarFile jarFile = new JarFile(url.getFile().substring("file:".length(), fileSeparator));
JarEntry descriptorEntry = jarFile.getJarEntry(KEYCLOAK_SCRIPTS_JSON_PATH);
ScriptProviderDescriptor descriptor;
try (InputStream is = jarFile.getInputStream(descriptorEntry)) {
descriptor = JsonSerialization.readValue(is, ScriptProviderDescriptor.class);
}
if (!Environment.isDistribution()) {
// script providers are only loaded from classpath when running embedded
descriptors = new ArrayList<>(descriptors);
descriptors.addAll(getScriptProviderDescriptorsFromClassPath(url));
}
for (ScriptProviderDescriptor descriptor : descriptors) {
for (Entry<String, List<ScriptProviderMetadata>> entry : descriptor.getProviders().entrySet()) {
if (isScriptForSpi(spi, entry.getKey())) {
for (ScriptProviderMetadata metadata : entry.getValue()) {
ProviderFactory provider = createDeployableScriptProvider(jarFile, entry, metadata);
providers.put(metadata.getId(), provider);
ProviderFactory<?> factory = DEPLOYEABLE_SCRIPT_PROVIDERS.get(entry.getKey()).apply(metadata);
providers.put(metadata.getId(), factory);
}
}
}
@ -762,31 +761,89 @@ class KeycloakProcessor {
return providers;
}
private ProviderFactory createDeployableScriptProvider(JarFile jarFile, Entry<String, List<ScriptProviderMetadata>> entry,
ScriptProviderMetadata metadata) throws IOException {
String fileName = metadata.getFileName();
private List<ScriptProviderDescriptor> getScriptProviderDescriptorsFromClassPath(URL url) throws IOException {
String file = url.getFile();
if (fileName == null) {
throw new RuntimeException("You must provide the script file name");
if (!file.endsWith(".json")) {
return List.of();
}
JarEntry scriptFile = jarFile.getJarEntry(fileName);
List<ScriptProviderDescriptor> descriptors = new ArrayList<>();
try (InputStream in = jarFile.getInputStream(scriptFile)) {
metadata.setCode(StreamUtil.readString(in, StandardCharsets.UTF_8));
try (InputStream is = url.openStream()) {
ScriptProviderDescriptor descriptor = JsonSerialization.readValue(is, ScriptProviderDescriptor.class);
configureScriptDescriptor(descriptor, fileName -> {
// descriptor is at META-INF/
Path basePath = Path.of(url.getPath()).getParent().getParent();
try {
return basePath.resolve(fileName).toUri().toURL().openStream();
} catch (IOException e) {
throw new RuntimeException("Failed to read script file from: " + fileName);
}
});
descriptors.add(descriptor);
}
metadata.setId(new StringBuilder("script").append("-").append(fileName).toString());
return descriptors;
}
String name = metadata.getName();
private List<ScriptProviderDescriptor> getScriptProviderDescriptorsFromJarFile(URL url) throws IOException {
String file = url.getFile();
if (name == null) {
name = fileName;
if (!file.contains(JAR_FILE_SEPARATOR)) {
return List.of();
}
metadata.setName(name);
List<ScriptProviderDescriptor> descriptors = new ArrayList<>();
return DEPLOYEABLE_SCRIPT_PROVIDERS.get(entry.getKey()).apply(metadata);
try (JarFile jarFile = new JarFile(file.substring("file:".length(), file.indexOf(JAR_FILE_SEPARATOR)))) {
JarEntry descriptorEntry = jarFile.getJarEntry(KEYCLOAK_SCRIPTS_JSON_PATH);
try (InputStream is = jarFile.getInputStream(descriptorEntry)) {
ScriptProviderDescriptor descriptor = JsonSerialization.readValue(is, ScriptProviderDescriptor.class);
configureScriptDescriptor(descriptor, fileName -> {
try {
JarEntry scriptFile = jarFile.getJarEntry(fileName);
return jarFile.getInputStream(scriptFile);
} catch (IOException cause) {
throw new RuntimeException("Failed to read script file from file: " + fileName, cause);
}
});
descriptors.add(descriptor);
}
}
return descriptors;
}
private static void configureScriptDescriptor(ScriptProviderDescriptor descriptor, Function<String, InputStream> jsFileLoader) throws IOException {
for (List<ScriptProviderMetadata> metadatas : descriptor.getProviders().values()) {
for (ScriptProviderMetadata metadata : metadatas) {
String fileName = metadata.getFileName();
if (fileName == null) {
throw new RuntimeException("You must provide the script file name");
}
try (InputStream in = jsFileLoader.apply(fileName)) {
metadata.setCode(StreamUtil.readString(in, StandardCharsets.UTF_8));
}
metadata.setId(new StringBuilder("script").append("-").append(fileName).toString());
String name = metadata.getName();
if (name == null) {
name = fileName;
}
metadata.setName(name);
}
}
}
private boolean isScriptForSpi(Spi spi, String type) {

View file

@ -58,7 +58,6 @@ When running in embedded mode, the `build` phase happens every time the server i
There are a few limitations when running tests. The well-known limitations are:
* FIPS tests not working
* Deploying script providers not working. Probably any test deploying JAR files.
* Re-starting the server during a test execution is taking too much metaspace. Need more investigation.
## Debugging - tips & tricks