From e11a8b52b7b5b12feb0ff17f019ccde6945b0526 Mon Sep 17 00:00:00 2001 From: asjervanasten Date: Sun, 3 Mar 2024 21:07:57 +0100 Subject: [PATCH 1/2] Allows to use specific datasource credentials for Liquibase Fixes https://github.com/quarkusio/quarkus/issues/31214 --- .../test/LiquibaseExtensionConfigFixture.java | 8 ++++ .../quarkus/liquibase/LiquibaseFactory.java | 21 ++++++++- .../liquibase/runtime/LiquibaseConfig.java | 12 +++++ .../liquibase/runtime/LiquibaseCreator.java | 2 + .../LiquibaseDataSourceRuntimeConfig.java | 14 ++++++ .../LiquibaseFunctionalityResource.java | 47 ++++++++++++++++++- .../src/main/resources/application.properties | 15 +++++- .../main/resources/db/second/changeLog.xml | 9 ++++ .../main/resources/db/second/create-table.xml | 18 +++++++ .../src/main/resources/db/second/initdb.sql | 2 + .../resources/db/second/insert-into-table.xml | 14 ++++++ .../liquibase/LiquibaseFunctionalityTest.java | 13 +++++ 12 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 integration-tests/liquibase/src/main/resources/db/second/changeLog.xml create mode 100644 integration-tests/liquibase/src/main/resources/db/second/create-table.xml create mode 100644 integration-tests/liquibase/src/main/resources/db/second/initdb.sql create mode 100644 integration-tests/liquibase/src/main/resources/db/second/insert-into-table.xml diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigFixture.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigFixture.java index 258c33815d843..1e4f916ab50d9 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigFixture.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigFixture.java @@ -121,6 +121,14 @@ public String defaultSchemaName(String datasourceName) { return getStringValue("quarkus.liquibase.%s.default-schema-name", datasourceName); } + public String username(String datasourceName) { + return getStringValue("quarkus.liquibase.%s.username", datasourceName); + } + + public String password(String datasourceName) { + return getStringValue("quarkus.liquibase.%s.password", datasourceName); + } + public String liquibaseCatalogName(String datasourceName) { return getStringValue("quarkus.liquibase.%s.liquibase-catalog-name", datasourceName); } diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/LiquibaseFactory.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/LiquibaseFactory.java index d26d6d25c480a..db1c460fa1b64 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/LiquibaseFactory.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/LiquibaseFactory.java @@ -2,10 +2,13 @@ import java.io.FileNotFoundException; import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DriverManager; import java.util.Map; import javax.sql.DataSource; +import io.agroal.api.AgroalDataSource; import io.quarkus.liquibase.runtime.LiquibaseConfig; import io.quarkus.runtime.util.StringUtil; import liquibase.Contexts; @@ -78,8 +81,22 @@ public Liquibase createLiquibase() { try (ResourceAccessor resourceAccessor = resolveResourceAccessor()) { String parsedChangeLog = parseChangeLog(config.changeLog); - Database database = DatabaseFactory.getInstance() - .findCorrectDatabaseImplementation(new JdbcConnection(dataSource.getConnection())); + Database database; + + if (config.username.isPresent() && config.password.isPresent()) { + AgroalDataSource agroalDataSource = dataSource.unwrap(AgroalDataSource.class); + String jdbcUrl = agroalDataSource.getConfiguration().connectionPoolConfiguration() + .connectionFactoryConfiguration().jdbcUrl(); + Connection connection = DriverManager.getConnection(jdbcUrl, config.username.get(), config.password.get()); + + database = DatabaseFactory.getInstance() + .findCorrectDatabaseImplementation( + new JdbcConnection(connection)); + + } else { + database = DatabaseFactory.getInstance() + .findCorrectDatabaseImplementation(new JdbcConnection(dataSource.getConnection())); + } if (database != null) { database.setDatabaseChangeLogLockTableName(config.databaseChangeLogLockTableName); diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseConfig.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseConfig.java index 3af8105e1c621..de1f2e47a1f36 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseConfig.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseConfig.java @@ -85,4 +85,16 @@ public class LiquibaseConfig { */ public Optional liquibaseTablespaceName = Optional.empty(); + /** + * The username that Liquibase uses to connect to the database. + * If no username is configured, falls back to the datasource username and password. + */ + public Optional username = Optional.empty(); + + /** + * The password that Liquibase uses to connect to the database. + * If no password is configured, falls back to the datasource username and password. + */ + public Optional password = Optional.empty(); + } diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseCreator.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseCreator.java index c3d20635e8612..a6c06d69fb47a 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseCreator.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseCreator.java @@ -33,6 +33,8 @@ public LiquibaseFactory createLiquibaseFactory(DataSource dataSource, String dat if (liquibaseRuntimeConfig.databaseChangeLogTableName.isPresent()) { config.databaseChangeLogTableName = liquibaseRuntimeConfig.databaseChangeLogTableName.get(); } + config.password = liquibaseRuntimeConfig.password; + config.username = liquibaseRuntimeConfig.username; config.defaultSchemaName = liquibaseRuntimeConfig.defaultSchemaName; config.defaultCatalogName = liquibaseRuntimeConfig.defaultCatalogName; config.liquibaseTablespaceName = liquibaseRuntimeConfig.liquibaseTablespaceName; diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseDataSourceRuntimeConfig.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseDataSourceRuntimeConfig.java index 639590f5d5b58..c8cb0d1cf3e56 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseDataSourceRuntimeConfig.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseDataSourceRuntimeConfig.java @@ -101,6 +101,20 @@ public static final LiquibaseDataSourceRuntimeConfig defaultConfig() { @ConfigItem public Optional defaultSchemaName = Optional.empty(); + /** + * The username that Liquibase uses to connect to the database. + * If no specific username is configured, falls back to the datasource username and password. + */ + @ConfigItem + public Optional username = Optional.empty(); + + /** + * The password that Liquibase uses to connect to the database. + * If no specific password is configured, falls back to the datasource username and password. + */ + @ConfigItem + public Optional password = Optional.empty(); + /** * The name of the catalog with the liquibase tables. */ diff --git a/integration-tests/liquibase/src/main/java/io/quarkus/it/liquibase/LiquibaseFunctionalityResource.java b/integration-tests/liquibase/src/main/java/io/quarkus/it/liquibase/LiquibaseFunctionalityResource.java index b71310c69e38c..5ac440f938368 100644 --- a/integration-tests/liquibase/src/main/java/io/quarkus/it/liquibase/LiquibaseFunctionalityResource.java +++ b/integration-tests/liquibase/src/main/java/io/quarkus/it/liquibase/LiquibaseFunctionalityResource.java @@ -1,5 +1,8 @@ package io.quarkus.it.liquibase; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -9,6 +12,9 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.WebApplicationException; +import io.agroal.api.AgroalDataSource; +import io.quarkus.agroal.DataSource; +import io.quarkus.liquibase.LiquibaseDataSource; import io.quarkus.liquibase.LiquibaseFactory; import liquibase.Liquibase; import liquibase.changelog.ChangeSet; @@ -21,6 +27,14 @@ public class LiquibaseFunctionalityResource { @Inject LiquibaseFactory liquibaseFactory; + @Inject + @LiquibaseDataSource("second") + LiquibaseFactory liquibaseSecondFactory; + + @Inject + @DataSource("second") + AgroalDataSource dataSource; + @GET @Path("update") public String doUpdateAuto() { @@ -42,6 +56,38 @@ public String doUpdateAuto() { } } + @GET + @Path("updateWithDedicatedUser") + public String updateWithDedicatedUser() { + try (Liquibase liquibase = liquibaseSecondFactory.createLiquibase()) { + liquibase.update(liquibaseSecondFactory.createContexts(), liquibaseSecondFactory.createLabels()); + List status = liquibase.getChangeSetStatuses(liquibaseSecondFactory.createContexts(), + liquibaseSecondFactory.createLabels()); + List changeSets = Objects.requireNonNull(status, + "ChangeSetStatus is null! Database update was not applied"); + return changeSets.stream() + .filter(ChangeSetStatus::getPreviouslyRan) + .map(ChangeSetStatus::getChangeSet) + .map(ChangeSet::getId) + .collect(Collectors.joining(",")); + } catch (Exception ex) { + throw new WebApplicationException(ex.getMessage(), ex); + } + + } + + @GET + @Path("created-by") + public String returnCreatedByUser() throws SQLException { + try (Connection connection = dataSource.getConnection()) { + ResultSet s = connection.createStatement().executeQuery("SELECT CREATEDBY FROM QUARKUS_TABLE WHERE ID = 1"); + if (s.next()) { + return s.getString("CREATEDBY"); + } + return null; + } + } + private void assertCommandScopeResolvesProperly() { try { new CommandScope("dropAll"); @@ -49,5 +95,4 @@ private void assertCommandScopeResolvesProperly() { throw new RuntimeException("Unable to load 'dropAll' via Liquibase's CommandScope", e); } } - } diff --git a/integration-tests/liquibase/src/main/resources/application.properties b/integration-tests/liquibase/src/main/resources/application.properties index f2bc258ff694d..a357b4c04ebf7 100644 --- a/integration-tests/liquibase/src/main/resources/application.properties +++ b/integration-tests/liquibase/src/main/resources/application.properties @@ -2,7 +2,13 @@ quarkus.datasource.db-kind=h2 quarkus.datasource.username=sa quarkus.datasource.password=sa -quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test_quarkus;DB_CLOSE_DELAY=-1 +quarkus.datasource.jdbc.url=jdbc:h2:mem:test + +# Second datasource +quarkus.datasource.second.db-kind=h2 +quarkus.datasource.second.username=sa +quarkus.datasource.second.password=sa +quarkus.datasource.second.jdbc.url=jdbc:h2:mem:second;INIT=RUNSCRIPT FROM 'src/main/resources/db/second/initdb.sql' # Liquibase config properties quarkus.liquibase.change-log=db/changeLog.xml @@ -11,6 +17,13 @@ quarkus.liquibase.migrate-at-start=false quarkus.liquibase.database-change-log-lock-table-name=changelog_lock quarkus.liquibase.database-change-log-table-name=changelog +# Config for second datasource with different user / password +quarkus.liquibase.second.username=usr +quarkus.liquibase.second.password=pass +quarkus.liquibase.second.change-log=db/second/changeLog.xml +quarkus.liquibase.second.clean-at-start=false +quarkus.liquibase.second.migrate-at-start=false + # Debug logging #quarkus.log.console.level=DEBUG #quarkus.log.category."liquibase".level=DEBUG diff --git a/integration-tests/liquibase/src/main/resources/db/second/changeLog.xml b/integration-tests/liquibase/src/main/resources/db/second/changeLog.xml new file mode 100644 index 0000000000000..8d79230fa4d32 --- /dev/null +++ b/integration-tests/liquibase/src/main/resources/db/second/changeLog.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/integration-tests/liquibase/src/main/resources/db/second/create-table.xml b/integration-tests/liquibase/src/main/resources/db/second/create-table.xml new file mode 100644 index 0000000000000..7878e39dd51fd --- /dev/null +++ b/integration-tests/liquibase/src/main/resources/db/second/create-table.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/integration-tests/liquibase/src/main/resources/db/second/initdb.sql b/integration-tests/liquibase/src/main/resources/db/second/initdb.sql new file mode 100644 index 0000000000000..ba7afae2a27d8 --- /dev/null +++ b/integration-tests/liquibase/src/main/resources/db/second/initdb.sql @@ -0,0 +1,2 @@ +CREATE USER IF NOT EXISTS usr PASSWORD 'pass' ADMIN; +GRANT ALL ON SCHEMA PUBLIC TO usr; \ No newline at end of file diff --git a/integration-tests/liquibase/src/main/resources/db/second/insert-into-table.xml b/integration-tests/liquibase/src/main/resources/db/second/insert-into-table.xml new file mode 100644 index 0000000000000..60c8d153f099a --- /dev/null +++ b/integration-tests/liquibase/src/main/resources/db/second/insert-into-table.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityTest.java b/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityTest.java index 52246e1254d69..f2b7ccfe6a5ad 100644 --- a/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityTest.java +++ b/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityTest.java @@ -5,6 +5,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; import io.quarkus.test.junit.QuarkusTest; @@ -18,6 +20,17 @@ public void testLiquibaseQuarkusFunctionality() { doTestLiquibaseQuarkusFunctionality(isIncludeAllExpectedToWork()); } + @Test + @DisplayName("Migrates a schema correctly using dedicated username and password from config properties") + @DisabledOnOs(value = OS.WINDOWS, disabledReason = "Our Windows CI does not have Docker installed properly") + public void testLiquibaseUsingDedicatedUsernameAndPassword() { + when().get("/liquibase/updateWithDedicatedUser").then().body(is( + "create-quarkus-table,insert-into-quarkus-table")); + + when().get("/liquibase/created-by").then().body(is( + "USR")); + } + static void doTestLiquibaseQuarkusFunctionality(boolean isIncludeAllExpectedToWork) { when() .get("/liquibase/update") From 0258098f5a69be915a6b74d6a14b08422d6ed7fc Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 17 Apr 2024 16:04:12 +0200 Subject: [PATCH 2/2] Adjust the new Liquibase tests a bit Not sure exactly what was going wrong but I had to create two users to make things work and also make sure everything was done in one go. --- .../LiquibaseFunctionalityResource.java | 32 ++++++++----------- .../src/main/resources/application.properties | 8 ++--- .../src/main/resources/db/second/initdb.sql | 7 ++-- .../liquibase/LiquibaseFunctionalityTest.java | 8 +---- 4 files changed, 23 insertions(+), 32 deletions(-) diff --git a/integration-tests/liquibase/src/main/java/io/quarkus/it/liquibase/LiquibaseFunctionalityResource.java b/integration-tests/liquibase/src/main/java/io/quarkus/it/liquibase/LiquibaseFunctionalityResource.java index 5ac440f938368..501fbdb58a82f 100644 --- a/integration-tests/liquibase/src/main/java/io/quarkus/it/liquibase/LiquibaseFunctionalityResource.java +++ b/integration-tests/liquibase/src/main/java/io/quarkus/it/liquibase/LiquibaseFunctionalityResource.java @@ -2,7 +2,7 @@ import java.sql.Connection; import java.sql.ResultSet; -import java.sql.SQLException; +import java.sql.Statement; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -33,7 +33,7 @@ public class LiquibaseFunctionalityResource { @Inject @DataSource("second") - AgroalDataSource dataSource; + AgroalDataSource secondDataSource; @GET @Path("update") @@ -46,6 +46,7 @@ public String doUpdateAuto() { liquibaseFactory.createLabels()); List changeSets = Objects.requireNonNull(status, "ChangeSetStatus is null! Database update was not applied"); + return changeSets.stream() .filter(ChangeSetStatus::getPreviouslyRan) .map(ChangeSetStatus::getChangeSet) @@ -65,29 +66,22 @@ public String updateWithDedicatedUser() { liquibaseSecondFactory.createLabels()); List changeSets = Objects.requireNonNull(status, "ChangeSetStatus is null! Database update was not applied"); - return changeSets.stream() - .filter(ChangeSetStatus::getPreviouslyRan) - .map(ChangeSetStatus::getChangeSet) - .map(ChangeSet::getId) - .collect(Collectors.joining(",")); + + try (Connection connection = secondDataSource.getConnection()) { + try (Statement s = connection.createStatement()) { + ResultSet rs = s.executeQuery("SELECT CREATEDBY FROM QUARKUS_TABLE WHERE ID = 1"); + if (rs.next()) { + return rs.getString("CREATEDBY"); + } + return null; + } + } } catch (Exception ex) { throw new WebApplicationException(ex.getMessage(), ex); } } - @GET - @Path("created-by") - public String returnCreatedByUser() throws SQLException { - try (Connection connection = dataSource.getConnection()) { - ResultSet s = connection.createStatement().executeQuery("SELECT CREATEDBY FROM QUARKUS_TABLE WHERE ID = 1"); - if (s.next()) { - return s.getString("CREATEDBY"); - } - return null; - } - } - private void assertCommandScopeResolvesProperly() { try { new CommandScope("dropAll"); diff --git a/integration-tests/liquibase/src/main/resources/application.properties b/integration-tests/liquibase/src/main/resources/application.properties index a357b4c04ebf7..036f9cef122dc 100644 --- a/integration-tests/liquibase/src/main/resources/application.properties +++ b/integration-tests/liquibase/src/main/resources/application.properties @@ -2,12 +2,12 @@ quarkus.datasource.db-kind=h2 quarkus.datasource.username=sa quarkus.datasource.password=sa -quarkus.datasource.jdbc.url=jdbc:h2:mem:test +quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 # Second datasource quarkus.datasource.second.db-kind=h2 -quarkus.datasource.second.username=sa -quarkus.datasource.second.password=sa +quarkus.datasource.second.username=readonly +quarkus.datasource.second.password=readonly quarkus.datasource.second.jdbc.url=jdbc:h2:mem:second;INIT=RUNSCRIPT FROM 'src/main/resources/db/second/initdb.sql' # Liquibase config properties @@ -18,7 +18,7 @@ quarkus.liquibase.database-change-log-lock-table-name=changelog_lock quarkus.liquibase.database-change-log-table-name=changelog # Config for second datasource with different user / password -quarkus.liquibase.second.username=usr +quarkus.liquibase.second.username=admin quarkus.liquibase.second.password=pass quarkus.liquibase.second.change-log=db/second/changeLog.xml quarkus.liquibase.second.clean-at-start=false diff --git a/integration-tests/liquibase/src/main/resources/db/second/initdb.sql b/integration-tests/liquibase/src/main/resources/db/second/initdb.sql index ba7afae2a27d8..f1f2c732613c6 100644 --- a/integration-tests/liquibase/src/main/resources/db/second/initdb.sql +++ b/integration-tests/liquibase/src/main/resources/db/second/initdb.sql @@ -1,2 +1,5 @@ -CREATE USER IF NOT EXISTS usr PASSWORD 'pass' ADMIN; -GRANT ALL ON SCHEMA PUBLIC TO usr; \ No newline at end of file +CREATE USER IF NOT EXISTS admin PASSWORD 'pass' ADMIN; +GRANT ALL ON SCHEMA PUBLIC TO admin; + +CREATE USER IF NOT EXISTS readonly PASSWORD 'readonly' ADMIN; +GRANT SELECT ON SCHEMA PUBLIC TO readonly; \ No newline at end of file diff --git a/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityTest.java b/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityTest.java index f2b7ccfe6a5ad..447537cdef628 100644 --- a/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityTest.java +++ b/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityTest.java @@ -5,8 +5,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.OS; import io.quarkus.test.junit.QuarkusTest; @@ -22,13 +20,9 @@ public void testLiquibaseQuarkusFunctionality() { @Test @DisplayName("Migrates a schema correctly using dedicated username and password from config properties") - @DisabledOnOs(value = OS.WINDOWS, disabledReason = "Our Windows CI does not have Docker installed properly") public void testLiquibaseUsingDedicatedUsernameAndPassword() { when().get("/liquibase/updateWithDedicatedUser").then().body(is( - "create-quarkus-table,insert-into-quarkus-table")); - - when().get("/liquibase/created-by").then().body(is( - "USR")); + "ADMIN")); } static void doTestLiquibaseQuarkusFunctionality(boolean isIncludeAllExpectedToWork) {