Merge "Bootstrap magnum OpenAPI build"
This commit is contained in:
commit
02d674d362
codegenerator
zuul.d
@ -25,6 +25,9 @@ from codegenerator.common.schema import SpecSchema
|
||||
from codegenerator.metadata.base import MetadataBase
|
||||
from codegenerator.metadata.baremetal import BaremetalMetadata
|
||||
from codegenerator.metadata.block_storage import BlockStorageMetadata
|
||||
from codegenerator.metadata.container_infrastructure_management import (
|
||||
ContainerInfrastructureManagementMetadata,
|
||||
)
|
||||
from codegenerator.metadata.compute import ComputeMetadata
|
||||
from codegenerator.metadata.dns import DnsMetadata
|
||||
from codegenerator.metadata.identity import IdentityMetadata
|
||||
@ -55,6 +58,7 @@ SERVICE_METADATA_MAP: dict[str, ty.Type[MetadataBase]] = {
|
||||
"block-storage": BlockStorageMetadata,
|
||||
"volume": BlockStorageMetadata,
|
||||
"compute": ComputeMetadata,
|
||||
"container-infrastructure-management": ContainerInfrastructureManagementMetadata,
|
||||
"dns": DnsMetadata,
|
||||
"identity": IdentityMetadata,
|
||||
"image": ImageMetadata,
|
||||
|
@ -0,0 +1,33 @@
|
||||
# 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.
|
||||
#
|
||||
import typing as ty
|
||||
|
||||
from codegenerator.types import OperationModel
|
||||
from codegenerator.metadata.base import MetadataBase
|
||||
|
||||
|
||||
class ContainerInfrastructureManagementMetadata(MetadataBase):
|
||||
@staticmethod
|
||||
def get_operation_key(
|
||||
operation, path: str, method: str, resource_name: str
|
||||
) -> ty.Tuple[str | None, bool]:
|
||||
skip: bool = False
|
||||
operation_key: str | None = None
|
||||
|
||||
return (operation_key, skip)
|
||||
|
||||
@staticmethod
|
||||
def post_process_operation(
|
||||
resource_name: str, operation_name: str, operation
|
||||
):
|
||||
return operation
|
@ -285,8 +285,8 @@ class OpenStackServerSourceBase:
|
||||
|
||||
versioned_methods = {}
|
||||
controller_actions = {}
|
||||
framework = None
|
||||
if hasattr(controller, "controller"):
|
||||
# framework = None
|
||||
if hasattr(controller, "controller") and framework != "pecan":
|
||||
# wsgi
|
||||
framework = "wsgi"
|
||||
contr = controller.controller
|
||||
@ -304,9 +304,13 @@ class OpenStackServerSourceBase:
|
||||
# Pecan base app
|
||||
framework = "pecan"
|
||||
contr = controller
|
||||
if hasattr(controller, "versioned_methods"):
|
||||
versioned_methods = contr.versioned_methods
|
||||
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported controller {controller}")
|
||||
raise RuntimeError(
|
||||
f"Unsupported controller {controller} {framework}"
|
||||
)
|
||||
# logging.debug("Actions: %s, Versioned methods: %s", actions, versioned_methods)
|
||||
|
||||
# path_spec = openapi_spec.paths.setdefault(path, PathSchema())
|
||||
@ -317,7 +321,6 @@ class OpenStackServerSourceBase:
|
||||
if path_elements and VERSION_RE.match(path_elements[0]):
|
||||
path_elements.pop(0)
|
||||
operation_tags = self._get_tags_for_url(path)
|
||||
print(f"tags={operation_tags} for {path}")
|
||||
|
||||
# Build path parameters (/foo/{foo_id}/bar/{id} => $foo_id, $foo_bar_id)
|
||||
# Since for same path we are here multiple times check presence of
|
||||
@ -745,7 +748,14 @@ class OpenStackServerSourceBase:
|
||||
body_spec = getattr(fdef, "body_type", None)
|
||||
if body_spec:
|
||||
body_schema = _convert_wsme_to_jsonschema(body_spec)
|
||||
schema_name = body_spec.__name__
|
||||
if hasattr(body_spec, "__name__"):
|
||||
schema_name = body_spec.__name__
|
||||
else:
|
||||
schema_name = (
|
||||
"".join([x.title() for x in path_resource_names])
|
||||
+ func.__name__.title()
|
||||
+ "Request"
|
||||
)
|
||||
openapi_spec.components.schemas.setdefault(
|
||||
schema_name, TypeSchema(**body_schema)
|
||||
)
|
||||
|
277
codegenerator/openapi/magnum.py
Normal file
277
codegenerator/openapi/magnum.py
Normal file
@ -0,0 +1,277 @@
|
||||
# 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.
|
||||
#
|
||||
import inspect
|
||||
import logging
|
||||
from multiprocessing import Process
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest import mock
|
||||
|
||||
import fixtures
|
||||
|
||||
from codegenerator.common.schema import SpecSchema
|
||||
from codegenerator.common.schema import TypeSchema
|
||||
from codegenerator.openapi.base import (
|
||||
OpenStackServerSourceBase,
|
||||
_convert_wsme_to_jsonschema,
|
||||
)
|
||||
from codegenerator.openapi.utils import merge_api_ref_doc
|
||||
|
||||
from ruamel.yaml.scalarstring import LiteralScalarString
|
||||
|
||||
|
||||
class MagnumGenerator(OpenStackServerSourceBase):
|
||||
URL_TAG_MAP = {}
|
||||
|
||||
def _api_ver_major(self, ver):
|
||||
return ver.ver_major
|
||||
|
||||
def _api_ver_minor(self, ver):
|
||||
return ver.ver_minor
|
||||
|
||||
def _api_ver(self, ver):
|
||||
return (ver.ver_major, ver.ver_minor)
|
||||
|
||||
def _build_routes(self, mapper, node, path=""):
|
||||
if hasattr(node, "versioned_methods"):
|
||||
resource = None
|
||||
parent = path.split("/")[-1]
|
||||
# Construct resource name from the path
|
||||
if parent.endswith("ies"):
|
||||
resource = parent[0 : len(parent) - 3] + "y"
|
||||
else:
|
||||
resource = parent[0:-1]
|
||||
for method, vers in node.versioned_methods.items():
|
||||
url = path
|
||||
if method == "post":
|
||||
conditions = {"method": ["POST"]}
|
||||
elif method == "patch":
|
||||
conditions = {"method": ["PATCH"]}
|
||||
url += f"/{{{resource}_id}}"
|
||||
elif method == "delete":
|
||||
conditions = {"method": ["DELETE"]}
|
||||
elif method == "get":
|
||||
conditions = {"method": ["GET"]}
|
||||
else:
|
||||
conditions = {"method": ["POST"]}
|
||||
if method in getattr(node, "_custom_actions", []):
|
||||
url += f"/{method}"
|
||||
conditions = {
|
||||
"method": getattr(node, "_custom_actions")[method]
|
||||
}
|
||||
|
||||
mapper.connect(
|
||||
url,
|
||||
controller=getattr(node, method),
|
||||
action=method,
|
||||
conditions=conditions,
|
||||
)
|
||||
|
||||
for part in dir(node):
|
||||
if part.startswith("_"):
|
||||
continue
|
||||
try:
|
||||
if callable(getattr(node, part)):
|
||||
# Iterate over functions to find what is exposed on the current
|
||||
# level
|
||||
# if part == "versioned_methods"
|
||||
obj = getattr(node, part)
|
||||
_pecan = getattr(obj, "_pecan", None)
|
||||
exposed = getattr(obj, "exposed", None)
|
||||
if _pecan and exposed:
|
||||
# Only whatever is pecan exposed is of interest
|
||||
conditions = {}
|
||||
action = None
|
||||
url = path
|
||||
resource = None
|
||||
parent = url.split("/")[-1]
|
||||
# Construct resource name from the path
|
||||
if parent.endswith("ies"):
|
||||
resource = parent[0 : len(parent) - 3] + "y"
|
||||
else:
|
||||
resource = parent[0:-1]
|
||||
# Identify the action from function name
|
||||
# https://pecan.readthedocs.io/en/latest/rest.html#url-mapping
|
||||
if part == "get_one":
|
||||
conditions["method"] = ["GET"]
|
||||
action = "show"
|
||||
url += f"/{{{resource}_id}}"
|
||||
elif part == "get_all":
|
||||
conditions["method"] = ["GET"]
|
||||
action = "list"
|
||||
elif part == "get":
|
||||
conditions["method"] = ["GET"]
|
||||
action = "get"
|
||||
# "Get" is tricky, it can be normal and root, so need to inspect params
|
||||
sig = inspect.signature(obj)
|
||||
for pname, pval in sig.parameters.items():
|
||||
if (
|
||||
"id" in pname
|
||||
and pval.default == pval.empty
|
||||
):
|
||||
url += f"/{{{resource}_id}}"
|
||||
elif part == "post":
|
||||
conditions["method"] = ["POST"]
|
||||
action = "create"
|
||||
# url += f"/{{{resource}_id}}"
|
||||
elif part == "put":
|
||||
conditions["method"] = ["PUT"]
|
||||
action = "update"
|
||||
url += f"/{{{resource}_id}}"
|
||||
elif part == "delete":
|
||||
conditions["method"] = ["DELETE"]
|
||||
action = "delete"
|
||||
url += f"/{{{resource}_id}}"
|
||||
|
||||
if action:
|
||||
# If we identified method as "interesting" register it into
|
||||
# the routes mapper
|
||||
mapper.connect(
|
||||
None,
|
||||
url,
|
||||
controller=obj,
|
||||
action=action,
|
||||
conditions=conditions,
|
||||
)
|
||||
# yield part
|
||||
except Exception as ex:
|
||||
logging.debug(f"method {part} is not callable due to {ex}")
|
||||
pass
|
||||
|
||||
if not hasattr(node, "__dict__"):
|
||||
return
|
||||
for subcontroller, v in node.__class__.__dict__.items():
|
||||
# Iterate over node attributes for subcontrollers
|
||||
if subcontroller.startswith("_"):
|
||||
continue
|
||||
|
||||
if subcontroller in ["__wrapped__", "__doc__"]:
|
||||
# Not underested in those
|
||||
continue
|
||||
subpath = f"{path}/{subcontroller}"
|
||||
self._build_routes(mapper, v, subpath)
|
||||
|
||||
return
|
||||
|
||||
def generate(self, target_dir, args):
|
||||
proc = Process(target=self._generate, args=[target_dir, args])
|
||||
proc.start()
|
||||
proc.join()
|
||||
if proc.exitcode != 0:
|
||||
raise RuntimeError("Error generating Magnum OpenAPI schema")
|
||||
|
||||
def _generate(self, target_dir, args):
|
||||
import pecan.testing
|
||||
from magnum.api.controllers import versions
|
||||
from magnum.api.controllers.v1 import Controller
|
||||
from magnum.api import app
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
self.min_api_version = versions.BASE_VER
|
||||
self.api_version = versions.CURRENT_MAX_VER
|
||||
|
||||
from pecan import make_app as pecan_make_app
|
||||
from routes import Mapper
|
||||
|
||||
work_dir = Path(target_dir)
|
||||
work_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
impl_path = Path(
|
||||
work_dir,
|
||||
"openapi_specs",
|
||||
"container-infrastructure-management",
|
||||
f"v{self.api_version}.yaml",
|
||||
)
|
||||
impl_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
openapi_spec = self.load_openapi(Path(impl_path))
|
||||
if not openapi_spec:
|
||||
openapi_spec = SpecSchema(
|
||||
info={
|
||||
"title": "OpenStack Container Managent Infrastructure API",
|
||||
"description": LiteralScalarString(
|
||||
"Container Management Infrastructure API provided by Magnum service"
|
||||
),
|
||||
"version": self.api_version,
|
||||
},
|
||||
openapi="3.1.0",
|
||||
security=[{"ApiKeyAuth": []}],
|
||||
components={
|
||||
"securitySchemes": {
|
||||
"ApiKeyAuth": {
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": "X-Auth-Token",
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
with mock.patch("pecan.request") as m:
|
||||
self.app = app.setup_app()
|
||||
self.root = self.app.application.app.root
|
||||
mapper = Mapper()
|
||||
|
||||
self._build_routes(mapper, self.root)
|
||||
|
||||
for route in mapper.matchlist:
|
||||
self._process_route(route, openapi_spec, framework="pecan")
|
||||
|
||||
if args.api_ref_src:
|
||||
merge_api_ref_doc(
|
||||
openapi_spec, args.api_ref_src, allow_strip_version=False
|
||||
)
|
||||
|
||||
self.dump_openapi(
|
||||
openapi_spec,
|
||||
Path(impl_path),
|
||||
args.validate,
|
||||
"container-infrastructure-management",
|
||||
)
|
||||
|
||||
lnk = Path(impl_path.parent, "v1.yaml")
|
||||
lnk.unlink(missing_ok=True)
|
||||
lnk.symlink_to(impl_path.name)
|
||||
|
||||
return impl_path
|
||||
|
||||
def _get_schema_ref(
|
||||
self,
|
||||
openapi_spec,
|
||||
name,
|
||||
description=None,
|
||||
schema_def=None,
|
||||
action_name=None,
|
||||
):
|
||||
schema: None = None
|
||||
ref: str | None
|
||||
mime_type: str | None = "application/json"
|
||||
|
||||
if name in [
|
||||
"LbaasLoadbalancersFailoverFailoverRequest",
|
||||
"OctaviaAmphoraeFailoverFailoverRequest",
|
||||
]:
|
||||
schema = openapi_spec.components.schemas.setdefault(
|
||||
name, TypeSchema(type="null")
|
||||
)
|
||||
ref = f"#/components/schemas/{name}"
|
||||
else:
|
||||
(ref, mime_type) = super()._get_schema_ref(
|
||||
openapi_spec,
|
||||
name,
|
||||
description,
|
||||
schema_def=schema_def,
|
||||
action_name=action_name,
|
||||
)
|
||||
|
||||
return (ref, mime_type)
|
@ -53,6 +53,11 @@ class OpenApiSchemaGenerator(BaseGenerator):
|
||||
|
||||
OctaviaGenerator().generate(target_dir, args)
|
||||
|
||||
def generate_magnum(self, target_dir, args):
|
||||
from codegenerator.openapi.magnum import MagnumGenerator
|
||||
|
||||
MagnumGenerator().generate(target_dir, args)
|
||||
|
||||
def generate_neutron(self, target_dir, args):
|
||||
from codegenerator.openapi.neutron import NeutronGenerator
|
||||
|
||||
@ -97,6 +102,12 @@ class OpenApiSchemaGenerator(BaseGenerator):
|
||||
self.generate_ironic(target_dir, args)
|
||||
elif args.service_type in ["block-storage", "volume"]:
|
||||
self.generate_cinder(target_dir, args)
|
||||
elif args.service_type in [
|
||||
"container-infrastructure-management",
|
||||
"container-infrastructure",
|
||||
"container-infra",
|
||||
]:
|
||||
self.generate_magnum(target_dir, args)
|
||||
elif args.service_type == "dns":
|
||||
self.generate_designate(target_dir, args)
|
||||
elif args.service_type == "image":
|
||||
|
@ -120,6 +120,35 @@
|
||||
project: "opendev.org/openstack/nova"
|
||||
path: "/api-ref/build/html/index.html"
|
||||
|
||||
- job:
|
||||
name: codegenerator-openapi-container-infrastructure-management-tips
|
||||
parent: codegenerator-openapi-tips-base
|
||||
description: |
|
||||
Generate OpenAPI spec for Magnum
|
||||
required-projects:
|
||||
- name: openstack/magnum
|
||||
|
||||
vars:
|
||||
openapi_service: container-infrastructure-management
|
||||
install_additional_projects:
|
||||
- project: "opendev.org/openstack/magnum"
|
||||
name: "."
|
||||
|
||||
- job:
|
||||
name: codegenerator-openapi-container-infrastructure-management-tips-with-api-ref
|
||||
parent: codegenerator-openapi-container-infrastructure-management-tips
|
||||
description: |
|
||||
Generate OpenAPI spec for Magnum consuming API-REF
|
||||
required-projects:
|
||||
- name: openstack/magnum
|
||||
|
||||
pre-run:
|
||||
- playbooks/openapi/pre-api-ref.yaml
|
||||
vars:
|
||||
codegenerator_api_ref:
|
||||
project: "opendev.org/openstack/magnum"
|
||||
path: "/api-ref/build/html/index.html"
|
||||
|
||||
- job:
|
||||
name: codegenerator-openapi-dns-tips
|
||||
parent: codegenerator-openapi-tips-base
|
||||
@ -364,6 +393,8 @@
|
||||
soft: true
|
||||
- name: codegenerator-openapi-compute-tips-with-api-ref
|
||||
soft: true
|
||||
- name: codegenerator-openapi-container-infrastructure-management-tips-with-api-ref
|
||||
soft: true
|
||||
- name: codegenerator-openapi-dns-tips-with-api-ref
|
||||
soft: true
|
||||
- name: codegenerator-openapi-identity-tips-with-api-ref
|
||||
|
@ -9,6 +9,7 @@
|
||||
- codegenerator-openapi-baremetal-tips-with-api-ref
|
||||
- codegenerator-openapi-block-storage-tips-with-api-ref
|
||||
- codegenerator-openapi-compute-tips-with-api-ref
|
||||
- codegenerator-openapi-container-infrastructure-management-tips-with-api-ref
|
||||
- codegenerator-openapi-dns-tips-with-api-ref
|
||||
- codegenerator-openapi-identity-tips-with-api-ref
|
||||
- codegenerator-openapi-image-tips-with-api-ref
|
||||
@ -27,6 +28,7 @@
|
||||
- codegenerator-openapi-baremetal-tips-with-api-ref
|
||||
- codegenerator-openapi-block-storage-tips-with-api-ref
|
||||
- codegenerator-openapi-compute-tips-with-api-ref
|
||||
- codegenerator-openapi-container-infrastructure-management-tips-with-api-ref
|
||||
- codegenerator-openapi-dns-tips-with-api-ref
|
||||
- codegenerator-openapi-identity-tips-with-api-ref
|
||||
- codegenerator-openapi-image-tips-with-api-ref
|
||||
|
Loading…
x
Reference in New Issue
Block a user