#!/usr/bin/env python

"""
Wizard is a next-generation autoinstall management system with an
eye towards flexibility and scalability.

Copyright (c) 2009-2010 the Wizard development team

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

import os
import optparse
import sys
import logging
import traceback

# import some non-standard modules to make it fail out early
import decorator

_root_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.insert(0, _root_dir)
sys.path.insert(0, os.path.join(_root_dir, "plugins/scripts")) # hack to load scripts plugins for now

import wizard
from wizard import command, prompt

def main():
    usage = """usage: %prog COMMAND [ARGS]

Wizard is a Git-based autoinstall management system for scripts.

User commands:
    backup          Backup data not on filesystem (database, etc)
    install         Installs an application
    migrate         Migrate autoinstalls from old format to Git-based format
    remove          Removes an autoinstall, databases and other files
    restore         Restores files and database to previous version
    upgrade         Upgrades an autoinstall to the latest version

Administrative commands:
    prepare-upgrade Downloads and tests a software upgrade
    blacklist       Marks an autoinstall to not try upgrades
    errors          Lists all broken autoinstall metadata
    list            Lists autoinstalls, with optional filtering
    mass-migrate    Performs mass migration of autoinstalls of an application
    mass-upgrade    Performs mass upgrade of autoinstalls of an application
    research        Print statistics about a possible upgrade
    summary         Generate statistics (see help for subcommands)

Plumbing commands:
    prepare-pristine Downloads and extracts pristine upstream files
    prepare-config   Prepares configuration files for versioning
    quota            Prints the usage and available quota of a directory

See '%prog help COMMAND' for more information on a specific command."""

    parser = optparse.OptionParser(usage)
    parser.disable_interspersed_args()
    _, args = parser.parse_args() # no global options
    rest_argv = args[1:]
    baton = command.OptionBaton()
    baton.add("--versions-path", dest="versions_path", metavar="PATH",
        default=getenvpath("WIZARD_VERSIONS_PATH") or "/afs/athena.mit.edu/contrib/scripts/sec-tools/store/versions",
        help="Location of parallel-find output directory, or a file containing a newline separated list of 'all autoinstalls' (for development work).  Environment variable is WIZARD_VERSIONS_PATH.")
    baton.add("--srv-path", dest="srv_path", metavar="PATH",
        default=getenvpath("WIZARD_SRV_PATH") or "/afs/athena.mit.edu/contrib/scripts/git/autoinstalls",
        help="Location of autoinstall Git repositories, such that $REPO_PATH/$APP.git is a repository (for development work).  Environment variable is WIZARD_SRV_PATH.")
    baton.add("--dry-run", dest="dry_run", action="store_true",
            default=False, help="Performs the operation without actually modifying any files.  Use in combination with --verbose to see commands that will be run.")
    # common variables for mass commands
    baton.add("--seen", dest="seen",
            default=None, help="File to read/write paths of successfully modified installs;"
            "these will be skipped on re-runs.  If --log-dir is specified, this is automatically enabled.")
    baton.add("--no-parallelize", dest="no_parallelize", action="store_true",
            default=False, help="Turn off parallelization")
    baton.add("--max-processes", dest="max_processes", type="int", metavar="N",
            default=5, help="Maximum subprocesses to run concurrently")
    baton.add("--limit", dest="limit", type="int",
            default=None, help="Limit the number of autoinstalls to look at.")
    baton.add("--user", "-u", dest="user",
            default=None, help="Only mass migrate a certain user's installs.  No effect if versions_path is a file.")
    try:
        command_name = args[0]
    except IndexError:
        parser.print_help()
        sys.exit(1)
    baton.add("--log-dir", dest="log_dir",
        default=getenvpath("WIZARD_LOG_DIR") or "/tmp/wizard-%s" % command_name,
        help="Log files for Wizard children processes are placed here.")
    if command_name == "help":
        try:
            help_module = get_command(rest_argv[0])
        except ImportError:
            parser.error("invalid action")
        except IndexError:
            parser.print_help()
            sys.exit(1)
        help_module.main(['--help'], baton)
    # This is a gigantic hack to handle the case of AFS + Scripts style
    # permissions, where we usually don't have access to the HOME
    # directory.  AFS will throttle you if you trigger too many
    # lack of permissions, and since we run Git a lot and Git
    # persistently stats the home directory, this can cause pretty
    # big problems for our performance.
    if not os.access(os.environ['HOME'], os.R_OK):
        os.putenv('HOME', '/disabled')
    # Dispatch commands
    command_module = get_command(command_name)
    try:
        command_module.main(rest_argv, baton)
    except prompt.UserCancel as e:
        print str(e)
        sys.exit(1)
    except Exception as e:
        # log the exception
        msg = traceback.format_exc()
        if command.logging_setup:
            outfun = logging.error
        else:
            outfun = sys.stderr.write
        if isinstance(e, wizard.Error):
            if e.quiet and not command.debug:
                msg = str(e)
                if command.logging_setup:
                    msg = msg.replace("ERROR: ", "")
            outfun(msg)
            sys.exit(e.exitcode)
        else:
            outfun(msg)
            sys.exit(1)

def get_command(name):
    name = name.replace("-", "_")
    __import__("wizard.command." + name)
    return getattr(wizard.command, name)

def getenvpath(name):
    val = os.getenv(name)
    if val:
        val = os.path.abspath(val)
    return val

if __name__ == "__main__":
    main()

