]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blobdiff - src/allmydata/scripts/runner.py
CLI: put "[global-opts]" in all command synopses
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / scripts / runner.py
index 94acfdb92ccd297b4fd85aaf175272b7bdc7983a..085967079d7842102a2ca3151ab22af3455549a9 100644 (file)
-#! /usr/bin/env python
 
-import os, subprocess, sys, signal, time
+import os, sys
+from cStringIO import StringIO
+
 from twisted.python import usage
 
-from twisted.python.procutils import which
+from allmydata.scripts.common import get_default_nodedir
+from allmydata.scripts import debug, create_node, startstop_node, cli, keygen, stats_gatherer, admin
+from allmydata.util.encodingutil import quote_output, get_io_encoding
 
-def testtwistd(loc):
-    try:
-        return subprocess.call(["python", loc,], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    except:
-        return -1
-    
-twistd = None
-if not twistd:
-    for maybetwistd in which("twistd"):
-        ret = testtwistd(maybetwistd)
-        if ret == 0:
-            twistd = maybetwistd
-            break
-
-if not twistd:
-    for maybetwistd in which("twistd.py"):
-        ret = testtwistd(maybetwistd)
-        if ret == 0:
-            twistd = maybetwistd
-            break
-
-if not twistd:
-    maybetwistd = os.path.join(sys.prefix, 'Scripts', 'twistd')
-    ret = testtwistd(maybetwistd)
-    if ret == 0:
-        twistd = maybetwistd
-
-if not twistd:
-    maybetwistd = os.path.join(sys.prefix, 'Scripts', 'twistd.py')
-    ret = testtwistd(maybetwistd)
-    if ret == 0:
-        twistd = maybetwistd
-
-if not twistd:
-    print "Can't find twistd (it comes with Twisted).  Aborting."
-    sys.exit(1)
-    
-class StartOptions(usage.Options):
-    optParameters = [
-        ["basedir", "C", ".", "which directory to start the node in"],
-        ]
+def GROUP(s):
+    # Usage.parseOptions compares argv[1] against command[0], so it will
+    # effectively ignore any "subcommand" that starts with a newline. We use
+    # these to insert section headers into the --help output.
+    return [("\n" + s, None, None, None)]
 
-class StopOptions(usage.Options):
-    optParameters = [
-        ["basedir", "C", ".", "which directory to stop the node in"],
-        ]
 
-class RestartOptions(usage.Options):
-    optParameters = [
-        ["basedir", "C", ".", "which directory to restart the node in"],
-        ]
+_default_nodedir = get_default_nodedir()
 
-class CreateClientOptions(usage.Options):
-    optParameters = [
-        ["basedir", "C", None, "which directory to create the client in"],
-        ]
-    optFlags = [
-        ["quiet", "q", "operate silently"],
-        ]
+NODEDIR_HELP = ("Specify which Tahoe node directory should be used. The "
+                "directory should either contain a full Tahoe node, or a "
+                "file named node.url that points to some other Tahoe node. "
+                "It should also contain a file named '"
+                + os.path.join('private', 'aliases') +
+                "' which contains the mapping from alias name to root "
+                "dirnode URI.")
+if _default_nodedir:
+    NODEDIR_HELP += " [default for most commands: " + quote_output(_default_nodedir) + "]"
 
-    def parseArgs(self, *args):
-        if len(args) > 0:
-            self['basedir'] = args[0]
-        if len(args) > 1:
-            raise usage.UsageError("I wasn't expecting so many arguments")
-
-    def postOptions(self):
-        if self['basedir'] is None:
-            raise usage.UsageError("<basedir> parameter is required")
-        self['basedir'] = os.path.abspath(self['basedir'])
+class Options(usage.Options):
+    # unit tests can override these to point at StringIO instances
+    stdin = sys.stdin
+    stdout = sys.stdout
+    stderr = sys.stderr
+
+    synopsis = "\nUsage:  tahoe <command> [command options]"
+    subCommands = ( GROUP("Administration")
+                    +   create_node.subCommands
+                    +   keygen.subCommands
+                    +   stats_gatherer.subCommands
+                    +   admin.subCommands
+                    + GROUP("Controlling a node")
+                    +   startstop_node.subCommands
+                    + GROUP("Debugging")
+                    +   debug.subCommands
+                    + GROUP("Using the filesystem")
+                    +   cli.subCommands
+                    )
 
-class CreateIntroducerOptions(usage.Options):
-    optParameters = [
-        ["basedir", "C", None, "which directory to create the introducer in"],
-        ]
     optFlags = [
-        ["quiet", "q", "operate silently"],
-        ]
-
-    def parseArgs(self, *args):
-        if len(args) > 0:
-            self['basedir'] = args[0]
-        if len(args) > 1:
-            raise usage.UsageError("I wasn't expecting so many arguments")
-
-    def postOptions(self):
-        if self['basedir'] is None:
-            raise usage.UsageError("<basedir> parameter is required")
-        self['basedir'] = os.path.abspath(self['basedir'])
+        ["quiet", "q", "Operate silently."],
+        ["version", "V", "Display version numbers."],
+        ["version-and-path", None, "Display version numbers and paths to their locations."],
+    ]
+    optParameters = [
+        ["node-directory", "d", None, NODEDIR_HELP],
+    ]
 
-client_tac = """
-# -*- python -*-
+    def opt_version(self):
+        import allmydata
+        print >>self.stdout, allmydata.get_package_versions_string(debug=True)
+        self.no_command_needed = True
 
-from allmydata import client
-from twisted.application import service
+    def opt_version_and_path(self):
+        import allmydata
+        print >>self.stdout, allmydata.get_package_versions_string(show_paths=True, debug=True)
+        self.no_command_needed = True
 
-c = client.Client()
+    def getSynopsis(self):
+        return "\nUsage: tahoe [global-opts] <command> [command-options]"
 
-application = service.Application("allmydata_client")
-c.setServiceParent(application)
-"""
+    def getUsage(self, **kwargs):
+        t = usage.Options.getUsage(self, **kwargs)
+        return t + "\nPlease run 'tahoe <command> --help' for more details on each command.\n"
 
-introducer_tac = """
-# -*- python -*-
+    def postOptions(self):
+        if not hasattr(self, 'subOptions'):
+            if not hasattr(self, 'no_command_needed'):
+                raise usage.UsageError("must specify a command")
+            sys.exit(0)
 
-from allmydata import introducer_and_vdrive
-from twisted.application import service
 
-c = introducer_and_vdrive.IntroducerAndVdrive()
+create_dispatch = {}
+for module in (create_node, keygen, stats_gatherer):
+    create_dispatch.update(module.dispatch)
 
-application = service.Application("allmydata_introducer")
-c.setServiceParent(application)
-"""
+def runner(argv,
+           run_by_human=True,
+           stdin=None, stdout=None, stderr=None,
+           install_node_control=True, additional_commands=None):
 
-class Options(usage.Options):
-    synopsis = "Usage:  allmydata <command> [command options]"
+    stdin  = stdin  or sys.stdin
+    stdout = stdout or sys.stdout
+    stderr = stderr or sys.stderr
 
-    subCommands = [
-        ["create-client", None, CreateClientOptions, "Create a client node."],
-        ["create-introducer", None, CreateIntroducerOptions, "Create a introducer node."],
-        ["start", None, StartOptions, "Start a node (of any type)."],
-        ["stop", None, StopOptions, "Stop a node."],
-        ["restart", None, RestartOptions, "Restart a node."],
-        ]
+    config = Options()
+    if install_node_control:
+        config.subCommands.extend(startstop_node.subCommands)
 
-    def postOptions(self):
-        if not hasattr(self, 'subOptions'):
-            raise usage.UsageError("must specify a command")
+    ac_dispatch = {}
+    if additional_commands:
+        for ac in additional_commands:
+            config.subCommands.extend(ac.subCommands)
+            ac_dispatch.update(ac.dispatch)
 
-def runner(argv, run_by_human=True):
-    config = Options()
     try:
         config.parseOptions(argv)
     except usage.error, e:
         if not run_by_human:
             raise
-        print "%s:  %s" % (sys.argv[0], e)
-        print
-        c = getattr(config, 'subOptions', config)
-        print str(c)
+        c = config
+        while hasattr(c, 'subOptions'):
+            c = c.subOptions
+        print >>stdout, str(c)
+        try:
+            msg = e.args[0].decode(get_io_encoding())
+        except Exception:
+            msg = repr(e)
+        print >>stdout, "%s:  %s\n" % (sys.argv[0], quote_output(msg, quotemarks=False))
         return 1
 
     command = config.subCommand
     so = config.subOptions
 
-    if command == "create-client":
-        rc = create_client(so)
-    elif command == "create-introducer":
-        rc = create_introducer(so)
-    elif command == "start":
-        rc = start(so)
-    elif command == "stop":
-        rc = stop(so)
-    elif command == "restart":
-        rc = restart(so)
-    rc = rc or 0
+    if config['quiet']:
+        stdout = StringIO()
+
+    so.stdout = stdout
+    so.stderr = stderr
+    so.stdin = stdin
+
+    if command in create_dispatch:
+        rc = create_dispatch[command](so, stdout, stderr)
+    elif command in startstop_node.dispatch:
+        rc = startstop_node.dispatch[command](so, stdout, stderr)
+    elif command in debug.dispatch:
+        rc = debug.dispatch[command](so)
+    elif command in admin.dispatch:
+        rc = admin.dispatch[command](so)
+    elif command in cli.dispatch:
+        rc = cli.dispatch[command](so)
+    elif command in ac_dispatch:
+        rc = ac_dispatch[command](so, stdout, stderr)
+    else:
+        raise usage.UsageError()
+
     return rc
 
-def run():
-    rc = runner(sys.argv[1:])
-    sys.exit(rc)
 
-def create_client(config):
-    basedir = config['basedir']
-    if os.path.exists(basedir):
-        if os.listdir(basedir):
-            print "The base directory already exists: %s" % basedir
-            print "To avoid clobbering anything, I am going to quit now"
-            print "Please use a different directory, or delete this one"
-            return -1
-        # we're willing to use an empty directory
-    else:
-        os.mkdir(basedir)
-    f = open(os.path.join(basedir, "client.tac"), "w")
-    f.write(client_tac)
-    f.close()
-    if not config['quiet']:
-        print "client created in %s" % basedir
-        print " please copy introducer.furl and vdrive.furl into the directory"
-
-def create_introducer(config):
-    basedir = config['basedir']
-    if os.path.exists(basedir):
-        if os.listdir(basedir):
-            print "The base directory already exists: %s" % basedir
-            print "To avoid clobbering anything, I am going to quit now"
-            print "Please use a different directory, or delete this one"
-            return -1
-        # we're willing to use an empty directory
-    else:
-        os.mkdir(basedir)
-    f = open(os.path.join(basedir, "introducer.tac"), "w")
-    f.write(introducer_tac)
-    f.close()
-    if not config['quiet']:
-        print "introducer created in %s" % basedir
-
-def start(config):
-    basedir = config['basedir']
-    if os.path.exists(os.path.join(basedir, "client.tac")):
-        tac = "client.tac"
-        type = "client"
-    elif os.path.exists(os.path.join(basedir, "introducer.tac")):
-        tac = "introducer.tac"
-        type = "introducer"
-    else:
-        print "%s does not look like a node directory" % basedir
-        sys.exit(1)
-    os.chdir(basedir)
-    rc = subprocess.call(["python", twistd, "-y", tac,])
-    if rc == 0:
-        print "%s node probably started" % type
-        return 0
-    else:
-        print "%s node probably not started" % type
-        return 1
+def run(install_node_control=True):
+    try:
+        if sys.platform == "win32":
+            from allmydata.windows.fixups import initialize
+            initialize()
 
-def stop(config):
-    basedir = config['basedir']
-    pidfile = os.path.join(basedir, "twistd.pid")
-    if not os.path.exists(pidfile):
-        print "%s does not look like a running node directory (no twistd.pid)" % basedir
-        return 1
-    pid = open(pidfile, "r").read()
-    pid = int(pid)
-
-    timer = 0
-    os.kill(pid, signal.SIGTERM)
-    time.sleep(0.1)
-    while timer < 5:
-        # poll once per second until twistd.pid goes away, up to 5 seconds
-        try:
-            os.kill(pid, 0)
-        except OSError:
-            print "process %d is dead" % pid
-            return
-        timer += 1
-        time.sleep(1)
-    print "never saw process go away"
-    return 1
-
-def restart(config):
-    rc = stop(config)
-    if rc:
-        print "not restarting"
-        return rc
-    return start(config)
+        rc = runner(sys.argv[1:], install_node_control=install_node_control)
+    except Exception:
+        import traceback
+        traceback.print_exc()
+        rc = 1
+
+    sys.exit(rc)