Java CFEnv is a library for easily accessing the environment variables set when deploying an application to Cloud Foundry.
It is modeled after the design of the node library node-cfenv and other -cfenv
libraries in the Cloud Foundry ecosystem.
The class CfEnv
is the entry point to the API for accessing Cloud Foundry environment variables.
In a Spring application, you can use the Spring Expression Language to invoke methods on bean of type CfEnv
to set properties.
CFEnv’s Boot support sets common application properties so that Java objects such as the DataSource
or the RabbitConnectionFactory
are created using Spring Boot autoconfiguration.
The 1.0 M1 blog provides some additional background information.
You can access the stable release from maven central using the coordinates
<dependency>
<groupId>io.pivotal.cfenv</groupId>
<artifactId>java-cfenv-boot</artifactId>
<version>1.0.1.RELEASE</version>
</dependency>
If you want to use snapshot versions, use the following coordinates
<dependency>
<groupId>io.pivotal.cfenv</groupId>
<artifactId>java-cfenv-boot</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
</dependency>
Add the dependency and repository definition if you want to use the milestone or release candidates
<dependency>
<groupId>io.pivotal.cfenv</groupId>
<artifactId>java-cfenv-boot</artifactId>
<version>1.1.0.RC1</version>
</dependency>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>http://repo.spring.io/libs-milestone-local</url>
</repository>
The entry point is the class CfEnv
which parses Cloud Foundry environment variables such as VCAP_SERVICES
.
The information in VCAP_SERVICES
is a JSON string that contains credential information to access bound services, such as a database.
For example, here is the value of VCAP_SERVICES
for an application bound to the mysql and redis services.
{
"p-mysql": [
{
"credentials": {
"hostname": "10.0.4.35",
"port": 3306,
"name": "cf_2e23d10a_8738_8c3c_66cf_13e44422698c",
"username": "8McHri7aKbuTEGCR",
"password": "J2BNJYkeXAH9idkG",
"uri": "mysql://8McHri7aKbuTEGCR:J2BNJYkeXAH9idkG@10.0.4.35:3306/cf_2e23d10a_8738_8c3c_66cf_13e44422698c?reconnect=true",
"jdbcUrl": "jdbc:mysql://10.0.4.35:3306/cf_2e23d10a_8738_8c3c_66cf_13e44422698c?user=8McHri7aKbuTEGCR&password=J2BNJYkeXAH9idkG"
},
"syslog_drain_url": null,
"volume_mounts": [],
"label": "p-mysql",
"provider": null,
"plan": "100mb",
"name": "mysql",
"tags": [
"mysql",
"relational"
]
}
],
"p-redis": [
{
"credentials": {
"host": "10.0.4.30",
"password": "291452d8-d4a1-3bec-90d9-g50503138248",
"port": 45470
},
"syslog_drain_url": null,
"volume_mounts": [],
"label": "p-redis",
"provider": null,
"plan": "shared-vm",
"name": "redis",
"tags": [
"pivotal",
"redis"
]
}
],
"nfs": [
{
"binding_name": null,
"credentials": {},
"instance_name": "nfs1",
"label": "nfs",
"name": "nfs1",
"plan": "Existing",
"provider": null,
"syslog_drain_url": null,
"tags": [
"nfs"
],
"volume_mounts": [
{
"container_dir": "/var/vcap/data/78525ee7-196c-4ed4-8ac6-857d15334631",
"device_type": "shared",
"mode": "rw"
}
]
}
]
}
The keys in the JSON are not always identical across services, for example in the above JSON the mysql service has added the key named hostname
to the credentials while the redis service has contributed the key named host
.
Most relational database services do not expose a key named jdbcUrl
so the URL needs to be created by extracting individual fields and building up the URL string. Since this is such a common case, the CfEnv library provides support to retrieve the URL string for several databases.
To get access to a specific service’s credentials, the keys tag
, name
, or label
and be specified as a criteria for selecting one of the services in the array.
Using the JSON from the previous section, here are some simple API calls to extract credential information.
CfEnv cfEnv = new CfEnv();
String redisHost = cfEnv.findCredentialsByTag("redis").getHost();
String redisPort = cfEnv.findCredentialsByTag("redis").getPort();
String redisPassword = cfEnv.findCredentailsByTag("redis").getPassword();
Multiple strings can be passed to match against more than one tag.
There are additional finder methods to search by name
and label
and the finder method support passing a regex string for pattern matching.
The classes CfService
and CfCredentials
are returned from the following API calls and have methods for accessing common fields in addition to a generic get(String)
map API.
CfEnv cfEnv = new CfEnv();
List<CfService> cfService = cfEnv.findAllServices();
CfService redisService = cfEnv.findServiceByTag("redis");
List<String> redisServiceTags = redisService.getTags();
String redisPlan = redisService.getPlan();
redisPlan = redisService.get("plan")
CfCredentials redisCredentials = cfEnv.findCredentialsByTag("redis");
String redisPort = redisCredentials.getPort();
Integer redisPort = redisCredentials.getMap().get("port");
cfService = cfEnv.findServiceByName("redis");
cfService = cfEnv.findServiceByLabel("p-redis");
cfService = cfEnv.findServiceByLabel(".*-redis");
The class CfVolume
contains information for a shared disk provided by Cloud Foundry volume services.
You can access it using the getVolumes
method on CfService
as shown below.
CfEnv cfEnv = new CfEnv();
List<CfVolume> cfVolumes = cfEnv.findServiceByName("nfs1").getVolumes();
String path = cfVolumes.get(0).getPath();
There is additional support for getting the JDBC URL contained in the module spring-cfenv-jdbc
.
The entry point to the API is the class CfJdbcEnv
which is a subclass of CfEnv
and adds a few methods.
The method findJdbcService
will heuristically look at all services for known tags, labels and names of common database services to create the URL.
CfEnvJdbc cfEnvJdbc = new CfEnvJdbc()
CfJdbcService cfJdbcService = cfEnvJdbc.findJdbcService();
String url = cfJdbcService.getUrl();
String username = cfJdbcService.getUsername();
String password = cfJdbcService.getPassword();
String driverClassName = cfJdbcService.getDriverClassName();
If there is more than one database bound to the application, an exception will be thrown and you should use the findJdbcServiceByName
method to locate a unique database service.
String jdbcUrl1 = cfEnvJdbc.findJdbcServiceByName("mysqlA").getUrl();
String jdbcUrl2 = cfEnvJdbc.findJdbcServiceByName("mysqlB").getUrl();
If you register a the CfEnv
class as a bean, then you can use the Spring Expression Language to set properties.
@Bean
public CfJdbcEnv cfJdbcEnv() {
return new CfJdbcEnv();
}
Then in a property file imported by Spring, refer to the CfEnvJdbc bean using the following syntax.
myDatasourceUrl=#{ cfJdbcEnv.findJdbcService().getUrl() }
Or say for cassandra, you can use the CfEnv
class registered as a bean.
@Bean
public CfEnv cfEnv() {
return new CfEnv();
}
cassandra.contact-points=#{ cfEnv.findCredentialsByTag('cassandra').get('node_ips') }
cassandra.username=#{ cfEnv.findCredentialsByTag('cassandra').getUserName() }
cassandra.password=#{ cfEnv.findCredentialsByTag('cassandra').getPassword() }
cassandra.port=#{ cfEnv.findCredentialsByTag('cassandra').get('cqlsh_port') }
Similar for setting a custom application property to access the disk mounted by Volume Services.
myapp.config.path=#{ cfEnv.findServiceByName("nfs1").getVolumes().get(0).getPath() }
The module java-cfenv-boot
provides several EnvironmentPostProcessor
implementations that set well known Boot properties so that Boot’s auto-configuration can kick in.
For example, the CfDataSourceEnvironmentPostProcessor
sets the Boot property spring.datasource.url
.
Just add a dependency on java-cfenv-boot
.
The list of supported services are:
-
Databases - DB2, MySQL, Oracle, Postgresl, SqlServer
-
RabbitMQ
-
Cassandara
-
MongoDB
-
Redis
There are two other modules that contain support for Pivotal’s Spring Cloud Service and Single Sign-On Service tiles.
The java-cfenv-boot-pivotal-scs
module provides support for Config Server and java-cfenv-boot-pivotal-sso
module provides support for single sign on. You can read more about the integration with the Pivotal Single Sign-On Service in the SSO library readme for that module.
Note that the scs
module is not working correctly in the 1.0.0.RELEASE, use snapshot builds until the next point release is made.
You must disable the java buildpack’s auto-reconfiguration so that you always delegate to Boot to create beans.
cf set-env <APP> JBP_CONFIG_SPRING_AUTO_RECONFIGURATION '{enabled: false}'
Since the auto-reconfiguration also set the cloud profile, you will have to do that explicitly
cf set-env <APP> SPRING_PROFILES_ACTIVE cloud
The interface CfEnvProcesor
simplifies what you need to write in most cases.
The environment post processor, CfEnvPostProcessor
delegates to all CfEnvProcessors that are discovered using Spring’s SpringFactoriesLoader
.
Here is the implementation for MongoDB
public class MongoCfEnvProcessor implements CfEnvProcessor {
private static String mongoScheme = "mongodb";
@Override
public boolean accept(CfService service) { (1)
return service.existsByTagIgnoreCase("mongodb") ||
service.existsByLabelStartsWith("mongolab") ||
service.existsByUriSchemeStartsWith(mongoScheme) ||
service.existsByCredentialsContainsUriField(mongoScheme);
}
@Override
public void process(CfCredentials cfCredentials, Map<String, Object> properties) { (2)
properties.put("spring.data.mongodb.uri", cfCredentials.getUri(mongoScheme));
}
@Override
public CfEnvProcessorProperties getProperties() { (3)
return CfEnvProcessorProperties.builder()
.propertyPrefixes("spring.data.mongodb")
.serviceName("MongoDB")
.build();
}
}
-
In the
accept
method is where you put determine how to identify your service type. TheCfService
class has several methods to help make this as easy as possible. -
Copy over necessary values from the
CfCredentails
object to Spring Boot auto-configuration keys. -
Create a
CfEnvProcessorProperties
instance so that logging which is done inCfEnvPostProcessor
reflects your specific service.
Remember to add an entry in spring.factories
so that your processor can be discovered.
io.pivotal.cfenv.spring.boot.CfEnvProcessor=com.example.MyCoolServiceCfEnvProcessor