# # 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 routes import six from heat.api.openstack.v1 import actions from heat.api.openstack.v1 import build_info from heat.api.openstack.v1 import events from heat.api.openstack.v1 import resources from heat.api.openstack.v1 import services from heat.api.openstack.v1 import software_configs from heat.api.openstack.v1 import software_deployments from heat.api.openstack.v1 import stacks from heat.common import wsgi class API(wsgi.Router): """ WSGI router for Heat v1 ReST API requests. """ def __init__(self, conf, **local_conf): self.conf = conf mapper = routes.Mapper() default_resource = wsgi.Resource(wsgi.DefaultMethodController(), wsgi.JSONRequestDeserializer()) def connect(controller, path_prefix, routes): """ This function connects the list of routes to the given controller, prepending the given path_prefix. Then for each URL it finds which request methods aren't handled and configures those to return a 405 error. Finally, it adds a handler for the OPTIONS method to all URLs that returns the list of allowed methods with 204 status code. """ # register the routes with the mapper, while keeping track of which # methods are defined for each URL urls = {} for r in routes: url = path_prefix + r['url'] methods = r['method'] if isinstance(methods, six.string_types): methods = [methods] methods_str = ','.join(methods) mapper.connect(r['name'], url, controller=controller, action=r['action'], conditions={'method': methods_str}) if url not in urls: urls[url] = methods else: urls[url] += methods # now register the missing methods to return 405s, and register # a handler for OPTIONS that returns the list of allowed methods for url, methods in urls.items(): all_methods = ['HEAD', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE'] missing_methods = [m for m in all_methods if m not in methods] allowed_methods_str = ','.join(methods) mapper.connect(url, controller=default_resource, action='reject', allowed_methods=allowed_methods_str, conditions={'method': missing_methods}) if 'OPTIONS' not in methods: mapper.connect(url, controller=default_resource, action='options', allowed_methods=allowed_methods_str, conditions={'method': 'OPTIONS'}) # Stacks stacks_resource = stacks.create_resource(conf) connect(controller=stacks_resource, path_prefix='/{tenant_id}', routes=[ # Template handling { 'name': 'template_validate', 'url': '/validate', 'action': 'validate_template', 'method': 'POST' }, { 'name': 'resource_types', 'url': '/resource_types', 'action': 'list_resource_types', 'method': 'GET' }, { 'name': 'resource_schema', 'url': '/resource_types/{type_name}', 'action': 'resource_schema', 'method': 'GET' }, { 'name': 'generate_template', 'url': '/resource_types/{type_name}/template', 'action': 'generate_template', 'method': 'GET' }, { 'name': 'template_versions', 'url': '/template_versions', 'action': 'list_template_versions', 'method': 'GET' }, { 'name': 'template_functions', 'url': '/template_versions/{template_version}' '/functions', 'action': 'list_template_functions', 'method': 'GET' }, # Stack collection { 'name': 'stack_index', 'url': '/stacks', 'action': 'index', 'method': 'GET' }, { 'name': 'stack_create', 'url': '/stacks', 'action': 'create', 'method': 'POST' }, { 'name': 'stack_preview', 'url': '/stacks/preview', 'action': 'preview', 'method': 'POST' }, { 'name': 'stack_detail', 'url': '/stacks/detail', 'action': 'detail', 'method': 'GET' }, # Stack data { 'name': 'stack_lookup', 'url': '/stacks/{stack_name}', 'action': 'lookup', 'method': ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] }, # \x3A matches on a colon. # Routes treats : specially in its regexp { 'name': 'stack_lookup', 'url': r'/stacks/{stack_name:arn\x3A.*}', 'action': 'lookup', 'method': ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] }, { 'name': 'stack_lookup_subpath', 'url': '/stacks/{stack_name}/' '{path:resources|events|template|actions}', 'action': 'lookup', 'method': 'GET' }, { 'name': 'stack_lookup_subpath_post', 'url': '/stacks/{stack_name}/' '{path:resources|events|template|actions}', 'action': 'lookup', 'method': 'POST' }, { 'name': 'stack_show', 'url': '/stacks/{stack_name}/{stack_id}', 'action': 'show', 'method': 'GET' }, { 'name': 'stack_lookup', 'url': '/stacks/{stack_name}/{stack_id}/template', 'action': 'template', 'method': 'GET' }, # Stack update/delete { 'name': 'stack_update', 'url': '/stacks/{stack_name}/{stack_id}', 'action': 'update', 'method': 'PUT' }, { 'name': 'stack_update_patch', 'url': '/stacks/{stack_name}/{stack_id}', 'action': 'update_patch', 'method': 'PATCH' }, { 'name': 'stack_delete', 'url': '/stacks/{stack_name}/{stack_id}', 'action': 'delete', 'method': 'DELETE' }, # Stack abandon { 'name': 'stack_abandon', 'url': '/stacks/{stack_name}/{stack_id}/abandon', 'action': 'abandon', 'method': 'DELETE' }, { 'name': 'stack_snapshot', 'url': '/stacks/{stack_name}/{stack_id}/snapshots', 'action': 'snapshot', 'method': 'POST' }, { 'name': 'stack_snapshot_show', 'url': '/stacks/{stack_name}/{stack_id}/snapshots/' '{snapshot_id}', 'action': 'show_snapshot', 'method': 'GET' }, { 'name': 'stack_snapshot_delete', 'url': '/stacks/{stack_name}/{stack_id}/snapshots/' '{snapshot_id}', 'action': 'delete_snapshot', 'method': 'DELETE' }, { 'name': 'stack_list_snapshots', 'url': '/stacks/{stack_name}/{stack_id}/snapshots', 'action': 'list_snapshots', 'method': 'GET' }, { 'name': 'stack_snapshot_restore', 'url': '/stacks/{stack_name}/{stack_id}/snapshots/' '{snapshot_id}/restore', 'action': 'restore_snapshot', 'method': 'POST' } ]) # Resources resources_resource = resources.create_resource(conf) stack_path = '/{tenant_id}/stacks/{stack_name}/{stack_id}' connect(controller=resources_resource, path_prefix=stack_path, routes=[ # Resource collection { 'name': 'resource_index', 'url': '/resources', 'action': 'index', 'method': 'GET' }, # Resource data { 'name': 'resource_show', 'url': '/resources/{resource_name}', 'action': 'show', 'method': 'GET' }, { 'name': 'resource_metadata_show', 'url': '/resources/{resource_name}/metadata', 'action': 'metadata', 'method': 'GET' }, { 'name': 'resource_signal', 'url': '/resources/{resource_name}/signal', 'action': 'signal', 'method': 'POST' } ]) # Events events_resource = events.create_resource(conf) connect(controller=events_resource, path_prefix=stack_path, routes=[ # Stack event collection { 'name': 'event_index_stack', 'url': '/events', 'action': 'index', 'method': 'GET' }, # Resource event collection { 'name': 'event_index_resource', 'url': '/resources/{resource_name}/events', 'action': 'index', 'method': 'GET' }, # Event data { 'name': 'event_show', 'url': '/resources/{resource_name}/events/{event_id}', 'action': 'show', 'method': 'GET' } ]) # Actions actions_resource = actions.create_resource(conf) connect(controller=actions_resource, path_prefix=stack_path, routes=[ { 'name': 'action_stack', 'url': '/actions', 'action': 'action', 'method': 'POST' } ]) # Info info_resource = build_info.create_resource(conf) connect(controller=info_resource, path_prefix='/{tenant_id}', routes=[ { 'name': 'build_info', 'url': '/build_info', 'action': 'build_info', 'method': 'GET' } ]) # Software configs software_config_resource = software_configs.create_resource(conf) connect(controller=software_config_resource, path_prefix='/{tenant_id}/software_configs', routes=[ { 'name': 'software_config_index', 'url': '', 'action': 'index', 'method': 'GET' }, { 'name': 'software_config_create', 'url': '', 'action': 'create', 'method': 'POST' }, { 'name': 'software_config_show', 'url': '/{config_id}', 'action': 'show', 'method': 'GET' }, { 'name': 'software_config_delete', 'url': '/{config_id}', 'action': 'delete', 'method': 'DELETE' } ]) # Software deployments sd_resource = software_deployments.create_resource(conf) connect(controller=sd_resource, path_prefix='/{tenant_id}/software_deployments', routes=[ { 'name': 'software_deployment_index', 'url': '', 'action': 'index', 'method': 'GET' }, { 'name': 'software_deployment_metadata', 'url': '/metadata/{server_id}', 'action': 'metadata', 'method': 'GET' }, { 'name': 'software_deployment_create', 'url': '', 'action': 'create', 'method': 'POST' }, { 'name': 'software_deployment_show', 'url': '/{deployment_id}', 'action': 'show', 'method': 'GET' }, { 'name': 'software_deployment_update', 'url': '/{deployment_id}', 'action': 'update', 'method': 'PUT' }, { 'name': 'software_deployment_delete', 'url': '/{deployment_id}', 'action': 'delete', 'method': 'DELETE' } ]) # Services service_resource = services.create_resource(conf) with mapper.submapper( controller=service_resource, path_prefix='/{tenant_id}/services' ) as sa_mapper: sa_mapper.connect("service_index", "", action="index", conditions={'method': 'GET'}) # now that all the routes are defined, add a handler for super(API, self).__init__(mapper)