diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ImportRealmMixin.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ImportRealmMixin.java index 10dc2634b3..1656bbbc5f 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ImportRealmMixin.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ImportRealmMixin.java @@ -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()); } } } diff --git a/services/src/main/java/org/keycloak/exportimport/ExportImportConfig.java b/services/src/main/java/org/keycloak/exportimport/ExportImportConfig.java index 08c26909d3..1b6dfed295 100644 --- a/services/src/main/java/org/keycloak/exportimport/ExportImportConfig.java +++ b/services/src/main/java/org/keycloak/exportimport/ExportImportConfig.java @@ -18,6 +18,8 @@ package org.keycloak.exportimport; import java.io.Closeable; +import java.util.Optional; +import java.util.stream.Stream; /** * @author Stian Thorgersen @@ -59,6 +61,18 @@ public class ExportImportConfig { public static String getAction() { 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 getDir() { + return Optional.ofNullable(System.getProperty(DIR)); + } public static Closeable setAction(String exportImportAction) { System.setProperty(ACTION, exportImportAction); @@ -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)); + } } diff --git a/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java b/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java index 1fc0a9fa81..adab353850 100644 --- a/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java +++ b/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java @@ -73,37 +73,36 @@ 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"); - } - try { - return importProvider.isMasterRealmExported(); - } catch (IOException e) { - throw new RuntimeException(e); + 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 -> { - try { - return provider.isMasterRealmExported(); - } catch (IOException e) { - throw new RuntimeException("Failed to run import", e); - } - }); + boolean isImportMasterIncludedAtStartup() { + return getStartupImportProviders().map(Supplier::get) + .anyMatch(provider -> { + try { + return provider.isMasterRealmExported(); + } catch (IOException e) { + throw new RuntimeException("Failed to run import", e); + } + }); } public boolean isRunExport() { @@ -111,22 +110,19 @@ public class ExportImportManager { } public void runImport() { - try { - importProvider.importModel(); - } catch (IOException e) { - throw new RuntimeException("Failed to run import", e); + 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> getStartupImportProviders(String dir) { + private Stream> getStartupImportProviders() { + var dirProp = ExportImportConfig.getDir(); + if (dirProp.isEmpty()) { + return Stream.empty(); + } + String dir = dirProp.get(); + Stream factories = sessionFactory.getProviderFactoriesStream(ImportProvider.class); return factories.flatMap(factory -> { String providerId = factory.getId(); if ("dir".equals(providerId)) { - Supplier func = () -> { - ExportImportConfig.setDir(dir); - return session.getProvider(ImportProvider.class, providerId); - }; + Supplier func = () -> session.getProvider(ImportProvider.class, providerId); return Stream.of(func); } if ("singleFile".equals(providerId)) { diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index aff7b497a0..90f62adf59 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -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 getImportDirectory() { - return Optional.ofNullable(System.getProperty("keycloak.import")); - } - public void importRealm(RealmRepresentation rep, String from) { boolean exists = false; try (KeycloakSession session = sessionFactory.create()) { diff --git a/services/src/test/java/org/keycloak/exportimport/ExportImportManagerTest.java b/services/src/test/java/org/keycloak/exportimport/ExportImportManagerTest.java new file mode 100644 index 0000000000..6b22706ed9 --- /dev/null +++ b/services/src/test/java/org/keycloak/exportimport/ExportImportManagerTest.java @@ -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 getProvider(Class 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()); + } + +}