Wizard is a Git-based autoinstall management system for scripts.
Its commands are:
+ backup Backup data not on filesystem (database, etc)
configure Configures an autoinstall (database, etc) to work
errors Lists all broken autoinstall metadata
install Installs an application
ERROR: Upgrade script failed, details:
%s""" % self.details
+
+class BackupFailure(Error):
+ """Backup script failed."""
+ #: String details of failure
+ details = None
+ def __init__(self, details):
+ self.details = details
+ def __str__(self):
+ return """
+
+ERROR: Backup script failed, details:
+
+%s""" % self.details
import re
import distutils.version
import os
+import datetime
+import logging
+import shlex
-from wizard import app, deploy, install, shell, util
+from wizard import app, deploy, install, scripts, shell, util
from wizard.app import php
def make_filename_regex(var):
result = sh.eval("php", "maintenance/update.php", "--quick", log=True)
if not result.rstrip().split()[-1] == "Done.":
raise app.UpgradeFailure(result)
+ def backup(self, deployment, options):
+ sh = shell.Shell()
+ vars = deployment.extract()
+ if any(vars[i] is None for i in ['WIZARD_DBSERVER', 'WIZARD_DBNAME', 'WIZARD_DBUSER', 'WIZARD_DBPASSWORD']):
+ raise app.BackupFailure("Missing WIZARD variables from configuration files")
+ # XXX: duplicate code, refactor, also, race condition
+ backupdir = os.path.join(".scripts", "backups")
+ outdir = os.path.join(backupdir, str(deployment.version) + "-" + datetime.date.today().isoformat())
+ if not os.path.exists(backupdir):
+ os.mkdir(backupdir)
+ if os.path.exists(outdir):
+ util.safe_unlink(outdir)
+ os.mkdir(outdir)
+ outfile = os.path.join(outdir, "db.sql")
+ # XXX: add support for getting these out of options
+ triplet = scripts.get_sql_credentials()
+ args = ["mysqldump", "--compress", "-r", outfile];
+ if triplet is not None:
+ server, user, password = triplet
+ args += ["-h", server, "-u", user, "-p" + password]
+ name = shlex.split(vars['WIZARD_DBNAME'])[0]
+ args.append(name)
+ try:
+ sh.call(*args)
+ sh.call("gzip", "--best", outfile)
+ except shell.CallError as e:
+ raise app.BackupFailure(e.stderr)
self.add_option("-h", "--help", action="help", help=optparse.SUPPRESS_HELP)
group = optparse.OptionGroup(self, "Common Options")
group.add_option("-v", "--verbose", dest="verbose", action="store_true",
- default=boolish(os.getenv("WIZARD_VERBOSE")), help="Turns on verbose output. Environment variable is WIZARD_VERBOSE")
+ default=boolish(os.getenv("WIZARD_VERBOSE")), help="Turns on verbose output. Envvar is WIZARD_VERBOSE")
group.add_option("--debug", dest="debug", action="store_true",
- default=boolish(os.getenv("WIZARD_DEBUG")), help="Turns on debugging output. Environment variable is WIZARD_DEBUG")
+ default=boolish(os.getenv("WIZARD_DEBUG")), help="Turns on debugging output. Envvar is WIZARD_DEBUG")
group.add_option("-q", "--quiet", dest="quiet", action="store_true",
- default=boolish(os.getenv("WIZARD_QUIET")), help="Turns off output to stdout. Environment variable is WIZARD_QUIET")
+ default=boolish(os.getenv("WIZARD_QUIET")), help="Turns off output to stdout. Envvar is WIZARD_QUIET")
group.add_option("--log-file", dest="log_file", metavar="FILE",
default=None, help="Logs verbose output to file")
self.add_option_group(group)
--- /dev/null
+import logging
+import optparse
+import sys
+import distutils.version
+
+from wizard import command, deploy, git, shell, util
+
+def main(argv, baton):
+ options, args = parse_args(argv, baton)
+ if not args:
+ dir = "."
+ else:
+ dir = args[0]
+ shell.drop_priviledges(dir, options.log_file)
+ util.chdir(dir)
+ d = deploy.Deployment(".")
+ d.verify()
+ d.verifyConfigured()
+ d.application.backup(d, options)
+
+def parse_args(argv, baton):
+ usage = """usage: %prog backup [ARGS] [DIR]
+
+Takes a configured autoinstall and performs a backup of
+its data. This data is stored by default in
+.scripts/backups/x.y.z-yyyy-mm-dd"""
+ parser = command.WizardOptionParser(usage)
+ options, args = parser.parse_all(argv)
+ if len(args) > 1:
+ parser.error("too many arguments")
+ return options, args
+
if has_git:
logging.warning("Force removing .git directory")
- if not options.dry_run: backup = safe_unlink(".git")
+ if not options.dry_run: backup = util.safe_unlink(".git")
logging.info(".git backed up to %s" % backup)
if has_scripts:
logging.warning("Force removing .scripts directory")
- if not options.dry_run: backup = safe_unlink(".scripts")
+ if not options.dry_run: backup = util.safe_unlink(".scripts")
logging.info(".scripts backed up to %s" % backup)
-def safe_unlink(file):
- """Moves a file to a backup location."""
- prefix = "%s.bak" % file
- name = None
- for i in itertools.count():
- name = "%s.%d" % (prefix, i)
- if not os.path.exists(name):
- break
- os.rename(file, name)
- return name
-
def make_repository(sh, options, repo, tag):
sh.call("git", "init") # create repository
# configure our alternates (to save space and make this quick)
def install(self, version, options):
"""
Run for 'wizard configure' (and, by proxy, 'wizard install')
- to configure an application.
+ to configure an application. This assumes that the current
+ working directory is a deployment.
"""
raise NotImplemented
def upgrade(self, version, options):
"""
Run for 'wizard upgrade' to upgrade database schemas and other
- non-versioned data in an application.
+ non-versioned data in an application. This assumes that
+ the current working directory is the deployment.
+ """
+ raise NotImplemented
+ def backup(self, deployment, options):
+ """
+ Run for 'wizard backup' and upgrades to backup database schemas
+ and other non-versioned data in an application. This assumes
+ that the current working directory is the deployment.
"""
raise NotImplemented
def detectVersion(self, deployment):
import getpass
import wizard
-from wizard import shell, util
+from wizard import scripts, shell, util
def fetch(options, path, post=None):
"""
def execute(self, options):
"""Attempts to create a database using Scripts utilities."""
sh = shell.Shell()
- try:
- triplet = sh.eval("/mit/scripts/sql/bin/get-password").split()
- except:
+ triplet = scripts.get_sql_credentials()
+ if not triplet:
raise StrategyFailed
name = os.path.basename(os.getcwd())
username = os.getenv('USER')
for sets in (argsets_strategy, argsets_strategy_with_side_effects):
for argset in sets:
if all_set(argset): continue
- argset.strategy.execute(options)
+ try:
+ argset.strategy.execute(options)
+ except StrategyFailed:
+ pass
for arg in argset.args:
if getattr(options, arg.name) is None:
# XXX: arg.prompt(options)
--- /dev/null
+from wizard import shell
+
+def get_sql_credentials():
+ """
+ Attempts to determine a user's MySQL credentials. They are
+ returned as a three-tuple (host, user, password).
+ """
+ sh = shell.Shell()
+ try:
+ return sh.eval("/mit/scripts/sql/bin/get-password").split()
+ except CallError:
+ return None
+
import sys
import socket
import errno
+import itertools
import wizard
,"Wizard-args: %s" % " ".join(sys.argv)
])
+def safe_unlink(file):
+ """Moves a file/dir to a backup location."""
+ prefix = "%s.bak" % file
+ name = None
+ for i in itertools.count():
+ name = "%s.%d" % (prefix, i)
+ if not os.path.exists(name):
+ break
+ os.rename(file, name)
+ return name
+
class NoOperatorInfo(wizard.Error):
"""No information could be found about the operator from Kerberos."""
pass