Invalid migration export for empty database

Fixes #32535

Signed-off-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
Martin Bartoš 2025-02-11 08:24:53 +00:00 committed by GitHub
parent c650984267
commit fe40730aed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 75 additions and 12 deletions

View File

@ -35,7 +35,6 @@ import liquibase.snapshot.SnapshotControl;
import liquibase.snapshot.SnapshotGeneratorFactory;
import liquibase.statement.SqlStatement;
import liquibase.statement.core.AddColumnStatement;
import liquibase.statement.core.CreateDatabaseChangeLogTableStatement;
import liquibase.statement.core.SetNullableStatement;
import liquibase.statement.core.UpdateStatement;
import liquibase.structure.core.Column;
@ -54,7 +53,6 @@ import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
@ -211,13 +209,11 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
loggingExecutor.comment("* Keycloak database creation script - apply this script to empty DB *");
loggingExecutor.comment("*********************************************************************" + StreamUtil.getLineSeparator());
loggingExecutor.execute(new CreateDatabaseChangeLogTableStatement());
// DatabaseChangeLogTable is automatically added to the script by Liquibase
// DatabaseChangeLogLockTable is created before this code is executed and recreated if it does not exist automatically
// in org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService.init() called indirectly from
// KeycloakApplication constructor (search for waitForLock() call). Hence it is not included in the creation script.
loggingExecutor.comment("*********************************************************************" + StreamUtil.getLineSeparator());
executorService.setExecutor(LiquibaseConstants.JDBC_EXECUTOR, database, oldTemplate);
}

View File

@ -57,7 +57,6 @@ import liquibase.snapshot.SnapshotControl;
import liquibase.snapshot.SnapshotGeneratorFactory;
import liquibase.statement.SqlStatement;
import liquibase.statement.core.AddColumnStatement;
import liquibase.statement.core.CreateDatabaseChangeLogTableStatement;
import liquibase.statement.core.SetNullableStatement;
import liquibase.statement.core.UpdateStatement;
import liquibase.structure.core.Column;
@ -213,13 +212,11 @@ public class QuarkusJpaUpdaterProvider implements JpaUpdaterProvider {
loggingExecutor.comment("* Keycloak database creation script - apply this script to empty DB *");
loggingExecutor.comment("*********************************************************************" + StreamUtil.getLineSeparator());
loggingExecutor.execute(new CreateDatabaseChangeLogTableStatement());
// DatabaseChangeLogTable is automatically added to the script by Liquibase
// DatabaseChangeLogLockTable is created before this code is executed and recreated if it does not exist automatically
// in org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService.init() called indirectly from
// KeycloakApplication constructor (search for waitForLock() call). Hence it is not included in the creation script.
loggingExecutor.comment("*********************************************************************" + StreamUtil.getLineSeparator());
executorService.setExecutor(LiquibaseConstants.JDBC_EXECUTOR, database, oldTemplate);
}

View File

@ -17,14 +17,30 @@
package org.keycloak.it.storage.database;
import io.quarkus.logging.Log;
import io.quarkus.test.junit.main.Launch;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.utils.RawDistRootPath;
import org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand;
import io.quarkus.test.junit.main.Launch;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.apache.commons.lang3.StringUtils.countMatches;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public abstract class BasicDatabaseTest {
@ -37,7 +53,7 @@ public abstract class BasicDatabaseTest {
@Test
@Launch({ "start", AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG, "--http-enabled=true", "--hostname-strict=false", "--db-username=wrong" })
void testWrongUsername(CLIResult cliResult) {
protected void testWrongUsername(CLIResult cliResult) {
cliResult.assertMessage("ERROR: Failed to obtain JDBC connection");
assertWrongUsername(cliResult);
}
@ -46,7 +62,7 @@ public abstract class BasicDatabaseTest {
@Test
@Launch({ "start", AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG, "--http-enabled=true", "--hostname-strict=false", "--db-password=wrong" })
void testWrongPassword(CLIResult cliResult) {
protected void testWrongPassword(CLIResult cliResult) {
cliResult.assertMessage("ERROR: Failed to obtain JDBC connection");
assertWrongPassword(cliResult);
}
@ -69,4 +85,33 @@ public abstract class BasicDatabaseTest {
cliResult.assertMessage("Realm 'master' imported");
cliResult.assertMessage("Import finished successfully");
}
public void assertManualDbInitialization(CLIResult cliResult, RawDistRootPath rawDistRootPath) {
cliResult.assertMessage("Database not initialized, please initialize database with");
var output = readKeycloakDbUpdateScript(rawDistRootPath);
assertThat(output, notNullValue());
assertThat(output, containsString("Create Database Change Log Table"));
var outputLowerCase = output.toLowerCase();
var count = countMatches(outputLowerCase, "create table public.databasechangelog") + countMatches(outputLowerCase, "create table keycloak.databasechangelog");
assertThat(count, is(1));
}
protected static String readKeycloakDbUpdateScript(RawDistRootPath path) {
final String defaultScriptName = "keycloak-database-update.sql";
Path scriptPath = Paths.get(path.getDistRootPath() + File.separator + "bin" + File.separator + defaultScriptName);
File script = new File(scriptPath.toString());
assertThat(String.format("Script '%s' does not exist!", defaultScriptName), script.isFile(), is(true));
try {
var result = FileUtils.readFileToString(script, Charset.defaultCharset());
Log.infof("Deleting Keycloak DB update script '%s'", defaultScriptName);
Files.delete(scriptPath);
return result;
} catch (IOException e) {
throw new AssertionError(String.format("Cannot read or delete script '%s'", defaultScriptName), e);
}
}
}

View File

@ -23,6 +23,7 @@ import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.WithDatabase;
import org.keycloak.it.storage.database.MariaDBTest;
import org.keycloak.it.utils.RawDistRootPath;
import org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand;
import io.quarkus.test.junit.main.Launch;
@ -39,4 +40,11 @@ public class MariaDBDistTest extends MariaDBTest {
super.testSuccessful(result);
}
@Tag(DistributionTest.STORAGE)
@Test
@Launch({"start", AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG, "--spi-connections-jpa-quarkus-migration-strategy=manual", "--spi-connections-jpa-quarkus-initialize-empty=false", "--http-enabled=true", "--hostname-strict=false",})
public void testKeycloakDbUpdateScript(CLIResult cliResult, RawDistRootPath rawDistRootPath) {
assertManualDbInitialization(cliResult, rawDistRootPath);
}
}

View File

@ -6,6 +6,7 @@ import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.WithDatabase;
import org.keycloak.it.storage.database.MySQLTest;
import org.keycloak.it.utils.RawDistRootPath;
import org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand;
import io.quarkus.test.junit.main.Launch;
@ -21,4 +22,11 @@ public class MySQLDistTest extends MySQLTest {
protected void testSuccessful(CLIResult result) {
super.testSuccessful(result);
}
@Tag(DistributionTest.STORAGE)
@Test
@Launch({"start", AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG, "--spi-connections-jpa-quarkus-migration-strategy=manual", "--spi-connections-jpa-quarkus-initialize-empty=false", "--http-enabled=true", "--hostname-strict=false",})
public void testKeycloakDbUpdateScript(CLIResult cliResult, RawDistRootPath rawDistRootPath) {
assertManualDbInitialization(cliResult, rawDistRootPath);
}
}

View File

@ -28,6 +28,8 @@ import org.keycloak.it.junit5.extension.WithDatabase;
import org.keycloak.it.storage.database.PostgreSQLTest;
import io.quarkus.test.junit.main.Launch;
import org.keycloak.it.utils.RawDistRootPath;
import org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand;
@DistributionTest(removeBuildOptionsAfterBuild = true)
@WithDatabase(alias = "postgres")
@ -39,4 +41,11 @@ public class PostgreSQLDistTest extends PostgreSQLTest {
public void testDbOptionFromPersistedConfigSource(CLIResult cliResult) {
assertThat(cliResult.getOutput(),containsString("postgres (Persisted)"));
}
@Tag(DistributionTest.STORAGE)
@Test
@Launch({"start", AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG, "--spi-connections-jpa-quarkus-migration-strategy=manual", "--spi-connections-jpa-quarkus-initialize-empty=false", "--http-enabled=true", "--hostname-strict=false",})
public void testKeycloakDbUpdateScript(CLIResult cliResult, RawDistRootPath rawDistRootPath) {
assertManualDbInitialization(cliResult, rawDistRootPath);
}
}