Skip to content

Instantly share code, notes, and snippets.

@kyleterry
Created September 26, 2014 07:33
Show Gist options
  • Save kyleterry/de507c3d1f45f4a3b753 to your computer and use it in GitHub Desktop.
Save kyleterry/de507c3d1f45f4a3b753 to your computer and use it in GitHub Desktop.
General deploy script
#!/usr/bin/env bash
set -u
set -e
### Non-function Echos and Conditionals
cat <<"HERE"
_____ _ _
| __ \ | | (_)
| | | | ___ _ __ | | ___ _ _ _ _ __ __ _
| | | |/ _ \ '_ \| |/ _ \| | | | | '_ \ / _` |
| |__| | __/ |_) | | (_) | |_| | | | | | (_| |
|_____/ \___| .__/|_|\___/ \__, |_|_| |_|\__, |
| | __/ | __/ |
|_| |___/ |___/
HERE
### End Non-function Echos and Conditionals
### Setup Config
config_dir=${config_dir:-/etc/project}
source "${config_dir}/deploy.cfg"
action_do_deploy="deploy"
action_maintenance_flag="maintenance"
### End Setup Config
### Helper Functions
function usage {
cat <<HERE
Usage: ${0} -e ENVIRONMENT [options]
ENVIRONMENTS
production
staging
reporting
OPTIONS
-h
This message
-e ENVIRONMENT
Sets the environment to take action against
-m on|off
Toggles the maintenance page and exits
-r
Temporary flag for settings tag for utility deploys
-u
Also issue an apt-get update and upgrade. This might add a little
time to the deploy, so use with care.
HERE
}
function log {
if [[ -z ${1} ]]; then
return
fi
if [[ -n ${2} && ${2} =~ ^(error|fatal|warning) ]]; then
echo -e "\e[01;31m[$(date)] ${environment}: ${1}\e[0m" 1>&2
else
echo "[$(date)] ${environment}: ${1}" | tee -a "${log_file}"
fi
}
### End Helper Functions
### Command Line Args and Setup
function init {
action=${action_do_deploy}
environment=
upgrade=false
while getopts "he:m:r:" opt
do
case ${opt} in
h)
usage
exit 0
;;
e)
environment=${OPTARG}
;;
m)
if [ "${OPTARG}" == "on" ] || [ "${OPTARG}" == "off" ]; then
action=${action_maintenance_flag}
maintenance_flag=${OPTARG}
else
usage
exit 1
fi
;;
r)
revision=${OPTARG}
;;
u)
upgrade=true
;;
:)
echo "Missing option argument for -${opt}"; usage; exit 1
;;
\?)
echo "Unknown option: -${opt}"; usage; exit 1
;;
*)
echo "Unimplimented option: -${opt}"; usage; exit 1
;;
esac
done
if [[ -z ${environment} ]]; then
echo "FATAL: An environment must be set"
usage
exit 1
fi
# ATTENTION: make double sure ${docroot} is set right. Disabling the maint.
# flag will execute an `rm -f`. It's quoted, so word globbing isn't an issue
# but incorrectly set directories could lead to lost data.
case ${environment} in
production)
chef_util_search="roles:utility_${datacenter} AND chef_environment:production"
chef_web_search="roles:web_${datacenter} AND chef_environment:production"
docroot="/var/www/www.example.com/current"
;;
staging)
chef_util_search="roles:utility_${datacenter} AND chef_environment:staging"
chef_web_search="roles:web_${datacenter} AND chef_environment:staging"
docroot="/var/www/staging.example.com/current"
new_relic_enabled=false
;;
reporting)
chef_util_search=
chef_web_search="roles:web_${datacenter} AND chef_environment:reporting"
new_relic_enabled=false
docroot="/var/www/reporting.example.com/current"
;;
esac
mkdir -p "${log_file_DIR}"
log_file=${log_file_DIR}/deploys-${environment}.log
touch "${log_file}"
}
function confirmations {
while true; do
read -p "You are about to deploy to ${environment}. Are you sure? [no] " answer
case ${answer} in
yes|Yes|YES)
break
;;
*)
echo "Aborting."
exit 1
;;
esac
done
if [ "${action}" == "${action_do_deploy}" ]; then
answer=
while true; do
read -p "Does your deploy contain migrations or anything requiring downtime? (yes or no) " answer
case ${answer} in
yes|Yes|YES)
maintenance=true
maintenance_flag="on"
break
;;
no|No|NO)
maintenance=false
maintenance_flag="off"
break
;;
*)
echo "Please answer yes or no"
;;
esac
done
fi
}
### End Command Line Args and Setup
### Deploy Hook Functions
function run_pre_hooks {
run_hooks "pre"
}
function run_post_hooks {
run_hooks "post"
}
function run_hooks {
if [[ -z ${1} ]]; then
return
fi
hooks_dir="${deploy_hooks_dir}/${1}/${environment}"
log "Running hooks in ${hooks_dir}"
if [[ -d ${hooks_dir} ]]; then
for script in ${hooks_dir}/* ; do
if [[ -x ${script} ]]; then
source "${script}"
fi
done
fi
}
### End Deploy Hook Functions
### Deploy Actions
function toggle_maintenance_state {
local state=${1}
log "Toggling maintenance mode to ${state}"
if [ "${state}" == "on" ]; then
local action_command='touch'
local action_description='Setting'
elif [ "${state}" == "off" ]; then
# make double sure ${docroot} is set right.
local action_command='rm -f'
local action_description='Removing'
else
log "FATAL: Invalid state \"${state}\" for toggle_maintenance_flag_state"
exit 1
fi
if [[ ! -z "$action_command" ]]; then
log "${action_description} maintenance flags on web nodes"
knife ssh "roles:web_${environment}" "${action_command} '${docroot}/maintenance.flag'" >> "${log_file}" 2>&1
fi
}
function toggle_new_relic_monitoring {
local state=${1}
log "Toggling New Relic Availability Monitoring to ${state}"
if ${new_relic_enabled}; then
if [ "${state}" == "on" ]; then
curl -q -s "https://api.newrelic.com/accounts/${new_relic_account_id}/applications/${new_relic_app_id}/ping_targets/enable" -X POST -H "X-Api-Key:${new_relic_api_key}" >> "${log_file}" 2>&1
elif [ "${state}" == "off" ]; then
curl -q -s "https://api.newrelic.com/accounts/${new_relic_account_id}/applications/${new_relic_app_id}/ping_targets/disable" -X POST -H "X-Api-Key:${new_relic_api_key}" >> "${log_file}" 2>&1
else
log "FATAL: Invalid state \"${state}\" for toggle_new_relic_monitoring"
exit 1
fi
fi
}
function do_deploy {
if ${maintenance}; then
toggle_new_relic_monitoring "off"
toggle_maintenance_state "on"
fi
log "Running pre-deploy hooks..."
run_pre_hooks >> "${log_file}" 2>&1
if [[ -n ${chef_util_search} ]]; then
log "Deploying utility"
do_upgrade ${chef_util_search}
knife ssh ${chef_util_search} "sudo chef-client --once" >> "${log_file}" 2>&1
fi
log "Deploying code to web nodes"
do_upgrade ${chef_web_search}
knife ssh "${chef_web_search}" "sudo chef-client --once" >> "${log_file}" 2>&1
log "Running post-deploy hooks..."
run_post_hooks >> "${log_file}" 2>&1
if ${maintenance}; then
toggle_new_relic_monitoring "on"
toggle_maintenance_state "off"
fi
}
function do_upgrade {
if [[ ${#} -lt 1 ]]; then
log "ERROR: Misuse of do_upgrade; expecting 1 arguments, got ${#}" "error"
exit 1
fi
local chef_search=${1}
if ${upgrade}; then
knife ssh ${chef_search} "sudo apt-get update && sudo apt-get dist-upgrade -y" >> "${log_file}" 2>&1
fi
}
function do_maintenance_only {
# If "maintenance" mode is ON then we want to turn availability monitoring OFF
local new_relic_mode="on"
if [ "${maintenance_flag}" == "on" ]; then
new_relic_mode="off"
fi
toggle_new_relic_monitoring "${new_relic_mode}"
toggle_maintenance_state "${maintenance_flag}"
}
### End Deploy Actions
### Error Handling
function trap_errors {
local line=${1}
log "Error while running deploy ${script_name}:${line}" "error"
log "All hosts put into maintenance mode or disabled on the load balancer" "error"
log "are still in that state. Exiting..." "error"
}
trap 'trap_errors ${LINENO}' ERR
### End Error Handling
### Main
init "${@}"
confirmations
log "Starting new deploy action"
deploy_start=${SECONDS}
case ${action} in
${action_do_deploy})
log "Deploying code ${environment}"
do_deploy
;;
${action_maintenance_flag})
log "Toggling maintenance mode only; no deploy!"
do_maintenance_only
if [ "${maintenance_flag}" == "on" ]; then
log "${environment} is now in maintenance mode."
elif [ "${maintenance_flag}" == "off" ]; then
log "${environment} has returned from maintenance mode."
fi
;;
esac
deploy_stop=${SECONDS}
log "Action took $((deploy_stop - deploy_start)) seconds to complete\n"
### End Main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment