Deploying Node.js Applications with AWS OpsWorks — part 2
In the last article I described how to build a simple cookbook for deploying Node.JS applications using Chef. Check it out if you want to recapitulate the steps as we will re-use them here.
In this post I want to describe how to group your Chef code using Custom Resources
, which were introduced with Chef 12.5.
Custom Resources
Resources are the most essential part of the Chef DSL like git
, package
or template
. Once upon a time ... to create your own resources you had to use HWRP (heavy weight resource providers). Then LWRP (light weight resource providers) were introduced to make your life easier. Now there's this new kid on the block called Custom Resources
and again it should make things even more simple. I don't want to go into details, but what I noticed first is the ability to run recipes from within your Custom Resource is now as simple as include_recipe
.
So why do we even need to define our own resources?
Coming back to our Cookbook, we defined five steps that are necessary for the deployment:
- install
git
- install
nodejs
- get the code through
git/github
- install external node packages
- run the app
First of all, not all of these steps are necessary for each deployment. Secondly Chef Resources are easier to handle, than include_recipe
syntax and thirdly they are easier to test, especially for users of your cookbook, who don't need to stub things that come from within you cookbook logic.
Ok, now we decided to build a Custom Resource for our deployment, how does it look like and how do we split it? As I mentioned before we might not want to run all steps for each deployment. This decision is more subjective and for our deployment I decided to not try to install git (1) or Node.js (2) on each run, whereas code updates (3), npm dependencies (4) and the app run environment (5) should be checked on each run. This doesn’t mean you cannot update Node.js later on, but I will come to this later.
Now we know how to split up functionality, we will create two Custom Resources, each of them must be one file located in the cookbook’s directory within a resources
folder. The fist one will be named setup.rb
and the other one deploy.rb
. The filename plus it's cookbook will give the resource it's name, for simplicity we will assume this cookbook is called node-app. So afterwards we will have the two resources node_app_setup
and node_app_deploy
.
Enough talking, let’s write some code :)
So what did we do here. Most of the code is copied from the last blog post, but what changed is, that our Chef code is part of an action
block and strings we hard-coded before are now defined as properties
. Check out the Chef docs for Custom Resources to learn more details. This means we can now call the resources from outside and configure them for our needs, like this:
Doesn’t it look amazing?
As an user, you don’t need to care anymore what’s happening behind the scenes, but as the maintainer you can easily change things in the background without anyone telling! Also both functionalities can still be triggered on each deployment, but it’s simplier now to just run the node_app_deploy
resource on each run and node_app_setup
only on one intial run.
Chefspec Matcher
As a side note I want to mention, that with the created resources it’s now simpler for a user of your cookbook to run tests using chefspec
, as the normal spec run does not step into resources. To make the life of your user's simpler, every resource should come with a chefspec matcher! It's not more than a matchers.rb
file inside your cookbook within the libraries
folder, that looks like this:
In any chefspec test it can now be used like:
Conclusion
So let’s review what we did: We encapsulated the logic for our deployment into two Custom Resources, node_app_setup
and node_app_deploy
to give the user the possibility to use our deployment cookbook for multiple different configurations and provided a matchers.rb
file to simplify external testability.
In the next article we will see how to use our new resources with AWS OpsWorks and the data OpsWorks injects.