@@ -1,7 +1,8 @@
-#!/bin/bash
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
 
 # debops: run ansible-playbook with some customization
-# Copyright (C) 2014 Maciej Delmanowski <drybjed@gmail.com>
+# Copyright (C) 2014 Hartmut Goebel <h.goebel@crazy-compilers.com>
 # Part of the DebOps project - http://debops.org/
 
 
@@ -26,185 +27,142 @@
 # 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
-}
-
+from __future__ import print_function
+
+import sys
+import os
+
+from debops import *
+from debops.cmds import *
+
+PATHSEP = ':'
+
+def gen_ansible_cfg(filename, debops_root, playbooks_path, inventory_path):
+    # Generate Ansible configuration file
+    def p(*args): print(*args, file=fh)
+    with open(filename, "w") as fh:
+        p("# Ansible configuration file generated by DebOps, all changes will be lost")
+        p()
+        p("[defaults]")
+        p("hostfile           = ", inventory_path)
+        p()
+        p("roles_path         = ", PATHSEP.join((
+            os.path.join(debops_root, "roles"),
+            os.path.join(debops_root, "ansible", "roles"),
+            os.path.join(playbooks_path, "..", "roles"),
+            os.path.join(playbooks_path, "roles"),
+            "/etc/ansible/roles")
+        ))
+        p("action_plugins     =", PATHSEP.join((
+            os.path.join(debops_root, "ansible", "action_plugins"),
+            os.path.join(playbooks_path, "action_plugins"),
+            "/usr/share/ansible_plugins/action_plugins")
+        ))
+        p("callback_plugins   =", PATHSEP.join((
+            os.path.join(debops_root, "ansible", "callback_plugins"),
+            os.path.join(playbooks_path, "callback_plugins"),
+            "/usr/share/ansible_plugins/callback_plugins")
+        ))
+        p("connection_plugins =", PATHSEP.join((
+            os.path.join(debops_root, "ansible", "connection_plugins"),
+            os.path.join(playbooks_path, "connection_plugins"),
+            "/usr/share/ansible_plugins/connection_plugins")
+         ))
+        p("lookup_plugins     =", PATHSEP.join((
+            os.path.join(debops_root, "ansible", "lookup_plugins"),
+            os.path.join(playbooks_path, "lookup_plugins"),
+            "/usr/share/ansible_plugins/lookup_plugins")
+        ))
+        p("vars_plugins       =", PATHSEP.join((
+            os.path.join(debops_root, "ansible", "vars_plugins"),
+            os.path.join(playbooks_path, "vars_plugins"),
+            "/usr/share/ansible_plugins/vars_plugins")
+        ))
+        p("filter_plugins     =", PATHSEP.join((
+            os.path.join(debops_root, "ansible", "filter_plugins"),
+            os.path.join(playbooks_path, "filter_plugins"),
+            "/usr/share/ansible_plugins/filter_plugins")
+        ))
 
 # ---- 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} "${@}"
-
+def main(cmd_args):
+    debops_root = find_debops_project(required=True)
+    # :todo: Source DebOps configuration file
+    #[ -r ${debops_config} ] && source ${debops_config}
+
+    # Make sure required commands are present
+    require_commands('ansible-playbook')
+
+    playbooks_path = find_playbookpath(debops_root, required=True)
+
+    # 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
+    tries = [
+        (debops_root, "playbooks", "site.yml"),
+        (debops_root, "ansible", "playbooks", "site.yml"),
+        (playbooks_path, "site.yml"),
+    ]
+    if len(cmd_args) > 0:
+        maybe_play = cmd_args[0]
+        tries[:0] = [
+            (maybe_play,),
+            (debops_root, "playbooks", maybe_play + ".yml"),
+            (debops_root, "ansible", "playbooks", maybe_play + ".yml"),
+            (playbooks_path, maybe_play + ".yml"),
+        ]
+    else:
+        # set for error message
+        maybe_play = 'site.yml'
+    for parts in tries:
+        play = os.path.join(*parts)
+        if os.path.isfile(play):
+            break
+    else:
+        # loop finished without finding a playbook
+        error_msg("Playbook %s not found" % maybe_play)
+
+    inventory_path = find_inventorypath(debops_root)
+    os.environ['ANSIBLE_HOSTS'] = inventory_path
+
+    ansible_config_file = os.path.join(debops_root, ANSIBLE_CONFIG_FILE)
+    os.environ['ANSIBLE_CONIG'] = os.path.abspath(ansible_config_file)
+    gen_ansible_cfg(ansible_config_file, debops_root, playbooks_path,
+                    inventory_path)
+
+    # Add custom configuration options to ansible.cfg
+    # :todo: replace ansible_config_hook by data from .debops.cfg
+    #if type(ansible_config_hook):
+    #    ansible_config_hook #>> ${debops_root}/${ANSIBLE_CONFIG_FILE}
+
+    # Allow insecure SSH connections if requested
+    if INSECURE:
+        os.environ['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
+
+    # Create path to EncFS encrypted directory, based on inventory name
+    encfs_encrypted = os.path.join(os.path.dirname(inventory_path),
+                                   ENCFS_PREFIX + SECRET_NAME)
+
+    # Check if encrypted secret directory exists and use it
+    padlock = os.path.join(encfs_encrypted, PADLOCK_CMD)
+    if False and is_executable(padlock):
+        print("Found encrypted secrets in", encfs_encrypted)
+        padlock_unlock(encfs_encrypted)
+        # :todo: rethink: install an on-exit handler?
+    else:
+        padlock = None
+    try:
+        # Run ansible-playbook with custom environment
+        print("Running Ansible playbook from:")
+        print(play, "...")
+        subprocess.call(['ansible-playbook', play] + cmd_args)
+    finally:
+        if padlock:
+            padlock_lock(encfs_encrypted)
+
+
+try:
+    main(sys.argv[1:])
+except KeyboardInterrupt:
+    raise SystemExit('... aborted')