|
#!/bin/bash |
|
# Runs tasks to deploy code from bitbucket/github/local vcs to a Pantheon dev environment including: |
|
# - Setup of SSH and git configuration to avoid password prompts |
|
# - Race condition handling for workflows, terminus commands, and git commands |
|
# - Logging of failed code deployment task for automatic retries |
|
# - Configuration of environment variables commonly required in enterprise deployment scripts |
|
# !! IMPORTANT !! |
|
# Set SSH_PRIVATE_KEY_PATH to the path of the private key you want to use for deployment to Pantheon. This key should not be password protected. |
|
# Usage: |
|
# ./deploy-dev <site-name or uuid> <environment> |
|
# |
|
# Additional examples of common enterprise deployment tasks are included in this script, but are not enabled by default. |
|
# |
|
# ### |
|
|
|
SITE=$1 |
|
ENV=$2 |
|
current_dir=$3 |
|
START=$SECONDS |
|
|
|
echo -e "Starting ${SITE} code deployment to ${ENV} environment" | sed "s/^/[$SITE] /" >>"${current_dir}/logs/deploy-$ENV.log" |
|
|
|
# Configure script variable for use in other functions |
|
|
|
function setup_variables { |
|
# !! IMPORTANT !! Change this value to the path of the private key you want to use for deployment to Pantheon. This key should not be password protected. |
|
# Example: SSH_PRIVATE_KEY_PATH="~/.ssh/id_rsa" |
|
SSH_PRIVATE_KEY_PATH= |
|
# if the SSH_PRIVATE_KEY_PATH is not set, ask the user which key to use. Exit if the user doesn't provide a key. |
|
if [[ -z "$SSH_PRIVATE_KEY_PATH" ]]; then |
|
echo "SSH_PRIVATE_KEY_PATH is not set. Please enter the path to the private key you want to use for deployment to Pantheon." |
|
read -p "Enter the path to the private key: " SSH_PRIVATE_KEY_PATH |
|
if [[ -z "$SSH_PRIVATE_KEY_PATH" ]]; then |
|
echo "SSH_PRIVATE_KEY_PATH is not set. Exiting." |
|
exit 1 |
|
fi |
|
fi |
|
|
|
# Set the Pantheon remote URL for the site |
|
PANTHEON_REMOTE=$(terminus connection:info "$SITE.dev" --field=git_url) |
|
|
|
echo "Adding Pantheon remote to git repo. Value of PANTHEON_REMOTE: $PANTHEON_REMOTE" |
|
} |
|
|
|
|
|
# Setup ssh config for drush.in hosts. |
|
# This is to help avoid password prompts when using git and ssh. |
|
function ssh_config_setup() { |
|
echo -e "Host *.drush.in\n StrictHostKeyChecking no\n PasswordAuthentication no\n HostkeyAlgorithms +ssh-rsa\n PubkeyAcceptedAlgorithms +ssh-rsa" >> ~/.ssh/config |
|
} |
|
|
|
# Set system environment variables for ssh / git ASKPASS in case a GUI is currently being used |
|
function ssh_askpass_setup() { |
|
unset SSH_ASKPASS |
|
export GIT_ASKPASS= |
|
} |
|
|
|
|
|
# Ensure that the SSH agent is running and has the private key loaded for this site. |
|
# This is critical for avoiding password prompts when using git and ssh. |
|
|
|
function ssh_agent_setup() { |
|
# set SSH_PRIVATE_KEY to the contents of the private key |
|
SSH_PRIVATE_KEY=$(cat $SSH_PRIVATE_KEY_PATH) |
|
echo "$SSH_PRIVATE_KEY" > private.key |
|
chmod 600 private.key |
|
eval `ssh-agent -s` |
|
ssh-add private.key |
|
ssh-add -L | ssh-keygen -l -E md5 -f - | awk '{print substr($2,5)}' |
|
} |
|
|
|
|
|
# Scan for and add the Pantheon SSH host keys to the known_hosts file for this site's code and app servers. |
|
# This is to avoid prompts to confirm the authenticity of the host when connecting via SSH. |
|
|
|
function ssh_host_key_scan { |
|
|
|
SITE_ID=$(terminus site:lookup "$SITE") |
|
ssh-keyscan -t rsa -p 2222 "appserver.dev.${SITE_ID}.drush.in" >>~/.ssh/known_hosts |
|
ssh-keyscan -t rsa -p 2222 "codeserver.dev.${SITE_ID}.drush.in" >>~/.ssh/known_hosts |
|
} |
|
|
|
# Basic code push to Pantheon environment, assumes master branch is checked out |
|
function code_push() { |
|
# Push code to Pantheon environment |
|
echo "Pushing code to $ENV environment" |
|
git remote add pantheon $PANTHEON_REMOTE |
|
git push pantheon HEAD:refs/heads/master |
|
|
|
# if the push fails with a non-zero exit code, add the site to the failed-sites-<env>.txt file |
|
if [[ "$?" -ne 0 ]]; then |
|
echo "Code push failed for $SITE.$ENV" |
|
echo "[code] $SITE" >>"${current_dir}/logs/failed-sites-$ENV.txt" |
|
fi |
|
} |
|
|
|
# Wait for running workflows to complete - this is a workaround for workflow:wait unreliable behavior |
|
# Configure TIMEOUT_SEC to set the maximum time to wait for workflows to complete per site |
|
# Configure retries to set the maximum number of times to check for running workflows |
|
function check_workflows { |
|
local id=$1 |
|
START_TIME="$(date -u +%s)" |
|
TIMEOUT_SEC=1400 |
|
local retries=25 |
|
while (($(date +%s) < $START_TIME + $TIMEOUT_SEC)); do |
|
set +e |
|
CURRENT_TIME="$(date -u +%s)" |
|
elapsed_seconds=$(($CURRENT_TIME - $START_TIME)) |
|
if [[ -z "$id" ]]; then |
|
WORKFLOW_RUNNING=$(terminus workflow:list "$SITE" --fields=id,status,env,workflow | grep $ENV) |
|
else |
|
WORKFLOW_STATUS=$(terminus workflow:info:status $SITE --id=$id --field=status) |
|
fi |
|
set +e |
|
if [ $elapsed_seconds -gt $TIMEOUT_SEC ]; then |
|
echo "timeout of $TIMEOUT_SEC sec" |
|
# The workflow is still running after the timeout, so add the site to the failed-sites-$ENV.txt file |
|
echo "[code] $SITE" >>"${current_dir}/logs/failed-sites-$ENV.txt" |
|
exit 124 |
|
fi |
|
|
|
echo "Total workflow time elapsed is $elapsed_seconds sec" |
|
|
|
if [[ "$WORKFLOW_RUNNING" =~ "running" ]]; then |
|
echo "Workflows running, waiting 15 seconds and checking again." |
|
sleep 15 |
|
elif [[ "$WORKFLOW_RUNNING" =~ "failed" || "$WORKFLOW_STATUS" =~ "failed" ]]; then |
|
echo "Workflows failed, exiting." |
|
# The workflow failed, so add the site to the failed-sites-<env>.txt file |
|
echo "[code] $SITE" >>"${current_dir}/logs/failed-sites-$ENV.txt" |
|
return 1 |
|
else |
|
echo "Workflows completed successfully." |
|
break |
|
fi |
|
done |
|
} |
|
|
|
# Example: run a terminus command as a background task - eg to create a multidev environment if it doesn't exist |
|
function background_task() { |
|
set +e |
|
# Create multidev in background if it doesn't exist - this is to speed up builds |
|
echo "Creating multidev as a background task if it doesn't exist." |
|
# Push to multidev env, create branch on Pantheon if it doesnt exist |
|
# Check if multidev exists for current site and env |
|
ENV_EXISTS=$(terminus multidev:list $SITE --format=list | grep -w $ENV) |
|
# Doesn't exist; create it |
|
# If ENV_exists is not empty, the environment exists. Check that MD_STATUS is not 0, if it is, then the multidev exists and we can skip creation. |
|
if [[ -n "$ENV_EXISTS" ]]; then |
|
echo "Site $SITE has a $ENV multidev. Skipping creation." |
|
MD_STATUS=0 |
|
|
|
else |
|
[[ -z "$ENV_EXISTS" ]] |
|
echo "Site $SITE does not have a $ENV multidev. Creating." |
|
./bg.sh "terminus -n multidev:create $SITE.dev $ENV --yes" |
|
id=$(terminus workflow:list "$SITE" --fields=id,status,env,workflow | grep -w 'Create a Multidev environment' | grep -v "succeeded" | awk '{print $1}' | head -n 1) |
|
check_workflows $id |
|
MD_STATUS=0 |
|
fi |
|
|
|
return $MD_STATUS |
|
} |
|
|
|
# Example: Remove untracked files from the git repository |
|
function remove_untracked() { |
|
set +e |
|
UNTRACKED_FILES=$(git status -u) |
|
|
|
if [[ -n "$UNTRACKED_FILES" ]]; then |
|
echo "Untracked files found, attempting to remove." |
|
git clean -f -d |
|
else |
|
echo "No untracked files found." |
|
fi |
|
} |
|
|
|
# Example: Advanced automatic merge conflict resolution |
|
function resolve_conflicts { |
|
# SET CONFLICTS_LIST to the output of git conflicts |
|
CONFLICTS_LIST=$(git conflicts) |
|
|
|
if [[ -n "$CONFLICTS_LIST" ]]; then |
|
echo "Merge conflicts found, attempting to resolve." |
|
git theirs composer.lock |
|
git commit --no-edit |
|
fi |
|
} |
|
|
|
# Example: automatic cleanup of uncommitted files |
|
function git_cleanup() { |
|
|
|
MODIFIED_OR_ADDED_LIST=$(git status --porcelain) |
|
|
|
if [[ -n "$MODIFIED_OR_ADDED_LIST" ]]; then |
|
echo "Uncommitted files found:" |
|
echo $MODIFIED_OR_ADDED_LIST |
|
|
|
echo "Attempting to remove uncommitted files." |
|
git clean -fdX |
|
fi |
|
} |
|
# Optional: Push code to Pantheon environment, retrying up to 3 times if it failed. |
|
# This is useful for managing sites with large codebases/diffs that may fail to push due to timeouts or other issues. |
|
function code_push_retry() { |
|
local retries=3 |
|
while [ $retries -gt 0 ]; do |
|
set +e |
|
echo "Pushing code to $ENV environment" |
|
git remote add pantheon $PANTHEON_REMOTE |
|
git push pantheon HEAD:refs/heads/master |
|
|
|
PUSH_STATUS="$?" |
|
if [[ "$PUSH_STATUS" == 0 ]]; then |
|
echo "Pushed to Pantheon $SITE.$ENV successfully." |
|
break |
|
else |
|
echo "Code push not successful, waiting 15 seconds and trying sync again." |
|
sleep 15 |
|
retries=$((retries - 1)) |
|
fi |
|
done |
|
} |
|
|
|
setup_variables |
|
ssh_config_setup |
|
ssh_askpass_setup |
|
ssh_agent_setup |
|
ssh_host_key_scan |
|
# Uncomment the function call below to retry the code push to Pantheon if it fails |
|
# code_push_retry |
|
code_push 2>&1 | sed "s/^/[$SITE] /" >>"${current_dir}/logs/deploy-$ENV.log" |
|
check_workflows 2>&1 | sed "s/^/[$SITE] /" >>"${current_dir}/logs/deploy-$ENV.log" |
|
|
|
# if any of the functions above fail, the code deployment has failed. Add the site to the failed-sites-<env>.txt file. |
|
if [[ $? -eq 0 ]]; then |
|
echo "Code push tasks successful for $SITE.$ENV" | sed "s/^/[$SITE] /" >>"${current_dir}/logs/deploy-$ENV.log" |
|
# if the site was |
|
if [[ -f "${current_dir}/logs/failed-sites-$ENV.txt" ]]; then |
|
sed -i "/$SITE/d" "${current_dir}/logs/failed-sites-$ENV.txt" |
|
fi |
|
else |
|
echo "Code push tasks failed for $SITE.$ENV" | sed "s/^/[$SITE] /" >>"${current_dir}/logs/deploy-$ENV.log" |
|
echo "[code] $SITE" >>"${current_dir}/logs/failed-sites-$ENV.txt" |
|
fi |
|
|
|
# Report time to results. |
|
DURATION=$((SECONDS - START)) |
|
TIME_DIFF=$(bc <<<"scale=2; $DURATION / 60") |
|
MIN=$(printf "%.2f" $TIME_DIFF) |
|
echo -e "Finished dev code push tasks ${SITE} in ${MIN} minutes" | sed "s/^/[$SITE] /" >>"${current_dir}/logs/deploy-$ENV.log" |
|
|