Last active
July 28, 2022 11:45
Terraform Planner script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import glob | |
import json | |
import subprocess | |
import sys | |
import json | |
from pathlib import Path | |
env = 'alpha' | |
keyword = '' | |
def find_backend_file_paths(keyword, env): | |
""" | |
Returns backend file paths that match the given keyword and env | |
e.g. | |
keyword -> os | |
env -> alpha | |
will return all backend file paths that belong to 'alpha' dir and contain 'os' keyword | |
./environments/alpha/os.backend.tfvars -> will match | |
./environments/alpha/test.backend.tfvars -> will not match | |
./environments/prod/os.backend.tfvars -> will not match | |
If the keyword is empty then it matches all the files with the env dir | |
./environments/alpha/os.backend.tfvars -> will match | |
./environments/alpha/test.backend.tfvars -> will match | |
./environments/prod/os.backend.tfvars -> will not match | |
""" | |
all_backend_files = glob.glob('./**/*.backend.tfvars', recursive=True) | |
if not keyword: | |
matched_backend_files = all_backend_files | |
else: | |
matched_backend_files = list(filter(lambda path: (keyword in path), all_backend_files)) | |
return filter_file_paths_by_env(matched_backend_files, env) | |
def find_shared_tfvars_file_path(): | |
""" | |
Returns shared.tfvars file path | |
""" | |
return ' '.join(glob.glob('./**/shared.tfvars', recursive=True)) | |
def filter_file_paths_by_env(file_paths, env): | |
""" | |
Returns the input file paths for the given env | |
""" | |
return list(filter(lambda f: (env in f), file_paths)) | |
def map_backend_tfvars(backend_file_paths): | |
""" | |
Returns a map of backend file paths and their corresponding tfvars file path | |
e.g. ./environments/alpha/os.backend.tfvars -> ./environments/alpha/os.tfvars | |
""" | |
backend_tfvars_map = {} | |
for backend_file_path in backend_file_paths: | |
backend_tfvars_map[backend_file_path] = backend_file_path.replace(".backend", "") | |
return backend_tfvars_map | |
def clear_local_tf_state(): | |
""" | |
Removes terraform.tfstate since we are using different remote backend configurations for each app | |
""" | |
print('Clearing terraform.tfstate file') | |
command_call("rm .terraform/terraform.tfstate") | |
def clear_local_tf_folder(): | |
""" | |
Removes .terraform folder for re-initializing in case the region has changed | |
""" | |
print('Clearing .terraform folder') | |
command_call("rm -rf .terraform") | |
def determine_backend_region(backend_file_path): | |
""" | |
Determine backend aws region | |
""" | |
if 'us-west-2' in backend_file_path: | |
return 'us-west-2' | |
else: | |
return 'us-east-1' | |
def clear_local_tf_cache(backend_file_path): | |
""" | |
Determines whether to clear local tf folder or the state file | |
We want to avoid downloading plugins for each init operation and instead aim to re-use the donwloaded modules/plugins if the execution is for a matching region | |
e.g. | |
If we have previously downloaded plugins/modules for us-east-1 region and the current execution is also for same region then clear only tfstate file | |
If we have previously downloaded plugins/modules for us-west-2 region and the current execution is also for a different region then clear tf folder for re-initialization | |
""" | |
tf_state_backend_region = 'us-east-1' | |
try: | |
with open('./.terraform/terraform.tfstate', 'r') as json_file: | |
data = json.load(json_file) | |
tf_state_backend_region = data['backend']['config']['region'] | |
except OSError as e: | |
print(e) | |
clear_local_tf_folder() | |
return None | |
backend_region = determine_backend_region(backend_file_path) | |
if tf_state_backend_region != backend_region: | |
clear_local_tf_folder() | |
else: | |
clear_local_tf_state() | |
def execute_tf_init(backend_file_path): | |
""" | |
Performs tf init for the given backend | |
""" | |
clear_local_tf_cache(backend_file_path) | |
print("Executing TF init for backend: " + backend_file_path) | |
command_call("terraform init -backend-config={path}".format(path = backend_file_path.lstrip("./"))) | |
def execute_tf_plan(shared_tfvars_path, tfvars_path): | |
""" | |
Performs tf plan for the given shared file and tfvars file | |
If the region is 'us-west-2' we then switch the shared file path to the region specific one e.g. shared-us-west-2.tfvars instead of shared.tfvars | |
""" | |
formatted_shared_tfvars_path = shared_tfvars_path.lstrip("./") | |
formatted_tfvars_path = tfvars_path.lstrip("./") | |
file_name = Path(tfvars_path).stem + ".plan" | |
if 'us-west-2' in tfvars_path: | |
formatted_shared_tfvars_path = formatted_shared_tfvars_path.replace(".tfvars", "-us-west-2.tfvars") | |
print("Executing TF plan for [{}, {}]: ".format(formatted_shared_tfvars_path, formatted_tfvars_path)) | |
tf_plan_cmd = "terraform plan -var-file={shared_tfvars_path} -var-file={tfvars_path} -no-color > {file_name}".format(shared_tfvars_path = formatted_shared_tfvars_path, \ | |
tfvars_path = formatted_tfvars_path, file_name = file_name) | |
command_call(tf_plan_cmd) | |
def command_call(command): | |
""" | |
Executes command in shell mode | |
""" | |
try: | |
subprocess.check_call(command, shell=True, stdout=sys.stdout, stderr=subprocess.STDOUT) | |
except subprocess.CalledProcessError as e: | |
print(e.stderr) | |
if __name__ == '__main__': | |
backend_file_paths = find_backend_file_paths(keyword, env) | |
shared_tfvars_path = find_shared_tfvars_file_path() | |
backend_tfvars_dict = map_backend_tfvars(backend_file_paths) | |
for backend_file_path in backend_file_paths: | |
execute_tf_init(backend_file_path) | |
execute_tf_plan(shared_tfvars_path, backend_tfvars_dict[backend_file_path]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment