#!/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
-sys.path.insert(0,os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+# 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
+from wizard import command, prompt
def main():
usage = """usage: %prog COMMAND [ARGS]
Wizard is a Git-based autoinstall management system for scripts.
-Its commands are:
- configure Configures an autoinstall (database, etc) to work
- errors Lists all broken autoinstall metadata
+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:
+ 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
- migrate Migrate autoinstalls from old format to Git-based format
- prepare-config Prepares configuration files for versioning
+ mass-upgrade Performs mass upgrade of autoinstalls of an application
+ research Print statistics about a possible upgrade
summary Generate statistics (see help for subcommands)
- upgrade Upgrades an autoinstall to the latest version
+
+Utility commands:
+ prepare-pristine Downloads and extracts pristine upstream files
+ prepare-new Prepares a new repository
+ 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."""
_, args = parser.parse_args() # no global options
rest_argv = args[1:]
baton = command.OptionBaton()
- baton.add("--versions-path", dest="versions_path",
+ 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",
+ 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("--log-dir", dest="log_dir",
- default=getenvpath("WIZARD_LOG_DIR") or None,
- help="Log files for Wizard children processes are placed here.")
+ 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()
- raise SystemExit(1)
+ 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])
parser.error("invalid action")
except IndexError:
parser.print_help()
- raise SystemExit(1)
+ 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 = get_command(command_name)
- except ImportError:
- parser.error("invalid action")
- command_module.main(rest_argv, baton)
+ 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("-", "_")