Skip to content

When creating an initialization-specific DataSource, Flyway, Liquibase and our script-based auto-configuration all incorrectly assume that the primary DataSource will always have been derived from DataSourceProperties #25643

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

Closed
wilkinsona opened this issue Mar 15, 2021 · 1 comment
Assignees
Labels
type: bug A general bug
Milestone

Comments

@wilkinsona
Copy link
Member

wilkinsona commented Mar 15, 2021

If a user defines their own DataSource bean and configures flyway.user, FlywayAutoConfiguration will create a DataSource that doesn't work or that points to a different database. For example, with HSQLDB on the classpath, imagine that the user configures their own DataSource bean:

@Bean
DataSource dataSource() {
    return DataSourceBuilder.create().url("jdbc:hsqldb:mem:user-configured")
            .username("alice").password("secret").build();
}

And also configures some custom credentials for Flyway to use:

spring.flyway.user=bob
spring.flyway.password=confidential

When the application starts, Flyway will migrate a different in-memory database:

2021-03-15 10:31:40.579  INFO 64744 --- [           main] o.f.c.internal.license.VersionPrinter    : Flyway Community Edition 7.1.1 by Redgate
2021-03-15 10:31:40.850  INFO 64744 --- [           main] o.f.c.i.database.base.DatabaseType       : Database: jdbc:hsqldb:mem:4b57a301-1a5a-4c81-91ec-38349439c30d (HSQL Database Engine 2.5)
2021-03-15 10:31:40.881  INFO 64744 --- [           main] o.f.core.internal.command.DbValidate     : Successfully validated 1 migration (execution time 00:00.017s)
2021-03-15 10:31:40.888  INFO 64744 --- [           main] o.f.c.i.s.JdbcTableSchemaHistory         : Creating Schema History table "PUBLIC"."flyway_schema_history" ...
2021-03-15 10:31:40.911  INFO 64744 --- [           main] o.f.core.internal.command.DbMigrate      : Current version of schema "PUBLIC": << Empty Schema >>
2021-03-15 10:31:40.914  INFO 64744 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version "1 - init"
2021-03-15 10:31:40.926  INFO 64744 --- [           main] o.f.core.internal.command.DbMigrate      : Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.019s)

Alternatively, if HSQLDB is swapped for an external database such as Postgres:

@Bean
DataSource dataSource() {
    return DataSourceBuilder.create().url("jdbc:postgresql://localhost/example")
            .username("alice").password("secret").build();
}

The application will fail to start:


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.3)

2021-03-15 10:45:16.814  INFO 66071 --- [           main] hManuallyConfiguredDatasourceApplication : Starting FlywayWithManuallyConfiguredDatasourceApplication using Java 1.8.0_282 on wilkinsona-a01.vmware.com with PID 66071 (/Users/awilkinson/dev/workspaces/spring-projects/spring-boot/master/flyway-with-manually-configured-datasource/target/classes started by awilkinson in /Users/awilkinson/dev/workspaces/spring-projects/spring-boot/master/flyway-with-manually-configured-datasource)
2021-03-15 10:45:16.816  INFO 66071 --- [           main] hManuallyConfiguredDatasourceApplication : No active profile set, falling back to default profiles: default
2021-03-15 10:45:17.363  WARN 66071 --- [           main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'flywayInitializer' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Unsatisfied dependency expressed through method 'flywayInitializer' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flyway' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.flywaydb.core.Flyway]: Factory method 'flyway' threw exception; nested exception is org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Failed to determine suitable jdbc url
2021-03-15 10:45:17.374  INFO 66071 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-03-15 10:45:17.388 ERROR 66071 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine suitable jdbc url


Action:

Consider the following:
	If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
	If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

The failure analysis hasn't helped very much here as it has hidden the fact that the failure is occurring when trying to configure Flyway. Running with --debug reveals the point at which the failure occurs:

2021-03-15 10:48:17.294 DEBUG 66817 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : Application failed to start due to an exception

org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Failed to determine suitable jdbc url
	at org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.determineUrl(DataSourceProperties.java:280) ~[spring-boot-autoconfigure-2.4.3.jar:2.4.3]
	at org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration$FlywayConfiguration.getProperty(FlywayAutoConfiguration.java:294) ~[spring-boot-autoconfigure-2.4.3.jar:2.4.3]
	at org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration$FlywayConfiguration.configureDataSource(FlywayAutoConfiguration.java:149) ~[spring-boot-autoconfigure-2.4.3.jar:2.4.3]
	at org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration$FlywayConfiguration.flyway(FlywayAutoConfiguration.java:133) ~[spring-boot-autoconfigure-2.4.3.jar:2.4.3]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_282]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_282]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_282]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_282]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1334) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:541) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1334) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.4.jar:5.3.4]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:917) ~[spring-context-5.3.4.jar:5.3.4]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:582) ~[spring-context-5.3.4.jar:5.3.4]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:767) [spring-boot-2.4.3.jar:2.4.3]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) [spring-boot-2.4.3.jar:2.4.3]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:426) [spring-boot-2.4.3.jar:2.4.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:326) [spring-boot-2.4.3.jar:2.4.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1311) [spring-boot-2.4.3.jar:2.4.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1300) [spring-boot-2.4.3.jar:2.4.3]
	at com.example.demo.FlywayWithManuallyConfiguredDatasourceApplication.main(FlywayWithManuallyConfiguredDatasourceApplication.java:17) [classes/:na]

2021-03-15 10:48:17.294 ERROR 66817 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine suitable jdbc url


Action:

Consider the following:
	If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
	If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

I think we need to decouple Flyway's auto-configuration from DataSourceProperties. Ideally, we'd derive the configuration of the Flyway-specific DataSource from the metadata of the primary DataSource. If that's not possible then we'll need to provide a new type that encapsulates everything we need to create the Flyway-specific DataSource. Users would then need to define a bean that is an instance of this type for Flyway auto-configuration to work with Flyway-specific credentials.

@wilkinsona wilkinsona added the type: bug A general bug label Mar 15, 2021
@wilkinsona wilkinsona changed the title When creating a Flyway-specific DataSource, FlywayAutoConfiguration incorrectly assumes that the primary DataSource was derived from DataSourceProperties When creating a Flyway-specific DataSource, FlywayAutoConfiguration incorrectly assumes that the primary DataSource will always have been derived from DataSourceProperties Mar 15, 2021
@wilkinsona wilkinsona changed the title When creating a Flyway-specific DataSource, FlywayAutoConfiguration incorrectly assumes that the primary DataSource will always have been derived from DataSourceProperties When creating an initialization-specific DataSource, Flyway, Liquibase and our script-based auto-configuration all incorrectly assume that the primary DataSource will always have been derived from DataSourceProperties Mar 15, 2021
@wilkinsona
Copy link
Member Author

The auto-configuration for Liquibase and our own script-based initialization are similarly broken.

@philwebb philwebb self-assigned this Mar 15, 2021
philwebb added a commit that referenced this issue Mar 18, 2021
Refactor `DataSourceBuilder` to use direct property mappers rather than
the `Binder` and aliases. Supported DataSource types now include two-way
mappers which allows us to both get and set properties in a uniform way.

A new `derivedFrom` factory method has been added which allows a new
`DataSource` to be derived from an existing one. This update is
primarily to allow Flyway and Liquibase migrations to work against a
`@Bean` configured DataSource rather than assuming that the primary
DataSource was always created via auto-configuration.

See gh-25643
@philwebb philwebb added this to the 2.5.0-M3 milestone Mar 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

2 participants