From f8e9abd1c8efd99d29decb0e7e928e5fa2727d10 Mon Sep 17 00:00:00 2001 From: asjervanasten Date: Sun, 3 Mar 2024 21:07:57 +0100 Subject: [PATCH] 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 258c33815d843e..1e4f916ab50d90 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 d26d6d25c480aa..db1c460fa1b64c 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 3af8105e1c621c..de1f2e47a1f36e 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 c3d20635e86120..a6c06d69fb47ab 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 639590f5d5b584..c8cb0d1cf3e562 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 b71310c69e38cd..5ac440f9383686 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 f2bc258ff694d1..a357b4c04ebf7e 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 00000000000000..8d79230fa4d328 --- /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 00000000000000..7878e39dd51fd3 --- /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 00000000000000..ba7afae2a27d80 --- /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 00000000000000..60c8d153f099a6 --- /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 52246e1254d69c..f2b7ccfe6a5ad9 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")