8000 introduce plugins to load openapi specs by giograno · Pull Request #11497 · localstack/localstack · GitHub
[go: up one dir, main page]

Skip to content

introduce plugins to load openapi specs #11497

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 20 additions & 13 deletions localstack-core/localstack/aws/handlers/validation.py
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, this handler and the plugin definition deserves quite a lot more documentation. It's a cool new feature where only a very small amount of our contributors know about. Please make sure that everyone can take a look at this file and knows what it does, and how one can interact with it (enable it, disable it, register OpenAPI Spec plugins,...). ;)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i second alex' comment here and think it can do with even a bit more doc. specifically i think it needs a more concrete example on how to use it. where do i put the openapi.yml? how do i correctly instantiate a new plugin? For some inspiration, maybe the docs on the WebAppExtension can help.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import logging
import os
from pathlib import Path

from openapi_core import OpenAPI
from openapi_core.contrib.werkzeug import WerkzeugOpenAPIRequest, WerkzeugOpenAPIResponse
Expand All @@ -13,28 +12,34 @@
RequestValidationError,
)
from openapi_core.validation.response.exceptions import ResponseValidationError
from plux import PluginManager

from localstack import config
from localstack.aws.api import RequestContext
from localstack.aws.chain import Handler, HandlerChain
from localstack.constants import INTERNAL_RESOURCE_PATH
from localstack.http import Response
from localstack.plugins import OASPlugin

LOG = logging.getLogger(__name__)


# TODO: replace with from importlib.resources.files when https://github.com/python/importlib_resources/issues/311 is
# resolved. Import from a namespace package is broken when installing in editable mode.
oas_path = os.path.join(os.path.dirname(__file__), "..", "..", "openapi.yaml")
class CoreOASPlugin(OASPlugin):
name = "localstack.core"

def __init__(self):
path = os.path.join(os.path.dirname(__file__), "..", "..", "openapi.yaml")
super().__init__(path)


class OpenAPIValidator(Handler):
openapi: "OpenAPI"
open_apis: list["OpenAPI"]

def __init__(self) -> None:
path = Path(oas_path)
assert path.exists()
self.openapi = OpenAPI.from_path(path)
specs = PluginManager("localstack.openapi.spec").load_all()
self.open_apis = []
for spec in specs:
self.open_apis.append(OpenAPI.from_path(spec.spec_path))


class OpenAPIRequestValidator(OpenAPIValidator):
Expand All @@ -51,7 +56,8 @@ def __call__(self, chain: HandlerChain, context: RequestContext, response: Respo

if path.startswith(f"{INTERNAL_RESOURCE_PATH}/") or path.startswith("/_aws/"):
try:
self.openapi.validate_request(WerkzeugOpenAPIRequest(context.request))
for openapi in self.open_apis:
openapi.validate_request(WerkzeugOpenAPIRequest(context.request))
except RequestValidationError as e:
# Note: in this handler we only check validation errors, e.g., wrong body, missing required in the body.
response.status_code = 400
Expand All @@ -77,10 +83,11 @@ def __call__(self, chain: HandlerChain, context: RequestContext, response: Respo

if path.startswith(f"{INTERNAL_RESOURCE_PATH}/") or path.startswith("/_aws/"):
try:
self.openapi.validate_response(
WerkzeugOpenAPIRequest(context.request),
WerkzeugOpenAPIResponse(response),
)
for openapi in self.open_apis:
openapi.validate_response(
WerkzeugOpenAPIRequest(context.request),
WerkzeugOpenAPIResponse(response),
)
except ResponseValidationError as exc:
LOG.error("Response validation failed for %s: $s", path, exc)
response.status_code = 500
Expand Down
19 changes: 19 additions & 0 deletions localstack-core/localstack/plugins.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import logging
import os
from pathlib import Path

import yaml
from plux import Plugin

from localstack import config
from localstack.runtime import hooks
Expand All @@ -21,3 +26,17 @@ def delete_cached_certificate():
LOG.debug("Removing the cached local SSL certificate")
target_file = get_cert_pem_file_path()
rm_rf(target_file)


class OASPlugin(Plugin):
namespace = "localstack.openapi.spec"

def __init__(self, spec_path: os.PathLike | str) -> None:
if isinstance(spec_path, str):
spec_path = Path(spec_path)
self.spec_path = spec_path
self.spec = {}

def load(self):
with self.spec_path.open("r") as f:
self.spec = yaml.safe_load(f)
Loading
0