- We have safe, non-braindead
version detection with `git describe --tags`. Switch
everything to use it.
+- wizard.util is pretty braindead at this point. Fix up
+ the wildly varying conventions in it.
- Better error message if daemon/scripts-security-upd
is not on scripts-security-upd list
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
info Reports information about an autoinstall
+ install Installs an application
list Lists autoinstalls, with optional filtering
massmigrate Performs mass migration of autoinstalls of an application
migrate Migrate autoinstalls from old format to Git-based format
except OSError:
pass
+ os.chmod("config", 0777) # XXX: vaguely sketchy
postdata = {
'Sitename': options.title,
'EmergencyContact': options.email,
import logging
import sys
-from wizard import command, deploy, install, util
+from wizard import command, deploy, git
def main(argv, baton):
# SKETCHY!
- tag = util.get_dir_tag()
+ tag = git.describe()
application, _, version = tag.partition('-')
- name = "wizard.app." + application
- __import__(name)
- app_module = sys.modules[name]
- app = app_module.Application(application)
+ app = deploy.applications()[application]
handler = app.install_handler
usage = """usage: %prog configure [ARGS]
--- /dev/null
+import os
+import shutil
+import logging
+import errno
+import sys
+
+import wizard
+from wizard import command, deploy, shell, util
+
+def main(argv, baton):
+ options, args = parse_args(argv)
+ # XXX: do something smart if -scripts is not at the end
+ dir = args[0]
+ if os.path.exists(dir):
+ raise DirectoryExistsError
+ appname, _, version = options.app.partition('-')
+ app = deploy.applications()[appname]
+ sh = shell.Shell()
+ sh.call("git", "clone", "--shared", app.repository, dir)
+ with util.ChangeDirectory(dir):
+ if version:
+ sh.call("git", "checkout", options.app)
+ # this command's stdin should be hooked up to ours
+ try:
+ sh.call("wizard", "configure", *args[1:], interactive=True)
+ except shell.PythonCallError:
+ sys.exit(1)
+
+def parse_args(argv):
+ usage = """usage: %prog install [--app APP] [DIR -- [SETUPARGS]]
+
+Autoinstalls an application."""
+ parser = command.WizardOptionParser(usage)
+ parser.add_option("--app", dest="app",
+ help="Application to install, optionally specifying a version as APP-VERSION")
+ options, args = parser.parse_all(argv)
+ if not args:
+ # XXX: in the future, not specifying stuff is supported, since
+ # we'll prompt for it interactively
+ parser.error("must specify application")
+ return options, args
+
+class DirectoryExistsError(wizard.Error):
+ def __str__(self):
+ return "Directory already exists"
return (on_success, on_error)
on_success, on_error = make_on_pair(d)
sh.wait() # wait for a parallel processing slot to be available
- sh.callAsUser(shell.wizard_bin, "migrate", d.location, *base_args,
+ sh.callAsUser("wizard", "migrate", d.location, *base_args,
uid=uid, on_success=on_success, on_error=on_error)
sh.join()
for name, deploys in errors.items():
--- /dev/null
+"""
+Helper functions for dealing with Git.
+"""
+
+from wizard import shell
+
+def describe():
+ """Finds the output of git describe --tags of the current directory."""
+ return shell.Shell().safeCall("git", "describe", "--tags")[0].rstrip()
+
preloads = None
def __init__(self, preloads):
self.preloads = preloads
- self.message = str(self)
def __str__(self):
return "Did not recognize these preloads: " + ", ".join(self.preloads)
param = None
def __init__(self, arg):
self.arg = arg
- self.message = str(self)
def __str__(self):
return "Missing required parameter %s; try specifying %s" % (self.arg.name, self.arg.option)
import os
import wizard
-import wizard.util
+from wizard import util
wizard_bin = sys.argv[0]
"""
This is the path to the wizard executable as specified
-by the caller; it lets us recursively invoke wizard. Example::
-
- from wizard import shell
- sh = shell.Shell()
- sh.call(shell.wizard_bin, "list")
+by the caller; it lets us recursively invoke wizard.
"""
def is_python(args):
"""Detects whether or not an argument list invokes a Python program."""
- return args[0] == "python" or args[0] == wizard_bin
+ return args[0] == "python" or args[0] == "wizard"
class Shell(object):
"""
def call(self, *args, **kwargs):
"""
Performs a system call. The actual executable and options should
- be passed as arguments to this function. Several keyword arguments
+ be passed as arguments to this function. It will magically
+ ensure that 'wizard' as a command works. Several keyword arguments
are also supported:
:param python: explicitly marks the subprocess as Python or not Python
for improved error reporting. By default, we use
:func:`is_python` to autodetect this.
:param input: input to feed the subprocess on standard input.
+ :param interactive: whether or not directly hook up all pipes
+ to the controlling terminal, to allow interaction with subprocess.
:returns: a tuple of strings ``(stdout, stderr)``
>>> sh = Shell()
return
if kwargs["python"] is None and is_python(args):
kwargs["python"] = True
+ if args[0] == "wizard":
+ args = list(args)
+ args[0] = wizard_bin
+ kwargs.setdefault("input", None)
+ kwargs.setdefault("interactive", False)
+ if kwargs["interactive"]:
+ stdout=sys.stdout
+ stdin=sys.stdin
+ stderr=sys.stderr
+ else:
+ stdout=subprocess.PIPE
+ stdin=subprocess.PIPE
+ stderr=subprocess.PIPE
# XXX: There is a possible problem here where we can fill up
# the kernel buffer if we have 64KB of data. This shouldn't
# be a problem, and the fix for such case would be to write to
# ourself, and then setting up a
# SIGCHILD handler to write a single byte to the pipe to get
# us out of select() when a subprocess exits.
- proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
+ proc = subprocess.Popen(args, stdout=stdout, stderr=stderr, stdin=stdin)
if hasattr(self, "_async"):
self._async(proc, args, **kwargs)
return proc
- kwargs.setdefault("input", None)
stdout, stderr = proc.communicate(kwargs["input"])
- self._log(stdout, stderr)
+ if not kwargs["interactive"]:
+ self._log(stdout, stderr)
if proc.returncode:
if kwargs["python"]: eclass = PythonCallError
else: eclass = CallError
uid = kwargs.pop("uid", None)
kwargs.setdefault("python", is_python(args))
if not user and not uid: return self.call(*args, **kwargs)
- if wizard.util.get_operator_name():
+ if util.get_operator_name():
# This might be generalized as "preserve some environment"
- args.insert(0, "SSH_GSSAPI_NAME=" + wizard.util.get_operator_name())
+ args.insert(0, "SSH_GSSAPI_NAME=" + util.get_operator_name())
if uid: return self.call("sudo", "-u", "#" + str(uid), *args, **kwargs)
if user: return self.call("sudo", "-u", user, *args, **kwargs)
def safeCall(self, *args, **kwargs):
#: Name of the uncaught exception.
name = None
def __init__(self, code, args, stdout, stderr):
- self.name = wizard.util.get_exception_name(stderr)
+ if stderr: self.name = util.get_exception_name(stderr)
CallError.__init__(self, code, args, stdout, stderr)
def __str__(self):
- return "PythonCallError [%s]" % self.name
+ if self.name:
+ return "PythonCallError [%s]" % self.name
+ else:
+ return "PythonCallError"
import sys
import wizard
-from wizard import shell
class ChangeDirectory(object):
"""
"""
return pwd.getpwuid(get_dir_uid(dir)).pw_name
-def get_dir_tag():
- """Finds the output of git describe --tags of the current directory."""
- return shell.Shell().safeCall("git", "describe", "--tags")[0].rstrip()
-
def get_revision():
"""Returns the commit ID of the current Wizard install."""
wizard_git = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".git")