From 00bb92561d07e434d8373a765bf892afd98b0ca9 Mon Sep 17 00:00:00 2001 From: Felipe Monteiro Date: Tue, 27 Jun 2017 17:01:07 +0100 Subject: [PATCH] DECKHAND-2: Design core Deckhand API framework This commit implements the core Deckhand API framework. It does not implement any real API routes. The core framework is modeled after Drydock's [0]. This commit specifically: - implements the core API framework which uses falcon - implements errors.py for preliminary errors - implements base resource class from which other API resources will inherit to build out the API itself - implements base API router - implements entry-point for kicking off deckhand - updates base README.rst with instructions on - running and installing -- similar to Drydock's - implements dummy API resource for secrets, to be fleshed out further in a follow-up commit [0] https://github.com/att-comdev/drydock --- AUTHORS | 1 + ChangeLog | 4 +- README.rst | 9 ++++ deckhand/control/__init__.py | 0 deckhand/control/api.py | 37 +++++++++++++ deckhand/control/base.py | 102 +++++++++++++++++++++++++++++++++++ deckhand/control/secrets.py | 36 +++++++++++++ deckhand/deckhand.py | 23 ++++++++ deckhand/errors.py | 21 ++++++++ requirements.txt | 7 +-- test-requirements.txt | 1 + tox.ini | 4 +- 12 files changed, 239 insertions(+), 6 deletions(-) create mode 100644 deckhand/control/__init__.py create mode 100644 deckhand/control/api.py create mode 100644 deckhand/control/base.py create mode 100644 deckhand/control/secrets.py create mode 100644 deckhand/deckhand.py create mode 100644 deckhand/errors.py diff --git a/AUTHORS b/AUTHORS index 72dbc755..25c07434 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,2 +1,3 @@ Alan Meadows Felipe Monteiro +Felipe Monteiro diff --git a/ChangeLog b/ChangeLog index efc5c23e..323b2ee2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,8 @@ CHANGES ======= -* DECKHAND-11: Add oslo.config integration to Deckhand +* pep8 +* Cookie-butter falcon logic +* Oslo config integration (#1) * Add ChangeLog * Initial commit diff --git a/README.rst b/README.rst index 089d8a0e..2f709a99 100644 --- a/README.rst +++ b/README.rst @@ -2,3 +2,12 @@ Deckhand ======== A foundational python REST YAML processing engine providing data and secrets management to other platform services. + +To run:: + + $ sudo pip install uwsgi + $ virtualenv -p python3 /var/tmp/deckhand + $ . /var/tmp/deckhand/bin/activate + $ sudo pip install . + $ python setup.py install + $ uwsgi --http :9000 -w deckhand.deckhand --callable deckhand --enable-threads -L diff --git a/deckhand/control/__init__.py b/deckhand/control/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/deckhand/control/api.py b/deckhand/control/api.py new file mode 100644 index 00000000..a5ca0606 --- /dev/null +++ b/deckhand/control/api.py @@ -0,0 +1,37 @@ +# Copyright 2017 AT&T Intellectual Property. All other rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import falcon + +from deckhand.control import base as api_base +from deckhand.control import secrets + + +def start_api(state_manager=None): + """Start the Deckhand API service. + + Create routes for the v1.0 API. + """ + control_api = falcon.API(request_type=api_base.DeckhandRequest) + + v1_0_routes = [ + ('/secrets', secrets.SecretsResource()) + ] + + for path, res in v1_0_routes: + control_api.add_route(os.path.join('/api/v1.0', path), res) + + return control_api diff --git a/deckhand/control/base.py b/deckhand/control/base.py new file mode 100644 index 00000000..7b70498d --- /dev/null +++ b/deckhand/control/base.py @@ -0,0 +1,102 @@ +# Copyright 2017 AT&T Intellectual Property. All other rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import uuid + +import falcon +from falcon import request + +from deckhand import errors + + +class BaseResource(object): + """Base resource class for implementing API resources.""" + + def __init__(self): + self.authorized_roles = [] + + def on_options(self, req, resp): + self_attrs = dir(self) + methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH'] + allowed_methods = [] + + for m in methods: + if 'on_' + m.lower() in self_attrs: + allowed_methods.append(m) + + resp.headers['Allow'] = ','.join(allowed_methods) + resp.status = falcon.HTTP_200 + + # For authorizing access at the Resource level. A Resource requiring + # finer-grained authorization at the method or instance level must + # implement that in the request handlers + def authorize_roles(self, role_list): + authorized = set(self.authorized_roles) + applied = set(role_list) + + if authorized.isdisjoint(applied): + return False + else: + return True + + def req_json(self, req): + if req.content_length is None or req.content_length == 0: + return None + + if req.content_type is not None and req.content_type.lower() \ + == 'application/json': + raw_body = req.stream.read(req.content_length or 0) + + if raw_body is None: + return None + + try: + json_body = json.loads(raw_body.decode('utf-8')) + return json_body + except json.JSONDecodeError as jex: + raise errors.InvalidFormat("%s: Invalid JSON in body: %s" % ( + req.path, jex)) + else: + raise errors.InvalidFormat("Requires application/json payload") + + def return_error(self, resp, status_code, message="", retry=False): + resp.body = json.dumps( + {'type': 'error', 'message': message, 'retry': retry}) + resp.status = status_code + + +class DeckhandRequestContext(object): + + def __init__(self): + self.user = None + self.roles = ['anyone'] + self.request_id = str(uuid.uuid4()) + + def set_user(self, user): + self.user = user + + def add_role(self, role): + self.roles.append(role) + + def add_roles(self, roles): + self.roles.extend(roles) + + def remove_role(self, role): + if role in self.roles: + self.roles.remove(role) + + +class DeckhandRequest(request.Request): + context_type = DeckhandRequestContext diff --git a/deckhand/control/secrets.py b/deckhand/control/secrets.py new file mode 100644 index 00000000..96048a88 --- /dev/null +++ b/deckhand/control/secrets.py @@ -0,0 +1,36 @@ +# Copyright 2017 AT&T Intellectual Property. All other rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import falcon + +from oslo_serialization import jsonutils as json + +from deckhand.control import base as api_base + + +class SecretsResource(api_base.BaseResource): + """API resource for interacting with Barbican. + + TODO(felipemonteiro): Once Barbican integration is fully implemented, + implement API endpoints below. + """ + + def __init__(self, **kwargs): + super(SecretsResource, self).__init__(**kwargs) + self.authorized_roles = ['user'] + + def on_get(self, req, resp): + # TODO(felipemonteiro): Implement this API endpoint. + resp.body = json.dumps({'secrets': 'test_secrets'}) + resp.status = falcon.HTTP_200 diff --git a/deckhand/deckhand.py b/deckhand/deckhand.py new file mode 100644 index 00000000..a64b2378 --- /dev/null +++ b/deckhand/deckhand.py @@ -0,0 +1,23 @@ +# Copyright 2017 AT&T Intellectual Property. All other rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from deckhand.control import api + + +def start_deckhand(): + return api.start_api() + + +# Callable to be used by uwsgi. +deckhand = start_deckhand() diff --git a/deckhand/errors.py b/deckhand/errors.py new file mode 100644 index 00000000..1a1e3c62 --- /dev/null +++ b/deckhand/errors.py @@ -0,0 +1,21 @@ +# Copyright 2017 AT&T Intellectual Property. All other rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class ApiError(Exception): + pass + + +class InvalidFormat(ApiError): + pass diff --git a/requirements.txt b/requirements.txt index ec98e895..eb5f7f6e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ -# API falcon==1.1.0 - -# Oslo oslo.config>=3.22.0 # Apache-2.0 +oslo.config>=3.22.0 # Apache-2.0 +oslo.serialization>=1.10.0 # Apache-2.0 +python-barbicanclient>=4.0.0 # Apache-2.0 +keystoneauth1>=2.21.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index e69de29b..17d1a8e9 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -0,0 +1 @@ +falcon==1.1.0 \ No newline at end of file diff --git a/tox.ini b/tox.ini index 9e2846e3..5a9a54f1 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,7 @@ commands = oslo-config-generator --config-file=etc/deckhand/config-generator.con commands = flake8 {posargs} [flake8] -# D100, D103, D104 deal with docstrings in public functions +# D100-104 deal with docstrings in public functions # D205, D400, D401 deal with docstring formatting -ignore=E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E251,H405,D100,D103,D104,D205,D400,D401 +ignore=E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E251,H405,D100,D101,D102,D103,D104,D205,D400,D401,I100 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools/xenserver*,releasenotes