diff options
Diffstat (limited to 'libbe/command/serve_storage.py')
-rw-r--r-- | libbe/command/serve_storage.py | 328 |
1 files changed, 0 insertions, 328 deletions
diff --git a/libbe/command/serve_storage.py b/libbe/command/serve_storage.py deleted file mode 100644 index 1d8d0dd..0000000 --- a/libbe/command/serve_storage.py +++ /dev/null @@ -1,328 +0,0 @@ -# Copyright (C) 2010-2012 Chris Ball <cjb@laptop.org> -# W. Trevor King <wking@tremily.us> -# -# This file is part of Bugs Everywhere. -# -# Bugs Everywhere is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 2 of the License, or (at your option) any -# later version. -# -# Bugs Everywhere is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# Bugs Everywhere. If not, see <http://www.gnu.org/licenses/>. - -"""Define the :py:class:`Serve` serving BE Storage over HTTP. - -See Also --------- -:py:mod:`libbe.storage.http` : the associated client -""" - -import logging -import os.path - -import libbe -import libbe.command -import libbe.command.util -import libbe.util.http -import libbe.util.subproc -import libbe.util.wsgi -import libbe.version - -if libbe.TESTING: - import copy - import doctest - import StringIO - import sys - import unittest - import wsgiref.validate - try: - import cherrypy.test.webtest - cherrypy_test_webtest = True - except ImportError: - cherrypy_test_webtest = None - - import libbe.bugdir - import libbe.util.wsgi - - -class ServerApp (libbe.util.wsgi.WSGI_AppObject, - libbe.util.wsgi.WSGI_DataObject): - """WSGI server for a BE Storage instance over HTTP. - - RESTful_ WSGI request handler for serving the - libbe.storage.http.HTTP backend with GET, POST, and HEAD commands. - For more information on authentication and REST, see John - Calcote's `Open Sourcery article`_ - - .. _RESTful: http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm - .. _Open Sourcery article: http://jcalcote.wordpress.com/2009/08/10/restful-authentication/ - - This serves files from a connected storage instance, usually - a VCS-based repository located on the local machine. - - Notes - ----- - - The GET and HEAD requests are identical except that the HEAD - request omits the actual content of the file. - """ - server_version = 'BE-storage-server/' + libbe.version.version() - - def __init__(self, storage=None, notify=False, **kwargs): - super(ServerApp, self).__init__( - urls=[ - (r'^add/?', self.add), - (r'^exists/?', self.exists), - (r'^remove/?', self.remove), - (r'^ancestors/?', self.ancestors), - (r'^children/?', self.children), - (r'^get/(.+)', self.get), - (r'^set/(.+)', self.set), - (r'^commit/?', self.commit), - (r'^revision-id/?', self.revision_id), - (r'^changed/?', self.changed), - (r'^version/?', self.version), - ], - **kwargs) - self.storage = storage - self.notify = notify - - # handlers - def add(self, environ, start_response): - data = self.post_data(environ) - source = 'post' - id = self.data_get_id(data, source=source) - parent = self.data_get_string( - data, 'parent', default=None, source=source) - 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): - data = self.query_data(environ) - source = 'query' - id = self.data_get_id(data, source=source) - revision = self.data_get_string( - data, 'revision', default=None, source=source) - content = str(self.storage.exists(id, revision)) - return self.ok_response(environ, start_response, content) - - def remove(self, environ, start_response): - data = self.post_data(environ) - source = 'post' - id = self.data_get_id(data, source=source) - recursive = self.data_get_boolean( - data, 'recursive', default=False, source=source) - if recursive == True: - 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): - data = self.query_data(environ) - source = 'query' - id = self.data_get_id(data, source=source) - revision = self.data_get_string( - data, 'revision', default=None, source=source) - content = '\n'.join(self.storage.ancestors(id, revision))+'\n' - return self.ok_response(environ, start_response, content) - - def children(self, environ, start_response): - data = self.query_data(environ) - source = 'query' - id = self.data_get_id(data, default=None, source=source) - revision = self.data_get_string( - data, 'revision', default=None, source=source) - content = '\n'.join(self.storage.children(id, revision)) - return self.ok_response(environ, start_response, content) - - def get(self, environ, start_response): - data = self.query_data(environ) - source = 'query' - try: - id = environ['be-server.url_args'][0] - except: - raise libbe.util.wsgi.HandlerError(404, 'Not Found') - revision = self.data_get_string( - data, 'revision', default=None, source=source) - content = self.storage.get(id, revision=revision) - be_version = self.storage.storage_version(revision) - return self.ok_response(environ, start_response, content, - headers=[('X-BE-Version', be_version)]) - - def set(self, environ, start_response): - data = self.post_data(environ) - try: - id = environ['be-server.url_args'][0] - except: - raise libbe.util.wsgi.HandlerError(404, 'Not Found') - if not 'value' in data: - raise libbe.util.wsgi.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): - data = self.post_data(environ) - if not 'summary' in data: - raise libbe.util.wsgi.HandlerError( - 406, 'Missing query key summary') - summary = data['summary'] - if not 'body' in data or data['body'] == 'None': - data['body'] = None - body = data['body'] - if not 'allow_empty' in data \ - or data['allow_empty'] == 'True': - allow_empty = True - else: - allow_empty = False - try: - revision = self.storage.commit(summary, body, allow_empty) - except libbe.storage.EmptyCommit, e: - raise libbe.util.wsgi.HandlerError( - libbe.util.http.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): - data = self.query_data(environ) - source = 'query' - index = int(self.data_get_string( - data, 'index', default=libbe.util.wsgi.HandlerError, - source=source)) - content = self.storage.revision_id(index) - return self.ok_response(environ, start_response, content) - - def changed(self, environ, start_response): - data = self.query_data(environ) - source = 'query' - revision = self.data_get_string( - data, 'revision', default=None, source=source) - add,mod,rem = self.storage.changed(revision) - content = '\n\n'.join(['\n'.join(p) for p in (add,mod,rem)]) - return self.ok_response(environ, start_response, content) - - def version(self, environ, start_response): - data = self.query_data(environ) - source = 'query' - revision = self.data_get_string( - data, 'revision', default=None, source=source) - content = self.storage.storage_version(revision) - return self.ok_response(environ, start_response, content) - - 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 ServeStorage (libbe.util.wsgi.ServerCommand): - """Serve bug directory storage over HTTP. - - This allows you to run local `be` commands interfacing with remote - data, transmitting file reads/writes/etc. over the network. - - :py:class:`~libbe.command.base.Command` wrapper around - :py:class:`ServerApp`. - """ - - name = 'serve-storage' - - def _get_app(self, logger, storage, **kwargs): - return ServerApp( - logger=logger, storage=storage, notify=kwargs.get('notify', False)) - - def _long_help(self): - return """ -Example usage:: - - $ be serve-storage - -And in another terminal (or after backgrounding the server):: - - $ be --repo http://localhost:8000/ list - -If you bind your server to a public interface, take a look at the -``--read-only`` option so other people can't mess with your repository. -""" - - -# alias for libbe.command.base.get_command_class() -Serve_storage = ServeStorage - - -if libbe.TESTING: - class ServerAppTestCase (libbe.util.wsgi.WSGITestCase): - def setUp(self): - super(ServerAppTestCase, self).setUp() - self.bd = libbe.bugdir.SimpleBugDir(memory=False) - self.app = ServerApp(self.bd.storage, logger=self.logger) - - def tearDown(self): - self.bd.cleanup() - super(ServerAppTestCase, self).tearDown() - - def test_add_get(self): - try: - self.getURL(self.app, '/add/', method='GET') - except libbe.util.wsgi.HandlerError as e: - self.failUnless(e.code == 404, e) - else: - self.fail('GET /add/ did not raise 404') - - def test_add_post(self): - self.getURL(self.app, '/add/', method='POST', - data_dict={'id':'123456', 'parent':'abc123', - 'directory':'True'}) - self.failUnless(self.status == '200 OK', self.status) - self.failUnless(self.response_headers == [], - self.response_headers) - self.failUnless(self.exc_info is None, self.exc_info) - # Note: other methods tested in libbe.storage.http - - # TODO: integration tests on Serve? - - unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) - suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) |