Merge ~lloydwaltersj/maas:missing-response into maas:master
- Git
- lp:~lloydwaltersj/maas
- missing-response
- Merge into master
Status: | Merged |
---|---|
Approved by: | Jack Lloyd-Walters |
Approved revision: | 54c3fcc4efc1ea4d29f205f379f2b86752ec93d6 |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~lloydwaltersj/maas:missing-response |
Merge into: | maas:master |
Diff against target: |
153 lines (+59/-8) 3 files modified
src/maasserver/api/doc_oapi.py (+42/-6) src/maasserver/api/machines.py (+15/-0) src/maasserver/management/commands/tests/test_generate_oapi.py (+2/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
MAAS Lander | Approve | ||
Adam Collard (community) | Approve | ||
Review via email: mp+428924@code.launchpad.net |
Commit message
Generate responses for API operations.
Description of the change
OAS3 requires all operations present some responses, but some of our API operations do not have any defined.
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b missing-response lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: e56068f4d787c75
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b missing-response lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: 0e830a1cb0d1a7b
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b missing-response lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: 372c110118966ff
Jack Lloyd-Walters (lloydwaltersj) wrote : | # |
jenkins: !test
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b missing-response lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: 372c110118966ff
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b missing-response lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: 7c9ee43b86e273e
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b missing-response lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: 7c628c8d3c41171
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b missing-response lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: f17159fcb1e073f
Adam Collard (adam-collard) wrote : | # |
Let's clean this up with the @deprecated work but get it landed for now
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b missing-response lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: b7abfa15244a4b7
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b missing-response lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: e9f2306ed446856
Adam Collard (adam-collard) : | # |
Jack Lloyd-Walters (lloydwaltersj) : | # |
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b missing-response lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: 42977b1868fab13
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b missing-response lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: b8594841bc15b78
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b missing-response lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: 54c3fcc4efc1ea4
MAAS Lander (maas-lander) wrote : | # |
LANDING
-b missing-response lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED BUILD
LOG: http://
Preview Diff
1 | diff --git a/src/maasserver/api/doc_oapi.py b/src/maasserver/api/doc_oapi.py | |||
2 | index 25922cd..c0d0212 100644 | |||
3 | --- a/src/maasserver/api/doc_oapi.py | |||
4 | +++ b/src/maasserver/api/doc_oapi.py | |||
5 | @@ -7,7 +7,7 @@ This definition follows the rules and limitations of the ReST documentation. | |||
6 | 7 | (see doc.py and doc_handler.py). | 7 | (see doc.py and doc_handler.py). |
7 | 8 | """ | 8 | """ |
8 | 9 | 9 | ||
10 | 10 | from inspect import getdoc | 10 | from inspect import getdoc, signature |
11 | 11 | import json | 11 | import json |
12 | 12 | import re | 12 | import re |
13 | 13 | from textwrap import dedent | 13 | from textwrap import dedent |
14 | @@ -144,7 +144,9 @@ def _prettify(doc): | |||
15 | 144 | return re.sub("(?<![.\n])[\n]+", " ", dedent(doc)).strip() | 144 | return re.sub("(?<![.\n])[\n]+", " ", dedent(doc)).strip() |
16 | 145 | 145 | ||
17 | 146 | 146 | ||
19 | 147 | def _render_oapi_oper_item(http_method, op, doc, uri_params, function): | 147 | def _render_oapi_oper_item( |
20 | 148 | http_method, op, doc, uri_params, function, resources | ||
21 | 149 | ): | ||
22 | 148 | oper_id = op or support.OperationsResource.crudmap.get(http_method) | 150 | oper_id = op or support.OperationsResource.crudmap.get(http_method) |
23 | 149 | oper_obj = { | 151 | oper_obj = { |
24 | 150 | "operationId": f"{doc.name}_{oper_id}", | 152 | "operationId": f"{doc.name}_{oper_id}", |
25 | @@ -154,14 +156,16 @@ def _render_oapi_oper_item(http_method, op, doc, uri_params, function): | |||
26 | 154 | "responses": {}, | 156 | "responses": {}, |
27 | 155 | } | 157 | } |
28 | 156 | oper_docstring = _oapi_item_from_docstring( | 158 | oper_docstring = _oapi_item_from_docstring( |
30 | 157 | function, http_method, uri_params | 159 | function, http_method, uri_params, doc, resources |
31 | 158 | ) | 160 | ) |
32 | 159 | # Only overwrite the values that are non-blank | 161 | # Only overwrite the values that are non-blank |
33 | 160 | oper_obj.update({k: v for k, v in oper_docstring.items() if v}) | 162 | oper_obj.update({k: v for k, v in oper_docstring.items() if v}) |
34 | 161 | return oper_obj | 163 | return oper_obj |
35 | 162 | 164 | ||
36 | 163 | 165 | ||
38 | 164 | def _oapi_item_from_docstring(function, http_method, uri_params): | 166 | def _oapi_item_from_docstring( |
39 | 167 | function, http_method, uri_params, doc, resources | ||
40 | 168 | ): | ||
41 | 165 | def _type_to_string(schema): | 169 | def _type_to_string(schema): |
42 | 166 | match schema: | 170 | match schema: |
43 | 167 | case "Boolean": | 171 | case "Boolean": |
44 | @@ -187,6 +191,9 @@ def _oapi_item_from_docstring(function, http_method, uri_params): | |||
45 | 187 | paired.extend([status, content]) | 191 | paired.extend([status, content]) |
46 | 188 | else: | 192 | else: |
47 | 189 | content = response | 193 | content = response |
48 | 194 | # edge case where a response is not given in the docstring | ||
49 | 195 | if paired == [] and content: | ||
50 | 196 | paired.extend([content, content]) | ||
51 | 190 | paired = iter(paired) | 197 | paired = iter(paired) |
52 | 191 | return zip(paired, paired) | 198 | return zip(paired, paired) |
53 | 192 | 199 | ||
54 | @@ -202,6 +209,8 @@ def _oapi_item_from_docstring(function, http_method, uri_params): | |||
55 | 202 | ap_dict = ap.get_dict() | 209 | ap_dict = ap.get_dict() |
56 | 203 | oper_obj["summary"] = ap_dict["description_title"].strip() | 210 | oper_obj["summary"] = ap_dict["description_title"].strip() |
57 | 204 | oper_obj["description"] = _prettify(ap_dict["description"]) | 211 | oper_obj["description"] = _prettify(ap_dict["description"]) |
58 | 212 | if "deprecated" in oper_obj["description"].lower(): | ||
59 | 213 | oper_obj["deprecated"] = True | ||
60 | 205 | for param in ap_dict["params"]: | 214 | for param in ap_dict["params"]: |
61 | 206 | description = _prettify(param["description_stripped"]) | 215 | description = _prettify(param["description_stripped"]) |
62 | 207 | name = param["name"].strip("}{") | 216 | name = param["name"].strip("}{") |
63 | @@ -268,11 +277,38 @@ def _oapi_item_from_docstring(function, http_method, uri_params): | |||
64 | 268 | 277 | ||
65 | 269 | status_code = status["name"] | 278 | status_code = status["name"] |
66 | 270 | if not status_code.isdigit(): | 279 | if not status_code.isdigit(): |
68 | 271 | status_code = _prettify(status["description_stripped"]) | 280 | status_code = ( |
69 | 281 | "200" if "success" in status_code.lower() else "404" | ||
70 | 282 | ) | ||
71 | 272 | oper_obj.setdefault("responses", {}).update( | 283 | oper_obj.setdefault("responses", {}).update( |
72 | 273 | {status_code: response}, | 284 | {status_code: response}, |
73 | 274 | ) | 285 | ) |
74 | 275 | 286 | ||
75 | 287 | # populate deprecated functions properties using their replacement | ||
76 | 288 | if ( | ||
77 | 289 | replacement_method := getattr(function, "deprecated", function) | ||
78 | 290 | ) is not function: | ||
79 | 291 | oper_obj["deprecated"] = True | ||
80 | 292 | oper_obj["responses"] = _oapi_item_from_docstring( | ||
81 | 293 | replacement_method, http_method, uri_params, doc, resources | ||
82 | 294 | )["responses"] | ||
83 | 295 | |||
84 | 296 | # if a response is still empty, query the function | ||
85 | 297 | if not oper_obj.get("responses"): | ||
86 | 298 | # fetch a response by calling the function | ||
87 | 299 | status, msg = "200", "" | ||
88 | 300 | try: | ||
89 | 301 | msg = function(*[""] * len(signature(function).parameters)) | ||
90 | 302 | except Exception as e: | ||
91 | 303 | status = "404" | ||
92 | 304 | msg = str(e) | ||
93 | 305 | oper_obj["responses"] = { | ||
94 | 306 | status: { | ||
95 | 307 | "content": {"text/plain": {"schema": {"type": "string"}}}, | ||
96 | 308 | "description": msg, | ||
97 | 309 | } | ||
98 | 310 | } | ||
99 | 311 | |||
100 | 276 | if body.get("properties"): | 312 | if body.get("properties"): |
101 | 277 | oper_obj.update( | 313 | oper_obj.update( |
102 | 278 | { | 314 | { |
103 | @@ -317,6 +353,6 @@ def _render_oapi_paths(): | |||
104 | 317 | _new_path_item(params), | 353 | _new_path_item(params), |
105 | 318 | ) | 354 | ) |
106 | 319 | path_item[http_method.lower()] = _render_oapi_oper_item( | 355 | path_item[http_method.lower()] = _render_oapi_oper_item( |
108 | 320 | http_method, op, doc, params, function | 356 | http_method, op, doc, params, function, resources |
109 | 321 | ) | 357 | ) |
110 | 322 | return paths | 358 | return paths |
111 | diff --git a/src/maasserver/api/machines.py b/src/maasserver/api/machines.py | |||
112 | index b6e92c6..93175c4 100644 | |||
113 | --- a/src/maasserver/api/machines.py | |||
114 | +++ b/src/maasserver/api/machines.py | |||
115 | @@ -669,6 +669,21 @@ class MachineHandler(NodeHandler, WorkloadAnnotationsMixin, PowerMixin): | |||
116 | 669 | 669 | ||
117 | 670 | @operation(idempotent=True) | 670 | @operation(idempotent=True) |
118 | 671 | def get_token(self, request, system_id): | 671 | def get_token(self, request, system_id): |
119 | 672 | """@description-title Get a machine token | ||
120 | 673 | |||
121 | 674 | @param (string) "{system_id}" [required=true] The machines' system_id. | ||
122 | 675 | |||
123 | 676 | @success (http-status-code) "200" 200 | ||
124 | 677 | @success (json) "success-json" A JSON object containing information | ||
125 | 678 | about the machine token. | ||
126 | 679 | @success-example "success-json" [exkey=machines-placeholder] | ||
127 | 680 | placeholder text | ||
128 | 681 | |||
129 | 682 | @error (http-status-code) "404" 404 | ||
130 | 683 | @error (content) "not-found" The requested machine is not found. | ||
131 | 684 | @error-example "not-found" | ||
132 | 685 | No Machine matches the given query. | ||
133 | 686 | """ | ||
134 | 672 | node = self.model.objects.get_node_or_404( | 687 | node = self.model.objects.get_node_or_404( |
135 | 673 | system_id=system_id, | 688 | system_id=system_id, |
136 | 674 | user=request.user, | 689 | user=request.user, |
137 | diff --git a/src/maasserver/management/commands/tests/test_generate_oapi.py b/src/maasserver/management/commands/tests/test_generate_oapi.py | |||
138 | index 2c49814..1431de1 100644 | |||
139 | --- a/src/maasserver/management/commands/tests/test_generate_oapi.py | |||
140 | +++ b/src/maasserver/management/commands/tests/test_generate_oapi.py | |||
141 | @@ -10,10 +10,10 @@ from django.core.management import call_command | |||
142 | 10 | import yaml | 10 | import yaml |
143 | 11 | 11 | ||
144 | 12 | from maasserver.api.doc_oapi import get_api_endpoint | 12 | from maasserver.api.doc_oapi import get_api_endpoint |
146 | 13 | from maastesting.testcase import MAASTestCase | 13 | from maasserver.testing.testcase import MAASServerTestCase |
147 | 14 | 14 | ||
148 | 15 | 15 | ||
150 | 16 | class TestOAPIDoc(MAASTestCase): | 16 | class TestOAPIDoc(MAASServerTestCase): |
151 | 17 | def test_generate_spec(self): | 17 | def test_generate_spec(self): |
152 | 18 | spec = get_api_endpoint() | 18 | spec = get_api_endpoint() |
153 | 19 | spec["externalDocs"]["url"] = "https://maas.io/docs" | 19 | spec["externalDocs"]["url"] = "https://maas.io/docs" |
Merge conflicts