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 | return description |
7 | |
8 | |
9 | -def get_api_landing_page(): |
10 | - """Return the API landing page""" |
11 | - description = { |
12 | - "title": "MAAS API", |
13 | - "description": "API landing page for MAAS", |
14 | - "resources": [ |
15 | - { |
16 | - "path": "/MAAS/api", |
17 | - "rel": "self", |
18 | - "type": "application/json", |
19 | - "title": "this document", |
20 | - }, |
21 | - { |
22 | - "path": "/MAAS/api/2.0/openapi.yaml", |
23 | - "rel": "service-desc", |
24 | - "type": "application/openapi+yaml", |
25 | - "title": "the API definition", |
26 | - }, |
27 | - { |
28 | - "path": "/MAAS/docs/api.html", |
29 | - "rel": "service-doc", |
30 | - "type": "text/html", |
31 | - "title": "the API documentation", |
32 | - }, |
33 | - ], |
34 | - } |
35 | - return description |
36 | - |
37 | - |
38 | def find_api_resources(urlconf=None): |
39 | """Find the API resources defined in `urlconf`. |
40 | |
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 | generate_pod_types_doc, |
47 | generate_power_types_doc, |
48 | get_api_description, |
49 | - get_api_landing_page, |
50 | ) |
51 | from maasserver.api.templates import APITemplateRenderer |
52 | from maasserver.utils import build_absolute_uri |
53 | @@ -243,19 +242,3 @@ def describe(request): |
54 | json.dumps(description, sort_keys=True), |
55 | content_type="application/json", |
56 | ) |
57 | - |
58 | - |
59 | -def api_landing_page(request): |
60 | - """Render a landing page with pointers for the MAAS API. |
61 | - |
62 | - :return: An `HttpResponse` containing a JSON page with pointers to both |
63 | - human-readable documentation and api definitions. |
64 | - """ |
65 | - description = get_api_landing_page() |
66 | - for link in description["resources"]: |
67 | - link["href"] = build_absolute_uri(request, link["path"]) |
68 | - # Return as a JSON document |
69 | - return HttpResponse( |
70 | - json.dumps(description), |
71 | - content_type="application/json", |
72 | - ) |
73 | diff --git a/src/maasserver/api/doc_oapi.py b/src/maasserver/api/doc_oapi.py |
74 | 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 | +# Copyright 2014-2022 Canonical Ltd. This software is licensed under the |
80 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
81 | + |
82 | +import json |
83 | + |
84 | +from django.http import HttpResponse |
85 | +import yaml |
86 | + |
87 | +from maasserver.utils import build_absolute_uri |
88 | + |
89 | + |
90 | +def landing_page(request): |
91 | + """Render a landing page with pointers for the MAAS API. |
92 | + |
93 | + :return: An `HttpResponse` containing a JSON page with pointers to both |
94 | + human-readable documentation and api definitions. |
95 | + """ |
96 | + description = get_api_landing_page() |
97 | + for link in description["resources"]: |
98 | + link["href"] = build_absolute_uri(request, link["path"]) |
99 | + # Return as a JSON document |
100 | + return HttpResponse( |
101 | + json.dumps(description), |
102 | + content_type="application/json", |
103 | + ) |
104 | + |
105 | + |
106 | +def endpoint(request): |
107 | + """Render the OpenApi endpoint. |
108 | + |
109 | + :return: An `HttpResponse` containing a YAML document that complies |
110 | + with the OpenApi spec 3.0. |
111 | + """ |
112 | + description = get_api_endpoint() |
113 | + doc = description["externalDocs"] |
114 | + doc["url"] = build_absolute_uri(request, doc["url"]) |
115 | + # Return as a YAML document |
116 | + return HttpResponse( |
117 | + yaml.dump(description), |
118 | + content_type="application/yaml", |
119 | + ) |
120 | + |
121 | + |
122 | +def get_api_landing_page(): |
123 | + """Return the API landing page""" |
124 | + description = { |
125 | + "title": "MAAS API", |
126 | + "description": "API landing page for MAAS", |
127 | + "resources": [ |
128 | + { |
129 | + "path": "/MAAS/api", |
130 | + "rel": "self", |
131 | + "type": "application/json", |
132 | + "title": "this document", |
133 | + }, |
134 | + { |
135 | + "path": "/MAAS/api/2.0/openapi.yaml", |
136 | + "rel": "service-desc", |
137 | + "type": "application/openapi+yaml", |
138 | + "title": "the API definition", |
139 | + }, |
140 | + { |
141 | + "path": "/MAAS/docs/api.html", |
142 | + "rel": "service-doc", |
143 | + "type": "text/html", |
144 | + "title": "the API documentation", |
145 | + }, |
146 | + ], |
147 | + } |
148 | + return description |
149 | + |
150 | + |
151 | +def get_api_endpoint(): |
152 | + """Return the API endpoint""" |
153 | + description = { |
154 | + "openapi": "3.0.0", |
155 | + "info": {"title": "MAAS OpenApi Endpoint", "version": "1.0.0"}, |
156 | + "paths": [], |
157 | + "externalDocs": { |
158 | + "description": "MAAS API documentation", |
159 | + "url": "/MAAS/docs/api.html", |
160 | + }, |
161 | + } |
162 | + 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 | MatchesStructure, |
169 | Not, |
170 | ) |
171 | +import yaml |
172 | |
173 | from maasserver.api import doc as doc_module |
174 | from maasserver.api.doc import ( |
175 | @@ -41,7 +42,8 @@ from maasserver.api.doc import ( |
176 | generate_power_types_doc, |
177 | get_api_description, |
178 | ) |
179 | -from maasserver.api.doc_handler import api_landing_page, render_api_docs |
180 | +from maasserver.api.doc_handler import render_api_docs |
181 | +from maasserver.api.doc_oapi import endpoint, landing_page |
182 | from maasserver.api.support import ( |
183 | operation, |
184 | OperationsHandler, |
185 | @@ -83,14 +85,28 @@ class TestGetAPIDescription(MAASTestCase): |
186 | class TestLandingPage(MAASTestCase): |
187 | def test_links(self): |
188 | request = factory.make_fake_request() |
189 | - landing_page = api_landing_page(request) |
190 | - content = json.loads(landing_page.content) |
191 | + page = landing_page(request) |
192 | + content = json.loads(page.content) |
193 | resources = content["resources"] |
194 | host = request.get_host() |
195 | for link in resources: |
196 | href = f"http://{host}{link['path']}" |
197 | self.assertEqual(link["href"], href) |
198 | - self.assertEqual(resources[0]["type"], landing_page["content-type"]) |
199 | + self.assertEqual(resources[0]["type"], page["content-type"]) |
200 | + |
201 | + |
202 | +class TestApiEndpoint(MAASTestCase): |
203 | + def test_required_fields(self): |
204 | + request = factory.make_fake_request() |
205 | + page = endpoint(request) |
206 | + content = yaml.safe_load(page.content) |
207 | + self.assertIn("openapi", content) |
208 | + self.assertIn("info", content) |
209 | + self.assertIn("paths", content) |
210 | + info = content["info"] |
211 | + self.assertIsInstance(info, dict) |
212 | + self.assertIn("title", info) |
213 | + self.assertIn("version", info) |
214 | |
215 | |
216 | 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 | from django.views.generic import TemplateView |
223 | |
224 | from maasserver import urls_api |
225 | -from maasserver.api.doc_handler import api_landing_page |
226 | +from maasserver.api.doc_oapi import landing_page |
227 | from maasserver.bootresources import ( |
228 | simplestreams_file_handler, |
229 | simplestreams_stream_handler, |
230 | @@ -77,7 +77,7 @@ urlpatterns += [re_path(r"^accounts/logout/$", logout, name="logout")] |
231 | |
232 | # API URLs. If old API requested, provide error message directing to new API. |
233 | urlpatterns += [ |
234 | - re_path(r"^api/$", api_landing_page), |
235 | + re_path(r"^api/$", landing_page), |
236 | re_path(r"^api/2\.0/", include(urls_api)), |
237 | re_path( |
238 | 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 | ) |
245 | from maasserver.api.dnsresources import DNSResourceHandler, DNSResourcesHandler |
246 | from maasserver.api.doc_handler import describe |
247 | +from maasserver.api.doc_oapi import endpoint |
248 | from maasserver.api.domains import DomainHandler, DomainsHandler |
249 | from maasserver.api.events import EventsHandler |
250 | from maasserver.api.fabrics import FabricHandler, FabricsHandler |
251 | @@ -330,11 +331,11 @@ license_keys_handler = AdminRestrictedResource( |
252 | LicenseKeysHandler, authentication=api_auth |
253 | ) |
254 | |
255 | - |
256 | # API URLs accessible to anonymous users. |
257 | urlpatterns = [ |
258 | re_path(r"describe/$", describe, name="describe"), |
259 | re_path(r"version/$", version_handler, name="version_handler"), |
260 | + re_path(r"openapi.yaml$", endpoint, name="openapi_endpoint"), |
261 | ] |
262 | |
263 |
UNIT TESTS
-b OpenApi-Endpoint lp:~lloydwaltersj/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS b6559865300372e 3afafca5b4
COMMIT: 631a5f07824410f