Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom Liquibase credentials #39361

Merged
merged 2 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Expand Up @@ -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;
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()) {
appiepollo14 marked this conversation as resolved.
Show resolved Hide resolved
AgroalDataSource agroalDataSource = dataSource.unwrap(AgroalDataSource.class);
String jdbcUrl = agroalDataSource.getConfiguration().connectionPoolConfiguration()
.connectionFactoryConfiguration().jdbcUrl();
Connection connection = DriverManager.getConnection(jdbcUrl, config.username.get(), config.password.get());
appiepollo14 marked this conversation as resolved.
Show resolved Hide resolved

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.Statement;
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 secondDataSource;

@GET
@Path("update")
public String doUpdateAuto() {
Expand All @@ -32,6 +46,7 @@ public String doUpdateAuto() {
liquibaseFactory.createLabels());
List<ChangeSetStatus> changeSets = Objects.requireNonNull(status,
"ChangeSetStatus is null! Database update was not applied");

return changeSets.stream()
.filter(ChangeSetStatus::getPreviouslyRan)
.map(ChangeSetStatus::getChangeSet)
Expand All @@ -42,12 +57,36 @@ 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");

try (Connection connection = secondDataSource.getConnection()) {
try (Statement s = connection.createStatement()) {
ResultSet rs = s.executeQuery("SELECT CREATEDBY FROM QUARKUS_TABLE WHERE ID = 1");
if (rs.next()) {
return rs.getString("CREATEDBY");
}
return null;
}
}
} catch (Exception ex) {
throw new WebApplicationException(ex.getMessage(), ex);
}

}

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;DB_CLOSE_DELAY=-1

# Second datasource
quarkus.datasource.second.db-kind=h2
quarkus.datasource.second.username=readonly
quarkus.datasource.second.password=readonly
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=admin
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,5 @@
CREATE USER IF NOT EXISTS admin PASSWORD 'pass' ADMIN;
GRANT ALL ON SCHEMA PUBLIC TO admin;

CREATE USER IF NOT EXISTS readonly PASSWORD 'readonly' ADMIN;
GRANT SELECT ON SCHEMA PUBLIC TO readonly;
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 @@ -18,6 +18,13 @@ 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(
"ADMIN"));
}

static void doTestLiquibaseQuarkusFunctionality(boolean isIncludeAllExpectedToWork) {
when()
.get("/liquibase/update")
Expand Down