Skip to content

Commit

Permalink
Allows to use specific datasource credentials for Liquibase
Browse files Browse the repository at this point in the history
Fixes #31214
  • Loading branch information
appiepollo14 committed Mar 31, 2024
1 parent 8823d1f commit 6fc144d
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,16 @@ public class LiquibaseConfig {
*/
public Optional<String> 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<String> 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<String> password = Optional.empty();

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ public static final LiquibaseDataSourceRuntimeConfig defaultConfig() {
@ConfigItem
public Optional<String> 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<String> 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<String> password = Optional.empty();

/**
* The name of the catalog with the liquibase tables.
*/
Expand Down
9 changes: 4 additions & 5 deletions integration-tests/liquibase/pom.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-integration-tests-parent</artifactId>
<groupId>io.quarkus</groupId>
Expand Down Expand Up @@ -50,9 +48,12 @@
<artifactId>quarkus-test-h2</artifactId>
<scope>test</scope>
</dependency>

<!-- Minimal test dependencies to *-deployment artifacts for consistent build order -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>

<scope>test</scope>
<exclusions>
<exclusion>
Expand All @@ -61,8 +62,6 @@
</exclusion>
</exclusions>
</dependency>

<!-- Minimal test dependencies to *-deployment artifacts for consistent build order -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2-deployment</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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() {
Expand All @@ -42,12 +56,43 @@ public String doUpdateAuto() {
}
}

@GET
@Path("updateWithDedicatedUser")
public String updateWithDedicatedUser() {
try (Liquibase liquibase = liquibaseSecondFactory.createLiquibase()) {
liquibase.update(liquibaseSecondFactory.createContexts(), liquibaseSecondFactory.createLabels());
List<ChangeSetStatus> status = liquibase.getChangeSetStatuses(liquibaseSecondFactory.createContexts(),
liquibaseSecondFactory.createLabels());
List<ChangeSetStatus> 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");
} catch (Exception e) {
throw new RuntimeException("Unable to load 'dropAll' via Liquibase's CommandScope", e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">

<include relativeToChangelogFile="true" file="create-table.xml"/>
<include relativeToChangelogFile="true" file="insert-into-table.xml"/>
</databaseChangeLog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">

<changeSet author="dev" id="create-quarkus-table">
<createTable tableName="QUARKUS_TABLE">
<column name="ID" type="INT">
<constraints nullable="false"/>
</column>
<column name="NAME" type="VARCHAR(255)"/>
<column name="CREATEDBY" type="VARCHAR(100)" defaultValueComputed="CURRENT_USER">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
</databaseChangeLog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CREATE USER IF NOT EXISTS usr PASSWORD 'pass' ADMIN;
GRANT ALL ON SCHEMA PUBLIC TO usr;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">

<changeSet author="dev" id="insert-into-quarkus-table">
<insert tableName="QUARKUS_TABLE">
<column name="ID" value="1"/>
<column name="NAME" value="1.0.1 #[title]"/>
</insert>
</changeSet>

</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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")
Expand Down

0 comments on commit 6fc144d

Please sign in to comment.