From 125b7719304a8b07b39131bc27b8217526ecae41 Mon Sep 17 00:00:00 2001 From: bmatei-visma Date: Fri, 29 Sep 2023 17:43:57 +0300 Subject: [PATCH 1/8] feat: commit work in progress version of old tf.sh --- new_tf | 539 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 539 insertions(+) create mode 100755 new_tf diff --git a/new_tf b/new_tf new file mode 100755 index 0000000..2c4e356 --- /dev/null +++ b/new_tf @@ -0,0 +1,539 @@ +#!/bin/bash + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +## utility +contains() { + if [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] + then + true + else + false + fi +} + +install_required() { + if [ "$TF_WRAPPER_DEBUG" == "true" ]; then + echo -e "${YELLOW}Running install_required${NC}" + fi + + if [ "$(which tfswitch)" == "" ]; then + echo "tfswitch is not installed...\nInstalling latest version of tfswitch" + curl -L https://raw.githubusercontent.com/warrensbox/terraform-switcher/release/install.sh -o $HOME/install.sh && chmod 755 $HOME/install.sh + $HOME/install.sh -b $HOME/bin && rm $HOME/bin/install.sh + echo "${GREEN}Tfswitch has been installed${NC}" + elif [ "$TF_WRAPPER_DEBUG" == "true" ]; then + echo -e "${GREEN}tfswitch is installed${NC}" + fi +} + +search_up() { + if [ "$TF_WRAPPER_DEBUG" == "true" ]; then + echo -e "${YELLOW}Running search_up${NC}" + fi + + local LOOK=${PWD%/} + while [[ -n $LOOK ]]; do + [[ -d $LOOK/.tf ]] && { + DIR="$LOOK" + return + } + LOOK=${LOOK%/*} + done + if [[ -d /.tf ]] + then + DIR=/ + fi +} + +terraform_version_select() { + if [ "$TF_WRAPPER_DEBUG" == "true" ]; then + echo -e "${YELLOW}Running terraform_version_select${NC}" + fi + # Define which terraform version to use + TF_VERSION_FILE=$DIR/stacks/$TERRAFORM_STACK/terraform.version + TF_VERSION_FILE_ROOT=$DIR/terraform.version + + #TF_VERSION_FILE needs to match the SEMVAR MAJOR.MINOR.PATCH + if [ -f "$TF_VERSION_FILE" ]; then + TF_VERSION=`cat $TF_VERSION_FILE` + elif [ -f "$TF_VERSION_FILE_ROOT" ]; then + TF_VERSION=`cat $TF_VERSION_FILE_ROOT` + else + (>&2 echo -e "${RED}No version of terraform has been specified. Create first a terraform.version file with a proper semver${NC}") + exit 1 + fi + + if ! [[ $TF_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "The version specified in the 'terraform.version', doesn't match semver MAJOR.MINOR.PATCH" + exit 1 + fi + + if [ "$(terraform version | head -n 1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" != "$TF_VERSION" ]; then + echo -e "${YELLOW}Terraform couldn't be found or it didn't match the $TF_VERSION${NC} version" + tfswitch "$TF_VERSION" + fi +} + +read_accounts() { + if [ "$TF_WRAPPER_DEBUG" == "true" ]; then + echo -e "${YELLOW}Running read_accounts${NC}" + fi + + ACCOUNTS_FILE=$DIR/accounts + if ! [[ -e $ACCOUNTS_FILE ]]; then + (>&2 echo -e "${RED}$ACCOUNTS_FILE file missing.${NC}") + exit 1 + fi + + while IFS=$'=' read -r -a anArray + do + accounts[${anArray[0]}]=`echo ${anArray[1]} | sed s/[^0-9]//` + done < $ACCOUNTS_FILE +} + +bootstrap() { + if [ "$TF_WRAPPER_DEBUG" == "true" ]; then + echo -e "${YELLOW}Running bootstrap${NC}" + fi + install_required + search_up + read_accounts + + # Setup variables used throughout the script + WORKSPACE_FILE=$DIR/.tf/.workspace + STACK_FILE=$DIR/.tf/.stack + VALID_STACKS=$(find $DIR/stacks -maxdepth 1 -mindepth 1 -type d -printf "%f " | tr ' ' '\n' | sort | tr '\n' ' ') + + # Local configuration file, for some api keys + if [ -f "$DIR/terraform.tfvars" ]; then + EXTRA_VAR_FILE=-var-file=../../terraform.tfvars + fi + + terraform_version_select +} + +context() { + if [ ! -e $WORKSPACE_FILE ] || [ ! -e $STACK_FILE ]; then + echo -e "${RED}Workspace or stack file doesn't exist!${NC}" + exit 1 + fi + + export TERRAFORM_WORKSPACE=$(cat $WORKSPACE_FILE) + export TERRAFORM_STACK=$(cat $STACK_FILE) + export AWS_PROFILE=$TERRAFORM_WORKSPACE + + if [ -z "$TERRAFORM_WORKSPACE" ] || [ -z "$TERRAFORM_STACK" ]; then + echo -e "${RED}Missing value from workspace or stack file!${NC}" + exit 1 + fi + + echo -e "Current stack: ${GREEN}$TERRAFORM_STACK${NC}" + echo -e "Current workspace: ${GREEN}$TERRAFORM_WORKSPACE${NC}" +} + +## workspace + +workspace() { + WORKSPACE_SUBCOMMAND=$1 + shift + case $WORKSPACE_SUBCOMMAND in + select) + workspace_select "$@" + ;; + list) + echo -e "Currently using workspace: [${GREEN}$(cat $WORKSPACE_FILE)${NC}]" + echo -e "Available workspaces are: [${YELLOW}${!accounts[@]}${NC}]" + ;; + help) + echo -e "Usage: ${YELLOW}$TF_BIN_NAME workspace ${NC} +Available subcommands: + +select Selects the working workspace +list List the selected workspace and available workspaces +help Prints available workspace subcommands + +${YELLOW}To initialize an already selected workspace use '$TF_BIN_NAME init'${NC}" + ;; + *) + echo -e "The command ${RED}$WORKSPACE_SUBCOMMAND${NC} is not available.\nUse '${YELLOW}$TF_BIN_NAME workspace help${NC}' to see the available command" + ;; + esac + return $? +} + +workspace_setup() { + echo $TERRAFORM_STACK + CURRENT_WORKSPACE=`terraform workspace show` + if [ $CURRENT_WORKSPACE != $TERRAFORM_WORKSPACE ] + then + echo -e "${GREEN}Workspace switched to $TERRAFORM_WORKSPACE${NC}" + workspace_init + fi +} + +workspace_init() { + stack_verify + + if [ -z $TF_USE_CURRENT_PROFILE ] + then + export AWS_PROFILE=$TERRAFORM_WORKSPACE + fi + STACK_DIR=$DIR/stacks/$TERRAFORM_STACK + + rm -rf $STACK_DIR/.terraform $STACK_DIR/terraform.tfstate.d $STACK_DIR/.terraform.lock.hcl + BACKEND_BUCKET="terraform-state-${accounts[${TERRAFORM_WORKSPACE}]}" + STATE_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/terraform-state\`].{keyid:TargetKeyId}" --output text) + + terraform -chdir=$STACK_DIR init -backend-config="bucket=${BACKEND_BUCKET}" -backend-config="key=stacks/$TERRAFORM_STACK" -backend-config="encrypt=true" -backend-config="kms_key_id=${STATE_KEY_ID}" + set +e + terraform -chdir=$STACK_DIR workspace select $TERRAFORM_WORKSPACE + if [ $? != 0 ] + then + terraform -chdir=$STACK_DIR workspace new $TERRAFORM_WORKSPACE + terraform -chdir=$STACK_DIR workspace select $TERRAFORM_WORKSPACE + fi + set -e +} + +workspace_verify() { + if [ -n "$TERRAFORM_WORKSPACE" ]; then + if [ ${accounts[$TERRAFORM_WORKSPACE]+abc} ]; then + echo -e "${GREEN}Current workspace: $TERRAFORM_WORKSPACE (from env)${NC}" + workspace_setup + else + (>&2 echo -e "${RED}Invalid workspace '$TERRAFORM_WORKSPACE'. Valid workspaces: [${!accounts[@]}].${NC}") + return 1 + fi + elif [ -e $WORKSPACE_FILE ]; then + WORKSPACE=`cat $WORKSPACE_FILE` + if [ ${accounts[$WORKSPACE]+abc} ]; then + export TERRAFORM_WORKSPACE=$WORKSPACE + echo -e "${GREEN}Current workspace: $TERRAFORM_WORKSPACE${NC}" + workspace_init + else + (>&2 echo -e "${RED}No current workspace. Usage: $TF_BIN_NAME workspace [${!accounts[@]}].${NC}") + return 1 + fi + else + (>&2 echo -e "${RED}No current workspace. Usage: $$TF_BIN_NAME workspace [${!accounts[@]}].${NC}") + return 1 + fi + return 0 +} + +workspace_select() { + if [ -n "$1" ] && [ ${accounts[$1]+abc} ] + then + export TERRAFORM_WORKSPACE=$1 + echo $TERRAFORM_WORKSPACE > $WORKSPACE_FILE + echo -e "${YELLOW}Switching workspace to $TERRAFORM_WORKSPACE${NC}" + workspace_setup + else + (>&2 echo -e "${RED}workspace '$1' is invalid. Usage: $TF_BIN_NAME workspace accounts[@]}].${NC}") + return 1 + fi + return 0 +} + +## stack + +stack() { + STACK_SUBCOMMAND=$1 + shift + case $STACK_SUBCOMMAND in + select) + stack_select "$@" + ;; + list) + echo -e "Currently using stack: [${GREEN}$(cat $STACK_FILE)${NC}]" + echo -e "Available stacks are: [${YELLOW}$VALID_STACKS${NC}]" + ;; + help) + echo -e "Usage: ${YELLOW}$TF_BIN_NAME stack ${NC} +Available subcommands: + +select Selects the working stack +list List the selected stack and available stacks +help Prints available stack subcommands" + ;; + *) + echo -e "The command ${RED}$STACK_SUBCOMMAND${NC} is not available.\nUse '${YELLOW}$TF_BIN_NAME stack help${NC}' to see the available command" + ;; + esac + return $? +} + +stack_verify() { + if [ -n "$TERRAFORM_STACK" ]; then + if contains "$VALID_STACKS" $TERRAFORM_STACK + then + echo -e "${GREEN}Current stack: $TERRAFORM_STACK (from env)${NC}" + else + (>&2 echo -e "${RED}Invalid stack '$TERRAFORM_STACK' (from env). Valid stacks: $VALID_STACKS.${NC}") + return 1 + fi + elif [ -e $STACK_FILE ]; then + STACK=`cat $STACK_FILE` + if contains "$VALID_STACKS" $STACK + then + export TERRAFORM_STACK=$STACK + echo -e "${GREEN}Current stack: $TERRAFORM_STACK${NC}" + + stack_global=$DIR/stacks/$TERRAFORM_STACK/global.symlink.tf + stack_backend=$DIR/stacks/$TERRAFORM_STACK/backend.symlink.tf + if [ -f $stack_global ]; then + rm $stack_global + fi + + if [ ! -h $stack_global ]; then + ln -s $DIR/global.tf $stack_global + fi + + if [ -f $stack_backend ]; then + rm $stack_backend + fi + + if [ ! -h $stack_backend ]; then + ln -s $DIR/backend.tf $stack_backend + fi + else + (>&2 echo -e "${RED}No current stack. Use: $TF_BIN_NAME stack [stack name]${NC}") + fi + else + (>&2 echo -e "${RED}No current stack. Usage: $TF_BIN_NAME stack [$VALID_STACKS]${NC}") + return 1 + fi + + return $? +} + +stack_select() { + if contains "$VALID_STACKS" $1 + then + echo -e "${YELLOW}Switching stack to $1.${NC}" + export TERRAFORM_STACK=$1 + echo $TERRAFORM_STACK > $STACK_FILE + echo -e "${GREEN}Stack switched to $TERRAFORM_STACK.${NC}" + else + (>&2 echo -e "${RED}stack '$1' is invalid. Usage: $TF_BIN_NAME stack [$VALID_STACKS]${NC}") + return 1 + fi + + terraform_version_select + return $? +} + +## backend +backend() { + if [ ! -e $WORKSPACE_FILE ]; then + echo -e "${RED}Workspace file doesn't exist!${NC}" + return 1 + fi + + TERRAFORM_WORKSPACE=$(cat $WORKSPACE_FILE) + export AWS_PROFILE=$TERRAFORM_WORKSPACE + + echo -e "Using directory: ${GREEN}state-management${NC}" + echo -e "Current workspace: ${GREEN}$TERRAFORM_WORKSPACE${NC}" + backend=$DIR/state-management/backend.symlink.tf + if [ -f $backend ]; then + rm $backend + fi + + if [ ! -h $backend ]; then + ln -s $DIR/backend.tf $backend + fi + + BACKEND_BUCKET="terraform-state-${accounts[${TERRAFORM_WORKSPACE}]}" + STATE_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/terraform-state\`].{keyid:TargetKeyId}" --output text) + if [ "$1" == "init" ]; then + rm -rf .terraform terraform.tfstate.d .terraform.lock.hcl + terraform -chdir=$DIR/state-management $1 ${@:2} -backend-config="bucket=${BACKEND_BUCKET}" -backend-config="key=backend/terraform.tfstate" -backend-config="encrypt=true" -backend-config="kms_key_id=${STATE_KEY_ID}" + else + terraform -chdir=$DIR/state-management "$@" + fi + return $? +} + +## dependencies +dependencies() { + context + + ADD_STATUS=d + if [ "x$1" = "xstatus" ] + then + ADD_STATUS=1 + fi + # check dependencies between modules + echo -e "digraph { + compound = \"true\" + newrank = \"true\" + node[style=filled]\n" + for stack in $VALID_STACKS + do + ATTRIBUTES= + if [ "$ADD_STATUS" = "1" ] + then + COLOR=red + set +e + result=$(export TERRAFORM_STACK=$stack; $0 plan -detailed-exitcode 2> /dev/null) + EXIT_CODE=$? + set -e + if [[ $EXIT_CODE == 2 ]] + then + COLOR=yellow + elif [[ $EXIT_CODE == 0 ]] + then + COLOR=green + fi + if [[ -n "$ATTRIBUTES" ]] + then + ATTRIBUTES=${ATTRIBUTES}, + fi + ATTRIBUTES=${ATTRIBUTES}fillcolor=${COLOR} + fi + echo -e \"$stack\"[$ATTRIBUTES] + done + grep \\\"terraform_remote_state $DIR/stacks/*/*.tf | sed -e 's/.*\/stacks\///; s/\/.*terraform_remote_state//; s/["{]//g; s/ /" -> "/; s/\s\+$/"/; s/^/"/' + + echo -e "}" +} + +## chamber +chamber() { + export AWS_REGION=eu-west-1 + COMMAND=$1 + case $COMMAND in + exec|help|history|list) + chamber "$@" + return $? + ;; + read|write) + shift + SERVICE=$1 + shift + export CHAMBER_KMS_KEY_ALIAS=alias/$SERVICE-configuration-secrets + chamber $COMMAND $SERVICE "$@" + return $? + ;; + esac + return 1 +} + +## conf +conf() { + context + + PREFIX=$1 + shift + COMMAND=$1 + case $COMMAND in + diff) + shift + FILE=$1 + shift + if [ -z $FILE ] + then + (>&2 echo -e "${RED}Specify a file name${NC}") + exit 1 + fi + aws s3 cp s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/$FILE - | diff $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/$FILE "$@" -- - + ;; + cp) + shift + CONF_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/$PREFIX-configuration\`].{keyid:TargetKeyId}" --output text) + aws s3 cp --sse "aws:kms" --sse-kms-key-id "${CONF_KEY_ID}" $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/ s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ --recursive "$@" + ;; + sync) + shift + CONF_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/$PREFIX-configuration\`].{keyid:TargetKeyId}" --output text) + aws s3 sync --sse "aws:kms" --sse-kms-key-id "${CONF_KEY_ID}" --delete $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/ s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ + ;; + *) + echo "Files on S3:" + aws s3 ls --recursive s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ + echo "Sync (dryrun):" + aws s3 sync --delete --dryrun $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/ s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ + ;; + esac + return $? +} + +## completion + +## main +main() { + bootstrap + + TF_COMMAND=$1 + shift + case $TF_COMMAND in + deps) + dependencies "$@" + ;; + conf) + conf "$@" + ;; + chamber) + chamber "$@" + ;; + init) + workspace_verify + ;; + backend) + backend "$@" + ;; + apply) + context + export TF_CLI_ARGS="-var-file=$DIR/envvars/${TERRAFORM_WORKSPACE}.tfvars -var-file=$DIR/global.tfvars $EXTRA_VAR_FILE -input=false -auto-approve=false" + terraform -chdir=$DIR/stacks/$TERRAFORM_STACK $TF_COMMAND "$@" + ;; + plan|destroy|import|refresh|console) + context + export TF_CLI_ARGS="-var-file=$DIR/envvars/${TERRAFORM_WORKSPACE}.tfvars -var-file=$DIR/global.tfvars $EXTRA_VAR_FILE -input=false" + terraform -chdir=$DIR/stacks/$TERRAFORM_STACK $TF_COMMAND "$@" + ;; + get|validate|state|graph|fmt|show|taint|untaint|version|output|force-unlock|metadata|login|logout|providers) + context + terraform -chdir=$DIR/stacks/$TERRAFORM_STACK $TF_COMMAND "$@" + ;; + workspace) + workspace "$@" + ;; + stack) + stack "$@" + ;; + help) + echo -e "${YELLOW}Available $TF_BIN_NAME commands\n$TF_BIN_NAME has priority over terraform${NC}" + echo " + deps Displays dependencies between stacks + conf Interact with S3 configuration buckets + chamber Run chamber commands + init Forcefully initialize a workspace + backend Run normal terraform commands on the terraform state bucket + stack Manage stacks for the selected workspace\n" + echo -e "\n${YELLOW}Available terraform commands${NC}\n" + terraform -help + ;; + *) + (>&2 echo -e "${RED}Command '$TF_COMMAND' is not supported. Use '$TF_BIN_NAME help' for more information${NC}") + echo "${YELLOW}$TF_BIN_NAME is compatible with terrafom version ~>1.5. If you require a command that is above this version, please raise an issue at https://github.com/vismaosscomponents/terraform-wrapper${NC}" + ;; + esac + + exit $? +} + +## Running script +# Get binary name for output purposes +TF_BIN_NAME=$(echo $0 | rev | cut -d '/' -f1 | rev) +# globally scoped +declare -A accounts +main "$@" +## End of running script From d11cd83ba6ced366bdd46164b8ed0f5c22e7e2c3 Mon Sep 17 00:00:00 2001 From: bmatei-visma Date: Mon, 2 Oct 2023 14:38:04 +0300 Subject: [PATCH 2/8] feat: add new commands, refactor for usability and readability --- README.md | 4 + new_tf | 224 +++++++++++++++++++++++++++++++----------------------- 2 files changed, 133 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index bc0f9a4..8b188bf 100644 --- a/README.md +++ b/README.md @@ -163,3 +163,7 @@ This file should be excluded from source control. It will be loaded during ``pla 1. Initialize a project. 1. Use symlinks if supported instead of copying ``global.tf`` as ``global.symlink.tf`` 1. ? Find current stack from current directory, to be able to use ``cd stacks/xxx`` instead of ``tf stack xxx`` + +# Limitations + +* the workspace is tied with the AWS account. Can't have multiple workspaces under the same AWS account \ No newline at end of file diff --git a/new_tf b/new_tf index 2c4e356..48c39ea 100755 --- a/new_tf +++ b/new_tf @@ -17,6 +17,55 @@ contains() { fi } +context_stack() { + if [ ! -e $STACK_FILE ]; then + echo -e "${RED}Stack file doesn't exist!${NC}" + return 1 + fi + + export TERRAFORM_STACK=$(cat $STACK_FILE) + + if [ -z "$TERRAFORM_STACK" ]; then + echo -e "${RED}Missing value from stack file!${NC}" + return 1 + fi + + return 0 +} + +context_workspace() { + if [ ! -e $WORKSPACE_FILE ]; then + echo -e "${RED}Workspace file doesn't exist!${NC}" + return 1 + fi + + export TERRAFORM_WORKSPACE=$(cat $WORKSPACE_FILE) + export AWS_PROFILE=$TERRAFORM_WORKSPACE + if [ -z "$TERRAFORM_WORKSPACE" ]; then + echo -e "${RED}Missing value from workspace file!${NC}" + return 1 + fi + + return 0 +} + +context() { + context_stack + if [ $? -eq 1 ]; then + exit 1 + fi + context_workspace + if [ $? -eq 1 ]; then + exit 1 + fi + + if [ $1 ] || [ "$TF_WRAPPER_DEBUG" == "true" ] + then + echo -e "Using stack: ${GREEN}${TERRAFORM_STACK}${NC}" + echo -e "Using workspace: ${GREEN}${TERRAFORM_WORKSPACE}${NC}" + fi +} + install_required() { if [ "$TF_WRAPPER_DEBUG" == "true" ]; then echo -e "${YELLOW}Running install_required${NC}" @@ -55,7 +104,9 @@ terraform_version_select() { if [ "$TF_WRAPPER_DEBUG" == "true" ]; then echo -e "${YELLOW}Running terraform_version_select${NC}" fi + # Define which terraform version to use + context_stack TF_VERSION_FILE=$DIR/stacks/$TERRAFORM_STACK/terraform.version TF_VERSION_FILE_ROOT=$DIR/terraform.version @@ -99,6 +150,7 @@ read_accounts() { bootstrap() { if [ "$TF_WRAPPER_DEBUG" == "true" ]; then + echo -e "Running in ${YELLOW}debug${NC} mode" echo -e "${YELLOW}Running bootstrap${NC}" fi install_required @@ -118,25 +170,6 @@ bootstrap() { terraform_version_select } -context() { - if [ ! -e $WORKSPACE_FILE ] || [ ! -e $STACK_FILE ]; then - echo -e "${RED}Workspace or stack file doesn't exist!${NC}" - exit 1 - fi - - export TERRAFORM_WORKSPACE=$(cat $WORKSPACE_FILE) - export TERRAFORM_STACK=$(cat $STACK_FILE) - export AWS_PROFILE=$TERRAFORM_WORKSPACE - - if [ -z "$TERRAFORM_WORKSPACE" ] || [ -z "$TERRAFORM_STACK" ]; then - echo -e "${RED}Missing value from workspace or stack file!${NC}" - exit 1 - fi - - echo -e "Current stack: ${GREEN}$TERRAFORM_STACK${NC}" - echo -e "Current workspace: ${GREEN}$TERRAFORM_WORKSPACE${NC}" -} - ## workspace workspace() { @@ -147,7 +180,8 @@ workspace() { workspace_select "$@" ;; list) - echo -e "Currently using workspace: [${GREEN}$(cat $WORKSPACE_FILE)${NC}]" + context_workspace + echo -e "Currently using workspace: [${GREEN}$TERRAFORM_WORKSPACE${NC}]" echo -e "Available workspaces are: [${YELLOW}${!accounts[@]}${NC}]" ;; help) @@ -167,18 +201,11 @@ ${YELLOW}To initialize an already selected workspace use '$TF_BIN_NAME init'${NC return $? } -workspace_setup() { - echo $TERRAFORM_STACK - CURRENT_WORKSPACE=`terraform workspace show` - if [ $CURRENT_WORKSPACE != $TERRAFORM_WORKSPACE ] - then - echo -e "${GREEN}Workspace switched to $TERRAFORM_WORKSPACE${NC}" - workspace_init - fi -} - workspace_init() { - stack_verify + if (! stack_verify) then + echo -e "${RED}An error occured while verifying the stack${NC}" + return 1 + fi if [ -z $TF_USE_CURRENT_PROFILE ] then @@ -199,30 +226,30 @@ workspace_init() { terraform -chdir=$STACK_DIR workspace select $TERRAFORM_WORKSPACE fi set -e + return $? } workspace_verify() { + workspace_valid() { + if [ ${accounts[$TERRAFORM_WORKSPACE]+abc} ]; then + echo -e "Current workspace: ${GREEN}$TERRAFORM_WORKSPACE${NC}" + workspace_init + else + (>&2 echo -e "${RED}Invalid workspace '$TERRAFORM_WORKSPACE'.\n$TF_BIN_NAME workspace select \nValid workspaces: [${!accounts[@]}].${NC}") + return 1 + fi + } + + # Keeping this if for automation purposes via environment variables if [ -n "$TERRAFORM_WORKSPACE" ]; then - if [ ${accounts[$TERRAFORM_WORKSPACE]+abc} ]; then - echo -e "${GREEN}Current workspace: $TERRAFORM_WORKSPACE (from env)${NC}" - workspace_setup - else - (>&2 echo -e "${RED}Invalid workspace '$TERRAFORM_WORKSPACE'. Valid workspaces: [${!accounts[@]}].${NC}") + if (! workspace_valid "env") then return 1 fi - elif [ -e $WORKSPACE_FILE ]; then - WORKSPACE=`cat $WORKSPACE_FILE` - if [ ${accounts[$WORKSPACE]+abc} ]; then - export TERRAFORM_WORKSPACE=$WORKSPACE - echo -e "${GREEN}Current workspace: $TERRAFORM_WORKSPACE${NC}" - workspace_init - else - (>&2 echo -e "${RED}No current workspace. Usage: $TF_BIN_NAME workspace [${!accounts[@]}].${NC}") + else + context_workspace + if (! workspace_valid ".tf/.workspace") then return 1 fi - else - (>&2 echo -e "${RED}No current workspace. Usage: $$TF_BIN_NAME workspace [${!accounts[@]}].${NC}") - return 1 fi return 0 } @@ -230,10 +257,15 @@ workspace_verify() { workspace_select() { if [ -n "$1" ] && [ ${accounts[$1]+abc} ] then - export TERRAFORM_WORKSPACE=$1 - echo $TERRAFORM_WORKSPACE > $WORKSPACE_FILE - echo -e "${YELLOW}Switching workspace to $TERRAFORM_WORKSPACE${NC}" - workspace_setup + context_workspace + if [ "$1" != "$TERRAFORM_WORKSPACE" ]; then + export TERRAFORM_WORKSPACE=$1 + echo $TERRAFORM_WORKSPACE > $WORKSPACE_FILE + echo -e "${YELLOW}Switching workspace to $TERRAFORM_WORKSPACE${NC}" + workspace_verify + else + echo -e "Already on workspace ${GREEN}$TERRAFORM_WORKSPACE${NC}" + fi else (>&2 echo -e "${RED}workspace '$1' is invalid. Usage: $TF_BIN_NAME workspace accounts[@]}].${NC}") return 1 @@ -251,7 +283,8 @@ stack() { stack_select "$@" ;; list) - echo -e "Currently using stack: [${GREEN}$(cat $STACK_FILE)${NC}]" + context_stack + echo -e "Currently using stack: [${GREEN}$TERRAFORM_STACK${NC}]" echo -e "Available stacks are: [${YELLOW}$VALID_STACKS${NC}]" ;; help) @@ -270,46 +303,47 @@ help Prints available stack subcommands" } stack_verify() { - if [ -n "$TERRAFORM_STACK" ]; then + stack_valid(){ if contains "$VALID_STACKS" $TERRAFORM_STACK then - echo -e "${GREEN}Current stack: $TERRAFORM_STACK (from env)${NC}" + echo -e "Current stack: ${GREEN}$TERRAFORM_STACK${NC}" else - (>&2 echo -e "${RED}Invalid stack '$TERRAFORM_STACK' (from env). Valid stacks: $VALID_STACKS.${NC}") + (>&2 echo -e "${RED}Invalid stack '$TERRAFORM_STACK'.\n$TF_BIN_NAME stack select [$VALID_STACKS]${NC}") return 1 fi - elif [ -e $STACK_FILE ]; then - STACK=`cat $STACK_FILE` - if contains "$VALID_STACKS" $STACK - then - export TERRAFORM_STACK=$STACK - echo -e "${GREEN}Current stack: $TERRAFORM_STACK${NC}" - - stack_global=$DIR/stacks/$TERRAFORM_STACK/global.symlink.tf - stack_backend=$DIR/stacks/$TERRAFORM_STACK/backend.symlink.tf - if [ -f $stack_global ]; then - rm $stack_global - fi - - if [ ! -h $stack_global ]; then - ln -s $DIR/global.tf $stack_global - fi - - if [ -f $stack_backend ]; then - rm $stack_backend - fi + return 0 + } - if [ ! -h $stack_backend ]; then - ln -s $DIR/backend.tf $stack_backend - fi - else - (>&2 echo -e "${RED}No current stack. Use: $TF_BIN_NAME stack [stack name]${NC}") + # Keeping this if for automation purposes via environment variables + if [ -n "$TERRAFORM_STACK" ]; then + if (! stack_valid "env") then + return 1 fi else - (>&2 echo -e "${RED}No current stack. Usage: $TF_BIN_NAME stack [$VALID_STACKS]${NC}") - return 1 + context_stack + if (! stack_valid ".tf/.stack") then + return 1 + fi fi + + stack_global=$DIR/stacks/$TERRAFORM_STACK/global.symlink.tf + stack_backend=$DIR/stacks/$TERRAFORM_STACK/backend.symlink.tf + if [ -f $stack_global ]; then + rm $stack_global + fi + + if [ ! -h $stack_global ]; then + ln -s $DIR/global.tf $stack_global + fi + + if [ -f $stack_backend ]; then + rm $stack_backend + fi + + if [ ! -h $stack_backend ]; then + ln -s $DIR/backend.tf $stack_backend + fi return $? } @@ -331,13 +365,12 @@ stack_select() { ## backend backend() { - if [ ! -e $WORKSPACE_FILE ]; then - echo -e "${RED}Workspace file doesn't exist!${NC}" + if [ ${#@} -lt 1 ]; then + echo -e "${RED}No command has been provided for backend.\nUsual terraform commands can be used.${NC}" + echo -e "${YELLOW}Usage: $TF_BIN_NAME backend ${NC}" return 1 fi - - TERRAFORM_WORKSPACE=$(cat $WORKSPACE_FILE) - export AWS_PROFILE=$TERRAFORM_WORKSPACE + context_workspace echo -e "Using directory: ${GREEN}state-management${NC}" echo -e "Current workspace: ${GREEN}$TERRAFORM_WORKSPACE${NC}" @@ -363,8 +396,6 @@ backend() { ## dependencies dependencies() { - context - ADD_STATUS=d if [ "x$1" = "xstatus" ] then @@ -428,8 +459,6 @@ chamber() { ## conf conf() { - context - PREFIX=$1 shift COMMAND=$1 @@ -465,8 +494,6 @@ conf() { return $? } -## completion - ## main main() { bootstrap @@ -475,9 +502,11 @@ main() { shift case $TF_COMMAND in deps) + context dependencies "$@" ;; conf) + context conf "$@" ;; chamber) @@ -509,6 +538,9 @@ main() { stack) stack "$@" ;; + ctx) + context true + ;; help) echo -e "${YELLOW}Available $TF_BIN_NAME commands\n$TF_BIN_NAME has priority over terraform${NC}" echo " @@ -516,14 +548,16 @@ main() { conf Interact with S3 configuration buckets chamber Run chamber commands init Forcefully initialize a workspace + ctx Show selected stack and workspace + workspace Wraps workspaces around 'accounts' file backend Run normal terraform commands on the terraform state bucket - stack Manage stacks for the selected workspace\n" + stack Manage stacks for the selected workspace" echo -e "\n${YELLOW}Available terraform commands${NC}\n" terraform -help ;; *) (>&2 echo -e "${RED}Command '$TF_COMMAND' is not supported. Use '$TF_BIN_NAME help' for more information${NC}") - echo "${YELLOW}$TF_BIN_NAME is compatible with terrafom version ~>1.5. If you require a command that is above this version, please raise an issue at https://github.com/vismaosscomponents/terraform-wrapper${NC}" + echo -e "${YELLOW}$TF_BIN_NAME is compatible with terrafom version ~>1.5. If you require a command that is above this version, please raise an issue at https://github.com/vismaosscomponents/terraform-wrapper${NC}" ;; esac From 0c1fa864853eacbc18f14db07692e7e94d2ca27a Mon Sep 17 00:00:00 2001 From: bmatei-visma Date: Mon, 2 Oct 2023 14:38:55 +0300 Subject: [PATCH 3/8] chore: update contents of tf.sh with new_tf.sh --- new_tf | 573 ------------------------------------- tf.sh | 872 +++++++++++++++++++++++++++++++++------------------------ 2 files changed, 499 insertions(+), 946 deletions(-) delete mode 100755 new_tf diff --git a/new_tf b/new_tf deleted file mode 100755 index 48c39ea..0000000 --- a/new_tf +++ /dev/null @@ -1,573 +0,0 @@ -#!/bin/bash - -set -e - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -## utility -contains() { - if [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] - then - true - else - false - fi -} - -context_stack() { - if [ ! -e $STACK_FILE ]; then - echo -e "${RED}Stack file doesn't exist!${NC}" - return 1 - fi - - export TERRAFORM_STACK=$(cat $STACK_FILE) - - if [ -z "$TERRAFORM_STACK" ]; then - echo -e "${RED}Missing value from stack file!${NC}" - return 1 - fi - - return 0 -} - -context_workspace() { - if [ ! -e $WORKSPACE_FILE ]; then - echo -e "${RED}Workspace file doesn't exist!${NC}" - return 1 - fi - - export TERRAFORM_WORKSPACE=$(cat $WORKSPACE_FILE) - export AWS_PROFILE=$TERRAFORM_WORKSPACE - if [ -z "$TERRAFORM_WORKSPACE" ]; then - echo -e "${RED}Missing value from workspace file!${NC}" - return 1 - fi - - return 0 -} - -context() { - context_stack - if [ $? -eq 1 ]; then - exit 1 - fi - context_workspace - if [ $? -eq 1 ]; then - exit 1 - fi - - if [ $1 ] || [ "$TF_WRAPPER_DEBUG" == "true" ] - then - echo -e "Using stack: ${GREEN}${TERRAFORM_STACK}${NC}" - echo -e "Using workspace: ${GREEN}${TERRAFORM_WORKSPACE}${NC}" - fi -} - -install_required() { - if [ "$TF_WRAPPER_DEBUG" == "true" ]; then - echo -e "${YELLOW}Running install_required${NC}" - fi - - if [ "$(which tfswitch)" == "" ]; then - echo "tfswitch is not installed...\nInstalling latest version of tfswitch" - curl -L https://raw.githubusercontent.com/warrensbox/terraform-switcher/release/install.sh -o $HOME/install.sh && chmod 755 $HOME/install.sh - $HOME/install.sh -b $HOME/bin && rm $HOME/bin/install.sh - echo "${GREEN}Tfswitch has been installed${NC}" - elif [ "$TF_WRAPPER_DEBUG" == "true" ]; then - echo -e "${GREEN}tfswitch is installed${NC}" - fi -} - -search_up() { - if [ "$TF_WRAPPER_DEBUG" == "true" ]; then - echo -e "${YELLOW}Running search_up${NC}" - fi - - local LOOK=${PWD%/} - while [[ -n $LOOK ]]; do - [[ -d $LOOK/.tf ]] && { - DIR="$LOOK" - return - } - LOOK=${LOOK%/*} - done - if [[ -d /.tf ]] - then - DIR=/ - fi -} - -terraform_version_select() { - if [ "$TF_WRAPPER_DEBUG" == "true" ]; then - echo -e "${YELLOW}Running terraform_version_select${NC}" - fi - - # Define which terraform version to use - context_stack - TF_VERSION_FILE=$DIR/stacks/$TERRAFORM_STACK/terraform.version - TF_VERSION_FILE_ROOT=$DIR/terraform.version - - #TF_VERSION_FILE needs to match the SEMVAR MAJOR.MINOR.PATCH - if [ -f "$TF_VERSION_FILE" ]; then - TF_VERSION=`cat $TF_VERSION_FILE` - elif [ -f "$TF_VERSION_FILE_ROOT" ]; then - TF_VERSION=`cat $TF_VERSION_FILE_ROOT` - else - (>&2 echo -e "${RED}No version of terraform has been specified. Create first a terraform.version file with a proper semver${NC}") - exit 1 - fi - - if ! [[ $TF_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "The version specified in the 'terraform.version', doesn't match semver MAJOR.MINOR.PATCH" - exit 1 - fi - - if [ "$(terraform version | head -n 1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" != "$TF_VERSION" ]; then - echo -e "${YELLOW}Terraform couldn't be found or it didn't match the $TF_VERSION${NC} version" - tfswitch "$TF_VERSION" - fi -} - -read_accounts() { - if [ "$TF_WRAPPER_DEBUG" == "true" ]; then - echo -e "${YELLOW}Running read_accounts${NC}" - fi - - ACCOUNTS_FILE=$DIR/accounts - if ! [[ -e $ACCOUNTS_FILE ]]; then - (>&2 echo -e "${RED}$ACCOUNTS_FILE file missing.${NC}") - exit 1 - fi - - while IFS=$'=' read -r -a anArray - do - accounts[${anArray[0]}]=`echo ${anArray[1]} | sed s/[^0-9]//` - done < $ACCOUNTS_FILE -} - -bootstrap() { - if [ "$TF_WRAPPER_DEBUG" == "true" ]; then - echo -e "Running in ${YELLOW}debug${NC} mode" - echo -e "${YELLOW}Running bootstrap${NC}" - fi - install_required - search_up - read_accounts - - # Setup variables used throughout the script - WORKSPACE_FILE=$DIR/.tf/.workspace - STACK_FILE=$DIR/.tf/.stack - VALID_STACKS=$(find $DIR/stacks -maxdepth 1 -mindepth 1 -type d -printf "%f " | tr ' ' '\n' | sort | tr '\n' ' ') - - # Local configuration file, for some api keys - if [ -f "$DIR/terraform.tfvars" ]; then - EXTRA_VAR_FILE=-var-file=../../terraform.tfvars - fi - - terraform_version_select -} - -## workspace - -workspace() { - WORKSPACE_SUBCOMMAND=$1 - shift - case $WORKSPACE_SUBCOMMAND in - select) - workspace_select "$@" - ;; - list) - context_workspace - echo -e "Currently using workspace: [${GREEN}$TERRAFORM_WORKSPACE${NC}]" - echo -e "Available workspaces are: [${YELLOW}${!accounts[@]}${NC}]" - ;; - help) - echo -e "Usage: ${YELLOW}$TF_BIN_NAME workspace ${NC} -Available subcommands: - -select Selects the working workspace -list List the selected workspace and available workspaces -help Prints available workspace subcommands - -${YELLOW}To initialize an already selected workspace use '$TF_BIN_NAME init'${NC}" - ;; - *) - echo -e "The command ${RED}$WORKSPACE_SUBCOMMAND${NC} is not available.\nUse '${YELLOW}$TF_BIN_NAME workspace help${NC}' to see the available command" - ;; - esac - return $? -} - -workspace_init() { - if (! stack_verify) then - echo -e "${RED}An error occured while verifying the stack${NC}" - return 1 - fi - - if [ -z $TF_USE_CURRENT_PROFILE ] - then - export AWS_PROFILE=$TERRAFORM_WORKSPACE - fi - STACK_DIR=$DIR/stacks/$TERRAFORM_STACK - - rm -rf $STACK_DIR/.terraform $STACK_DIR/terraform.tfstate.d $STACK_DIR/.terraform.lock.hcl - BACKEND_BUCKET="terraform-state-${accounts[${TERRAFORM_WORKSPACE}]}" - STATE_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/terraform-state\`].{keyid:TargetKeyId}" --output text) - - terraform -chdir=$STACK_DIR init -backend-config="bucket=${BACKEND_BUCKET}" -backend-config="key=stacks/$TERRAFORM_STACK" -backend-config="encrypt=true" -backend-config="kms_key_id=${STATE_KEY_ID}" - set +e - terraform -chdir=$STACK_DIR workspace select $TERRAFORM_WORKSPACE - if [ $? != 0 ] - then - terraform -chdir=$STACK_DIR workspace new $TERRAFORM_WORKSPACE - terraform -chdir=$STACK_DIR workspace select $TERRAFORM_WORKSPACE - fi - set -e - return $? -} - -workspace_verify() { - workspace_valid() { - if [ ${accounts[$TERRAFORM_WORKSPACE]+abc} ]; then - echo -e "Current workspace: ${GREEN}$TERRAFORM_WORKSPACE${NC}" - workspace_init - else - (>&2 echo -e "${RED}Invalid workspace '$TERRAFORM_WORKSPACE'.\n$TF_BIN_NAME workspace select \nValid workspaces: [${!accounts[@]}].${NC}") - return 1 - fi - } - - # Keeping this if for automation purposes via environment variables - if [ -n "$TERRAFORM_WORKSPACE" ]; then - if (! workspace_valid "env") then - return 1 - fi - else - context_workspace - if (! workspace_valid ".tf/.workspace") then - return 1 - fi - fi - return 0 -} - -workspace_select() { - if [ -n "$1" ] && [ ${accounts[$1]+abc} ] - then - context_workspace - if [ "$1" != "$TERRAFORM_WORKSPACE" ]; then - export TERRAFORM_WORKSPACE=$1 - echo $TERRAFORM_WORKSPACE > $WORKSPACE_FILE - echo -e "${YELLOW}Switching workspace to $TERRAFORM_WORKSPACE${NC}" - workspace_verify - else - echo -e "Already on workspace ${GREEN}$TERRAFORM_WORKSPACE${NC}" - fi - else - (>&2 echo -e "${RED}workspace '$1' is invalid. Usage: $TF_BIN_NAME workspace accounts[@]}].${NC}") - return 1 - fi - return 0 -} - -## stack - -stack() { - STACK_SUBCOMMAND=$1 - shift - case $STACK_SUBCOMMAND in - select) - stack_select "$@" - ;; - list) - context_stack - echo -e "Currently using stack: [${GREEN}$TERRAFORM_STACK${NC}]" - echo -e "Available stacks are: [${YELLOW}$VALID_STACKS${NC}]" - ;; - help) - echo -e "Usage: ${YELLOW}$TF_BIN_NAME stack ${NC} -Available subcommands: - -select Selects the working stack -list List the selected stack and available stacks -help Prints available stack subcommands" - ;; - *) - echo -e "The command ${RED}$STACK_SUBCOMMAND${NC} is not available.\nUse '${YELLOW}$TF_BIN_NAME stack help${NC}' to see the available command" - ;; - esac - return $? -} - -stack_verify() { - stack_valid(){ - if contains "$VALID_STACKS" $TERRAFORM_STACK - then - echo -e "Current stack: ${GREEN}$TERRAFORM_STACK${NC}" - else - (>&2 echo -e "${RED}Invalid stack '$TERRAFORM_STACK'.\n$TF_BIN_NAME stack select [$VALID_STACKS]${NC}") - return 1 - fi - return 0 - } - - # Keeping this if for automation purposes via environment variables - if [ -n "$TERRAFORM_STACK" ]; then - if (! stack_valid "env") then - return 1 - fi - else - context_stack - if (! stack_valid ".tf/.stack") then - return 1 - fi - fi - - - stack_global=$DIR/stacks/$TERRAFORM_STACK/global.symlink.tf - stack_backend=$DIR/stacks/$TERRAFORM_STACK/backend.symlink.tf - if [ -f $stack_global ]; then - rm $stack_global - fi - - if [ ! -h $stack_global ]; then - ln -s $DIR/global.tf $stack_global - fi - - if [ -f $stack_backend ]; then - rm $stack_backend - fi - - if [ ! -h $stack_backend ]; then - ln -s $DIR/backend.tf $stack_backend - fi - return $? -} - -stack_select() { - if contains "$VALID_STACKS" $1 - then - echo -e "${YELLOW}Switching stack to $1.${NC}" - export TERRAFORM_STACK=$1 - echo $TERRAFORM_STACK > $STACK_FILE - echo -e "${GREEN}Stack switched to $TERRAFORM_STACK.${NC}" - else - (>&2 echo -e "${RED}stack '$1' is invalid. Usage: $TF_BIN_NAME stack [$VALID_STACKS]${NC}") - return 1 - fi - - terraform_version_select - return $? -} - -## backend -backend() { - if [ ${#@} -lt 1 ]; then - echo -e "${RED}No command has been provided for backend.\nUsual terraform commands can be used.${NC}" - echo -e "${YELLOW}Usage: $TF_BIN_NAME backend ${NC}" - return 1 - fi - context_workspace - - echo -e "Using directory: ${GREEN}state-management${NC}" - echo -e "Current workspace: ${GREEN}$TERRAFORM_WORKSPACE${NC}" - backend=$DIR/state-management/backend.symlink.tf - if [ -f $backend ]; then - rm $backend - fi - - if [ ! -h $backend ]; then - ln -s $DIR/backend.tf $backend - fi - - BACKEND_BUCKET="terraform-state-${accounts[${TERRAFORM_WORKSPACE}]}" - STATE_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/terraform-state\`].{keyid:TargetKeyId}" --output text) - if [ "$1" == "init" ]; then - rm -rf .terraform terraform.tfstate.d .terraform.lock.hcl - terraform -chdir=$DIR/state-management $1 ${@:2} -backend-config="bucket=${BACKEND_BUCKET}" -backend-config="key=backend/terraform.tfstate" -backend-config="encrypt=true" -backend-config="kms_key_id=${STATE_KEY_ID}" - else - terraform -chdir=$DIR/state-management "$@" - fi - return $? -} - -## dependencies -dependencies() { - ADD_STATUS=d - if [ "x$1" = "xstatus" ] - then - ADD_STATUS=1 - fi - # check dependencies between modules - echo -e "digraph { - compound = \"true\" - newrank = \"true\" - node[style=filled]\n" - for stack in $VALID_STACKS - do - ATTRIBUTES= - if [ "$ADD_STATUS" = "1" ] - then - COLOR=red - set +e - result=$(export TERRAFORM_STACK=$stack; $0 plan -detailed-exitcode 2> /dev/null) - EXIT_CODE=$? - set -e - if [[ $EXIT_CODE == 2 ]] - then - COLOR=yellow - elif [[ $EXIT_CODE == 0 ]] - then - COLOR=green - fi - if [[ -n "$ATTRIBUTES" ]] - then - ATTRIBUTES=${ATTRIBUTES}, - fi - ATTRIBUTES=${ATTRIBUTES}fillcolor=${COLOR} - fi - echo -e \"$stack\"[$ATTRIBUTES] - done - grep \\\"terraform_remote_state $DIR/stacks/*/*.tf | sed -e 's/.*\/stacks\///; s/\/.*terraform_remote_state//; s/["{]//g; s/ /" -> "/; s/\s\+$/"/; s/^/"/' - - echo -e "}" -} - -## chamber -chamber() { - export AWS_REGION=eu-west-1 - COMMAND=$1 - case $COMMAND in - exec|help|history|list) - chamber "$@" - return $? - ;; - read|write) - shift - SERVICE=$1 - shift - export CHAMBER_KMS_KEY_ALIAS=alias/$SERVICE-configuration-secrets - chamber $COMMAND $SERVICE "$@" - return $? - ;; - esac - return 1 -} - -## conf -conf() { - PREFIX=$1 - shift - COMMAND=$1 - case $COMMAND in - diff) - shift - FILE=$1 - shift - if [ -z $FILE ] - then - (>&2 echo -e "${RED}Specify a file name${NC}") - exit 1 - fi - aws s3 cp s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/$FILE - | diff $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/$FILE "$@" -- - - ;; - cp) - shift - CONF_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/$PREFIX-configuration\`].{keyid:TargetKeyId}" --output text) - aws s3 cp --sse "aws:kms" --sse-kms-key-id "${CONF_KEY_ID}" $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/ s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ --recursive "$@" - ;; - sync) - shift - CONF_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/$PREFIX-configuration\`].{keyid:TargetKeyId}" --output text) - aws s3 sync --sse "aws:kms" --sse-kms-key-id "${CONF_KEY_ID}" --delete $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/ s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ - ;; - *) - echo "Files on S3:" - aws s3 ls --recursive s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ - echo "Sync (dryrun):" - aws s3 sync --delete --dryrun $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/ s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ - ;; - esac - return $? -} - -## main -main() { - bootstrap - - TF_COMMAND=$1 - shift - case $TF_COMMAND in - deps) - context - dependencies "$@" - ;; - conf) - context - conf "$@" - ;; - chamber) - chamber "$@" - ;; - init) - workspace_verify - ;; - backend) - backend "$@" - ;; - apply) - context - export TF_CLI_ARGS="-var-file=$DIR/envvars/${TERRAFORM_WORKSPACE}.tfvars -var-file=$DIR/global.tfvars $EXTRA_VAR_FILE -input=false -auto-approve=false" - terraform -chdir=$DIR/stacks/$TERRAFORM_STACK $TF_COMMAND "$@" - ;; - plan|destroy|import|refresh|console) - context - export TF_CLI_ARGS="-var-file=$DIR/envvars/${TERRAFORM_WORKSPACE}.tfvars -var-file=$DIR/global.tfvars $EXTRA_VAR_FILE -input=false" - terraform -chdir=$DIR/stacks/$TERRAFORM_STACK $TF_COMMAND "$@" - ;; - get|validate|state|graph|fmt|show|taint|untaint|version|output|force-unlock|metadata|login|logout|providers) - context - terraform -chdir=$DIR/stacks/$TERRAFORM_STACK $TF_COMMAND "$@" - ;; - workspace) - workspace "$@" - ;; - stack) - stack "$@" - ;; - ctx) - context true - ;; - help) - echo -e "${YELLOW}Available $TF_BIN_NAME commands\n$TF_BIN_NAME has priority over terraform${NC}" - echo " - deps Displays dependencies between stacks - conf Interact with S3 configuration buckets - chamber Run chamber commands - init Forcefully initialize a workspace - ctx Show selected stack and workspace - workspace Wraps workspaces around 'accounts' file - backend Run normal terraform commands on the terraform state bucket - stack Manage stacks for the selected workspace" - echo -e "\n${YELLOW}Available terraform commands${NC}\n" - terraform -help - ;; - *) - (>&2 echo -e "${RED}Command '$TF_COMMAND' is not supported. Use '$TF_BIN_NAME help' for more information${NC}") - echo -e "${YELLOW}$TF_BIN_NAME is compatible with terrafom version ~>1.5. If you require a command that is above this version, please raise an issue at https://github.com/vismaosscomponents/terraform-wrapper${NC}" - ;; - esac - - exit $? -} - -## Running script -# Get binary name for output purposes -TF_BIN_NAME=$(echo $0 | rev | cut -d '/' -f1 | rev) -# globally scoped -declare -A accounts -main "$@" -## End of running script diff --git a/tf.sh b/tf.sh index 963abe3..ce340ba 100755 --- a/tf.sh +++ b/tf.sh @@ -7,11 +7,7 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color -function usage { - echo "Usage: $0 " - exit 1; -} - +## utility contains() { if [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] then @@ -21,97 +17,85 @@ contains() { fi } -# Login with Privileged User Management -# 1st parameter: target AWS profile -# 2nd parameter: target AWS account id -function login() { - set +e - EXIT_CODE=$(winpty --version 2>&1 > /dev/null) - EXIT_CODE=$? - set -e - PTY= - if [[ $EXIT_CODE == 0 ]] - then - PTY="winpty" - fi +context_stack() { + if [ -n "$TERRAFORM_STACK" ]; then + return 0 + fi - PYTHON=python - set +e - EXIT_CODE=$(python3 --version 2>&1 > /dev/null) - EXIT_CODE=$? - set -e - if [[ $EXIT_CODE == 0 ]] - then - PYTHON="python3" - fi + if [ ! -e $STACK_FILE ]; then + echo -e "${RED}Stack file doesn't exist!${NC}" + return 1 + fi - $PTY $PYTHON $TFDIR/pum-aws.py --profile $1 --account $2 -} + export TERRAFORM_STACK=$(cat $STACK_FILE) -function setup_workspace { - cd $DIR/stacks/$TERRAFORM_STACK - CURRENT_WORKSPACE=`$TERRAFORM_BIN workspace show` - if [ $CURRENT_WORKSPACE != $TERRAFORM_WORKSPACE ] - then - init_workspace + if [ -z "$TERRAFORM_STACK" ]; then + echo -e "${RED}Missing value from stack file!${NC}" + return 1 fi + + return 0 } -function backend { - if [ -z $TF_USE_CURRENT_PROFILE ] - then +context_workspace() { + if [ -n "$TERRAFORM_WORKSPACE" ]; then export AWS_PROFILE=$TERRAFORM_WORKSPACE + return 0 fi - echo "Using directory 'state-management'" - cd $DIR/state-management - cp $DIR/backend.tf backend.symlink.tf + if [ ! -e $WORKSPACE_FILE ]; then + echo -e "${RED}Workspace file doesn't exist!${NC}" + return 1 + fi - BACKEND_BUCKET="terraform-state-${accounts[${TERRAFORM_WORKSPACE}]}" - STATE_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/terraform-state\`].{keyid:TargetKeyId}" --output text) - if [ "$1" == "init" ]; then - rm -rf .terraform terraform.tfstate.d .terraform.lock.hcl - $TERRAFORM_BIN $1 ${@:2} -backend-config="bucket=${BACKEND_BUCKET}" -backend-config="key=backend/terraform.tfstate" -backend-config="encrypt=true" -backend-config="kms_key_id=${STATE_KEY_ID}" - else - $TERRAFORM_BIN $@ + export TERRAFORM_WORKSPACE=$(cat $WORKSPACE_FILE) + export AWS_PROFILE=$TERRAFORM_WORKSPACE + if [ -z "$TERRAFORM_WORKSPACE" ]; then + echo -e "${RED}Missing value from workspace file!${NC}" + return 1 fi + + return 0 } -function init_workspace { - if [ -z $TF_USE_CURRENT_PROFILE ] - then - export AWS_PROFILE=$TERRAFORM_WORKSPACE +context() { + context_stack + if [ $? -eq 1 ]; then + exit 1 fi - cd $DIR/stacks/$TERRAFORM_STACK - - rm -rf .terraform terraform.tfstate.d .terraform.lock.hcl - BACKEND_BUCKET="terraform-state-${accounts[${TERRAFORM_WORKSPACE}]}" - STATE_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/terraform-state\`].{keyid:TargetKeyId}" --output text) - if [ $TERRAFORM_BIN = "terraform-0.14" ]; then - EXTRA=-upgrade + + context_workspace + if [ $? -eq 1 ]; then + exit 1 fi - $TERRAFORM_BIN init -backend-config="bucket=${BACKEND_BUCKET}" -backend-config="key=stacks/$TERRAFORM_STACK" -backend-config="encrypt=true" -backend-config="kms_key_id=${STATE_KEY_ID}" $EXTRA - set +e - $TERRAFORM_BIN workspace select $TERRAFORM_WORKSPACE - if [ $? != 0 ] + + if [ $1 ] || [ "$TF_WRAPPER_DEBUG" == "true" ] then - $TERRAFORM_BIN workspace new $TERRAFORM_WORKSPACE - $TERRAFORM_BIN workspace select $TERRAFORM_WORKSPACE + echo -e "Using stack: ${GREEN}${TERRAFORM_STACK}${NC}" + echo -e "Using workspace: ${GREEN}${TERRAFORM_WORKSPACE}${NC}" fi - set -e } -SOURCE="${BASH_SOURCE[0]}" -while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink - TFDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" - SOURCE="$(readlink "$SOURCE")" - [[ $SOURCE != /* ]] && SOURCE="$TFDIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located -done -TFDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" +install_required() { + if [ "$TF_WRAPPER_DEBUG" == "true" ]; then + echo -e "${YELLOW}Running install_required${NC}" + fi -DIR= + if [ "$(which tfswitch)" == "" ]; then + echo "tfswitch is not installed...\nInstalling latest version of tfswitch" + curl -L https://raw.githubusercontent.com/warrensbox/terraform-switcher/release/install.sh -o $HOME/install.sh && chmod 755 $HOME/install.sh + $HOME/install.sh -b $HOME/bin && rm $HOME/bin/install.sh + echo "${GREEN}Tfswitch has been installed${NC}" + elif [ "$TF_WRAPPER_DEBUG" == "true" ]; then + echo -e "${GREEN}tfswitch is installed${NC}" + fi +} search_up() { + if [ "$TF_WRAPPER_DEBUG" == "true" ]; then + echo -e "${YELLOW}Running search_up${NC}" + fi + local LOOK=${PWD%/} while [[ -n $LOOK ]]; do [[ -d $LOOK/.tf ]] && { @@ -126,338 +110,480 @@ search_up() { fi } -search_up +terraform_version_select() { + if [ "$TF_WRAPPER_DEBUG" == "true" ]; then + echo -e "${YELLOW}Running terraform_version_select${NC}" + fi -#SOURCE="${BASH_SOURCE[0]}" -#while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink -# DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" -# SOURCE="$(readlink "$SOURCE")" -# [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located -#done -#DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + # Define which terraform version to use + context_stack + TF_VERSION_FILE=$DIR/stacks/$TERRAFORM_STACK/terraform.version + TF_VERSION_FILE_ROOT=$DIR/terraform.version -if [ "$1" = "init" ] -then - if [ "x$DIR" = "x" ] - then - mkdir -p .tf - search_up - echo -e "${GREEN}Initialized directory $DIR${NC}" - exit 0 + #TF_VERSION_FILE needs to match the SEMVAR MAJOR.MINOR.PATCH + if [ -f "$TF_VERSION_FILE" ]; then + TF_VERSION=`cat $TF_VERSION_FILE` + elif [ -f "$TF_VERSION_FILE_ROOT" ]; then + TF_VERSION=`cat $TF_VERSION_FILE_ROOT` else - (>&2 echo -e "${RED}Already initialized in $DIR${NC}") + (>&2 echo -e "${RED}No version of terraform has been specified. Create first a terraform.version file with a proper semver${NC}") exit 1 fi -fi - -if [ "x$DIR" = "x" ] -then - (>&2 echo -e "${RED}No initialized directory.${NC}") - exit 1 -else - echo -e "${GREEN}Root: $DIR${NC}" -fi - -WORKSPACE_FILE=$DIR/.tf/.workspace -STACK_FILE=$DIR/.tf/.stack -ACCOUNTS_FILE=$DIR/accounts - -## Read accounts -if ! [[ -e $ACCOUNTS_FILE ]] -then - (>&2 echo -e "${RED}$ACCOUNTS_FILE file missing.${NC}") - exit 1 -fi - -declare -A accounts -while IFS=$'=' read -r -a anArray -do - accounts[${anArray[0]}]=`echo ${anArray[1]} | sed s/[^0-9]//` -done < $ACCOUNTS_FILE - -if ! [ -d $DIR/stacks ] -then - (>&2 echo -e "${RED}No current stack. Create 'stacks' directory and use: $0 stack [stack name].${NC}") - exit 1 -fi - -VALID_STACKS=`find $DIR/stacks -maxdepth 1 -mindepth 1 -type d -printf "%f " | tr ' ' '\n' | sort | tr '\n' ' '` - -## Select stack -if [ "x$TERRAFORM_STACK" = "x" ] && [ "$1" = "stack" ] -then - shift - if contains "$VALID_STACKS" $1 - then - echo -e "${YELLOW}Switching stack to $1.${NC}" - export TERRAFORM_STACK=$1 - echo $TERRAFORM_STACK > $STACK_FILE - echo -e "${GREEN}Stack switched to $TERRAFORM_STACK.${NC}" - exit 0 - else - (>&2 echo -e "${RED}stack '$1' is invalid. Usage: $0 stack [$VALID_STACKS].${NC}") + + if ! [[ $TF_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "The version specified in the 'terraform.version', doesn't match semver MAJOR.MINOR.PATCH" exit 1 fi -fi -## Verify stack -if [ "x$TERRAFORM_STACK" != "x" ] -then - if contains "$VALID_STACKS" $TERRAFORM_STACK - then - echo -e "${GREEN}Current stack: $TERRAFORM_STACK (from env)${NC}" - else - (>&2 echo -e "${RED}Invalid stack '$TERRAFORM_STACK' (from env). Valid stacks: $VALID_STACKS.${NC}") - exit 1 + if [ "$(terraform version | head -n 1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" != "$TF_VERSION" ]; then + echo -e "${YELLOW}Terraform couldn't be found or it didn't match the $TF_VERSION${NC} version" + tfswitch "$TF_VERSION" fi -elif [[ -e $STACK_FILE ]] -then - STACK=`cat $STACK_FILE` - if contains "$VALID_STACKS" $STACK - then - export TERRAFORM_STACK=$STACK - echo -e "${GREEN}Current stack: $TERRAFORM_STACK${NC}" - else - (>&2 echo -e "${RED}No current stack. Use: $0 stack [stack name].${NC}") - fi -else - (>&2 echo -e "${RED}No current stack. Usage: $0 stack [$VALID_STACKS].${NC}") - exit 1 -fi - -# Define which terraform version to use -TF_VERSION_FILE=$DIR/stacks/$TERRAFORM_STACK/terraform.version -TF_VERSION_FILE_ROOT=$DIR/terraform.version -TERRAFORM_BIN="terraform-0.11" - -if [ -f "$TF_VERSION_FILE" ]; then - TF_VERSION=`cat $TF_VERSION_FILE` -elif [ -f "$TF_VERSION_FILE_ROOT" ]; then - TF_VERSION=`cat $TF_VERSION_FILE_ROOT` -fi - -if [ -n "$TF_VERSION" ]; then - if [ $TF_VERSION = "0.14" ]; then - echo "Working with terraform v0.14 ..." - TERRAFORM_BIN="terraform-0.14" - elif [ $TF_VERSION = "0.12" ]; then - echo "Working with terraform v0.12 ..." - TERRAFORM_BIN="terraform-0.12" - elif [ $TF_VERSION = "0.13" ]; then - echo "Working with terraform v0.13 ..." - TERRAFORM_BIN="terraform-0.13" - elif [ $TF_VERSION = "0.15" ]; then - echo "Working with terraform v0.15 ..." - TERRAFORM_BIN="terraform-0.15" - elif [ $TF_VERSION = "1.0" ]; then - echo "Working with terraform v1.0 ..." - TERRAFORM_BIN="terraform-1.0" - else - echo "The used version from terraform.version file is wrong or missing!" +} + +read_accounts() { + if [ "$TF_WRAPPER_DEBUG" == "true" ]; then + echo -e "${YELLOW}Running read_accounts${NC}" + fi + + ACCOUNTS_FILE=$DIR/accounts + if ! [[ -e $ACCOUNTS_FILE ]]; then + (>&2 echo -e "${RED}$ACCOUNTS_FILE file missing.${NC}") exit 1 fi -fi -# The stack exists, switch to it -cd $DIR/stacks/$TERRAFORM_STACK -cp $DIR/global.tf global.symlink.tf -cp $DIR/backend.tf backend.symlink.tf + while IFS=$'=' read -r -a anArray + do + accounts[${anArray[0]}]=`echo ${anArray[1]} | sed s/[^0-9]//` + done < $ACCOUNTS_FILE +} -if [ "$1" = "login" ] -then - if [ "x$TERRAFORM_WORKSPACE" = "x" ] +bootstrap() { + if [ "$TF_WRAPPER_DEBUG" == "true" ]; then + echo -e "Running in ${YELLOW}debug${NC} mode" + echo -e "${YELLOW}Running bootstrap${NC}" + fi + + install_required + search_up + + if [ ! -d "$DIR/.tf" ]; then + mkdir -p $DIR/.tf + fi + + read_accounts + + # Setup variables used throughout the script + WORKSPACE_FILE=$DIR/.tf/.workspace + STACK_FILE=$DIR/.tf/.stack + VALID_STACKS=$(find $DIR/stacks -maxdepth 1 -mindepth 1 -type d -printf "%f " | tr ' ' '\n' | sort | tr '\n' ' ') + + # Local configuration file, for some api keys + if [ -f "$DIR/terraform.tfvars" ]; then + EXTRA_VAR_FILE=-var-file=../../terraform.tfvars + fi + + terraform_version_select +} + +## workspace + +workspace() { + WORKSPACE_SUBCOMMAND=$1 + shift + case $WORKSPACE_SUBCOMMAND in + select) + workspace_select "$@" + ;; + list) + context_workspace + echo -e "Currently using workspace: [${GREEN}$TERRAFORM_WORKSPACE${NC}]" + echo -e "Available workspaces are: [${YELLOW}${!accounts[@]}${NC}]" + ;; + help) + echo -e "Usage: ${YELLOW}$TF_BIN_NAME workspace ${NC} +Available subcommands: + +select Selects the working workspace +list List the selected workspace and available workspaces +help Prints available workspace subcommands + +${YELLOW}To initialize an already selected workspace use '$TF_BIN_NAME init'${NC}" + ;; + *) + echo -e "The command ${RED}$WORKSPACE_SUBCOMMAND${NC} is not available.\nUse '${YELLOW}$TF_BIN_NAME workspace help${NC}' to see the available command" + ;; + esac + return $? +} + +workspace_init() { + if (! stack_verify) then + echo -e "${RED}An error occured while verifying the stack${NC}" + return 1 + fi + + if [ -z $TF_USE_CURRENT_PROFILE ] then - TERRAFORM_WORKSPACE=`cat $WORKSPACE_FILE` + export AWS_PROFILE=$TERRAFORM_WORKSPACE fi - if [ ${accounts[$TERRAFORM_WORKSPACE]+abc} ] + STACK_DIR=$DIR/stacks/$TERRAFORM_STACK + + rm -rf $STACK_DIR/.terraform $STACK_DIR/terraform.tfstate.d $STACK_DIR/.terraform.lock.hcl + BACKEND_BUCKET="terraform-state-${accounts[${TERRAFORM_WORKSPACE}]}" + STATE_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/terraform-state\`].{keyid:TargetKeyId}" --output text) + + terraform -chdir=$STACK_DIR init -backend-config="bucket=${BACKEND_BUCKET}" -backend-config="key=stacks/$TERRAFORM_STACK" -backend-config="encrypt=true" -backend-config="kms_key_id=${STATE_KEY_ID}" + set +e + terraform -chdir=$STACK_DIR workspace select $TERRAFORM_WORKSPACE + if [ $? != 0 ] then - echo -e "${GREEN}Current workspace: $TERRAFORM_WORKSPACE${NC}" + terraform -chdir=$STACK_DIR workspace new $TERRAFORM_WORKSPACE + terraform -chdir=$STACK_DIR workspace select $TERRAFORM_WORKSPACE + fi + set -e + return $? +} + +workspace_verify() { + workspace_valid() { + if [ ${accounts[$TERRAFORM_WORKSPACE]+abc} ]; then + echo -e "Current workspace: ${GREEN}$TERRAFORM_WORKSPACE${NC}" + workspace_init else - (>&2 echo -e "${RED}No current workspace. Usage: $0 workspace [${!accounts[@]}].${NC}") - exit 1 + (>&2 echo -e "${RED}Invalid workspace '$TERRAFORM_WORKSPACE'.\n$TF_BIN_NAME workspace select \nValid workspaces: [${!accounts[@]}].${NC}") + return 1 + fi + } + + # Keeping this if for automation purposes via environment variables + if [ -n "$TERRAFORM_WORKSPACE" ]; then + if (! workspace_valid "env") then + return 1 + fi + else + context_workspace + if (! workspace_valid ".tf/.workspace") then + return 1 + fi fi - login $TERRAFORM_WORKSPACE ${accounts[$TERRAFORM_WORKSPACE]} - exit 0; -fi + return 0 +} -## Select (and setup) workspace -if [ "x$TERRAFORM_WORKSPACE" = "x" ] && [ "$1" = "workspace" ] -then - shift - if [ "x$1" != "x" ] && [ ${accounts[$1]+abc} ] +workspace_select() { + if [ -n "$1" ] && [ ${accounts[$1]+abc} ] then - export TERRAFORM_WORKSPACE=$1 - echo $TERRAFORM_WORKSPACE > $WORKSPACE_FILE - echo -e "${YELLOW}Switching workspace to $TERRAFORM_WORKSPACE${NC}" - setup_workspace - echo -e "${GREEN}Workspace switched to $TERRAFORM_WORKSPACE${NC}" - exit 0 + context_workspace + if [ "$1" != "$TERRAFORM_WORKSPACE" ]; then + export TERRAFORM_WORKSPACE=$1 + echo $TERRAFORM_WORKSPACE > $WORKSPACE_FILE + echo -e "${YELLOW}Switching workspace to $TERRAFORM_WORKSPACE${NC}" + workspace_verify + else + echo -e "Already on workspace ${GREEN}$TERRAFORM_WORKSPACE${NC}" + fi else - (>&2 echo -e "${RED}workspace '$1' is invalid. Usage: $0 workspace [${!accounts[@]}].${NC}") - exit 1 + (>&2 echo -e "${RED}workspace '$1' is invalid. Usage: $TF_BIN_NAME workspace accounts[@]}].${NC}") + return 1 fi -fi + return 0 +} -## Verify workspace -if [ "x$TERRAFORM_WORKSPACE" != "x" ] -then - if [ ${accounts[$TERRAFORM_WORKSPACE]+abc} ] - then - echo -e "${GREEN}Current workspace: $TERRAFORM_WORKSPACE (from env)${NC}" - setup_workspace +## stack + +stack() { + STACK_SUBCOMMAND=$1 + shift + case $STACK_SUBCOMMAND in + select) + stack_select "$@" + ;; + list) + context_stack + echo -e "Currently using stack: [${GREEN}$TERRAFORM_STACK${NC}]" + echo -e "Available stacks are: [${YELLOW}$VALID_STACKS${NC}]" + ;; + help) + echo -e "Usage: ${YELLOW}$TF_BIN_NAME stack ${NC} +Available subcommands: + +select Selects the working stack +list List the selected stack and available stacks +help Prints available stack subcommands" + ;; + *) + echo -e "The command ${RED}$STACK_SUBCOMMAND${NC} is not available.\nUse '${YELLOW}$TF_BIN_NAME stack help${NC}' to see the available command" + ;; + esac + return $? +} + +stack_verify() { + stack_valid(){ + if contains "$VALID_STACKS" $TERRAFORM_STACK + then + echo -e "Current stack: ${GREEN}$TERRAFORM_STACK${NC}" + else + (>&2 echo -e "${RED}Invalid stack '$TERRAFORM_STACK'.\n$TF_BIN_NAME stack select [$VALID_STACKS]${NC}") + return 1 + fi + return 0 + } + + # Keeping this if for automation purposes via environment variables + if [ -n "$TERRAFORM_STACK" ]; then + if (! stack_valid "env") then + return 1 + fi else - (>&2 echo -e "${RED}Invalid workspace '$TERRAFORM_WORKSPACE'. Valid workspaces: [${!accounts[@]}].${NC}") - exit 1 + context_stack + if (! stack_valid ".tf/.stack") then + return 1 + fi fi -elif [[ -e $WORKSPACE_FILE ]] -then - WORKSPACE=`cat $WORKSPACE_FILE` - if [ ${accounts[$WORKSPACE]+abc} ] + + + stack_global=$DIR/stacks/$TERRAFORM_STACK/global.symlink.tf + stack_backend=$DIR/stacks/$TERRAFORM_STACK/backend.symlink.tf + if [ -f $stack_global ]; then + rm $stack_global + fi + + if [ ! -h $stack_global ]; then + ln -s $DIR/global.tf $stack_global + fi + + if [ -f $stack_backend ]; then + rm $stack_backend + fi + + if [ ! -h $stack_backend ]; then + ln -s $DIR/backend.tf $stack_backend + fi + return $? +} + +stack_select() { + if contains "$VALID_STACKS" $1 then - export TERRAFORM_WORKSPACE=$WORKSPACE - echo -e "${GREEN}Current workspace: $TERRAFORM_WORKSPACE${NC}" - setup_workspace + echo -e "${YELLOW}Switching stack to $1.${NC}" + export TERRAFORM_STACK=$1 + echo $TERRAFORM_STACK > $STACK_FILE + echo -e "${GREEN}Stack switched to $TERRAFORM_STACK.${NC}" else - (>&2 echo -e "${RED}No current workspace. Usage: $0 workspace [${!accounts[@]}].${NC}") - exit 1 + (>&2 echo -e "${RED}stack '$1' is invalid. Usage: $TF_BIN_NAME stack [$VALID_STACKS]${NC}") + return 1 fi -else - (>&2 echo -e "${RED}No current workspace. Usage: $0 workspace [${!accounts[@]}].${NC}") - exit 1 -fi -if [ -z $TF_USE_CURRENT_PROFILE ] -then - export AWS_PROFILE=$TERRAFORM_WORKSPACE -fi + terraform_version_select + return $? +} + +## backend +backend() { + if [ ${#@} -lt 1 ]; then + echo -e "${RED}No command has been provided for backend.\nUsual terraform commands can be used.${NC}" + echo -e "${YELLOW}Usage: $TF_BIN_NAME backend ${NC}" + return 1 + fi + context_workspace + + echo -e "Using directory: ${GREEN}state-management${NC}" + echo -e "Current workspace: ${GREEN}$TERRAFORM_WORKSPACE${NC}" + backend=$DIR/state-management/backend.symlink.tf + if [ -f $backend ]; then + rm $backend + fi -# Local configuration file, for some api keys -if [ -f "$DIR/terraform.tfvars" ]; then - EXTRA_VAR_FILE=-var-file=../../terraform.tfvars -fi + if [ ! -h $backend ]; then + ln -s $DIR/backend.tf $backend + fi -TF_COMMAND=$1 -shift + BACKEND_BUCKET="terraform-state-${accounts[${TERRAFORM_WORKSPACE}]}" + STATE_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/terraform-state\`].{keyid:TargetKeyId}" --output text) + if [ "$1" == "init" ]; then + rm -rf .terraform terraform.tfstate.d .terraform.lock.hcl + terraform -chdir=$DIR/state-management $1 ${@:2} -backend-config="bucket=${BACKEND_BUCKET}" -backend-config="key=backend/terraform.tfstate" -backend-config="encrypt=true" -backend-config="kms_key_id=${STATE_KEY_ID}" + else + terraform -chdir=$DIR/state-management "$@" + fi + return $? +} -case $TF_COMMAND in - deps) - ADD_STATUS=d - if [ "x$1" = "xstatus" ] +## dependencies +dependencies() { + ADD_STATUS=d + if [ "x$1" = "xstatus" ] + then + ADD_STATUS=1 + fi + # check dependencies between modules + echo -e "digraph { + compound = \"true\" + newrank = \"true\" + node[style=filled]\n" + for stack in $VALID_STACKS + do + ATTRIBUTES= + if [ "$ADD_STATUS" = "1" ] then - ADD_STATUS=1 + COLOR=red + set +e + result=$(export TERRAFORM_STACK=$stack; $0 plan -detailed-exitcode 2> /dev/null) + EXIT_CODE=$? + set -e + if [[ $EXIT_CODE == 2 ]] + then + COLOR=yellow + elif [[ $EXIT_CODE == 0 ]] + then + COLOR=green + fi + if [[ -n "$ATTRIBUTES" ]] + then + ATTRIBUTES=${ATTRIBUTES}, + fi + ATTRIBUTES=${ATTRIBUTES}fillcolor=${COLOR} fi - # check dependencies between modules - echo -e "digraph { - compound = \"true\" - newrank = \"true\" - node[style=filled] -" - for stack in $VALID_STACKS - do - ATTRIBUTES= - if [ "$ADD_STATUS" = "1" ] + echo -e \"$stack\"[$ATTRIBUTES] + done + grep \\\"terraform_remote_state $DIR/stacks/*/*.tf | sed -e 's/.*\/stacks\///; s/\/.*terraform_remote_state//; s/["{]//g; s/ /" -> "/; s/\s\+$/"/; s/^/"/' + + echo -e "}" +} + +## chamber +chamber() { + export AWS_REGION=eu-west-1 + COMMAND=$1 + case $COMMAND in + exec|help|history|list) + chamber "$@" + return $? + ;; + read|write) + shift + SERVICE=$1 + shift + export CHAMBER_KMS_KEY_ALIAS=alias/$SERVICE-configuration-secrets + chamber $COMMAND $SERVICE "$@" + return $? + ;; + esac + return 1 +} + +## conf +conf() { + PREFIX=$1 + shift + COMMAND=$1 + case $COMMAND in + diff) + shift + FILE=$1 + shift + if [ -z $FILE ] then - COLOR=red - set +e - result=$(export TERRAFORM_STACK=$stack; $0 plan -detailed-exitcode 2> /dev/null) - EXIT_CODE=$? - set -e - if [[ $EXIT_CODE == 2 ]] - then - COLOR=yellow - elif [[ $EXIT_CODE == 0 ]] - then - COLOR=green - fi - if [[ -n "$ATTRIBUTES" ]] - then - ATTRIBUTES=${ATTRIBUTES}, - fi - ATTRIBUTES=${ATTRIBUTES}fillcolor=${COLOR} + (>&2 echo -e "${RED}Specify a file name${NC}") + exit 1 fi - echo -e \"$stack\"[$ATTRIBUTES] - done - grep \\\"terraform_remote_state $DIR/stacks/*/*.tf | sed -e 's/.*\/stacks\///; s/\/.*terraform_remote_state//; s/["{]//g; s/ /" -> "/; s/\s\+$/"/; s/^/"/' - - echo -e "}" - exit 0; - ;; - conf) - PREFIX=$1 - shift - COMMAND=$1 - case $COMMAND in - diff) - shift - FILE=$1 - shift - if [ -z $FILE ] - then - (>&2 echo -e "${RED}Specify a file name${NC}") - exit 1 - fi - aws s3 cp s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/$FILE - | diff $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/$FILE "$@" -- - - ;; - cp) - shift - CONF_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/$PREFIX-configuration\`].{keyid:TargetKeyId}" --output text) - aws s3 cp --sse "aws:kms" --sse-kms-key-id "${CONF_KEY_ID}" $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/ s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ --recursive "$@" - ;; - sync) - shift - CONF_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/$PREFIX-configuration\`].{keyid:TargetKeyId}" --output text) - aws s3 sync --sse "aws:kms" --sse-kms-key-id "${CONF_KEY_ID}" --delete $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/ s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ - ;; - "") - echo "Files on S3:" - aws s3 ls --recursive s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ - echo "Sync (dryrun):" - aws s3 sync --delete --dryrun $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/ s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ - ;; - esac - exit $? - ;; - chamber) - export AWS_REGION=eu-west-1 - COMMAND=$1 - case $COMMAND in - exec|help|history|list) - chamber "$@" - exit $? - ;; - read|write) - shift - SERVICE=$1 - shift - export CHAMBER_KMS_KEY_ALIAS=alias/$SERVICE-configuration-secrets - chamber $COMMAND $SERVICE "$@" - exit $? - ;; - esac - exit 1; - ;; - apply) - export TF_CLI_ARGS="-var-file=../../envvars/${TERRAFORM_WORKSPACE}.tfvars -var-file=../../global.tfvars $EXTRA_VAR_FILE -input=false -auto-approve=false" - ;; - plan|validate|destroy|import|refresh) - export TF_CLI_ARGS="-var-file=../../envvars/${TERRAFORM_WORKSPACE}.tfvars -var-file=../../global.tfvars $EXTRA_VAR_FILE -input=false" - ;; - get|help|state|graph|fmt|show|taint|untaint|version|output) - # Should work without any change - ;; - re-init) - init_workspace - exit $? - ;; - backend) - backend $@ - exit $? - ;; - *) - (>&2 echo -e "${RED}Command $TF_COMMAND is unsupported! Use terraform $TF_COMMAND at your own risks...${NC}") - exit 1; - ;; -esac - -$TERRAFORM_BIN $TF_COMMAND "$@" -exit $? + aws s3 cp s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/$FILE - | diff $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/$FILE "$@" -- - + ;; + cp) + shift + CONF_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/$PREFIX-configuration\`].{keyid:TargetKeyId}" --output text) + aws s3 cp --sse "aws:kms" --sse-kms-key-id "${CONF_KEY_ID}" $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/ s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ --recursive "$@" + ;; + sync) + shift + CONF_KEY_ID=$(aws kms list-aliases --query "Aliases[?AliasName==\`alias/$PREFIX-configuration\`].{keyid:TargetKeyId}" --output text) + aws s3 sync --sse "aws:kms" --sse-kms-key-id "${CONF_KEY_ID}" --delete $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/ s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ + ;; + *) + echo "Files on S3:" + aws s3 ls --recursive s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ + echo "Sync (dryrun):" + aws s3 sync --delete --dryrun $DIR/configurationfiles/$PREFIX/$TERRAFORM_WORKSPACE/ s3://$PREFIX-configuration-${accounts[$TERRAFORM_WORKSPACE]}/ + ;; + esac + return $? +} + +## main +main() { + bootstrap + + TF_COMMAND=$1 + shift + case $TF_COMMAND in + deps) + context + dependencies "$@" + ;; + conf) + context + conf "$@" + ;; + chamber) + chamber "$@" + ;; + init) + workspace_verify + ;; + backend) + backend "$@" + ;; + apply) + context + export TF_CLI_ARGS="-var-file=$DIR/envvars/${TERRAFORM_WORKSPACE}.tfvars -var-file=$DIR/global.tfvars $EXTRA_VAR_FILE -input=false -auto-approve=false" + terraform -chdir=$DIR/stacks/$TERRAFORM_STACK $TF_COMMAND "$@" + ;; + plan|destroy|import|refresh|console) + context + export TF_CLI_ARGS="-var-file=$DIR/envvars/${TERRAFORM_WORKSPACE}.tfvars -var-file=$DIR/global.tfvars $EXTRA_VAR_FILE -input=false" + terraform -chdir=$DIR/stacks/$TERRAFORM_STACK $TF_COMMAND "$@" + ;; + get|validate|state|graph|fmt|show|taint|untaint|version|output|force-unlock|metadata|login|logout|providers) + context + terraform -chdir=$DIR/stacks/$TERRAFORM_STACK $TF_COMMAND "$@" + ;; + workspace) + workspace "$@" + ;; + stack) + stack "$@" + ;; + ctx) + context true + ;; + help) + echo -e "${YELLOW}Available $TF_BIN_NAME commands\n$TF_BIN_NAME has priority over terraform${NC}" + echo " + deps Displays dependencies between stacks + conf Interact with S3 configuration buckets + chamber Run chamber commands + init Forcefully initialize a workspace + ctx Show selected stack and workspace + workspace Wraps workspaces around 'accounts' file + backend Run normal terraform commands on the terraform state bucket + stack Manage stacks for the selected workspace" + echo -e "\n${YELLOW}Available terraform commands${NC}\n" + terraform -help + ;; + *) + (>&2 echo -e "${RED}Command '$TF_COMMAND' is not supported. Use '$TF_BIN_NAME help' for more information${NC}") + echo -e "${YELLOW}$TF_BIN_NAME is compatible with terrafom version ~>1.5. If you require a command that is above this version, please raise an issue at https://github.com/vismaosscomponents/terraform-wrapper${NC}" + ;; + esac + + exit $? +} + +## Running script +# Get binary name for output purposes +TF_BIN_NAME=$(echo $0 | rev | cut -d '/' -f1 | rev) +# globally scoped +declare -A accounts +main "$@" +## End of running script From e3c1c4abe2b84b520b6b11df5f2f9b4474eb4979 Mon Sep 17 00:00:00 2001 From: bmatei-visma Date: Thu, 5 Oct 2023 17:31:24 +0300 Subject: [PATCH 4/8] fix: deletion of tfswitch install script --- tf.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tf.sh b/tf.sh index ce340ba..832a53f 100755 --- a/tf.sh +++ b/tf.sh @@ -84,7 +84,7 @@ install_required() { if [ "$(which tfswitch)" == "" ]; then echo "tfswitch is not installed...\nInstalling latest version of tfswitch" curl -L https://raw.githubusercontent.com/warrensbox/terraform-switcher/release/install.sh -o $HOME/install.sh && chmod 755 $HOME/install.sh - $HOME/install.sh -b $HOME/bin && rm $HOME/bin/install.sh + $HOME/install.sh -b $HOME/bin && rm $HOME/install.sh echo "${GREEN}Tfswitch has been installed${NC}" elif [ "$TF_WRAPPER_DEBUG" == "true" ]; then echo -e "${GREEN}tfswitch is installed${NC}" From cc0a2918fcfaa5b7e53abf7b1f26fb18f6e694d3 Mon Sep 17 00:00:00 2001 From: bmatei-visma Date: Fri, 6 Oct 2023 10:36:53 +0300 Subject: [PATCH 5/8] chore: change install script download directory --- tf.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tf.sh b/tf.sh index 832a53f..6b6383d 100755 --- a/tf.sh +++ b/tf.sh @@ -83,8 +83,8 @@ install_required() { if [ "$(which tfswitch)" == "" ]; then echo "tfswitch is not installed...\nInstalling latest version of tfswitch" - curl -L https://raw.githubusercontent.com/warrensbox/terraform-switcher/release/install.sh -o $HOME/install.sh && chmod 755 $HOME/install.sh - $HOME/install.sh -b $HOME/bin && rm $HOME/install.sh + curl -L https://raw.githubusercontent.com/warrensbox/terraform-switcher/release/install.sh -o /tmp/install.sh && chmod 755 /tmp/install.sh + /tmp/install.sh -b $HOME/bin && rm /tmp/install.sh echo "${GREEN}Tfswitch has been installed${NC}" elif [ "$TF_WRAPPER_DEBUG" == "true" ]; then echo -e "${GREEN}tfswitch is installed${NC}" From 7a963bab286575ec38ae139cd2702851c74d68fb Mon Sep 17 00:00:00 2001 From: bmatei-visma Date: Fri, 6 Oct 2023 10:54:33 +0300 Subject: [PATCH 6/8] chore: update README.md --- README.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8b188bf..6c9ce0d 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,11 @@ Inspired by https://stackoverflow.com/a/45968410 # Installing -## Terraform -Install terraform, make sure ``terraform-0.11`` is on the path - ## Main script Copy ``tf.sh somewhere``, ``/usr/local/bin/tf`` for instance. +Terraform in installed based on the `terraform.version` file, via tfswitch. + ## Bash prompt Copy ``tf-prompt.sh`` somewhere, source it from your bash profile. @@ -81,21 +80,23 @@ Execute the script from a directory inside a root (i.e. one of the parent direct Select a stack: - tf stack network + tf stack select network Select a workspace: - tf workspace acc - + tf workspace select acc + Login with PUM: - tf login + Run pum-aws to get the credentials Do some stuff: tf plan tf apply +Use `tf help` to see supported tf (custom and terraform) commands. + ``global.tf``, ``global.tfvars`` and ``envvars/.tfvars`` are automatically used when running: * ``apply`` * ``destroy`` @@ -120,9 +121,12 @@ How to create the backend: ## Subsequent modifications Switching between workspaces: -* run `tf.sh workspace ` +* run `tf.sh workspace select ` * run `tf.sh backend init` +## Check current context (stack/workspace) + +* tf ctx # Dependency graph @@ -140,7 +144,7 @@ See examples: [dependency graph 1](deps-status-1.png), [dependency graph 2](deps The wrapper expects an executable named ``terraform-0.11`` to be on the path. You can use version 0.12 or 0.14 in this way: * create a file named ``terraform.version`` on the root of your repo, or in a stack to use that version for a single stack -* file should contain ``0.12`` or ``0.14`` to select the version +* file should contain ``0.12.0`` or ``0.14.0`` to select the version # Extra credentials @@ -162,8 +166,10 @@ This file should be excluded from source control. It will be loaded during ``pla 1. Bash autocompletion 1. Initialize a project. 1. Use symlinks if supported instead of copying ``global.tf`` as ``global.symlink.tf`` -1. ? Find current stack from current directory, to be able to use ``cd stacks/xxx`` instead of ``tf stack xxx`` +1. ? Find current stack from current directory, to be able to use ``cd stacks/xxx`` instead of ``tf stack select xxx`` # Limitations -* the workspace is tied with the AWS account. Can't have multiple workspaces under the same AWS account \ No newline at end of file +* the workspace is tied with the AWS account. Can't have multiple workspaces under the same AWS account +* terraform.version file is required in the root directory or at the stack level +** the contents of the version needs to exactly match (e.g. 1.5.7) \ No newline at end of file From d0e13402ea766742ed0aa72022dabb8a8c0e7d8e Mon Sep 17 00:00:00 2001 From: bmatei-visma Date: Fri, 6 Oct 2023 11:06:31 +0300 Subject: [PATCH 7/8] fix: read first line of terraform.version instead of the whole file --- tf.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tf.sh b/tf.sh index 6b6383d..83772e7 100755 --- a/tf.sh +++ b/tf.sh @@ -122,9 +122,9 @@ terraform_version_select() { #TF_VERSION_FILE needs to match the SEMVAR MAJOR.MINOR.PATCH if [ -f "$TF_VERSION_FILE" ]; then - TF_VERSION=`cat $TF_VERSION_FILE` + TF_VERSION=`head -n 1 $TF_VERSION_FILE` elif [ -f "$TF_VERSION_FILE_ROOT" ]; then - TF_VERSION=`cat $TF_VERSION_FILE_ROOT` + TF_VERSION=`head -n 1 $TF_VERSION_FILE_ROOT` else (>&2 echo -e "${RED}No version of terraform has been specified. Create first a terraform.version file with a proper semver${NC}") exit 1 From eb259f10ddd967abaf0ac77dbd21d4cf9491c283 Mon Sep 17 00:00:00 2001 From: bmatei-visma Date: Fri, 6 Oct 2023 11:13:05 +0300 Subject: [PATCH 8/8] chore: update readme.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6c9ce0d..523c7fe 100644 --- a/README.md +++ b/README.md @@ -86,9 +86,11 @@ Select a workspace: tf workspace select acc +* creating workspace requires write permissions because the buckets are encrypted with KMS keys + Login with PUM: - Run pum-aws to get the credentials + Run pum-aws retrieve the credentials Do some stuff: @@ -172,4 +174,4 @@ This file should be excluded from source control. It will be loaded during ``pla * the workspace is tied with the AWS account. Can't have multiple workspaces under the same AWS account * terraform.version file is required in the root directory or at the stack level -** the contents of the version needs to exactly match (e.g. 1.5.7) \ No newline at end of file +** the contents of the version needs to exactly match (e.g. 1.5.7)