Skip to content

Commit d1b229e

Browse files
committed
Merge pull request #25456 from bono007
* pr/25456: Polish contribution Add configuration properties for Flyway's Vault and Conjur support Closes gh-25456
2 parents 14c4221 + 288bece commit d1b229e

File tree

4 files changed

+150
-82
lines changed

4 files changed

+150
-82
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
* @author Dan Zheng
9292
* @author András Deák
9393
* @author Semyon Danilov
94+
* @author Chris Bono
9495
* @since 1.1.0
9596
*/
9697
@Configuration(proxyBeanMethods = false)
@@ -245,6 +246,14 @@ private void configureProperties(FluentConfiguration configuration, FlywayProper
245246
// No method reference for compatibility with Flyway 6.x
246247
map.from(properties.getSkipExecutingMigrations()).whenNonNull()
247248
.to((skipExecutingMigrations) -> configuration.skipExecutingMigrations(skipExecutingMigrations));
249+
// Teams secrets management properties (all non-method reference for
250+
// compatibility with Flyway 6.x)
251+
map.from(properties.getConjurUrl()).to((conjurUrl) -> configuration.conjurUrl(conjurUrl));
252+
map.from(properties.getConjurToken()).to((conjurToken) -> configuration.conjurToken(conjurToken));
253+
map.from(properties.getVaultUrl()).to((vaultUrl) -> configuration.vaultUrl(vaultUrl));
254+
map.from(properties.getVaultToken()).to((vaultToken) -> configuration.vaultToken(vaultToken));
255+
map.from(properties.getVaultSecrets()).whenNot(List::isEmpty)
256+
.to((vaultSecrets) -> configuration.vaultSecrets(vaultSecrets.toArray(new String[0])));
248257
}
249258

250259
private void configureCreateSchemas(FluentConfiguration configuration, boolean createSchemas) {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@
3333
* @author Dave Syer
3434
* @author Eddú Meléndez
3535
* @author Stephane Nicoll
36+
* @author Chris Bono
3637
* @since 1.1.0
3738
*/
3839
@ConfigurationProperties(prefix = "spring.flyway")
@@ -328,6 +329,34 @@ public class FlywayProperties {
328329
*/
329330
private Boolean skipExecutingMigrations;
330331

332+
/**
333+
* REST API URL of the Conjur server. Requires Flyway teams.
334+
*/
335+
private String conjurUrl;
336+
337+
/**
338+
* Conjur token required to access secrets. Requires Flyway teams.
339+
*/
340+
private String conjurToken;
341+
342+
/**
343+
* REST API URL of the Vault server. Requires Flyway teams.
344+
*/
345+
private String vaultUrl;
346+
347+
/**
348+
* Vault token required to access secrets. Requires Flyway teams.
349+
*/
350+
private String vaultToken;
351+
352+
/**
353+
* Comma-separated list of paths to secrets that contain Flyway configurations. This
354+
* must start with the name of the engine followed by '/data/' and end with the name
355+
* of the secret. The resulting form is '{engine}/data/{path}/{to}/{secret_name}'.
356+
* Requires Flyway teams.
357+
*/
358+
private List<String> vaultSecrets;
359+
331360
public boolean isEnabled() {
332361
return this.enabled;
333362
}
@@ -772,4 +801,44 @@ public void setSkipExecutingMigrations(Boolean skipExecutingMigrations) {
772801
this.skipExecutingMigrations = skipExecutingMigrations;
773802
}
774803

804+
public String getConjurUrl() {
805+
return this.conjurUrl;
806+
}
807+
808+
public void setConjurUrl(String conjurUrl) {
809+
this.conjurUrl = conjurUrl;
810+
}
811+
812+
public String getConjurToken() {
813+
return this.conjurToken;
814+
}
815+
816+
public void setConjurToken(String conjurToken) {
817+
this.conjurToken = conjurToken;
818+
}
819+
820+
public String getVaultUrl() {
821+
return this.vaultUrl;
822+
}
823+
824+
public void setVaultUrl(String vaultUrl) {
825+
this.vaultUrl = vaultUrl;
826+
}
827+
828+
public String getVaultToken() {
829+
return this.vaultToken;
830+
}
831+
832+
public void setVaultToken(String vaultToken) {
833+
this.vaultToken = vaultToken;
834+
}
835+
836+
public List<String> getVaultSecrets() {
837+
return this.vaultSecrets;
838+
}
839+
840+
public void setVaultSecrets(List<String> vaultSecrets) {
841+
this.vaultSecrets = vaultSecrets;
842+
}
843+
775844
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java

Lines changed: 70 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@
4646
import org.springframework.boot.jdbc.DataSourceBuilder;
4747
import org.springframework.boot.jdbc.SchemaManagement;
4848
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
49+
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
4950
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
51+
import org.springframework.boot.test.context.runner.ContextConsumer;
5052
import org.springframework.boot.test.system.CapturedOutput;
5153
import org.springframework.boot.test.system.OutputCaptureExtension;
5254
import org.springframework.context.annotation.Bean;
@@ -82,6 +84,7 @@
8284
* @author Dominic Gunn
8385
* @author András Deák
8486
* @author Takaaki Shimbo
87+
* @author Chris Bono
8588
*/
8689
@ExtendWith(OutputCaptureExtension.class)
8790
class FlywayAutoConfigurationTests {
@@ -413,34 +416,21 @@ void configurationCustomizersAreConfiguredAndOrdered() {
413416
@Test
414417
void batchIsCorrectlyMapped() {
415418
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
416-
.withPropertyValues("spring.flyway.batch=true").run((context) -> {
417-
assertThat(context).hasFailed();
418-
Throwable failure = context.getStartupFailure();
419-
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
420-
assertThat(failure).hasMessageContaining(" batch ");
421-
});
419+
.withPropertyValues("spring.flyway.batch=true").run(validateFlywayTeamsPropertyOnly("batch"));
422420
}
423421

424422
@Test
425423
void dryRunOutputIsCorrectlyMapped() {
426424
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
427-
.withPropertyValues("spring.flyway.dryRunOutput=dryrun.sql").run((context) -> {
428-
assertThat(context).hasFailed();
429-
Throwable failure = context.getStartupFailure();
430-
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
431-
assertThat(failure).hasMessageContaining(" dryRunOutput ");
432-
});
425+
.withPropertyValues("spring.flyway.dryRunOutput=dryrun.sql")
426+
.run(validateFlywayTeamsPropertyOnly("dryRunOutput"));
433427
}
434428

435429
@Test
436430
void errorOverridesIsCorrectlyMapped() {
437431
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
438-
.withPropertyValues("spring.flyway.errorOverrides=D12345").run((context) -> {
439-
assertThat(context).hasFailed();
440-
Throwable failure = context.getStartupFailure();
441-
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
442-
assertThat(failure).hasMessageContaining(" errorOverrides ");
443-
});
432+
.withPropertyValues("spring.flyway.errorOverrides=D12345")
433+
.run(validateFlywayTeamsPropertyOnly("errorOverrides"));
444434
}
445435

446436
@Test
@@ -453,45 +443,28 @@ void licenseKeyIsCorrectlyMapped(CapturedOutput output) {
453443
@Test
454444
void oracleSqlplusIsCorrectlyMapped() {
455445
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
456-
.withPropertyValues("spring.flyway.oracle-sqlplus=true").run((context) -> {
457-
assertThat(context).hasFailed();
458-
Throwable failure = context.getStartupFailure();
459-
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
460-
assertThat(failure).hasMessageContaining(" oracle.sqlplus ");
461-
});
446+
.withPropertyValues("spring.flyway.oracle-sqlplus=true")
447+
.run(validateFlywayTeamsPropertyOnly("oracle.sqlplus"));
462448
}
463449

464450
@Test
465451
void oracleSqlplusWarnIsCorrectlyMapped() {
466452
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
467-
.withPropertyValues("spring.flyway.oracle-sqlplus-warn=true").run((context) -> {
468-
assertThat(context).hasFailed();
469-
Throwable failure = context.getStartupFailure();
470-
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
471-
assertThat(failure).hasMessageContaining(" oracle.sqlplusWarn ");
472-
});
453+
.withPropertyValues("spring.flyway.oracle-sqlplus-warn=true")
454+
.run(validateFlywayTeamsPropertyOnly("oracle.sqlplusWarn"));
473455
}
474456

475457
@Test
476458
void streamIsCorrectlyMapped() {
477459
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
478-
.withPropertyValues("spring.flyway.stream=true").run((context) -> {
479-
assertThat(context).hasFailed();
480-
Throwable failure = context.getStartupFailure();
481-
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
482-
assertThat(failure).hasMessageContaining(" stream ");
483-
});
460+
.withPropertyValues("spring.flyway.stream=true").run(validateFlywayTeamsPropertyOnly("stream"));
484461
}
485462

486463
@Test
487464
void undoSqlMigrationPrefix() {
488465
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
489-
.withPropertyValues("spring.flyway.undo-sql-migration-prefix=undo").run((context) -> {
490-
assertThat(context).hasFailed();
491-
Throwable failure = context.getStartupFailure();
492-
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
493-
assertThat(failure).hasMessageContaining(" undoSqlMigrationPrefix ");
494-
});
466+
.withPropertyValues("spring.flyway.undo-sql-migration-prefix=undo")
467+
.run(validateFlywayTeamsPropertyOnly("undoSqlMigrationPrefix"));
495468
}
496469

497470
@Test
@@ -526,67 +499,77 @@ void initSqlsWithFlywayUrl() {
526499
@Test
527500
void cherryPickIsCorrectlyMapped() {
528501
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
529-
.withPropertyValues("spring.flyway.cherry-pick=1.1").run((context) -> {
530-
assertThat(context).hasFailed();
531-
Throwable failure = context.getStartupFailure();
532-
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
533-
assertThat(failure).hasMessageContaining(" cherryPick ");
534-
});
502+
.withPropertyValues("spring.flyway.cherry-pick=1.1").run(validateFlywayTeamsPropertyOnly("cherryPick"));
535503
}
536504

537505
@Test
538506
void jdbcPropertiesAreCorrectlyMapped() {
539507
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
540-
.withPropertyValues("spring.flyway.jdbc-properties.prop=value").run((context) -> {
541-
assertThat(context).hasFailed();
542-
Throwable failure = context.getStartupFailure();
543-
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
544-
assertThat(failure).hasMessageContaining(" jdbcProperties ");
545-
});
508+
.withPropertyValues("spring.flyway.jdbc-properties.prop=value")
509+
.run(validateFlywayTeamsPropertyOnly("jdbcProperties"));
546510
}
547511

548512
@Test
549513
void oracleKerberosCacheFileIsCorrectlyMapped() {
550514
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
551-
.withPropertyValues("spring.flyway.oracle-kerberos-cache-file=/tmp/cache").run((context) -> {
552-
assertThat(context).hasFailed();
553-
Throwable failure = context.getStartupFailure();
554-
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
555-
assertThat(failure).hasMessageContaining(" oracle.kerberosCacheFile ");
556-
});
515+
.withPropertyValues("spring.flyway.oracle-kerberos-cache-file=/tmp/cache")
516+
.run(validateFlywayTeamsPropertyOnly("oracle.kerberosCacheFile"));
557517
}
558518

559519
@Test
560520
void oracleKerberosConfigFileIsCorrectlyMapped() {
561521
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
562-
.withPropertyValues("spring.flyway.oracle-kerberos-config-file=/tmp/config").run((context) -> {
563-
assertThat(context).hasFailed();
564-
Throwable failure = context.getStartupFailure();
565-
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
566-
assertThat(failure).hasMessageContaining(" oracle.kerberosConfigFile ");
567-
});
522+
.withPropertyValues("spring.flyway.oracle-kerberos-config-file=/tmp/config")
523+
.run(validateFlywayTeamsPropertyOnly("oracle.kerberosConfigFile"));
568524
}
569525

570526
@Test
571527
void outputQueryResultsIsCorrectlyMapped() {
572528
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
573-
.withPropertyValues("spring.flyway.output-query-results=false").run((context) -> {
574-
assertThat(context).hasFailed();
575-
Throwable failure = context.getStartupFailure();
576-
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
577-
assertThat(failure).hasMessageContaining(" outputQueryResults ");
578-
});
529+
.withPropertyValues("spring.flyway.output-query-results=false")
530+
.run(validateFlywayTeamsPropertyOnly("outputQueryResults"));
579531
}
580532

581533
@Test
582534
void skipExecutingMigrationsIsCorrectlyMapped() {
583535
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
584-
.withPropertyValues("spring.flyway.skip-executing-migrations=true").run((context) -> {
585-
assertThat(context).hasFailed();
586-
Throwable failure = context.getStartupFailure();
587-
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
588-
assertThat(failure).hasMessageContaining(" skipExecutingMigrations ");
589-
});
536+
.withPropertyValues("spring.flyway.skip-executing-migrations=true")
537+
.run(validateFlywayTeamsPropertyOnly("skipExecutingMigrations"));
538+
}
539+
540+
@Test
541+
void conjurUrlIsCorrectlyMapped() {
542+
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
543+
.withPropertyValues("spring.flyway.conjur-url=http://foo.com/secrets")
544+
.run(validateFlywayTeamsPropertyOnly("conjurUrl"));
545+
}
546+
547+
@Test
548+
void conjurTokenIsCorrectlyMapped() {
549+
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
550+
.withPropertyValues("spring.flyway.conjur-token=5150")
551+
.run(validateFlywayTeamsPropertyOnly("conjurToken"));
552+
}
553+
554+
@Test
555+
void vaultUrlIsCorrectlyMapped() {
556+
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
557+
.withPropertyValues("spring.flyway.vault-url=http://foo.com/secrets")
558+
.run(validateFlywayTeamsPropertyOnly("vaultUrl"));
559+
}
560+
561+
@Test
562+
void vaultTokenIsCorrectlyMapped() {
563+
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
564+
.withPropertyValues("spring.flyway.vault-token=5150")
565+
.run(validateFlywayTeamsPropertyOnly("vaultToken"));
566+
}
567+
568+
@Test
569+
void vaultSecretsIsCorrectlyMapped() {
570+
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
571+
.withPropertyValues("spring.flyway.vault-secrets=kv/data/test/1/config,kv/data/test/2/config")
572+
.run(validateFlywayTeamsPropertyOnly("vaultSecrets"));
590573
}
591574

592575
@Test
@@ -616,6 +599,15 @@ void whenCustomFlywayIsDefinedThenJooqDslContextDependsOnIt() {
616599
});
617600
}
618601

602+
private ContextConsumer<AssertableApplicationContext> validateFlywayTeamsPropertyOnly(String propertyName) {
603+
return (context) -> {
604+
assertThat(context).hasFailed();
605+
Throwable failure = context.getStartupFailure();
606+
assertThat(failure).hasRootCauseInstanceOf(FlywayTeamsUpgradeRequiredException.class);
607+
assertThat(failure).hasMessageContaining(String.format(" %s ", propertyName));
608+
};
609+
}
610+
619611
@Configuration(proxyBeanMethods = false)
620612
static class FlywayDataSourceConfiguration {
621613

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
* Tests for {@link FlywayProperties}.
4141
*
4242
* @author Stephane Nicoll
43+
* @author Chris Bono
4344
*/
4445
class FlywayPropertiesTests {
4546

@@ -108,9 +109,6 @@ void expectedPropertiesAreManaged() {
108109
// Handled by the conversion service
109110
ignoreProperties(configuration, "baselineVersionAsString", "encodingAsString", "locationsAsStrings",
110111
"targetAsString");
111-
// Teams-only properties that we cannot detect as no exception is thrown and
112-
// getters return null
113-
ignoreProperties(configuration, "conjurToken", "conjurUrl", "vaultSecrets", "vaultToken", "vaultUrl");
114112
// Handled as initSql array
115113
ignoreProperties(configuration, "initSql");
116114
ignoreProperties(properties, "initSqls");

0 commit comments

Comments
 (0)