This repository has been archived by the owner on May 11, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[#83] Custom PropertySourceLocator using nested directory structure
- Loading branch information
1 parent
457cbd2
commit 9e5345c
Showing
14 changed files
with
384 additions
and
6 deletions.
There are no files selected for viewing
67 changes: 67 additions & 0 deletions
67
...nfra-spring-property/src/main/java/com/ofg/infrastructure/property/FileSystemLocator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package com.ofg.infrastructure.property; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.cloud.config.client.PropertySourceLocator; | ||
import org.springframework.cloud.config.server.SpringApplicationEnvironmentRepository; | ||
import org.springframework.core.env.CompositePropertySource; | ||
import org.springframework.core.env.Environment; | ||
import org.springframework.core.env.MapPropertySource; | ||
import org.springframework.core.env.PropertySource; | ||
|
||
import java.io.File; | ||
import java.util.Map; | ||
|
||
public class FileSystemLocator implements PropertySourceLocator { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(FileSystemLocator.class); | ||
|
||
private final File propertiesFolder; | ||
private final String environment; | ||
private final String applicationName; | ||
private final String countryCode; | ||
|
||
public FileSystemLocator(File propertiesFolder, String environment, String applicationName, String countryCode) { | ||
this.propertiesFolder = propertiesFolder; | ||
this.environment = environment; | ||
this.applicationName = applicationName; | ||
this.countryCode = countryCode; | ||
} | ||
|
||
@Override | ||
public PropertySource<?> locate(Environment environment) { | ||
final SpringApplicationEnvironmentRepository springEnv = new SpringApplicationEnvironmentRepository(); | ||
final String[] propertiesPath = getPropertiesPath(); | ||
log.debug("Loading configuration from {}", propertiesPath); | ||
springEnv.setSearchLocations(propertiesPath); | ||
final org.springframework.cloud.config.Environment loadedEnvs = springEnv.findOne(applicationName, "prod", null); | ||
|
||
CompositePropertySource composite = new CompositePropertySource("configService"); | ||
for (org.springframework.cloud.config.PropertySource source : loadedEnvs.getPropertySources()) { | ||
@SuppressWarnings("unchecked") | ||
Map<String, Object> map = decrypt((Map<String, Object>) source.getSource()); | ||
composite.addPropertySource(new MapPropertySource(source.getName(), map)); | ||
} | ||
return composite; | ||
} | ||
|
||
private String[] getPropertiesPath() { | ||
final File envFolder = new File(propertiesFolder, environment); | ||
final File appFolder = new File(envFolder, applicationName); | ||
final File path = appFolder.getAbsoluteFile(); | ||
return new String[] { | ||
addConfigFile(path, ".properties"), | ||
addConfigFile(path, ".yaml"), | ||
addConfigFile(path, "-" + countryCode + ".properties"), | ||
addConfigFile(path, "-" + countryCode + ".yaml") | ||
}; | ||
} | ||
|
||
private String addConfigFile(File path, String suffix) { | ||
return new File(path, applicationName + suffix).getAbsolutePath(); | ||
} | ||
|
||
private Map<String, Object> decrypt(Map<String, Object> sourceMap) { | ||
return sourceMap; | ||
} | ||
} |
66 changes: 66 additions & 0 deletions
66
...pring-property/src/main/java/com/ofg/infrastructure/property/PropertiesConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package com.ofg.infrastructure.property; | ||
|
||
import org.springframework.context.ApplicationContextInitializer; | ||
import org.springframework.context.ConfigurableApplicationContext; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.core.Ordered; | ||
|
||
import java.io.File; | ||
import java.util.Objects; | ||
|
||
@Configuration | ||
public class PropertiesConfiguration implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered { | ||
|
||
public static final String CONFIG_FOLDER = "CONFIG_FOLDER"; | ||
public static final String APP_ENV = "APP_ENV"; | ||
public static final String COUNTRY_CODE = "countryCode"; | ||
|
||
@Bean | ||
public FileSystemLocator fileSystemLocator() { | ||
return new FileSystemLocator( | ||
findPropertiesFolder(), | ||
findEnvironment(), | ||
findApplicationName(), | ||
findCountry()); | ||
} | ||
|
||
private String findCountry() { | ||
return Objects.requireNonNull(System.getProperty(COUNTRY_CODE), "No " + COUNTRY_CODE + " property found"); | ||
} | ||
|
||
private String findApplicationName() { | ||
return "micro-app"; | ||
} | ||
|
||
private File findPropertiesFolder() { | ||
final File defaultConfigDirectory = new File(System.getProperty("user.home"), "config"); | ||
final String configFolder = getProperty(CONFIG_FOLDER, defaultConfigDirectory.getAbsolutePath()); | ||
return new File(configFolder); | ||
} | ||
|
||
private String findEnvironment() { | ||
final String envOrNull = getProperty(APP_ENV, null); | ||
return Objects.requireNonNull(envOrNull, "No " + APP_ENV + " property found"); | ||
} | ||
|
||
private static String getProperty(String name, String defValue) { | ||
final String valOrNull = System.getProperty(name); | ||
if (valOrNull == null) { | ||
final String envValueOrNull = System.getenv(name); | ||
return envValueOrNull != null ? envValueOrNull : defValue; | ||
} else { | ||
return valOrNull; | ||
} | ||
} | ||
|
||
@Override | ||
public void initialize(ConfigurableApplicationContext applicationContext) { | ||
} | ||
|
||
@Override | ||
public int getOrder() { | ||
return 0; | ||
} | ||
|
||
} |
2 changes: 2 additions & 0 deletions
2
micro-infra-spring-property/src/main/resources/META-INF/spring.factories
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
org.springframework.cloud.bootstrap.BootstrapConfiguration=\ | ||
com.ofg.infrastructure.property.PropertiesConfiguration |
15 changes: 15 additions & 0 deletions
15
...g-property/src/test/groovy/com/ofg/infrastructure/property/AbstractIntegrationTest.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.ofg.infrastructure.property | ||
|
||
import spock.lang.Specification | ||
|
||
import static com.ofg.infrastructure.property.PropertiesConfiguration.APP_ENV | ||
import static com.ofg.infrastructure.property.PropertiesConfiguration.COUNTRY_CODE | ||
|
||
|
||
abstract class AbstractIntegrationTest extends Specification { | ||
|
||
def setupSpec() { | ||
System.setProperty(APP_ENV, "prod") | ||
System.setProperty(COUNTRY_CODE, "pl") | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
...rty/src/test/groovy/com/ofg/infrastructure/property/DecryptingPropertyExtendedTest.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package com.ofg.infrastructure.property | ||
|
||
import org.springframework.boot.builder.SpringApplicationBuilder | ||
import spock.lang.Ignore | ||
|
||
class DecryptingPropertyExtendedTest extends AbstractIntegrationTest { | ||
|
||
def "should fail when encryption key is not provided and there are encrypted passwords"() { | ||
given: | ||
System.setProperty("encrypt.key", "wrongKey") | ||
when: | ||
def context = new SpringApplicationBuilder(DecryptingPropertyTestApp) | ||
.web(false) | ||
.showBanner(false) | ||
.properties("enc:{cipher}bb3336c80dffc7a6d13faea47cf1920cf391a03319249d8a6e38c289d1de7232") | ||
.run() | ||
then: | ||
thrown(IllegalStateException) | ||
cleanup: | ||
context?.close() | ||
} | ||
|
||
@Ignore("Currently fails with NoSuchBeanDefinitionException: TextEncryptor") | ||
def "should not fail when encryption key is not provided and there are no encrypted passwords"() { | ||
when: | ||
def context = new SpringApplicationBuilder(DecryptingPropertyTestApp) | ||
.web(false) | ||
.showBanner(false) | ||
.properties("normal.prop=normal.prop.value") | ||
.run() | ||
then: | ||
context.environment.getProperty("normal.prop") == "normal.prop.value" | ||
cleanup: | ||
context?.close() | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
...ng-property/src/test/groovy/com/ofg/infrastructure/property/DecryptingPropertyTest.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package com.ofg.infrastructure.property | ||
|
||
import org.springframework.boot.builder.SpringApplicationBuilder | ||
import org.springframework.context.ConfigurableApplicationContext | ||
import org.springframework.context.annotation.Configuration | ||
import org.springframework.context.annotation.PropertySource | ||
import spock.lang.Ignore | ||
import spock.lang.Shared | ||
|
||
class DecryptingPropertyTest extends AbstractIntegrationTest { | ||
|
||
@Shared | ||
private ConfigurableApplicationContext context | ||
|
||
def setupSpec() { | ||
System.setProperty("encrypt.key", "eKey") //To simulate setting environment variable | ||
context = new SpringApplicationBuilder(DecryptingPropertyTestApp, TestConfigurationWithPropertySource) | ||
.web(false) | ||
.showBanner(false) | ||
.properties("enc.prop:{cipher}f43b8323cd82a74aafa1fba5efdce529274b58f68145903e6cc7e460e07e0e20") | ||
.run("--spring.config.name=decryptingPropertyTest") | ||
} | ||
|
||
def cleanupSpec() { | ||
System.properties.remove("encrypt.key") //TODO: Replace with @RestoreSystemProperties from Spock 1.0, when available... | ||
context?.close() | ||
} | ||
|
||
def "should decrypt properties from application.properties"() { | ||
expect: | ||
context.environment.getProperty("enc.application.prop") == "enc.application.prop.value" | ||
} | ||
|
||
def "should decrypt properties from set properties"() { | ||
expect: | ||
context.environment.getProperty("enc.prop") == "enc.prop.value" | ||
} | ||
|
||
@Ignore("Currently EnvironmentDecryptApplicationListener is run too early") | ||
def "should decrypt properties added with @PropertySource"() { | ||
expect: | ||
context.environment.getProperty("enc.propertySource.prop") == "enc.propertySource.prop.value" | ||
} | ||
} | ||
|
||
@Configuration | ||
@PropertySource("testConfigurationWithPropertySource.properties") | ||
class TestConfigurationWithPropertySource { | ||
} |
11 changes: 11 additions & 0 deletions
11
...property/src/test/groovy/com/ofg/infrastructure/property/DecryptingPropertyTestApp.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.ofg.infrastructure.property | ||
|
||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration | ||
import org.springframework.context.annotation.ComponentScan | ||
import org.springframework.context.annotation.Configuration | ||
|
||
@Configuration | ||
@EnableAutoConfiguration | ||
@ComponentScan(basePackages = ['org.springframework.cloud.autoconfigure']) | ||
class DecryptingPropertyTestApp { | ||
} |
114 changes: 114 additions & 0 deletions
114
...property/src/test/groovy/com/ofg/infrastructure/property/LoadingFromFileSystemTest.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package com.ofg.infrastructure.property | ||
|
||
import org.springframework.beans.factory.annotation.Value | ||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration | ||
import org.springframework.boot.builder.SpringApplicationBuilder | ||
import org.springframework.context.ConfigurableApplicationContext | ||
import org.springframework.context.annotation.Bean | ||
import org.springframework.context.annotation.ComponentScan | ||
import org.springframework.context.annotation.Configuration | ||
import spock.lang.Ignore | ||
import spock.lang.Shared | ||
|
||
class LoadingFromFileSystemTest extends AbstractIntegrationTest { | ||
|
||
@Shared | ||
private ConfigurableApplicationContext context | ||
@Shared | ||
private MyBean myBean | ||
|
||
def setupSpec() { | ||
System.setProperty(PropertiesConfiguration.CONFIG_FOLDER, findConfigDirInTestResources()) | ||
System.setProperty(PropertiesConfiguration.COUNTRY_CODE, "pl") | ||
context = new SpringApplicationBuilder(BasicApp) | ||
.web(false) | ||
.showBanner(false) | ||
.run() | ||
myBean = context.getBean(MyBean) | ||
} | ||
|
||
private String findConfigDirInTestResources() { | ||
URL resourceInSrcTestRoot = getClass().getResource("/logback-test.groovy") | ||
String srcTestResources = new File(resourceInSrcTestRoot.file).parent | ||
return new File(srcTestResources, 'test-config-dir').absolutePath | ||
} | ||
|
||
void cleanupSpec() { | ||
context?.close() | ||
} | ||
|
||
def 'should read property from default .properties file'() { | ||
expect: | ||
myBean.globalPropKey == 'global prop value' | ||
} | ||
|
||
def 'should read property from default .yaml file'() { | ||
expect: | ||
myBean.countryPropKey == 'country prop value' | ||
} | ||
|
||
def 'should read property from country-specific .properties file'() { | ||
expect: | ||
myBean.globalYamlKey == 'global yaml value' | ||
} | ||
|
||
def 'should read property from country-specific .yaml file'() { | ||
expect: | ||
myBean.countryYamlKey == 'country yaml value' | ||
} | ||
|
||
def 'should override property from country-specific .properties file'() { | ||
expect: | ||
myBean.globalDefaultKey == 'overriden default value' | ||
} | ||
|
||
def 'should override property from country-specific .yaml file'() { | ||
expect: | ||
myBean.globalYamlDefault == 'overriden default yaml value' | ||
} | ||
|
||
def '.yaml has priority over .properties'() { | ||
expect: | ||
myBean.custom == 'yaml value' | ||
myBean.customCountry == 'yaml country value' | ||
} | ||
|
||
} | ||
|
||
@Configuration | ||
@EnableAutoConfiguration | ||
@ComponentScan(basePackages = ['org.springframework.cloud.autoconfigure']) | ||
class BasicApp { | ||
|
||
@Bean | ||
def myBean() { | ||
return new MyBean() | ||
} | ||
|
||
} | ||
|
||
class MyBean { | ||
@Value('${global.prop.key}') | ||
String globalPropKey; | ||
|
||
@Value('${country.prop.key}') | ||
String countryPropKey; | ||
|
||
@Value('${global.yaml.key}') | ||
String globalYamlKey; | ||
|
||
@Value('${country.yaml.key}') | ||
String countryYamlKey; | ||
|
||
@Value('${global.default.key}') | ||
String globalDefaultKey; | ||
|
||
@Value('${global.yaml.default}') | ||
String globalYamlDefault; | ||
|
||
@Value('${custom}') | ||
String custom; | ||
|
||
@Value('${custom.country}') | ||
String customCountry; | ||
} |
5 changes: 2 additions & 3 deletions
5
...test/groovy/com/ofg/infrastructure/property/decrypt/DecryptingPropertyExtendedTest.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.