#!/bin/bash
#
# push-to-pantheon.sh
#
# This script will push all of the generated build results created on a
# CI server such as Travis or Circle to a Drupal site hosted on Pantheon.
#
# This script is designed only to be called by the CI server.  It relies
# on special encrypted environment variables that must be set up in advance
# to provide the credentials used to log in to the Pantheon dashboard.
#
# The specific environment variables we need are:
#
#   SITE_NAME: The human-readable name of the site to give to site-install
#   PSITE: The name of the Pantheon site to push to
#   PENV:  The environment of the site to push to (e.g. "dev")
#   PEMAIL: The email address to log on to Pantheon with
#   PPASS: The password to log on to Pantheon with
#   CI_BOT_EMAIL: The email address to use in the git commit attribution
#   CI_BOT_NAME: The name to use in the git commit attribution
#   DRUSH_VERSION: The version of Drush to use on Pantheon
#   TERMINUS_VERSION: The version of Terminus to download and use in this script
#
# It is also expected that there should be an SSH private key in
# $HOME/.ssh that can be used to push code to the Pantheon git repository.
#
# For specific configuration instructions, see:
#
#   https://github.com/pantheon-systems/travis-scripts
#   https://github.com/pantheon-systems/circle-scripts
#

SELF_DIRNAME="`dirname -- "$0"`"
PROJECT_BASE_DIR="`cd -P -- "$SELF_DIRNAME/.." && pwd -P`"
DRUPAL_ROOT="$PROJECT_BASE_DIR/drupal"
if [ ! -d "$DRUPAL_ROOT" ] && [ -d "$PROJECT_BASE_DIR/web/sites" ]
then
  DRUPAL_ROOT="$PROJECT_BASE_DIR/web"
fi
if [ ! -d "$DRUPAL_ROOT" ] && [ -d "$PROJECT_BASE_DIR/htdocs/sites" ]
then
  DRUPAL_ROOT="$PROJECT_BASE_DIR/htdocs"
fi
if [ ! -d "$DRUPAL_ROOT" ] && [ -d "$PROJECT_BASE_DIR/code/sites" ]
then
  DRUPAL_ROOT="$PROJECT_BASE_DIR/code"
fi
if [ ! -d "$DRUPAL_ROOT" ] && [ -d "$PROJECT_BASE_DIR/../sites" ] && [ -f "$PROJECT_BASE_DIR/../index.php" ]
then
  DRUPAL_ROOT="`cd -P -- "$PROJECT_BASE_DIR/.." && pwd -P`"
fi

# Directory where we will temporarily check out the pantheon site
WORK_REPO="$HOME/pantheon-site"

#
# Exit with a message if the previous function returned an error.
#
# Usage:
#
#   aborterr "Description of what went wrong"
#
function aborterr() {
  if [ $? != 0 ]
  then
    echo "$1" >&2
    exit 1
  fi
}

#
# If there is an environment variable defined that contains
# a GitHub OAuth token, then tell Composer to use it.
#
# https://github.com/composer/composer/blob/master/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens
#
if [ -n "$GITHUB_OAUTH_TOKEN" ]
then
  composer config -g github-oauth.github.com "$GITHUB_OAUTH_TOKEN"
fi

#
# Check the result of the last operation, and either print progress or call aborterr
#
# Usage:
#
#   check "Something the script did" "Message to display if it did not work"
#
function check() {
  aborterr "$2"
  echo "$1"
}

# Provide default values for commit attribution if not
# provided in enviornment variables.
_CI_BOT_DOMAIN="${CI_BOT_DOMAIN:-${PEMAIL/*@/}}"
_CI_BOT_EMAIL="${CI_BOT_EMAIL:-ci-bot@$_CI_BOT_DOMAIN}"
_CI_BOT_NAME="${CI_BOT_NAME:-CI Bot}"

# If Drush version is not specified, then default to Drush 6
_DRUSH_VERSION="${DRUSH_VERSION:-6}"

# If Terminus version is not specified, then default to 0.5.5
_TERMINUS_VERSION="${TERMINUS_VERSION:-0.5.5}"

# If the CI provides us with an explicit build location (e.g. on Travis),
# then we'll always believe that.  Otherwise, we'll stick with our base
# assumption, which is that the project base dir is in the directory
# above where this script is located (i.e. $PROJECT_BASE_DIR/bin/push-to-pantheon)
if [ -n "$TRAVIS_BUILD_DIR" ]
then
  PROJECT_BASE_DIR="$TRAVIS_BUILD_DIR"
fi

# Add vendor/bin, $HOME/bin and $HOME/.composer/vendor/bin to our $PATH
export PATH="$PROJECT_BASE_DIR/bin:$HOME/bin:$HOME/terminus/vendor/bin:$PATH"

# Do not attempt to push the site up to Pantheon unless the PPASS
# environment variable is set.  If we receive a PR from an external
# repository, then the secure environment variables will not be set;
# if we test PRs, we cannot send them to Pantheon in this instance.
if [ -n "$PPASS" ]
then
  # Dynamic hosts through Pantheon mean constantly checking interactively
  # that we mean to connect to an unknown host. We ignore those here.
  echo "StrictHostKeyChecking no" > $HOME/.ssh/config

  # Create a local policy file that specifies the version of Drush that
  # the CI server should use when running Drush commands remotely on Pantheon.
  # n.b. we still want to use drush8 for Drupal 8 sites
  mkdir -p "$HOME/.drush"
  cat << __EOF__ > "$HOME/.drush/policy.drush.inc"
<?php

function policy_drush_sitealias_alter(&\$alias_record) {
  // Fix pantheon aliases!
  if (isset(\$alias_record['remote-host']) && (substr(\$alias_record['remote-host'], 0, 10) == 'appserver.')) {
    \$alias_record['path-aliases']['%drush-script'] = 'drush$_DRUSH_VERSION';
  }
}
__EOF__

  # Capture the commit message
  export CI_COMMIT_MSG="$(git log --format=%B --no-merges -n 1)"

  # If Terminus is not already in the $PATH, then install it.
  CHECK_TERMINUS="$(which terminus)"
  if [ -z "$CHECK_TERMINUS" ]
  then
    # We install via Composer rather than using curl to fetch the phar
    # because this way we always get the latest version.
    # n.b. Composer global require does not work here, because Circle CI starts off
    # with a global composer.json that requires phpunit 4.1.*, and Terminus
    # requires 4.6 or later.  We'll make our own location for terminus installs.
    mkdir -p $HOME/terminus
    cd $HOME/terminus
    composer require terminus/terminus
  fi

  # Set up Terminus and aliases, and wake up the site
  echo "Log in to Pantheon via Terminus"
  # Redirect output when logging in.  Terminus prints the email address
  # used during login to stdout, which will end up in the log.  It's better
  # to conceal the email address used, to make it harder for a third party
  # to try to log in via a brute-force password-guessing attack.
  terminus auth login "$PEMAIL" --password="$PPASS" >/dev/null 2>&1
  check "Logged in to Pantheon via Terminus" "Could not log in to Pantheon with Terminus"
  echo "Fetch aliases via Terminus"
  mkdir -p $HOME/.drush
  terminus sites aliases
  aborterr "Could not fetch aliases via Terminus"
  echo "Wake up the site $PSITE"
  terminus site wake --site="$PSITE" --env="$PENV"
  check "Site wakeup successful" "Could not wake up site"
  PUUID=$(terminus site info --site="$PSITE" --field=id 2>/dev/null | sed -e 's/^[^:]*: *//')
  check "UUID for $PSITE is $PUUID" "Could not get UUID for $PSITE"

  # Make sure we are in git mode
  terminus site connection-mode --site="$PSITE" --env="$PENV" --set=git
  check "Changed connection mode to 'git' for $PSITE $PENV environment" "Could not change connection mode to 'git' for $PSITE $PENV environment"

  # Identify the automation user
  git config --global user.email "$_CI_BOT_EMAIL"
  git config --global user.name "$_CI_BOT_NAME"

  # Clone the Pantheon repository into a separate directory, then
  # move the .git file to the location where we placed our composer targets
  cd $PROJECT_BASE_DIR
  REPO="ssh://codeserver.$PENV.$PUUID@codeserver.$PENV.$PUUID.drush.in:2222/~/repository.git"
  git clone --depth 1 "$REPO" "$WORK_REPO"
  check "git clone successful" "Could not clone repository from $REPO"
  # Get rid of any .git directories created by composer
  rm -rf $(find "$DRUPAL_ROOT" -name ".git")
  # Move the .git directory we checked out on top of the files we built.
  # This is functionally equivalent to copying the built files over the
  # checked-out repository, except more efficient.
  mv "$WORK_REPO/.git" "$DRUPAL_ROOT"

  # Use our .gitignore file from our source repository to build
  # a .gitattributes file in our Pantheon repository.  Anything
  # inside of 'drupal' that is ignored in the source repository is
  # ignored because it is a built target.  We want to also ignore
  # the same set of files when making diffs on the Pantheon repository,
  # to avoid large diffs full of uninteresting changes.
  if [ ! -f "$DRUPAL_ROOT/.gitattributes" ]
  then
    grep drupal $PROJECT_BASE_DIR/.gitignore | sed -e 's#drupal/\(.*\)#\1 -diff#' -e 's#/ -diff#/** -diff#' > "$DRUPAL_ROOT/.gitattributes"
  fi

  # Output of the diff vs upstream.
  cd "$DRUPAL_ROOT"
  echo "Here's the status change!"
  git status

  # If there is no settings.php file on Pantheon, then we will create one.
  # n.b. there is always a settings.php file in $TAVIS_BUILD_DIR; it was
  # created when we did the local `drush site-install` prior to testing on
  # behat.  We'll remove it now, and replace it with some other settings.php
  # file.
  chmod +w sites/default
  rm -f sites/default/settings.php
  aborterr "Could not remote built settings.php file."
  if [ ! -f "$WORK_REPO/sites/default/settings.php" ]
  then
    cp -f "$WORK_REPO/sites/default/default.settings.php" sites/default/settings.php
    aborterr "Could not create settings.php with default.settings.php."
    # settings.php is excluded by our .gitignore, but
    # we need one on Pantheon in order to use drush si,
    # so we will add it with `git add -f`.
    git add -f sites/default/settings.php
    aborterr "Could not add settings.php"
    INSTALL_SITE=true
  else
    # Maintain existing Pantheon settings.php file.
    cp "$WORK_REPO/sites/default/settings.php" sites/default/settings.php
    aborterr "Could not preserve existing settings.php file."

    # Check to see if a pre-installed site is running okay.  If we
    # can't bootstrap Drupal, then we'll re-install
    echo "Drush status on the remote site:"
    drush --strict=0 @pantheon.$PSITE.$PENV status
    # Check to see if we are having any bootstrap problems.
    if [ $? == 0 ]
    then
      BOOTSTRAPPED=$(drush  --strict=0 @pantheon.$PSITE.$PENV status "Drupal bootstrap" 2>/dev/null)
    fi
    if [ $? != 0 ] || [ -z "$BOOTSTRAPPED" ]
    then
      # Wipe the site and start over
      terminus site wipe --site="$PSITE" --env="$PENV" --yes
      INSTALL_SITE=true
    fi
  fi

  # Push our built files up to Pantheon
  git add --all
  aborterr "'git add --all' failed"
  git commit -a -m "Built by CI: '$CI_COMMIT_MSG'"
  aborterr "'git commit' failed"
  git push origin master
  aborterr "'git push' failed"

  # If we created a settings.php file, then run 'site install'
  if [ -n "$INSTALL_SITE" ]
  then
    # We need to go back to sftp mode to run site-install
    terminus site connection-mode --site="$PSITE" --env="$PENV" --set=sftp
    check "Changed connection mode to 'sftp' for $PSITE $PENV environment" "Could not change connection mode to 'sftp' for $PSITE $PENV environment"
    # Create a new site with site-install.
    drush --strict=0 @pantheon.$PSITE.$PENV -y site-install standard --site-name="$SITE_NAME Pantheon Test Site" --account-name=admin --account-pass="[REDACTED]"
    aborterr "Could not install Drupal on Pantheon test site"
    # Commit the modification made to settings.php and switch back to git mode
    terminus site code commit --site="$PSITE" --env="$PENV" --message="Settings.php modifications made by site-install" --yes
    aborterr "Could not commit settings.php changes"
    terminus site connection-mode --site="$PSITE" --env="$PENV" --set=git
    aborterr "Could not switch back to git mode"
    # Because the site password is in the log, generate a new random
    # password for the site, and change it.  The site owner can use
    # `drush uli` to log in.
    RANDPASS=$(cat /dev/urandom | env LC_CTYPE=C tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1 )
    drush --strict=0 @pantheon.$PSITE.$PENV user-password admin --password="$RANDPASS"
    aborterr "Could not reset password on Pantheon test site"
  else
    # If the database already exists, just run updatedb to bring it up-to-date
    # with the code we just pushed.
    # n.b. If we wanted to re-run our behat tests on the pantheon site, then
    # we would probably want to install a fresh site every time.
    drush --strict=0 @pantheon.$PSITE.$PENV -y updatedb
    aborterr "updatedb failed on Pantheon site"
  fi

  SITE_URI=$(drush --strict=0 sa @pantheon.$PSITE.$PENV --format=csv --fields=uri 2>/dev/null)
  aborterr "Could not get URI for @pantheon.$PSITE.$PENV"

  # Try to curl the homepage, to give the system time to spin up
  curl --max-time 30 $SITE_URI &>/dev/null
fi
