Abstracting out Environment Variables for Bluemix
Abstracting Variables
When developing an application that will be deployed to Bluemix, it’s nice to run locally and connect to the same services and backends you’ll be using in production (instead of faking data or responses). The goal is to abstract out the differences between accessing them on Bluemix and accessing them locally. Here is an easy way to do that without using a proxy and without updating your local environment variables for every different project you’re working on.
Note: this post will cover the Node.js and Liberty runtimes – for a more in depth view on just Node.js, refer to this blog post.
If you navigate over to your environment variables for a given application in Bluemix, you’ll see something like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
{ "user-provided": [ { "name": "AlchemyAPI-2e", "label": "user-provided", "credentials": { "apikey": "YOUR_KEY" } } ], "mongolab": [ { "name": "MongoLab-bh", "label": "mongolab", "plan": "sandbox", "credentials": { "uri": "mongodb://username:password@host.mongolab.com:port/db" } } ] } |
In your local environment, make a VCAP_SERVICES.json
file whose content and structure is identical to the environment variables listed above.
Node.js
For the Node.js runtime, make a module vcapServices.js
whose content is:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var vcapServices; // if running in Bluemix, use the environment variables if (process.env.VCAP_SERVICES) { vcapServices = JSON.parse(process.env.VCAP_SERVICES); // otherwise use our JSON file } else { try { vcapServices = require('./VCAP_SERVICES.json'); } catch (e) { console.error(e); } } module.exports = vcapServices; |
This creates a singleton module that is a JSON representation of your environment variables. For example, to acess mongolab credentials, you would:
1 2 3 |
var vcapServices = require('./vcapServices'); var mongoCredentials = vcapServices.mongolab[0].credentials; var credentialsURI = mongoCredentials.uri; |
Liberty
For the Liberty runtime, you can make a class with a singleton object that has getters for your environment variables. We make it a singleton to avoid parsing the JSON every time. The call to get this object calls initialize()
if the object has not yet been initialized. This method gets the JSON object from either the “VCAP_SERVICES” environment variable (if running on Bluemix) or from the VCAP_SERVICES.json file we created earlier, parses it, and sets values that can be retrieved from getter methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
public class VcapServices { public VcapServices() {} /** private strings that hold the environment variables we're interested in */ private String MONGO_URI; private String ALCHEMY_KEY; /** Our singleton object */ private static VcapServices vcapServices; /** Method to get singleton object - initialize it if it has not yet been created */ static public VcapServices getVcapServices() { if (vcapServices == null) { vcapServices = new VcapServices(); vcapServices.initialize(); } return vcapServices; } /** Initialize our singleton object, and initialize the environment variables */ public void initialize() { Object tempVcap; String VCAP_SERVICES = System.getenv("VCAP_SERVICES"); if (VCAP_SERVICES != null) { tempVcap = JSON.parse(VCAP_SERVICES); } else { JSONParser parser = new JSONParser(); try { tempVcap = parser.parse(new FileReader("path/to/VCAP_SERVICES.json")); } catch (Exception e) { e.printStackTrace(); } } JSONObject json = (JSONObject) tempVcap; // put code here for parsing the json object and setting the private // strings declared above (in this case - MONGO_URI and ALCHEMY_KEY) // I'm omitting that code in this post as it's quite verbose and not // what this post is about. } public String getMongoURI() { return MONGO_URI; } public String getAlchemyApiKey() { return ALCHEMY_KEY; } } |
To access your environment variables in other classes, all you need to do is:
1 2 |
String alchemyKey = VcapServices.getVcapServices().getAlchemyApiKey(); String mongoURI = VcapServices.getVcapServices().getMongoURI(); |
Just make sure to add path/to/VCAP_SERVICES.json
to both your .gitignore
and .cfignore
!
Jon, a number of the Node.js boilerplates and samples on Bluemix are using the following Node.js package for abstracting VCAP_SERVICES: https://www.npmjs.com/package/cfenv
Would love to hear how your approach relates to that.
The cfenv package requires you to manually enter the VCAP_SERVICES when running locally. Once you do this, using cfenv to access the services will prevent the need for the code I posted for the Node.js runtime. A way to combine my approach with using the cfenv package would be:
var cfenv = require('cfenv');
var appEnv;
// if we're in CF or Bluemix, don't need to specify services manually
if (process.env.VCAP_SERVICES) {
appEnv = cfenv.getAppEnv();
// otherwise, specify the service values when creating the appEnv
} else {
try {
appEnv = cfenv.getAppEnv({
vcap: {
services: require('./VCAP_SERVICES.json')
}
});
} catch (e) {
console.error(e);
}
}
This way you don’t have to hard code in the values to your code and can still keep a config file, and you can use the helper methods cfenv provides for accessing/parsing the vcap services.