Skip to content

Commit

Permalink
Allows to use specific datasource credentials for Liquibase
Browse files Browse the repository at this point in the history
  • Loading branch information
appiepollo14 committed Mar 30, 2024
1 parent eb56608 commit 6569d8c
Show file tree
Hide file tree
Showing 15 changed files with 243 additions and 5 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
17 changes: 17 additions & 0 deletions integration-tests/liquibase/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
Expand Down Expand Up @@ -115,6 +119,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,6 +23,13 @@ public class LiquibaseFunctionalityResource {
@Inject
LiquibaseFactory liquibaseFactory;

@Inject
@LiquibaseDataSource("second")
LiquibaseFactory liquibaseSecondFactory;

@Inject
EntityManager entityManager;

@GET
@Path("update")
public String doUpdateAuto() {
Expand All @@ -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<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() {
return entityManager.createQuery("select a from AppEntity a where a.id = 1", AppEntity.class)
.getSingleResult().getCreatedBy();
}

private void assertCommandScopeResolvesProperly() {
try {
new CommandScope("dropAll");
Expand Down
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,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
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 @@ -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")
Expand Down
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 6569d8c

Please sign in to comment.