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 Apr 10, 2024
1 parent 83648b1 commit c95f472
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 4 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,11 +1,14 @@
package io.quarkus.liquibase;

import java.sql.Connection;
import java.sql.DriverManager;
import java.io.FileNotFoundException;
import java.nio.file.Paths;
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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,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 @@ -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;
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
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 c95f472

Please sign in to comment.