Last active
January 10, 2020 17:29
-
-
Save brodygov/22cecf784e7c2b5b3f830507651068fb to your computer and use it in GitHub Desktop.
This file contains 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
#!/usr/bin/env bash | |
set -eu | |
# shellcheck source=/dev/null | |
. "$(dirname "$0")/lib/common.sh" | |
usage() { | |
cat >&2 <<EOM | |
Usage: $0 [OPTIONS...] <bucket_name> <state_file_path> <terraform_dir> <region> <dynamodb_table> | |
Configure terraform to store state in S3 with a dynamodb lock table. | |
This script will create the relevant S3 bucket and dynamodb table as needed, | |
manage the .terraform directory with either the older symlinking style or the | |
newer separate subdirectory style, and then run \`terraform init\`. | |
Arguments: | |
bucket_name: Name of S3 bucket containing state files | |
state_file_path: S3 key path to the state file | |
terraform_dir: Directory in which to run terraform init | |
region: AWS region to connect to | |
dynamodb_table: Name of dynamodb table for state file locking | |
Options: | |
-h, --help Display this message | |
--module-style Use the newer separate subdirectory style and do not create | |
or manage any .terraform symlinks. | |
--shared-style Use the older shared style with .terraform symlinks. | |
This script understands two modes for managing the .terraform directory, where | |
terraform keeps local information about modules and remote state. | |
shared / identity-devops-private style: | |
In the older, shared directory style, the terraform_dir is shared among | |
multiple environments, with env variables delivered from | |
identity-devops-private. This means that we have to blow away the | |
.terraform directory every run so that there is no cross-env contamination. | |
To make this safer, we use a system of symlinks so that the .terraform | |
directory contents remain in persistently under .deploy/, and all we do on | |
an individual run is to swap the .terraform symlink to point to it. This | |
script manages a tree of .terraform directories under '.deploy/' scoped by | |
the S3 state bucket, region, and key path. | |
module / local style: (newer, preferred) | |
In the new style, where we keep a separate subdirectory for each | |
environment, there is no reuse of the subdirectory, so we can use a | |
standard plain .terraform directory without doing any special management. | |
EOM | |
} | |
# Log all terraform and aws commands in this script | |
terraform() { | |
echo >&2 "+ terraform $*" | |
env terraform "$@" | |
} | |
aws() { | |
echo >&2 "+ aws $*" | |
env aws "$@" | |
} | |
# Ensure remote state S3 bucket and dynamodb table exist. | |
# If not, create them. | |
check_or_create_remote_state_resources() { | |
echo >&2 "+ aws s3api head-bucket --bucket $BUCKET" | |
output="$(env aws s3api head-bucket --bucket "$BUCKET" 2>&1)" \ | |
&& ret=$? || ret=$? | |
if grep -F "Not Found" <<< "$output" >/dev/null; then | |
log "$output" | |
log "Bucket $BUCKET does not exist, creating..." | |
log "Creating an s3 bucket for terraform state" | |
aws s3 mb "s3://$BUCKET" --region "$REGION" | |
log "Enabling versioning on the s3 bucket" | |
aws s3api put-bucket-versioning --bucket "$BUCKET" \ | |
--versioning-configuration Status=Enabled | |
elif [ "$ret" -ne 0 ]; then | |
exit "$ret" | |
fi | |
log "State lock table: $LOCK_TABLE" | |
if ! aws dynamodb describe-table --table-name "$LOCK_TABLE" \ | |
--region "$REGION" >/dev/null | |
then | |
log "Lock table does not exist, creating..." | |
log "Creating a dynamodb table for terraform lock files" | |
aws dynamodb create-table \ | |
--region "${REGION}" \ | |
--table-name "$LOCK_TABLE" \ | |
--attribute-definitions AttributeName=LockID,AttributeType=S \ | |
--key-schema AttributeName=LockID,KeyType=HASH \ | |
--sse-specification Enabled=true \ | |
--provisioned-throughput ReadCapacityUnits=2,WriteCapacityUnits=1 | |
log "Waiting for table to appear" | |
aws dynamodb wait table-exists --table-name "$LOCK_TABLE" \ | |
--region "$REGION" | |
log "Finished creating dynamodb table $LOCK_TABLE" | |
fi | |
} | |
MODULE_STYLE=1 | |
while [ $# -gt 0 ] && [[ $1 == -* ]]; do | |
case "$1" in | |
-h|--help) | |
usage | |
exit 0 | |
;; | |
--module-style) | |
MODULE_STYLE=1 | |
;; | |
--shared-style) | |
MODULE_STYLE= | |
;; | |
*) | |
usage | |
echo_red >&2 "Unknown option: $1" | |
exit 1 | |
;; | |
esac | |
shift | |
done | |
if [ $# -ne 5 ] ; then | |
usage | |
exit 1 | |
fi | |
BUCKET=$1 | |
STATE=$2 | |
TF_DIR=$3 | |
REGION=$4 | |
LOCK_TABLE=$5 | |
if [ -n "$MODULE_STYLE" ]; then | |
log --blue "Setting up TF state (local, module style .terraform)" | |
else | |
log --blue "Setting up TF state and symlinking .terraform (shared style)" | |
fi | |
log "State file: $STATE" | |
log "State bucket: $BUCKET" | |
check_or_create_remote_state_resources | |
# Set up local .terraform directory with either the old or new .terraform | |
# directory management styles. | |
# | |
cd "${TF_DIR}" | |
# Sanity check: make sure we have a main.tf | |
assert_file_exists "main.tf" | |
if [ -z "$MODULE_STYLE" ]; then | |
log --blue "Setting up shared style .terraform symlink" | |
local_state_path=".deploy/$BUCKET/$REGION/$STATE/.terraform" | |
if [ ! -d "$local_state_path" ]; then | |
log "Creating new local state directory" | |
fi | |
mkdir -vp "$local_state_path" >&2 | |
if [ -L .terraform ]; then | |
run rm -v .terraform >&2 | |
elif [ -d .terraform ]; then | |
# We should no longer need to ever delete the .terraform directory. | |
echo_red >&2 "error: .terraform is a directory, not the expected symlink" | |
echo_red >&2 "Cowardly refusing to proceed" | |
exit 5 | |
fi | |
log "Linking .terraform to local state directory" | |
run ln -sv "$local_state_path" .terraform >&2 | |
fi | |
log --blue "Calling terraform init" | |
# https://github.com/hashicorp/terraform/issues/12762 | |
case "$(CHECKPOINT_DISABLE=1 terraform --version)" in | |
*v0.9.*|*v0.10.*|*v0.11.*|*v0.12.*) | |
terraform init \ | |
-backend-config="bucket=${BUCKET}" \ | |
-backend-config="key=${STATE}" \ | |
-backend-config="dynamodb_table=$LOCK_TABLE" \ | |
-backend-config="region=${REGION}" | |
;; | |
*) | |
echo_red >&2 "$0: ERROR: Unsupported terraform version" | |
exit 1 | |
;; | |
esac |
This file contains 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
#!/usr/bin/env bash | |
set -eu | |
# Try really hard not to let anything accidentally write to stdout. | |
# Point stdout at stderr and open FD 3 to point to the original stdout. | |
# Use echo >&3 to write to stdout hereafter. | |
exec 3>&1 1>&2 | |
BASENAME="$(basename "$0")" | |
if [ $# -ne 0 ]; then | |
cat >&2 <<EOM | |
usage: $BASENAME | |
Print the directory containing environment-specific variables. Clone the | |
private repo containing these variables if it doesn't exist. Within this | |
directory, the caller should source '\$ENV.sh'. | |
LOCATION OF PRIVATE REPO: | |
This script expects to find the private configuration checked out in a separate | |
repository located (from the root of this repo) at ../{repo-name}-private/. | |
Customize this path with \$IDENTITY_DEVOPS_PRIVATE_PATH. | |
If the checkout does not exist, it will offer to clone the repo for you. Set | |
environment variable \$IDENTITY_DEVOPS_PRIVATE_URL to configure the URL for | |
identity-devops-private, otherwise 'git remote get-url origin' will be used | |
with an appended '-private'. | |
Set SKIP_GIT_CLONE=1 in your environment to skip the prompt for the git | |
clone. | |
Set SKIP_GIT_PULL=1 in your environment to skip the automatic git pull. | |
EOM | |
exit 1 | |
fi | |
SENTINEL_ENV_NAME=prod | |
SKIP_GIT_CLONE="${SKIP_GIT_CLONE-}" | |
SKIP_GIT_PULL="${SKIP_GIT_PULL-}" | |
# shellcheck source=/dev/null | |
. "$(dirname "$0")/lib/common.sh" | |
# Determine the likely URL for identity-devops-private based on the "origin" | |
# git remote of the current repository (just by appending -private). | |
# Expects that the CWD is under the current git checkout. | |
get_private_url_from_origin() { | |
local origin_url private_url | |
origin_url="$(run git remote get-url origin)" | |
# splice off trailing .git if present | |
origin_url="${origin_url%.git}" | |
# splice off trailing / if present | |
origin_url="${origin_url%/}" | |
private_url="$origin_url-private" | |
echo >&2 "Inferred default identity-devops-private URL: $private_url" | |
echo "$private_url" | |
} | |
get_private_path() { | |
local toplevel basename | |
# We assume git rev-parse --show-toplevel returns an absolute path with no | |
# trailing slash. | |
toplevel="$(run git rev-parse --show-toplevel)" | |
if [ -z "$toplevel" ]; then | |
echo "This script needs to be run within a valid git repo." >&2 | |
return 1 | |
fi | |
basename="$(basename "$toplevel")" | |
# ../{basename}-private | |
echo "$(dirname "$toplevel")/$basename-private" | |
} | |
# usage: clone_private_repo PARENT_DIR | |
# | |
# Git clone the identity-devops-private repo under PARENT_DIR locally. | |
clone_private_repo() { | |
local parent_dir clone_url | |
parent_dir="$1" | |
clone_url="${IDENTITY_DEVOPS_PRIVATE_URL-$(get_private_url_from_origin)}" | |
pushd "$parent_dir" | |
run git clone "$clone_url" | |
popd | |
} | |
check_maybe_clone_private_repo() { | |
local path | |
path="$1" | |
if [ ! -d "$path" ]; then | |
echo >&2 "warning: Private repo is not checked out at $path" | |
if [ -n "$SKIP_GIT_CLONE" ]; then | |
echo >&2 "SKIP_GIT_CLONE is set, aborting." | |
return 1 | |
fi | |
if prompt_yn "Do you want to git clone the private repo?"; then | |
echo >&2 "OK, cloning..." | |
clone_private_repo "$(dirname "$path")" | |
if [ -d "$path" ]; then | |
echo >&2 "Clone done" | |
else | |
echo >&2 "Something went wrong cloning." | |
return 2 | |
fi | |
else | |
echo >&2 "OK, aborting." | |
return 1 | |
fi | |
fi | |
} | |
git_pull() { | |
if [ -n "$SKIP_GIT_PULL" ]; then | |
echo >&2 "SKIP_GIT_PULL is set, skipping git pull of private repo" | |
return | |
fi | |
local dir basename | |
dir="$1" | |
basename="$(basename "$dir")" | |
echo >&2 "Updating $basename, set env var SKIP_GIT_PULL=1 to skip" | |
local cur_branch | |
cur_branch="$(run git -C "$dir" symbolic-ref --short HEAD)" | |
if [ "$cur_branch" != "master" ]; then | |
echo_yellow >&2 \ | |
"Warning: current $basename branch is $cur_branch, not master" | |
fi | |
if run git -C "$dir" pull --ff-only --no-rebase; then | |
return | |
else | |
echo_red >&2 "Error: git pull failed" | |
fi | |
} | |
log "Looking for env-specific variables" | |
private_path="${IDENTITY_DEVOPS_PRIVATE_PATH-$(get_private_path)}" | |
check_maybe_clone_private_repo "$private_path" | |
git_pull "$private_path" | |
# assume there should be an env called prod or else bail out | |
if [ ! -e "$private_path/env/$SENTINEL_ENV_NAME.sh" ]; then | |
echo >&2 "Somehow $private_path/env/$SENTINEL_ENV_NAME.sh is missing!" | |
exit 3 | |
fi | |
found="$private_path/env" | |
log "Found env variables at $found/" | |
echo >&3 "$found" |
This file contains 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
#!/bin/bash | |
# | |
# Currently, our automation depends on a number of environment variables to | |
# configure terraform. See | |
# https://www.terraform.io/docs/configuration/variables.html#environment-variables. | |
# | |
# There are some variables that are the same across each run, but some user | |
# specific configuration that's mixed in. This script is an attempt to factor | |
# out and check for some of that configuration. | |
# | |
# Do not source this directly, only source it from another script that depends | |
# on this environment configuration, because it does some error checking and | |
# may exit your shell. | |
# See: https://github.com/18F/identity-devops/pull/252 | |
# Increment this to make breaking changes in the environment config. The | |
# load-env.sh script will bail out if the environment does not set a variable | |
# $ID_ENV_COMPAT_VERSION >= this value, which provides a way to ensure that new | |
# scripts get run with a new enough environment config. | |
ENFORCED_ENV_COMPAT_VERSION=4 | |
BASENAME="$(basename "${BASH_SOURCE[0]}")" | |
DIRNAME="$(dirname "${BASH_SOURCE[0]}")" | |
# shellcheck source=/dev/null | |
. "$DIRNAME/lib/common.sh" | |
usage() { | |
cat >&2 <<EOM | |
Usage: $BASENAME ENVIRONMENT_NAME | |
Load environment files for ENVIRONMENT_NAME, and do some sanity checking to | |
ensure we have all the necessary variables. | |
This script loads environment variables from identity-devops-private. It is not | |
typically called directly, but instead is invoked by \`deploy\`. | |
NOTE: This script might exit your shell if you source it and don't have all the | |
right stuff set up. It's a good idea to only source it from another script, or | |
just to be really sure you've set all the necessary variables. | |
Set \$ENV_DEBUG=1 to print environment variables once we finish. | |
Set \$SKIP_GIT_PULL=1 to skip automatic git pull of identity-devops-private. | |
(Expert mode only) Set \$ID_ENV_FILE to override the normal environment loader | |
and use a single env file. Not recommended unless you have particular reason to | |
do so. | |
EOM | |
} | |
# Make sure the loaded environment is new enough. | |
enforce_environment_compat_version() { | |
if [ -z "${ID_ENV_COMPAT_VERSION-}" ]; then | |
echo_red "$BASENAME: error: \$ID_ENV_COMPAT_VERSION not set by env" | |
echo_red "This ought to be set in identity-devops-private/env/*.sh" | |
echo_red "Maybe check if that repo is up-to-date?" | |
return 1 | |
fi | |
if [ "$ID_ENV_COMPAT_VERSION" -lt "$ENFORCED_ENV_COMPAT_VERSION" ]; then | |
echo_red "$BASENAME: error: \$ID_ENV_COMPAT_VERSION set by env is old" | |
echo_red "This means your identity-devops-private clone is outdated." | |
echo_red "Try updating that repo to latest master?" | |
echo_red "\$ID_ENV_COMPAT_VERSION: $ID_ENV_COMPAT_VERSION" | |
echo_red "\$ENFORCED_ENV_COMPAT_VERSION: $ENFORCED_ENV_COMPAT_VERSION" | |
return 1 | |
fi | |
} | |
if [ $# -ne 1 ]; then | |
usage | |
# The `return 1 || exit 1` pattern allows us to return non-zero exit codes | |
# to the user without exiting their shell if they are sourcing this file. | |
return 1 2>/dev/null || exit 1 | |
fi | |
echo_blue >&2 "$BASENAME $*" | |
export TF_VAR_env_name="$1" | |
ID_ENV_FILE="${ID_ENV_FILE-}" | |
if [ -n "$ID_ENV_FILE" ]; then | |
echo_red "Warning: not using normal variables from identity-devops-private" | |
echo_red "Loading variables as requested from '$ID_ENV_FILE'" | |
# shellcheck source=/dev/null | |
. "$ID_ENV_FILE" | |
enforce_environment_compat_version || return 4 2>/dev/null || exit 4 | |
return 0 2>/dev/null || exit 0 | |
fi | |
# Locate and potentially git clone identity-devops-private | |
ID_ENV_DIR="$(run "$DIRNAME/get-private-env.sh")" | |
if [ -z "$ID_ENV_DIR" ]; then | |
echo_red "get-private-env.sh failed" | |
return 3 >/dev/null || exit 3 | |
fi | |
if [ "$(wc -l <<< "$ID_ENV_DIR")" -ne 1 ]; then | |
echo_red "get-private-env.sh bug: file path shouldn't be multiple lines" | |
echo_red "Path: '$ID_ENV_DIR'" | |
return 3 >/dev/null || exit 3 | |
fi | |
env_specific_path="$ID_ENV_DIR/$TF_VAR_env_name.sh" | |
if [ -e "$env_specific_path" ]; then | |
log "Sourcing env-specific private env vars." | |
log "Path: '$env_specific_path'" | |
# shellcheck source=/dev/null | |
. "$env_specific_path" | |
else | |
log "No env-specific vars file found: ($TF_VAR_env_name.sh)" | |
echo_red >&2 "Unknown environment: '$TF_VAR_env_name'" | |
echo_red >&2 "Please create env file $TF_VAR_env_name.sh in $ID_ENV_DIR" | |
return 4 2>/dev/null || exit 4 | |
fi | |
enforce_environment_compat_version || return 4 2>/dev/null || exit 4 | |
if [ -n "${ENV_DEBUG-}" ]; then | |
env | |
fi | |
if env | grep ^TV_VAR_; then | |
echo_red "Found variables named TV_VAR_, but you probably meant TF_VAR_!" | |
echo_red "$(env | grep ^TV_VAR_)" | |
fi | |
# shellcheck disable=SC2163,SC2086 | |
export ${!TF_VAR_*} |
This file contains 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
#!/usr/bin/env bash | |
set -euo pipefail | |
# shellcheck source=/dev/null | |
. "$(dirname "$0")/lib/common.sh" | |
# Directory where we will install terraform | |
TERRAFORM_DOT_D="${TERRAFORM_DOT_D-}" | |
if [ -z "$TERRAFORM_DOT_D" ]; then | |
TERRAFORM_DOT_D="$HOME/.terraform.d" | |
if [ ! -d "$TERRAFORM_DOT_D" ]; then | |
run mkdir -vp "$TERRAFORM_DOT_D" | |
fi | |
fi | |
TF_DEPRECATED_DIR="${TF_DEPRECATED_DIR-"$HOME/.terraform-plugins"}" | |
TERRAFORM_EXE_DIR="$TERRAFORM_DOT_D/tf-switch" | |
TERRAFORM_PLUGIN_DIR="$TERRAFORM_DOT_D/plugin-cache" | |
TF_DOWNLOAD_URL="https://releases.hashicorp.com/terraform" | |
# Set this to skip installing TF symlink to TERRAFORM_SYMLINK | |
ID_TF_SKIP_SYMLINK="${ID_TF_SKIP_SYMLINK-}" | |
# Set this to skip GPG verification | |
ID_TF_SKIP_GPG="${ID_TF_SKIP_GPG-}" | |
# Set this to skip the terraform plugin cache check | |
ID_TF_SKIP_PLUGIN_CACHE="${ID_TF_SKIP_PLUGIN_CACHE-}" | |
# Location of installed TF symlink | |
TERRAFORM_SYMLINK="${TERRAFORM_SYMLINK-/usr/local/bin/terraform}" | |
SUDO_LN= | |
# Hashicorp GPG key fingerprint | |
TF_GPG_KEY_FINGERPRINT=91A6E7F85D05C65630BEF18951852D87348FFC4C | |
TF_GPG_KEY_CONTENT=' | |
-----BEGIN PGP PUBLIC KEY BLOCK----- | |
mQENBFMORM0BCADBRyKO1MhCirazOSVwcfTr1xUxjPvfxD3hjUwHtjsOy/bT6p9f | |
W2mRPfwnq2JB5As+paL3UGDsSRDnK9KAxQb0NNF4+eVhr/EJ18s3wwXXDMjpIifq | |
fIm2WyH3G+aRLTLPIpscUNKDyxFOUbsmgXAmJ46Re1fn8uKxKRHbfa39aeuEYWFA | |
3drdL1WoUngvED7f+RnKBK2G6ZEpO+LDovQk19xGjiMTtPJrjMjZJ3QXqPvx5wca | |
KSZLr4lMTuoTI/ZXyZy5bD4tShiZz6KcyX27cD70q2iRcEZ0poLKHyEIDAi3TM5k | |
SwbbWBFd5RNPOR0qzrb/0p9ksKK48IIfH2FvABEBAAG0K0hhc2hpQ29ycCBTZWN1 | |
cml0eSA8c2VjdXJpdHlAaGFzaGljb3JwLmNvbT6JATgEEwECACIFAlMORM0CGwMG | |
CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEFGFLYc0j/xMyWIIAIPhcVqiQ59n | |
Jc07gjUX0SWBJAxEG1lKxfzS4Xp+57h2xxTpdotGQ1fZwsihaIqow337YHQI3q0i | |
SqV534Ms+j/tU7X8sq11xFJIeEVG8PASRCwmryUwghFKPlHETQ8jJ+Y8+1asRydi | |
psP3B/5Mjhqv/uOK+Vy3zAyIpyDOMtIpOVfjSpCplVRdtSTFWBu9Em7j5I2HMn1w | |
sJZnJgXKpybpibGiiTtmnFLOwibmprSu04rsnP4ncdC2XRD4wIjoyA+4PKgX3sCO | |
klEzKryWYBmLkJOMDdo52LttP3279s7XrkLEE7ia0fXa2c12EQ0f0DQ1tGUvyVEW | |
WmJVccm5bq25AQ0EUw5EzQEIANaPUY04/g7AmYkOMjaCZ6iTp9hB5Rsj/4ee/ln9 | |
wArzRO9+3eejLWh53FoN1rO+su7tiXJA5YAzVy6tuolrqjM8DBztPxdLBbEi4V+j | |
2tK0dATdBQBHEh3OJApO2UBtcjaZBT31zrG9K55D+CrcgIVEHAKY8Cb4kLBkb5wM | |
skn+DrASKU0BNIV1qRsxfiUdQHZfSqtp004nrql1lbFMLFEuiY8FZrkkQ9qduixo | |
mTT6f34/oiY+Jam3zCK7RDN/OjuWheIPGj/Qbx9JuNiwgX6yRj7OE1tjUx6d8g9y | |
0H1fmLJbb3WZZbuuGFnK6qrE3bGeY8+AWaJAZ37wpWh1p0cAEQEAAYkBHwQYAQIA | |
CQUCUw5EzQIbDAAKCRBRhS2HNI/8TJntCAClU7TOO/X053eKF1jqNW4A1qpxctVc | |
z8eTcY8Om5O4f6a/rfxfNFKn9Qyja/OG1xWNobETy7MiMXYjaa8uUx5iFy6kMVaP | |
0BXJ59NLZjMARGw6lVTYDTIvzqqqwLxgliSDfSnqUhubGwvykANPO+93BBx89MRG | |
unNoYGXtPlhNFrAsB1VR8+EyKLv2HQtGCPSFBhrjuzH3gxGibNDDdFQLxxuJWepJ | |
EK1UbTS4ms0NgZ2Uknqn1WRU1Ki7rE4sTy68iZtWpKQXZEJa0IGnuI2sSINGcXCJ | |
oEIgXTMyCILo34Fa/C6VCm2WBgz9zZO8/rHIiQm1J5zqz0DrDwKBUM9C | |
=LYpS | |
-----END PGP PUBLIC KEY BLOCK----- | |
' | |
EXE_SUFFIX= | |
case "$OSTYPE" in | |
*darwin*) TF_OS=darwin ;; | |
linux-gnu) TF_OS=linux ;; | |
cygwin|msys) | |
TF_OS=windows | |
EXE_SUFFIX=.exe | |
;; | |
solaris*) TF_OS=solaris ;; | |
freebsd*) TF_OS=freebsd ;; | |
*) | |
echo >&2 "Unknown OSTYPE '$OSTYPE'" | |
echo >&2 "If you know the appropriate mapping, add this to the OSTYPE" | |
echo >&2 "case statement: using $TF_DOWNLOAD_URL" | |
exit 3 | |
;; | |
esac | |
case "$(uname -m)" in | |
x86_64|amd64) TF_ARCH=amd64 ;; | |
i386|i686) TF_ARCH=386 ;; | |
arm*) TF_ARCH=arm ;; | |
*) | |
echo >&2 "Unknown architecture '$(uname -m)'" | |
exit 4 | |
;; | |
esac | |
usage() { | |
cat >&2 <<EOM | |
usage: $(basename "$0") TERRAFORM_VERSION | |
Download and install precompiled terraform binaries from Github. | |
Keep multiple versions installed under $TERRAFORM_EXE_DIR and | |
symlink the chosen active binary at $TERRAFORM_SYMLINK. | |
Much of this script's behavior is configurable by environment variables, such | |
as TERRAFORM_SYMLINK to set the symlink install location, or TERRAFORM_DOT_D to | |
override the location used instead of ~/.terraform.d/. | |
For example: | |
$(basename "$0") 0.9.11 | |
Known Terraform versions: | |
EOM | |
echo "$KNOWN_TF_VERSIONS" | cut -d' ' -f1 | sed 's/^/ /' | |
} | |
# If macOS shipped with a modern version of bash (i.e. Bash 4.0), we would have | |
# associative arrays and wouldn't need this hack. | |
# | |
# Upstream references for the releases: | |
# - https://releases.hashicorp.com/terraform/ | |
# | |
KNOWN_TF_VERSIONS=' | |
0.9.11 | |
0.10.8 | |
0.11.14 | |
0.12.17 | |
' | |
sha256_cmd() { | |
if which sha256sum >/dev/null; then | |
run sha256sum "$@" | |
elif which shasum >/dev/null; then | |
run shasum -a 256 "$@" | |
else | |
echo >&2 "Could not find sha256sum or shasum" | |
return 1 | |
fi | |
} | |
install_tf_version() { | |
local target download_prefix terraform_exe tmpdir checksum_file csum | |
local download_basename | |
target="$1" | |
terraform_exe="$2" | |
echo_blue "Installing terraform version $target" | |
download_prefix="$TF_DOWNLOAD_URL/$target" | |
download_basename="terraform_${target}_${TF_OS}_${TF_ARCH}.zip" | |
checksum_file="terraform_${target}_SHA256SUMS" | |
tmpdir="$(mktemp -d)" | |
( | |
echo >&2 "+ cd '$tmpdir'" | |
cd "$tmpdir" | |
run curl -Sf --remote-name-all \ | |
"${download_prefix}/${checksum_file}"{,.sig} \ | |
"${download_prefix}/${download_basename}" | |
if [ -n "$ID_TF_SKIP_GPG" ]; then | |
echo "\$ID_TF_SKIP_GPG is set, skipping GPG verification" | |
else | |
echo >&2 "Checking GPG signature" | |
if ! run gpg --batch --list-keys "$TF_GPG_KEY_FINGERPRINT"; then | |
#echo >&2 "Fetching Hashicorp GPG key" | |
#run gpg --recv-keys "$TF_GPG_KEY_FINGERPRINT" | |
echo >&2 "Importing Hashicorp GPG key" | |
run gpg --import <<< "$TF_GPG_KEY_CONTENT" | |
fi | |
run gpg --batch --status-fd 1 --verify "$checksum_file"{.sig,} \ | |
| grep '^\[GNUPG:\] VALIDSIG '"$TF_GPG_KEY_FINGERPRINT" | |
echo >&2 "OK, finished verifying" | |
fi | |
echo >&2 "Checking SHA256 checksum" | |
csum="$(run grep "${TF_OS}_${TF_ARCH}.zip" "$checksum_file")" | |
sha256_cmd -c <<< "$csum" | |
echo >&2 "OK" | |
run unzip -d "$tmpdir" "$tmpdir/$download_basename" | |
mv -v "$tmpdir/terraform" "$terraform_exe" | |
) | |
rm -r "$tmpdir" | |
echo_blue "New terraform was installed to $terraform_exe" | |
} | |
setup_terraform_plugin_cache() { | |
if [ -n "$ID_TF_SKIP_PLUGIN_CACHE" ]; then | |
echo >&2 "Skipping terraform plugin cache management as requested" | |
return | |
fi | |
if [ ! -d "$TERRAFORM_PLUGIN_DIR" ]; then | |
run mkdir -v "$TERRAFORM_PLUGIN_DIR" | |
fi | |
if [ -e "$HOME/.terraformrc" ]; then | |
if ! grep "^plugin_cache_dir " "$HOME/.terraformrc" >/dev/null; then | |
cat >&2 <<EOM | |
Please modify ~/.terraformrc to include something like: | |
plugin_cache_dir = "\$HOME/.terraform.d/plugin-cache" | |
If you want to skip this check and not cache plugins, set | |
ID_TF_SKIP_PLUGIN_CACHE=1 when running this script. | |
EOM | |
if prompt_yn "Answer Yes when this is done"; then | |
setup_terraform_plugin_cache | |
else | |
return 1 | |
fi | |
fi | |
else | |
echo "$HOME/.terraformrc does not exist." | |
if prompt_yn "Should I create it for you?"; then | |
cat > "$HOME/.terraformrc" <<EOM | |
plugin_cache_dir = "\$HOME/.terraform.d/plugin-cache" | |
EOM | |
echo "Created:" | |
cat "$HOME/.terraformrc" | |
else | |
return 1 | |
fi | |
fi | |
} | |
install_tf_symlink() { | |
local terraform_exe | |
terraform_exe="$1" | |
if [ -n "$ID_TF_SKIP_SYMLINK" ]; then | |
echo "ID_TF_SKIP_SYMLINK is set, not installing terraform symlink" | |
return | |
fi | |
# if homebrew terraform is installed, unlink it | |
if which brew >/dev/null; then | |
# if we already have any terraforms, unlink first | |
if run brew list terraform; then | |
run brew unlink terraform | |
fi | |
fi | |
echo_blue "Installing terraform symlink to $TERRAFORM_SYMLINK" | |
if [ -n "$SUDO_LN" ]; then | |
run sudo ln -sfv "$terraform_exe" "$TERRAFORM_SYMLINK" | |
else | |
run ln -sfv "$terraform_exe" "$TERRAFORM_SYMLINK" | |
fi | |
} | |
deprecation_check() { | |
if grep "$TF_DEPRECATED_DIR" "$HOME/.terraformrc" >/dev/null; then | |
echo_yellow >&2 "Warning: found reference to $TF_DEPRECATED_DIR in $HOME/.terraformrc" | |
echo_yellow >&2 "You may want to remove the acme plugin entirely from your ~/.terraformrc" | |
fi | |
if [ -d "$TF_DEPRECATED_DIR" ]; then | |
echo_yellow >&2 "Warning: found directory from older terraform-switch.sh" | |
echo_yellow >&2 "You may want to run: rm -r $TF_DEPRECATED_DIR" | |
fi | |
} | |
main() { | |
local target_version current_version terraform_exe | |
target_version="$1" | |
if which terraform >/dev/null; then | |
current_version="$(get_terraform_version)" | |
else | |
current_version= | |
fi | |
setup_terraform_plugin_cache | |
deprecation_check | |
if [ "v$target_version" = "$current_version" ]; then | |
echo "Already running terraform $target_version" | |
return | |
fi | |
if ! which gpg >/dev/null; then | |
echo >&2 "$(basename "$0"): error, gpg not found" | |
echo >&2 "Set ID_TF_SKIP_GPG=1 if you want to skip the signature check" | |
return 1 | |
fi | |
if [ ! -e "$TERRAFORM_EXE_DIR" ]; then | |
run mkdir -v "$TERRAFORM_EXE_DIR" | |
fi | |
terraform_exe="$TERRAFORM_EXE_DIR/terraform_${target_version}$EXE_SUFFIX" | |
if [ -e "$terraform_exe" ]; then | |
echo_blue "Terraform $target_version already installed at $terraform_exe" | |
else | |
echo "Terraform $target_version does not appear to be installed." | |
if prompt_yn "Install it?"; then | |
install_tf_version "$target_version" "$terraform_exe" | |
fi | |
fi | |
install_tf_symlink "$terraform_exe" | |
echo_blue 'All done!' | |
} | |
if [ $# -lt 1 ]; then | |
usage | |
exit 1 | |
fi | |
main "$@" |
This file contains 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
#!/usr/bin/env bash | |
# function to exit process with error message | |
die() { echo_red "$*" >&2 ; exit 1; } | |
# http://redsymbol.net/articles/unofficial-bash-strict-mode/ | |
set -euo pipefail | |
# Try really hard not to let anything accidentally write to stdout. | |
# Point stdout at stderr and open FD 3 to point to the original stdout. | |
# Use echo >&3 to write to stdout hereafter. | |
exec 3>&1 1>&2 | |
basename="$(basename "$0")" | |
usage() { | |
cat >&2 <<EOM | |
# identity-devops-private configs: | |
usage: $basename [OPTIONS] ENV_NAME TERRAFORM_DIR [TERRAFORM_COMMANDS...] | |
# newer module-style configs: | |
usage: $basename [OPTIONS] TERRAFORM_DIR/ENV_NAME [TERRAFORM_COMMANDS...] | |
Run TERRAFORM_COMMANDS against environment ENV_NAME using the configuration in | |
TERRAFORM_DIR. | |
Arguments: | |
TERRAFORM_DIR: | |
Directory path relative to the repo root to a terraform directory such | |
as "terraform_app" | |
ENV_NAME: | |
Environment name for the deployment, e.g. "dev", "prod", or "global". | |
The specifics will differ between terraform dirs. | |
TERRAFORM_COMMANDS: | |
Arguments and options passed to terraform, such as "plan", "apply", or | |
"state list". | |
Options: | |
-h, --help Display this message | |
--import-remote-state Bootstrap terraform remote state management by | |
importing into terraform management the terraform | |
remote state S3 bucket and DynamoDB table that are | |
used to manage TF remote state, which are | |
automatically created by configure_state_bucket.sh. | |
For older terraform directories that use identity-devops-private, env-specific | |
config variables will be loaded automatically from identity-devops-private. | |
This script will automatically delete .terraform/ and set it up as a symlink to | |
a common .deploy/ subdirectory. | |
Example: | |
$basename dev terraform-app plan | |
For newer terraform directories that use module-style configuration, any | |
env-specific config variables will be loaded directly from the specified | |
subdirectory, and identity-devops-private will not be used. In this mode, | |
.terraform/ is left as-is and not managed specially, which makes it easier to | |
use terraform without invoking this script. | |
Example: | |
$basename terraform-sms/sandbox plan | |
EOM | |
} | |
# usage: module_style_check_required_files TF_DIR | |
module_style_check_required_files() { | |
local tf_dir | |
tf_dir="$1" | |
if [ ! -e "$tf_dir/main.tf" ]; then | |
echo_red >&2 "$basename: No such file: $tf_dir/main.tf" | |
echo_red >&2 "Are you sure that $tf_dir is a module-style TF_DIR?" | |
return 1 | |
fi | |
if [ ! -e "$tf_dir/env-vars.sh" ]; then | |
echo_red >&2 "$basename: No such file: $tf_dir/env-vars.sh" | |
echo_red >&2 "Are you sure that $tf_dir is a module-style TF_DIR?" | |
return 1 | |
fi | |
} | |
# usage: verify_not_module_style | |
# | |
# Variables: $TF_DIR, $ENVIRONMENT | |
# | |
# Ensure that the target directory is not a module/subdirectory style TF_DIR. | |
# | |
verify_not_module_style() { | |
local tf_dir test_file | |
tf_dir="$TF_DIR/$ENVIRONMENT" | |
test_file="$tf_dir/env-vars.sh" | |
if [ -e "$test_file" ]; then | |
echo_red >&2 " | |
Error: mismatch between expected and apparent .terraform directory style. | |
I thought this was a shared-style TF_DIR that uses identity-devops-private. | |
However, it contains the env-vars.sh that is only supposed to exist in | |
module/subdirectory style TF_DIRs. | |
$basename: file unexpectedly exists: $test_file | |
Are you sure that $tf_dir is a shared-style TF_DIR? | |
Try instead: | |
$basename $TF_DIR/$ENVIRONMENT ${TF_CMD[*]} | |
" | |
return 1 | |
fi | |
} | |
if [ $# -lt 2 ]; then | |
usage | |
exit 1 | |
fi | |
repo_root_before_cd="$(git rev-parse --show-toplevel 2>/dev/null || true)" | |
# This script assumes it is being run from the repo root. | |
cd "$(dirname "$0")" | |
cd "$(git rev-parse --show-toplevel)" | |
# shellcheck source=/dev/null | |
. "./bin/lib/common.sh" | |
# Make sure our repo root didn't change after cd, because that implies that the | |
# user is running a different script than they expected. | |
verify_repo_root_unchanged "$repo_root_before_cd" "$basename" | |
ORIGIN_REMOTE_NAME="${ORIGIN_REMOTE_NAME-origin}" | |
# Parse options | |
IMPORT_REMOTE_STATE= | |
while [ $# -gt 0 ] && [[ $1 == -* ]]; do | |
case "$1" in | |
-h|--help) | |
usage | |
exit 0 | |
;; | |
--import-remote-state) | |
IMPORT_REMOTE_STATE=1 | |
;; | |
*) | |
usage | |
exit 1 | |
;; | |
esac | |
shift | |
done | |
# This is a bit of a mess since we handle both old identity-devops-private | |
# style config and new module-style config. Once we deprecate the former, we can | |
# simplify the argument parsing dramatically. | |
ENVIRONMENT_OR_TF_DIR=$1; shift | |
if [ -d "$ENVIRONMENT_OR_TF_DIR" ]; then | |
# assume new module-style config if first arg is a directory that exists | |
MODULE_STYLE=1 | |
ENVIRONMENT_OR_TF_DIR="${ENVIRONMENT_OR_TF_DIR%/}" # strip off trailing / | |
# split input on '/' | |
IFS='/' read -ra parts <<< "$ENVIRONMENT_OR_TF_DIR" | |
# Turn "./foo/bar" into "foo/bar" | |
if [ "${parts[0]-}" = "." ]; then | |
parts=("${parts[@]:1}") | |
fi | |
if [ "${#parts[@]}" -ne 2 ]; then | |
echo_red >&2 "Error: could not parse first argument as TF_DIR." | |
echo_red >&2 | |
echo_red >&2 "First arg is a directory, so I'm assuming it is a new" | |
echo_red >&2 "module-style terraform dir. This format is expected to" | |
echo_red >&2 "contain two '/'-separated parts." | |
echo_red >&2 | |
echo_red >&2 "Expected format: '<TF_DIR>/<ENV>/'" | |
echo_red >&2 "Received: '$ENVIRONMENT_OR_TF_DIR'" | |
exit 2 | |
fi | |
TF_DIR="${parts[0]}" | |
ENVIRONMENT="${parts[1]}" | |
TF_DIR_FULL="${parts[0]}/${parts[1]}" | |
echo_blue >&2 "TF_DIR: $TF_DIR (style: module vars)" | |
echo_blue >&2 "ENVIRONMENT: $ENVIRONMENT" | |
module_style_check_required_files "$ENVIRONMENT_OR_TF_DIR" | |
else | |
# otherwise use traditional identity-devops-private style | |
MODULE_STYLE= | |
# environment is first arg | |
ENVIRONMENT="$ENVIRONMENT_OR_TF_DIR" | |
# tf_dir is second arg | |
TF_DIR=$1; shift | |
TF_DIR="${TF_DIR%/}" # strip off trailing / | |
TF_DIR_FULL="$TF_DIR" | |
echo_blue >&2 "TF_DIR: $TF_DIR (style: identity-devops-private vars)" | |
echo_blue >&2 "ENVIRONMENT: $ENVIRONMENT" | |
fi | |
# If we're loading the terraform remote state, then override our default | |
# argument parsing. (Yeah, this is ugly.) | |
if [ -n "$IMPORT_REMOTE_STATE" ]; then | |
if [ $# -gt 0 ]; then | |
usage | |
echo_red "Cannot pass arguments when --import-remote-state is set" | |
exit 1 | |
fi | |
echo_blue "Importing TF remote state, as requested..." | |
TF_CMD=("command-should-not-be-reached") | |
else | |
# Consume all remaining arguments as Terraform command | |
if [ $# -eq 0 ]; then | |
usage | |
exit 1 | |
fi | |
TF_CMD=("$@") | |
fi | |
case "$TF_DIR" in | |
terraform-dns|terraform-cloudtrail|terraform-common) | |
# These configurations apply account wide, so set the environment to the | |
# account ID. | |
# | |
# https://stackoverflow.com/a/33791322 | |
if [ "$ENVIRONMENT" != "global" ]; then | |
echo_red >&2 "$TF_DIR applies to the whole AWS account!" | |
echo_red >&2 "For safety, pass 'global' as the environment name" | |
die "You provided '$ENVIRONMENT'" | |
fi | |
ENVIRONMENT="account_global_$(run aws sts get-caller-identity --output text --query 'Account')" | |
echo >&2 "Forcing environment name to be $ENVIRONMENT since $TF_DIR is account wide configuration" | |
;; | |
*/?*) | |
echo_red >&2 "\$TF_DIR cannot contain slashes, must be at top level" | |
die "You provided '$TF_DIR'" | |
;; | |
esac | |
check_release_versioning() { | |
local strict_check rev_name | |
echo >&2 "DEPLOY: Checking whether environment versioning is recommended" | |
case "$TF_DIR" in | |
terraform-app|terraform-sms) | |
# versioning enabled | |
;; | |
*) | |
# versioning not enabled | |
return | |
;; | |
esac | |
case "$ENVIRONMENT" in | |
# strict checks in these user-facing environments | |
prod|staging|int) | |
strict_check=1 | |
;; | |
# limited checks in these testing environments | |
dev|qa|pt|dm) | |
strict_check= | |
;; | |
# no checking in other environments | |
*) | |
return | |
;; | |
esac | |
rev_name="$(git rev-parse --abbrev-ref HEAD)" | |
# make sure we're on stages/$ENVIRONMENT | |
if [ "$rev_name" != "stages/$ENVIRONMENT" ]; then | |
echo_yellow >&2 " | |
warning: Current git branch is not the expected stages/$ENVIRONMENT | |
Environment: $ENVIRONMENT | |
Expected branch: stages/$ENVIRONMENT | |
Actual branch: $rev_name | |
" | |
# check is fatal for strict environments | |
if [ -n "$strict_check" ] && [ -z "${DEPLOY_WEIRD_BRANCH-}" ]; then | |
echo_red >&2 "error: branch name must be stages/$ENVIRONMENT" | |
echo_red >&2 "set DEPLOY_WEIRD_BRANCH=1 to skip this check" | |
return 2 | |
fi | |
else | |
# make sure local stages/<env> is the same as origin/stages/<env> | |
# fetch latest from remote | |
if ! run git fetch "$ORIGIN_REMOTE_NAME"; then | |
echo_yellow >&2 "git fetch failed" | |
fi | |
local local_ref origin_ref | |
local_ref="$(git rev-parse "stages/$ENVIRONMENT")" | |
origin_ref="$(git rev-parse "$ORIGIN_REMOTE_NAME/stages/$ENVIRONMENT")" | |
if [ "$local_ref" != "$origin_ref" ]; then | |
echo_yellow >&2 " | |
Warning: Local stages branch differs from origin stages branch! | |
Maybe you need to git push? | |
stages/$ENVIRONMENT: $local_ref | |
$ORIGIN_REMOTE_NAME/stages/$ENVIRONMENT: $origin_ref | |
You can also set ORIGIN_REMOTE_NAME if your primary git remote is named | |
something other than 'origin'. | |
" | |
# check is fatal for strict environments | |
if [ -n "$strict_check" ] && [ -z "${DEPLOY_WEIRD_BRANCH-}" ]; then | |
echo_red >&2 "error: local and remote stages branches differ" | |
echo_red >&2 "set DEPLOY_WEIRD_BRANCH=1 to skip this check" | |
return 3 | |
fi | |
fi | |
fi | |
if [ -n "$strict_check" ]; then | |
if [[ ( $(cat VERSION.txt) == *pre* || | |
! "$(git log -n 1 --pretty=format:'%d')" =~ tag: ) ]] | |
then | |
echo_yellow >&2 " | |
############################################################################### | |
# INFORMATIONAL # | |
############################################################################### | |
We would like to start using tagged release versions that we carry through | |
environments for user-facing environments. | |
The $ENVIRONMENT environment ideally should be deployed from a tagged, | |
non-prerelease version because it is one of the environments officially | |
supported by the devops team for client use. | |
Please consider running \`rake release\` to edit VERSION.txt and create a | |
tagged release. | |
See https://github.com/18F/identity-devops/tree/master/doc/process/releases.md | |
for more details about our release tools. | |
The current version in VERSION.txt is: $(cat VERSION.txt) | |
" | |
fi | |
fi | |
} | |
# Verify that the account ID expected from env vars matches the actual AWS | |
# account that we connect to when running AWS commands. This isn't as useful | |
# for newer module-style terraform directories, which can independently specify | |
# 'allowed_account_ids = ["01234..."]' in their main.tf files. | |
check_account_id() { | |
real_account_id="$(run aws sts get-caller-identity --output text --query 'Account')" | |
if [ "$real_account_id" != "$aws_account_id" ]; then | |
echo_red "Account ID from environment does not match actual account" | |
echo_red "From env vars: $aws_account_id" | |
echo_red "Actual account ID: $real_account_id" | |
echo_red "Are you sure you set AWS_PROFILE / secret key right?" | |
die "error: Account ID mismatch" | |
fi | |
} | |
# Use the newer, massively simpler, module style of keeping env vars in subdirs. | |
load_env_module_style() { | |
local env_vars_file | |
env_vars_file="$TF_DIR/$ENVIRONMENT/env-vars.sh" | |
echo >&2 "DEPLOY: Loading module-style env variables from $env_vars_file..." | |
# shellcheck source=/dev/null | |
. "$env_vars_file" | |
if [ -z "${aws_account_id-}" ]; then | |
echo_red "Expected to find var \$aws_account_id in env vars file" | |
return 3 | |
fi | |
MOVED_TO_TFVARS= | |
STATE="${TF_DIR_FULL}.tfstate" | |
} | |
# Load and validate environment variables from identity-devops-private using | |
# bin/load-env.sh and bin/get-tfvars-for-env | |
load_env_devops_private_style() { | |
echo >&2 "DEPLOY: Loading shared-style environment variables..." | |
verify_not_module_style | |
. bin/load-env.sh "$ENVIRONMENT" | |
if [ -z "${TF_VAR_account_id-}" ]; then | |
echo_red >&2 "Expected to find TF_VAR_account_id in private env" | |
return 3 | |
fi | |
aws_account_id="$TF_VAR_account_id" | |
if [ -n "$MOVED_TO_TFVARS" ]; then | |
echo >&2 "Looks like this environment has switched to using .tfvars files" | |
# read a list of .tfvars files printed by get-tfvars-for-env | |
tfvars_names="$(bin/get-tfvars-for-env "$aws_account_id" "$ENVIRONMENT")" | |
# render an array prefixing each filename with -var-file | |
# in bash 4 we could use mapfile instead | |
tf_vars_opts=() | |
while IFS= read -r line; do | |
tf_vars_opts+=( "-var-file" "$line" ) | |
done <<< "$tfvars_names" | |
fi | |
# Check that current version of terraform is supported by our environment. This | |
# array should be set in the environment-specific variables. | |
check_terraform_version "${ID_SUPPORTED_TERRAFORM_VERSIONS[@]}" >&2 | |
STATE="${TF_DIR}/terraform-${TF_VAR_env_name}.tfstate" | |
case "$TF_DIR" in | |
terraform-dns|terraform-cloudtrail|terraform-common) | |
echo "This TF_DIR uses a common state file, not env-specific" | |
STATE=${TF_DIR}/terraform.tfstate | |
;; | |
esac | |
} | |
import_remote_state_resources() { | |
local addr1 addr2 cmd1 cmd2 | |
# Import the terraform remote state S3 bucket and remote state lock table. | |
# The commands will end up looking something like: | |
# terraform import $prefix.tf-state.aws_s3_bucket.tf-state $TERRAFORM_STATE_BUCKET | |
# terraform import \ | |
# module.main.module.tf-state.aws_dynamodb_table.tf-lock-table $ | |
# terraform import module.... ID_state_lock_table | |
if [ -z "${ID_state_module_prefix-}" ]; then | |
echo_red "error: ID_state_module_prefix is unset" | |
echo_red "This must be set to the terraform address of the module" | |
echo_red "where the remote state resources should be imported." | |
echo_red "For example, 'module.main.module.tf-state'" | |
return 5 | |
fi | |
# Prepare commands for execution and prompt for confirmation / edits | |
while true; do | |
addr1="$ID_state_module_prefix.aws_s3_bucket.tf-state" | |
addr2="$ID_state_module_prefix.aws_dynamodb_table.tf-lock-table" | |
cmd1=(terraform import "$addr1" "$TERRAFORM_STATE_BUCKET") | |
cmd2=(terraform import "$addr2" "$ID_state_lock_table") | |
echo "About to run these terraform commands:" | |
echo " + ${cmd1[*]}" | |
echo " + ${cmd2[*]}" | |
if prompt_yn "Is this correct? (Answer N to edit)"; then | |
break | |
fi | |
read -rp "Terraform remote state S3 bucket name: " TERRAFORM_STATE_BUCKET | |
read -rp "Terraform remote state lock table name: " ID_state_lock_table | |
done | |
# Send output from these commands to the real stdout (FD 3) | |
run "${cmd1[@]}" >&3 | |
run "${cmd2[@]}" >&3 | |
echo_blue "Finished importing terraform remote state resources" | |
} | |
# Be sure ruby is working and bundler is setup since some terraform modules | |
# rely on erb templates and ruby to be working. | |
run bundle check >&2 | |
check_release_versioning | |
if [ -n "$MODULE_STYLE" ]; then | |
style_description="module, separate subdirectory style" | |
load_env_module_style | |
else | |
style_description="shared, identity-devops-private, symlink style" | |
load_env_devops_private_style | |
fi | |
echo_blue >&2 "Finished loading environment variables" | |
echo_blue >&2 "The .terraform directory for $TF_DIR is: $style_description" | |
check_account_id | |
echo >&2 "Using state file: $STATE" | |
echo >&2 "Ensuring ${TF_DIR_FULL} is a terraform directory" | |
if [ ! -f "${TF_DIR_FULL}/main.tf" ] ; then | |
echo_red "deploy: not found: '${TF_DIR_FULL}/main.tf'" | |
echo_red "Are you sure '${TF_DIR_FULL}' is a terraform project folder?" | |
echo_red "Known examples include terraform-app, etc." | |
echo_red "Did you specify your username? This script no longer takes a " | |
echo_red "username argument." | |
die "error: Could not find terraform files" | |
fi | |
if [ -z "${TERRAFORM_STATE_BUCKET:=}" ] ; then | |
die "You must set the TERRAFORM_STATE_BUCKET environment variable. \ | |
This should contain the name of the s3 bucket used to store terraform state for this run." | |
fi | |
if [ -z "${TERRAFORM_STATE_BUCKET_REGION:=}" ] ; then | |
die "You must set the TERRAFORM_STATE_BUCKET_REGION environment variable. \ | |
This should contain the region of the s3 bucket used to store terraform state for this run." | |
fi | |
if [ -z "${ID_state_lock_table-}" ]; then | |
die "Must set ID_state_lock_table to dynamodb terraform state locking table" | |
fi | |
if [ -n "$MODULE_STYLE" ]; then | |
style_option="--module-style" | |
else | |
style_option="--shared-style" | |
fi | |
echo >&2 "Configuring state bucket $TERRAFORM_STATE_BUCKET with path $STATE" | |
run bin/configure_state_bucket.sh "$style_option" "$TERRAFORM_STATE_BUCKET" \ | |
"$STATE" "$TF_DIR_FULL" "$TERRAFORM_STATE_BUCKET_REGION" "$ID_state_lock_table" | |
echo >&2 "+ cd $TF_DIR_FULL" | |
cd "$TF_DIR_FULL" | |
cat >&2 <<EOF | |
######################################## | |
# | |
# Deploy environment: | |
# TF_DIR: $TF_DIR | |
# ENVIRONMENT: $ENVIRONMENT | |
# | |
######################################## | |
EOF | |
echo_blue >&2 "Running terraform get" | |
run terraform get >&2 | |
# If we are using variables from .tfvars files, splice in the -var-file options | |
# after the first element in TF_CMD, which will be the terraform command like | |
# plan/apply etc. (Only do this for terraform subcommands that accept | |
# -var-file. For terraform apply you can't pass vars if you also pass a plan | |
# file, so allow the caller to disable loading vars with ID_SKIP_LOADING_VARS.) | |
if [ -n "$MOVED_TO_TFVARS" ] && [ -z "${ID_SKIP_LOADING_VARS-}" ]; then | |
tf_subcommand="${TF_CMD[0]-}" | |
# these are the subcommands that accept -var-file | |
case "$tf_subcommand" in | |
apply|destroy|import|plan|refresh) | |
TF_CMD=( | |
"$tf_subcommand" "${tf_vars_opts[@]}" "${TF_CMD[@]:1}" | |
) | |
;; | |
esac | |
fi | |
if [ -n "$IMPORT_REMOTE_STATE" ]; then | |
# Import terraform remote state rather than running any terraform commands | |
import_remote_state_resources | |
echo_blue "All done" | |
exit | |
fi | |
echo >&2 | |
echo_blue >&2 "Running terraform..." | |
# Run main terraform command. This is the only command that should print to | |
# stdout (reassigned to FD 3) | |
run terraform "${TF_CMD[@]}" >&3 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment