diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java index 37b35946f5b..adcbd84a497 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java @@ -175,6 +175,7 @@ public class FlywayAutoConfiguration { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); String[] locations = new LocationResolver(configuration.getDataSource()) .resolveLocations(properties.getLocations()).toArray(new String[0]); + configureFailOnMissingLocations(configuration, properties.isFailOnMissingLocations()); map.from(locations).to(configuration::locations); map.from(properties.getEncoding()).to(configuration::encoding); map.from(properties.getConnectRetries()).to(configuration::connectRetries); @@ -252,6 +253,23 @@ public class FlywayAutoConfiguration { map.from(properties.getVaultToken()).to((vaultToken) -> configuration.vaultToken(vaultToken)); map.from(properties.getVaultSecrets()).whenNot(List::isEmpty) .to((vaultSecrets) -> configuration.vaultSecrets(vaultSecrets.toArray(new String[0]))); + // No method reference for compatibility with Flyway < 7.8 + map.from(properties.getIgnoreMigrationPatterns()).whenNot(List::isEmpty) + .to((ignoreMigrationPatterns) -> configuration + .ignoreMigrationPatterns(ignoreMigrationPatterns.toArray(new String[0]))); + // No method reference for compatibility with Flyway version < 7.9 + map.from(properties.getDetectEncoding()) + .to((detectEncoding) -> configuration.detectEncoding(detectEncoding)); + } + + private void configureFailOnMissingLocations(FluentConfiguration configuration, + boolean failOnMissingLocations) { + try { + configuration.failOnMissingLocations(failOnMissingLocations); + } + catch (NoSuchMethodError ex) { + // Flyway < 7.9 + } } private void configureCreateSchemas(FluentConfiguration configuration, boolean createSchemas) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java index b5e9ae7af82..61087b52483 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java @@ -52,6 +52,11 @@ public class FlywayProperties { @Deprecated private boolean checkLocation = true; + /** + * Whether to fail if a location of migration scripts doesn't exist. + */ + private boolean failOnMissingLocations; + /** * Locations of migrations scripts. Can contain the special "{vendor}" placeholder to * use vendor-specific locations. @@ -355,6 +360,18 @@ public class FlywayProperties { */ private List vaultSecrets; + /** + * Ignore migrations that match this comma-separated list of patterns when validating + * migrations. Requires Flyway Teams. + */ + private List ignoreMigrationPatterns; + + /** + * Whether to attempt to automatically detect SQL migration file encoding. Requires + * Flyway Teams. + */ + private Boolean detectEncoding; + public boolean isEnabled() { return this.enabled; } @@ -375,6 +392,14 @@ public class FlywayProperties { this.checkLocation = checkLocation; } + public boolean isFailOnMissingLocations() { + return this.failOnMissingLocations; + } + + public void setFailOnMissingLocations(boolean failOnMissingLocations) { + this.failOnMissingLocations = failOnMissingLocations; + } + public List getLocations() { return this.locations; } @@ -842,4 +867,20 @@ public class FlywayProperties { this.vaultSecrets = vaultSecrets; } + public List getIgnoreMigrationPatterns() { + return this.ignoreMigrationPatterns; + } + + public void setIgnoreMigrationPatterns(List ignoreMigrationPatterns) { + this.ignoreMigrationPatterns = ignoreMigrationPatterns; + } + + public Boolean getDetectEncoding() { + return this.detectEncoding; + } + + public void setDetectEncoding(final Boolean detectEncoding) { + this.detectEncoding = detectEncoding; + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index 0a39f1ec261..ca90fc991fe 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -288,6 +288,7 @@ class FlywayAutoConfigurationTests { } @Test + @Deprecated void checkLocationsAllMissing() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.locations:classpath:db/missing1,classpath:db/migration2") @@ -299,6 +300,7 @@ class FlywayAutoConfigurationTests { } @Test + @Deprecated void checkLocationsAllExist() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.locations:classpath:db/changelog,classpath:db/migration") @@ -306,6 +308,7 @@ class FlywayAutoConfigurationTests { } @Test + @Deprecated void checkLocationsAllExistWithImplicitClasspathPrefix() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.locations:db/changelog,db/migration") @@ -313,12 +316,53 @@ class FlywayAutoConfigurationTests { } @Test + @Deprecated void checkLocationsAllExistWithFilesystemPrefix() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.locations:filesystem:src/test/resources/db/migration") .run((context) -> assertThat(context).hasNotFailed()); } + @Test + void failOnMissingLocationsAllMissing() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.check-location=false", + "spring.flyway.fail-on-missing-locations=true") + .withPropertyValues("spring.flyway.locations:classpath:db/missing1,classpath:db/migration2") + .run((context) -> { + assertThat(context).hasFailed(); + assertThat(context).getFailure().isInstanceOf(BeanCreationException.class); + assertThat(context).getFailure().hasMessageContaining("Unable to resolve location"); + }); + } + + @Test + void failOnMissingLocationsAllExist() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.check-location=false", + "spring.flyway.fail-on-missing-locations=true") + .withPropertyValues("spring.flyway.locations:classpath:db/changelog,classpath:db/migration") + .run((context) -> assertThat(context).hasNotFailed()); + } + + @Test + void failOnMissingLocationsAllExistWithImplicitClasspathPrefix() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.check-location=false", + "spring.flyway.fail-on-missing-locations=true") + .withPropertyValues("spring.flyway.locations:db/changelog,db/migration") + .run((context) -> assertThat(context).hasNotFailed()); + } + + @Test + void failOnMissingLocationsAllExistWithFilesystemPrefix() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.check-location=false", + "spring.flyway.fail-on-missing-locations=true") + .withPropertyValues("spring.flyway.locations:filesystem:src/test/resources/db/migration") + .run((context) -> assertThat(context).hasNotFailed()); + } + @Test void customFlywayMigrationStrategy() { this.contextRunner diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java index 7ff514f9000..60d00403b14 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java @@ -48,6 +48,7 @@ class FlywayPropertiesTests { void defaultValuesAreConsistent() { FlywayProperties properties = new FlywayProperties(); Configuration configuration = new FluentConfiguration(); + assertThat(configuration.getFailOnMissingLocations()).isEqualTo(properties.isFailOnMissingLocations()); assertThat(properties.getLocations().stream().map(Location::new).toArray(Location[]::new)) .isEqualTo(configuration.getLocations()); assertThat(properties.getEncoding()).isEqualTo(configuration.getEncoding()); @@ -91,6 +92,7 @@ class FlywayPropertiesTests { assertThat(configuration.isSkipDefaultResolvers()).isEqualTo(properties.isSkipDefaultResolvers()); assertThat(configuration.isValidateMigrationNaming()).isEqualTo(properties.isValidateMigrationNaming()); assertThat(configuration.isValidateOnMigrate()).isEqualTo(properties.isValidateOnMigrate()); + assertThat(properties.getDetectEncoding()).isNull(); } @Test @@ -119,6 +121,8 @@ class FlywayPropertiesTests { ignoreProperties(configuration, "shouldCreateSchemas"); // Getters for the DataSource settings rather than actual properties ignoreProperties(configuration, "password", "url", "user"); + // Properties not exposed by Flyway + ignoreProperties(configuration, "failOnMissingTarget"); List configurationKeys = new ArrayList<>(configuration.keySet()); Collections.sort(configurationKeys); List propertiesKeys = new ArrayList<>(properties.keySet()); diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 7111d31c38f..d672f1b06bb 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -306,7 +306,7 @@ bom { ] } } - library("Flyway", "7.7.3") { + library("Flyway", "7.9.2") { group("org.flywaydb") { modules = [ "flyway-core"