#!/bin/bash

# debops: run ansible-playbook with some customization
# Copyright (C) 2014 Maciej Delmanowski <drybjed@gmail.com>
# Part of the DebOps project - http://debops.org/


# This program is free software; you can redistribute
# it and/or modify it under the terms of the
# GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License,
# or (at your option) any later version.
#
# This program is distributed in the hope that it will
# be useful, but WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General
# Public License along with this program; if not,
# write to the Free Software Foundation, Inc., 59
# Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# An on-line copy of the GNU General Public License can
# be downloaded from the FSF web page at:
# http://www.gnu.org/copyleft/gpl.html


set -e

# ---- Global constants ----

declare -r DEBOPS_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/debops"
declare -r DEBOPS_CONFIG=".debops.cfg"
declare -r DEBOPS_INVENTORY="inventory"
declare -r SCRIPT_NAME="$(basename ${0})"

declare -r ENCFS_PREFIX=".encfs."
declare -r SECRET_NAME="secret"

declare -r ANSIBLE_CONFIG_FILE="ansible.cfg"


# ---- Configuration variables ----

# Don't check SSH fingerprint on connection (to enable, set INSECURE=1 on the
# command line)
[ -z "${INSECURE}" ] && INSECURE=0

# Locations where DebOps playbooks might be found
DEBOPS_PLAYBOOKS_PATHS=(
  "${DEBOPS_DATA_HOME}/debops-playbooks/playbooks"
  "/usr/local/share/debops/debops-playbooks/playbooks"
  "/usr/share/debops/debops-playbooks/playbooks"
)

# List of possible inventory directories, relative to DebOps root project directory
ANSIBLE_INVENTORY_PATHS=( "ansible/${DEBOPS_INVENTORY}" "${DEBOPS_INVENTORY}" )

# Name of the script used to unlock/lock the encrypted directory
PADLOCK="padlock"


# ---- Functions ----

# Find specified file or directory in parent dir (if not specified, finds $DEBOPS_CONFIG)
# Source: https://unix.stackexchange.com/questions/13464
find_up () {
  local name=${1:-$DEBOPS_CONFIG}
  local slashes="${PWD//[^\/]/}"
  local directory="${PWD}"

  for (( n=${#slashes}; n>0; --n )) ; do
    test -e "${directory}/${name}" && echo "$(readlink -f ${directory}/${name})" && return
    directory="$directory/.."
  done
}

# Display error message and exit
error_msg () {
  local severity="${2:-Error}"
  local message="${1}"

  echo >&2 "${SCRIPT_NAME}: ${severity}: ${message}"
  set +e
  [[ "${severity}" == "Error" ]] && exit 1
  set -e
}

# Check if required commands exist
require_commands () {
  for name in ${@} ; do
    if ! type ${name} > /dev/null 2>&1 ; then
      error_msg "${name}: command not found"
    fi
  done
}


# ---- DebOps environment setup ----

# Find DebOps configuration file
debops_config="$(find_up)"

# Exit if we are outside of project directory
[ -z "${debops_config}" ] && error_msg "Not a DebOps project directory"

# Find root of the DebOps project dir
debops_root="$(dirname ${debops_config})"

# Source DebOps configuration file
[ -r ${debops_config} ] && source ${debops_config}


# ---- Main script ----

# Make sure required commands are present
require_commands ansible-playbook

# Check if playbooks are installed in various locations
for playbook_path in ${debops_root}/debops-playbooks/playbooks ${DEBOPS_PLAYBOOKS_PATHS[@]} ; do
  if [ -f ${playbook_path}/site.yml ] ; then
    debops_playbooks="${playbook_path}"
    break
  fi
done

[ -z "${debops_playbooks}" ] && error_msg "DebOps playbooks not installed"

# Check if Ansible inventory can be found in local directory
for inventory_path in "${ANSIBLE_INVENTORY_PATHS[@]}" ; do
  if [ -d ${debops_root}/${inventory_path} ] ; then
    ansible_inventory="${debops_root}/${inventory_path}"
    break
  fi
done

[ -z "${ansible_inventory}" ] && error_msg "Ansible inventory not found"

# Check if user specified a potential playbook name as the first argument. If
# yes, use it as the playbook name and remove it from the argument list
if [ $# -gt 0 ] ; then
  maybe_play="${1}"
  if [ -f ${maybe_play} ] ;then
    play="${maybe_play}" ; shift
  elif [ -f ${debops_root}/playbooks/${maybe_play}.yml ] ;then
    play="${debops_root}/playbooks/${maybe_play}.yml" ; shift
  elif [ -f ${debops_root}/ansible/playbooks/${maybe_play}.yml ] ;then
    play="${debops_root}/ansible/playbooks/${maybe_play}.yml" ; shift
  elif [ -f ${debops_playbooks}/${maybe_play}.yml ] ; then
    play="${debops_playbooks}/${maybe_play}.yml" ; shift
  elif [ -f ${debops_root}/playbooks/site.yml ] ; then
    play="${debops_root}/playbooks/site.yml"
  elif [ -f ${debops_root}/ansible/playbooks/site.yml ] ; then
    play="${debops_root}/ansible/playbooks/site.yml"
  else
    play="${debops_playbooks}/site.yml"
  fi
else
  play="${debops_playbooks}/site.yml"
fi

export ANSIBLE_HOSTS="${ansible_inventory}"

# Generate Ansible configuration file
cat <<EOF > ${debops_root}/${ANSIBLE_CONFIG_FILE}
# Ansible configuration file generated by DebOps, all changes will be lost

[defaults]
hostfile           = ${ansible_inventory}

roles_path         = ${debops_root}/roles:${debops_root}/ansible/roles:${debops_playbooks}/../roles:${debops_playbooks}/roles:/etc/ansible/roles

action_plugins     = ${debops_root}/ansible/action_plugins:${debops_playbooks}/action_plugins:/usr/share/ansible_plugins/action_plugins
callback_plugins   = ${debops_root}/ansible/callback_plugins:${debops_playbooks}/callback_plugins:/usr/share/ansible_plugins/callback_plugins
connection_plugins = ${debops_root}/ansible/connection_plugins:${debops_playbooks}/connection_plugins:/usr/share/ansible_plugins/connection_plugins
lookup_plugins     = ${debops_root}/ansible/lookup_plugins:${debops_playbooks}/lookup_plugins:/usr/share/ansible_plugins/lookup_plugins
vars_plugins       = ${debops_root}/ansible/vars_plugins:${debops_playbooks}/vars_plugins:/usr/share/ansible_plugins/vars_plugins
filter_plugins     = ${debops_root}/ansible/filter_plugins:${debops_playbooks}/filter_plugins:/usr/share/ansible_plugins/filter_plugins

EOF

# Add custom configuration options to ansible.cfg
if type ansible_config_hook > /dev/null 2>&1 ; then
  ansible_config_hook >> ${debops_root}/${ANSIBLE_CONFIG_FILE}
fi

export ANSIBLE_CONFIG="${debops_root}/${ANSIBLE_CONFIG_FILE}"

# Allow insecure SSH connections if requested
if [ ${INSECURE} -gt 0 ] ; then
  export ANSIBLE_HOST_KEY_CHECKING=False
fi

# Create path to EncFS encrypted directory, based on inventory name
encfs_encrypted="$(dirname ${ansible_inventory})/${ENCFS_PREFIX}${SECRET_NAME}"

# Check if encrypted secret directory exists and use it
if [ -x ${encfs_encrypted}/${PADLOCK} ] ; then
  echo "Found encrypted secrets in ${encfs_encrypted}"
  ${encfs_encrypted}/${PADLOCK} unlock
  trap "${encfs_encrypted}/${PADLOCK} lock" EXIT
fi

# Run ansible-playbook with custom environment
echo "Running Ansible playbook from:"
echo "${play} ..."
ansible-playbook ${play} "${@}"

