fix: encapsulating where static import/export state is set/used (#33690)

closes: #33596

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2024-10-22 10:03:39 -04:00 committed by GitHub
parent e6cd1a05c1
commit 307041c021
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 145 additions and 73 deletions

View file

@ -20,6 +20,8 @@ package org.keycloak.quarkus.runtime.cli.command;
import static org.keycloak.quarkus.runtime.cli.Picocli.NO_PARAM_LABEL;
import java.io.File;
import org.keycloak.exportimport.ExportImportConfig;
import org.keycloak.quarkus.runtime.Environment;
import picocli.CommandLine;
@ -39,7 +41,7 @@ public final class ImportRealmMixin {
File importDir = Environment.getHomePath().resolve("data").resolve("import").toFile();
if (importDir.exists()) {
System.setProperty("keycloak.import", importDir.getAbsolutePath());
ExportImportConfig.setDir(importDir.getAbsolutePath());
}
}
}

View file

@ -18,6 +18,8 @@
package org.keycloak.exportimport;
import java.io.Closeable;
import java.util.Optional;
import java.util.stream.Stream;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -60,6 +62,18 @@ public class ExportImportConfig {
return System.getProperty(ACTION);
}
public static String getStrategy() {
return System.getProperty(STRATEGY);
}
public static String setStrategy(Strategy strategy) {
return System.setProperty(STRATEGY, strategy.toString());
}
public static Optional<String> getDir() {
return Optional.ofNullable(System.getProperty(DIR));
}
public static Closeable setAction(String exportImportAction) {
System.setProperty(ACTION, exportImportAction);
return () -> System.getProperties().remove(ACTION);
@ -92,4 +106,9 @@ public class ExportImportConfig {
public static void setReplacePlaceholders(boolean replacePlaceholders) {
System.setProperty(REPLACE_PLACEHOLDERS, String.valueOf(replacePlaceholders));
}
public static void reset() {
Stream.of(FILE, DIR, ACTION, STRATEGY, REPLACE_PLACEHOLDERS)
.forEach(prop -> System.getProperties().remove(prop));
}
}

View file

@ -73,31 +73,30 @@ public class ExportImportManager {
if (importProvider == null) {
throw new RuntimeException("Import provider '" + providerId + "' not found");
}
} else if (ExportImportConfig.getDir().isPresent()) { // import at startup
ExportImportConfig.setStrategy(Strategy.IGNORE_EXISTING);
ExportImportConfig.setReplacePlaceholders(true);
// enables logging of what is imported
ExportImportConfig.setAction(ExportImportConfig.ACTION_IMPORT);
}
}
public boolean isRunImport() {
return importProvider != null;
}
public boolean isImportMasterIncluded() {
if (!isRunImport()) {
throw new IllegalStateException("Import not enabled");
}
if (importProvider != null) {
try {
return importProvider.isMasterRealmExported();
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
return isImportMasterIncludedAtStartup();
}
public boolean isImportMasterIncludedAtStartup(String dir) {
if (dir == null) {
throw new IllegalStateException("Import not enabled");
}
ExportImportConfig.setReplacePlaceholders(true);
return getStartupImportProviders(dir).map(Supplier::get).anyMatch(provider -> {
boolean isImportMasterIncludedAtStartup() {
return getStartupImportProviders().map(Supplier::get)
.anyMatch(provider -> {
try {
return provider.isMasterRealmExported();
} catch (IOException e) {
@ -111,22 +110,19 @@ public class ExportImportManager {
}
public void runImport() {
if (importProvider != null) {
try {
importProvider.importModel();
} catch (IOException e) {
throw new RuntimeException("Failed to run import", e);
}
} else {
runImportAtStartup();
}
}
public void runImportAtStartup(String dir) throws IOException {
System.setProperty(ExportImportConfig.STRATEGY, Strategy.IGNORE_EXISTING.toString());
ExportImportConfig.setReplacePlaceholders(true);
// enables logging of what is imported
ExportImportConfig.setAction(ExportImportConfig.ACTION_IMPORT);
// TODO: ideally the static setting above should be unset after this is run
getStartupImportProviders(dir).map(Supplier::get).forEach(ip -> {
public void runImportAtStartup() {
getStartupImportProviders().map(Supplier::get).forEach(ip -> {
try {
ip.importModel();
} catch (IOException e) {
@ -135,17 +131,20 @@ public class ExportImportManager {
});
}
private Stream<Supplier<ImportProvider>> getStartupImportProviders(String dir) {
private Stream<Supplier<ImportProvider>> getStartupImportProviders() {
var dirProp = ExportImportConfig.getDir();
if (dirProp.isEmpty()) {
return Stream.empty();
}
String dir = dirProp.get();
Stream<ProviderFactory> factories = sessionFactory.getProviderFactoriesStream(ImportProvider.class);
return factories.flatMap(factory -> {
String providerId = factory.getId();
if ("dir".equals(providerId)) {
Supplier<ImportProvider> func = () -> {
ExportImportConfig.setDir(dir);
return session.getProvider(ImportProvider.class, providerId);
};
Supplier<ImportProvider> func = () -> session.getProvider(ImportProvider.class, providerId);
return Stream.of(func);
}
if ("singleFile".equals(providerId)) {

View file

@ -48,7 +48,6 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.ServiceLoader;
import com.fasterxml.jackson.core.type.TypeReference;
@ -152,22 +151,18 @@ public abstract class KeycloakApplication extends Application {
var exportImportManager = bootstrapState.exportImportManager = new ExportImportManager(session);
bootstrapState.newInstall = applianceBootstrap.isNewInstall();
if (bootstrapState.newInstall) {
// check if this is an import command that is importing the master realm
boolean importingMaster = exportImportManager.isRunImport() && exportImportManager.isImportMasterIncluded();
// check if this is a start command that is importing the master realm
importingMaster |= getImportDirectory().filter(exportImportManager::isImportMasterIncludedAtStartup).isPresent();
if (!importingMaster) {
if (!exportImportManager.isImportMasterIncluded()) {
applianceBootstrap.createMasterRealm();
}
// these are also running in the initial bootstrap transaction - if there is a problem, the server won't be initialized at all
runImports(exportImportManager);
exportImportManager.runImport();
createTemporaryAdmin(session);
}
}
});
if (!bootstrapState.newInstall) {
runImports(bootstrapState.exportImportManager);
bootstrapState.exportImportManager.runImport();
}
importAddUser();
@ -175,14 +170,6 @@ public abstract class KeycloakApplication extends Application {
return bootstrapState.exportImportManager;
}
private void runImports(ExportImportManager exportImportManager) {
if (exportImportManager.isRunImport()) {
exportImportManager.runImport();
} else {
importRealms(exportImportManager);
}
}
protected abstract void createTemporaryAdmin(KeycloakSession session);
protected void loadConfig() {
@ -205,20 +192,6 @@ public abstract class KeycloakApplication extends Application {
return sessionFactory;
}
public void importRealms(ExportImportManager exportImportManager) {
getImportDirectory().ifPresent(dir -> {
try {
exportImportManager.runImportAtStartup(dir);
} catch (IOException cause) {
throw new RuntimeException("Failed to import realms", cause);
}
});
}
private Optional<String> getImportDirectory() {
return Optional.ofNullable(System.getProperty("keycloak.import"));
}
public void importRealm(RealmRepresentation rep, String from) {
boolean exists = false;
try (KeycloakSession session = sessionFactory.create()) {

View file

@ -0,0 +1,79 @@
package org.keycloak.exportimport;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import org.junit.After;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.Provider;
import org.keycloak.services.DefaultKeycloakContext;
import org.keycloak.services.DefaultKeycloakSession;
public class ExportImportManagerTest {
@After
public void reset() {
ExportImportConfig.reset();
}
@Test
public void testImportOnStartup() {
ExportImportConfig.setDir("/some/dir");
new ExportImportManager(new DefaultKeycloakSession(null) {
@Override
protected DefaultKeycloakContext createKeycloakContext(KeycloakSession session) {
return null;
}
});
assertEquals(ExportImportConfig.ACTION_IMPORT, ExportImportConfig.getAction());
assertEquals(Strategy.IGNORE_EXISTING.toString(), ExportImportConfig.getStrategy());
assertTrue(ExportImportConfig.isReplacePlaceholders());
}
@Test
public void testImport() {
ExportImportConfig.setAction(ExportImportConfig.ACTION_IMPORT);
new ExportImportManager(new DefaultKeycloakSession(null) {
@Override
protected DefaultKeycloakContext createKeycloakContext(KeycloakSession session) {
return null;
}
@Override
public <T extends Provider> T getProvider(Class<T> clazz, String id) {
return (T) new ImportProvider() {
@Override
public void close() {
}
@Override
public boolean isMasterRealmExported() throws IOException {
return false;
}
@Override
public void importModel() throws IOException {
}
};
}
});
assertEquals(ExportImportConfig.ACTION_IMPORT, ExportImportConfig.getAction());
assertNull(ExportImportConfig.getStrategy());
// we're now setting this in the Quarkus logic, it's left as false in the ExportImportManager
// for arquillian, or other legacy usage
assertFalse(ExportImportConfig.isReplacePlaceholders());
}
}