
Currently the webclient quotes the project name before sending the request, which causes a 404 if there is a / in the project name since the API doesn't unquote the name before searching for it. This commit makes the API less opinionated by unquoting provided project names before searching for them, which should make it less frustrating to use. Change-Id: I9a0158231f54809b90198875712ac0cbd543a254
240 lines
8.4 KiB
Python
240 lines
8.4 KiB
Python
# Copyright (c) 2013 Mirantis Inc.
|
|
#
|
|
# 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 oslo_config import cfg
|
|
from pecan.decorators import expose
|
|
from pecan import response
|
|
from pecan import rest
|
|
from pecan.secure import secure
|
|
from six.moves.urllib.parse import unquote
|
|
from wsme import types as wtypes
|
|
import wsmeext.pecan as wsme_pecan
|
|
|
|
from storyboard._i18n import _
|
|
from storyboard.api.auth import authorization_checks as checks
|
|
from storyboard.api.v1.search import search_engine
|
|
from storyboard.api.v1 import validations
|
|
from storyboard.api.v1 import wmodels
|
|
from storyboard.common import decorators
|
|
from storyboard.common import exception as exc
|
|
from storyboard.db.api import projects as projects_api
|
|
|
|
|
|
CONF = cfg.CONF
|
|
|
|
SEARCH_ENGINE = search_engine.get_engine()
|
|
|
|
|
|
class ProjectsController(rest.RestController):
|
|
"""REST controller for Projects.
|
|
|
|
At this moment it provides read-only operations.
|
|
"""
|
|
|
|
_custom_actions = {"search": ["GET"]}
|
|
|
|
validation_post_schema = validations.PROJECTS_POST_SCHEMA
|
|
validation_put_schema = validations.PROJECTS_PUT_SCHEMA
|
|
|
|
@decorators.db_exceptions
|
|
@secure(checks.guest)
|
|
@wsme_pecan.wsexpose(wmodels.Project, int)
|
|
def get_one_by_id(self, project_id):
|
|
"""Retrieve information about the given project.
|
|
|
|
:param project_id: Project ID.
|
|
"""
|
|
|
|
project = projects_api.project_get(project_id)
|
|
|
|
if project:
|
|
return wmodels.Project.from_db_model(project)
|
|
else:
|
|
raise exc.NotFound(_("Project %s not found") % project_id)
|
|
|
|
@decorators.db_exceptions
|
|
@secure(checks.guest)
|
|
@wsme_pecan.wsexpose(wmodels.Project, wtypes.text)
|
|
def get_one_by_name(self, project_name):
|
|
"""Retrieve information about the given project.
|
|
|
|
:param name: Project name.
|
|
"""
|
|
|
|
project = projects_api.project_get_by_name(project_name)
|
|
|
|
if project:
|
|
return wmodels.Project.from_db_model(project)
|
|
else:
|
|
raise exc.NotFound(_("Project %s not found") % project_name)
|
|
|
|
@decorators.db_exceptions
|
|
@secure(checks.guest)
|
|
@wsme_pecan.wsexpose([wmodels.Project], int, int, int, wtypes.text,
|
|
wtypes.text, int, int, wtypes.text, wtypes.text)
|
|
def get(self, marker=None, offset=None, limit=None, name=None,
|
|
description=None, project_group_id=None, subscriber_id=None,
|
|
sort_field='id', sort_dir='asc'):
|
|
"""Retrieve a list of projects.
|
|
|
|
Example::
|
|
|
|
curl https://my.example.org/api/v1/projects
|
|
|
|
:param marker: The resource id where the page should begin.
|
|
:param offset: The offset to start the page at.
|
|
:param limit: The number of projects to retrieve.
|
|
:param name: A string to filter the name by.
|
|
:param description: A string to filter the description by.
|
|
:param project_group_id: The ID of a project group to which the
|
|
projects must belong.
|
|
:param subscriber_id: The ID of a subscriber to filter results by.
|
|
:param sort_field: The name of the field to sort on.
|
|
:param sort_dir: Sort direction for results (asc, desc).
|
|
"""
|
|
|
|
# Boundary check on limit.
|
|
if limit is not None:
|
|
limit = max(0, limit)
|
|
|
|
# Resolve the marker record.
|
|
marker_project = None
|
|
if marker is not None:
|
|
marker_project = projects_api.project_get(marker)
|
|
|
|
projects = \
|
|
projects_api.project_get_all(marker=marker_project,
|
|
offset=offset,
|
|
limit=limit,
|
|
name=name,
|
|
description=description,
|
|
project_group_id=project_group_id,
|
|
subscriber_id=subscriber_id,
|
|
sort_field=sort_field,
|
|
sort_dir=sort_dir)
|
|
project_count = \
|
|
projects_api.project_get_count(name=name,
|
|
description=description,
|
|
project_group_id=project_group_id,
|
|
subscriber_id=subscriber_id)
|
|
|
|
# Apply the query response headers.
|
|
if limit:
|
|
response.headers['X-Limit'] = str(limit)
|
|
response.headers['X-Total'] = str(project_count)
|
|
if marker_project:
|
|
response.headers['X-Marker'] = str(marker_project.id)
|
|
if offset is not None:
|
|
response.headers['X-Offset'] = str(offset)
|
|
|
|
return [wmodels.Project.from_db_model(p) for p in projects]
|
|
|
|
@decorators.db_exceptions
|
|
@secure(checks.superuser)
|
|
@wsme_pecan.wsexpose(wmodels.Project, body=wmodels.Project)
|
|
def post(self, project):
|
|
"""Create a new project.
|
|
|
|
.. note:: This command is only available to Admin users.
|
|
|
|
Example::
|
|
|
|
curl https://my.example.org/api/v1/projects \\
|
|
-H 'Authorization: Bearer MY_ACCESS_TOKEN' \\
|
|
-H 'Content-Type: application/json;charset=UTF-8' \\
|
|
--data-binary '{"name":"test-project",\\
|
|
"description":"A test project"}'
|
|
|
|
:param project: A project within the request body.
|
|
"""
|
|
|
|
result = projects_api.project_create(project.as_dict())
|
|
return wmodels.Project.from_db_model(result)
|
|
|
|
@decorators.db_exceptions
|
|
@secure(checks.superuser)
|
|
@wsme_pecan.wsexpose(wmodels.Project, int, body=wmodels.Project)
|
|
def put(self, project_id, project):
|
|
"""Modify this project.
|
|
|
|
.. note:: This command is only available to Admin users.
|
|
|
|
Example::
|
|
|
|
curl https://my.example.org/api/v1/projects/10 -X PUT \\
|
|
-H 'Authorization: Bearer MY_ACCESS_TOKEN' \\
|
|
-H 'Content-Type: application/json;charset=UTF-8' \\
|
|
--data-binary '{"name":"test-project-update",\\
|
|
"description":"An updated test project"}'
|
|
|
|
:param project_id: An ID of the project.
|
|
:param project: A project within the request body.
|
|
"""
|
|
result = projects_api.project_update(project_id,
|
|
project.as_dict(omit_unset=True))
|
|
|
|
if result:
|
|
return wmodels.Project.from_db_model(result)
|
|
else:
|
|
raise exc.NotFound(_("Project %s not found") % project_id)
|
|
|
|
def _is_int(self, s):
|
|
try:
|
|
int(s)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
@decorators.db_exceptions
|
|
@secure(checks.guest)
|
|
@wsme_pecan.wsexpose([wmodels.Project], wtypes.text, wtypes.text, int,
|
|
int, int)
|
|
def search(self, q="", marker=None, offset=None, limit=None):
|
|
"""The search endpoint for projects.
|
|
|
|
Example::
|
|
|
|
curl https://my.example.org/api/v1/projects/search?q=cloud
|
|
|
|
:param q: The query string.
|
|
:return: List of Projects matching the query.
|
|
"""
|
|
|
|
projects = SEARCH_ENGINE.projects_query(q=q, marker=marker,
|
|
offset=offset,
|
|
limit=limit)
|
|
|
|
return [wmodels.Project.from_db_model(project) for project in projects]
|
|
|
|
@expose()
|
|
def _route(self, args, request):
|
|
if request.method == 'GET' and len(args) > 0:
|
|
# It's a request by a name or id
|
|
something = unquote(args[0])
|
|
|
|
if something == "search":
|
|
# Request to a search endpoint
|
|
return super(ProjectsController, self)._route(args, request)
|
|
|
|
if self._is_int(something):
|
|
# Get by id
|
|
return self.get_one_by_id, args
|
|
else:
|
|
# Get by name
|
|
return self.get_one_by_name, ["/".join(args)]
|
|
|
|
# Use default routing for all other requests
|
|
return super(ProjectsController, self)._route(args, request)
|