]> scripts.mit.edu Git - wizard.git/commitdiff
Implement 'wizard install', with other improvements.
authorEdward Z. Yang <ezyang@mit.edu>
Fri, 7 Aug 2009 06:37:46 +0000 (02:37 -0400)
committerEdward Z. Yang <ezyang@mit.edu>
Fri, 7 Aug 2009 06:41:21 +0000 (02:41 -0400)
* Chmod config directory to be 777 for mediawiki autoinstall
* Rewrite configure to use applications() function
* Replace wizard_bin with "wizard", which then gets converted
* Add support for interactive mode
* Remove wizard.util hack by splitting out into git module
* Allow PythonCallError to work even when stderr is None
* Update TODO

Signed-off-by: Edward Z. Yang <ezyang@mit.edu>
TODO
bin/wizard
wizard/app/mediawiki.py
wizard/command/configure.py
wizard/command/install.py [new file with mode: 0644]
wizard/command/massmigrate.py
wizard/git.py [new file with mode: 0644]
wizard/install.py
wizard/shell.py
wizard/util.py

diff --git a/TODO b/TODO
index 7c6fe0a965e041eb133fedae8989d247bd27b53d..6798bc76b4db68a0d81e0be18a2a7e8d81e3115a 100644 (file)
--- a/TODO
+++ b/TODO
@@ -5,6 +5,8 @@ TODO NOW:
 - 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
index 6ff98a4ad3c850431a14275a623ab0c341cf4772..7169496aebf63059858f98463e0052e32420c209 100755 (executable)
@@ -15,8 +15,10 @@ def main():
 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
index 8c87b463f412424165f09cd79b06daa7e075a00e..d0888e8f33ce3249abea15a4e3dc3457e1950e1a 100644 (file)
@@ -38,6 +38,7 @@ class Application(deploy.Application):
         except OSError:
             pass
 
+        os.chmod("config", 0777) # XXX: vaguely sketchy
         postdata = {
             'Sitename': options.title,
             'EmergencyContact': options.email,
index fc4aa7e1d7c47722486d01dd9f9db9ff3856f3f0..fa2a8b9967cfe74b4a497dc65956222853e0471c 100644 (file)
@@ -1,16 +1,13 @@
 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]
diff --git a/wizard/command/install.py b/wizard/command/install.py
new file mode 100644 (file)
index 0000000..faf627b
--- /dev/null
@@ -0,0 +1,45 @@
+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"
index ff1ded28f34cc5d8e6f088140013efc1e436a9fc..6af91cdfe1a345ea2375221d837b377f42cbec89 100644 (file)
@@ -51,7 +51,7 @@ def main(argv, baton):
             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():
diff --git a/wizard/git.py b/wizard/git.py
new file mode 100644 (file)
index 0000000..aaea68d
--- /dev/null
@@ -0,0 +1,10 @@
+"""
+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()
+
index 8cefc53839ad16eef392c758bc468fc88381c637..f019216a39d75398ee62cbf5d0164366e78521a7 100644 (file)
@@ -323,7 +323,6 @@ class UnrecognizedPreloads(Error):
     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)
 
@@ -333,6 +332,5 @@ class MissingRequiredParam(Error):
     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)
index 1fa749a5ba4a52735e3f3a98a8e2900355948773..7082f0142012aa0f46193c1c4a9745ba9771d15e 100644 (file)
@@ -12,21 +12,17 @@ import sys
 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):
     """
@@ -38,13 +34,16 @@ 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()
@@ -64,6 +63,19 @@ class Shell(object):
             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
@@ -73,13 +85,13 @@ class Shell(object):
         # 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
@@ -110,9 +122,9 @@ class Shell(object):
         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):
@@ -258,8 +270,11 @@ class PythonCallError(CallError):
     #: 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"
 
index 3d5f342636b21368495b586cb327157804cfd4a3..2b244de5cbbd4c3194ecf68a463110accea46673 100644 (file)
@@ -13,7 +13,6 @@ import pwd
 import sys
 
 import wizard
-from wizard import shell
 
 class ChangeDirectory(object):
     """
@@ -91,10 +90,6 @@ def get_dir_owner(dir = "."):
     """
     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")