Rewrite local connector to eliminate static direct API calls for
configuration.
This commit is contained in:
@@ -3,30 +3,39 @@ package org.springframework.cloud.util;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.cloud.CloudConnector;
|
||||
import org.springframework.cloud.CloudException;
|
||||
|
||||
/**
|
||||
* Environment available to the deployed app.
|
||||
*
|
||||
*
|
||||
* The main purpose of this class is to allow unit-testing of {@link CloudConnector} implementations
|
||||
* that rely on environment
|
||||
*
|
||||
*
|
||||
* @author Ramnivas Laddad
|
||||
*/
|
||||
public class EnvironmentAccessor {
|
||||
|
||||
|
||||
public Map<String, String> getEnv() {
|
||||
return System.getenv();
|
||||
}
|
||||
|
||||
|
||||
public String getEnvValue(String key) {
|
||||
return System.getenv(key);
|
||||
}
|
||||
|
||||
public String getPropertyValue(String key) {
|
||||
return System.getProperty(key);
|
||||
|
||||
public Properties getSystemProperties() {
|
||||
return System.getProperties();
|
||||
}
|
||||
|
||||
public String getSystemProperty(String key) {
|
||||
return getSystemProperty(key, null);
|
||||
}
|
||||
|
||||
public String getSystemProperty(String key, String def) {
|
||||
return System.getProperty(key, def);
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
#Spring Cloud local-configuration connector
|
||||
|
||||
This connector provides the ability to configure Spring Cloud services locally for development
|
||||
or testing. The current implementation reads from Java properties only; in order to prevent
|
||||
dependencies on the Spring Framework, the placeholder functionality is unavailable in the
|
||||
connector. Pull requests for also inspecting environment variables are welcome.
|
||||
or testing. The current implementation reads from Java properties only. Pull requests for also
|
||||
inspecting environment variables are welcome.
|
||||
|
||||
##Quick start
|
||||
|
||||
Since service URIs contain passwords and should not be stored in code, this connector does not
|
||||
attempt to read properties out of the classpath. You can provide a filename with service definitions
|
||||
by setting the `spring.cloud.propertiesFile` system property:
|
||||
attempt to read service definitions out of the classpath. You can provide service definitions
|
||||
as system properties
|
||||
|
||||
````
|
||||
java -Dspring.cloud.database='mysql://user:pass@host:1234/dbname' -jar my-app.jar
|
||||
````
|
||||
|
||||
and from a configuration properties file either by setting the `spring.cloud.propertiesFile` system property
|
||||
|
||||
````
|
||||
java -Dspring.cloud.propertiesFile=/path/to/spring-cloud.properties -jar my-app.jar
|
||||
````
|
||||
|
||||
or by passing in an open `InputStream`:
|
||||
or by providing a *bootstrap* properties file on the runtime classpath named
|
||||
`spring-cloud-bootstrap.properties`. This file will be inspected for only
|
||||
the property named `spring.cloud.propertiesFile`, and its value will be interpolated
|
||||
from the system properties.
|
||||
|
||||
````java
|
||||
InputStream propertyStream = new FileInputStream("/path/to/spring-cloud.properties");
|
||||
LocalConfigConnector.supplyProperties(propertyStream);
|
||||
Cloud cloud = new CloudFactory().getCloud();
|
||||
````properties
|
||||
spring.cloud.propertiesFile: ${user.home}/.config/myApp/spring-cloud.properties
|
||||
````
|
||||
|
||||
The property file should contain an application ID and the desired services in this format:
|
||||
The system properties or the configuration properties file should contain an application ID
|
||||
and the desired services in this format:
|
||||
|
||||
````properties
|
||||
spring.cloud.appId: myApp
|
||||
@@ -32,8 +39,7 @@ spring.cloud.database: mysql://user:pass@host:1234/dbname
|
||||
````
|
||||
|
||||
Service type is determined by the URI scheme. The connector will activate if it finds a property
|
||||
(in the system properties, supplied properties, or the file provided in `spring.cloud.propertiesFile`)
|
||||
named `spring.cloud.appId`.
|
||||
(in the system properties or the configuration properties file) named `spring.cloud.appId`.
|
||||
|
||||
##Property sources
|
||||
|
||||
@@ -43,31 +49,33 @@ This connector first attempts to read the system properties generally and a syst
|
||||
If a system property named `spring.cloud.propertiesFile` is found, that file will be loaded
|
||||
as a property list.
|
||||
|
||||
###Programmatically supplying properties
|
||||
You can programmatically supply a property source by calling the static method
|
||||
`LocalConfigConnector.supplyProperties(InputStream)` before invoking `getCloud()`.
|
||||
Calling this method will cause the connector to read the stream as a property list
|
||||
and then close the stream. Calling this method after invoking `getCloud()` will
|
||||
still read the stream, but the properties will have no effect on the connector
|
||||
service configuration. Calling this method multiple times will load the supplied
|
||||
streams onto the same `Properties` object, overwriting duplicates.
|
||||
###Providing a bootstrap properties file
|
||||
|
||||
To avoid having to manually configure run configurations or test runners with the path to the
|
||||
configuration properties file, the connector supports reading a templated filename out of the
|
||||
runtime classpath. This file must be named `spring-cloud-bootstrap.properties` and located
|
||||
at the classpath root, and for security the connector will not attempt to read any service URIs
|
||||
out of it. If the connector does find the file, it will read the property
|
||||
`spring.cloud.propertiesFile` and [substitute the pattern
|
||||
`${system.property}`](http://commons.apache.org/proper/commons-lang/javadocs/api-release/index.html?org/apache/commons/lang3/text/StrSubstitutor.html)
|
||||
with the appropriate value from the system properties. The most useful option is generally
|
||||
`${user.home}`.
|
||||
|
||||
A configuration properties file specified in the system properties will override any bootstrap
|
||||
file that may be available on the classpath.
|
||||
|
||||
###Property precedence
|
||||
To provide the maximum configuration flexibility, the connector will scan the available
|
||||
property sources in this order:
|
||||
|
||||
- programmatically-supplied properties
|
||||
- properties read from `spring.cloud.propertiesFile`
|
||||
- system properties
|
||||
|
||||
The last definition of a specific service ID wins. The connector will log a message at
|
||||
To provide the maximum configuration flexibility, the connector will override any properties
|
||||
(both application ID and service definitions) specified in the file at `spring.cloud.propertiesFile`
|
||||
with system properties defined at runtime. The connector will log a message at
|
||||
`WARN` if you override a service ID.
|
||||
|
||||
##Activating the connector
|
||||
|
||||
The Spring Cloud core expects exactly one cloud connector match the runtime environment.
|
||||
This connector identifies the "local cloud" by the presence of a property named
|
||||
`spring.cloud.appId`, which will be used in the `ApplicationInstanceInfo`.
|
||||
`spring.cloud.appId` in a configuration properties file or the system properties,
|
||||
which will be used in the `ApplicationInstanceInfo`.
|
||||
|
||||
##Service definitions
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@ description = 'Spring Cloud local-configuration connector'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-cloud-core')
|
||||
testCompile 'com.github.stefanbirkner:system-rules:1.5.0'
|
||||
compile 'org.apache.commons:commons-lang3:3.3.2'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.springframework.cloud.localconfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -20,6 +21,7 @@ import org.springframework.cloud.app.BasicApplicationInstanceInfo;
|
||||
import org.springframework.cloud.service.BaseServiceInfo;
|
||||
import org.springframework.cloud.service.FallbackBaseServiceInfoCreator;
|
||||
import org.springframework.cloud.service.UriBasedServiceData;
|
||||
import org.springframework.cloud.util.EnvironmentAccessor;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -46,11 +48,13 @@ public class LocalConfigConnector extends AbstractCloudConnector<UriBasedService
|
||||
public static final List<String> META_PROPERTIES = Collections.unmodifiableList(
|
||||
Arrays.asList(new String[] { APP_ID_PROPERTY, PROPERTIES_FILE_PROPERTY }));
|
||||
|
||||
/*--------------- sources for service-definition properties ---------------*/
|
||||
/*--------------- inject system property access for testing ---------------*/
|
||||
|
||||
static Properties programmaticProperties = new Properties();
|
||||
private EnvironmentAccessor env = new EnvironmentAccessor();
|
||||
|
||||
private Properties fileProperties = null;
|
||||
void setEnvironmentAccessor(EnvironmentAccessor env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
/*--------------- API implementation ---------------*/
|
||||
|
||||
@@ -59,6 +63,10 @@ public class LocalConfigConnector extends AbstractCloudConnector<UriBasedService
|
||||
super((Class) LocalConfigServiceInfoCreator.class);
|
||||
}
|
||||
|
||||
/*--------------- properties read out of the file at spring.cloud.propertiesFile ---------------*/
|
||||
|
||||
private Properties fileProperties = null;
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a property named {@code spring.cloud.appId} is present in any of the property sources.
|
||||
* On the first call, attempts to load properties from a file specified in {@code spring.cloud.propertiesFile}.
|
||||
@@ -79,15 +87,14 @@ public class LocalConfigConnector extends AbstractCloudConnector<UriBasedService
|
||||
|
||||
@Override
|
||||
protected List<UriBasedServiceData> getServicesData() {
|
||||
if(fileProperties == null)
|
||||
if (fileProperties == null)
|
||||
throw new IllegalStateException("isInMatchingCloud() must be called first to initialize connector");
|
||||
|
||||
LinkedHashMap<String, Properties> propertySources = new LinkedHashMap<String, Properties>();
|
||||
|
||||
propertySources.put("programmatic properties", programmaticProperties);
|
||||
propertySources.put("properties from file", fileProperties);
|
||||
try {
|
||||
propertySources.put("system properties", System.getProperties());
|
||||
propertySources.put("system properties", env.getSystemProperties());
|
||||
} catch (SecurityException e) {
|
||||
logger.log(Level.WARNING,
|
||||
"couldn't read system properties; no service definitions from system properties will be applied", e);
|
||||
@@ -103,20 +110,6 @@ public class LocalConfigConnector extends AbstractCloudConnector<UriBasedService
|
||||
|
||||
/*--------------- methods for manipulating properties and sources ---------------*/
|
||||
|
||||
/**
|
||||
* Adds properties to be scanned from the supplied {@link InputStream}, overwriting
|
||||
* existing properties with the same name. Closes the stream after loading.
|
||||
*
|
||||
* @param propertiesInputStream
|
||||
* a property list
|
||||
* @throws IOException
|
||||
* if the underlying load operation throws an exception
|
||||
*/
|
||||
public static void supplyProperties(final InputStream propertiesInputStream) throws IOException {
|
||||
programmaticProperties.load(propertiesInputStream);
|
||||
propertiesInputStream.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the presence of a supplied or system property named {@code spring.cloud.propertiesFile}. If the property
|
||||
* is present, load its contents into {@link #fileProperties}. If there's a problem, log but continue.
|
||||
@@ -125,29 +118,22 @@ public class LocalConfigConnector extends AbstractCloudConnector<UriBasedService
|
||||
fileProperties = new Properties();
|
||||
logger.fine("looking for a properties file");
|
||||
|
||||
String filename = null;
|
||||
// will search system properties and the classpath
|
||||
File propertiesFile = new PropertiesFileResolver(env).findCloudPropertiesFile();
|
||||
|
||||
filename = programmaticProperties.getProperty(PROPERTIES_FILE_PROPERTY);
|
||||
|
||||
try {
|
||||
filename = System.getProperty(PROPERTIES_FILE_PROPERTY, filename);
|
||||
} catch (SecurityException e) {
|
||||
logSystemReadException(PROPERTIES_FILE_PROPERTY, e);
|
||||
if (propertiesFile == null) {
|
||||
logger.info("not loading service definitions from a properties file");
|
||||
return;
|
||||
}
|
||||
|
||||
if (filename == null) {
|
||||
logger.info("did not find a system property " + PROPERTIES_FILE_PROPERTY);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("loading properties from file " + filename);
|
||||
logger.info("loading service definitions from properties file " + propertiesFile);
|
||||
|
||||
try {
|
||||
InputStream fis = openFile(filename);
|
||||
InputStream fis = openFile(propertiesFile);
|
||||
fileProperties.load(fis);
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, "exception while loading properties from file " + filename, e);
|
||||
logger.log(Level.SEVERE, "exception while loading properties from file " + propertiesFile, e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -156,27 +142,29 @@ public class LocalConfigConnector extends AbstractCloudConnector<UriBasedService
|
||||
|
||||
/**
|
||||
* Broken out into a separate method for mocking the filesystem.
|
||||
* @param filename the file to open
|
||||
*
|
||||
* @param filename
|
||||
* the file to open
|
||||
* @return a {@code FileInputStream} to the file
|
||||
* @throws IOException if opening the file throws
|
||||
* @throws IOException
|
||||
* if opening the file throws
|
||||
*/
|
||||
InputStream openFile(String filename) throws IOException {
|
||||
return new FileInputStream(filename);
|
||||
InputStream openFile(File file) throws IOException {
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a specific property in programmatically-supplied properties, properties from a file,
|
||||
* or the system properties. Last source wins.
|
||||
* Look for a specific property in the config file or the system properties.
|
||||
*
|
||||
* @param key
|
||||
* the property to look for
|
||||
* @return the highest-priority value for the key, or {@code null} if the key is not found
|
||||
* @return the preferred value for the key, or {@code null} if the key is not found
|
||||
*/
|
||||
private String findProperty(String key) {
|
||||
String value = programmaticProperties.getProperty(key);
|
||||
value = fileProperties.getProperty(key, value);
|
||||
String value = fileProperties.getProperty(key);
|
||||
|
||||
try {
|
||||
value = System.getProperty(key, value);
|
||||
value = env.getSystemProperty(key, value);
|
||||
} catch (SecurityException e) {
|
||||
logSystemReadException(key, e);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package org.springframework.cloud.localconfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.apache.commons.lang3.text.StrLookup;
|
||||
import org.apache.commons.lang3.text.StrSubstitutor;
|
||||
import org.springframework.cloud.util.EnvironmentAccessor;
|
||||
|
||||
/**
|
||||
* Helper class that handles finding and merging properties to be read by the connector.
|
||||
*
|
||||
* @author Christopher Smith
|
||||
*
|
||||
*/
|
||||
class PropertiesFileResolver {
|
||||
|
||||
public static final String BOOTSTRAP_PROPERTIES_FILENAME = "spring-cloud-bootstrap.properties";
|
||||
|
||||
private static final Logger logger = Logger.getLogger(PropertiesFileResolver.class.getName());
|
||||
|
||||
private final EnvironmentAccessor env;
|
||||
|
||||
private final String classpathPropertiesFilename;
|
||||
|
||||
PropertiesFileResolver(final EnvironmentAccessor env, final String classpathPropertiesFilename) {
|
||||
this.env = env;
|
||||
this.classpathPropertiesFilename = classpathPropertiesFilename;
|
||||
}
|
||||
|
||||
PropertiesFileResolver(final EnvironmentAccessor env) {
|
||||
this(env, BOOTSTRAP_PROPERTIES_FILENAME);
|
||||
}
|
||||
|
||||
PropertiesFileResolver() {
|
||||
this(new EnvironmentAccessor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the system properties for an entry named {@value LocalConfigConnector#PROPERTIES_FILE_PROPERTY} directing it to an
|
||||
* external properties file.
|
||||
*
|
||||
* @return a {@code File} pointing to the external properties file, or {@code null} if the system property couldn't be read
|
||||
*/
|
||||
File findCloudPropertiesFileFromSystem() {
|
||||
String filename = null;
|
||||
|
||||
try {
|
||||
filename = env.getSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY);
|
||||
} catch (SecurityException e) {
|
||||
logger.log(Level.WARNING, "SecurityManager prevented reading system property "
|
||||
+ LocalConfigConnector.PROPERTIES_FILE_PROPERTY, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (filename == null) {
|
||||
logger.fine("didn't find system property for a configuration file");
|
||||
return null;
|
||||
}
|
||||
|
||||
File file = new File(filename);
|
||||
logger.info("found system property for a configuration file: " + file);
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for a resource named {@code filename} (usually {@value #BOOTSTRAP_PROPERTIES_FILENAME}) on the classpath. If present,
|
||||
* it is loaded and
|
||||
* inspected for a property named {@value LocalConfigConnector#PROPERTIES_FILE_PROPERTY}, which is interpolated from the system
|
||||
* properties and returned.
|
||||
*
|
||||
* @return the filename derived from the classpath control file, or {@code null} if one couldn't be found
|
||||
*/
|
||||
File findCloudPropertiesFileFromClasspath() {
|
||||
// see if we have a spring-cloud.properties at all
|
||||
InputStream in = getClass().getClassLoader().getResourceAsStream(classpathPropertiesFilename);
|
||||
if (in == null) {
|
||||
logger.info("no " + classpathPropertiesFilename
|
||||
+ " found on the classpath to direct us to an external properties file");
|
||||
return null;
|
||||
}
|
||||
|
||||
// load it as a properties file
|
||||
Properties properties = new Properties();
|
||||
try {
|
||||
properties.load(in);
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, "found " + classpathPropertiesFilename
|
||||
+ " on the classpath but couldn't load it as a properties file", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
// read the spring.cloud.propertiesFile property from it
|
||||
String template = properties.getProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY);
|
||||
if (template == null) {
|
||||
logger.log(Level.SEVERE, "found properties file " + classpathPropertiesFilename
|
||||
+ " on the classpath, but it didn't contain a property named " + LocalConfigConnector.PROPERTIES_FILE_PROPERTY);
|
||||
return null;
|
||||
}
|
||||
|
||||
// if there's anything else, the client probably tried to put an app ID or other credentials there
|
||||
if (properties.entrySet().size() > 1)
|
||||
logger.warning("the properties file " + classpathPropertiesFilename + " contained properties besides "
|
||||
+ LocalConfigConnector.PROPERTIES_FILE_PROPERTY + "; ignoring");
|
||||
|
||||
logger.fine("substituting system properties into '" + template + "'");
|
||||
File configFile = new File(new StrSubstitutor(systemPropertiesLookup(env)).replace(template));
|
||||
logger.info("derived configuration file name: " + configFile);
|
||||
|
||||
return configFile;
|
||||
}
|
||||
|
||||
File findCloudPropertiesFile() {
|
||||
File file = findCloudPropertiesFileFromSystem();
|
||||
|
||||
if (file != null) {
|
||||
logger.info("using configuration file from system properties");
|
||||
return file;
|
||||
}
|
||||
|
||||
file = findCloudPropertiesFileFromClasspath();
|
||||
|
||||
if (file != null)
|
||||
logger.info("using configuration file derived from " + classpathPropertiesFilename);
|
||||
else
|
||||
logger.info("did not find any Spring Cloud configuration file");
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter from the {@link EnvironmentAccessor}'s system-property resolution to the {@code StrLookup} interface.
|
||||
*
|
||||
* @param env
|
||||
* the {@code EnvironmentAccessor} to use for the lookups
|
||||
* @return a {@code StrLookup} view of the accessor's system properties
|
||||
*/
|
||||
private StrLookup<String> systemPropertiesLookup(final EnvironmentAccessor env) {
|
||||
return new StrLookup<String>() {
|
||||
@Override
|
||||
public String lookup(String key) {
|
||||
return env.getSystemProperty(key);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,10 @@
|
||||
package org.springframework.cloud.localconfig;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.springframework.cloud.service.ServiceInfo;
|
||||
import org.springframework.cloud.service.UriBasedServiceInfo;
|
||||
@@ -17,7 +13,9 @@ public class AbstractLocalConfigConnectorTest {
|
||||
|
||||
public static final String PROPERTIES_FILE = "localconfig.testuris.properties";
|
||||
|
||||
protected LocalConfigConnector connector = new LocalConfigConnector();
|
||||
protected StubbedOpenFileLocalConfigConnector connector = new StubbedOpenFileLocalConfigConnector();
|
||||
|
||||
protected PassthroughEnvironmentAccessor env;
|
||||
|
||||
protected static final String HOSTNAME = "10.20.30.40";
|
||||
protected static final int PORT = 1234;
|
||||
@@ -26,14 +24,9 @@ public class AbstractLocalConfigConnectorTest {
|
||||
|
||||
@Before
|
||||
public void init() throws IOException {
|
||||
InputStream propertiesFile = getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE);
|
||||
LocalConfigConnector.supplyProperties(propertiesFile);
|
||||
assertTrue(connector.isInMatchingCloud());
|
||||
}
|
||||
|
||||
@After
|
||||
public void clearProperties() {
|
||||
LocalConfigConnector.programmaticProperties = new Properties();
|
||||
env = new PassthroughEnvironmentAccessor();
|
||||
env.setSystemProperty("spring.cloud.baz", "inline!");
|
||||
connector.setEnvironmentAccessor(env);
|
||||
}
|
||||
|
||||
protected static ServiceInfo getServiceInfo(List<ServiceInfo> serviceInfos, String serviceId) {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.springframework.cloud.localconfig;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.junit.Before;
|
||||
|
||||
public class AbstractLocalConfigConnectorWithUrisTest extends AbstractLocalConfigConnectorTest {
|
||||
|
||||
public static String PROPERTY_FILE_WITH_URIS = "localconfig.testuris.properties";
|
||||
|
||||
@Before
|
||||
public void useTestUris() {
|
||||
InputStream testUrisProperties = getClass().getClassLoader().getResourceAsStream(PROPERTY_FILE_WITH_URIS);
|
||||
env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTY_FILE_WITH_URIS);
|
||||
connector.setFileProvider(StubbedOpenFileLocalConfigConnector.fileContentsFromStream(PROPERTY_FILE_WITH_URIS, testUrisProperties));
|
||||
assertTrue(connector.isInMatchingCloud());
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import org.junit.Test;
|
||||
import org.springframework.cloud.service.ServiceInfo;
|
||||
import org.springframework.cloud.service.common.AmqpServiceInfo;
|
||||
|
||||
public class LocalConfigConnectorAmqpServiceTest extends AbstractLocalConfigConnectorTest {
|
||||
public class LocalConfigConnectorAmqpServiceTest extends AbstractLocalConfigConnectorWithUrisTest {
|
||||
|
||||
@Test
|
||||
public void serviceCreation() {
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.junit.Test;
|
||||
import org.springframework.cloud.service.ServiceInfo;
|
||||
import org.springframework.cloud.service.common.MongoServiceInfo;
|
||||
|
||||
public class LocalConfigConnectorMongoServiceTest extends AbstractLocalConfigConnectorTest {
|
||||
public class LocalConfigConnectorMongoServiceTest extends AbstractLocalConfigConnectorWithUrisTest {
|
||||
|
||||
@Test
|
||||
public void serviceCreation() {
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.junit.Test;
|
||||
import org.springframework.cloud.service.ServiceInfo;
|
||||
import org.springframework.cloud.service.common.MysqlServiceInfo;
|
||||
|
||||
public class LocalConfigConnectorMysqlServiceTest extends AbstractLocalConfigConnectorTest {
|
||||
public class LocalConfigConnectorMysqlServiceTest extends AbstractLocalConfigConnectorWithUrisTest {
|
||||
|
||||
@Test
|
||||
public void serviceCreation() {
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.junit.Test;
|
||||
import org.springframework.cloud.service.ServiceInfo;
|
||||
import org.springframework.cloud.service.common.PostgresqlServiceInfo;
|
||||
|
||||
public class LocalConfigConnectorPostgresqlServiceTest extends AbstractLocalConfigConnectorTest {
|
||||
public class LocalConfigConnectorPostgresqlServiceTest extends AbstractLocalConfigConnectorWithUrisTest {
|
||||
|
||||
@Test
|
||||
public void serviceCreation() {
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.junit.Test;
|
||||
import org.springframework.cloud.service.ServiceInfo;
|
||||
import org.springframework.cloud.service.common.RedisServiceInfo;
|
||||
|
||||
public class LocalConfigConnectorRedisServiceTest extends AbstractLocalConfigConnectorTest {
|
||||
public class LocalConfigConnectorRedisServiceTest extends AbstractLocalConfigConnectorWithUrisTest {
|
||||
|
||||
@Test
|
||||
public void serviceCreation() {
|
||||
|
||||
@@ -4,142 +4,41 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.contrib.java.lang.system.ClearSystemProperties;
|
||||
import org.junit.contrib.java.lang.system.ProvideSystemProperty;
|
||||
import org.springframework.cloud.service.UriBasedServiceData;
|
||||
|
||||
public class LocalConfigConnectorTest {
|
||||
public class LocalConfigConnectorTest extends AbstractLocalConfigConnectorTest {
|
||||
|
||||
static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
public static final String APP_ID_1 = "appId1";
|
||||
public static final String APP_ID_1_PROPERTY = LocalConfigConnector.APP_ID_PROPERTY + ": " + APP_ID_1;
|
||||
|
||||
public static final String APP_ID_2 = "appId2";
|
||||
public static final String APP_ID_2_PROPERTY = LocalConfigConnector.APP_ID_PROPERTY + ": " + APP_ID_2;
|
||||
public static final String APP_ID = "appId2";
|
||||
public static final String APP_ID_PROPERTY = LocalConfigConnector.APP_ID_PROPERTY + ": " + APP_ID;
|
||||
|
||||
public static final String PROPERTY_FILE_NAME = "localconfig.nonsense.properties";
|
||||
public static final String PROPERTY_FILE_PROPERTY = LocalConfigConnector.PROPERTIES_FILE_PROPERTY + ": " + PROPERTY_FILE_NAME;
|
||||
|
||||
public static class DetectAppIdTest {
|
||||
|
||||
private LocalConfigConnector connector;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
connector = new LocalConfigConnector();
|
||||
}
|
||||
|
||||
@After
|
||||
public void clearProperties() {
|
||||
LocalConfigConnector.programmaticProperties = new Properties();
|
||||
}
|
||||
|
||||
@Rule
|
||||
public final ClearSystemProperties NO_APP_ID_PROPERTY = new ClearSystemProperties(LocalConfigConnector.APP_ID_PROPERTY);
|
||||
|
||||
@Test
|
||||
public void testNoAppIdAnywhere() {
|
||||
assertFalse(connector.isInMatchingCloud());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProgrammaticAppId() throws IOException {
|
||||
LocalConfigConnector.supplyProperties(new ByteArrayInputStream(APP_ID_1_PROPERTY.getBytes(UTF_8)));
|
||||
assertTrue(connector.isInMatchingCloud());
|
||||
assertEquals(APP_ID_1, connector.getApplicationInstanceInfo().getAppId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProgrammaticAndFileAppIds() throws IOException {
|
||||
LocalConfigConnector.supplyProperties(new ByteArrayInputStream(APP_ID_1_PROPERTY.getBytes(UTF_8)));
|
||||
LocalConfigConnector.supplyProperties(new ByteArrayInputStream(PROPERTY_FILE_PROPERTY.getBytes(UTF_8)));
|
||||
|
||||
LocalConfigConnector stubConnector = new LocalConfigConnector() {
|
||||
@Override
|
||||
InputStream openFile(String filename) throws IOException {
|
||||
assertEquals(PROPERTY_FILE_NAME, filename);
|
||||
return new ByteArrayInputStream(APP_ID_2_PROPERTY.getBytes(UTF_8));
|
||||
};
|
||||
};
|
||||
|
||||
assertTrue(stubConnector.isInMatchingCloud());
|
||||
assertEquals(APP_ID_2, stubConnector.getApplicationInstanceInfo().getAppId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProgrammaticFilenamePlusSystemAppId() throws IOException {
|
||||
LocalConfigConnector.supplyProperties(new ByteArrayInputStream(PROPERTY_FILE_PROPERTY.getBytes(UTF_8)));
|
||||
|
||||
LocalConfigConnector stubConnector = new LocalConfigConnector() {
|
||||
@Override
|
||||
InputStream openFile(String filename) throws IOException {
|
||||
assertEquals(PROPERTY_FILE_NAME, filename);
|
||||
return new ByteArrayInputStream(APP_ID_2_PROPERTY.getBytes(UTF_8));
|
||||
};
|
||||
};
|
||||
|
||||
System.setProperty(LocalConfigConnector.APP_ID_PROPERTY, "helloApp");
|
||||
assertTrue(stubConnector.isInMatchingCloud());
|
||||
assertEquals("helloApp", stubConnector.getApplicationInstanceInfo().getAppId());
|
||||
}
|
||||
}
|
||||
|
||||
private LocalConfigConnector connector;
|
||||
|
||||
InputStream propertiesFile;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
connector = new LocalConfigConnector();
|
||||
propertiesFile = LocalConfigConnectorTest.class.getClassLoader().getResourceAsStream(PROPERTY_FILE_NAME);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanup() throws IOException {
|
||||
LocalConfigConnector.programmaticProperties = new Properties();
|
||||
propertiesFile.close();
|
||||
@Test
|
||||
public void testNoAppIdAnywhere() {
|
||||
assertFalse(connector.isInMatchingCloud());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadFromFile() throws IOException {
|
||||
LocalConfigConnector.supplyProperties(propertiesFile);
|
||||
public void testAppIdInConfigFile() throws IOException {
|
||||
env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTY_FILE_NAME);
|
||||
|
||||
connector.setFileProvider(StubbedOpenFileLocalConfigConnector.fileContentsFromString(PROPERTY_FILE_NAME, APP_ID_PROPERTY));
|
||||
|
||||
assertTrue(connector.isInMatchingCloud());
|
||||
assertEquals("testApp", connector.getApplicationInstanceInfo().getAppId());
|
||||
|
||||
List<UriBasedServiceData> services = connector.getServicesData();
|
||||
assertEquals(2, services.size());
|
||||
for (UriBasedServiceData service : services)
|
||||
if ("foo".equals(service.getKey()))
|
||||
assertEquals("bar", service.getUri());
|
||||
assertEquals(APP_ID, connector.getApplicationInstanceInfo().getAppId());
|
||||
}
|
||||
|
||||
@Rule
|
||||
public ProvideSystemProperty BAZ_PROPERTY = new ProvideSystemProperty("spring.cloud.baz", "inline!");
|
||||
|
||||
@Test
|
||||
public void testLoadFromInputStreamWithOverride() throws IOException {
|
||||
LocalConfigConnector.supplyProperties(propertiesFile);
|
||||
public void testAppIdInFileAndSystem() throws IOException {
|
||||
env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTY_FILE_NAME);
|
||||
env.setSystemProperty(LocalConfigConnector.APP_ID_PROPERTY, APP_ID);
|
||||
|
||||
connector.setFileProvider(StubbedOpenFileLocalConfigConnector.fileContentsFromString(PROPERTY_FILE_NAME, APP_ID_PROPERTY));
|
||||
|
||||
assertTrue(connector.isInMatchingCloud());
|
||||
assertEquals("testApp", connector.getApplicationInstanceInfo().getAppId());
|
||||
|
||||
List<UriBasedServiceData> services = connector.getServicesData();
|
||||
assertEquals(2, services.size());
|
||||
for(UriBasedServiceData service: services)
|
||||
if("baz".equals(service.getKey()))
|
||||
assertEquals("inline!", service.getUri());
|
||||
assertEquals(APP_ID, connector.getApplicationInstanceInfo().getAppId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,22 +6,16 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.contrib.java.lang.system.ProvideSystemProperty;
|
||||
import org.springframework.cloud.service.ServiceInfo;
|
||||
import org.springframework.cloud.service.common.MongoServiceInfo;
|
||||
|
||||
public class LocalConfigServiceOverrideTest extends AbstractLocalConfigConnectorTest {
|
||||
|
||||
@Rule
|
||||
public final ProvideSystemProperty OVERRIDE_MYSQL =
|
||||
new ProvideSystemProperty(
|
||||
"spring.cloud.candygram",
|
||||
"mongodb://youruser:yourpass@40.30.20.10:4321/dbname");
|
||||
public class LocalConfigServiceOverrideTest extends AbstractLocalConfigConnectorWithUrisTest {
|
||||
|
||||
@Test
|
||||
public void serviceOverride() {
|
||||
env.setSystemProperty("spring.cloud.candygram", "mongodb://youruser:yourpass@40.30.20.10:4321/dbname");
|
||||
|
||||
List<ServiceInfo> services = connector.getServiceInfos();
|
||||
ServiceInfo service = getServiceInfo(services, "candygram");
|
||||
assertNotNull(service);
|
||||
@@ -30,5 +24,4 @@ public class LocalConfigServiceOverrideTest extends AbstractLocalConfigConnector
|
||||
assertEquals("youruser", mongo.getUserName());
|
||||
assertEquals(4321, mongo.getPort());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.springframework.cloud.localconfig;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.cloud.util.EnvironmentAccessor;
|
||||
|
||||
class PassthroughEnvironmentAccessor extends EnvironmentAccessor {
|
||||
private Properties systemProperties = new Properties(System.getProperties());
|
||||
|
||||
void clear() {
|
||||
systemProperties.clear();
|
||||
}
|
||||
|
||||
void setSystemProperty(String key, String value) {
|
||||
systemProperties.setProperty(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSystemProperty(String key, String defaultValue) {
|
||||
return systemProperties.getProperty(key, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Properties getSystemProperties() {
|
||||
return systemProperties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.springframework.cloud.localconfig;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class PropertiesFileResolverTest {
|
||||
|
||||
private PassthroughEnvironmentAccessor env;
|
||||
|
||||
private PropertiesFileResolver resolver;
|
||||
|
||||
private String PROPERTIES_FILE_NAME = "/foo/bar.properties";
|
||||
|
||||
@Before
|
||||
public void setDefaults() {
|
||||
env = new PassthroughEnvironmentAccessor();
|
||||
resolver = new PropertiesFileResolver(env);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecurityExceptionHandling() {
|
||||
env = mock(PassthroughEnvironmentAccessor.class);
|
||||
resolver = new PropertiesFileResolver(env);
|
||||
|
||||
when(env.getSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY)).thenThrow(new SecurityException());
|
||||
assertNull(resolver.findCloudPropertiesFileFromSystem());
|
||||
verify(env).getSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingSystemProperty() {
|
||||
assertNull(resolver.findCloudPropertiesFileFromSystem());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSystemProperty() {
|
||||
env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTIES_FILE_NAME);
|
||||
assertEquals(PROPERTIES_FILE_NAME, resolver.findCloudPropertiesFileFromSystem().getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoClasspathFile() {
|
||||
resolver = new PropertiesFileResolver(env, "bazquux.properties");
|
||||
assertNull(resolver.findCloudPropertiesFileFromClasspath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClasspathFileWithoutKey() {
|
||||
resolver = new PropertiesFileResolver(env, "localconfig.testuris.properties");
|
||||
assertNull(resolver.findCloudPropertiesFileFromClasspath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteral() {
|
||||
resolver = new PropertiesFileResolver(env, "spring-cloud-literal.properties");
|
||||
assertEquals(PROPERTIES_FILE_NAME,
|
||||
resolver.findCloudPropertiesFileFromClasspath().getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTemplate() {
|
||||
resolver = new PropertiesFileResolver(env, "spring-cloud-template.properties");
|
||||
env.setSystemProperty("user.home", "/foo");
|
||||
assertEquals(PROPERTIES_FILE_NAME,
|
||||
resolver.findCloudPropertiesFileFromClasspath().getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromSystem() {
|
||||
env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTIES_FILE_NAME);
|
||||
assertEquals(PROPERTIES_FILE_NAME, resolver.findCloudPropertiesFile().getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromClasspath() {
|
||||
resolver = new PropertiesFileResolver(env, "spring-cloud-template.properties");
|
||||
env.setSystemProperty("user.home", "/foo");
|
||||
assertEquals(PROPERTIES_FILE_NAME,
|
||||
resolver.findCloudPropertiesFile().getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNowhere() {
|
||||
assertNull(resolver.findCloudPropertiesFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrecedence() {
|
||||
env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTIES_FILE_NAME);
|
||||
resolver = new PropertiesFileResolver(env, "spring-cloud-literal.properties");
|
||||
assertEquals(PROPERTIES_FILE_NAME, resolver.findCloudPropertiesFile().getPath());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.springframework.cloud.localconfig;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Provides an easy way to stub the {@code openFile} method on the local connector.
|
||||
*
|
||||
* @author Christopher Smith
|
||||
*
|
||||
*/
|
||||
class StubbedOpenFileLocalConfigConnector extends LocalConfigConnector {
|
||||
|
||||
static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
private InputStreamProvider fileProvider;
|
||||
|
||||
@Override
|
||||
InputStream openFile(File file) throws IOException {
|
||||
return fileProvider.openFile(file);
|
||||
}
|
||||
|
||||
public void setFileProvider(InputStreamProvider provider) {
|
||||
this.fileProvider = provider;
|
||||
}
|
||||
|
||||
interface InputStreamProvider {
|
||||
InputStream openFile(File file) throws IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the supplied input stream. Used for reading out of the classpath for testing.
|
||||
*
|
||||
* @param filename
|
||||
* the filename we expect the connector to open
|
||||
* @param contents
|
||||
* the contents to return
|
||||
*/
|
||||
static InputStreamProvider fileContentsFromStream(final String expectedFilename, final InputStream stream) {
|
||||
return new InputStreamProvider() {
|
||||
@Override
|
||||
public InputStream openFile(File file) throws IOException {
|
||||
assertEquals(expectedFilename, file.getPath());
|
||||
return stream;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream view of the provided string.
|
||||
*
|
||||
* @param filename
|
||||
* the filename we expect the connector to open
|
||||
* @param contents
|
||||
* the contents to return
|
||||
*/
|
||||
static InputStreamProvider fileContentsFromString(final String expectedFilename, final String contents) {
|
||||
return fileContentsFromStream(expectedFilename, new ByteArrayInputStream(contents.getBytes(UTF_8)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
spring.cloud.propertiesFile=/foo/bar.properties
|
||||
@@ -0,0 +1 @@
|
||||
spring.cloud.propertiesFile=${user.home}/bar.properties
|
||||
Reference in New Issue
Block a user