One problem I see crop up pretty regularly with Terraform codebases is a lot of repeated code, this repeated code is usually to facilitate the deployment of environments or multiple instances of the codebase. An example of this might be some networking and a virtual machine that hosts a web application this code is copied and pasted into different folders or repositories and some configuration (usually minor) changed. In this example if you had three environments you now have three copies of the codebase that needs to be maintained and kept in sync, this puts additional (and unnecessary) strain on your engineers. So, how would I fix this?
The answer to this question might seem potentially easy but in my experience it ends up being a little more complicated. To me this all starts with removing the repeated and often near identical code, and realistically your non-production environments should be identical to your production environment(s) perhaps just at a reduced scale. This can all be solved through the ingestion of configuration into our Terraform codebase/module. You wouldn't have three copies of your applications code now would you?
In my mind there are three ways that this can be achieved:
Lets go through each of these scenarios more in depth to find out how they would solve the problem of not repeating your code. But first, lets write the code that would be used in the copy+paste scenario and we can improve on it to fit each of the ingestion scenarios.
First we setup our providers.
Next we will setup all our networking infrastructure, as you can see we are statically assigning all of our values here.
Finally we will output our private key so that we can access the server!
Warning! Do not do this in any real world scenarios, this should be put into a secrets vault of some description!
Now that we have defined our input variables for our code we could go one step further and turn that code into a module itself. Doing this allows us to strictly version and control the version of our code that is consumed by each environment. In fact we could use either variable files or configuration files to drive the module! For the sake of this example we will statically assign the inputs.
First thing is first we will create the module itself, we will do this locally.
And now the relevant files for us to store our code:
For the sake of not making this post 10,000 lines I won't put the code in for the module itself as it would be identical to the code in the Variable Files section, just put into the empty files we created just before. I will instead show how we call the module.
The interface for the module is nice and simple, and is satisfied by passing through everything that we declared as input variables. Using modules allows a fantastic level of control on what the consumers of our code are doing, in this instance the module is defined locally however nothing is preventing us from storing it in git or a module registry like Terraform Cloud.
A lot of the keys here will look pretty familiar given they represent all of the input variables that we were previously passing into our Terraform code. The advantage of the configuration file approach is that we can house multiple projects in a single file, with all their environments along side it. We could even turn our configuration file into a module itself, or even a provider!
Let's look into our code now to see what's changed and how exactly we are consuming this configuration file.
All in all I wouldn't say there is a specific "you must use Method A every time because reason X" each one offers different benefits and you should select the one that is best suited to your situation!
Hopefully that's provided some valuable insight into getting your configuration into your Terraform code and reducing the amount of duplicated code! As always if you have feedback or would like to know more please feel free to reach out!
You can follow Brendan @BrendanLiamT on Twitter.