diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Configuration.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Configuration.java index 3d95a101a5..284354b0a5 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Configuration.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Configuration.java @@ -29,9 +29,9 @@ import io.smallrye.config.SmallRyeConfig; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.eclipse.microprofile.config.spi.ConfigSource; import org.keycloak.config.Option; -import org.keycloak.quarkus.runtime.Environment; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; +import org.keycloak.utils.StringUtil; import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX; @@ -56,6 +56,12 @@ public final class Configuration { return getOptionalBooleanValue(propertyName).orElse(false); } + public static boolean isBlank(Option option) { + return getOptionalKcValue(option.getKey()) + .map(StringUtil::isBlank) + .orElse(true); + } + public static boolean contains(Option option, String value) { return getOptionalValue(NS_KEYCLOAK_PREFIX + option.getKey()) .filter(f -> f.contains(value)) diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ExportPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ExportPropertyMappers.java index 8a8d53c78c..a2d101fb20 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ExportPropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ExportPropertyMappers.java @@ -23,6 +23,7 @@ import org.keycloak.config.ExportOptions; import org.keycloak.config.Option; import org.keycloak.config.OptionBuilder; import org.keycloak.config.OptionCategory; +import org.keycloak.exportimport.UsersExportStrategy; import org.keycloak.quarkus.runtime.cli.PropertyException; import org.keycloak.quarkus.runtime.configuration.Configuration; @@ -30,6 +31,7 @@ import java.util.Optional; import static org.keycloak.exportimport.ExportImportConfig.PROVIDER; import static org.keycloak.quarkus.runtime.configuration.Configuration.getOptionalValue; +import static org.keycloak.quarkus.runtime.configuration.Configuration.isBlank; import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption; public final class ExportPropertyMappers { @@ -67,7 +69,7 @@ public final class ExportPropertyMappers { .build(), fromOption(ExportOptions.USERS) .to("kc.spi-export-dir-users-export-strategy") - .isEnabled(ExportPropertyMappers::isDirProvider) + .validator(ExportPropertyMappers::validateUsersUsage) .paramLabel("strategy") .build(), fromOption(ExportOptions.USERS_PER_FILE) @@ -78,6 +80,18 @@ public final class ExportPropertyMappers { }; } + private static void validateUsersUsage(PropertyMapper mapper, ConfigValue value) { + mapper.validateExpectedValues(value, mapper::validateSingleValue); + + if (!isBlank(ExportOptions.FILE) && isBlank(ExportOptions.DIR)) { + var sameFileIsSpecified = UsersExportStrategy.SAME_FILE.toString().toLowerCase().equals(value.getValue()); + + if (!sameFileIsSpecified) { + throw new PropertyException("Property '--users' can be used only when exporting to a directory, or value set to 'same_file' when exporting to a file."); + } + } + } + public static void validateConfig() { if (getOptionalValue(EXPORTER_PROPERTY).isEmpty() && System.getProperty(PROVIDER) == null) { throw new PropertyException("Must specify either --dir or --file options."); diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ExportDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ExportDistTest.java index f688eb714b..2f1b4a02d4 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ExportDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ExportDistTest.java @@ -39,5 +39,16 @@ public class ExportDistTest { cliResult = dist.run("export", "--realm=master"); cliResult.assertError("Must specify either --dir or --file options."); + + cliResult = dist.run("export", "--file=master", "--users=skip"); + cliResult.assertError("Property '--users' can be used only when exporting to a directory, or value set to 'same_file' when exporting to a file."); + + cliResult = dist.run("export", "--file=some-file", "--users=same_file"); + cliResult.assertNoError("Property '--users' can be used only when exporting to a directory, or value set to 'same_file' when exporting to a file."); + cliResult.assertMessage("Exporting model into file"); + + cliResult = dist.run("export", "--dir=some-dir", "--users=skip"); + cliResult.assertMessage("Realm 'master' - data exported"); + } } diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java index 11226d30d6..8e8f51f98d 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java @@ -79,6 +79,11 @@ public interface CLIResult extends LaunchResult { () -> "The Error Output:\n " + getErrorOutput() + "\ndoesn't contains " + msg); } + default void assertNoError(String msg) { + assertFalse(getErrorOutput().contains(msg), + () -> "The Error Output:\n " + getErrorOutput() + "\n contains " + msg); + } + default void assertMessage(String message) { assertThat(getOutput(), containsString(message)); }