chiark / gitweb /
service.py: Fix the RemoteService `_mkcmd' protocol.
[chopwood] / service.py
index 0944e2c..9ba9fca 100644 (file)
@@ -78,8 +78,9 @@ class BasicService (object):
   A simple base class for services.
   """
 
-  def __init__(me, friendly, *args, **kw):
+  def __init__(me, friendly, name = None, *args, **kw):
     super(BasicService, me).__init__(*args)
+    me.name = name
     me.friendly = friendly
     me.meta = kw
 
@@ -182,17 +183,17 @@ class BasicRemoteService (BasicService):
   configuration.
   """
 
-  def _run(me, cmd, input = None):
+  def _run(me, cmd, input = None, state = None):
     """
     This is the core of the remote service machinery.  It issues a command
     and parses the response.  It will generate strings of informational
     output from the command; error responses cause appropriate exceptions to
     be raised.
 
-    The command is determined by passing the CMD argument to the `_mkcmd'
-    method, which a subclass must implement; it should return a list of
-    command-line arguments suitable for `subprocess.Popen'.  The INPUT is a
-    string to make available on the command's stdin; if None, then no input
+    The command is determined by passing the CMD and STATE arguments to the
+    `_mkcmd' method, which a subclass must implement; it should return a list
+    of command-line arguments suitable for `subprocess.Popen'.  The INPUT is
+    string to make available on the command's stdin; if None, then no input
     is provided to the command.  The `_describe' method must provide a
     description of the remote service for use in timeout messages.
 
@@ -206,7 +207,7 @@ class BasicRemoteService (BasicService):
 
     ## Run the command and collect its output and status.
     with U.timeout(30, "waiting for remote service %s" % me._describe()):
-      proc = SUB.Popen(me._mkcmd(cmd),
+      proc = SUB.Popen(me._mkcmd(cmd, state),
                        stdin = input is not None and SUB.PIPE or None,
                        stdout = SUB.PIPE, stderr = SUB.PIPE)
       out, err = proc.communicate(input)
@@ -247,9 +248,9 @@ class BasicRemoteService (BasicService):
     if not win:
       raise U.ExpectedError, (500, 'No reply from remote service')
 
-  def _run_noout(me, cmd, input = None):
+  def _run_noout(me, cmd, input = None, state = None):
     """Like `_run', but expect no output."""
-    for _ in me._run(cmd, input):
+    for _ in me._run(cmd, input, state):
       raise U.ExpectedError, (500, 'Unexpected output from remote service')
 
 class SSHRemoteService (BasicRemoteService):
@@ -280,25 +281,24 @@ class SSHRemoteService (BasicRemoteService):
     (probably of the form `LOGIN@HOSTNAME') and referring to the service
     NAME.
     """
-    super(SSHRemoteService, me).__init__(*args, **kw)
+    super(SSHRemoteService, me).__init__(name = name, *args, **kw)
     me._remote = remote
-    me._name = name
 
   def _describe(me):
     """Description of the remote service."""
-    return "`%s' via SSH to `%s'" % (me._name, me._remote),
+    return "`%s' via SSH to `%s'" % (me.name, me._remote),
 
-  def _mkcmd(me, cmd):
+  def _mkcmd(me, cmd, state):
     """Format a command for SSH.  Mainly escaping arguments."""
     return ['ssh', me._remote, ' '.join(map(CGI.urlencode, cmd))]
 
   def setpasswd(me, user, passwd):
     """Service protocol: set the USER's password to PASSWD."""
-    me._run_noout(['set', me._name, user], passwd + '\n')
+    me._run_noout(['set', me.name, user], passwd + '\n')
 
   def clearpasswd(me, user):
     """Service protocol: clear the USER's password."""
-    me._run_noout(['clear', me._name, user])
+    me._run_noout(['clear', me.name, user])
 
 CONF.export('SSHRemoteService')
 
@@ -328,29 +328,31 @@ class CommandRemoteService (BasicRemoteService):
   R_PAT = RX.compile('%(.)')
 
   def __init__(me, set, clear, *args, **kw):
-    """
-    Initialize the command remote service.
-    """
+    """Initialize the command remote service."""
     super(CommandRemoteService, me).__init__(*args, **kw)
     me._set = set
     me._clear = clear
-    me._map = dict(u = user)
 
-  def _subst(me, c):
+  def _describe(me):
+    """Description of the remote service."""
+    return "`%s' command service (%s)" % (me.name, ' '.join(me._default))
+
+  def _subst(me, c, map):
     """Return the substitution for the placeholder `%C'."""
-    return me._map.get(c, c)
+    return map.get(c, c)
 
-  def _mkcmd(me, cmd):
+  def _mkcmd(me, cmd, map):
     """Construct the command to be executed, by substituting placeholders."""
-    return [me.R_PAT.sub(lambda m: me._subst(m.group(1))) for arg in cmd]
+    return [me.R_PAT.sub(lambda m: me._subst(m.group(1), map), arg)
+            for arg in cmd]
 
   def setpasswd(me, user, passwd):
     """Service protocol: set the USER's password to PASSWD."""
-    me._run_noout(me._set, passwd + '\n')
+    me._run_noout(me._set, passwd + '\n', state = dict(u = user))
 
   def clearpasswd(me, user):
     """Service protocol: clear the USER's password."""
-    me._run_noout(me._clear)
+    me._run_noout(me._clear, state = dict(u = user))
 
 CONF.export('CommandRemoteService')
 
@@ -379,5 +381,7 @@ def add_master_service():
                                    'users', 'user', 'passwd'),
                  CFG.HASH,
                  friendly = 'Password changing service')
+  for name, svc in SERVICES.iteritems():
+    if svc.name is None: svc.name = name
 
 ###----- That's all, folks --------------------------------------------------