As stated previously, one of our goals is to move our database credentials out of our source code. In Chapter 1, I discussed how storing these credentials in a source file could pose a security risk if a member of the team left or had his/her computer stolen. There’s also another reason to consider, which is swapping out databases in our stack. You have seen by now how easy it is to create an RDS instance from scratch. In the future, you will be able to create new database instances from existing ones, and this will give you the ability to run a backup database in your stack in the event of a critical failure. It will also make it significantly easier to clone your entire stack and maintain separate stacks for production and development, a practice that is strongly recommended. By following these next steps, you will be able to maintain code that works under all of these scenarios, without having to deploy code changes or manage credentials.
Thus far, I’ve largely talked about two environments: local and production. The reality is that your workflow is likely to include writing and testing code in your local environment, then deploying and testing on a development stack, and, lastly, deploying to production. This involves three sets of credentials to manage! If we were to keep different credentials in different branches of our repository, it would be a pain to manage and highly prone to developer error. Instead, we’re going to use Environment Variables to tell our application where it’s running.
If you’re well-versed in Node.js already, you know that there’s a global object called process.env
that stores some information about the user environment in Node.js. You can find information about this here: http://nodejs.org/api/process.html#process_process_env. None of the properties in the documentation may seem that useful. Fortunately, we can add to them!
While editing the app, scroll down to the Environment Variables header, as shown in Figure 3-17, and you will see that you can add your own key-value properties here.
Figure 3-17. App Environment Variables
Add a variable called ENVIRONMENT, and give it a value of production. If/when you create a dev stack, you’ll change that value. Go ahead and click Save, at the bottom right. We’ve now set our app’s data source and environment variable ENVIRONMENT.
What is the connection between this Environment Variable and process.env? This is one of the subtle ways that the underlying technology of Chef works behind the scenes to make things easier and is only available as of Chef 11.10. When your app is deployed to the instances in the App Server layer, the Environment Variables you have set (up to 20) are included in the deployment script for your instances. Because our app is Node.js, the Environment Variables are added to process.env during the deployment process. If we were instead running a PHP app, the Environment Variables would be accessible via
getenv($variableName). Regardless, this interface in the App settings gives us an easy way to set environmental variables dynamically across all instances without getting into server configuration and without disrupting the elasticity of our application.
Next, after much delay, we have to return to our source code. Currently, the source is hard-coded to the local database. As described previously, we will have to support three different environments. First, open your code editor to /lib/globals.js. Find the database property, which has the credentials for localhost. Instead of returning a static object, we want to change database to a function (and hey, because it’s JavaScript, it’s not that hard!). Because there’s nothing else in this file yet, you will want to replace the entire file with Listing 3-1, but keep your local credentials handy, as we will not be discarding them entirely.
Listing 3-1. /lib/globals.js module.exports = {
applicationPort : 80, database : function(){
if(process.env.ENVIRONMENT){
var opsworks = require('./../opsworks'); var opsWorksDB = opsworks.db;
77
database : opsWorksDB.database, user : opsWorksDB.username, password : opsWorksDB.password }; return rdsConnection; } else {var local = require('./../config/local'); var localConnection = local.db;
return localConnection; }
} }
OK, there’s a lot happening here, so let’s break it down. First, instead of returning the static object, we’re checking to see if process.env.ENVIRONMENT exists. If it does, we don’t care what it is, and we will try to load database credentials from OpsWorks, hence the line var opsworks = require('./../opsworks');.
You’ll notice that we have no such file in our project. The Configure stack command, which runs during deployment, dynamically compiles an opsworks.js file in the root directory of our project. When we connected the RDS layer to the Photoalbums app, it automatically started storing credentials for the database in that file. This means that even if we change the database instance that’s used by the layer, we can update the credentials by running the Configure command on our stack, which we will return to in a moment. In the opsworks.js file, the database credentials are stored in a public variable named db. In fact, the whole file looks something like Listing 3-2.
Listing 3-2. Sample opsworks.js file
exports.db = {"adapter":"mysql", "database":"ops_db", "password":"AsdFGh3k", "port":3306, "reconnect":true, "username":"opsuser", "data_source_provider":"rds", "host":"opsinstance.ccdvt3hwog1a.us-east-1.rds.amazonaws.com" } exports.memcached = {"port":11211, "host":null}
In the ideal world, the syntax of the db variable would match the syntax we need in the mysql Node.js module. Sadly, the two are slightly different. For this reason, we instantiate a variable named rdsConnection
and re-map the values to it. Then, the rdsConnection variable is returned.
In the event that process.env.ENVIRONMENT is not set, we want to preserve our local database
credentials. In your project, create a directory named config and an empty file named local.js. Using your local database credentials, paste the following into it:
module.exports = { db : { host : 'localhost', port : 8889, database : 'photoalbums', user : 'root', password : 'root'j } }
I hope that made sense. We moved our local credentials to a separate file, which we will read from in
globals.js only if process.env.ENVIRONMENT is not found. Now, because we changed globals.database
into globals.database(), we will have to fix that in a few places. In server.js, navigate to the bottom of the file and make the change in this line:
var connection = mysql.createConnection(globals.database());
Save the change, and then we will have to change the declaration at the top of all three model files:
model-users, model-photos, and model-albums. In all three files, change line 3 to this:
var connection = mysql.createConnection(globals.database());
To emphasize the point, all we’re doing is adding a “()” after database.
Now we’ll put one last finishing touch on our credentials management. We will make our code
repository ignore our local credentials, so they don’t get pushed to the repo, shared with other developers, or deployed to the App Server. If you’re using Git, open the file .gitignore and add the following line:
/config/local.js
Save this change. If you’re using SVN, you want to ignore the file just the same. Commit all these changes to your repository and return to OpsWorks. After you saved your last changes there, you should have been returned to the detail view for the app. You should see at the top right a blue button that reads Deploy App. Click the button, to proceed to the Deploy App view. The only thing you need to change here is to add a comment that’s of some use to you (see Figure 3-18).
79
Click Deploy at the bottom right and wait for the deployment to complete. If after a few minutes it fails, click Show in the Log column and see if you can spot the error. If you get a green check mark next to your hostname, then you should have an app that’s now connected to your database. Click on nodejs-app1 and scroll down to Network Security, locating the Public IP for your instance. Click it, and you should be back at your Hello World screen. Now add /users/ to the URL.
If you see a pair of empty brackets, congratulations! You requested all users and received all zero of them. This means that your App Server was able to connect to the RDS instance, query the database, and return the results. If you saw an error message instead, then, sadly, something has gone wrong. Check for typos, and if you find none, try retracing your steps.