fix: warn instead of an error if optimized provider timestamps change (#41798) (#41946)

closes: #41268


(cherry picked from commit 02cd3ddfb7ed550cb7c4ac97fb98b4af9f0f1f8d)

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2025-08-18 11:37:38 -04:00 committed by GitHub
parent ec6e015dff
commit 4a6a66a449
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 36 additions and 12 deletions

View File

@ -92,6 +92,8 @@ import picocli.CommandLine.ParseResult;
public class Picocli {
static final String PROVIDER_TIMESTAMP_ERROR = "A provider JAR was updated since the last build, please rebuild for this to be fully utilized.";
static final String PROVIDER_TIMESTAMP_WARNING = "A provider jar has a different timestamp than when the optimized container image was created. If you are changing provider jars after the build, you must run another build to properly account for those modifications.";
static final String KC_PROVIDER_FILE_PREFIX = "kc.provider.file.";
public static final String ARG_PREFIX = "--";
public static final String ARG_SHORT_PREFIX = "-";
@ -105,6 +107,7 @@ public class Picocli {
private final ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
private Set<PropertyMapper<?>> allowedMappers;
private final List<String> unrecognizedArgs = new ArrayList<>();
private boolean warnedTimestampChanged;
public void parseAndRun(List<String> cliArgs) {
// perform two passes over the cli args. First without option validation to determine the current command, then with option validation enabled
@ -360,8 +363,19 @@ public class Picocli {
// we have to ignore things like the profile properties because the commands set them at runtime
checkChangesInBuildOptions((key, oldValue, newValue) -> {
if (key.startsWith(KC_PROVIDER_FILE_PREFIX)) {
if (timestampChanged(oldValue, newValue)) {
throw new PropertyException("A provider JAR was updated since the last build, please rebuild for this to be fully utilized.");
boolean changed = false;
if (newValue == null || oldValue == null) {
changed = true;
} else if (!warnedTimestampChanged && timestampChanged(oldValue, newValue)) {
if (Configuration.getOptionalBooleanKcValue("run-in-container").orElse(false)) {
warnedTimestampChanged = true;
warn(PROVIDER_TIMESTAMP_WARNING);
} else {
changed = true;
}
}
if (changed) {
throw new PropertyException(PROVIDER_TIMESTAMP_ERROR);
}
} else if (newValue != null && !isIgnoredPersistedOption(key)
&& isUserModifiable(Configuration.getConfigValue(key))
@ -478,13 +492,10 @@ public class Picocli {
}
static boolean timestampChanged(String oldValue, String newValue) {
if (newValue != null && oldValue != null) {
long longNewValue = Long.valueOf(newValue);
long longOldValue = Long.valueOf(oldValue);
// docker commonly truncates to the second at runtime, so we'll allow that special case
return ((longNewValue / 1000) * 1000) != longNewValue || ((longOldValue / 1000) * 1000) != longNewValue;
}
return true;
long longNewValue = Long.valueOf(newValue);
long longOldValue = Long.valueOf(oldValue);
// docker commonly truncates to the second at runtime, so we'll allow that special case
return ((longNewValue / 1000) * 1000) != longNewValue || ((longOldValue / 1000) * 1000) != longNewValue;
}
private void validateProperty(AbstractCommand abstractCommand, IncludeOptions options,

View File

@ -47,6 +47,7 @@ import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.KeycloakMain;
import org.keycloak.quarkus.runtime.configuration.AbstractConfigurationTest;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
import io.smallrye.config.SmallRyeConfig;
import picocli.CommandLine;
@ -536,9 +537,22 @@ public class PicocliTest extends AbstractConfigurationTest {
addPersistedConfigValues(Map.of(Picocli.KC_PROVIDER_FILE_PREFIX + "fake", "value"));
NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--optimized");
NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--optimized", "--http-enabled=true", "--hostname-strict=false");
assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
assertTrue(nonRunningPicocli.getErrString().contains("A provider JAR was updated since the last build, please rebuild for this to be fully utilized."));
assertTrue(nonRunningPicocli.getErrString().contains(Picocli.PROVIDER_TIMESTAMP_ERROR));
}
@Test
public void warnProviderChanged() {
build("build", "--db=dev-file");
putEnvVar("KC_RUN_IN_CONTAINER", "true");
String key = PersistedConfigSource.getInstance().getConfigValueProperties().keySet().stream().filter(k -> k.startsWith(Picocli.KC_PROVIDER_FILE_PREFIX)).findAny().orElseThrow();
addPersistedConfigValues(Map.of(key, "1")); // change to a fake timestamp
NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--optimized", "--http-enabled=true", "--hostname-strict=false");
assertEquals(nonRunningPicocli.getErrString(), CommandLine.ExitCode.OK, nonRunningPicocli.exitCode);
assertTrue(nonRunningPicocli.getOutString().contains(Picocli.PROVIDER_TIMESTAMP_WARNING));
}
@Test
@ -766,7 +780,6 @@ public class PicocliTest extends AbstractConfigurationTest {
@Test
public void timestampChanged() {
assertTrue(Picocli.timestampChanged("12345", null));
assertTrue(Picocli.timestampChanged("12345", "12346"));
assertTrue(Picocli.timestampChanged("12000", "12346"));
// new is truncated - should not be a change