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:
parent
e6cd1a05c1
commit
307041c021
5 changed files with 145 additions and 73 deletions
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
@ -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<String> 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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)) {
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue