Code

More Grande Splite stuff
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Sun, 22 Jul 2001 11:15:45 +0000 (11:15 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Sun, 22 Jul 2001 11:15:45 +0000 (11:15 +0000)
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@27 57a73879-2fb5-44c3-a270-3262357dd7e2

bin/roundup [new file with mode: 0755]
bin/roundup-mailgw [new file with mode: 0755]
bin/roundup-server [new file with mode: 0755]

diff --git a/bin/roundup b/bin/roundup
new file mode 100755 (executable)
index 0000000..0ca6554
--- /dev/null
@@ -0,0 +1,271 @@
+#! /usr/bin/python
+
+# $Id: roundup,v 1.1 2001-07-22 11:15:45 richard Exp $
+
+import sys
+if int(sys.version[0]) < 2:
+    print 'Roundup requires python 2.0 or later.'
+    sys.exit(1)
+
+import string, os, getpass
+from roundup import date, roundupdb, init
+
+def determineLogin(instance, argv, n = 2):
+    name = password = ''
+    if argv[2] == '-u':
+        l = argv[3].split(':')
+        name = l[0]
+        if len(l) > 1:
+            password = l[1]
+        n = 4
+    elif os.environ.has_key('ROUNDUP_LOGIN'):
+        l = os.environ['ROUNDUP_LOGIN'].split(':')
+        name = l[0]
+        if len(l) > 1:
+            password = l[1]
+    while not name:
+        name = raw_input('Login name: ')
+    while not password:
+        password = getpass.getpass('  password: ')
+    # TODO use the password...
+    return n, instance.open(name)
+
+def usage(message=''):
+    if message: message = 'Problem: '+message+'\n'
+    print '''%sUsage:
+
+ roundup [-i instance] init template
+   -- initialise the database
+ roundup [-i instance] spec classname
+   -- show the properties for a classname
+ roundup [-i instance] create [-u login] classname propname=value ...
+   -- create a new entry of a given class
+ roundup [-i instance] list [-c] classname
+   -- list the instances of a class
+ roundup [-i instance] history [-c] designator
+   -- show the history entries of a designator
+ roundup [-i instance] get [-c] designator[,designator,...] propname
+   -- get the given property of one or more designator(s)
+ roundup [-i instance] set [-u login] designator[,designator,...] propname=value ...
+   -- set the given property of one or more designator(s)
+ roundup [-i instance] find [-c] classname propname=value ...
+   -- find the class instances with a given property
+ roundup [-i instance] retire designator[,designator,...]
+   -- "retire" a designator
+ roundup help    
+   -- this help
+ roundup morehelp
+   -- even more detailed help
+'''%message
+
+def moreusage(message=''):
+    usage(message)
+    print '''
+All commands (except help) require an instance specifier. This is just the path
+to the roundup instance you're working with. It may be specified in the environment
+variable ROUNDUP_INSTANCE or on the command line as "-i instance".
+
+A designator is a classname and a nodeid concatenated, eg. bug1, user10, ...
+
+Property values are represented as strings in command arguments and in the
+printed results:
+ . Strings are, well, strings.
+ . Date values are printed in the full date format in the local time zone, and
+   accepted in the full format or any of the partial formats explained below.
+ . Link values are printed as node designators. When given as an argument,
+   node designators and key strings are both accepted.
+ . Multilink values are printed as lists of node designators joined by commas.
+   When given as an argument, node designators and key strings are both
+   accepted; an empty string, a single node, or a list of nodes joined by
+   commas is accepted.
+
+When multiple nodes are specified to the roundup get or roundup set
+commands, the specified properties are retrieved or set on all the listed
+nodes. 
+
+When multiple results are returned by the roundup get or roundup find
+commands, they are printed one per line (default) or joined by commas (with
+the -c) option. 
+
+Where the command changes data, a login name/password is required. The
+login may be specified as either "name" or "name:password".
+ . ROUNDUP_LOGIN environment variable
+ . the -u command-line option
+If either the name or password is not supplied, they are obtained from the
+command-line. 
+
+Date format examples:
+  "2000-04-17.03:45" means <Date 2000-04-17.08:45:00>
+  "2000-04-17" means <Date 2000-04-17.00:00:00>
+  "01-25" means <Date yyyy-01-25.00:00:00>
+  "08-13.22:13" means <Date yyyy-08-14.03:13:00>
+  "11-07.09:32:43" means <Date yyyy-11-07.14:32:43>
+  "14:25" means <Date yyyy-mm-dd.19:25:00>
+  "8:47:11" means <Date yyyy-mm-dd.13:47:11>
+  "." means "right now"
+'''
+
+def main():
+    argv = sys.argv
+
+    if len(argv) == 1:
+        usage('No command specified')
+        return 1
+
+    # handle help now
+    if argv[1] == 'help':
+        usage()
+        return 0
+    if argv[1] == 'morehelp':
+        moreusage()
+        return 0
+
+    # figure the instance home
+    n = 1
+    if argv[1] == '-i':
+        if len(argv) == 2:
+            usage()
+            return 1
+        instance_home = argv[2]
+        n = 3
+    else:
+        instance_home = os.environ.get('ROUNDUP_INSTANCE', '')
+    if not instance_home:
+        usage('No instance home specified')
+        return 1
+
+    # now figure the command
+    command = argv[n]
+    n = n + 1
+
+    if command == 'init':
+        adminpw = ''
+        confirm = 'x'
+        while adminpw != confirm:
+            adminpw = getpass.getpass('Admin Password:')
+            confirm = getpass.getpass('       Confirm:')
+        init.init(instance_home, argv[n],  adminpw)
+        return 0
+
+    # get the instance
+    path, instance = os.path.split(instance_home)
+    sys.path.insert(0, path)
+    try:
+        instance = __import__(instance)
+    finally:
+        del sys.path[0]
+
+    if command == 'get':
+        db = instance.open()
+        designators = string.split(argv[n], ',')
+        propname = argv[n+1]
+        # TODO: handle the -c option
+        for designator in designators:
+            classname, nodeid = roundupdb.splitDesignator(designator)
+            print db.getclass(classname).get(nodeid, propname)
+
+    elif command == 'set':
+        n, db = determineLogin(instance, argv, n)
+        designators = string.split(argv[n], ',')
+        props = {}
+        for prop in argv[n+1:]:
+            key, value = prop.split('=')
+            props[key] = value
+        for designator in designators:
+            classname, nodeid = roundupdb.splitDesignator(designator)
+            cl = db.getclass(classname)
+            properties = cl.getprops()
+            for key, value in props.items():
+                type =  properties[key]
+                if type.isStringType:
+                    continue
+                elif type.isDateType:
+                    props[key] = date.Date(value)
+                elif type.isIntervalType:
+                    props[key] = date.Interval(value)
+                elif type.isLinkType:
+                    props[key] = value
+                elif type.isMultilinkType:
+                    props[key] = value.split(',')
+            apply(cl.set, (nodeid, ), props)
+
+    elif command == 'find':
+        db = instance.open()
+        classname = argv[n]
+        cl = db.getclass(classname)
+
+        # look up the linked-to class and get the nodeid that has the value
+        propname, value = argv[n+1:].split('=')
+        propcl = cl[propname].classname
+        nodeid = propcl.lookup(value)
+
+        # now do the find
+        # TODO: handle the -c option
+        print cl.find(propname, nodeid)
+
+    elif command == 'spec':
+        db = instance.open()
+        classname = argv[n]
+        cl = db.getclass(classname)
+        for key, value in cl.properties.items():
+            print '%s: %s'%(key, value)
+
+    elif command == 'create':
+        n, db = determineLogin(instance, argv, n)
+        classname = argv[n]
+        cl = db.getclass(classname)
+        props = {}
+        properties = cl.getprops()
+        for prop in argv[n+1:]:
+            key, value = prop.split('=')
+            type =  properties[key]
+            if type.isStringType:
+                props[key] = value 
+            elif type.isDateType:
+                props[key] = date.Date(value)
+            elif type.isIntervalType:
+                props[key] = date.Interval(value)
+            elif type.isLinkType:
+                props[key] = value
+            elif type.isMultilinkType:
+                props[key] = value.split(',')
+        print apply(cl.create, (), props)
+
+    elif command == 'list':
+        db = instance.open()
+        classname = argv[n]
+        cl = db.getclass(classname)
+        key = cl.getkey() or cl.properties.keys()[0]
+        # TODO: handle the -c option
+        for nodeid in cl.list():
+            value = cl.get(nodeid, key)
+            print "%4s: %s"%(nodeid, value)
+
+    elif command == 'history':
+        db = instance.open()
+        classname, nodeid = roundupdb.splitDesignator(argv[n])
+        # TODO: handle the -c option
+        print db.getclass(classname).history(nodeid)
+
+    elif command == 'retire':
+        n, db = determineLogin(instance, argv, n)
+        designators = string.split(argv[n], ',')
+        for designator in designators:
+            classname, nodeid = roundupdb.splitDesignator(designator)
+            db.getclass(classname).retire(nodeid)
+
+    else:
+        print "Unknown command '%s'"%command
+        usage()
+        return 1
+
+    db.close()
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(main())
+
+#
+# $Log: not supported by cvs2svn $
+#
+
diff --git a/bin/roundup-mailgw b/bin/roundup-mailgw
new file mode 100755 (executable)
index 0000000..4add812
--- /dev/null
@@ -0,0 +1,34 @@
+#! /usr/bin/python
+
+# $ID: $
+
+import sys
+if int(sys.version[0]) < 2:
+    print "Roundup requires Python 2.0 or newer."
+    sys.exit(1)
+
+# figure the instance home
+import os
+if len(sys.argv) > 1:
+    instance_home = sys.argv[1]
+else:
+    instance_home = os.environ.get('ROUNDUP_INSTANCE', '')
+if not instance_home:
+    print 'No instance home specified'
+    sys.exit(1)
+
+# get the instance
+path, instance = os.path.split(instance_home)
+sys.path.insert(0, path)
+instance = __import__(instance)
+sys.path[0]
+
+# invokde the mail handler
+db = instance.open('admin')
+handler = instance.MailGW(db)
+handler.main(sys.stdin)
+
+#
+# $Log: not supported by cvs2svn $
+#
+
diff --git a/bin/roundup-server b/bin/roundup-server
new file mode 100755 (executable)
index 0000000..c1e12c1
--- /dev/null
@@ -0,0 +1,223 @@
+#!/usr/bin/python
+""" HTTP Server that serves roundup.
+
+Stolen from CGIHTTPServer
+
+$Id: roundup-server,v 1.1 2001-07-22 11:15:45 richard Exp $
+
+"""
+import sys
+if int(sys.version[0]) < 2:
+    print "Content-Type: text/plain\n"
+    print "Roundup requires Python 2.0 or newer."
+
+__version__ = "0.1"
+
+__all__ = ["RoundupRequestHandler"]
+
+import os, urllib, StringIO, traceback, cgi, binascii, string
+import BaseHTTPServer
+import SimpleHTTPServer
+
+# Roundup modules of use here
+from roundup import cgitb, cgi_client
+
+# These are here temporarily until I get a real reload system in place
+from roundup import date, hyperdb, hyper_bsddb, roundupdb, htmltemplate
+
+#
+##  Configuration
+#
+
+# This indicates where the Roundup instance lives
+ROUNDUPS = {
+    'test': '/tmp/roundup_test',
+}
+
+# Where to log debugging information to. Use an instance of DevNull if you
+# don't want to log anywhere.
+# TODO: actually use this stuff
+#class DevNull:
+#    def write(self, info):
+#        pass
+#LOG = open('/var/log/roundup.cgi.log', 'a')
+#LOG = DevNull()
+
+#
+##  end configuration
+#
+
+
+class RoundupRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+    def send_head(self):
+        """Version of send_head that support CGI scripts"""
+        # TODO: actually do the HEAD ...
+        return self.run_cgi()
+
+    def run_cgi(self):
+        """ Execute the CGI command. Wrap an innner call in an error
+            handler so all errors can be caught.
+        """
+        save_stdin = sys.stdin
+        sys.stdin = self.rfile
+        try:
+            self.inner_run_cgi()
+        except cgi_client.Unauthorised:
+            self.wfile.write('Content-Type: text/html\n')
+            self.wfile.write('Status: 403\n')
+            self.wfile.write('Unauthorised')
+        except:
+            try:
+                reload(cgitb)
+                self.wfile.write("Content-Type: text/html\n\n")
+                self.wfile.write(cgitb.breaker())
+                self.wfile.write(cgitb.html())
+            except:
+                self.wfile.write("Content-Type: text/html\n\n")
+                self.wfile.write("<pre>")
+                s = StringIO.StringIO()
+                traceback.print_exc(None, s)
+                self.wfile.write(cgi.escape(s.getvalue()))
+                self.wfile.write("</pre>\n")
+        sys.stdin = save_stdin
+
+    def inner_run_cgi(self):
+        ''' This is the inner part of the CGI handling
+        '''
+
+        rest = self.path
+        i = rest.rfind('?')
+        if i >= 0:
+            rest, query = rest[:i], rest[i+1:]
+        else:
+            query = ''
+
+        # figure the instance
+        if rest == '/':
+            raise ValueError, 'No instance specified'
+        l_path = string.split(rest, '/')
+        instance = urllib.unquote(l_path[1])
+        if ROUNDUPS.has_key(instance):
+            instance_home = ROUNDUPS[instance]
+            module_path, instance = os.path.split(instance_home)
+            sys.path.insert(0, module_path)
+            try:
+                instance = __import__(instance)
+            finally:
+                del sys.path[0]
+        else:
+            raise ValueError, 'No such instance "%s"'%instance
+
+        # figure out what the rest of the path is
+        if len(l_path) > 2:
+            rest = '/'.join(l_path[2:])
+        else:
+            rest = '/'
+
+        # Set up the CGI environment
+        env = {}
+        env['REQUEST_METHOD'] = self.command
+        env['PATH_INFO'] = urllib.unquote(rest)
+        if query:
+            env['QUERY_STRING'] = query
+        host = self.address_string()
+        if self.headers.typeheader is None:
+            env['CONTENT_TYPE'] = self.headers.type
+        else:
+            env['CONTENT_TYPE'] = self.headers.typeheader
+        length = self.headers.getheader('content-length')
+        if length:
+            env['CONTENT_LENGTH'] = length
+        co = filter(None, self.headers.getheaders('cookie'))
+        if co:
+            env['HTTP_COOKIE'] = ', '.join(co)
+        env['SCRIPT_NAME'] = ''
+        env['SERVER_NAME'] = self.server.server_name
+        env['SERVER_PORT'] = str(self.server.server_port)
+
+        decoded_query = query.replace('+', ' ')
+
+        # if root, setuid to nobody
+        # TODO why isn't this done much earlier? - say, in main()?
+        if not os.getuid():
+            nobody = nobody_uid()
+            os.setuid(nobody)
+
+        # reload all modules
+        # TODO check for file timestamp changes and dependencies
+        reload(date)
+        reload(hyperdb)
+        reload(roundupdb)
+        reload(htmltemplate)
+        reload(cgi_client)
+        sys.path.insert(0, module_path)
+        try:
+            reload(instance)
+        finally:
+            del sys.path[0]
+
+        # initialise the roundupdb, check for auth
+        db = instance.open('admin')
+        message = 'Unauthorised'
+        auth = self.headers.getheader('authorization')
+        if auth:
+            l = binascii.a2b_base64(auth.split(' ')[1]).split(':')
+            user = l[0]
+            password = None
+            if len(l) > 1:
+                password = l[1]
+            try:
+                uid = db.user.lookup(user)
+            except KeyError:
+                auth = None
+                message = 'Username not recognised'
+            else:
+                if password != db.user.get(uid, 'password'):
+                    message = 'Incorrect password'
+                    auth = None
+        db.close()
+        del db
+        if not auth:
+            self.send_response(401)
+            self.send_header('Content-Type', 'text/html')
+            self.send_header('WWW-Authenticate', 'basic realm="Roundup"')
+            self.end_headers()
+            self.wfile.write(message)
+            return
+
+        self.send_response(200, "Script output follows")
+
+        # do the roundup thang
+        db = instance.open(user)
+        client = instance.Client(self.wfile, db, env, user)
+        client.main()
+    do_POST = run_cgi
+
+nobody = None
+
+def nobody_uid():
+    """Internal routine to get nobody's uid"""
+    global nobody
+    if nobody:
+        return nobody
+    try:
+        import pwd
+    except ImportError:
+        return -1
+    try:
+        nobody = pwd.getpwnam('nobody')[2]
+    except KeyError:
+        nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
+    return nobody
+
+if __name__ == '__main__':
+    # TODO make this configurable again? command-line seems ok to me...
+    address = ('', 8080)
+    httpd = BaseHTTPServer.HTTPServer(address, RoundupRequestHandler)
+    print 'Roundup server started on', address
+    httpd.serve_forever()
+
+#
+# $Log: not supported by cvs2svn $
+#
+