Skip to content

Instantly share code, notes, and snippets.

@nir0s
Last active July 18, 2019 20:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nir0s/395037f6f7b5638635fd87b30755b13a to your computer and use it in GitHub Desktop.
Save nir0s/395037f6f7b5638635fd87b30755b13a to your computer and use it in GitHub Desktop.
Strigo's Development Environment Provisioning Script
#!/usr/bin/env bash
###
# The script uses CFN to provision a Target Group in the app-dev ALB, and creates
# an instance behind that target group, after which it attaches two host-header
# based rules to the HTTP/HTTPS listeners which target the newly created target group.
# After the stack is up, it installs the relevant Strigo app, ssc and job-worker builds
# on it for the relevant branch.
#
# NOTE: AWS_DEFAULT_REGION, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set prior to running this.
###
set -euo pipefail
declare -r APP_DEV_LISTENER_ARN='arn:aws:elasticloadbalancing:...'
function log() {
message="$1"
echo -e "\e[32m## ${message}\e[0m" >&2
}
function error_exit() {
#######################################
# Print a title in red
# TODO: echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $@" >&2
# Arguments:
# The message to be printed
#######################################
local message="$1"
echo "\e[91m${message}" >&2
exit 1
}
function install_python_dependency() {
#######################################
# Install dependencies required for stack provisioning
#######################################
local package="$1"
if ! is_python_package_installed ${package}; then
log "Installing ${package}..."
sudo pip install ${package} --upgrade
fi
}
function is_python_package_installed() {
local package_name="$1"
log "Verifying ${package_name} is installed..."
if pip freeze | grep ${package_name} >/dev/null; then
return 0
fi
return 1
}
function get_alb_listener_rule_priority() {
#######################################
# Get the required priority (1, 2, etc..) for the rule.
# Each rule must be given a priority when it is created.
#######################################
log "Retrieving listener rule priority..."
local priority=$(aws elbv2 describe-rules --region ${AWS_DEFAULT_REGION} --listener-arn ${APP_DEV_LISTENER_ARN} | jq '[.Rules[].Priority][:-1]' | jq '[.[] | tonumber] | max + 1')
log "Priority for ${APP_DEV_LISTENER_ARN} is: ${priority}"
echo ${priority}
}
function is_stack_exists() {
#######################################
# Check whether the stack `stack_name` exists.
# Note that this will not check if the stack exists and was created successfully.
#######################################
local stack_name="$1"
if aws cloudformation describe-stacks | jq ".Stacks[].StackName | contains(\"${stack_name}\")" | grep "true" >/dev/null; then
log "Stack already exists"
return 0
fi
log "Stack does not exist"
return 1
}
function create_stack() {
#######################################
# Idempotently create a stack
#######################################
local branch="$1"
local priority="$2"
log "Creating stack for branch: ${branch}..."
aws cloudformation create-stack --region ${AWS_DEFAULT_REGION} --stack-name "${branch}" --template-body "$(cat dev/cfn.yaml)" --parameters ParameterKey=Branch,ParameterValue="${branch}" ParameterKey=LoadBalancerPriority,ParameterValue=$priority
log "Waiting for creation to complete..."
aws cloudformation wait stack-create-complete --region ${AWS_DEFAULT_REGION} --stack-name "${branch}"
log "Done"
}
function delete_stack() {
#######################################
# Idempotently create a stack
#######################################
local branch="$1"
log "Deleting stack for branch: ${branch}..."
aws cloudformation delete-stack --region ${AWS_DEFAULT_REGION} --stack-name "${branch}"
log "Waiting for deletion to complete..."
aws cloudformation wait stack-delete-complete --region ${AWS_DEFAULT_REGION} --stack-name "${branch}"
log "Done"
}
function delete_resources() {
#######################################
# Idempotently create a stack
#######################################
local branch="$1"
# Making sure we don't accidentally pass an empty branch name in which case
# aws s3 rm will delete the entire bucket's contents.
if ! is_var_set ${branch}; then
return
fi
log "Deleting resources for branch: ${branch}..."
aws s3 rm --region ${AWS_DEFAULT_REGION} s3://strigo-deploy-dev/${branch} --recursive
log "Done"
}
function get_instance_id() {
local branch="$1"
log "Retrieving instance id for stack: ${branch}..."
local instance_id=$(aws cloudformation describe-stack-resources --region ${AWS_DEFAULT_REGION} --stack-name "${branch}" | jq -r .StackResources[0].PhysicalResourceId)
log "Instance ID is: ${instance_id}"
echo ${instance_id}
}
function get_instance_ip() {
local intsance_id="$1"
log "Retrieving instance ip for instance id: ${instance_id}..."
local instance_ip=$(aws ec2 describe-instances --region ${AWS_DEFAULT_REGION} --instance-id ${instance_id} | jq -r .Reservations[].Instances[].PrivateIpAddress)
log "Instance IP is: ${instance_ip}"
echo ${instance_ip}
}
function is_dir() {
local dir="$1"
if [[ -d ${dir} ]]; then
return 0
fi
return 1
}
function is_var_set() {
local var="$1"
if [[ -z ${var} || -R ${var} ]]; then
return 1
fi
return 0
}
function edit_hosts_file() {
#######################################
# Place instance ip in hosts file
# Used by ansible to provision the machine
#######################################
local instance_ip="$1"
local hosts_file_path="$2"
log "Editing hosts file: ${hosts_file_path}..."
sed -i "s/{{INSTANCE_IP}}/${instance_ip}/g" ${hosts_file_path}
log "Hosts file is:"
echo "$(cat ${hosts_file_path})" >&2
}
function clone_ansible_source() {
#######################################
# Clone or pull strigo-ops repo.
#######################################
local branch="$1"
local repo_name="strigo-ops"
local repo_slug="strigo/${repo_name}"
local clone=false
if ! is_dir ${repo_name}; then
clone=true
log "Cloning ${repo_slug}..."
git clone git@github.com:${repo_slug}.git
fi
pushd ${repo_name} >/dev/null
if is_var_set ${branch}; then
log "Checking out ${repo_slug}/${branch}..."
git checkout ${branch}
fi
if ! [ ${clone} = true ]; then
log "Pulling branch..."
git pull
fi
popd >/dev/null
}
function set_ssh_key() {
#######################################
# Write and add strigo's ssh key to the agent
# for ansible to be able to provision the instance
#######################################
local key="$1"
local key_file="${2:-private_key}"
log "Writing instance ssh key to: ${key_file}..."
echo -e "$key" > ${key_file}
chmod 600 ${key_file}
log "Adding instance ssh key agent..."
ssh-add ${key_file}
ssh-add -l
}
function ansible() {
#######################################
# Deploy strigo-app, strigo-job-worker and strigo-ssc
# On the provisioned instance (via ansible)
#######################################
local working_dir="$1"
local branch="$2"
local hosts_file="$3"
local strigo_app_version="$4"
local stack_created_now="$5"
local strigo_ssc_version="${6:-0.0.5}"
local strigo_job_worker_version="${7:-0.0.3}"
local vars="hosts=strigo-instance env=dev branch=\"${branch}\" aws_access_key=\"${AWS_ACCESS_KEY_ID}\" aws_secret_key=\"${AWS_SECRET_ACCESS_KEY}\""
local args="-i ${hosts_file} -vv"
if ${stack_created_now}; then
local strigo_app_tags='--skip-tags datadog'
local ansible_tags='--skip-tags common,install_python,datadog'
else
local strigo_app_tags='--tags deploy'
local ansible_tags='--tags deploy'
fi
pushd ${working_dir} >/dev/null
log "Running ansible..."
log "args: ${args}"
log "vars: ${vars}"
log "Hosts file: ${hosts_file}"
log "Branch: ${branch}"
log "strigo-app version: ${strigo_app_version}"
ansible-playbook ${args} strigo-app.yml -e "${vars} deb_dir=${branch}/strigo-app" -e "{'strigo_app': {'version':'$strigo_app_version'}}" ${strigo_app_tags}
ansible-playbook ${args} strigo-job-worker.yml -e "${vars}" -e "{'strigo_job_worker': {'version':'$strigo_job_worker_version'}}" ${ansible_tags}
ansible-playbook ${args} strigo-ssc.yml -e "${vars}" -e "{'strigo_ssc': {'version':'$strigo_ssc_version'}}" ${ansible_tags}
ansible-playbook ${args} strigo-mongo-dev.yml -e "${vars}" ${ansible_tags}
popd >/dev/null
}
function assert_required_vars_set() {
local required_unset=false
# TODO: Nicefy
if ! is_var_set ${AWS_ACCESS_KEY_ID}; then
echo "AWS_ACCESS_KEY_ID must be set!"
required_unset=true
fi
if ! is_var_set ${AWS_SECRET_ACCESS_KEY}; then
echo "AWS_SECRET_ACCESS_KEY must be set!"
required_unset=true
fi
if ! is_var_set ${AWS_DEFAULT_REGION}; then
echo "AWS_DEFAULT_REGION must be set!"
required_unset=true
fi
if ${required_unset}; then
error_exit "Some required environment variables are not set!"
fi
}
function main() {
# TODO: Add normal CLI
local branch="$1"
local instance_ssh_key="$2"
local strigo_app_version="$3"
local commit_message="$4"
local strigo_ops_branch="${5:-master}"
local hosts_file="${6:-dev/hosts}"
assert_required_vars_set
install_python_dependency "awscli"
local stack_created_now=false
local stack_exists=true
if ! is_stack_exists "${branch}"; then
stack_exists=false
fi
# Only want to create the stack if commit message is `make_env` and it doesn't exist.
if [[ "${commit_message}" == 'make_env' ]] && ! ${stack_exists}; then
log "Stack creation required."
local priority=$(get_alb_listener_rule_priority)
create_stack "${branch}" "${priority}"
stack_created_now=true
stack_exists=true
elif [[ "${commit_message}" == 'destroy_env' ]] && ${stack_exists}; then
log "Stack deletion required."
delete_stack "${branch}"
delete_resources "${branch}"
exit 0
fi
# Only want to try and deploy if the stack exists.
if ${stack_exists}; then
install_python_dependency "ansible==2.3.0.0"
log "Deployment required."
local instance_id=$(get_instance_id "${branch}")
local instance_ip=$(get_instance_ip "${instance_id}")
edit_hosts_file "${instance_ip}" "${hosts_file}"
clone_ansible_source "${strigo_ops_branch}"
set_ssh_key "${instance_ssh_key}"
ansible "strigo-ops/ansible" "${branch}" "../../${hosts_file}" "${strigo_app_version}" "${stack_created_now}"
else
log "Skipping deploy."
fi
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment