Feature: APIGateway Management API (#6588)
This commit is contained in:
parent
14f9c3d38a
commit
deb914fc54
@ -202,6 +202,15 @@
|
||||
- [ ] update_vpc_link
|
||||
</details>
|
||||
|
||||
## apigatewaymanagementapi
|
||||
<details>
|
||||
<summary>100% implemented</summary>
|
||||
|
||||
- [X] delete_connection
|
||||
- [X] get_connection
|
||||
- [X] post_to_connection
|
||||
</details>
|
||||
|
||||
## apigatewayv2
|
||||
<details>
|
||||
<summary>75% implemented</summary>
|
||||
@ -7246,7 +7255,6 @@
|
||||
- amplify
|
||||
- amplifybackend
|
||||
- amplifyuibuilder
|
||||
- apigatewaymanagementapi
|
||||
- appconfigdata
|
||||
- appfabric
|
||||
- appflow
|
||||
|
33
docs/docs/services/apigatewaymanagementapi.rst
Normal file
33
docs/docs/services/apigatewaymanagementapi.rst
Normal file
@ -0,0 +1,33 @@
|
||||
.. _implementedservice_apigatewaymanagementapi:
|
||||
|
||||
.. |start-h3| raw:: html
|
||||
|
||||
<h3>
|
||||
|
||||
.. |end-h3| raw:: html
|
||||
|
||||
</h3>
|
||||
|
||||
=======================
|
||||
apigatewaymanagementapi
|
||||
=======================
|
||||
|
||||
.. autoclass:: moto.apigatewaymanagementapi.models.ApiGatewayManagementApiBackend
|
||||
|
||||
|start-h3| Example usage |end-h3|
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
@mock_apigatewaymanagementapi
|
||||
def test_apigatewaymanagementapi_behaviour:
|
||||
boto3.client("apigatewaymanagementapi")
|
||||
...
|
||||
|
||||
|
||||
|
||||
|start-h3| Implemented features for this service |end-h3|
|
||||
|
||||
- [X] delete_connection
|
||||
- [X] get_connection
|
||||
- [X] post_to_connection
|
||||
|
@ -29,6 +29,9 @@ mock_acm = lazy_load(".acm", "mock_acm")
|
||||
mock_acmpca = lazy_load(".acmpca", "mock_acmpca", boto3_name="acm-pca")
|
||||
mock_amp = lazy_load(".amp", "mock_amp")
|
||||
mock_apigateway = lazy_load(".apigateway", "mock_apigateway")
|
||||
mock_apigatewaymanagementapi = lazy_load(
|
||||
".apigatewaymanagementapi", "mock_apigatewaymanagementapi"
|
||||
)
|
||||
mock_apigatewayv2 = lazy_load(".apigatewayv2", "mock_apigatewayv2")
|
||||
mock_appconfig = lazy_load(".appconfig", "mock_appconfig")
|
||||
mock_appsync = lazy_load(".appsync", "mock_appsync")
|
||||
|
5
moto/apigatewaymanagementapi/__init__.py
Normal file
5
moto/apigatewaymanagementapi/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""apigatewaymanagementapi module initialization; sets value for base decorator."""
|
||||
from .models import apigatewaymanagementapi_backends
|
||||
from ..core.models import base_decorator
|
||||
|
||||
mock_apigatewaymanagementapi = base_decorator(apigatewaymanagementapi_backends)
|
48
moto/apigatewaymanagementapi/models.py
Normal file
48
moto/apigatewaymanagementapi/models.py
Normal file
@ -0,0 +1,48 @@
|
||||
"""ApiGatewayManagementApiBackend class with methods for supported APIs."""
|
||||
from collections import defaultdict
|
||||
from typing import Any, Dict
|
||||
from moto.core import BaseBackend, BackendDict
|
||||
from moto.core.utils import unix_time
|
||||
|
||||
|
||||
class Connection:
|
||||
def __init__(self) -> None:
|
||||
self.connected_at = unix_time()
|
||||
self.source_ip = "192.168.0.1"
|
||||
self.user_agent = "Moto Mocks"
|
||||
self.data = b""
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"connectedAt": self.connected_at,
|
||||
"lastActiveAt": unix_time(),
|
||||
"identity": {
|
||||
"sourceIp": self.source_ip,
|
||||
"userAgent": self.user_agent,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ApiGatewayManagementApiBackend(BaseBackend):
|
||||
"""
|
||||
Connecting to this API in ServerMode/Docker requires Python >= 3.8 and an up-to-date `werkzeug` version (>=2.3.x)
|
||||
"""
|
||||
|
||||
def __init__(self, region_name: str, account_id: str):
|
||||
super().__init__(region_name, account_id)
|
||||
self.connections: Dict[str, Connection] = defaultdict(Connection)
|
||||
|
||||
def delete_connection(self, connection_id: str) -> None:
|
||||
self.connections.pop(connection_id, None)
|
||||
|
||||
def get_connection(self, connection_id: str) -> Connection:
|
||||
return self.connections[connection_id]
|
||||
|
||||
def post_to_connection(self, data: bytes, connection_id: str) -> None:
|
||||
cnctn = self.get_connection(connection_id)
|
||||
cnctn.data += data
|
||||
|
||||
|
||||
apigatewaymanagementapi_backends = BackendDict(
|
||||
ApiGatewayManagementApiBackend, "apigateway"
|
||||
)
|
46
moto/apigatewaymanagementapi/responses.py
Normal file
46
moto/apigatewaymanagementapi/responses.py
Normal file
@ -0,0 +1,46 @@
|
||||
"""Handles incoming apigatewaymanagementapi requests, invokes methods, returns responses."""
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from moto.core.responses import BaseResponse
|
||||
from .models import apigatewaymanagementapi_backends, ApiGatewayManagementApiBackend
|
||||
|
||||
|
||||
class ApiGatewayManagementApiResponse(BaseResponse):
|
||||
"""Handler for ApiGatewayManagementApi requests and responses."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(service_name="apigatewaymanagementapi")
|
||||
|
||||
def setup_class(
|
||||
self, request: Any, full_url: str, headers: Any, use_raw_body: bool = False
|
||||
) -> None:
|
||||
super().setup_class(request, full_url, headers, use_raw_body=True)
|
||||
|
||||
@property
|
||||
def apigatewaymanagementapi_backend(self) -> ApiGatewayManagementApiBackend:
|
||||
"""Return backend instance specific for this region."""
|
||||
return apigatewaymanagementapi_backends[self.current_account][self.region]
|
||||
|
||||
def delete_connection(self) -> str:
|
||||
connection_id = self.path.split("/@connections/")[-1]
|
||||
self.apigatewaymanagementapi_backend.delete_connection(
|
||||
connection_id=connection_id
|
||||
)
|
||||
return "{}"
|
||||
|
||||
def get_connection(self) -> str:
|
||||
connection_id = self.path.split("/@connections/")[-1]
|
||||
connection = self.apigatewaymanagementapi_backend.get_connection(
|
||||
connection_id=connection_id
|
||||
)
|
||||
return json.dumps(connection.to_dict())
|
||||
|
||||
def post_to_connection(self) -> str:
|
||||
connection_id = self.path.split("/@connections/")[-1]
|
||||
data = self.body
|
||||
self.apigatewaymanagementapi_backend.post_to_connection(
|
||||
data=data,
|
||||
connection_id=connection_id,
|
||||
)
|
||||
return "{}"
|
12
moto/apigatewaymanagementapi/urls.py
Normal file
12
moto/apigatewaymanagementapi/urls.py
Normal file
@ -0,0 +1,12 @@
|
||||
"""apigatewaymanagementapi base URL and path."""
|
||||
from .responses import ApiGatewayManagementApiResponse
|
||||
|
||||
url_bases = [r"https?://execute-api\.(.+)\.amazonaws\.com"]
|
||||
|
||||
|
||||
response = ApiGatewayManagementApiResponse()
|
||||
|
||||
|
||||
url_paths = {
|
||||
"{0}/@connections/(?P<connection_id>[^/]+)$": response.dispatch,
|
||||
}
|
@ -6,6 +6,10 @@ backend_url_patterns = [
|
||||
("acm-pca", re.compile("https?://acm-pca\\.(.+)\\.amazonaws\\.com")),
|
||||
("amp", re.compile("https?://aps\\.(.+)\\.amazonaws\\.com")),
|
||||
("apigateway", re.compile("https?://apigateway\\.(.+)\\.amazonaws.com")),
|
||||
(
|
||||
"apigatewaymanagementapi",
|
||||
re.compile("https?://execute-api\\.(.+)\\.amazonaws\\.com"),
|
||||
),
|
||||
("appconfig", re.compile("https?://appconfig\\.(.+)\\.amazonaws\\.com")),
|
||||
(
|
||||
"applicationautoscaling",
|
||||
@ -110,12 +114,9 @@ backend_url_patterns = [
|
||||
),
|
||||
(
|
||||
"meteringmarketplace",
|
||||
re.compile("https?://metering.marketplace\\.(.+)\\.amazonaws.com"),
|
||||
),
|
||||
(
|
||||
"meteringmarketplace",
|
||||
re.compile("https?://aws-marketplace\\.(.+)\\.amazonaws.com"),
|
||||
re.compile("https?://metering.marketplace.(.+).amazonaws.com"),
|
||||
),
|
||||
("meteringmarketplace", re.compile("https?://aws-marketplace.(.+).amazonaws.com")),
|
||||
("mq", re.compile("https?://mq\\.(.+)\\.amazonaws\\.com")),
|
||||
("opsworks", re.compile("https?://opsworks\\.us-east-1\\.amazonaws.com")),
|
||||
("organizations", re.compile("https?://organizations\\.(.+)\\.amazonaws\\.com")),
|
||||
|
@ -100,7 +100,14 @@ class DomainDispatcherApplication:
|
||||
try:
|
||||
credential_scope = auth.split(",")[0].split()[1]
|
||||
_, _, region, service, _ = credential_scope.split("/")
|
||||
service = SIGNING_ALIASES.get(service.lower(), service)
|
||||
path = environ.get("PATH_INFO", "")
|
||||
if service.lower() == "execute-api" and path.startswith(
|
||||
"/@connections"
|
||||
):
|
||||
# APIGateway Management API
|
||||
pass
|
||||
else:
|
||||
service = SIGNING_ALIASES.get(service.lower(), service)
|
||||
service = service.lower()
|
||||
except ValueError:
|
||||
# Signature format does not match, this is exceptional and we can't
|
||||
|
0
tests/test_apigatewaymanagementapi/__init__.py
Normal file
0
tests/test_apigatewaymanagementapi/__init__.py
Normal file
@ -0,0 +1,56 @@
|
||||
"""Unit tests for apigatewaymanagementapi-supported APIs."""
|
||||
import boto3
|
||||
|
||||
from moto import mock_apigatewaymanagementapi, settings
|
||||
from moto.core.versions import is_werkzeug_2_3_x
|
||||
from moto.apigatewaymanagementapi.models import apigatewaymanagementapi_backends
|
||||
from tests import DEFAULT_ACCOUNT_ID
|
||||
from unittest import SkipTest
|
||||
|
||||
# See our Development Tips on writing tests for hints on how to write good tests:
|
||||
# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
|
||||
|
||||
|
||||
@mock_apigatewaymanagementapi
|
||||
def test_delete_connection():
|
||||
if settings.TEST_SERVER_MODE and not is_werkzeug_2_3_x():
|
||||
# URL matching changed between 2.2.x and 2.3.x
|
||||
# 2.3.x has no problem matching the root path '/@connections', but 2.2.x refuses
|
||||
raise SkipTest("Can't test this in older werkzeug versions")
|
||||
client = boto3.client("apigatewaymanagementapi", region_name="eu-west-1")
|
||||
# NO-OP
|
||||
client.delete_connection(ConnectionId="anything")
|
||||
|
||||
|
||||
@mock_apigatewaymanagementapi
|
||||
def test_get_connection():
|
||||
if settings.TEST_SERVER_MODE and not is_werkzeug_2_3_x():
|
||||
# URL matching changed between 2.2.x and 2.3.x
|
||||
# 2.3.x has no problem matching the root path '/@connections', but 2.2.x refuses
|
||||
raise SkipTest("Can't test this in older werkzeug versions")
|
||||
client = boto3.client("apigatewaymanagementapi", region_name="us-east-2")
|
||||
conn = client.get_connection(ConnectionId="anything")
|
||||
|
||||
assert "ConnectedAt" in conn
|
||||
assert conn["Identity"] == {"SourceIp": "192.168.0.1", "UserAgent": "Moto Mocks"}
|
||||
assert "LastActiveAt" in conn
|
||||
|
||||
|
||||
@mock_apigatewaymanagementapi
|
||||
def test_post_to_connection():
|
||||
if settings.TEST_SERVER_MODE and not is_werkzeug_2_3_x():
|
||||
# URL matching changed between 2.2.x and 2.3.x
|
||||
# 2.3.x has no problem matching the root path '/@connections', but 2.2.x refuses
|
||||
raise SkipTest("Can't test this in older werkzeug versions")
|
||||
client = boto3.client("apigatewaymanagementapi", region_name="ap-southeast-1")
|
||||
client.post_to_connection(ConnectionId="anything", Data=b"my first bytes")
|
||||
|
||||
if not settings.TEST_SERVER_MODE:
|
||||
backend = apigatewaymanagementapi_backends[DEFAULT_ACCOUNT_ID]["ap-southeast-1"]
|
||||
assert backend.connections["anything"].data == b"my first bytes"
|
||||
|
||||
client.post_to_connection(ConnectionId="anything", Data=b"more bytes")
|
||||
|
||||
if not settings.TEST_SERVER_MODE:
|
||||
backend = apigatewaymanagementapi_backends[DEFAULT_ACCOUNT_ID]["ap-southeast-1"]
|
||||
assert backend.connections["anything"].data == b"my first bytesmore bytes"
|
Loading…
Reference in New Issue
Block a user