Skip to content

Instantly share code, notes, and snippets.

@antonbabenko
Created May 15, 2021 18:45
Show Gist options
  • Save antonbabenko/d77f8cf8bf891e589a6b5b0ab0e773ae to your computer and use it in GitHub Desktop.
Save antonbabenko/d77f8cf8bf891e589a6b5b0ab0e773ae to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
set -e
readonly CONTENT_MAIN_TF='module "wrapper" {}'
readonly CONTENT_VARIABLES_TF='variable "items" {
description = "Maps of items to create a wrapper from. Values are passed through to the module."
type = any
default = {}
}'
readonly CONTENT_OUTPUTS_TF='output "wrapper" {
description = "Map of outputs of a wrapper."
value = module.wrapper
}'
readonly CONTENT_VERSIONS_TF='terraform {
required_version = ">= 0.13"
}'
readonly CONTENT_README='# WRAPPER_TITLE
The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt).
You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module.
This wrapper does not implement any extra functionality.
## Usage with Terragrunt
`terragrunt.hcl`:
```hcl
terraform {
source = "git::git@github.com:MODULE_REPO_ORG/terraform-aws-MODULE_REPO_SHORTNAME.git?ref=master//WRAPPER_PATH"
}
inputs = {
items = {
my-item = {
# omitted... can be any argument supported by the module
}
my-second-item = {
# omitted... can be any argument supported by the module
}
# omitted...
}
}
```
## Usage with Terraform:
```hcl
module "wrapper" {
source = "MODULE_REPO_ORG/MODULE_REPO_SHORTNAME/aws//WRAPPER_PATH"
items = {
my-item = {
# omitted... can be any argument supported by the module
}
my-second-item = {
# omitted... can be any argument supported by the module
}
# omitted...
}
}
```
## Example: Manage multiple S3 buckets in one Terragrunt layer
`eu-west-1/s3-buckets/terragrunt.hcl`:
```hcl
terraform {
source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers"
}
inputs = {
items = {
bucket1 = {
bucket = "my-random-bucket-1"
force_destroy = true
}
bucket2 = {
bucket = "my-random-bucket-2"
force_destroy = true
}
}
}
```'
check_dependencies() {
if [[ ! $(command -v hcledit) ]]; then
echo "ERROR: The binary 'hcledit' is required by this script but is not installed or in the system's PATH."
echo "Check documentation: https://github.com/minamijoyo/hcledit"
exit 1
fi
}
create_tmp_file_tf() {
# Can't append extension for mktemp, so renaming instead
tmp_file=$(mktemp "${TMPDIR:-/tmp}/tfwrapper-XXXXXXXXXX")
mv "$tmp_file" "$tmp_file.tf"
tmp_file_tf="$tmp_file.tf"
echo "$CONTENT_MAIN_TF" > "$tmp_file_tf"
}
main() {
check_dependencies
local root_dir=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
local module_dir="" # empty (default) or modules/iam-user
local wrapper_dir="wrappers"
local module_repo_org="terraform-aws-modules"
local module_repo_name=$(basename "$root_dir")
local module_repo_shortname="${module_repo_name//terraform-aws-/}"
local overwrite="false"
local verbose="false"
local relative_source_path="../" # From "wrappers" to root_dir
local newline="--newline"
while [[ $# -gt 0 ]]; do
local key="$1"
case "$key" in
--root-dir)
root_dir="$2"
shift
;;
--module-dir)
module_dir="$2"
shift
;;
--wrapper-dir)
wrapper_dir="$2"
shift
;;
--module-repo-org)
module_repo_org="$2"
shift
;;
--module-repo-shortname)
module_repo_shortname="$2"
shift
;;
--overwrite)
overwrite="true"
;;
--verbose)
verbose="true"
;;
*)
echo "ERROR: Unrecognized argument: $key"
echo "Usage: $0 [--root-dir <root-dir>] [--module-dir <module-dir>] [--wrapper-dir <wrapper-dir>] [--module-repo-shortname <module-repo-shortname>] [--overwrite] [--verbose]"
echo "Example 1: $0 --root-dir /Users/Bob/Sites/terraform-aws-modules/terraform-aws-s3-bucket --module-dir modules/object"
echo "Example 2: $0 --module-dir modules/object"
echo "Example 3: $0 --overwrite --verbose"
exit 1
;;
esac
shift
done
if [[ -z "$root_dir" ]]; then
echo "--root-dir can't be empty. Remove it to use default value."
exit 1
fi
if [[ -z "$wrapper_dir" ]]; then
echo "--wrapper-dir can't be empty. Remove it to use default value."
exit 1
fi
if [[ -z "$module_repo_org" ]]; then
echo "--module-repo-org can't be empty. Remove it to use default value."
exit 1
fi
if [[ -z "$module_repo_shortname" ]]; then
echo "--module-repo-shortname can't be empty. It should be part of full repo name (eg, s3-bucket)."
exit 1
fi
if [ ! -d "$root_dir" ]; then
echo "Root directory $root_dir does not exist!"
exit 1
fi
full_module_dir="${root_dir}/${module_dir}"
if [ ! -d "$full_module_dir" ]; then
echo "Module directory $full_module_dir does not exist!"
exit 1
fi
# Remove "modules/" from "modules/iam-user"
module_name="${module_dir//modules\//}"
if [ "$module_name" == "" ]; then
wrapper_title="Wrapper for the root module"
wrapper_path="${wrapper_dir}"
else
wrapper_title="Wrapper for module: \`${module_dir}\`"
wrapper_path="${wrapper_dir}/${module_name}"
fi
# Wrappers will be stored in "wrappers/{module_name}"
output_dir="${root_dir}/${wrapper_dir}/${module_name}"
[ ! -d "$output_dir" ] && mkdir -p "$output_dir"
# Calculate relative depth for module source by number of slashes
module_depth="${module_dir//[^\/]}"
for _ in $(seq "${#module_depth}"); do
relative_source_path+="../"
done
create_tmp_file_tf
if [ "$verbose" == "true" ]; then
echo "Root directory: $root_dir"
echo "Module directory: $module_dir"
echo "Output directory: $output_dir"
echo "Temp file: $tmp_file_tf"
echo
fi
# Get names of module variables in all terraform files
declare -a module_vars=($(cat "${full_module_dir}"/*.tf | hcledit block list | grep variable. | cut -d'.' -f 2))
hcledit attribute append module.wrapper.source "\"${relative_source_path}${module_dir}\"" --newline -f "$tmp_file_tf" -u
hcledit attribute append module.wrapper.for_each var.items --newline -f "$tmp_file_tf" -u
for module_var in "${module_vars[@]}"; do
# Get default value for the variable
var_default=$(cat "${full_module_dir}"/*.tf | hcledit attribute get "variable.${module_var}.default")
# Empty default means that the variable is required
if [ "$var_default" == "" ]; then
var_value="each.value.${module_var}"
else
var_value="lookup(each.value, \"${module_var}\", "$var_default")"
fi
hcledit attribute append "module.wrapper.${module_var}" "${var_value}" $newline -f "$tmp_file_tf" -u
newline=""
done
if [ "$verbose" == "true" ]; then
cat "$tmp_file_tf"
fi
if [ "$overwrite" == "true" ]; then
echo "Saving files into ${output_dir}"
mv "$tmp_file_tf" "${output_dir}/main.tf"
echo "$CONTENT_VARIABLES_TF" > "${output_dir}/variables.tf"
echo "$CONTENT_OUTPUTS_TF" > "${output_dir}/outputs.tf"
echo "$CONTENT_VERSIONS_TF" > "${output_dir}/versions.tf"
echo "$CONTENT_README" > "${output_dir}/README.md"
sed -i "s#WRAPPER_TITLE#${wrapper_title}#g" "${output_dir}/README.md"
sed -i "s#WRAPPER_PATH#${wrapper_path}#g" "${output_dir}/README.md"
sed -i "s#MODULE_REPO_ORG#${module_repo_org}#g" "${output_dir}/README.md"
sed -i "s#MODULE_REPO_SHORTNAME#${module_repo_shortname}#g" "${output_dir}/README.md"
else
echo "There is nothing to save. Add --overwrite flag to write files."
fi
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment