I'm trying to use the liquibase gradle plugin and as it stands, I'd need to duplicate all of the connection settings that are usually found in properties files (or other sources), and write them in the gradle file. With many environments, I think this will inevitably lead to these two separate configuration setups going out of sync.
I think it would be much better if we could instantiate an Environment outside of the spring runtime and have it populate configuration settings like it would when starting an app, as described in the official documentation.
As far as I can see, this could be done somewhat easily by either making prepareEnvironment in SpringApplication.java public, or creating a separate helper method for this use case.
In my opinion this feature would be immensely useful, so far so that I believe I must've missed some obvious way to achieve this.
Comment From: wilkinsona
It's not clear to me how the feature that you have requested would help you to use the Liquibase Gradle plugin. Could you expand on that a bit please?
I also wonder if you've considered taking a different approach where you start your app in a different mode such that it migrates the database and then shuts down. That could look something like this:
public static void main(String[] args) {
if (args.length > 0 && args[0].equals("migrate")) {
new SpringApplicationBuilder(DatabaseMigration.class).web(WebApplicationType.NONE).run(args);
}
else {
SpringApplication.run(YourApplication.class, args);
}
}
@ImportAutoConfiguration(classes = { LiquibaseAutoConfiguration.class, DataSourceAutoConfiguration.class })
class DatabaseMigration {
}
You may also be interested in the discussion and suggestions in https://github.com/spring-projects/spring-boot/issues/787.
Comment From: Dzeri96
@wilkinsona So this comment in the linked discussion does what I want to do. They take properties from spring configuration files and use them to configure the gradle liquibase plugin, however their approach is extremely limited, because it has no support for variable interpolation, active profiles and so on.
What I'm proposing is to create an interface which would use Spring Boot's default property resolution to fetch properties without having to run the application. Sure, it would help me with liquibase, but it would be a general solution that goes beyond liquibase and gradle - the ability to leverage all of the work done on the configuration "engine", outside of spring boot. Your proposal would technically work, but as people have said in the original discussion, it would be nice to separate concerns. Also, imagine if someone wanted to use 5 gradle plugins. They'd have to maintain 6 different ways their app can start, just so they wouldn't have to duplicate config files.
Going back to my use case, this is something I've managed to implement yesterday as a proof of concept. You'll notice that it's more robust than the solution I linked above, but is still very brittle.
// Extract properties as Spring Boot would
StandardEnvironment springBootEnvironment = new StandardEnvironment();
PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver()
String activeProfilesEnvVariable = "$System.env.spring_profiles_active"
String[] profiles = activeProfilesEnvVariable.split(",")
println "Active spring profiles: " + profiles
if (activeProfilesEnvVariable != "null" && profiles.length != 0) {
for (final def profile in profiles) {
for (final def resDir in sourceSets.main.getResources().srcDirs) {
String searchPath = Paths.get("file:" + resDir.toString(), "application-" + profile + ".properties").toString()
var resources = resourcePatternResolver.getResources(searchPath)
for (final def res in resources) {
springBootEnvironment.getPropertySources().addLast(new ResourcePropertySource(res))
}
}
}
}
springBootEnvironment
.getPropertySources()
.addLast(new ResourcePropertySource(resourcePatternResolver.getResource("file:src/main/resources/application.properties")))
liquibase {
activities {
main {
databaseChangeLogLockTableName springBootEnvironment.getProperty("spring.liquibase.database-change-log-lock-table")
databaseChangeLogTableName springBootEnvironment.getProperty("spring.liquibase.database-change-log-table")
changelogFile springBootEnvironment.getProperty("spring.liquibase.change-log")
driver springBootEnvironment.getProperty("spring.liquibase.driver-class-name")
url springBootEnvironment.getProperty("spring.datasource.url")
username springBootEnvironment.getProperty("spring.datasource.username")
password springBootEnvironment.getProperty("spring.datasource.password")
promptForNonLocalDatabase true
}
}
}
With this, I already have support for active profiles, variable interpolation, ENV/fallback values, and file merging. Imagine if I could just call some function buildDefaultSpringEnvironment(String[] activeProfiles) and it would do everything for me.
Comment From: philwebb
The ConfigDataEnvironmentPostProcessor is one of the more complicated parts of our codebase and I think trying to extract logic to allow it to run outside of the application would be quite an endeavor. I'm afraid we can't really justify the investment given the relatively small number of folks who would benefit from it.
I think Andy's suggestion above would probably be our recommended way to proceed.
Thanks anyway for suggesting the idea.
Comment From: wilkinsona
Another option would be to define the relevant properties in the build and then pass them into an application.properties file at build time through resource processing and expansion of property references.
Comment From: Dzeri96
Alright, too bad. I hoped we could just make a few methods public and leave everything else as-is.