#!/usr/bin/env python

# pyinfra
# File: bin/pyinfra
# Desc: __main__ for pyinfra

'''
pyinfra

Usage:
    pyinfra -i INVENTORY DEPLOY [options] [-v | -vv]
    pyinfra -i INVENTORY --fact FACT [options] [-v]
    pyinfra (--facts | --help | --version)

Options:
    DEPLOY               Deploy script filename.
    -i INVENTORY         Inventory script filename.
    --limit HOSTNAME     Limit the inventory at runtime, supports *wildcards.
    --serial             Run commands on one host at a time.
    --nowait             Don't wait for all hosts at each operation.
    -v                   Prints remote input/output in realtime. -vv prints facts.
    --dry                Only print proposed changes.
    --fact FACT          Name of fact to run/test.
    --user USER          SSH user.
    --key KEY_FILE       SSH private key.
    --port PORT#         SSH port number.
    --sudo               Use sudo.
    --sudo-user USER     Which user to sudo to.
    --debug              Print debug info.
    --dir DEPLOY_DIR     Sets the directory to look for group_data/files/etc.
'''

from gevent import monkey
monkey.patch_all()

import sys
import json
import signal
import logging
from os import path

import gevent
from docopt import docopt
from coloredlogs import ColoredStreamHandler

from pyinfra import state, host, logger
from pyinfra.__version__ import VERSION
from pyinfra.cli import make_inventory, load_config, print_meta, print_results, json_encode

from pyinfra.api import State
from pyinfra.api.ssh import connect_all
from pyinfra.api.operations import run_ops
from pyinfra.api.facts import facts, get_facts, set_print_facts


# Handle ctrl+c
def signal_handler(signum, frame):
    print 'Exiting upon user request!'
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)

# Setup arguments
version = 'pyinfra v{0}'.format(VERSION)
arguments = docopt(__doc__, version=version)
arguments = {
    'inventory': arguments['-i'],
    'deploy': arguments['DEPLOY'],
    'verbose': arguments['-v'],
    'dry': arguments['--dry'],
    'serial': arguments['--serial'],
    'nowait': arguments['--nowait'],
    'debug': arguments['--debug'],
    'fact': arguments['--fact'],
    'limit': arguments['--limit'],
    'list_facts': arguments['--facts'],
    'deploy_dir': arguments['--dir'],
    'user': arguments['--user'],
    'key': arguments['--key'],
    'port': arguments['--port'],
    'sudo': arguments['--sudo'],
    'sudo_user': arguments['--sudo-user']
}

print_output = arguments['verbose'] > 0
print_facts = print_output if arguments['deploy'] is None else arguments['verbose'] > 1
set_print_facts(print_facts)

# Setup logs
logging.getLogger().setLevel(logging.CRITICAL)
log_level = logging.DEBUG if arguments['debug'] else logging.INFO
color_args = {
    'show_timestamps': False,
    'show_hostname': False,
    'show_name': False
}
handler = ColoredStreamHandler(level=log_level, **color_args)
logger.setLevel(log_level)
logger.addHandler(handler)


print
print '-> Welcome to {0}'.format(version)
print


# Quickly list facts & exit if desired
if arguments['list_facts']:
    logger.info('Available facts list:')
    print json.dumps(facts.keys(), indent=4)
    sys.exit(0)


# Now we're doing some work, either a dry or real run

# Setup our "working directory", taking --dir as default
if arguments['deploy_dir']:
    state.set_dir(arguments['deploy_dir'])
# This is the most common case: we have a deploy file so use it's pathname
elif arguments['deploy'] is not None:
    state.set_dir(path.dirname(arguments['deploy']))

# Load up the inventory from the filesystem
inventory = make_inventory(
    arguments['inventory'],
    limit=arguments['limit'],
    ssh_user=arguments['user'],
    ssh_key=arguments['key'],
    ssh_port=arguments['port']
)

# Load up any config.py from the filesystem
config = load_config()
# Arg based overrides
if arguments['sudo']:
    config.SUDO = True
    if arguments['sudo_user']:
        config.SUDO_USER = arguments['sudo_user']

# Set the state
state.new(State(inventory, config))

# Connect to all the servers
connect_all()
print

# No deploy file, we're getting a fact
if arguments['deploy'] is None:
    if ':' in arguments['fact']:
        fact, arg = arguments['fact'].split(':')
    else:
        fact = arguments['fact']
        arg = None

    fact_data = get_facts(
        fact, arg=arg,
        sudo=arguments['sudo'], sudo_user=arguments['sudo_user'],
        print_output=print_output
    )
    print json.dumps(fact_data, indent=4)

    sys.exit(0)

# We're building a deploy!
logger.info('Building deploy scripts...')

# By building the deploy scripts with pre_run we can pre-empt the facts required for the deploy
state.pre_run = True
for host_obj in inventory:
    host.set(host_obj)
    execfile(arguments['deploy'])
state.pre_run = False

# Now get the facts we discovered above, *before* we build any ops
for (fact, sudo, sudo_user) in state.pre_run_facts:
    gevent.spawn(
        get_facts, fact, arg=None,
        sudo=sudo, sudo_user=sudo_user, print_output=print_facts
    )

# This actually does the op build
for host_obj in inventory:
    host.set(host_obj)
    execfile(arguments['deploy'])

# Always show meta output
print
logger.info('Proposed changes:')
print_meta()

# Not running the op?
if arguments['dry']:
    if print_output:
        print
        logger.info('Proposed operations:')
        print json.dumps(state.ops, indent=4, default=json_encode)
        print
        logger.info('Operation meta:')
        print json.dumps(state.op_meta, indent=4)
        print
        logger.info('Operation order:')
        print json.dumps(state.op_order, indent=4)

# Run the operations we generated with the deploy file
else:
    print
    logger.info('Beginning operation run...')
    run_ops(
        serial=arguments['serial'],
        nowait=arguments['nowait'],
        print_output=print_output
    )

    print
    logger.info('Results:')
    print_results()


print
print '<- Thank you, goodbye'
print

sys.exit(0)
