aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorW. Trevor King <wking@drexel.edu>2011-04-16 17:08:35 -0400
committerW. Trevor King <wking@drexel.edu>2011-04-16 17:08:35 -0400
commit36699d8265073403f17afb4294b4dba07f52e88b (patch)
tree783a12a77afa19f25e37e23eb50f1fa8e270f83c
parent7cb2e5dceb0f459b16f16044e4ae2f5de3c2675e (diff)
downloadbugseverywhere-36699d8265073403f17afb4294b4dba07f52e88b.tar.gz
Add --notify to `be serve`.
-rw-r--r--NEWS1
-rw-r--r--libbe/command/serve.py54
-rw-r--r--libbe/storage/http.py1
-rw-r--r--libbe/util/subproc.py25
4 files changed, 72 insertions, 9 deletions
diff --git a/NEWS b/NEWS
index a93d47e..986e887 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,7 @@
April 16, 2011
* Added --preserve-uuids to `be import-xml`.
* Added --assigned, --severity, and --status to `be new`.
+ * Added --notify to `be serve`.
March 5, 2011
* Release version 1.0.1 (bugfixes).
diff --git a/libbe/command/serve.py b/libbe/command/serve.py
index ba4b0d8..b311139 100644
--- a/libbe/command/serve.py
+++ b/libbe/command/serve.py
@@ -58,6 +58,7 @@ import libbe
import libbe.command
import libbe.command.util
import libbe.util.encoding
+import libbe.util.subproc
import libbe.version
if libbe.TESTING == True:
@@ -507,9 +508,10 @@ class ServerApp (WSGI_AppObject):
"""
server_version = "BE-server/" + libbe.version.version()
- def __init__(self, storage, *args, **kwargs):
- WSGI_AppObject.__init__(self, *args, **kwargs)
+ def __init__(self, storage, notify=False, **kwargs):
+ WSGI_AppObject.__init__(self, **kwargs)
self.storage = storage
+ self.notify = notify
self.http_user_error = 418
self.urls = [
@@ -570,6 +572,9 @@ class ServerApp (WSGI_AppObject):
directory = self.data_get_boolean(
data, 'directory', default=False, source=source)
self.storage.add(id, parent=parent, directory=directory)
+ if self.notify:
+ self._notify(environ, 'add', id,
+ [('parent', parent), ('directory', directory)])
return self.ok_response(environ, start_response, None)
def exists(self, environ, start_response):
@@ -593,6 +598,8 @@ class ServerApp (WSGI_AppObject):
self.storage.recursive_remove(id)
else:
self.storage.remove(id)
+ if self.notify:
+ self._notify(environ, 'remove', id, [('recursive', recursive)])
return self.ok_response(environ, start_response, None)
def ancestors(self, environ, start_response):
@@ -641,6 +648,8 @@ class ServerApp (WSGI_AppObject):
raise _HandlerError(406, 'Missing query key value')
value = data['value']
self.storage.set(id, value)
+ if self.notify:
+ self._notify(environ, 'set', id, [('value', value)])
return self.ok_response(environ, start_response, None)
def commit(self, environ, start_response):
@@ -661,6 +670,10 @@ class ServerApp (WSGI_AppObject):
revision = self.storage.commit(summary, body, allow_empty)
except libbe.storage.EmptyCommit, e:
raise _HandlerError(self.http_user_error, 'EmptyCommit')
+ if self.notify:
+ self._notify(environ, 'commit', id,
+ [('allow_empty', allow_empty), ('summary', summary),
+ ('body', body)])
return self.ok_response(environ, start_response, revision)
def revision_id(self, environ, start_response):
@@ -700,6 +713,35 @@ class ServerApp (WSGI_AppObject):
raise _Unauthorized() # only non-guests allowed to write
# allow read-only commands for all users
+ def _notify(self, environ, command, id, params):
+ message = self._format_notification(environ, command, id, params)
+ self._submit_notification(message)
+
+ def _format_notification(self, environ, command, id, params):
+ key_length = len('command')
+ for key,value in params:
+ if len(key) > key_length and '\n' not in str(value):
+ key_length = len(key)
+ key_length += 1
+ lines = []
+ multi_line_params = []
+ for key,value in [('address', environ.get('REMOTE_ADDR', '-')),
+ ('command', command), ('id', id)]+params:
+ v = str(value)
+ if '\n' in v:
+ multi_line_params.append((key,v))
+ continue
+ lines.append('%*.*s %s' % (key_length, key_length, key+':', v))
+ lines.append('')
+ for key,value in multi_line_params:
+ lines.extend(['=== START %s ===' % key, v,
+ '=== STOP %s ===' % key, ''])
+ lines.append('')
+ return '\n'.join(lines)
+
+ def _submit_notification(self, message):
+ libbe.util.subproc.invoke(self.notify, stdin=message, shell=True)
+
class Serve (libbe.command.Command):
""":class:`~libbe.command.base.Command` wrapper around
@@ -721,6 +763,10 @@ class Serve (libbe.command.Command):
name='host', metavar='HOST', default='')),
libbe.command.Option(name='read-only', short_name='r',
help='Dissable operations that require writing'),
+ libbe.command.Option(name='notify', short_name='n',
+ help='Send notification emails for changes.',
+ arg=libbe.command.Argument(
+ name='notify', metavar='EMAIL-COMMAND', default=None)),
libbe.command.Option(name='ssl', short_name='s',
help='Use CherryPy to serve HTTPS (HTTP over SSL/TLS)'),
libbe.command.Option(name='auth', short_name='a',
@@ -742,7 +788,8 @@ class Serve (libbe.command.Command):
self._check_restricted_access(storage, params['auth'])
users = Users(params['auth'])
users.load()
- app = ServerApp(storage=storage, logger=self.logger)
+ app = ServerApp(
+ storage=storage, notify=params['notify'], logger=self.logger)
if params['auth'] != None:
app = AdminApp(app, users=users, logger=self.logger)
app = AuthenticationApp(app, realm=storage.repo,
@@ -860,6 +907,7 @@ if libbe.TESTING == True:
self.logger.setLevel(logging.INFO)
self.default_environ = { # required by PEP 333
'REQUEST_METHOD': 'GET', # 'POST', 'HEAD'
+ 'REMOTE_ADDR': '192.168.0.123',
'SCRIPT_NAME':'',
'PATH_INFO': '',
#'QUERY_STRING':'', # may be empty or absent
diff --git a/libbe/storage/http.py b/libbe/storage/http.py
index fe5bbc8..ee589a2 100644
--- a/libbe/storage/http.py
+++ b/libbe/storage/http.py
@@ -358,6 +358,7 @@ if TESTING == True:
# duplicated from libbe.command.serve.WSGITestCase
self.default_environ = {
'REQUEST_METHOD': 'GET', # 'POST', 'HEAD'
+ 'REMOTE_ADDR': '192.168.0.123',
'SCRIPT_NAME':'',
'PATH_INFO': '',
#'QUERY_STRING':'', # may be empty or absent
diff --git a/libbe/util/subproc.py b/libbe/util/subproc.py
index 412ed36..be3bf31 100644
--- a/libbe/util/subproc.py
+++ b/libbe/util/subproc.py
@@ -21,6 +21,7 @@ Functions for running external commands in subprocesses.
from subprocess import Popen, PIPE
import sys
+import types
import libbe
from encoding import get_encoding
@@ -45,7 +46,8 @@ class CommandError(Exception):
self.stderr = stderr
def invoke(args, stdin=None, stdout=PIPE, stderr=PIPE, expect=(0,),
- cwd=None, unicode_output=True, verbose=False, encoding=None):
+ cwd=None, shell=None, unicode_output=True, verbose=False,
+ encoding=None):
"""
expect should be a tuple of allowed exit codes. cwd should be
the directory from which the command will be executed. When
@@ -54,18 +56,29 @@ def invoke(args, stdin=None, stdout=PIPE, stderr=PIPE, expect=(0,),
"""
if cwd == None:
cwd = '.'
+ if isinstance(shell, types.StringTypes):
+ list_args = ' '.split(args) # sloppy, but just for logging
+ str_args = args
+ else:
+ list_args = args
+ str_args = ' '.join(args) # sloppy, but just for logging
if verbose == True:
- print >> sys.stderr, '%s$ %s' % (cwd, ' '.join(args))
+ print >> sys.stderr, '%s$ %s' % (cwd, str_args)
try :
if _POSIX:
- q = Popen(args, stdin=PIPE, stdout=stdout, stderr=stderr, cwd=cwd)
+ if shell is None:
+ shell = False
+ q = Popen(args, stdin=PIPE, stdout=stdout, stderr=stderr,
+ shell=shell, cwd=cwd)
else:
assert _MSWINDOWS==True, 'invalid platform'
+ if shell is None:
+ shell = True
# win32 don't have os.execvp() so have to run command in a shell
q = Popen(args, stdin=PIPE, stdout=stdout, stderr=stderr,
- shell=True, cwd=cwd)
+ shell=shell, cwd=cwd)
except OSError, e:
- raise CommandError(args, status=e.args[0], stderr=e)
+ raise CommandError(list_args, status=e.args[0], stderr=e)
stdout,stderr = q.communicate(input=stdin)
status = q.wait()
if unicode_output == True:
@@ -78,7 +91,7 @@ def invoke(args, stdin=None, stdout=PIPE, stderr=PIPE, expect=(0,),
if verbose == True:
print >> sys.stderr, '%d\n%s%s' % (status, stdout, stderr)
if status not in expect:
- raise CommandError(args, status, stdout, stderr)
+ raise CommandError(list_args, status, stdout, stderr)
return status, stdout, stderr
class Pipe (object):