Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Terraform modules/extensions #7

cespare opened this Issue Oct 11, 2012 · 11 comments


None yet
3 participants

cespare commented Oct 11, 2012

This is to open up a bit of a discussion about how we should handle packaging common Terraform code. It's based on a discussion I had with @dmacdougall. I'm hoping to get @philc's input as well.

Basic motivation: we're looking at bolstering our Terraform usage at Ooyala to make it easier to manage the various kinds of deployments with which we've found ourselves using it. As we go about adding functionality via modules on top of Terraform (distributed as gems), we want to make sure that we flesh out the recommended practices for extending Terraform and then follow those guidelines.

Here are some things we'd like to be able to do, in no particular order:

  • Have recipes for commonly used software (e.g., redis)
  • Replace ensure_rbenv and ensure_rbenv_ruby with recipes that install a pre-built Ruby from an apt package, and install rbenv from an internal mirror (not github), and don't rely on git
  • Lots of new tasks such as helpers to add an apt repo
  • Use Terraform with non-Ruby projects

While thinking about adding on such functionality, some basic questions about how we want to extend Terraform come to mind. Here are two ideas for how to standardize on Terraform extensions. First, two basic assumptions:

  1. Extensions should be (able to be) packaged as gems.
  2. Terraform should work on a box with any ruby (at least 1.8) on it; no rubygems/bundler necessarily present.

Status quo: two-phase terraforming

The way we do things right now in non-trivial deployments (see Barkeep) is a two-phase process. We have a basic Terraform script which sets up Ruby on the box. Then we have a second setup script which is run with the installed Ruby, and takes advantage of the fact that Ruby 1.9 and bundler are installed to run the ruby-specific setup (bundle install and db migrations).

Extending this idea, one logical way to manage Terraform extensions is to write the extensions assuming that Rubygems/bundler is available, then do the two-phase setup. In the second phase, you can require all your custom Terraform gems (which can live in a Gemfile).

Pros: This is purely a convention, and doesn't adding any extra functionality onto Terraform.

Cons: It's a little annoying to always need two scripts. This doesn't jive well with the use case of deploying a non-Ruby project, where you wouldn't ordinarily need to install ruby/rubygems/bundler anyway.

Cat all the files

One idea I had was that we can 'register' each Terraform DSL file (by calling some method in each one). This adds the file to a list. The write_dsl_file method can concatenate all the files in the list into a single mega DSL file that it writes out. This way, you can require some Terraform extensions, call write_dsl_file, and it will write out a single file that contains all the code you need to provision your system.

Pros: Simpler for the user.

Cons: Seems kind of hacky; might require some trickery to make the mega-file work properly. Requires adding functionality onto the currently very minimal feature set of Terraform.

Open questions

  1. What solution/convention do we want to use for managing Terraform extensions: "status quo", "cat all the files", or something else altogether?
  2. How do we allow for overriding tasks? (For example, if we want to override the rbenv stuff as mentioned above.) Maybe we can just do some monkey-patching.
  3. Should rbenv stuff be in Terraform core? What if the Terraform gem itself were modularized? What if you could, for example, require "terraform/rbenv" if you needed that functionality?

Anyway, I have more to add but this is already way too long. Thoughts?


cespare commented Oct 11, 2012

I should add that the expected outcome is either some conventions and/or code changes, and some documentation in the readme to settle these issues either way.


dmac commented Oct 11, 2012

Another option:

Users, manage your own deps

Terraform already provides a facility for writing custom deps. Terraform shouldn't need to care how those deps are created, as long as they've been run before satisfy_dependencies is called. We could define shared deps in gems, the contents of which are written out at deploy time just like the Terraform DSL.

Pros: Like "status quo", this doesn't require any additional functionality in Terraform, but also remains a one-step process.

Cons: It's a bit more work for the user than having Terraform manage it for you, and how a dep gem writes itself to a file in your deployment is all purely convention.


cespare commented Oct 11, 2012

Another option that @dmacdougall and I hashed out together:

Fine, users, we'll help you a little bit, jeez

Sort of a half-way point between "Users, manage your own deps" and "Cat all the files". Add a register_plugin method which you call in your plugin. This adds your filename to a list. Change write_dsl_file to write_dsl_files; this writes out the core Terraform DSL file as well as all the additional plugin DSL files.

Your provisioning script now looks something like this:

require "./terraform/terraform_dsl.rb"
Dir["./terraform/*.rb"].each { |plugin| require plugin }

Pros: The additions to Terraform are very small. The resulting behavior is intuitive to the end user (all the files written out correspond to plugins that were loaded). It's still a one-step process, without requiring rubygems anywhere in the process.

Cons: Requires some modifications to Terraform.


philc commented Oct 11, 2012

Interesting ideas guys. I'll post some comments this evening.


dmac commented Oct 11, 2012

I put together a sample implementation of "Fine, users, we'll help you a little bit, jeez". The relevant Terraform changes are in this commit, and I wrote a sample plugin that modularizes rbenv. We can use these as a starting point for more discussion.


cespare commented Oct 15, 2012

@philc bump?


philc commented Oct 16, 2012

Thanks for the thoughtful discussion and sample implementation guys; this is really pro.

I like Fine, users, we'll help you a little bit, jeez because we don't want to help out our users too much ;-) What's happening is straightforward Ruby and it doesn't require that the user make use of gems to get their job done.

Two suggestions: we rename Terraform.write_dsl_files to Terraform.write_files or Terraform.write_terraform_files.

Dmac, in your rbenv readme instructions, you can omit the first line:


If this line ever gets any more complicated, we should consider adding a method to Terraform which does this:

Dir["./terraform/*.rb"].each { |plugin| require plugin }

If common functionality (like rbenv) is going to exist in other repos, we should link to them from the readme. This extension framework is very timely; @harob and I were just yesterday contemplating were to stick our clojure recipes =)


philc commented Oct 16, 2012

I've added you both as committers in case you need to modify the Terraform repo directly rather than via pull requests.


cespare commented Oct 16, 2012

Alternatively, we can put the frequently-used but not-always-necessary recipes in the gem, but not load them automatically. For instance, rbenv can be present, but not in the core dsl. But you require terraform/rbenv to get it, no extra gem necessary.


cespare commented Oct 16, 2012

👍 for Terraform.write_terraform_files.

@dmac dmac closed this in 70129ec Oct 18, 2012


dmac commented Oct 18, 2012

I left the rbenv deps in core, but would also be fine with not including them by default (but still including it in the Terraform project as optional deps).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment