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..0d3daa1b1229a 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 = (AgroalDataSource) dataSource; + 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..87ea462c1f220 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. + */ + 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 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..d872f8f0eed73 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 JDBC URL is configured, falls back to the datasource username if not specified. + */ + @ConfigItem + public Optional username = Optional.empty(); + + /** + * The password that Liquibase uses to connect to the database. + * If no specific JDBC URL is configured, falls back to the datasource password if not specified. + */ + @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..45447fa5e75b1 100644 --- a/integration-tests/liquibase/pom.xml +++ b/integration-tests/liquibase/pom.xml @@ -26,6 +26,14 @@ io.quarkus quarkus-jdbc-h2 + + io.quarkus + quarkus-jdbc-postgresql + + + io.quarkus + quarkus-hibernate-orm + io.quarkus quarkus-resteasy @@ -76,6 +84,19 @@ + + io.quarkus + quarkus-jdbc-postgresql-deployment + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-liquibase-deployment @@ -115,6 +136,23 @@ + + io.quarkus + quarkus-hibernate-orm-deployment + ${project.version} + pom + test + + + * + * + + + + + jakarta.persistence + jakarta.persistence-api + 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..7377ffaaf8fa2 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("it") + 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..a4cbf3d75fa3c 100644 --- a/integration-tests/liquibase/src/main/resources/application.properties +++ b/integration-tests/liquibase/src/main/resources/application.properties @@ -2,7 +2,11 @@ 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 + +# PostGreSQL second datasource +quarkus.datasource."it".db-kind=postgresql +quarkus.datasource."it".devservices.init-script-path=db/second/initdb.sql # Liquibase config properties quarkus.liquibase.change-log=db/changeLog.xml @@ -11,6 +15,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."it".username=usr +quarkus.liquibase."it".password=pass +quarkus.liquibase."it".change-log=db/second/changeLog.xml +quarkus.liquibase."it".clean-at-start=false +quarkus.liquibase."it".migrate-at-start=false +quarkus.hibernate-orm.validation.enabled=false +quarkus.hibernate-orm.datasource=it + # 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..35fbb5519fb7b --- /dev/null +++ b/integration-tests/liquibase/src/main/resources/db/second/initdb.sql @@ -0,0 +1,2 @@ +CREATE USER usr with login password 'pass'; +GRANT ALL ON DATABASE it 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..54092ebdd416d 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 @@ -18,6 +18,20 @@ public void testLiquibaseQuarkusFunctionality() { doTestLiquibaseQuarkusFunctionality(isIncludeAllExpectedToWork()); } + @Test + @DisplayName("Migrates a schema correctly using dedicated username and password from config properties") + public void testLiquibaseUsingDedicatedUsernameAndPassword() { + when().get("/liquibase/updateWithDedicatedUser").then().body(is( + "create-quarkus-table,insert-into-quarkus-table")); + } + + @Test + @DisplayName("Checks the user") + public void testCreatedByUserIsDifferent() { + when().get("/liquibase/created-by").then().body(is( + "usr")); + } + static void doTestLiquibaseQuarkusFunctionality(boolean isIncludeAllExpectedToWork) { when() .get("/liquibase/update")