diff --git a/helm_drydock/control/api.py b/helm_drydock/control/api.py new file mode 100644 index 00000000..e584cdc0 --- /dev/null +++ b/helm_drydock/control/api.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 .designs import DesignsResource, DesignPartsResource +from .tasks import TasksResource +from .base import DrydockRequest +from .middleware import AuthMiddleware, ContextMiddleware, LoggingMiddleware + +def start_api(state_manager): + control_api = falcon.API(request_type=DrydockRequest, + middleware=[AuthMiddleware(), ContextMiddleware(), LoggingMiddleware()]) + + # API for managing orchestrator tasks + control_api.add_route('/tasks', TasksResource(state_manager=state_manager)) + control_api.add_route('/tasks/{task_id}', TaskResource(state_manager=state_manager)) + + # API for managing site design data + control_api.add_route('/designs', DesignsResource(state_manager=state_manager)) + control_api.add_route('/designs/{design_id}', DesignResource(state_manager=state_manager)) + control_api.add_route('/designs/{design_id}/parts', DesignsPartsResource(state_manager=state_manager)) + control_api.add_route('/designs/{design_id}/parts/{kind}', DesignsPartsKindsResource(state_manager=state_manager)) + control_api.add_route('/designs/{design_id}/parts/{kind}/{name}', DesignsPartResource(state_manager=state_manager)) + + return control_api diff --git a/helm_drydock/control/base.py b/helm_drydock/control/base.py new file mode 100644 index 00000000..04a1f8b1 --- /dev/null +++ b/helm_drydock/control/base.py @@ -0,0 +1,76 @@ +# 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.request as request +import uuid + +class BaseResource(object): + + 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 + + # By default, no one is authorized to use a resource + def authorize_roles(self, role_list): + return False + +class StatefulResource(BaseResource): + + def __init__(self, state_manager=None): + super(StatefulResource, self).__init__() + + if state_manager is None: + raise ValueError("StatefulResources require a state manager be set") + + self.state_manager = state_manager + + +class DrydockRequestContext(object): + + def __init__(self): + self.log_level = 'error' + self.user = None + self.roles = ['anyone'] + self.request_id = str(uuid.uuid4()) + self.external_marker = None + + def set_log_level(self, level): + if level in ['error', 'info', 'debug']: + self.log_level = level + + 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): + self.roles = [x for x in self.roles + if x != role] + + def set_external_marker(self, marker): + self.external_marker = str(marker)[:32] + +class DrydockRequest(request.Request) + context_type = DrydockRequestContext \ No newline at end of file diff --git a/helm_drydock/control/designs.py b/helm_drydock/control/designs.py new file mode 100644 index 00000000..eead1d19 --- /dev/null +++ b/helm_drydock/control/designs.py @@ -0,0 +1,45 @@ +# 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 +import json + +from .base import StatefulResource + +class DesignsResource(StatefulResource): + + def __init__(self, **kwargs): + super(DesignsResource, self).__init__(**kwargs) + + def on_get(self, req, resp): + state = self.state_manager + + designs = state.designs.keys() + + resp.body = json.dumps(designs) + resp.status = falcon.HTTP_200 + + def authorize_roles(self, role_list): + if 'user' in role_list: + return True + + return False + +class DesignResource(StatefulResource): + +class DesignsPartsResource(StatefulResource): + +class DesignsPartsKindsResource(StatefulResource): + +class DesignsPartResource(StatefulResource): + diff --git a/helm_drydock/control/middleware.py b/helm_drydock/control/middleware.py new file mode 100644 index 00000000..2ab5ad16 --- /dev/null +++ b/helm_drydock/control/middleware.py @@ -0,0 +1,73 @@ +# 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 + +class AuthMiddleware(object): + + # Authentication + def process_request(self, req, resp): + ctx = req.context + token = req.get_header('X-Auth-Token') + + user = self.validate_token(token) + + if user is not None: + ctx.set_user(user) + user_roles = self.role_list(user) + ctx.add_roles(user_roles) + else: + ctx.add_role('anyone') + + # Authorization + def process_resource(self, req, resp, resource): + ctx = req.context + + if not resource.authorize_roles(ctx.roles): + raise falcon.HTTPUnauthorized('Authentication required', + ('This resource requires an authorized role.')) + + # Return the username associated with an authenticated token or None + def validate_token(self, token): + if token == '42': + return 'scott' + elif token == 'bigboss': + return 'admin' + else: + return None + + # Return the list of roles assigned to the username + # Roles need to be an enum + def role_list(self, username): + if username == 'scott': + return ['user'] + elif username == 'admin': + return ['user', 'admin'] + +class ContextMiddleware(object): + + def process_request(self, req, resp): + ctx = req.context + + requested_logging = req.get_header('X-Log-Level') + + if requested_logging == 'DEBUG' and 'admin' in ctx.roles: + ctx.set_log_level('debug') + elif requested_logging == 'INFO': + ctx.set_log_level('info') + +class LoggingMiddleware(object): + + def process_response(self, req, resp, resource, req_succeeded): + ctx = req.context \ No newline at end of file diff --git a/helm_drydock/control/readme.md b/helm_drydock/control/readme.md index 2dad5b24..a4bf0d13 100644 --- a/helm_drydock/control/readme.md +++ b/helm_drydock/control/readme.md @@ -12,3 +12,14 @@ Anticipate basing this service on the falcon Python library POST - Create a new orchestration task and submit it for execution GET - Get status of a task DELETE - Cancel execution of a task if permitted + +### /designs ### + +POST - Create a new site design so design parts can be added +GET - Get a current design if available + +### /designs/{id}/parts + +POST - Submit a new design part to be ingested and added to this design +GET - View a currently defined design part +PUT - Replace an existing design part \ No newline at end of file diff --git a/helm_drydock/control/tasks.py b/helm_drydock/control/tasks.py new file mode 100644 index 00000000..9c65e925 --- /dev/null +++ b/helm_drydock/control/tasks.py @@ -0,0 +1,20 @@ +# 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 .base import StatefulResource + +class TasksResource(StatefulResource): + +class TaskResource(StatefulResource):