chiark / gitweb /
service.py: Incompatible changes to CommandRemoteService.
authorMark Wooding <mdw@distorted.org.uk>
Sat, 24 May 2014 13:00:03 +0000 (14:00 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 24 May 2014 21:59:15 +0000 (22:59 +0100)
Rather than having a constructor argument for each possible remote
command, we now have a more complex and flexible system.  Firstly,
there's a `default' command prefix, to which the remaining
remote-command arguments are appended.  Secondly, there's a dictionary
mapping command names to full command lists with placeholders (like the
old system).

service.py

index 9ba9fca..aa77388 100644 (file)
@@ -306,32 +306,39 @@ class CommandRemoteService (BasicRemoteService):
   """
   A remote service transported over a standard Unix command.
 
-  This is left rather generic.  We need to know some command lists SET and
-  CLEAR containing the relevant service names and arguments.  These are
-  simply executed, after simple placeholder substitution.
-
-  The SET command should read a password as its first line on stdin, and set
-  that as the user's new password.  The CLEAR command should simply prevent
-  the user from logging in with a password.  On success, the commands should
-  print a line `OK' to standard output, and on any kind of anticipated
-  failure, they should print `ERR' followed by an HTTP status code and a
-  message; in either case, the program should exit with status zero.  In
-  disastrous cases, it's acceptable to print an error message to stderr
-  and/or exit with a nonzero status.
-
-  The placeholders are as follows.
+  This is left rather generic.  Two strategies are available (and can be
+  combined using appropriate configuration).  A DEFAULT command list can be
+  specified, and will be invoked as `DEFAULT OP ARGS...', where OP ARGS form
+  a Chopwood remote command.  Additionally, an OPMAP dictionary can be
+  provided, mapping OP names (remote command names) to command lists
+  containing `%' placeholders, as follows:
 
   `%u'          the user's name
   `%%'          a single `%' character
+
+  On success, the commands should print a line `OK' to standard output, and
+  on any kind of anticipated failure, they should print `ERR' followed by an
+  HTTP status code and a message; in either case, the program should exit
+  with status zero.  In disastrous cases, it's acceptable to print an error
+  message to stderr and/or exit with a nonzero status.
+
+  Configuration hint: if you can only handle some subset of the available
+  commands, then your easy approach is to set commands for the operations you
+  can handle in the OPMAP, and set the DEFAULT to something like
+
+        ['echo', 'ERR 500', 'unsupported command:']
+
+  to reject other commands.
   """
 
   R_PAT = RX.compile('%(.)')
 
-  def __init__(me, set, clear, *args, **kw):
+  def __init__(me, default = ['ERR', '500', 'unimplemented command:'],
+               opmap = {}, *args, **kw):
     """Initialize the command remote service."""
     super(CommandRemoteService, me).__init__(*args, **kw)
-    me._set = set
-    me._clear = clear
+    me._default = default
+    me._opmap = opmap
 
   def _describe(me):
     """Description of the remote service."""
@@ -343,16 +350,35 @@ class CommandRemoteService (BasicRemoteService):
 
   def _mkcmd(me, cmd, map):
     """Construct the command to be executed, by substituting placeholders."""
+    if map is None: return cmd
     return [me.R_PAT.sub(lambda m: me._subst(m.group(1), map), arg)
             for arg in cmd]
 
+  def _dispatch(me, func, op, args, input = None):
+    """
+    Work out how to invoke a particular command.
+
+    Invoke FUNC, which works like `_run', with appropriate arguments.  The OP
+    is a remote command name; ARGS is a sequence of (C, ARG) pairs, where C
+    is a placeholder character and ARG is a string value; INPUT is the text
+    to provide to the command on standard input.
+    """
+    try:
+      cmd = me._opmap[op]
+    except KeyError:
+      cmd = me._default + [op] + [v for k, v in args]
+      map = None
+    else:
+      map = dict(args)
+    return func(cmd, input = input, state = map)
+
   def setpasswd(me, user, passwd):
     """Service protocol: set the USER's password to PASSWD."""
-    me._run_noout(me._set, passwd + '\n', state = dict(u = user))
+    me._dispatch(me._run_noout, 'set', [('u', user)], passwd + '\n')
 
   def clearpasswd(me, user):
     """Service protocol: clear the USER's password."""
-    me._run_noout(me._clear, state = dict(u = user))
+    me._dispatch(me._run_noout, 'clear', [('u', user)])
 
 CONF.export('CommandRemoteService')