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 fd851026ac..93be73ff97 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 @@ -91,6 +91,7 @@ import org.springframework.util.StringUtils; * @author Dan Zheng * @author András Deák * @author Semyon Danilov + * @author Chris Bono * @since 1.1.0 */ @Configuration(proxyBeanMethods = false) @@ -245,6 +246,15 @@ public class FlywayAutoConfiguration { // No method reference for compatibility with Flyway 6.x map.from(properties.getSkipExecutingMigrations()).whenNonNull() .to((skipExecutingMigrations) -> configuration.skipExecutingMigrations(skipExecutingMigrations)); + // Teams secrets management properties (all non-method reference for + // compatibility with Flyway 6.x) + map.from(properties.getConjurUrl()).whenNonNull().to((conjurUrl) -> configuration.conjurUrl(conjurUrl)); + map.from(properties.getConjurToken()).whenNonNull() + .to((conjurToken) -> configuration.conjurToken(conjurToken)); + map.from(properties.getVaultUrl()).whenNonNull().to((vaultUrl) -> configuration.vaultUrl(vaultUrl)); + map.from(properties.getVaultToken()).whenNonNull().to((vaultToken) -> configuration.vaultToken(vaultToken)); + map.from(properties.getVaultSecrets()).whenNonNull() + .to((vaultSecrets) -> configuration.vaultSecrets(vaultSecrets.toArray(new String[0]))); } 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 142a459576..518531d30b 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 @@ -33,6 +33,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * @author Dave Syer * @author Eddú Meléndez * @author Stephane Nicoll + * @author Chris Bono * @since 1.1.0 */ @ConfigurationProperties(prefix = "spring.flyway") @@ -328,6 +329,33 @@ public class FlywayProperties { */ private Boolean skipExecutingMigrations; + /** + * REST API URL of the Conjur server. Requires Flyway teams. + */ + private String conjurUrl; + + /** + * Conjur token required to access secrets. Requires Flyway teams. + */ + private String conjurToken; + + /** + * REST API URL of the Vault server. Requires Flyway teams. + */ + private String vaultUrl; + + /** + * Vault token required to access secrets. Requires Flyway teams. + */ + private String vaultToken; + + /** + * Comma-separated list of paths to secrets that contain Flyway configurations. Each + * path must start with the name of the engine and end with the name of the secret + * such 'kv/test/1/config'. Requires Flyway teams. + */ + private List vaultSecrets; + public boolean isEnabled() { return this.enabled; } @@ -772,4 +800,44 @@ public class FlywayProperties { this.skipExecutingMigrations = skipExecutingMigrations; } + public String getConjurUrl() { + return this.conjurUrl; + } + + public void setConjurUrl(String conjurUrl) { + this.conjurUrl = conjurUrl; + } + + public String getConjurToken() { + return this.conjurToken; + } + + public void setConjurToken(String conjurToken) { + this.conjurToken = conjurToken; + } + + public String getVaultUrl() { + return this.vaultUrl; + } + + public void setVaultUrl(String vaultUrl) { + this.vaultUrl = vaultUrl; + } + + public String getVaultToken() { + return this.vaultToken; + } + + public void setVaultToken(String vaultToken) { + this.vaultToken = vaultToken; + } + + public List getVaultSecrets() { + return this.vaultSecrets; + } + + public void setVaultSecrets(List vaultSecrets) { + this.vaultSecrets = vaultSecrets; + } + } 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 9e60ef80bf..7f8d3c6047 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 @@ -46,7 +46,9 @@ import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfigurati import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jdbc.SchemaManagement; import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.annotation.Bean; @@ -82,6 +84,7 @@ import static org.mockito.Mockito.mock; * @author Dominic Gunn * @author András Deák * @author Takaaki Shimbo + * @author Chris Bono */ @ExtendWith(OutputCaptureExtension.class) class FlywayAutoConfigurationTests { @@ -413,34 +416,21 @@ class FlywayAutoConfigurationTests { @Test void batchIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.batch=true").run((context) -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); - assertThat(failure).hasMessageContaining(" batch "); - }); + .withPropertyValues("spring.flyway.batch=true").run(validateTeamsPropertyCorrectlyMapped("batch")); } @Test void dryRunOutputIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.dryRunOutput=dryrun.sql").run((context) -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); - assertThat(failure).hasMessageContaining(" dryRunOutput "); - }); + .withPropertyValues("spring.flyway.dryRunOutput=dryrun.sql") + .run(validateTeamsPropertyCorrectlyMapped("dryRunOutput")); } @Test void errorOverridesIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.errorOverrides=D12345").run((context) -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); - assertThat(failure).hasMessageContaining(" errorOverrides "); - }); + .withPropertyValues("spring.flyway.errorOverrides=D12345") + .run(validateTeamsPropertyCorrectlyMapped("errorOverrides")); } @Test @@ -453,45 +443,28 @@ class FlywayAutoConfigurationTests { @Test void oracleSqlplusIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.oracle-sqlplus=true").run((context) -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); - assertThat(failure).hasMessageContaining(" oracle.sqlplus "); - }); + .withPropertyValues("spring.flyway.oracle-sqlplus=true") + .run(validateTeamsPropertyCorrectlyMapped("oracle.sqlplus")); } @Test void oracleSqlplusWarnIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.oracle-sqlplus-warn=true").run((context) -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); - assertThat(failure).hasMessageContaining(" oracle.sqlplusWarn "); - }); + .withPropertyValues("spring.flyway.oracle-sqlplus-warn=true") + .run(validateTeamsPropertyCorrectlyMapped("oracle.sqlplusWarn")); } @Test void streamIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.stream=true").run((context) -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); - assertThat(failure).hasMessageContaining(" stream "); - }); + .withPropertyValues("spring.flyway.stream=true").run(validateTeamsPropertyCorrectlyMapped("stream")); } @Test void undoSqlMigrationPrefix() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.undo-sql-migration-prefix=undo").run((context) -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); - assertThat(failure).hasMessageContaining(" undoSqlMigrationPrefix "); - }); + .withPropertyValues("spring.flyway.undo-sql-migration-prefix=undo") + .run(validateTeamsPropertyCorrectlyMapped("undoSqlMigrationPrefix")); } @Test @@ -526,67 +499,78 @@ class FlywayAutoConfigurationTests { @Test void cherryPickIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.cherry-pick=1.1").run((context) -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); - assertThat(failure).hasMessageContaining(" cherryPick "); - }); + .withPropertyValues("spring.flyway.cherry-pick=1.1") + .run(validateTeamsPropertyCorrectlyMapped("cherryPick")); } @Test void jdbcPropertiesAreCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.jdbc-properties.prop=value").run((context) -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); - assertThat(failure).hasMessageContaining(" jdbcProperties "); - }); + .withPropertyValues("spring.flyway.jdbc-properties.prop=value") + .run(validateTeamsPropertyCorrectlyMapped("jdbcProperties")); } @Test void oracleKerberosCacheFileIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.oracle-kerberos-cache-file=/tmp/cache").run((context) -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); - assertThat(failure).hasMessageContaining(" oracle.kerberosCacheFile "); - }); + .withPropertyValues("spring.flyway.oracle-kerberos-cache-file=/tmp/cache") + .run(validateTeamsPropertyCorrectlyMapped("oracle.kerberosCacheFile")); } @Test void oracleKerberosConfigFileIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.oracle-kerberos-config-file=/tmp/config").run((context) -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); - assertThat(failure).hasMessageContaining(" oracle.kerberosConfigFile "); - }); + .withPropertyValues("spring.flyway.oracle-kerberos-config-file=/tmp/config") + .run(validateTeamsPropertyCorrectlyMapped("oracle.kerberosConfigFile")); } @Test void outputQueryResultsIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.output-query-results=false").run((context) -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); - assertThat(failure).hasMessageContaining(" outputQueryResults "); - }); + .withPropertyValues("spring.flyway.output-query-results=false") + .run(validateTeamsPropertyCorrectlyMapped("outputQueryResults")); } @Test void skipExecutingMigrationsIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.skip-executing-migrations=true").run((context) -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); - assertThat(failure).hasMessageContaining(" skipExecutingMigrations "); - }); + .withPropertyValues("spring.flyway.skip-executing-migrations=true") + .run(validateTeamsPropertyCorrectlyMapped("skipExecutingMigrations")); + } + + @Test + void conjurUrlIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.conjur-url=http://foo.com/secrets") + .run(validateTeamsPropertyCorrectlyMapped("conjurUrl")); + } + + @Test + void conjurTokenIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.conjur-token=5150") + .run(validateTeamsPropertyCorrectlyMapped("conjurToken")); + } + + @Test + void vaultUrlIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.vault-url=http://foo.com/secrets") + .run(validateTeamsPropertyCorrectlyMapped("vaultUrl")); + } + + @Test + void vaultTokenIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.vault-token=5150") + .run(validateTeamsPropertyCorrectlyMapped("vaultToken")); + } + + @Test + void vaultSecretsIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.vault-secrets=kv/test/1/config,kv/test/2/config") + .run(validateTeamsPropertyCorrectlyMapped("vaultSecrets")); } @Test @@ -616,6 +600,15 @@ class FlywayAutoConfigurationTests { }); } + private ContextConsumer validateTeamsPropertyCorrectlyMapped(String propertyName) { + return (context) -> { + assertThat(context).hasFailed(); + Throwable failure = context.getStartupFailure(); + assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class); + assertThat(failure).hasMessageContaining(String.format(" %s ", propertyName)); + }; + } + @Configuration(proxyBeanMethods = false) static class FlywayDataSourceConfiguration { 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 f2c039997f..5a1ae774cd 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 @@ -40,6 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link FlywayProperties}. * * @author Stephane Nicoll + * @author Chris Bono */ class FlywayPropertiesTests { @@ -108,9 +109,6 @@ class FlywayPropertiesTests { // Handled by the conversion service ignoreProperties(configuration, "baselineVersionAsString", "encodingAsString", "locationsAsStrings", "targetAsString"); - // Teams-only properties that we cannot detect as no exception is thrown and - // getters return null - ignoreProperties(configuration, "conjurToken", "conjurUrl", "vaultSecrets", "vaultToken", "vaultUrl"); // Handled as initSql array ignoreProperties(configuration, "initSql"); ignoreProperties(properties, "initSqls");