#!/bin/bash
## START_OF_BASH_PLUS
#start_region
red="\033[38;5;196m"
neutral='\033[0m'
cyan='\033[00;34m'
white='\033[97m'
green="\033[38;5;46m"
ok=${green}"✔"${neutral}
# fail=${red}"✖"${neutral}

epoch () {
  echo `python -c "import time; print  ('{:0.0f}'.format(time.time()))"`
}

stopwatch () {
  BEGIN=$1
  NOW=`epoch`
  let DIFF=$(( $NOW - $BEGIN ))
  echo $DIFF
}

info () {
  >&2 printf " [ ${cyan}INFO${neutral} ] $1 $2\n"
}

user () {
  # shellcheck disable=SC2059
 >&2 printf "\r  [ \033[0;33m?\033[0m ] $1 "
}

success () {
  >&2 printf "   [ ${green}OK${neutral} ] $1 $2\n"
}

logv() {
  [[ "$debug" != "" ]] && log "$1"
}


fatal () {
  error $1 $2
  exit 1
}

error(){
 >&2  printf "[ ${red}ERROR${neutral} ] $1 $2\n"
}

# log a message with a seconds since program start prefix
log() {
  if [[ "$START" == "" ]]; then
      START=$(epoch)
  fi
  >&2 printf "[ $(printf %05d $(stopwatch $START)) ] $1\n"
}


die(){
    printf ${red}"$1${neutral} "$2"\n"
    exit 1
}

hr(){
  >&2   echo "================================================================================"
}

hr2(){
  >&2   echo "=================================================="
}

hr3(){
  >&2   echo "========================================"
}

strict_mode() {

  set -o errexit
  set -o nounset
  set -o pipefail
}

strict_mode_off() {
  unset -o errexit
  unset -o nounset
  unset -o pipefail
}

is_linux(){
    if [ "$(uname -s)" = "Linux" ]; then
        return 0
    else
        return 1
    fi
}

is_mac(){
    if [ "$(uname -s)" = "Darwin" ]; then
        return 0
    else
        return 1
    fi
}

is_jenkins(){
    if [ -n "${JENKINS_URL:-}" ]; then
        return 0
    else
        return 1
    fi
}

is_travis(){
    if [ -n "${TRAVIS:-}" ]; then
        return 0
    else
        return 1
    fi
}

is_ci(){
    if [ -n "${CI:-}" -o -n "${CI_NAME:-}" ] || is_jenkins || is_travis; then
        return 0
    else
        return 1
    fi
}

get_git_version() {
  echo "$(git describe --tags): $(git log -n1 --oneline)"
}

#===================================================================
# FUNCTION trap_add ()
#
# Purpose:  appends a command to a trap
#
# - 1st arg:  code to add
# - remaining args:  names of traps to modify
#
# Example:  trap_add 'echo "in trap DEBUG"' DEBUG
#
# See: http://stackoverflow.com/questions/3338030/multiple-bash-traps-for-the-same-signal
#===================================================================
trap_add() {
    trap_add_cmd=$1; shift || fatal "${FUNCNAME} usage error"
    new_cmd=
    for trap_add_name in "$@"; do
        # Grab the currently defined trap commands for this trap
        existing_cmd=`trap -p "${trap_add_name}" |  awk -F"'" '{print $2}'`

        # Define default command
        [ -z "${existing_cmd}" ] && existing_cmd="echo"

        # Generate the new command
        new_cmd="${existing_cmd};${trap_add_cmd}"

        # Assign the test
         trap   "${new_cmd}" "${trap_add_name}" || \
                fatal "unable to add to trap ${trap_add_name}"
    done
}

delete_on_exit() {
  [[ "$1" == "" || "$1" == "/" ]] && fatal "Attempting to delete root directory"
  logv "Deleting $1 on exit"
  _delete_on_exit+=($1)
  trap_add "_do_delete_on_exit" EXIT
}

_do_delete_on_exit() {
  for i in "${_delete_on_exit[@]}"; do
    rm -rf "$i"
  done
  _delete_on_exit=()
}
_delete_on_exit=()

temp_cp() {
  cp "$1" "$2"
  delete_on_exit "$2"
}

timestamp=$(date +%s)
#end_region
## END_OF_BASH_PLUS
#!/bin/bash
# ARG_OPTIONAL_REPEATED([inventory],[i],[ansible inventory file or directory],[])
# ARG_OPTIONAL_SINGLE([host-prefix],[],[The prefix to add to each hostname],[])
# ARG_OPTIONAL_SINGLE([template],[],[AMI or VMWare template to use to provision],[])
# ARG_OPTIONAL_SINGLE([target],[],[The provisioning target, one of: aws, vmware, azure, aws-service-catalog],[])
# ARG_OPTIONAL_SINGLE([count],[],[Number of instances to provision],[1])
# ARG_OPTIONAL_SINGLE([cloud-init],[],[Path to cloud-init file to use],[])
# ARG_OPTIONAL_SINGLE([group],[],[Ansible inventory group name],[])
# ARG_OPTIONAL_SINGLE([config],[],[A file of variables to add to the all ansible group],[])
# ARG_OPTIONAL_SINGLE([hostname],[],[Hostname to use for instance],[])
# ARG_OPTIONAL_BOOLEAN([verbose],[v],[Verbosity],[off])
# ARG_OPTIONAL_SINGLE([host-suffix-format],[],[Date format to append to hostname],[-%V%u-%H%M%S])
# ARG_OPTIONAL_REPEATED([limit],[l],[further limit selected hosts to an additional pattern],[])
# ARG_OPTIONAL_REPEATED([extra],[e],[set additional variables as key=value or YAML/JSON, if            filename prepend with @],[])
# ARG_OPTIONAL_BOOLEAN([install],[],[Install all ansible roles and then exit],[off])
# ARG_OPTIONAL_BOOLEAN([dev-mode],[],[skip role pinning],[off])
# ARG_OPTIONAL_BOOLEAN([debug],[],[skip role pinning],[off])
# ARG_OPTIONAL_BOOLEAN([print-ip],[],[Print only the provisioned IP's to stdout, everything else to stderr],[off])
# ARG_OPTIONAL_SINGLE([git-base],[],[The git repository base for roles],[https://github.com/moshloop])
# ARG_HELP([])
#start_region
# ARGBASH_GO()
# needed because of Argbash --> m4_ignore([
### START OF CODE GENERATED BY Argbash v2.8.1 one line above ###
# Argbash is a bash code generator used to get arguments parsing right.
# Argbash is FREE SOFTWARE, see https://argbash.io for more info


die()
{
	local _ret=$2
	test -n "$_ret" || _ret=1
	test "$_PRINT_HELP" = yes && print_help >&2
	echo "$1" >&2
	exit ${_ret}
}


begins_with_short_option()
{
	local first_option all_short_options='ivleh'
	first_option="${1:0:1}"
	test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0
}

# THE DEFAULTS INITIALIZATION - OPTIONALS
inventory=()
host_prefix=
template=
target=
count="1"
cloud_init=
group=
config=
hostname=
verbose="off"
host_suffix_format="-%V%u-%H%M%S"
limit=()
extra=()
install="off"
dev_mode="off"
debug="off"
print_ip="off"
git_base="https://github.com/moshloop"


print_help()
{
	printf 'Usage: %s [-i|--inventory <arg>] [--host-prefix <arg>] [--template <arg>] [--target <arg>] [--count <arg>] [--cloud-init <arg>] [--group <arg>] [--config <arg>] [--hostname <arg>] [-v|--(no-)verbose] [--host-suffix-format <arg>] [-l|--limit <arg>] [-e|--extra <arg>] [--(no-)install] [--(no-)dev-mode] [--(no-)debug] [--(no-)print-ip] [--git-base <arg>] [-h|--help]\n' "$0"
	printf '\t%s\n' "-i, --inventory: ansible inventory file or directory (empty by default)"
	printf '\t%s\n' "--host-prefix: The prefix to add to each hostname (no default)"
	printf '\t%s\n' "--template: AMI or VMWare template to use to provision (no default)"
	printf '\t%s\n' "--target: The provisioning target, one of: aws, vmware, azure, aws-service-catalog (no default)"
	printf '\t%s\n' "--count: Number of instances to provision (default: '1')"
	printf '\t%s\n' "--cloud-init: Path to cloud-init file to use (no default)"
	printf '\t%s\n' "--group: Ansible inventory group name (no default)"
	printf '\t%s\n' "--config: A file of variables to add to the all ansible group (no default)"
	printf '\t%s\n' "--hostname: Hostname to use for instance (no default)"
	printf '\t%s\n' "-v, --verbose, --no-verbose: Verbosity (off by default)"
	printf '\t%s\n' "--host-suffix-format: Date format to append to hostname (default: '-%V%u-%H%M%S')"
	printf '\t%s\n' "-l, --limit: further limit selected hosts to an additional pattern (empty by default)"
	printf '\t%s\n' "-e, --extra: set additional variables as key=value or YAML/JSON, if            filename prepend with @ (empty by default)"
	printf '\t%s\n' "--install, --no-install: Install all ansible roles and then exit (off by default)"
	printf '\t%s\n' "--dev-mode, --no-dev-mode: skip role pinning (off by default)"
	printf '\t%s\n' "--debug, --no-debug: skip role pinning (off by default)"
	printf '\t%s\n' "--print-ip, --no-print-ip: Print only the provisioned IP's to stdout, everything else to stderr (off by default)"
	printf '\t%s\n' "--git-base: The git repository base for roles (default: 'https://github.com/moshloop')"
	printf '\t%s\n' "-h, --help: Prints help"
}


parse_commandline()
{
	while test $# -gt 0
	do
		_key="$1"
		case "$_key" in
			-i|--inventory)
				test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
				inventory+=("$2")
				shift
				;;
			--inventory=*)
				inventory+=("${_key##--inventory=}")
				;;
			-i*)
				inventory+=("${_key##-i}")
				;;
			--host-prefix)
				test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
				host_prefix="$2"
				shift
				;;
			--host-prefix=*)
				host_prefix="${_key##--host-prefix=}"
				;;
			--template)
				test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
				template="$2"
				shift
				;;
			--template=*)
				template="${_key##--template=}"
				;;
			--target)
				test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
				target="$2"
				shift
				;;
			--target=*)
				target="${_key##--target=}"
				;;
			--count)
				test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
				count="$2"
				shift
				;;
			--count=*)
				count="${_key##--count=}"
				;;
			--cloud-init)
				test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
				cloud_init="$2"
				shift
				;;
			--cloud-init=*)
				cloud_init="${_key##--cloud-init=}"
				;;
			--group)
				test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
				group="$2"
				shift
				;;
			--group=*)
				group="${_key##--group=}"
				;;
			--config)
				test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
				config="$2"
				shift
				;;
			--config=*)
				config="${_key##--config=}"
				;;
			--hostname)
				test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
				hostname="$2"
				shift
				;;
			--hostname=*)
				hostname="${_key##--hostname=}"
				;;
			-v|--no-verbose|--verbose)
				verbose="on"
				test "${1:0:5}" = "--no-" && verbose="off"
				;;
			-v*)
				verbose="on"
				_next="${_key##-v}"
				if test -n "$_next" -a "$_next" != "$_key"
				then
					{ begins_with_short_option "$_next" && shift && set -- "-v" "-${_next}" "$@"; } || die "The short option '$_key' can't be decomposed to ${_key:0:2} and -${_key:2}, because ${_key:0:2} doesn't accept value and '-${_key:2:1}' doesn't correspond to a short option."
				fi
				;;
			--host-suffix-format)
				test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
				host_suffix_format="$2"
				shift
				;;
			--host-suffix-format=*)
				host_suffix_format="${_key##--host-suffix-format=}"
				;;
			-l|--limit)
				test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
				limit+=("$2")
				shift
				;;
			--limit=*)
				limit+=("${_key##--limit=}")
				;;
			-l*)
				limit+=("${_key##-l}")
				;;
			-e|--extra)
				test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
				extra+=("$2")
				shift
				;;
			--extra=*)
				extra+=("${_key##--extra=}")
				;;
			-e*)
				extra+=("${_key##-e}")
				;;
			--no-install|--install)
				install="on"
				test "${1:0:5}" = "--no-" && install="off"
				;;
			--no-dev-mode|--dev-mode)
				dev_mode="on"
				test "${1:0:5}" = "--no-" && dev_mode="off"
				;;
			--no-debug|--debug)
				debug="on"
				test "${1:0:5}" = "--no-" && debug="off"
				;;
			--no-print-ip|--print-ip)
				print_ip="on"
				test "${1:0:5}" = "--no-" && print_ip="off"
				;;
			--git-base)
				test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
				git_base="$2"
				shift
				;;
			--git-base=*)
				git_base="${_key##--git-base=}"
				;;
			-h|--help)
				print_help
				exit 0
				;;
			-h*)
				print_help
				exit 0
				;;
			*)
				_PRINT_HELP=yes die "FATAL ERROR: Got an unexpected argument '$1'" 1
				;;
		esac
		shift
	done
}

parse_commandline "$@"

# OTHER STUFF GENERATED BY Argbash

### END OF CODE GENERATED BY Argbash (sortof) ### ])
# [ <-- needed because of Argbash
#end_region


>&2 echo  "ansible-provision 4.4.2: 91a1c82 fix"
[[ "$DEBUG" != "" ]] && set -x

ROLE_NAME=provision
mkdir -p roles
DIR=$PWD/roles/$ROLE_NAME
GIT_BASE=$git_base
DEV_MODE=$dev_mode
ARGS=

for i in "${inventory[@]}"; do
  ARGS+=" -i $i"
done

for l in "${limit[@]}"; do
  ARGS+=" -l $l"
done

for e in "${extra[@]}"; do
  ARGS+=" -e $e"
done

[[ "$hostname" != "" && "$host_prefix" != "" ]] && hostname=${host_prefix}${hostname}
[[ "$hostname" != "" ]] && host_suffix_format=""
[[ "$hostname" != "" && "$group" == "" ]] && group="all"
[[ "$hostname" == "" && "$host_prefix" != "" ]] && hostname=${host_prefix}
[[ "$hostname" != "" && "$host_suffix_format" != "" ]] && hostname=${hostname}$(date +"$host_suffix_format")
[[ "$cloud_init" != "" ]] && ARGS+=" -e cloud_init_path=$cloud_init"
[[ "$hostname" != "" ]]   && ARGS+=" -e hostname=$hostname"
[[ "$count" != "" ]]      && ARGS+=" -e instance_count=$count"
[[ "$template" != "" ]]   && ARGS+=" -e template=$template"
[[ "$inventory" == ""     && -d inventory ]] && inventory="inventory"
[[ "$verbose" == "on" ]]  && ARGS+=" -v"
[[ "$inventory" == "" ]]  && inventory=.inventory; mkdir -p .inventory; ARGS+=" -i .inventory"

if [[ "$config" != "" ]]; then
  [[ ! -d $inventory/group_vars/all ]] && mkdir -p $inventory/group_vars/all
  temp_cp "$config" $inventory/group_vars/all/00$(basename $config)
fi

checkout_role() {
  mkdir -p roles
  DIR=roles/$1
  if [[ ! -e $DIR ]]; then
    git clone $GIT_BASE/ansible-$1.git $DIR
  fi
  pwd=$(pwd)

  if [[ "$ARG1" == "install" ]]; then
    return
  elif [[  "$ARG1" == "update" ]]; then
    cd $DIR
    git fetch --all
    cd $pwd
    return
  fi

  filter="._meta.hostvars | values[] | .ansible_$1_version"
  desired_tag=$( ansible-inventory -i $inventory --list | jq -r "$filter" | head -n1)

  pwd=$(pwd)
  cd $DIR
  current_tag=$(git describe --tags)

  if [[ "$desired_tag" != "null" && "$desired_tag" != "" && "$desired_tag" != "$current_tag" ]]; then
    log "Checking out $desired_tag"
    git fetch
    git checkout $desired_tag
  fi
  log "Using ansible-${1} $(git log -n1 --oneline)"
  cd $pwd
}

if [[ "$dev_mode" == 'off' ]]; then
  checkout_role "deploy"
  checkout_role "provision"
fi

[[ "$install" == "on" ]] && exit

if [[ ! -e $inventory/hosts && ! -e $inventory/hosts.ini ]]; then
  log "No hosts detected using $hostname/$group"
  echo "[$group]" > $inventory/hosts
  echo "$hostname" >> $inventory/hosts
  delete_on_exit $inventory/hosts
fi

cp $PWD/roles/$ROLE_NAME/$ROLE_NAME.yml $ROLE_NAME.yml
PLAYBOOK=$ROLE_NAME.yml
delete_on_exit $PLAYBOOK


export ANSIBLE_HASH_BEHAVIOUR=merge
export DISPLAY_SKIPPED_HOSTS=false
export DISPLAY_OK_HOSTS=false
export ANSIBLE_CALLBACK_WHITELIST=dense2
# export ANSIBLE_STDOUT_CALLBACK=${ANSIBLE_STDOUT_CALLBACK:-dense2}
export ANSIBLE_CALLBACK_PLUGINS=$DIR/callback_plugins
export ANSIBLE_INVENTORY_IGNORE_REGEX="out,inventory/build"
log "ansible-playbook $ARGS $PLAYBOOK"

if [[ "$print_ip" == "on" ]]; then
  output_dir=.out/$hostname
  mkdir -p $output_dir
  ARGS+=" -e output_dir=$output_dir"
fi
>&2 eval ansible-playbook $ARGS $PLAYBOOK $ANSIBLE_ARGS
if [[ "$print_ip" == "on" ]]; then
  for f in $(ls $output_dir); do
    cat $output_dir/$f | jq -r '.ip'
  done
fi
# ] <-- needed because of Argbash
