diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java index ec3e4ae24d3..3c585074dfc 100755 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java @@ -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); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/database/liquibase/QuarkusJpaUpdaterProvider.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/database/liquibase/QuarkusJpaUpdaterProvider.java index 4a532139387..0a32e9925b1 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/database/liquibase/QuarkusJpaUpdaterProvider.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/database/liquibase/QuarkusJpaUpdaterProvider.java @@ -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); } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/BasicDatabaseTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/BasicDatabaseTest.java index 3a2193cdffa..1fba633ce79 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/BasicDatabaseTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/BasicDatabaseTest.java @@ -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); + } + } } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/dist/MariaDBDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/dist/MariaDBDistTest.java index 101957b7308..e409eacad77 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/dist/MariaDBDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/dist/MariaDBDistTest.java @@ -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); + } + } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/dist/MySQLDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/dist/MySQLDistTest.java index d7779cb798b..53b2d50d25f 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/dist/MySQLDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/dist/MySQLDistTest.java @@ -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); + } } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/dist/PostgreSQLDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/dist/PostgreSQLDistTest.java index 3a7c2c31d64..fdc30ab8c1f 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/dist/PostgreSQLDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/dist/PostgreSQLDistTest.java @@ -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); + } }