#!/usr/bin/env python

# (c) 2014, James Tanner <tanner.jc@gmail.com>
#
# Ansible 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 3 of the License, or
# (at your option) any later version.
#
# Ansible 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 Ansible.  If not, see <http://www.gnu.org/licenses/>.
#
# ansible-pull is a script that runs ansible in local mode
# after checking out a playbooks directory from source repo.  There is an
# example playbook to bootstrap this script in the examples/ dir which
# installs ansible and sets it up to run on cron.

#__requires__ = ['ansible']
#import pkg_resources

import os
import sys
import traceback

from ansible import utils
from ansible import errors
from ansible.utils.vault import VaultEditor

from optparse import OptionParser

#-------------------------------------------------------------------------------------
# Utility functions for parsing actions/options
#-------------------------------------------------------------------------------------

VALID_ACTIONS = ("create", "decrypt", "edit", "encrypt", "rekey")

def build_option_parser(action):
    """
    Builds an option parser object based on the action
    the user wants to execute.
    """

    usage = "usage: %%prog [%s] [--help] [options] file_name" % "|".join(VALID_ACTIONS)
    epilog = "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0])
    OptionParser.format_epilog = lambda self, formatter: self.epilog
    parser = OptionParser(usage=usage, epilog=epilog)

    if not action:
        parser.print_help()
        sys.exit()

    # options for all actions
    #parser.add_option('-c', '--cipher', dest='cipher', default="AES256", help="cipher to use")
    parser.add_option('--debug', dest='debug', action="store_true", help="debug")
    parser.add_option('--vault-password-file', dest='password_file',
                    help="vault password file")

    # options specific to actions
    if action == "create":
        parser.set_usage("usage: %prog create [options] file_name")
    elif action == "decrypt":
        parser.set_usage("usage: %prog decrypt [options] file_name")
    elif action == "edit":
        parser.set_usage("usage: %prog edit [options] file_name")
    elif action == "encrypt":
        parser.set_usage("usage: %prog encrypt [options] file_name")
    elif action == "rekey":
        parser.set_usage("usage: %prog rekey [options] file_name")
    
    # done, return the parser
    return parser

def get_action(args):
    """
    Get the action the user wants to execute from the 
    sys argv list.
    """
    for i in range(0,len(args)):
        arg = args[i]
        if arg in VALID_ACTIONS:
            del args[i]
            return arg
    return None

def get_opt(options, k, defval=""):
    """
    Returns an option from an Optparse values instance.
    """
    try:
        data = getattr(options, k)
    except:
        return defval
    if k == "roles_path":
        if os.pathsep in data:
            data = data.split(os.pathsep)[0]
    return data

#-------------------------------------------------------------------------------------
# Command functions
#-------------------------------------------------------------------------------------

def _read_password(filename):
    f = open(filename, "rb")
    data = f.read()
    f.close()
    data = data.strip()
    return data

def execute_create(args, options, parser):

    if len(args) > 1:
        raise errors.AnsibleError("'create' does not accept more than one filename")        

    if not options.password_file:        
        password, new_password = utils.ask_vault_passwords(ask_vault_pass=True, confirm_vault=True)
    else:
        password = _read_password(options.password_file)

    cipher = 'AES256'
    if hasattr(options, 'cipher'):
        cipher = options.cipher

    this_editor = VaultEditor(cipher, password, args[0])
    this_editor.create_file()

def execute_decrypt(args, options, parser):

    if not options.password_file:        
        password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
    else:
        password = _read_password(options.password_file)

    cipher = 'AES256'
    if hasattr(options, 'cipher'):
        cipher = options.cipher

    for f in args:
        this_editor = VaultEditor(cipher, password, f)
        this_editor.decrypt_file()

    print "Decryption successful"

def execute_edit(args, options, parser):

    if len(args) > 1:
        raise errors.AnsibleError("create does not accept more than one filename")        

    if not options.password_file:        
        password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
    else:
        password = _read_password(options.password_file)

    cipher = None

    for f in args:
        this_editor = VaultEditor(cipher, password, f)
        this_editor.edit_file()

def execute_encrypt(args, options, parser):

    if not options.password_file:        
        password, new_password = utils.ask_vault_passwords(ask_vault_pass=True, confirm_vault=True)
    else:
        password = _read_password(options.password_file)

    cipher = 'AES256'
    if hasattr(options, 'cipher'):
        cipher = options.cipher

    for f in args:
        this_editor = VaultEditor(cipher, password, f)
        this_editor.encrypt_file()

    print "Encryption successful"

def execute_rekey(args, options, parser):

    if not options.password_file:        
        password, __ = utils.ask_vault_passwords(ask_vault_pass=True)
    else:
        password = _read_password(options.password_file)

    __, new_password = utils.ask_vault_passwords(ask_vault_pass=False, ask_new_vault_pass=True, confirm_new=True)

    cipher = None
    for f in args:
        this_editor = VaultEditor(cipher, password, f)
        this_editor.rekey_file(new_password)

    print "Rekey successful"

#-------------------------------------------------------------------------------------
# MAIN
#-------------------------------------------------------------------------------------

def main():

    action = get_action(sys.argv)
    parser = build_option_parser(action)
    (options, args) = parser.parse_args()

    # execute the desired action
    try:
        fn = globals()["execute_%s" % action]
        fn(args, options, parser)
    except Exception, err:
        if options.debug:
            print traceback.format_exc()
        print "ERROR:",err
        sys.exit(1)

if __name__ == "__main__":
    main()