Updating Terraform Modules versions with Bash
Splitting your Terraform setup into versioned modules can provide many benefits like DRYer code, decoupling updates to modules from updates to infrastructure and the ability to use different versions of modules for different environments. This all makes working with Terraform a much more enjoyable experience. However it can be tedious updating all the module source references every time you release a new version. So I wanted a quick bash script to do it for me!
#!/usr/local/bin/bash
module_destination="~/terraform-modules"
infrastructure_destination="~/infrastructure"
cd $module_destination
LATEST_TAG=$(git describe --abbrev=0 --tags)
printf "\n\033[37;1mUpdating all modules to Latest Tag: $LATEST_TAG\033[0m\n"
cd $infrastructure_destination
find . -name \*.tf -not -path "*/.terraform/*" -exec sed -i "s/v[0-9]\+.[0-9]\+.[0-9]\+/$LATEST_TAG/" {} +
Note: This script relies on you tagging your module with the format of v0.0.0.
Git Describe
We use git describe to grab the latest tag with this command:
git describe --abbrev=0 --tags
Checking the man pages we learn the following:
git-describe - Describe a commit using the most recent tag reachable from it
--tags
Instead of using only the annotated tags, use any tag found in refs/tags namespace.
This option enables matching a lightweight (non-annotated) tag.
--abbrev=<n>
Instead of using the default 7 hexadecimal digits as the abbreviated object name,
use <n> digits, or as many digits as needed to form a unique object name.
An <n> of 0 will suppress long format, only showing the closest tag.
The last line is what is important to us An <n> of 0 will suppress long format, only showing the closest tag. Without this command you'll end with tags like v0.1.156-13-ga481d05 instead of v0.1.156-13.
So the full command of git describe --abbrev=0 --tags simply says
give us the most recent tag minus all the fancy stuff
Find and Sed
This command performs the bulk of our work needed:
find . -name \*.tf -not -path "*/.terraform/*" -exec sed -i --follow-symlinks "s/v[0-9]\+.[0-9]\+.[0-9]\+/$LATEST_TAG/" {} +
Let's break this down piece by piece
find . -name \*.tf
Find all the files that end with .tf
-not -path "*/.terraform/*"
Ignore files that within folders .terraform
-exec sed
Execute the sed command on the return value of find
sed -i
Change the files in place
"s/v[0-9]\+.[0-9]\+.[0-9]\+/$LATEST_TAG/"
Replace v[0-9]\+.[0-9]\+.[0-9]\+ (which matches our tags like v0.3.2 or v10.12.999) with the that Latest Tag pulled with git
{} +
This is actually part of the -exec flag for find
-exec command {} +
This variant of the -exec action runs the specified command on the
selected files, but the command line is built by appending each
selected file name at the end; the total number of invocations of the
command will be much less than the number of matched files. The command
line is built in much the same way that xargs builds its command lines.
Only one instance of `{}' is allowed within the command...
This is how we can use the result of find like xargs, once for each return value.
And that's it!
Extra Reading
If you want to get more familiar with modules here are some good places to start: