]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/controllers/_rest_controller.py
3 from functools
import wraps
4 from typing
import Optional
8 from ..security
import Permission
9 from ._base
_controller
import BaseController
10 from ._endpoint
import Endpoint
11 from ._helpers
import _get_function_params
12 from ._permissions
import _set_func_permissions
13 from ._version
import APIVersion
16 class RESTController(BaseController
, skip_registry
=True):
18 Base class for providing a RESTful interface to a resource.
20 To use this class, simply derive a class from it and implement the methods
21 you want to support. The list of possible methods are:
34 curl -H "Content-Type: application/json" -X POST \
35 -d '{"username":"xyz","password":"xyz"}' https://127.0.0.1:8443/foo
36 curl https://127.0.0.1:8443/foo
37 curl https://127.0.0.1:8443/foo/0
41 # resource id parameter for using in get, set, and delete methods
42 # should be overridden by subclasses.
43 # to specify a composite id (two parameters) use '/'. e.g., "param1/param2".
44 # If subclasses don't override this property we try to infer the structure
46 RESOURCE_ID
: Optional
[str] = None
49 'GET': Permission
.READ
,
50 'POST': Permission
.CREATE
,
51 'PUT': Permission
.UPDATE
,
52 'DELETE': Permission
.DELETE
55 _method_mapping
= collections
.OrderedDict([
56 ('list', {'method': 'GET', 'resource': False, 'status': 200, 'version': APIVersion
.DEFAULT
}), # noqa E501 #pylint: disable=line-too-long
57 ('create', {'method': 'POST', 'resource': False, 'status': 201, 'version': APIVersion
.DEFAULT
}), # noqa E501 #pylint: disable=line-too-long
58 ('bulk_set', {'method': 'PUT', 'resource': False, 'status': 200, 'version': APIVersion
.DEFAULT
}), # noqa E501 #pylint: disable=line-too-long
59 ('bulk_delete', {'method': 'DELETE', 'resource': False, 'status': 204, 'version': APIVersion
.DEFAULT
}), # noqa E501 #pylint: disable=line-too-long
60 ('get', {'method': 'GET', 'resource': True, 'status': 200, 'version': APIVersion
.DEFAULT
}),
61 ('delete', {'method': 'DELETE', 'resource': True, 'status': 204, 'version': APIVersion
.DEFAULT
}), # noqa E501 #pylint: disable=line-too-long
62 ('set', {'method': 'PUT', 'resource': True, 'status': 200, 'version': APIVersion
.DEFAULT
}),
63 ('singleton_set', {'method': 'PUT', 'resource': False, 'status': 200, 'version': APIVersion
.DEFAULT
}) # noqa E501 #pylint: disable=line-too-long
67 def infer_resource_id(cls
):
68 if cls
.RESOURCE_ID
is not None:
69 return cls
.RESOURCE_ID
.split('/')
70 for k
, v
in cls
._method
_mapping
.items():
71 func
= getattr(cls
, k
, None)
72 while hasattr(func
, "__wrapped__"):
74 func
= func
.__wrapped
__
75 if v
['resource'] and func
:
76 path_params
= cls
.get_path_param_names()
77 params
= _get_function_params(func
)
78 return [p
['name'] for p
in params
79 if p
['required'] and p
['name'] not in path_params
]
84 result
= super().endpoints()
85 res_id_params
= cls
.infer_resource_id()
87 for name
, func
in inspect
.getmembers(cls
, predicate
=callable):
89 'no_resource_id_params': False,
94 'version': APIVersion
.DEFAULT
,
95 'sec_permissions': hasattr(func
, '_security_permissions'),
98 if name
in cls
._method
_mapping
:
99 cls
._update
_endpoint
_params
_method
_map
(
100 func
, res_id_params
, endpoint_params
, name
=name
)
102 elif hasattr(func
, "__collection_method__"):
103 cls
._update
_endpoint
_params
_collection
_map
(func
, endpoint_params
)
105 elif hasattr(func
, "__resource_method__"):
106 cls
._update
_endpoint
_params
_resource
_method
(
107 res_id_params
, endpoint_params
, func
)
112 if endpoint_params
['no_resource_id_params']:
113 raise TypeError("Could not infer the resource ID parameters for"
114 " method {} of controller {}. "
115 "Please specify the resource ID parameters "
116 "using the RESOURCE_ID class property"
117 .format(func
.__name
__, cls
.__name
__))
119 if endpoint_params
['method'] in ['GET', 'DELETE']:
120 params
= _get_function_params(func
)
121 if res_id_params
is None:
123 if endpoint_params
['query_params'] is None:
124 endpoint_params
['query_params'] = [p
['name'] for p
in params
# type: ignore
125 if p
['name'] not in res_id_params
]
127 func
= cls
._status
_code
_wrapper
(func
, endpoint_params
['status'])
128 endp_func
= Endpoint(endpoint_params
['method'], path
=endpoint_params
['path'],
129 query_params
=endpoint_params
['query_params'],
130 version
=endpoint_params
['version'])(func
) # type: ignore
131 if endpoint_params
['permission']:
132 _set_func_permissions(endp_func
, [endpoint_params
['permission']])
133 result
.append(cls
.Endpoint(cls
, endp_func
))
138 def _update_endpoint_params_resource_method(cls
, res_id_params
, endpoint_params
, func
):
139 if not res_id_params
:
140 endpoint_params
['no_resource_id_params'] = True
142 path_params
= ["{{{}}}".format(p
) for p
in res_id_params
]
143 endpoint_params
['path'] += "/{}".format("/".join(path_params
))
144 if func
.__resource
_method
__['path']:
145 endpoint_params
['path'] += func
.__resource
_method
__['path']
147 endpoint_params
['path'] += "/{}".format(func
.__name
__)
148 endpoint_params
['status'] = func
.__resource
_method
__['status']
149 endpoint_params
['method'] = func
.__resource
_method
__['method']
150 endpoint_params
['version'] = func
.__resource
_method
__['version']
151 endpoint_params
['query_params'] = func
.__resource
_method
__['query_params']
152 if not endpoint_params
['sec_permissions']:
153 endpoint_params
['permission'] = cls
._permission
_map
[endpoint_params
['method']]
156 def _update_endpoint_params_collection_map(cls
, func
, endpoint_params
):
157 if func
.__collection
_method
__['path']:
158 endpoint_params
['path'] = func
.__collection
_method
__['path']
160 endpoint_params
['path'] = "/{}".format(func
.__name
__)
161 endpoint_params
['status'] = func
.__collection
_method
__['status']
162 endpoint_params
['method'] = func
.__collection
_method
__['method']
163 endpoint_params
['query_params'] = func
.__collection
_method
__['query_params']
164 endpoint_params
['version'] = func
.__collection
_method
__['version']
165 if not endpoint_params
['sec_permissions']:
166 endpoint_params
['permission'] = cls
._permission
_map
[endpoint_params
['method']]
169 def _update_endpoint_params_method_map(cls
, func
, res_id_params
, endpoint_params
, name
=None):
170 meth
= cls
._method
_mapping
[func
.__name
__ if not name
else name
] # type: dict
173 if not res_id_params
:
174 endpoint_params
['no_resource_id_params'] = True
176 path_params
= ["{{{}}}".format(p
) for p
in res_id_params
]
177 endpoint_params
['path'] += "/{}".format("/".join(path_params
))
179 endpoint_params
['status'] = meth
['status']
180 endpoint_params
['method'] = meth
['method']
181 if hasattr(func
, "__method_map_method__"):
182 endpoint_params
['version'] = func
.__method
_map
_method
__['version']
183 if not endpoint_params
['sec_permissions']:
184 endpoint_params
['permission'] = cls
._permission
_map
[endpoint_params
['method']]
187 def _status_code_wrapper(cls
, func
, status_code
):
189 def wrapper(*vpath
, **params
):
190 cherrypy
.response
.status
= status_code
191 return func(*vpath
, **params
)
196 def Resource(method
=None, path
=None, status
=None, query_params
=None, # noqa: N802
197 version
: Optional
[APIVersion
] = APIVersion
.DEFAULT
):
205 func
.__resource
_method
__ = {
209 'query_params': query_params
,
216 def MethodMap(resource
=False, status
=None,
217 version
: Optional
[APIVersion
] = APIVersion
.DEFAULT
): # noqa: N802
223 func
.__method
_map
_method
__ = {
224 'resource': resource
,
232 def Collection(method
=None, path
=None, status
=None, query_params
=None, # noqa: N802
233 version
: Optional
[APIVersion
] = APIVersion
.DEFAULT
):
241 func
.__collection
_method
__ = {
245 'query_params': query_params
,