From 6569d8c0f38ad2be0c418c4f91cf7bf652037eb2 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 | 23 ++++++- .../liquibase/runtime/LiquibaseConfig.java | 12 ++++ .../liquibase/runtime/LiquibaseCreator.java | 2 + .../LiquibaseDataSourceRuntimeConfig.java | 14 +++++ integration-tests/liquibase/pom.xml | 17 ++++++ .../io/quarkus/it/liquibase/AppEntity.java | 61 +++++++++++++++++++ .../LiquibaseFunctionalityResource.java | 36 +++++++++++ .../src/main/resources/application.properties | 17 +++++- .../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/LiquibaseFunctionalityPMT.java | 2 +- .../liquibase/LiquibaseFunctionalityTest.java | 13 ++++ 15 files changed, 243 insertions(+), 5 deletions(-) create mode 100644 integration-tests/liquibase/src/main/java/io/quarkus/it/liquibase/AppEntity.java 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 00b34a5af882c..09d26bbee7baf 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 @@ -1,9 +1,12 @@ package io.quarkus.liquibase; +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 liquibase.Contexts; import liquibase.LabelExpression; @@ -31,9 +34,23 @@ public Liquibase createLiquibase() { try (ClassLoaderResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor( Thread.currentThread().getContextClassLoader())) { - 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); database.setDatabaseChangeLogTableName(config.databaseChangeLogTableName); 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 39923b207f611..a4f13d77054dd 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 @@ -80,4 +80,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 785425e30b1cd..df6e795cb415f 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 @@ -32,6 +32,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/pom.xml b/integration-tests/liquibase/pom.xml index 51828d7e90191..162ea09f041d1 100644 --- a/integration-tests/liquibase/pom.xml +++ b/integration-tests/liquibase/pom.xml @@ -26,6 +26,10 @@ io.quarkus quarkus-jdbc-h2 + + io.quarkus + quarkus-hibernate-orm + io.quarkus quarkus-resteasy @@ -115,6 +119,19 @@ + + io.quarkus + quarkus-hibernate-orm-deployment + ${project.version} + pom + test + + + * + * + + + diff --git a/integration-tests/liquibase/src/main/java/io/quarkus/it/liquibase/AppEntity.java b/integration-tests/liquibase/src/main/java/io/quarkus/it/liquibase/AppEntity.java new file mode 100644 index 0000000000000..1c78c03f53301 --- /dev/null +++ b/integration-tests/liquibase/src/main/java/io/quarkus/it/liquibase/AppEntity.java @@ -0,0 +1,61 @@ +package io.quarkus.it.liquibase; + +import java.util.Objects; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +/** + * Entity used within tests + */ +@Entity +@Table(name = "quarkus_table") +public class AppEntity { + + @Id + private int id; + + private String name; + + private String createdBy; + + public int getId() { + return id; + } + + public void setId(final int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + final AppEntity appEntity = (AppEntity) o; + return id == appEntity.id; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} 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..972cec46289c1 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 @@ -5,10 +5,12 @@ import java.util.stream.Collectors; import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.WebApplicationException; +import io.quarkus.liquibase.LiquibaseDataSource; import io.quarkus.liquibase.LiquibaseFactory; import liquibase.Liquibase; import liquibase.changelog.ChangeSet; @@ -21,6 +23,13 @@ public class LiquibaseFunctionalityResource { @Inject LiquibaseFactory liquibaseFactory; + @Inject + @LiquibaseDataSource("second") + LiquibaseFactory liquibaseSecondFactory; + + @Inject + EntityManager entityManager; + @GET @Path("update") public String doUpdateAuto() { @@ -42,6 +51,33 @@ 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() { + return entityManager.createQuery("select a from AppEntity a where a.id = 1", AppEntity.class) + .getSingleResult().getCreatedBy(); + } + 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 f2bc258ff694d..4fe58c15f5dca 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,15 @@ 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 +quarkus.hibernate-orm.validation.enabled=false +quarkus.hibernate-orm.datasource=second + # 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/LiquibaseFunctionalityPMT.java b/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityPMT.java index 1bcb6cb604d16..be116d45939ab 100644 --- a/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityPMT.java +++ b/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityPMT.java @@ -16,7 +16,7 @@ public class LiquibaseFunctionalityPMT { @RegisterExtension static final QuarkusProdModeTest config = new QuarkusProdModeTest() .withApplicationRoot(jar -> jar - .addClasses(LiquibaseApp.class, LiquibaseFunctionalityResource.class) + .addClasses(AppEntity.class, LiquibaseApp.class, LiquibaseFunctionalityResource.class) .addAsResource("db") .addAsResource("application.properties")) .setApplicationName("liquibase-prodmode-test") 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")