Merge ~lloydwaltersj/maas:OpenApi-Endpoint into maas:master
- Git
- lp:~lloydwaltersj/maas
- OpenApi-Endpoint
- Merge into master
Status: | Merged |
---|---|
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~lloydwaltersj/maas:OpenApi-Endpoint |
Merge into: | maas:master |
Diff against target: |
261 lines (+108/-53) 6 files modified
src/maasserver/api/doc.py (+0/-29) src/maasserver/api/doc_handler.py (+0/-17) src/maasserver/api/doc_oapi.py (+84/-0) src/maasserver/api/tests/test_doc.py (+20/-4) src/maasserver/urls.py (+2/-2) src/maasserver/urls_api.py (+2/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Adam Collard (community) | Approve | ||
MAAS Lander | Approve | ||
Alberto Donato (community) | Needs Information | ||
Review via email: mp+427288@code.launchpad.net |
Commit message
Introduce OpenApi endpoint
Description of the change
Jack Lloyd-Walters (lloydwaltersj) : | # |
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b OpenApi-Endpoint lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: ddbc67b4b5e16c7
Jack Lloyd-Walters (lloydwaltersj) wrote : | # |
jenkins: !test
Alberto Donato (ack) wrote : | # |
looks mostly good, a couple of comments and a question inline
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b OpenApi-Endpoint lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: ddbc67b4b5e16c7
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b OpenApi-Endpoint lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: d0852abe819e5de
Alberto Donato (ack) : | # |
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b OpenApi-Endpoint lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: 4d1c04bdada5c89
Alexsander de Souza (alexsander-souza) wrote : | # |
let's move OpenAPI-specific functions to `doc_oapi.py`, keeping the ReST and the OpenAPI segregated
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b OpenApi-Endpoint lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: 82c175c5d16198d
Adam Collard (adam-collard) wrote : | # |
+1 with a question inline
Alexsander de Souza (alexsander-souza) : | # |
Preview Diff
1 | diff --git a/src/maasserver/api/doc.py b/src/maasserver/api/doc.py | |||
2 | index 4e5c3d4..6bc7ae2 100644 | |||
3 | --- a/src/maasserver/api/doc.py | |||
4 | +++ b/src/maasserver/api/doc.py | |||
5 | @@ -36,35 +36,6 @@ def get_api_description(): | |||
6 | 36 | return description | 36 | return description |
7 | 37 | 37 | ||
8 | 38 | 38 | ||
9 | 39 | def get_api_landing_page(): | ||
10 | 40 | """Return the API landing page""" | ||
11 | 41 | description = { | ||
12 | 42 | "title": "MAAS API", | ||
13 | 43 | "description": "API landing page for MAAS", | ||
14 | 44 | "resources": [ | ||
15 | 45 | { | ||
16 | 46 | "path": "/MAAS/api", | ||
17 | 47 | "rel": "self", | ||
18 | 48 | "type": "application/json", | ||
19 | 49 | "title": "this document", | ||
20 | 50 | }, | ||
21 | 51 | { | ||
22 | 52 | "path": "/MAAS/api/2.0/openapi.yaml", | ||
23 | 53 | "rel": "service-desc", | ||
24 | 54 | "type": "application/openapi+yaml", | ||
25 | 55 | "title": "the API definition", | ||
26 | 56 | }, | ||
27 | 57 | { | ||
28 | 58 | "path": "/MAAS/docs/api.html", | ||
29 | 59 | "rel": "service-doc", | ||
30 | 60 | "type": "text/html", | ||
31 | 61 | "title": "the API documentation", | ||
32 | 62 | }, | ||
33 | 63 | ], | ||
34 | 64 | } | ||
35 | 65 | return description | ||
36 | 66 | |||
37 | 67 | |||
38 | 68 | def find_api_resources(urlconf=None): | 39 | def find_api_resources(urlconf=None): |
39 | 69 | """Find the API resources defined in `urlconf`. | 40 | """Find the API resources defined in `urlconf`. |
40 | 70 | 41 | ||
41 | diff --git a/src/maasserver/api/doc_handler.py b/src/maasserver/api/doc_handler.py | |||
42 | index f8e5ba8..3dd41c6 100644 | |||
43 | --- a/src/maasserver/api/doc_handler.py | |||
44 | +++ b/src/maasserver/api/doc_handler.py | |||
45 | @@ -71,7 +71,6 @@ from maasserver.api.doc import ( | |||
46 | 71 | generate_pod_types_doc, | 71 | generate_pod_types_doc, |
47 | 72 | generate_power_types_doc, | 72 | generate_power_types_doc, |
48 | 73 | get_api_description, | 73 | get_api_description, |
49 | 74 | get_api_landing_page, | ||
50 | 75 | ) | 74 | ) |
51 | 76 | from maasserver.api.templates import APITemplateRenderer | 75 | from maasserver.api.templates import APITemplateRenderer |
52 | 77 | from maasserver.utils import build_absolute_uri | 76 | from maasserver.utils import build_absolute_uri |
53 | @@ -243,19 +242,3 @@ def describe(request): | |||
54 | 243 | json.dumps(description, sort_keys=True), | 242 | json.dumps(description, sort_keys=True), |
55 | 244 | content_type="application/json", | 243 | content_type="application/json", |
56 | 245 | ) | 244 | ) |
57 | 246 | |||
58 | 247 | |||
59 | 248 | def api_landing_page(request): | ||
60 | 249 | """Render a landing page with pointers for the MAAS API. | ||
61 | 250 | |||
62 | 251 | :return: An `HttpResponse` containing a JSON page with pointers to both | ||
63 | 252 | human-readable documentation and api definitions. | ||
64 | 253 | """ | ||
65 | 254 | description = get_api_landing_page() | ||
66 | 255 | for link in description["resources"]: | ||
67 | 256 | link["href"] = build_absolute_uri(request, link["path"]) | ||
68 | 257 | # Return as a JSON document | ||
69 | 258 | return HttpResponse( | ||
70 | 259 | json.dumps(description), | ||
71 | 260 | content_type="application/json", | ||
72 | 261 | ) | ||
73 | diff --git a/src/maasserver/api/doc_oapi.py b/src/maasserver/api/doc_oapi.py | |||
74 | 262 | new file mode 100644 | 245 | new file mode 100644 |
75 | index 0000000..d9dfe25 | |||
76 | --- /dev/null | |||
77 | +++ b/src/maasserver/api/doc_oapi.py | |||
78 | @@ -0,0 +1,84 @@ | |||
79 | 1 | # Copyright 2014-2022 Canonical Ltd. This software is licensed under the | ||
80 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
81 | 3 | |||
82 | 4 | import json | ||
83 | 5 | |||
84 | 6 | from django.http import HttpResponse | ||
85 | 7 | import yaml | ||
86 | 8 | |||
87 | 9 | from maasserver.utils import build_absolute_uri | ||
88 | 10 | |||
89 | 11 | |||
90 | 12 | def landing_page(request): | ||
91 | 13 | """Render a landing page with pointers for the MAAS API. | ||
92 | 14 | |||
93 | 15 | :return: An `HttpResponse` containing a JSON page with pointers to both | ||
94 | 16 | human-readable documentation and api definitions. | ||
95 | 17 | """ | ||
96 | 18 | description = get_api_landing_page() | ||
97 | 19 | for link in description["resources"]: | ||
98 | 20 | link["href"] = build_absolute_uri(request, link["path"]) | ||
99 | 21 | # Return as a JSON document | ||
100 | 22 | return HttpResponse( | ||
101 | 23 | json.dumps(description), | ||
102 | 24 | content_type="application/json", | ||
103 | 25 | ) | ||
104 | 26 | |||
105 | 27 | |||
106 | 28 | def endpoint(request): | ||
107 | 29 | """Render the OpenApi endpoint. | ||
108 | 30 | |||
109 | 31 | :return: An `HttpResponse` containing a YAML document that complies | ||
110 | 32 | with the OpenApi spec 3.0. | ||
111 | 33 | """ | ||
112 | 34 | description = get_api_endpoint() | ||
113 | 35 | doc = description["externalDocs"] | ||
114 | 36 | doc["url"] = build_absolute_uri(request, doc["url"]) | ||
115 | 37 | # Return as a YAML document | ||
116 | 38 | return HttpResponse( | ||
117 | 39 | yaml.dump(description), | ||
118 | 40 | content_type="application/yaml", | ||
119 | 41 | ) | ||
120 | 42 | |||
121 | 43 | |||
122 | 44 | def get_api_landing_page(): | ||
123 | 45 | """Return the API landing page""" | ||
124 | 46 | description = { | ||
125 | 47 | "title": "MAAS API", | ||
126 | 48 | "description": "API landing page for MAAS", | ||
127 | 49 | "resources": [ | ||
128 | 50 | { | ||
129 | 51 | "path": "/MAAS/api", | ||
130 | 52 | "rel": "self", | ||
131 | 53 | "type": "application/json", | ||
132 | 54 | "title": "this document", | ||
133 | 55 | }, | ||
134 | 56 | { | ||
135 | 57 | "path": "/MAAS/api/2.0/openapi.yaml", | ||
136 | 58 | "rel": "service-desc", | ||
137 | 59 | "type": "application/openapi+yaml", | ||
138 | 60 | "title": "the API definition", | ||
139 | 61 | }, | ||
140 | 62 | { | ||
141 | 63 | "path": "/MAAS/docs/api.html", | ||
142 | 64 | "rel": "service-doc", | ||
143 | 65 | "type": "text/html", | ||
144 | 66 | "title": "the API documentation", | ||
145 | 67 | }, | ||
146 | 68 | ], | ||
147 | 69 | } | ||
148 | 70 | return description | ||
149 | 71 | |||
150 | 72 | |||
151 | 73 | def get_api_endpoint(): | ||
152 | 74 | """Return the API endpoint""" | ||
153 | 75 | description = { | ||
154 | 76 | "openapi": "3.0.0", | ||
155 | 77 | "info": {"title": "MAAS OpenApi Endpoint", "version": "1.0.0"}, | ||
156 | 78 | "paths": [], | ||
157 | 79 | "externalDocs": { | ||
158 | 80 | "description": "MAAS API documentation", | ||
159 | 81 | "url": "/MAAS/docs/api.html", | ||
160 | 82 | }, | ||
161 | 83 | } | ||
162 | 84 | return description | ||
163 | diff --git a/src/maasserver/api/tests/test_doc.py b/src/maasserver/api/tests/test_doc.py | |||
164 | index cb9d061..ad02930 100644 | |||
165 | --- a/src/maasserver/api/tests/test_doc.py | |||
166 | +++ b/src/maasserver/api/tests/test_doc.py | |||
167 | @@ -26,6 +26,7 @@ from testtools.matchers import ( | |||
168 | 26 | MatchesStructure, | 26 | MatchesStructure, |
169 | 27 | Not, | 27 | Not, |
170 | 28 | ) | 28 | ) |
171 | 29 | import yaml | ||
172 | 29 | 30 | ||
173 | 30 | from maasserver.api import doc as doc_module | 31 | from maasserver.api import doc as doc_module |
174 | 31 | from maasserver.api.doc import ( | 32 | from maasserver.api.doc import ( |
175 | @@ -41,7 +42,8 @@ from maasserver.api.doc import ( | |||
176 | 41 | generate_power_types_doc, | 42 | generate_power_types_doc, |
177 | 42 | get_api_description, | 43 | get_api_description, |
178 | 43 | ) | 44 | ) |
180 | 44 | from maasserver.api.doc_handler import api_landing_page, render_api_docs | 45 | from maasserver.api.doc_handler import render_api_docs |
181 | 46 | from maasserver.api.doc_oapi import endpoint, landing_page | ||
182 | 45 | from maasserver.api.support import ( | 47 | from maasserver.api.support import ( |
183 | 46 | operation, | 48 | operation, |
184 | 47 | OperationsHandler, | 49 | OperationsHandler, |
185 | @@ -83,14 +85,28 @@ class TestGetAPIDescription(MAASTestCase): | |||
186 | 83 | class TestLandingPage(MAASTestCase): | 85 | class TestLandingPage(MAASTestCase): |
187 | 84 | def test_links(self): | 86 | def test_links(self): |
188 | 85 | request = factory.make_fake_request() | 87 | request = factory.make_fake_request() |
191 | 86 | landing_page = api_landing_page(request) | 88 | page = landing_page(request) |
192 | 87 | content = json.loads(landing_page.content) | 89 | content = json.loads(page.content) |
193 | 88 | resources = content["resources"] | 90 | resources = content["resources"] |
194 | 89 | host = request.get_host() | 91 | host = request.get_host() |
195 | 90 | for link in resources: | 92 | for link in resources: |
196 | 91 | href = f"http://{host}{link['path']}" | 93 | href = f"http://{host}{link['path']}" |
197 | 92 | self.assertEqual(link["href"], href) | 94 | self.assertEqual(link["href"], href) |
199 | 93 | self.assertEqual(resources[0]["type"], landing_page["content-type"]) | 95 | self.assertEqual(resources[0]["type"], page["content-type"]) |
200 | 96 | |||
201 | 97 | |||
202 | 98 | class TestApiEndpoint(MAASTestCase): | ||
203 | 99 | def test_required_fields(self): | ||
204 | 100 | request = factory.make_fake_request() | ||
205 | 101 | page = endpoint(request) | ||
206 | 102 | content = yaml.safe_load(page.content) | ||
207 | 103 | self.assertIn("openapi", content) | ||
208 | 104 | self.assertIn("info", content) | ||
209 | 105 | self.assertIn("paths", content) | ||
210 | 106 | info = content["info"] | ||
211 | 107 | self.assertIsInstance(info, dict) | ||
212 | 108 | self.assertIn("title", info) | ||
213 | 109 | self.assertIn("version", info) | ||
214 | 94 | 110 | ||
215 | 95 | 111 | ||
216 | 96 | class TestFindingResources(MAASTestCase): | 112 | class TestFindingResources(MAASTestCase): |
217 | diff --git a/src/maasserver/urls.py b/src/maasserver/urls.py | |||
218 | index 404dc11..c2cba09 100644 | |||
219 | --- a/src/maasserver/urls.py | |||
220 | +++ b/src/maasserver/urls.py | |||
221 | @@ -9,7 +9,7 @@ from django.urls import include, re_path | |||
222 | 9 | from django.views.generic import TemplateView | 9 | from django.views.generic import TemplateView |
223 | 10 | 10 | ||
224 | 11 | from maasserver import urls_api | 11 | from maasserver import urls_api |
226 | 12 | from maasserver.api.doc_handler import api_landing_page | 12 | from maasserver.api.doc_oapi import landing_page |
227 | 13 | from maasserver.bootresources import ( | 13 | from maasserver.bootresources import ( |
228 | 14 | simplestreams_file_handler, | 14 | simplestreams_file_handler, |
229 | 15 | simplestreams_stream_handler, | 15 | simplestreams_stream_handler, |
230 | @@ -77,7 +77,7 @@ urlpatterns += [re_path(r"^accounts/logout/$", logout, name="logout")] | |||
231 | 77 | 77 | ||
232 | 78 | # API URLs. If old API requested, provide error message directing to new API. | 78 | # API URLs. If old API requested, provide error message directing to new API. |
233 | 79 | urlpatterns += [ | 79 | urlpatterns += [ |
235 | 80 | re_path(r"^api/$", api_landing_page), | 80 | re_path(r"^api/$", landing_page), |
236 | 81 | re_path(r"^api/2\.0/", include(urls_api)), | 81 | re_path(r"^api/2\.0/", include(urls_api)), |
237 | 82 | re_path( | 82 | re_path( |
238 | 83 | r"^api/version/", | 83 | r"^api/version/", |
239 | diff --git a/src/maasserver/urls_api.py b/src/maasserver/urls_api.py | |||
240 | index bd7eb41..184263b 100644 | |||
241 | --- a/src/maasserver/urls_api.py | |||
242 | +++ b/src/maasserver/urls_api.py | |||
243 | @@ -37,6 +37,7 @@ from maasserver.api.dnsresourcerecords import ( | |||
244 | 37 | ) | 37 | ) |
245 | 38 | from maasserver.api.dnsresources import DNSResourceHandler, DNSResourcesHandler | 38 | from maasserver.api.dnsresources import DNSResourceHandler, DNSResourcesHandler |
246 | 39 | from maasserver.api.doc_handler import describe | 39 | from maasserver.api.doc_handler import describe |
247 | 40 | from maasserver.api.doc_oapi import endpoint | ||
248 | 40 | from maasserver.api.domains import DomainHandler, DomainsHandler | 41 | from maasserver.api.domains import DomainHandler, DomainsHandler |
249 | 41 | from maasserver.api.events import EventsHandler | 42 | from maasserver.api.events import EventsHandler |
250 | 42 | from maasserver.api.fabrics import FabricHandler, FabricsHandler | 43 | from maasserver.api.fabrics import FabricHandler, FabricsHandler |
251 | @@ -330,11 +331,11 @@ license_keys_handler = AdminRestrictedResource( | |||
252 | 330 | LicenseKeysHandler, authentication=api_auth | 331 | LicenseKeysHandler, authentication=api_auth |
253 | 331 | ) | 332 | ) |
254 | 332 | 333 | ||
255 | 333 | |||
256 | 334 | # API URLs accessible to anonymous users. | 334 | # API URLs accessible to anonymous users. |
257 | 335 | urlpatterns = [ | 335 | urlpatterns = [ |
258 | 336 | re_path(r"describe/$", describe, name="describe"), | 336 | re_path(r"describe/$", describe, name="describe"), |
259 | 337 | re_path(r"version/$", version_handler, name="version_handler"), | 337 | re_path(r"version/$", version_handler, name="version_handler"), |
260 | 338 | re_path(r"openapi.yaml$", endpoint, name="openapi_endpoint"), | ||
261 | 338 | ] | 339 | ] |
262 | 339 | 340 | ||
263 | 340 | 341 |
UNIT TESTS
-b OpenApi-Endpoint lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS b6559865300372e 3afafca5b4
COMMIT: 631a5f07824410f