diff --git a/README.md b/README.md
index d1af99e..ca00877 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ mvn quarkus:dev "-Dquarkus.args=-h"
Starting the import of a configuration can be done as follows:
```bash
-mvn quarkus:dev "-Dquarkus.args=-s http://localhost:40800 -u keycloak -p root configure -c ../keycloak-configuration-eam"
+mvn quarkus:dev "-Dquarkus.args=configure -s http://localhost:40800 -u keycloak -p root -c ../keycloak-configuration-eam"
```
The help of a sub-command is shown as follows:
@@ -48,3 +48,32 @@ The help of a sub-command is shown as follows:
```bash
mvn quarkus:dev "-Dquarkus.args=configure -h"
```
+
+## Execution examples
+
+- Show help:
+ ```bash
+ mvn quarkus:dev "-Dquarkus.args=-h"
+ ```
+- Show help of the `configure` sub-command:
+ ```bash
+ mvn quarkus:dev "-Dquarkus.args=configure -h"
+ ```
+- Execute configuration import:
+ ```bash
+ mvn quarkus:dev "-Dquarkus.args=configure -s http://localhost:40800 -u keycloak -p root -c ../keycloak-configuration-eam"
+ ```
+- Show help of the `export-secrets` sub-command:
+ ```bash
+ mvn quarkus:dev "-Dquarkus.args=export-secrets -h"
+ ```
+- Export client secrets of all clients of the realm `eam`:
+ ```bash
+ mvn quarkus:dev "-Dquarkus.args=export-secrets -s http://localhost:40800 -u keycloak -p root -r eam"
+ ```
+
+Specify the log level via `-Dquarkus.log.level`. For example, to set the log level to `INFO`:
+
+```bash
+mvn quarkus:dev "-Dquarkus.args=export-secrets -s http://localhost:40800 -u keycloak -p root -r eam" "-Dquarkus.log.level=INFO"
+```
diff --git a/pom.xml b/pom.xml
index 5377301..7c1bced 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,6 +31,7 @@
3.4.1
+ 2.3
1.18.28
@@ -74,6 +75,12 @@
quarkus-picocli
+
+ org.apache.velocity
+ velocity-engine-core
+ ${velocity.version}
+
+
org.projectlombok
lombok
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/boundary/AbstractImporter.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/boundary/AbstractImporter.java
index 2aecd95..5e39af0 100644
--- a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/boundary/AbstractImporter.java
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/boundary/AbstractImporter.java
@@ -12,6 +12,7 @@
import java.util.regex.Pattern;
import java.util.stream.Stream;
+import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
import org.keycloak.admin.client.Keycloak;
@@ -19,8 +20,8 @@
import com.cycrilabs.keycloak.configurator.commands.configure.control.EntityStore;
import com.cycrilabs.keycloak.configurator.commands.configure.entity.ConfigureCommandConfiguration;
import com.cycrilabs.keycloak.configurator.commands.configure.entity.EntityImportType;
-import com.cycrilabs.keycloak.configurator.shared.control.JsonbConfigurator;
-import com.cycrilabs.keycloak.configurator.shared.entity.KeycloakConfiguration;
+import com.cycrilabs.keycloak.configurator.shared.control.JsonbFactory;
+import com.cycrilabs.keycloak.configurator.shared.control.KeycloakFactory;
import io.quarkus.logging.Log;
@@ -28,14 +29,15 @@ public abstract class AbstractImporter {
public static final String PATH_SEPARATOR = Pattern.quote(System.getProperty("file.separator"));
@Inject
- protected KeycloakConfiguration configuration;
- @Inject
- protected ConfigureCommandConfiguration subConfiguration;
- @Inject
+ protected ConfigureCommandConfiguration configuration;
protected Keycloak keycloak;
-
protected EntityStore entityStore;
+ @PostConstruct
+ public void init() {
+ keycloak = KeycloakFactory.create(configuration);
+ }
+
protected T loadEntity(final Path filepath, final Class dtoClass) {
final String json = loadJsonFromResource(filepath);
return fromJson(json, dtoClass);
@@ -51,12 +53,12 @@ private String loadJsonFromResource(final Path filePath) {
}
private T fromJson(final String content, final Class dtoClass) {
- return JsonbConfigurator.getJsonb().fromJson(content, dtoClass);
+ return JsonbFactory.getJsonb().fromJson(content, dtoClass);
}
public void runImport(final EntityStore entityStore) {
- this.entityStore = entityStore;
Log.infof("Executing importer %s.", getClass().getSimpleName());
+ this.entityStore = entityStore;
final List importFiles = getEntityFilePaths(getEntityDirectory());
for (final Path importFile : importFiles) {
final Object o = importFile(importFile);
@@ -65,7 +67,7 @@ public void runImport(final EntityStore entityStore) {
}
private List getEntityFilePaths(final String entityDir) {
- final String dir = Paths.get(subConfiguration.getConfigDirectory(), entityDir).toString();
+ final String dir = Paths.get(configuration.getConfigDirectory(), entityDir).toString();
try (final Stream stream = Files.walk(Paths.get(dir))) {
return stream
.filter(Files::isRegularFile)
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommand.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommand.java
index 3803f00..fc5779d 100644
--- a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommand.java
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommand.java
@@ -6,13 +6,16 @@
import jakarta.inject.Inject;
import com.cycrilabs.keycloak.configurator.commands.configure.boundary.AbstractImporter;
-import com.cycrilabs.keycloak.configurator.shared.entity.KeycloakConfiguration;
+import com.cycrilabs.keycloak.configurator.commands.configure.entity.ConfigureCommandConfiguration;
+import com.cycrilabs.keycloak.configurator.shared.control.KeycloakOptions;
import io.quarkus.logging.Log;
import picocli.CommandLine;
@CommandLine.Command(name = "configure", mixinStandardHelpOptions = true)
public class ConfigureCommand implements Runnable {
+ @CommandLine.Mixin
+ KeycloakOptions keycloakOptions;
@CommandLine.Option(required = true, names = { "-c", "--config" },
description = "Directory containing the keycloak configuration files.")
String configDirectory = "";
@@ -20,14 +23,14 @@ public class ConfigureCommand implements Runnable {
@Inject
EntityStore entityStore;
@Inject
- KeycloakConfiguration configuration;
+ ConfigureCommandConfiguration configuration;
@Inject
Instance importers;
@Override
public void run() {
Log.infof("Running importers for server %s with configuration %s.",
- configuration.getServer(), configDirectory);
+ configuration.getServer(), configuration.getConfigDirectory());
importers.stream()
.sorted(Comparator.comparingInt(AbstractImporter::getPriority))
.forEach(importer -> importer.runImport(entityStore));
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommandConfigurationProducer.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommandConfigurationProducer.java
index 41f0c06..f9f13f3 100644
--- a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommandConfigurationProducer.java
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommandConfigurationProducer.java
@@ -12,8 +12,6 @@ public class ConfigureCommandConfigurationProducer {
@Produces
@ApplicationScoped
ConfigureCommandConfiguration createConfiguration(final CommandLine.ParseResult parseResult) {
- return new ConfigureCommandConfiguration(
- parseResult.subcommand().matchedOption("c").getValue().toString()
- );
+ return new ConfigureCommandConfiguration(parseResult);
}
}
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/entity/ConfigureCommandConfiguration.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/entity/ConfigureCommandConfiguration.java
index cd2fd9c..4ff92e5 100644
--- a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/entity/ConfigureCommandConfiguration.java
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/entity/ConfigureCommandConfiguration.java
@@ -1,10 +1,17 @@
package com.cycrilabs.keycloak.configurator.commands.configure.entity;
-import lombok.AllArgsConstructor;
import lombok.Getter;
-@AllArgsConstructor
+import com.cycrilabs.keycloak.configurator.shared.entity.KeycloakConfiguration;
+
+import picocli.CommandLine.ParseResult;
+
@Getter
-public class ConfigureCommandConfiguration {
- String configDirectory;
+public class ConfigureCommandConfiguration extends KeycloakConfiguration {
+ private final String configDirectory;
+
+ public ConfigureCommandConfiguration(final ParseResult parseResult) {
+ super(parseResult);
+ configDirectory = getMatchedOption(parseResult, "-c");
+ }
}
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/boundary/ExportSecrets.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/boundary/ExportSecrets.java
new file mode 100644
index 0000000..c9d283c
--- /dev/null
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/boundary/ExportSecrets.java
@@ -0,0 +1,99 @@
+package com.cycrilabs.keycloak.configurator.commands.secrets.boundary;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.velocity.Template;
+import org.apache.velocity.runtime.parser.ParseException;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.representations.idm.ClientRepresentation;
+
+import com.cycrilabs.keycloak.configurator.commands.secrets.entity.ExportSecretsCommandConfiguration;
+import com.cycrilabs.keycloak.configurator.shared.control.KeycloakFactory;
+import com.cycrilabs.keycloak.configurator.shared.control.VelocityUtils;
+
+import io.quarkus.logging.Log;
+
+@ApplicationScoped
+public class ExportSecrets {
+ private static final String TEMPLATE_NAME_PLACEHOLDER = "client-name";
+
+ @Inject
+ ExportSecretsCommandConfiguration configuration;
+ Keycloak keycloak;
+
+ @PostConstruct
+ public void init() {
+ keycloak = KeycloakFactory.create(configuration);
+ }
+
+ public void export() throws IOException, ParseException, URISyntaxException {
+ final Collection templates = VelocityUtils.loadTemplates(loadTemplateFiles());
+ final List clients = loadClientSecrets();
+ for (final ClientRepresentation client : clients) {
+ for (final Template template : templates) {
+ Log.infof("Generating secret file(s) for client '%s.'", client.getClientId());
+ writeFiles(client, template);
+ }
+ }
+ }
+
+ private Collection loadTemplateFiles() throws URISyntaxException, FileNotFoundException {
+ return configuration.getConfigDirectory() == null
+ ? List.of(getDefaultTemplateFile())
+ : FileUtils.listFiles(new File(configuration.getConfigDirectory()), null, true);
+ }
+
+ private File getDefaultTemplateFile() throws FileNotFoundException, URISyntaxException {
+ return new File(Optional.ofNullable(getClass().getResource("client-name.env"))
+ .orElseThrow(() -> new FileNotFoundException("Default template not found."))
+ .toURI());
+ }
+
+ private List loadClientSecrets() {
+ return keycloak.realm(configuration.getRealmName())
+ .clients()
+ .findAll()
+ .stream()
+ .filter(client -> client.getSecret() != null)
+ .toList();
+ }
+
+ private void writeFiles(final ClientRepresentation client, final Template template) {
+ final String fileContent = generateFileContent(client, template);
+ final Path targetFile = getTargetFile(client.getClientId(), template.getName());
+ try {
+ Files.writeString(targetFile, fileContent, StandardCharsets.UTF_8);
+ } catch (final IOException e) {
+ Log.errorf("Failed to write file '%s.'", targetFile.toString());
+ }
+ }
+
+ private String generateFileContent(final ClientRepresentation client, final Template template) {
+ return VelocityUtils.mergeTemplate(template, VelocityUtils.createVelocityContext(
+ Map.ofEntries(
+ Map.entry("secret", client.getSecret())
+ )
+ ));
+ }
+
+ private Path getTargetFile(final String clientId, final String templateName) {
+ final String filename = templateName.replace(TEMPLATE_NAME_PLACEHOLDER, clientId);
+ return Paths.get(configuration.getOutputDirectory(), filename);
+ }
+}
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/control/ExportSecretsCommand.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/control/ExportSecretsCommand.java
index 3bd90de..9c02899 100644
--- a/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/control/ExportSecretsCommand.java
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/control/ExportSecretsCommand.java
@@ -1,22 +1,41 @@
package com.cycrilabs.keycloak.configurator.commands.secrets.control;
+import jakarta.inject.Inject;
+
+import com.cycrilabs.keycloak.configurator.commands.secrets.boundary.ExportSecrets;
+import com.cycrilabs.keycloak.configurator.commands.secrets.entity.ExportSecretsCommandConfiguration;
+import com.cycrilabs.keycloak.configurator.shared.control.KeycloakOptions;
+
import io.quarkus.logging.Log;
import picocli.CommandLine;
@CommandLine.Command(name = "export-secrets", mixinStandardHelpOptions = true)
public class ExportSecretsCommand implements Runnable {
- @CommandLine.Option(required = true, names = { "-s", "--server" },
- description = "Keycloak server that will be configured.")
- String target = "";
- @CommandLine.Option(required = true, names = { "-u", "--username" },
- description = "Username of the admin user that is used for configuration.")
- String username = "";
- @CommandLine.Option(required = true, names = { "-p", "--password" },
- description = "Password of the admin user that is used for configuration.")
- String password = "";
+ @CommandLine.Mixin
+ KeycloakOptions keycloakOptions;
+ @CommandLine.Option(required = true, names = { "-r", "--realm" },
+ description = "Realm name to export secrets from.")
+ String realm;
+ @CommandLine.Option(names = { "-c", "--config" },
+ description = "Directory containing templates for secret output files.")
+ String configDirectory;
+ @CommandLine.Option(names = { "-o", "--output" }, defaultValue = "./",
+ description = "Output directory for generate files.")
+ String outputDirectory;
+
+ @Inject
+ ExportSecretsCommandConfiguration configuration;
+ @Inject
+ ExportSecrets secretExporter;
@Override
public void run() {
- Log.infof("Fetching secrets from target %s.", target);
+ try {
+ Log.infof("Exporting secrets from realm '%s'.", configuration.getRealmName());
+ secretExporter.export();
+ } catch (final Exception e) {
+ Log.errorf(e, "Failed to export secrets from realm '%s'.",
+ configuration.getRealmName());
+ }
}
}
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/control/ExportSecretsCommandConfigurationProducer.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/control/ExportSecretsCommandConfigurationProducer.java
new file mode 100644
index 0000000..24a9d93
--- /dev/null
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/control/ExportSecretsCommandConfigurationProducer.java
@@ -0,0 +1,18 @@
+package com.cycrilabs.keycloak.configurator.commands.secrets.control;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Produces;
+
+import com.cycrilabs.keycloak.configurator.commands.secrets.entity.ExportSecretsCommandConfiguration;
+
+import picocli.CommandLine;
+
+@ApplicationScoped
+public class ExportSecretsCommandConfigurationProducer {
+ @Produces
+ @ApplicationScoped
+ ExportSecretsCommandConfiguration createConfiguration(
+ final CommandLine.ParseResult parseResult) {
+ return new ExportSecretsCommandConfiguration(parseResult);
+ }
+}
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/entity/ExportSecretsCommandConfiguration.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/entity/ExportSecretsCommandConfiguration.java
new file mode 100644
index 0000000..b36508f
--- /dev/null
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/entity/ExportSecretsCommandConfiguration.java
@@ -0,0 +1,21 @@
+package com.cycrilabs.keycloak.configurator.commands.secrets.entity;
+
+import lombok.Getter;
+
+import com.cycrilabs.keycloak.configurator.shared.entity.KeycloakConfiguration;
+
+import picocli.CommandLine.ParseResult;
+
+@Getter
+public class ExportSecretsCommandConfiguration extends KeycloakConfiguration {
+ private final String realmName;
+ private final String configDirectory;
+ private final String outputDirectory;
+
+ public ExportSecretsCommandConfiguration(final ParseResult parseResult) {
+ super(parseResult);
+ realmName = getMatchedOption(parseResult, "-r");
+ configDirectory = getMatchedOption(parseResult, "-c");
+ outputDirectory = getMatchedOption(parseResult, "-o");
+ }
+}
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/EntryCommand.java b/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/EntryCommand.java
index 90c3158..2b39665 100644
--- a/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/EntryCommand.java
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/EntryCommand.java
@@ -11,13 +11,4 @@
version = "0.1.0",
subcommands = { ConfigureCommand.class, ExportSecretsCommand.class })
public class EntryCommand {
- @CommandLine.Option(required = true, names = { "-s", "--server" },
- description = "Keycloak server that will be configured.")
- String server = "";
- @CommandLine.Option(required = true, names = { "-u", "--username" },
- description = "Username of the admin user that is used for configuration.")
- String username = "";
- @CommandLine.Option(required = true, names = { "-p", "--password" },
- description = "Password of the admin user that is used for configuration.")
- String password = "";
}
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/JsonbConfigurator.java b/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/JsonbFactory.java
similarity index 90%
rename from src/main/java/com/cycrilabs/keycloak/configurator/shared/control/JsonbConfigurator.java
rename to src/main/java/com/cycrilabs/keycloak/configurator/shared/control/JsonbFactory.java
index 152d8a2..591b134 100644
--- a/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/JsonbConfigurator.java
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/JsonbFactory.java
@@ -7,8 +7,8 @@
import lombok.NoArgsConstructor;
@NoArgsConstructor(staticName = "getInstance")
-public class JsonbConfigurator {
- private static final JsonbConfigurator INSTANCE = new JsonbConfigurator();
+public class JsonbFactory {
+ private static final JsonbFactory INSTANCE = new JsonbFactory();
private Jsonb jsonbFormatting;
private Jsonb jsonb;
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/KeycloakConfigurationProducer.java b/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/KeycloakConfigurationProducer.java
deleted file mode 100644
index b5b98de..0000000
--- a/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/KeycloakConfigurationProducer.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.cycrilabs.keycloak.configurator.shared.control;
-
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.enterprise.inject.Produces;
-
-import com.cycrilabs.keycloak.configurator.shared.entity.KeycloakConfiguration;
-
-import picocli.CommandLine;
-
-@ApplicationScoped
-public class KeycloakConfigurationProducer {
- @Produces
- @ApplicationScoped
- KeycloakConfiguration createConfiguration(final CommandLine.ParseResult parseResult) {
- return new KeycloakConfiguration(
- parseResult.matchedOption("s").getValue().toString(),
- parseResult.matchedOption("u").getValue().toString(),
- parseResult.matchedOption("p").getValue().toString()
- );
- }
-}
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/KeycloakProducer.java b/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/KeycloakFactory.java
similarity index 68%
rename from src/main/java/com/cycrilabs/keycloak/configurator/shared/control/KeycloakProducer.java
rename to src/main/java/com/cycrilabs/keycloak/configurator/shared/control/KeycloakFactory.java
index 1472097..55f80dd 100644
--- a/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/KeycloakProducer.java
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/KeycloakFactory.java
@@ -1,8 +1,6 @@
package com.cycrilabs.keycloak.configurator.shared.control;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.enterprise.inject.Produces;
-import jakarta.inject.Inject;
+import lombok.NoArgsConstructor;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
@@ -10,14 +8,9 @@
import com.cycrilabs.keycloak.configurator.shared.entity.KeycloakConfiguration;
-@ApplicationScoped
-public class KeycloakProducer {
- @Inject
- KeycloakConfiguration configuration;
-
- @Produces
- @ApplicationScoped
- Keycloak keycloak() {
+@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
+public class KeycloakFactory {
+ public static Keycloak create(final KeycloakConfiguration configuration) {
return KeycloakBuilder.builder()
.serverUrl(configuration.getServer())
.realm("master")
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/KeycloakOptions.java b/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/KeycloakOptions.java
new file mode 100644
index 0000000..50283d3
--- /dev/null
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/KeycloakOptions.java
@@ -0,0 +1,18 @@
+package com.cycrilabs.keycloak.configurator.shared.control;
+
+import picocli.CommandLine;
+
+public class KeycloakOptions {
+ @CommandLine.Option(required = true, names = { "-s", "--server" },
+ description = "Keycloak server that will be configured.",
+ scope = CommandLine.ScopeType.INHERIT)
+ String server = "";
+ @CommandLine.Option(required = true, names = { "-u", "--username" },
+ description = "Username of the admin user that is used for configuration.",
+ scope = CommandLine.ScopeType.INHERIT)
+ String username = "";
+ @CommandLine.Option(required = true, names = { "-p", "--password" },
+ description = "Password of the admin user that is used for configuration.",
+ scope = CommandLine.ScopeType.INHERIT)
+ String password = "";
+}
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/VelocityUtils.java b/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/VelocityUtils.java
new file mode 100644
index 0000000..61d8b94
--- /dev/null
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/VelocityUtils.java
@@ -0,0 +1,64 @@
+package com.cycrilabs.keycloak.configurator.shared.control;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+import lombok.NoArgsConstructor;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.runtime.parser.ParseException;
+
+@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
+public class VelocityUtils {
+ public static Collection loadTemplates(final Collection templateFiles)
+ throws IOException, ParseException {
+ final Collection templates = new ArrayList<>(templateFiles.size());
+ for (final File templateFile : templateFiles) {
+ final Template template = VelocityUtils.loadTemplate(templateFile);
+ templates.add(template);
+ }
+ return templates;
+ }
+
+ public static Template loadTemplate(final File file) throws ParseException, IOException {
+ final String templateString = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
+ return loadTemplateFromString(file.getName(), templateString);
+ }
+
+ public static Template loadTemplateFromString(final String name, final String templateString)
+ throws ParseException {
+ final RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices();
+ final StringReader reader = new StringReader(templateString);
+ final Template template = new Template();
+ template.setName(name);
+ template.setRuntimeServices(runtimeServices);
+ template.setData(runtimeServices.parse(reader, template));
+ template.initDocument();
+ return template;
+ }
+
+ public static VelocityContext createVelocityContext(final Map data) {
+ return data.entrySet()
+ .stream()
+ .reduce(new VelocityContext(), (c, e) -> {
+ c.put(e.getKey(), e.getValue());
+ return c;
+ }, (c1, c2) -> c1);
+ }
+
+ public static String mergeTemplate(final Template template, final VelocityContext context) {
+ final StringWriter writer = new StringWriter();
+ template.merge(context, writer);
+ return writer.toString();
+ }
+}
diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/shared/entity/KeycloakConfiguration.java b/src/main/java/com/cycrilabs/keycloak/configurator/shared/entity/KeycloakConfiguration.java
index ac1fdf5..10e76cd 100644
--- a/src/main/java/com/cycrilabs/keycloak/configurator/shared/entity/KeycloakConfiguration.java
+++ b/src/main/java/com/cycrilabs/keycloak/configurator/shared/entity/KeycloakConfiguration.java
@@ -1,12 +1,27 @@
package com.cycrilabs.keycloak.configurator.shared.entity;
-import lombok.AllArgsConstructor;
import lombok.Getter;
-@AllArgsConstructor
+import picocli.CommandLine.ParseResult;
+
@Getter
-public class KeycloakConfiguration {
- String server;
- String username;
- String password;
+public abstract class KeycloakConfiguration {
+ private String server;
+ private String username;
+ private String password;
+
+ protected KeycloakConfiguration() {
+ // required to avoid "No default constructor for class" error
+ }
+
+ protected KeycloakConfiguration(final ParseResult parseResult) {
+ this.server = getMatchedOption(parseResult, "-s");
+ this.username = getMatchedOption(parseResult, "-u");
+ this.password = getMatchedOption(parseResult, "-p");
+ }
+
+ protected String getMatchedOption(final ParseResult parseResult, final String name) {
+ return parseResult.subcommand().commandSpec().optionsMap().get(name).getValue();
+ }
}
+
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index e69de29..ea70d8d 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -0,0 +1,5 @@
+# set default log level
+quarkus.log.level=ERROR
+
+# include default template
+quarkus.native.resources.includes=com/cycrilabs/keycloak/**/*.env
diff --git a/src/main/resources/com/cycrilabs/keycloak/configurator/commands/secrets/boundary/client-name.env b/src/main/resources/com/cycrilabs/keycloak/configurator/commands/secrets/boundary/client-name.env
new file mode 100644
index 0000000..c4c273f
--- /dev/null
+++ b/src/main/resources/com/cycrilabs/keycloak/configurator/commands/secrets/boundary/client-name.env
@@ -0,0 +1 @@
+QUARKUS_OIDC_CREDENTIALS_SECRET=$secret