launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #17943
[Merge] lp:~blr/turnip/create-api into lp:turnip
Bayard 'kit' Randel has proposed merging lp:~blr/turnip/create-api into lp:turnip.
Requested reviews:
Canonical Launchpad Branches (canonical-launchpad-branches)
For more details, see:
https://code.launchpad.net/~blr/turnip/create-api/+merge/250688
* Replace the twisted create endpoint with a Cornice webservice.
* Add create/delete endpoints and implementation, with relevant tests.
--
Your team Launchpad code reviewers is subscribed to branch lp:turnip.
=== modified file 'Makefile'
--- Makefile 2015-01-19 08:58:30 +0000
+++ Makefile 2015-02-23 23:56:16 +0000
@@ -10,6 +10,8 @@
BUILDOUT_CFG := buildout.cfg
BUILDOUT = PYTHONPATH= $(BUILDOUT_BIN) -qc $(BUILDOUT_CFG)
+APP_PATH := ./turnip
+
default: check
@@ -54,6 +56,10 @@
touch --no-create $@
+bin/api:
+ PYTHONPATH=$(APP_PATH) pserve api.ini --reload
+
+
bin/twistd: $(BUILDOUT_BIN) $(BUILDOUT_CFG) setup.py
$(BUILDOUT) install runtime
@@ -87,7 +93,9 @@
clean_all: clean_buildout clean_eggs
+lint:
+ @flake8 turnip
.PHONY: \
build build-update-paths check clean clean_all clean_buildout \
- clean_eggs default dist
+ clean_eggs default dist lint
=== added file 'api.ini'
--- api.ini 1970-01-01 00:00:00 +0000
+++ api.ini 2015-02-23 23:56:16 +0000
@@ -0,0 +1,45 @@
+[app:main]
+use = egg:turnip
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.debug_templates = true
+pyramid.default_locale_name = en
+
+[server:main]
+use = egg:waitress#main
+host = 0.0.0.0
+port = 19417
+
+# Begin logging configuration
+
+[loggers]
+keys = root, api
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_api]
+level = DEBUG
+handlers =
+qualname = api
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+
+# End logging configuration
=== modified file 'requirements.txt'
--- requirements.txt 2015-02-18 22:53:47 +0000
+++ requirements.txt 2015-02-23 23:56:16 +0000
@@ -1,11 +1,25 @@
+PasteDeploy==1.5.2
+Twisted==15.0.0
+WebOb==1.4
argparse==1.2.1
-envdir==0.7
+cornice==0.17
+flake8==2.3.0
lazr.sshserver==0.1.1
+mccabe==0.3
+pep8==1.6.1
pyasn1==0.1.7
pycrypto==2.6.1
+pyflakes==0.8.1
+pyramid==1.5.2
PyYAML==3.11
+repoze.lru==0.6
+simplejson==3.6.5
+translationstring==1.3
Twisted==14.0.2
+venusian==1.0
+waitress==0.8.9
wsgiref==0.1.2
zope.component==4.2.1
+zope.deprecation==4.1.2
zope.event==4.0.3
zope.interface==4.1.2
=== modified file 'setup.py'
--- setup.py 2015-02-17 21:32:29 +0000
+++ setup.py 2015-02-23 23:56:16 +0000
@@ -4,18 +4,23 @@
import os
-from setuptools import setup
+from setuptools import (
+ find_packages,
+ setup
+)
here = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(here, 'README')) as f:
README = f.read()
+requires = ['cornice', 'lazr.sshserver', 'pygit2', 'Twisted', 'waitress',
+ 'zope.interface']
setup(
name='turnip',
version='0.1',
- packages=['turnip'],
+ packages=find_packages(),
include_package_data=True,
zip_safe=False,
maintainer='LAZR Developers',
@@ -36,4 +41,9 @@
'testtools',
]),
test_suite='turnip.tests',
+ entry_points = """\
+ [paste.app_factory]
+ main = turnip.api:main
+ """,
+ paster_plugins=['pyramid'],
)
=== added directory 'turnip/api'
=== removed file 'turnip/api.py'
--- turnip/api.py 2015-02-03 16:59:42 +0000
+++ turnip/api.py 1970-01-01 00:00:00 +0000
@@ -1,59 +0,0 @@
-from __future__ import (
- absolute_import,
- print_function,
- unicode_literals,
- )
-
-import os
-
-from twisted.internet import defer
-from twisted.internet.utils import getProcessValue
-from twisted.web import (
- resource,
- server,
- static,
- )
-
-from turnip.helpers import compose_path
-
-
-class TurnipAPIResource(resource.Resource):
-
- def __init__(self, root):
- resource.Resource.__init__(self)
- self.root = root
-
- def getChild(self, name, request):
- if name == b'':
- return static.Data(b'Turnip API endpoint', type=b'text/plain')
- if name == b'create':
- return CreateResource(self.root)
- else:
- return resource.NoResource(b'No such resource')
-
- def render_GET(self, request):
- return b'Turnip API service endpoint'
-
-
-class CreateResource(resource.Resource):
-
- def __init__(self, root):
- resource.Resource.__init__(self)
- self.root = root
-
- @defer.inlineCallbacks
- def createRepo(self, request, raw_path):
- repo_path = compose_path(self.root, raw_path)
- if os.path.exists(repo_path):
- raise Exception("Repository '%s' already exists" % repo_path)
- ret = yield getProcessValue('git', ('init', '--bare', repo_path))
- if ret != 0:
- raise Exception("'git init' failed")
- request.write(b'OK')
- request.finish()
-
- def render_POST(self, request):
- path = request.args['path'][0]
- d = self.createRepo(request, path)
- d.addErrback(request.processingFailed)
- return server.NOT_DONE_YET
=== added file 'turnip/api/__init__.py'
--- turnip/api/__init__.py 1970-01-01 00:00:00 +0000
+++ turnip/api/__init__.py 2015-02-23 23:56:16 +0000
@@ -0,0 +1,10 @@
+"""Main entry point
+"""
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.include("cornice")
+ config.scan("turnip.api.views")
+ return config.make_wsgi_app()
=== added file 'turnip/api/store.py'
--- turnip/api/store.py 1970-01-01 00:00:00 +0000
+++ turnip/api/store.py 2015-02-23 23:56:16 +0000
@@ -0,0 +1,24 @@
+import shutil
+
+import pygit2
+
+
+class Store(object):
+ """Provides methods for manipulating repos on disk with pygit2."""
+ @classmethod
+ def init(self, repo, isBare=True):
+ """Initialise a git repo with pygit2."""
+ try:
+ repo_path = pygit2.init_repository(repo, isBare)
+ except pygit2.GitError as e:
+ print('Unable to create repository: %s' % e)
+ return
+ return repo_path
+
+ @classmethod
+ def delete(self, repo):
+ """Permanently delete a git repository from repo store."""
+ try:
+ shutil.rmtree(repo)
+ except (IOError, OSError) as e:
+ print('Unable to delete repository: %s' % e)
=== added file 'turnip/api/views.py'
--- turnip/api/views.py 1970-01-01 00:00:00 +0000
+++ turnip/api/views.py 2015-02-23 23:56:16 +0000
@@ -0,0 +1,38 @@
+import os
+import shutil
+
+from cornice.resource import resource
+from cornice.util import extract_json_data
+import pygit2
+
+from turnip.config import TurnipConfig
+from turnip.api.store import Store
+
+@resource(collection_path='repo', path='/repo/{name}')
+class RepoAPI(object):
+ """Provides HTTP API for repository actions."""
+
+ def __init__(self, request):
+ config = TurnipConfig()
+ self.request = request
+ self.repo_store = config.get('repo_store')
+
+ def collection_post(self):
+ """Initialise a new git repository."""
+ repo_path = extract_json_data(self.request).get('repo_path')
+ if not repo_path:
+ self.request.errors.add('body', 'repo_path',
+ 'repo_path is missing')
+ return
+ repo = os.path.join(self.repo_store, repo_path)
+ isBare = extract_json_data(self.request).get('bare_repo')
+ Store.init(repo, isBare)
+
+ def delete(self):
+
+ name = self.request.matchdict['name']
+ if not name:
+ self.request.errors.add('body', 'name', 'repo name is missing')
+ return
+ repo = os.path.join(self.repo_store, name)
+ Store.delete(repo)
=== added file 'turnip/tests/test_api.py'
--- turnip/tests/test_api.py 1970-01-01 00:00:00 +0000
+++ turnip/tests/test_api.py 2015-02-23 23:56:16 +0000
@@ -0,0 +1,44 @@
+from __future__ import print_function
+
+import json
+import os
+import shutil
+import unittest
+import uuid
+
+from webtest import TestApp
+
+from turnip import api
+from turnip.config import TurnipConfig
+
+
+class ApiTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.config = TurnipConfig()
+ repo_store = self.config.get('repo_store')
+ self.repo_path = str(uuid.uuid1())
+ self.repo_store = os.path.join(repo_store, self.repo_path)
+
+ def remove_store(self):
+ if os.path.exists(self.repo_store):
+ shutil.rmtree(self.repo_store)
+
+ def test_repo_post(self):
+ self.addCleanup(self.remove_store)
+ app = TestApp(api.main({}))
+ resp = app.post('/repo', json.dumps({'repo_path': self.repo_path}))
+ self.assertEquals(resp.status_code, 200)
+ # cleanup
+
+
+ def test_repo_delete(self):
+ self.addCleanup(self.remove_store)
+ app = TestApp(api.main({}))
+ app.post('/repo', json.dumps({'repo_path': self.repo_path}))
+ resp = app.delete('/repo/{}'.format(self.repo_path))
+ self.assertEquals(resp.status_code, 200)
+ self.assertFalse(os.path.exists(self.repo_store))
+
+if __name__ == '__main__':
+ unittest.main()
=== modified file 'versions.cfg'
--- versions.cfg 2015-02-17 21:32:29 +0000
+++ versions.cfg 2015-02-23 23:56:16 +0000
@@ -6,11 +6,13 @@
[versions]
# Alphabetical, case-SENSITIVE, blank line after this comment
+cornice = 0.17
extras = 0.0.3
fixtures = 0.3.12
lazr.sshserver = 0.1.1
pyasn1 = 0.1.6
pycrypto = 2.6
+pygit2 = 0.21.4
python-mimeparse = 0.1.4
PyYAML = 3.11
setuptools = 0.6c11
Follow ups