Gradle flyway script to update multiple databases - gradle

I have a Gradle script which uses the flyway plugin to create some database tables, and now I need to update the script so that it can work with 2 separate sets of flyway scripts, each of which updates a separate database.
In the single-database version of the script I simply do this:
flyway {
url = 'jdbc:edb://localhost:5432/mydb'
schemas = ['my_schema']
user = 'my_user'
password = 'my_pass'
locations = ['filesystem:src/main/resources/db/flyway/my_db']
}
I've been experimenting with declaring a new task to run the scripts for the second db:
task flywayMigrate2 << {
ext {
flyway {
url = 'jdbc:edb://localhost:5432/mydb2'
schemas = ['my_schema2']
user = 'my_user2'
password = 'my_pass2'
locations = ['filesystem:src/main/resources/db/flyway/my_db2']
}
}
}
flywayMigrate2.finalizedBy flywayMigrate
My Gradle skills are poor, but I know this isn't the right way to do it - my understanding is that the ext block overwrites the original flyway configuration, so if I wanted to run flywayMigrate after flywayMigrate2 it would continue to use the second set of config values rather than reverting to the original set.
I can't be the first person to need to do this, but I'm struggling to find a decent approach, can anyone help?

The flyway { ... } extension object is for configuring properties which are common to all flyway tasks. Each task also has properties which can be configured, my guess is that task level properties override properties configured on the flyway { ... } extension object.
I think you'd just configure two FlywayMigrateTask instances in your build.gradle
import org.flywaydb.gradle.task.*
task migrate1(type: FlywayMigrateTask) {
url = 'jdbc:edb://localhost:5432/mydb1'
user = 'user1'
locations = ['filesystem:src/main/flyway/db1/migration']
// etc
}
task migrate2(type: FlywayMigrateTask) {
url = 'jdbc:edb://localhost:5432/mydb2'
user = 'user2'
locations = ['filesystem:src/main/flyway/db2/migration']
// etc
}
See also AbstractFlywayTask.java

I don't think that you have to define another Gradle task in order to introduce a second Flyway configuration.
According to the documentation you simply can overwrite either using Gradle properties
gradle -Pflyway.user=myUsr -Pflyway.schemas=schema1,schema2 -Pflyway.placeholders.keyABC=valXYZ
or system properties
gradle -Dflyway.user=myUser -Dflyway.schemas=schema1,schema2 -Dflyway.placeholders.keyABC=valueXYZ
or providing an external Flyway config
gradle -Dflyway.configFiles=path/to/myAlternativeConfig.conf flywayMigrate
The Flyway Gradle plugin follows the following configuration order:
System properties
Environment variables
Custom config files
Gradle properties
Flyway configuration section in build.gradle
<user-home>/flyway.conf
Flyway Gradle plugin defaults
Alternatively you could introduce a new task that overwrites the Gradle properties of Flyway.
task migrate2(FlywayMigrateTask) {
project.ext['flyway.user']='myUsr'
project.ext['flyway.password']='mySecretPwd'
project.ext['flyway.schemas']='schema1,schema2,schema3'
project.ext['flyway.placeholders.keyABC']='valueXYZ'
project.ext['flyway.placeholders.otherplaceholder']='value123'
}

Related

Gradle Version Catalog (Published): How to dynamically set up repository

I'm not that experienced with Gradle and are currently running into problems when trying to use the new version catalog feature.
Goal:
Using a Gradle 7.4.2 version catalog, managed in a standalone GIT repository and published to private JFrog artifactory, in a second project.
Every project member's artifactory credentials are already available in $HOME/.gradle/gradle.properties (auto-generated by JFrog) and are supposed to be re-used.
Issue:
according to the current Gradle documentation, a published version catalog is supposed to be defined in settings.gradle(.kts) within any project that wants to use the catalog;
inserting that piece of code results in an error because Gradle has no repository definition available for artifact look-up
therefore, adding a repository definition:
// my settings.gradle.kts
rootProject.name = "catalog-consumer"
dependencyResolutionManagement {
val catalogVersion = "0.1.0"
val artifactoryUri = "..."
val catalogGAV = "..."
repositories{
maven {
url = uri("$artifactoryUri")
credentials {
// TODO: how to access user's local gradle.properties for credentials?
username = "$artifactory_user" // key as generated by JFrog
password = "$artifactory_password" // key as generated by JFrog
}
}
}
versionCatalogs {
create("libs") {
from("$catalogGAV")
}
}
}
now, facing the problem that the user's gradle.properties does not seem to be loaded, yet - but hardcoding credentials is not viable :)
Question:
Is the only option to manually check for and load the user's gradle.properties file?
Originally, when reading the documentation, I assumed that the settings file would probably try to look up existing repository definitions from the project's build.gradle.kts, but that wasn't the case either. If I understand it correctly, the settings file is evaluated before everything else, isn't it?
Manually loading the user's config just seems odd to me, therefore, I wanted to ask whether or not I'm missing a mechanism or lifecycle hook that would take care of this. Also possible that I use the version catalog feature incorrectly :D
Any hints very much appreciated!
See the docs here: https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:handling_credentials
Named repository credentials
If you named the repository and add credentials(PasswordCredentials::class)...
// ./settings.gradle.kts
dependencyResolutionManagement {
repositories {
maven {
name = "mySecureRepository"
credentials(PasswordCredentials::class)
// url = uri(<<some repository url>>)
}
}
}
then Gradle will automatically fetch the username/pass from the first found definition:
Using a command line argument
./gradlew build -PmySecureRepositoryUsername=my-username
environment variables prefixed with ORG_GRADLE_PROJECT_ (this is useful for CI/CD)
ORG_GRADLE_PROJECT_mySecureRepositoryUsername=my-username
ORG_GRADLE_PROJECT_mySecureRepositoryPassword=my-password
$GRADLE_USER_HOME/gradle.properties
mySecureRepositoryUsername=my-username
mySecureRepositoryPassword=my-password
gradle.properties in the project root - obviously don't put credentials in your project!
gradle.properties in the Gradle installation directory
Manual providers
If you need to manually set the property names, then you can define your own providers.
// ./settings.gradle.kts
val artifactoryUser = providers.gradleProperty("artifactory_user")
val artifactoryPassword = providers.gradleProperty("artifactory_password")
dependencyResolutionManagement {
repositories {
maven {
name = "mySecureRepository"
credentials {
username = artifactoryUser.get()
password = artifactoryPassword.get()
}
// url = uri(<<some repository url>>)
}
}
}
Again, then Gradle will fetch these properties from either
$GRADLE_USER_HOME/gradle.properties
artifactory_user=my-username
artifactory_password=my-password
or environment variables
ORG_GRADLE_PROJECT_artifactory_user=myUsername
ORG_GRADLE_PROJECT_artifactory_password=my-password

Multiple Gradle bootJar Configurations - Different mainClass Attributes?

I have a Gradle/Spring Boot build using a .kts (Kotlin Style) Gradle build file. (That last part might not matter.)
My Spring Boot application has two different classes that can be used to start it, one for "client" mode and one for "server" mode.
I have a single bootJar task that looks like:
tasks.bootJar {
mainClassName = "com.me.ClientApplication"
}
So by default when I run ./gradlew build bootJar, I get a client version of the JAR that when run with java -jar theJar.jar, executes the ClientApplication class.
However, I would also like to publish a second JAR that has a different mainClassName and runs com.me.ServerApplication instead.
How should I approach this? I'm fairly new to Gradle - should I be providing the mainClassName to the ./gradlew command to override it? Or can I define separate tasks like clientBootJar and serverBootJar that will produce separate artifacts?
Since your configuration is mutually exclusive (can't have two main files), I would say that having two tasks would be optimal:
tasks.bootJarClient {
mainClassName = "com.me.ClientApplication"
}
tasks.bootJarClient {
mainClassName = "com.me.ServerApplication"
}
Other approach is to use properties, but it makes more sense mostly if you have much greater variance.
Now to make a custom task, in your build.gradle.kts add something along those lines (didn't test it with SpringBoot specifically):
open class BootJarClient : BootJar() {
override mainClassName = "YourClientClassName"
}
tasks.register<BootJarClient>("bootJarClient") {
group = "Other"
}
// Server is basically the same
Naming of the properties are taken from GitHub sources.

ORA-01882: timezone region not found in Liquibase Gradle plugin

In our project we are using Liquibase gradle plugin. Recently, we updated ojdbc8 plugin to version 18.3.0.0. Unfortunately, it caused our Liquibase task to fail with ORA-01882: timezone region not found. I found some solutions for this error (e.g. there: ORA-01882: timezone region not found), but I have no idea how I could add this -Duser.timezone or -Doracle.jdbc.timezoneAsRegion property to gradle task. I tried different approaches, but with no success.
This is how some crucial parts of our build.gradle look like:
liquibase {
activities {
oracle {
changeLogFile "$liquibasePath/db.changelog-master.xml"
driver liquibaseProps['oracle.driver']
url "jdbc:oracle:thin:#${liquibaseProps['oracle.ip.port']}:${liquibaseProps['oracle.schema']}"
username liquibaseProps['oracle.username']
password liquibaseProps['oracle.password']
outputDefaultSchema false
outputDefaultCatalog false
}
}
}
def generate(taskName, taskDescription, generateCommand) {
project.task(taskName, type: LiquibaseTask) {
group = 'Liquibase'
description = taskDescription
inputs.property('databases', getRunList())
inputs.dir liquibasePath
outputs.dir sqlScriptsPath
doLast {
new LiquibaseSqlCleanupTask(sqlScriptsPath).execute()
}
}
}
You need to set that as a system property when running gradle. The docs for that are at https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_system_properties, but here is a copy/paste of the most relevant info:
Using the -D command-line option, you can pass a system property to
the JVM which runs Gradle. The -D option of the gradle command has the
same effect as the -D option of the java command.
You can also set system properties in gradle.properties files with the
prefix systemProp.
So you could create a gradle.properties file in the root directory of your project with contents like this:
systemProp.oracle.jdbc.timezoneAsRegion=false

How to use ant.properties in a `vendor.gradle` vendor settings file?

I have a multi-project gradle build with an ant build script that contains a task such as this (simplified):
<target name="get-version">
<!-- Goes out and fetches the versioning for each project and sets prop -->
<property name="version" value="${myDesiredVersion}" />
</target>
and inside my build.gradle script I have an ant script loader and then I depend on this target; my issue is that this isn't being evaluated in a way that allows me to use my ant.properties['version'] in vendor.gradle like
// version = '1.0.2' -- works
version = ant.properties['version'] // doesn't work
I'm a gradle noobie and to top it off I'm migrating an ant project to start using gradle so I could be way off course. It's important to note that I can't just outright include the version in the vendor.gradle file since it's being generated by the ant-script.
I'm not familiar with how I could insert this ahead of the gradle lifecycle... I tried something like this
gradle.beforeProject { p ->
tasks['get-version'].execute() // appears to execute successfully?
configure(p) {
def vendorSettings = file("${myRoot}/vendor.gradle")
if(vendorSettings.exists()){
println "Loading vendor settings for project " + p
println ant.properties['version'] // but outputs null here
// ant.properties is still a valid observable map
apply from: vendorSettings
}
}
}
and the ant version property was still null - note that it works outside of this, I think the scoping is a little different then I think it is in this specific situation
You might have something like
task antTask {
//...
doLast {
file("vendor.properties").withInputStream { is ->
Properties props = new Properties()
props.load(is)
props.each { prop -> ext.set(prop.key, prop.value) }
}
}
}
And after the task is executed you could access properties like ext.version, etc.
But, this won't work if you want to use the properties in task configurations, because all the tasks are configured first, only after that are executed if necessary. So your antTask is running only after all other tasks are already configured. So you probably need to rethink how you generate these ant properties and either precompute them before running gradle, or port the logic to gradle build.

Gradle: How do I run my LIquibase changesets as part of my normal build process?

I'm using Gradle 2.7 with the Gradle Liquibase plugin v 1.1.1. How do I run my changeSets as part of doing my normal build using
gradle build
? I currently have this in my build.gradle file ...
liquibase {
activities {
main {
File propsFile = new File("${project.rootDir}/src/main/resources/liquibase.properties")
Properties properties = new Properties()
properties.load(new FileInputStream(propsFile))
changeLogFile 'src/main/resources/db.changelog-1.0.xmlll'
url '${url}'
username '${username}'
password '${password}'
}
runList = main
}
}
However when I run the above command ("gradle build"), the above is not run. I know its not run because the username/password are incorrect and I have no change log file ending in ".xmlll". WHat else do I need to add to assure that hte plugin always attempts to execute the changeset?
You need to define a task dependency from build to update.
build.dependsOn update
To get task to run before your tests (assuming you didn't define a new one), you can do
test.dependsOn update
Ref:
Gradle task docs
You can define this in your gradle build file
apply plugin: 'liquibase'
dependencies {
classpath 'org.liquibase:liquibase-gradle-plugin:1.2.0'
}
liquibase {
activities {
main {
changeLogFile 'changelog.xml'
url 'jdbc:h2:db/liquibase_workshop;FILE_LOCK=NO'
username 'sa'
password ''
}
}
And then run this command
gradle update

Resources