Over the last few months, I have spent a lot of time working on AWS. I often need to spin up EC2 instances, databases, or other assets for testing. Doing this by hand can become burdensome. You need to click through the AWS CLI and keep track of everything you have created. This sounds like a perfect use case for infrastructure as code. Enter Pulumi!
Motivation
If you are familiar with python, the learning curve for Pulumi is relatively low. I quickly learned how to spin up and destroy infrastructure in a programmatic way. One topic that I always struggled with was configuration and template files. Pulumi has no obvious built-in way to create template files that contain the dynamic values generated from Pulumi. For example, I may want to pass the IP address of my EC2 instance into a .env file.
After many different experiments, I have finally landed on a pattern that allows me to write templates using Jinja2. This approach will enable me to:
- Define and render templates using Jinja2.
- Automatically update the templates by hashing the template files.
TL/DR
You can find all of the code on GitHub: https://github.com/SamEdwardes/personal-blog/tree/main/blog/2022-07-14-pulumi-with-jinja-templates. You can download the code as a zip file using this link from DownGit.
Run the code below to spin up the infrastructure for yourself!
Setup
To spin up AWS infrastructure, Pulumi needs to be able to log into your account. You can do this through AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY
and environment variables.
Next, create a virtual environment, and install the dependencies:
Lets see what is inside the requirements.txt:
There are several packages we installed in addition to the minimum requirements from Pulumi:
- pulumi-command: Used to execute commands on the remote EC2 server.
- Jinja2: Used to generate dynamic templates.
- pycryptodome: Used to hash our template files so that Pulumi automatically.
Next, we need to create a new public and private key that we will use to SSH into our EC2 instance. I made a Python script that can quickly generate a unique public/private key pair. Create a unique pair by running:
Expand to see create_keypair.py
Lastly, create a new pulumi stack. I will name my stack dev, but you can call it whatever you like.
pulumi up
You are now ready to spin up your infrastructure. Just run:
_main_.py deep dive
Let us take a closer look at main.py and see what is happening.
Expand to see __main__.py
Below we will walk through some of the critical parts of the code.
create_template(path: str)
This is a helper function so we can quickly create a jinja2.Template
object. We wrap this logic in a function so we can easily call it later inside of pulumi_command.remote.Command
.
hash_file(path: str)
This function will create a unique hash of our template files. Note that we return a pulumi.Output
object.
command_render_template
This code chunk is really the core of what we are doing:
The basic approach is to run an echo
command on the remote server that writes our rendered template to a file.
The tricky part is getting our rendered template into "TEMPLATE CONTENTS"
. To do this, we need to use pulumi.Output.concat
. This function allows you to use the output of other pulumi.Output
objects. Notice that we pass in all the values we want to access in our template.
Then, we can use pulumi.Output.concat().apply
to pass these values into another function. Here, we will create a jinja2.Template
object with our create_template
function and dynamically render the values.
Note that the keyword arguments inside create_template(local_file_path).render
should match the values in your template.
See the results
Now that you have run pulumi up
and created a dynamically rendered template let us check out the results. SSH into your EC2 instance:
Once inside your EC2 instance, inspect the template that we generated.
Wrap up
With jinja2 and Pulumi we are now able to turn this:
Into this!