#!/bin/bash

# TODO(cyrille): Drop once deploy.py has been properly used, both by hand and on CI.
# Script to deploy a Bob release, both server and client.
#
# The canonical place for our releases are the Docker Images in Docker Hub.
# They are generated by CircleCI in an untainted way.
#
# The server gets released by creating a new deployment on Amazon Web Service's
# (AWS) ECS (EC2 Container Service), and waiting for new containers to be
# spawned and old containers to be stopped.
#
# The client gets released by deploying static files on OVH storage.
#
# Environment variables required:
# - GITHUB_TOKEN: GitHub credentials to check CI status, update the release and
#   the prod branch.
# - OS_PASSWORD, OS_USERNAME, etc: OpenStack credentials to push client app to OVH server.
# - SLACK_INTEGRATION_URL: Webhook to report deploy status to Slack.
#
# AWS CLI must be installed, and configured for a user with the bob-emploi-deploy policy:
# https://console.aws.amazon.com/iam/home?region=us-east-1#/policies/arn:aws:iam::951168128976:policy/bob-emploi-deploy$serviceLevelSummary
# That policy must be kept in sync with the actual actions used in this file.
#
# Optional:
# - CIRCLE_STAGE: if "deploy", the script will know it's run as part of Circle
#   CI deployment.
# - DRY_RUN: do not actually deploy.
#
# Usage:
# frontend/release/deploy.sh $CIRCLE_TAG
# TODO(cyrille): Translate to python scripting.

set -e
readonly DIRNAME=$(dirname "$0")
# Import functions echo_error, echo_warning...
source "$DIRNAME/echo_with_colors.sh"
source "$DIRNAME/../cli/bashrc"

if [ -n "$DRY_RUN" ]; then
  echo_warning 'DRY RUN: will not actually modify anything.'
fi

readonly TAG="$1"
if [ -z "$TAG" ]; then
  echo_error 'No tag provided.'
  exit 1
fi

if [ -z "$(git tag -l "$TAG")" ]; then
  echo_error "The tag $TAG does not exist locally."
  exit 2
fi

if [ -z "$OS_PASSWORD" ]; then
  echo_error 'Set up OpenStack credentials first.'
  echo "* Go to https://www.ovh.com/manager/cloud and select: Servers -> OpenStack"
  echo "* Create an user if you do not have any, and copy the password"
  echo "* Click on the little wrench and select 'Downloading an Openstack configuration file'"
  echo "* Select 'GRA - Gravelines'"
  echo "* Source this file to export the OpenStack environment variables, it will ask for your password."
  exit 3
fi

if ! command -v aws >/dev/null 2>&1; then
  echo_error 'Install and configure the aws CLI that is necessary for deployment.'
  echo "* Ask your favorite admin for the access to the AWS project if you do not have it yet"
  echo "* Make sure you have the action rights to the bob-emploi-deploy policy (or equivalent)"
  echo "* Log into your AWS console and go to IAM (https://console.aws.amazon.com/iam/home)"
  echo "* Create a new 'Access key ID' and the corresponding 'Secret' if you do not already have one"
  echo "* Run 'aws configure' and add your credentials (make sure to set the region to 'eu-west-3')"
  exit 4
fi

if [ -z "$SLACK_INTEGRATION_URL" ]; then
  echo_error 'Set up the Slack integration first.'
  echo "* Find private URL for Slack Integration at https://bayesimpact.slack.com/apps/A0F7XDUAZ-incoming-webhooks"
  echo "* Add this URL in your bashrc as SLACK_INTEGRATION_URL env var"
  exit 5
fi

if ! command -v swift >/dev/null 2>&1; then
  echo_error 'Set up the OpenStack Swift tool first.'
  echo "* Installation is probably as simple as \`pip install python-swiftclient\`"
  exit 6
fi

if ! swift list > /dev/null 2>&1; then
  echo_error 'OpenStack credentials are incorrect.'
  echo "* Go to https://www.ovh.com/manager/cloud and select: Servers -> OpenStack"
  echo "* Create an user if you do not have any, and copy the password"
  echo "* Click on the little wrench and select 'Downloading an Openstack configuration file'"
  echo "* Select 'GRA - Gravelines'"
  echo "* Source this file to export the OpenStack environment variables, it will ask for your password."
  exit 7
fi

if ! pip show python-keystoneclient > /dev/null; then
  echo_error 'Set up the keystoneclient first.'
  echo "* Installation is probably as simple as \`pip install python-keystoneclient\`"
  exit 8
fi

if [ "$CI" ] && [ -z "$GITHUB_TOKEN" ]; then
  echo_error 'Setup GITHUB_TOKEN env variable to get `hub` to work.'
  echo '* Use one from the bob-emploi GitHub user.'
  echo '* Create one on https://github.com/settings/tokens'
  exit 9
fi

if [[ -z "$GITHUB_TOKEN" ]]; then
  readonly GIT_ORIGIN_WITH_WRITE_PERMISSION="origin"
else
  readonly GIT_ORIGIN_WITH_WRITE_PERMISSION="https://$GITHUB_TOKEN@github.com/bayesimpact/bob-emploi-internal.git"
fi
readonly DOCKER_SERVER_REPO="bob-emploi-frontend-server"
readonly DOCKER_CLIENT_REPO="bob-emploi-frontend"
readonly DOCKER_TAG="tag-$TAG"
readonly DOCKER_SERVER_IMAGE="bayesimpact/$DOCKER_SERVER_REPO:$DOCKER_TAG"
readonly DOCKER_CLIENT_IMAGE="bayesimpact/$DOCKER_CLIENT_REPO:$DOCKER_TAG"
readonly ECS_FAMILY="frontend-flask"
readonly ECS_SERVICE="flask-lb"
# TODO(cyrille): Get those from cloudformation somehow.
readonly US_AWS_REGION="us-east-1"
readonly US_ECS_CLUSTER="bob-us-ECSCluster-Edamal28jXOS"
readonly US_ECS_FAMILY="bob-frontend-server"
readonly US_ECS_SERVICE="bob-us-ECSService-MN39WAE8W7QK"
# Our OpenStack container, see
# https://www.ovh.com/manager/cloud/index.html#/iaas/pci/project/7b9ade05d5f84f719adc2cbc76c07eec/storage
readonly OPEN_STACK_CONTAINER="PE Static Assets"
# Our fallback s3 bucket, see
# https://s3.console.aws.amazon.com/s3/buckets/bob-emploi-client/?region=eu-west-3&tab=overview
readonly S3_BUCKET=bob-emploi-client
# When migrating to a new region, set this to the new region to update both region at once.
readonly ALTERNATE_AWS_REGION=


function docker_tag_exists {
  local image=$1
  local tag=$2
  curl --silent -f -lSL "https://index.docker.io/v1/repositories/$image/tags/$tag" > /dev/null
}

echo_info 'Checking that the Docker images exists…'
if (! docker_tag_exists bayesimpact/$DOCKER_CLIENT_REPO $DOCKER_TAG) || (! docker_tag_exists bayesimpact/$DOCKER_SERVER_REPO $DOCKER_TAG); then
  echo_error "The tag $DOCKER_TAG is not present in Docker Registry."
  exit 10
fi

# Prepare Release Notes.
readonly RELEASE_NOTES=$(mktemp)
hub release show $TAG 2> /dev/null > $RELEASE_NOTES || {
  echo_error "No release notes found."
  exit 11
}
echo 'Release notes are:'
echo '-------------------------------------------------------------------------------'
cat $RELEASE_NOTES
echo '-------------------------------------------------------------------------------'

sed -i -e "/^#/d" $RELEASE_NOTES
if [ -z "$(grep "^." $RELEASE_NOTES)" ]; then
  echo_error 'Canceling deployment due to empty release notes. This is not expected, the workflow step ask-for-manual-approval should have required the release notes.'
  exit 12
fi


# Deploying the tagged version by updating the corresponding parameter in the CloudFormation stack.
# TODO(cyrille): Use cloudformation/deploy_parameter.sh script.
function deploy_stack_server {
  # Change set must only have alphanumeric characters or dashes.
  local CHANGE_SET_NAME=$(sed s'/[^-a-zA-Z0-9]/-/g' <<< "deploy-$TAG")
  local NEW_PARAMETERS="$(
    aws cloudformation describe-stacks $@ |
    jq '.Stacks[0].Parameters' |
    jq 'map(del(.ParameterValue)|.UsePreviousValue=true)' |
    jq --arg docker_tag "$DOCKER_TAG" 'map(select(.ParameterKey == "FlaskDockerTag") = (.ParameterValue = $docker_tag|del(.UsePreviousValue)))'
  )"

  echo_info 'Rolling out the new task definition…'
  aws cloudformation create-change-set --change-set-name "$CHANGE_SET_NAME" --use-previous-template $@ --parameters "$NEW_PARAMETERS" --capabilities CAPABILITY_IAM
  if [ -z "$DRY_RUN" ]; then
    aws cloudformation wait change-set-create-complete --change-set-name "$CHANGE_SET_NAME" $@
    aws cloudformation execute-change-set --change-set-name "$CHANGE_SET_NAME" $@
  fi
}

PREVIOUS_DOCKER_TAG=""
function _needs_server_deploy() {
  local previous
  previous="$(bob_stack_var "$1" FlaskDockerTag)"
  if [[ $previous == "$DOCKER_TAG" ]]; then
    return 1
  fi
  if [ -z "$PREVIOUS_DOCKER_TAG" ]; then
    PREVIOUS_DOCKER_TAG="$previous"
    return
  fi
  if [[ $previous != "$PREVIOUS_DOCKER_TAG" ]]; then
    echo_warning "Found inconsistent previous tags: $previous and $PREVIOUS_DOCKER_TAG"
  fi
}

readonly DEPLOYABLE_DEPLOYMENTS="$(jq -r '.[]|select(.deprecatedFor | not)|.deployment' "$DIRNAME/stack_deployments.json")"
echo "$DEPLOYABLE_DEPLOYMENTS" | while read deployment; do
  if _needs_server_deploy "$deployment"; then
    deploy_stack_server $(__bob_stack_params "$deployment")
  fi
done

echo "$DEPLOYABLE_DEPLOYMENTS" | while read deployment; do
  if [ -z "$DRY_RUN" ]; then
    aws cloudformation wait stack-update-complete $(__bob_stack_params "$deployment")
  fi
  echo_success "Server deployed for $deployment!"
done

# Deploying the client.


# To get the files, this script downloads the Docker Images from Docker
# Registry, then extract the html folder from the Docker Image (note that to do
# that we need to create a temporary container using that image). The html
# folder is extracted as a TAR archive that we unpack in a local dir.
#
# Once we have the file we can upload them to OVH Storage using the OpenStack
# tool swift.

echo_info 'Downloading the client Docker Image…'
docker pull $DOCKER_CLIENT_IMAGE

echo_info 'Extracting the archive from the Docker Image…'
readonly TMP_TAR_FILE="$(mktemp).tar"
readonly TMP_DOCKER_CONTAINER=$(docker create $DOCKER_CLIENT_IMAGE)
docker cp $TMP_DOCKER_CONTAINER:/usr/share/bob-emploi/html - > $TMP_TAR_FILE
docker rm $TMP_DOCKER_CONTAINER

echo_info 'Extracting files from the archive…'
readonly TMP_DIR=$(mktemp -d)
tar -xf $TMP_TAR_FILE -C $TMP_DIR --strip-components 1
rm -r $TMP_TAR_FILE

echo_info 'Uploading files to the OpenStack container and S3 bucket…'
pushd $TMP_DIR
rm -r *Demo
if [ -z "$DRY_RUN" ]; then
  swift upload "$OPEN_STACK_CONTAINER" --skip-identical *
  aws s3 cp "$(pwd)" "s3://$S3_BUCKET/" --recursive
else
  ls -R .
fi
popd

rm -r $TMP_DIR

echo_info 'Logging the deployment on GitHub…'
echo >> $RELEASE_NOTES
echo "Deployed on $(date -R -u)" >> $RELEASE_NOTES

function get_previous_release() {
  if [[ "$PREVIOUS_DOCKER_TAG" == "tag-"* ]]; then
    echo "${PREVIOUS_DOCKER_TAG#"tag-"}"
    return
  fi
  >&2 echo_warning "No previous deployment found. Relaying on git tags."
  git describe --tags origin/prod
}
readonly PREVIOUS_RELEASE="$(get_previous_release)"

if [ -z "$DRY_RUN" ]; then
  hub release edit --draft=false --file=$RELEASE_NOTES $TAG
  git push -f "$GIT_ORIGIN_WITH_WRITE_PERMISSION" ${TAG}~0:prod
fi

# TODO(cyrille): Add release info to Sentry, with sourcemap url.

# Ping Slack to say the deployment is done.
readonly ROLLBACK_COMMAND=\`"frontend/release/deploy.sh $PREVIOUS_RELEASE"\`
readonly SLACK_MESSAGE=$(jq -R --slurp --arg tag "$TAG" --arg rollback "$ROLLBACK_COMMAND" \
  '{text: "A new version of Bob has been deployed (\($tag)).\n\(.)\nTo rollback run: \($rollback)"}' \
  $RELEASE_NOTES)
if [ -z "$DRY_RUN" ]; then
  wget -o /dev/null -O /dev/null --post-data="$SLACK_MESSAGE" "$SLACK_INTEGRATION_URL"
else
  echo 'Would send the following message to Slack:'
  echo "$SLACK_MESSAGE"
  echo ''
fi
rm -f $RELEASE_NOTES

echo_success "Success!"
echo "Please wait ~15 minutes and check that everything works. If needed rollback using: $ROLLBACK_COMMAND."
