Colonize is a configurable, albeit opinionated way to organize and manage your terraform templates. It revolves around the idea of environments, and allows you to organize templates, and template data around that common organizational structure.
Once it's been configured, it allows for hierarical templates and variables, and the ability to organize them in a defined manageable way.
The easiest way to get going with colonize is to download it from https://github.com/craigmonson/colonize/releases, unpack it and plop it into your path:
# grab the darwin version:
%> curl https://github.com/craigmonson/colonize/releases/download/<version>/colonize-<version>.darwin-amd64.tar.gz > colonize.tar.gz
# unpack it
%> tar zxpvf colonize.tar.gz
# mv to path
%> mv colonize-<version>/colonize ~/bin/colonize
# ensure it's executable
%> chmod u+x ~/bin/colonize
You can also use the typical go get
functionality to grab the entire
repo and install the executable, but you've got to have your golang environment
set up:
# install via golang tools
%> go install github.com/craigmonson/colonize
Colonize is opinionated about the structure of a project, so, for this to be used you must structure your terraform project in a specific way.
The project root, according to colonize, is whereever the .colonize.yaml
file
is located. This is typically in the root of your git repo, but doesn't have
to be. Configuration will not be read any higher in the tree than the project
root.
There is one, global .colonize.yaml
file that configures how clonize runs.
This should be located in the root of your project. Colonize will walk up the
tree until it finds the file, at which point it'll assume that it's found the
project root. Environment configuration will be read through the branches up to
the root.
A Leaf is an endpoint in the tree, and should contain all your functional terraform code. This is where you keep the templates that you craft in order to manage infrastructure. This should be familiar to you if you've used terraform before.
A Branch is just a pathway to a leaf. These contain NO functional terraform
templates, and ONLY contain configuration in the env
directory.
Colonize will utilize all of the configuration in each env
directory, in each
branch, from the root, to the leaf, when running colonize commands. This would
allow you to configure, say, an account variable in the root, and that account
variable would be available to every template in the tree, without you having
to cut and paste it into every template.
Each branch in the directory tree, from the root to the leaf can have environment specific files, all stored in the 'env' directory at that particular level. These files will be combined and used at each point in the leaves, when running terraform commands. Colonize and Terraform will work together to utilize these combined files when the terraform commands are executed.
Colonize utilizes terraform, and the way terraform runs commands to provide
environment specific configuration for your functional templates. It does this
by combining files through the tree, and placing them in the working directory
of the currently executing template. Those files are all prefixed with an
underscore ("_"), for example: _combined_variables.tf
.
Colonize creates several variable, and assigns them values, automatically from the generated config of the project / tree. You can also define your own derived variables to be used in your templates as well. Unlike terraform, colonize allows you to create variables and values from already existing values.
Colonize expects several specially named files in the env
directory. Each
one allows you to configure your templates at runtime in any way you see fit.
These files are:
- <environment>.tfvars
- derived.tfvars
- *.tf
- remote_setup.sh
In addition, you can name the templates in your templte directory to have environment specific templates as well:
- foo.tf.dev
- foo.tf.base
Lets look a little deeper into each of these files.
These are the driving environment spcific variable assignment files that will distinguish settings between your different environments in terraform. Each environment is expected to map directly to a specific tfvars file. I think it's best described through an example:
test
└── web
├── env
│ ├── dev.tfvars
│ ├── prod.tfvars
│ ├── qa.tfvars
├── main.tf
Here our web
is setup with one main.tf
file, where it's assumed that we're
using terraforms variables, and variable substitutions to create a more modular
and reusable template. Lets assume that web is spinning up a single instance,
and we've got our instance size set to a variable:
# main.tf
variable "size" {}
resource "fake_instance", "fake" {
size = "${var.size}"
...
}
We can now specify our instance size in each of the environment specific files:
# dev.tfvars
size = "small"
# qa.tfvars
size = "medium"
# prod.tfvars
size = "large"
Now, when we simply run one of our colonize commands with the environment set
appropriately, colonize will set things up so that terraform uses the right
tfvars file: colonize prep -environment=dev
would use the dev.tfvars
file
when doing terraform things.
Colonize will store all of those variable assignments in the _combined.tfvars
file in the leaf. It will also generate a variables file for you, also in
the root, named _combined_variables.tf
NOTE: Colonize can only use single string assignment variables at the moment. NO MAPS OR LISTS
To Aid in configuration, colonize allows for single pass derived variables,
meaning that colonize will pass over the derived variables once for substitution.
Ths allows you to create more varied values based off of already defined
variables; Something that terraform currently doesn't do. Colonize will first
generate the environment files as noted above. It will then combine the
derived files, then make substitutions utilizing the combined variable file
from above. For simplicity sake, it uses the same syntax that terraform does
for variable interpolations, but it does NOT allow the use of anything but
the ${var.variable_name}
, so NO FUNCTIONS. Lets take a look.
Given the tree:
test
└── web
├── env
│ ├── dev.tfvars
│ ├── prod.tfvars
│ ├── qa.tfvars
│ ├── derived.tfvars
├── main.tf
As in the example above, the different files have different values for the
variable size
. Lets utilize that, and the environment
varible that
Colonize automatically generates for us. So our derived file looks like:
tag_name = "web-${var.environment}-${var.size}"
So, we'll run colonize prep -environment=dev
, and colonize will build both the
_combined_derived.tfvars
and _combined_derived.tf
files for you, with the
contents:
# _combined_derived.tfvars
tag_name = "web-dev-small"
# _combined_derived.tf
variable "tag_name" {}
We can now utilize this variable in our templates as normal. Do note that in many cases it's possible to just utilize terraform for variable interpolations, but in some cases it might be beneficial to allow for derived variables to simplify the templates.
Any tf file in the configuration directory env
will be combined and placed
into the _combined.tf
file in the leaf. There is nothing fancy that happens,
it just combines all the .tf files it finds in the tree between the root and
leaf.
The remote_setup.sh file is used to execute the terraform remote command to ensure the state is properly synced for your terraform runs.
Colonize looks for only one remote_setup.sh file in the roots config (env
)
directory. Like the derived tfvars files above, this one also allows for
variable interpolation. Colonize will read in, do the variable substitutions,
and write it out in the leaf directory to a file named _remote_setup.sh
.
The remote is used when colonize does it's thing with terraform, like plan,
apply and destroy.
In the leaves, you can have distinct templates per environment if you need. By naming the files with the appropriate extensions, colonize will know which ones to combine when it prepares for the run.
Files named in the pattern: template_name.tf.<environment>
will be included
when the environment matches. So, a file named foo.tf.dev
would be included
in the terraform run only if then environment is set to dev. If the
environment is set to anything else, then it won't be included. If there is a
template that's named with the .default
extension, then environments that
do NOT have a specific template, will use the default one. These files
will be combined into the _combined.tf
file. Lets take a look at an example:
test
└── web
├── env
│ ├── ...
├── main.tf
├── creds.tf.default
├── creds.tf.prod
├── db.tf.dev
In the example above, we've got 4 terraform templates. When we run the command:
colonize prep -environment=prod
, then Colonize will include creds.tf.prod
,
and ONLY creds.tf.prod
into the _combined.tf
file. Terraform will still
use any *.tf files on it's own, so any terraform commands (plan, apply etc) will
use main.tf
as well.
If we, instead, run the command: colonize prep -environment=dev
, then Colonize
will include creds.tf.default
AND db.tf.dev
into the _combined.tf
file. As
before, terraform commands will utilize main.tf
as well.
You can execute terraform commands on multiple leaves under a given branch, so long as you include a build_order.txt file at the branch level. This explicitly defines the order of the leaves to be executed in for the given command. Given the branch directory structure:
├── db
├── mailer
├── vpc
├── web
├── env
and a build\_order.txt
file with the contents:
db
web
mailer
If you ran the command: colonize prep -environment=dev
, colonize would
execute prep
first in db, followed by web, then finally by mailer. The
underlying directories can be either branches or leaves, and colonize will drop
ito each and do the right thing as it moves along that particular branch: Drop
into directories that are branches, and execute prep
on all the leaves it
encounters along the way, honoring each build\_order.txt
as it goes along.
The best thing to do when refering to the execution of commnds in colonize is to review the inline documentation. However, what follows is a quick overview on what's available via the colonize commands.
The commands mostly build upon themselves, so follow this order:
- init
- prep
- plan
- apply
- destroy
- clean
NOTE: prep will be run automatically for the plan command. This is to allow for the closest similarities to the actual terraform commands, from which colonize tries to mimic. (plan, apply, destroy)
The init command runs an interactive process to help initialize your Colonize
project. It will ask a series of questions and provide defaults for building your
.colonize.yaml
file.
The following output is an example of the Colonize init command's interactive process. Each variable is provided with a default value, where entering nothing will result in accepting that variable's default.
Enter 'environments_dir' [env]:
Enter 'base_environment_ext' [default]:
Enter 'combined_vals_file' [_combined.tfvars]:
Enter 'combined_vars_file' [_combined_variables.tf]:
Enter 'combined_derived_vals_file' [_combined_derived.tfvars]:
Enter 'combined_derived_vars_file' [_combined_derived.tf]:
Enter 'combined_tf_file' [_combined.tf]:
Enter 'combined_remote_config_file' [_remote_setup.sh]:
Enter 'remote_config_file' [remote_setup.sh]:
Enter 'derived_file' [derived.tfvars]:
Enter 'vals_file_env_post_string' [.tfvars]:
After completing each variable, the init command will display each setting and
prompt you for acceptance. After the settings have been accepted, a
.colonize.yaml
file will be created in the current directory, as well as the
selected environments_dir
.
Optional Command Flags
--accept-defaults
: This will accept all default values, automatically.
Example:
Running colonize init --accept-defaults
, would result in the following directory
structure:
.
├── .colonize.yaml
└── env
The contents of the .colonize.yaml
file would be as follows:
## Generated by Colonize init
---
environments_dir: env
base_environment_ext: default
autogenerate_comment: This file generated by colonize
combined_vals_file: _combined.tfvars
combined_vars_file: _combined_variables.tf
combined_derived_vals_file: _combined_derived.tfvars
combined_derived_vars_file: _combined_derived.tf
combined_tf_file: _combined.tf
combined_remote_config_file: _remote_setup.sh
remote_config_file: remote_setup.sh
derived_file: derived.tfvars
vals_file_env_post_string: .tfvars
Of course, if you run the itneractive process and make modifications to any of
the variable defaults, the .colonize.yaml
file would match those settings
that you selected.
The prep command is the workhorse of the colonize command. It does all of
the combining and tree walking to generate files that the installed terraform
will utilize in it's plan / apply / destroy runs. As one would expect, this
prepares terraform for the given environment <env>
All of the generated files are prepended with the underscore ("_"), so should be easily identifiable upon completion of the execution.
It should be noted that once prep has been successfully executed, you should be able to execute any terraform command, and the generated files will be utilized as expected. Since colonize only runs a subset of the terraform commands, you can execute prep and run any terraform command to execute outside of colonize. Since it's terraform that does the state data syncing, everything should stay ok, but you should be very careful with this approach, as remote file setup etc may need to be manually handled.
Prep does 2 things via terraform:
- It removes any
.terraform
directory (remote state) as the first step of the execution. - It executes
terraform get -update
as the last step of the execution.
The get -update
isn't such a big deal, but it's VERY important to
note that prep will remove the .terraform
directory, as, depending on
what non-colonize commads you've been executing, you may accidentally remove
non-sync'd state data.
plan wraps terraform plan
. It's important to understand that
plan will execute prep first, regardless if prep has already been
run. This is important to know, because prep will delete the .terraform
direcory as a first step.
apply wraps terraform apply
, and runs it against the existing plan
that was created in the plan
step. So yes, in order to run apply
,
you need to run plan
first.
destroy wraps terraform destroy
, and will fully destroy the template
stack. It does not need an apply.
clean is akin to make clean
and should remove all of the generated
files that are created in the prep
step. This happens regardless if
a destroy or apply were done before hand.
The generate command is used to provide convienience to generating Colonize
resources and project structures. Generate provides sub-commands for each
resource-type
to create.
The branch generation sub-command is used to generate a Colonize branch, including build order file, environment directory, environment tfvars, and optionally a list of leafs underneath the branch.
The following command:
$ colonize generate branch myapp --leafs security_groups,database,instances
Will generate the following branch & leaf structures
myapp
├── build_order.txt
├── database
│ └── main.tf
├── env
│ ├── dev.tfvars
│ ├── test.tfvars
│ └── prod.tfvars
├── instances
│ └── main.tf
└── security_groups
└── main.tf
The leaf generation sub-command is used to generate a Colonize leaf, including environment directory, environment tfvars, and will append to the build order file. Given we are in the mybranch directory, the following command:
$ colonize generate leaf myleaf
Will generate the following structure in the mybranch
branch
mybranch
├── build_order.txt
├── env
└── myleaf
└── main.tf
... and build\_order.txt
will have the single myleaf
line added to it.
- 0.1.1-alpha - bugfix release.
- 0.1.0-alpha - initial release.
- 0.0.0 - still in development.
- Craig Monson
- Joey Yore
- Lars Cromley