Skip to content
This repository has been archived by the owner on May 11, 2021. It is now read-only.

Commit

Permalink
[#83] Custom PropertySourceLocator using nested directory structure
Browse files Browse the repository at this point in the history
  • Loading branch information
nurkiewicz committed Nov 12, 2014
1 parent 457cbd2 commit 9e5345c
Show file tree
Hide file tree
Showing 14 changed files with 384 additions and 6 deletions.
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;
}
}
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;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.ofg.infrastructure.property.PropertiesConfiguration
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")
}
}
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()
}
}
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 {
}
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 {
}
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;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.ofg.infrastructure.property.decrypt

import com.ofg.infrastructure.property.AbstractIntegrationTest
import org.springframework.boot.builder.SpringApplicationBuilder
import spock.lang.Ignore
import spock.lang.Specification

class DecryptingPropertyExtendedTest extends Specification {
class DecryptingPropertyExtendedTest extends AbstractIntegrationTest {

def "should fail when encryption key is not provided and there are encrypted passwords"() {
given:
Expand Down
Loading

0 comments on commit 9e5345c

Please sign in to comment.