KEYCLOAK-19808 throw error when using start command after start-dev without rebuild first

This commit is contained in:
Dominik Guhr 2021-11-10 10:46:08 +01:00 committed by Pedro Igor
parent f6daca8a60
commit 8bd18ff21a
7 changed files with 104 additions and 23 deletions

View file

@ -23,9 +23,7 @@ import java.io.File;
import java.io.FilenameFilter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -41,6 +39,8 @@ public final class Environment {
public static final String ENV_PROFILE ="KC_PROFILE";
public static final String DATA_PATH = "/data";
public static final String DEFAULT_THEMES_PATH = "/themes";
public static final String DEV_PROFILE_VALUE = "dev";
public static final String USER_INVOKED_CLI_COMMAND = "picocli.invoked.command";
private Environment() {}
@ -92,7 +92,30 @@ public final class Environment {
}
return "./kc.sh";
}
/**
* Sets the originally invoked cli args. Useful to verify the originally invoked command
* when calling another cli command internally (e.g. start-dev calls build internally)
*/
public static void setUserInvokedCliArgs(List<String> cliArgs) {
System.setProperty(USER_INVOKED_CLI_COMMAND, String.join(",", cliArgs));
}
/**
* Reads the previously set system property for the originally command.
* Use the System variable, when you trigger other command executions internally, but need a reference to the
* actually invoked command.
*
* @return the invoked command from the CLI, or empty List if not set.
*/
public static List<String> getUserInvokedCliArgs() {
if(System.getProperty(USER_INVOKED_CLI_COMMAND) == null) {
return Collections.emptyList();
}
return List.of(System.getProperty(USER_INVOKED_CLI_COMMAND).split(","));
}
public static String getConfigArgs() {
return System.getProperty(CLI_ARGS, "");
}
@ -123,7 +146,7 @@ public final class Environment {
}
public static boolean isDevMode() {
if ("dev".equalsIgnoreCase(getProfile())) {
if (DEV_PROFILE_VALUE.equalsIgnoreCase(getProfile())) {
return true;
}
@ -132,7 +155,11 @@ public final class Environment {
return true;
}
return "dev".equals(getBuiltTimeProperty(PROFILE).orElse(null));
return DEV_PROFILE_VALUE.equals(getBuiltTimeProperty(PROFILE).orElse(null));
}
public static boolean isDevProfile(){
return Optional.ofNullable(getProfile()).orElse("").equalsIgnoreCase(DEV_PROFILE_VALUE);
}
public static boolean isImportExportMode() {
@ -144,7 +171,7 @@ public final class Environment {
}
public static void forceDevProfile() {
setProfile("dev");
setProfile(DEV_PROFILE_VALUE);
}
public static Map<String, File> getProviderFiles() {

View file

@ -17,10 +17,10 @@
package org.keycloak.quarkus.runtime;
import static org.keycloak.quarkus.runtime.Environment.isDevMode;
import static org.keycloak.quarkus.runtime.Environment.isDevProfile;
import static org.keycloak.quarkus.runtime.Environment.getProfileOrDefault;
import static org.keycloak.quarkus.runtime.cli.Picocli.error;
import static org.keycloak.quarkus.runtime.cli.Picocli.parseAndRun;
import static org.keycloak.quarkus.runtime.Environment.getProfileOrDefault;
import java.io.PrintWriter;
import java.util.ArrayList;
@ -80,7 +80,7 @@ public class KeycloakMain implements QuarkusApplication {
*/
@Override
public int run(String... args) throws Exception {
if (isDevMode()) {
if (isDevProfile()) {
LOGGER.warnf("Running the server in dev mode. DO NOT use this configuration in production.");
}
Quarkus.waitForExit();

View file

@ -132,12 +132,13 @@ public final class Picocli {
private static boolean requiresReAugmentation(CommandLine cmd) {
if (hasConfigChanges()) {
cmd.getOut().println("Changes detected in configuration. Updating the server image.");
cmd.getOut().printf("For an optional runtime and bypass this step, please run the '%s' command prior to starting the server:%n%n\t%s %s %s%n",
Build.NAME,
Environment.getCommand(),
Build.NAME,
String.join(" ", asList(ARG_SPLIT.split(Environment.getConfigArgs()))) + "\n");
if(!isDevMode()) {
cmd.getOut().printf("For an optional runtime and bypass this step, please run the '%s' command prior to starting the server:%n%n\t%s %s %s%n",
Build.NAME,
Environment.getCommand(),
Build.NAME,
String.join(" ", asList(ARG_SPLIT.split(Environment.getConfigArgs()))) + "\n");
}
return true;
}
@ -152,6 +153,8 @@ public final class Picocli {
// force the server image to be set with the dev profile
Environment.forceDevProfile();
}
Environment.setUserInvokedCliArgs(cliArgs);
}
List<String> configArgsList = new ArrayList<>(cliArgs);
@ -166,7 +169,9 @@ public final class Picocli {
cmd.execute(configArgsList.toArray(new String[0]));
cmd.getOut().printf("Next time you run the server, just run:%n%n\t%s %s%n%n", Environment.getCommand(), Start.NAME);
if(!isDevMode()) {
cmd.getOut().printf("Next time you run the server, just run:%n%n\t%s %s%n%n", Environment.getCommand(), Start.NAME);
}
}
private static boolean hasProviderChanges() {

View file

@ -25,6 +25,8 @@ import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Spec;
import picocli.CommandLine.Option;
import static org.keycloak.quarkus.runtime.cli.Picocli.error;
public abstract class AbstractCommand {
@Spec
@ -49,4 +51,8 @@ public abstract class AbstractCommand {
public void setConfigFile(String path) {
System.setProperty(KeycloakConfigSourceProvider.KEYCLOAK_CONFIG_FILE_PROP, path);
}
protected void showDevNotAllowedErrorAndExit(String cmd) {
error(spec.commandLine(), String.format("You can not '%s' the server using the 'dev' configuration profile. Please re-build the server first, using './kc.sh build' for the default production profile, or using '/.kc.sh build --profile=<profile>' with a profile more suitable for production.%n", cmd));
}
}

View file

@ -17,7 +17,7 @@
package org.keycloak.quarkus.runtime.cli.command;
import static org.keycloak.quarkus.runtime.Environment.getHomePath;
import static org.keycloak.quarkus.runtime.Environment.*;
import static org.keycloak.quarkus.runtime.cli.Picocli.error;
import static org.keycloak.quarkus.runtime.cli.Picocli.println;
@ -29,6 +29,8 @@ import io.quarkus.bootstrap.runner.RunnerClassLoader;
import io.quarkus.runtime.configuration.ProfileManager;
import picocli.CommandLine.Command;
import java.util.List;
@Command(name = Build.NAME,
header = "Creates a new and optimized server image.",
description = {
@ -67,14 +69,19 @@ public final class Build extends AbstractCommand implements Runnable {
@Override
public void run() {
exitWithErrorIfDevProfileIsSetAndNotStartDev();
System.setProperty("quarkus.launch.rebuild", "true");
println(spec.commandLine(), "Updating the configuration and installing your custom providers, if any. Please wait.");
try {
beforeReaugmentationOnWindows();
QuarkusEntryPoint.main();
println(spec.commandLine(), "Server configuration updated and persisted. Run the following command to review the configuration:\n");
println(spec.commandLine(), "\t" + Environment.getCommand() + " show-config\n");
if (!isDevMode()) {
println(spec.commandLine(), "Server configuration updated and persisted. Run the following command to review the configuration:\n");
println(spec.commandLine(), "\t" + Environment.getCommand() + " show-config\n");
}
} catch (Throwable throwable) {
error(spec.commandLine(), "Failed to update server configuration.", throwable);
} finally {
@ -82,6 +89,13 @@ public final class Build extends AbstractCommand implements Runnable {
}
}
private void exitWithErrorIfDevProfileIsSetAndNotStartDev() {
List<String> userInvokedCliArgs = Environment.getUserInvokedCliArgs();
if(Environment.isDevProfile() && !userInvokedCliArgs.contains(StartDev.NAME)) {
showDevNotAllowedErrorAndExit(Build.NAME);
}
}
private void beforeReaugmentationOnWindows() {
// On Windows, files generated during re-augmentation are locked and can't be re-created.
// To workaround this behavior, we reset the internal cache of the runner classloader and force files

View file

@ -17,15 +17,22 @@
package org.keycloak.quarkus.runtime.cli.command;
import static org.keycloak.quarkus.runtime.Environment.isDevProfile;
import static org.keycloak.quarkus.runtime.cli.Picocli.NO_PARAM_LABEL;
import static org.keycloak.quarkus.runtime.cli.Picocli.error;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuiltTimeProperty;
import org.keycloak.quarkus.runtime.Environment;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import java.util.List;
import java.util.Optional;
@Command(name = Start.NAME,
header = "Start the server.",
description = {
"%nUse this command to run the server in production."
"%nUse this command to run the server in production."
},
footer = "%nYou may use the \"--auto-build\" option when starting the server to avoid running the \"build\" command everytime you need to change a static property:%n%n"
+ " $ ${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME} --auto-build <OPTIONS>%n%n"
@ -37,11 +44,33 @@ public final class Start extends AbstractStartCommand implements Runnable {
public static final String NAME = "start";
@CommandLine.Option(names = {AUTO_BUILD_OPTION_SHORT, AUTO_BUILD_OPTION_LONG },
@CommandLine.Option(names = {AUTO_BUILD_OPTION_SHORT, AUTO_BUILD_OPTION_LONG},
description = "Automatically detects whether the server configuration changed and a new server image must be built" +
" prior to starting the server. This option provides an alternative to manually running the '" + Build.NAME + "'" +
" prior to starting the server. Use this configuration carefully in production as it might impact the startup time.",
paramLabel = NO_PARAM_LABEL,
order = 1)
Boolean autoConfig;
@Override
protected void doBeforeRun() {
checkIfProfileIsNotDev();
}
/**
* Checks if the profile provided by either the current argument, the system environment or the persisted properties is dev.
* Fails with an error when dev profile is used for the start command, or continues with the found profile if its not the dev profile.
*/
private void checkIfProfileIsNotDev() {
List<String> currentCliArgs = spec.commandLine().getParseResult().expandedArgs();
Optional<String> currentProfile = Optional.ofNullable(Environment.getProfile());
Optional<String> persistedProfile = getBuiltTimeProperty("kc.profile");
setProfile(currentProfile.orElse(persistedProfile.orElse("prod")));
if (isDevProfile() && (!currentCliArgs.contains(AUTO_BUILD_OPTION_LONG) || !currentCliArgs.contains(AUTO_BUILD_OPTION_SHORT))) {
showDevNotAllowedErrorAndExit(Start.NAME);
}
}
}

View file

@ -35,7 +35,7 @@ public final class Messages {
public static IllegalStateException httpsConfigurationNotSet() {
StringBuilder builder = new StringBuilder("Key material not provided to setup HTTPS. Please configure your keys/certificates");
if (!"dev".equals(Environment.getProfile())) {
if (!Environment.DEV_PROFILE_VALUE.equals(Environment.getProfile())) {
builder.append(" or start the server in development mode");
}
builder.append(".");