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 static org.keycloak.quarkus.runtime.cli.Picocli.NO_PARAM_LABEL;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
import org.keycloak.exportimport.ExportImportConfig;
|
||||||
import org.keycloak.quarkus.runtime.Environment;
|
import org.keycloak.quarkus.runtime.Environment;
|
||||||
|
|
||||||
import picocli.CommandLine;
|
import picocli.CommandLine;
|
||||||
|
@ -39,7 +41,7 @@ public final class ImportRealmMixin {
|
||||||
File importDir = Environment.getHomePath().resolve("data").resolve("import").toFile();
|
File importDir = Environment.getHomePath().resolve("data").resolve("import").toFile();
|
||||||
|
|
||||||
if (importDir.exists()) {
|
if (importDir.exists()) {
|
||||||
System.setProperty("keycloak.import", importDir.getAbsolutePath());
|
ExportImportConfig.setDir(importDir.getAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
package org.keycloak.exportimport;
|
package org.keycloak.exportimport;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -60,6 +62,18 @@ public class ExportImportConfig {
|
||||||
return System.getProperty(ACTION);
|
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) {
|
public static Closeable setAction(String exportImportAction) {
|
||||||
System.setProperty(ACTION, exportImportAction);
|
System.setProperty(ACTION, exportImportAction);
|
||||||
return () -> System.getProperties().remove(ACTION);
|
return () -> System.getProperties().remove(ACTION);
|
||||||
|
@ -92,4 +106,9 @@ public class ExportImportConfig {
|
||||||
public static void setReplacePlaceholders(boolean replacePlaceholders) {
|
public static void setReplacePlaceholders(boolean replacePlaceholders) {
|
||||||
System.setProperty(REPLACE_PLACEHOLDERS, String.valueOf(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,31 +73,30 @@ public class ExportImportManager {
|
||||||
if (importProvider == null) {
|
if (importProvider == null) {
|
||||||
throw new RuntimeException("Import provider '" + providerId + "' not found");
|
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() {
|
public boolean isImportMasterIncluded() {
|
||||||
if (!isRunImport()) {
|
if (importProvider != null) {
|
||||||
throw new IllegalStateException("Import not enabled");
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
return importProvider.isMasterRealmExported();
|
return importProvider.isMasterRealmExported();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(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 {
|
try {
|
||||||
return provider.isMasterRealmExported();
|
return provider.isMasterRealmExported();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -111,22 +110,19 @@ public class ExportImportManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runImport() {
|
public void runImport() {
|
||||||
|
if (importProvider != null) {
|
||||||
try {
|
try {
|
||||||
importProvider.importModel();
|
importProvider.importModel();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("Failed to run import", e);
|
throw new RuntimeException("Failed to run import", e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
runImportAtStartup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runImportAtStartup(String dir) throws IOException {
|
public void runImportAtStartup() {
|
||||||
System.setProperty(ExportImportConfig.STRATEGY, Strategy.IGNORE_EXISTING.toString());
|
getStartupImportProviders().map(Supplier::get).forEach(ip -> {
|
||||||
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 -> {
|
|
||||||
try {
|
try {
|
||||||
ip.importModel();
|
ip.importModel();
|
||||||
} catch (IOException e) {
|
} 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);
|
Stream<ProviderFactory> factories = sessionFactory.getProviderFactoriesStream(ImportProvider.class);
|
||||||
|
|
||||||
return factories.flatMap(factory -> {
|
return factories.flatMap(factory -> {
|
||||||
String providerId = factory.getId();
|
String providerId = factory.getId();
|
||||||
|
|
||||||
if ("dir".equals(providerId)) {
|
if ("dir".equals(providerId)) {
|
||||||
Supplier<ImportProvider> func = () -> {
|
Supplier<ImportProvider> func = () -> session.getProvider(ImportProvider.class, providerId);
|
||||||
ExportImportConfig.setDir(dir);
|
|
||||||
return session.getProvider(ImportProvider.class, providerId);
|
|
||||||
};
|
|
||||||
return Stream.of(func);
|
return Stream.of(func);
|
||||||
}
|
}
|
||||||
if ("singleFile".equals(providerId)) {
|
if ("singleFile".equals(providerId)) {
|
||||||
|
|
|
@ -48,7 +48,6 @@ import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
@ -152,22 +151,18 @@ public abstract class KeycloakApplication extends Application {
|
||||||
var exportImportManager = bootstrapState.exportImportManager = new ExportImportManager(session);
|
var exportImportManager = bootstrapState.exportImportManager = new ExportImportManager(session);
|
||||||
bootstrapState.newInstall = applianceBootstrap.isNewInstall();
|
bootstrapState.newInstall = applianceBootstrap.isNewInstall();
|
||||||
if (bootstrapState.newInstall) {
|
if (bootstrapState.newInstall) {
|
||||||
// check if this is an import command that is importing the master realm
|
if (!exportImportManager.isImportMasterIncluded()) {
|
||||||
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) {
|
|
||||||
applianceBootstrap.createMasterRealm();
|
applianceBootstrap.createMasterRealm();
|
||||||
}
|
}
|
||||||
// these are also running in the initial bootstrap transaction - if there is a problem, the server won't be initialized at all
|
// 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);
|
createTemporaryAdmin(session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!bootstrapState.newInstall) {
|
if (!bootstrapState.newInstall) {
|
||||||
runImports(bootstrapState.exportImportManager);
|
bootstrapState.exportImportManager.runImport();
|
||||||
}
|
}
|
||||||
|
|
||||||
importAddUser();
|
importAddUser();
|
||||||
|
@ -175,14 +170,6 @@ public abstract class KeycloakApplication extends Application {
|
||||||
return bootstrapState.exportImportManager;
|
return bootstrapState.exportImportManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runImports(ExportImportManager exportImportManager) {
|
|
||||||
if (exportImportManager.isRunImport()) {
|
|
||||||
exportImportManager.runImport();
|
|
||||||
} else {
|
|
||||||
importRealms(exportImportManager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void createTemporaryAdmin(KeycloakSession session);
|
protected abstract void createTemporaryAdmin(KeycloakSession session);
|
||||||
|
|
||||||
protected void loadConfig() {
|
protected void loadConfig() {
|
||||||
|
@ -205,20 +192,6 @@ public abstract class KeycloakApplication extends Application {
|
||||||
return sessionFactory;
|
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) {
|
public void importRealm(RealmRepresentation rep, String from) {
|
||||||
boolean exists = false;
|
boolean exists = false;
|
||||||
try (KeycloakSession session = sessionFactory.create()) {
|
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