Implement APIGateway create_base_path_mapping (#4475)
This commit is contained in:
parent
98ca9b82e1
commit
2218806f3d
@ -22,11 +22,11 @@
|
||||
|
||||
## apigateway
|
||||
<details>
|
||||
<summary>48% implemented</summary>
|
||||
<summary>49% implemented</summary>
|
||||
|
||||
- [X] create_api_key
|
||||
- [X] create_authorizer
|
||||
- [ ] create_base_path_mapping
|
||||
- [X] create_base_path_mapping
|
||||
- [X] create_deployment
|
||||
- [ ] create_documentation_part
|
||||
- [ ] create_documentation_version
|
||||
@ -2716,29 +2716,24 @@
|
||||
|
||||
## lambda
|
||||
<details>
|
||||
<summary>41% implemented</summary>
|
||||
<summary>48% implemented</summary>
|
||||
|
||||
- [ ] add_layer_version_permission
|
||||
- [X] add_permission
|
||||
- [ ] create_alias
|
||||
- [ ] create_code_signing_config
|
||||
- [X] create_event_source_mapping
|
||||
- [X] create_function
|
||||
- [ ] delete_alias
|
||||
- [ ] delete_code_signing_config
|
||||
- [X] delete_event_source_mapping
|
||||
- [X] delete_function
|
||||
- [ ] delete_function_code_signing_config
|
||||
- [X] delete_function_concurrency
|
||||
- [ ] delete_function_event_invoke_config
|
||||
- [ ] delete_layer_version
|
||||
- [ ] delete_provisioned_concurrency_config
|
||||
- [ ] get_account_settings
|
||||
- [ ] get_alias
|
||||
- [ ] get_code_signing_config
|
||||
- [X] get_event_source_mapping
|
||||
- [X] get_function
|
||||
- [ ] get_function_code_signing_config
|
||||
- [X] get_function_concurrency
|
||||
- [ ] get_function_configuration
|
||||
- [ ] get_function_event_invoke_config
|
||||
@ -2750,11 +2745,9 @@
|
||||
- [X] invoke
|
||||
- [ ] invoke_async
|
||||
- [ ] list_aliases
|
||||
- [ ] list_code_signing_configs
|
||||
- [X] list_event_source_mappings
|
||||
- [ ] list_function_event_invoke_configs
|
||||
- [X] list_functions
|
||||
- [ ] list_functions_by_code_signing_config
|
||||
- [ ] list_layer_versions
|
||||
- [X] list_layers
|
||||
- [ ] list_provisioned_concurrency_configs
|
||||
@ -2762,7 +2755,6 @@
|
||||
- [X] list_versions_by_function
|
||||
- [X] publish_layer_version
|
||||
- [ ] publish_version
|
||||
- [ ] put_function_code_signing_config
|
||||
- [X] put_function_concurrency
|
||||
- [ ] put_function_event_invoke_config
|
||||
- [ ] put_provisioned_concurrency_config
|
||||
@ -2771,7 +2763,6 @@
|
||||
- [X] tag_resource
|
||||
- [X] untag_resource
|
||||
- [ ] update_alias
|
||||
- [ ] update_code_signing_config
|
||||
- [X] update_event_source_mapping
|
||||
- [X] update_function_code
|
||||
- [X] update_function_configuration
|
||||
|
@ -13,6 +13,10 @@ class AccessDeniedException(JsonRESTError):
|
||||
pass
|
||||
|
||||
|
||||
class ConflictException(JsonRESTError):
|
||||
code = 409
|
||||
|
||||
|
||||
class AwsProxyNotAllowed(BadRequestException):
|
||||
def __init__(self):
|
||||
super(AwsProxyNotAllowed, self).__init__(
|
||||
@ -225,3 +229,39 @@ class MethodNotFoundException(NotFoundException):
|
||||
super(MethodNotFoundException, self).__init__(
|
||||
"NotFoundException", "Invalid Method identifier specified"
|
||||
)
|
||||
|
||||
|
||||
class InvalidBasePathException(BadRequestException):
|
||||
code = 400
|
||||
|
||||
def __init__(self):
|
||||
super(InvalidBasePathException, self).__init__(
|
||||
"BadRequestException",
|
||||
"API Gateway V1 doesn't support the slash character (/) in base path mappings. "
|
||||
"To create a multi-level base path mapping, use API Gateway V2.",
|
||||
)
|
||||
|
||||
|
||||
class InvalidRestApiIdForBasePathMappingException(BadRequestException):
|
||||
code = 400
|
||||
|
||||
def __init__(self):
|
||||
super(InvalidRestApiIdForBasePathMappingException, self).__init__(
|
||||
"BadRequestException", "Invalid REST API identifier specified"
|
||||
)
|
||||
|
||||
|
||||
class InvalidStageException(BadRequestException):
|
||||
code = 400
|
||||
|
||||
def __init__(self):
|
||||
super(InvalidStageException, self).__init__(
|
||||
"BadRequestException", "Invalid stage identifier specified"
|
||||
)
|
||||
|
||||
|
||||
class BasePathConflictException(ConflictException):
|
||||
def __init__(self):
|
||||
super(BasePathConflictException, self).__init__(
|
||||
"ConflictException", "Base path already exists for this domain name"
|
||||
)
|
||||
|
@ -43,6 +43,10 @@ from .exceptions import (
|
||||
RequestValidatorNotFound,
|
||||
ModelNotFound,
|
||||
ApiKeyValueMinLength,
|
||||
InvalidBasePathException,
|
||||
InvalidRestApiIdForBasePathMappingException,
|
||||
InvalidStageException,
|
||||
BasePathConflictException,
|
||||
)
|
||||
from ..core.models import responses_mock
|
||||
from moto.apigateway.exceptions import MethodNotFoundException
|
||||
@ -1081,6 +1085,20 @@ class Model(BaseModel, dict):
|
||||
self["generateCliSkeleton"] = kwargs.get("generate_cli_skeleton")
|
||||
|
||||
|
||||
class BasePathMapping(BaseModel, dict):
|
||||
def __init__(self, domain_name, rest_api_id, **kwargs):
|
||||
super(BasePathMapping, self).__init__()
|
||||
self["domain_name"] = domain_name
|
||||
self["restApiId"] = rest_api_id
|
||||
if kwargs.get("basePath"):
|
||||
self["basePath"] = kwargs.get("basePath")
|
||||
else:
|
||||
self["basePath"] = "(none)"
|
||||
|
||||
if kwargs.get("stage"):
|
||||
self["stage"] = kwargs.get("stage")
|
||||
|
||||
|
||||
class APIGatewayBackend(BaseBackend):
|
||||
def __init__(self, region_name):
|
||||
super(APIGatewayBackend, self).__init__()
|
||||
@ -1091,6 +1109,7 @@ class APIGatewayBackend(BaseBackend):
|
||||
self.domain_names = {}
|
||||
self.models = {}
|
||||
self.region_name = region_name
|
||||
self.base_path_mappings = {}
|
||||
|
||||
def reset(self):
|
||||
region_name = self.region_name
|
||||
@ -1705,6 +1724,40 @@ class APIGatewayBackend(BaseBackend):
|
||||
restApi = self.get_rest_api(restapi_id)
|
||||
return restApi.update_request_validator(validator_id, patch_operations)
|
||||
|
||||
def create_base_path_mapping(
|
||||
self, domain_name, rest_api_id, base_path=None, stage=None
|
||||
):
|
||||
if domain_name not in self.domain_names:
|
||||
raise DomainNameNotFound()
|
||||
|
||||
if base_path and "/" in base_path:
|
||||
raise InvalidBasePathException()
|
||||
|
||||
if rest_api_id not in self.apis:
|
||||
raise InvalidRestApiIdForBasePathMappingException()
|
||||
|
||||
if stage and self.apis[rest_api_id].stages.get(stage) is None:
|
||||
raise InvalidStageException()
|
||||
|
||||
new_base_path_mapping = BasePathMapping(
|
||||
domain_name=domain_name,
|
||||
rest_api_id=rest_api_id,
|
||||
basePath=base_path,
|
||||
stage=stage,
|
||||
)
|
||||
|
||||
new_base_path = new_base_path_mapping.get("basePath")
|
||||
if self.base_path_mappings.get(domain_name) is None:
|
||||
self.base_path_mappings[domain_name] = {}
|
||||
else:
|
||||
if (
|
||||
self.base_path_mappings[domain_name].get(new_base_path)
|
||||
and new_base_path != "(none)"
|
||||
):
|
||||
raise BasePathConflictException()
|
||||
self.base_path_mappings[domain_name][new_base_path] = new_base_path_mapping
|
||||
return new_base_path_mapping
|
||||
|
||||
|
||||
apigateway_backends = {}
|
||||
for region_name in Session().get_available_regions("apigateway"):
|
||||
|
@ -22,6 +22,7 @@ from .exceptions import (
|
||||
NoIntegrationDefined,
|
||||
NoIntegrationResponseDefined,
|
||||
NotFoundException,
|
||||
ConflictException,
|
||||
)
|
||||
|
||||
API_KEY_SOURCES = ["AUTHORIZER", "HEADER"]
|
||||
@ -849,3 +850,27 @@ class APIGatewayResponse(BaseResponse):
|
||||
error.message, error.error_type
|
||||
),
|
||||
)
|
||||
|
||||
def base_path_mappings(self, request, full_url, headers):
|
||||
self.setup_class(request, full_url, headers)
|
||||
|
||||
url_path_parts = self.path.split("/")
|
||||
domain_name = url_path_parts[2]
|
||||
|
||||
try:
|
||||
if self.method == "GET":
|
||||
# TODO implements
|
||||
pass
|
||||
elif self.method == "POST":
|
||||
base_path = self._get_param("basePath")
|
||||
rest_api_id = self._get_param("restApiId")
|
||||
stage = self._get_param("stage")
|
||||
|
||||
base_path_mapping_resp = self.backend.create_base_path_mapping(
|
||||
domain_name, rest_api_id, base_path, stage,
|
||||
)
|
||||
return 201, {}, json.dumps(base_path_mapping_resp)
|
||||
except BadRequestException as e:
|
||||
return self.error("BadRequestException", e.message)
|
||||
except ConflictException as e:
|
||||
return self.error("ConflictException", e.message, 409)
|
||||
|
@ -27,6 +27,7 @@ url_paths = {
|
||||
"{0}/restapis/(?P<function_id>[^/]+)/models$": response.models,
|
||||
"{0}/restapis/(?P<function_id>[^/]+)/models/(?P<model_name>[^/]+)/?$": response.model_induvidual,
|
||||
"{0}/domainnames/(?P<domain_name>[^/]+)/?$": response.domain_name_induvidual,
|
||||
"{0}/domainnames/(?P<domain_name>[^/]+)/basepathmappings$": response.base_path_mappings,
|
||||
"{0}/usageplans/(?P<usage_plan_id>[^/]+)/?$": response.usage_plan_individual,
|
||||
"{0}/usageplans/(?P<usage_plan_id>[^/]+)/keys$": response.usage_plan_keys,
|
||||
"{0}/usageplans/(?P<usage_plan_id>[^/]+)/keys/(?P<api_key_id>[^/]+)/?$": response.usage_plan_key_individual,
|
||||
|
@ -2349,3 +2349,156 @@ def test_delete_domain_name_unknown_domainname():
|
||||
"Invalid domain name identifier specified"
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("NotFoundException")
|
||||
|
||||
|
||||
@mock_apigateway
|
||||
def test_create_base_path_mapping():
|
||||
client = boto3.client("apigateway", region_name="us-west-2")
|
||||
domain_name = "testDomain"
|
||||
client.create_domain_name(
|
||||
domainName=domain_name,
|
||||
certificateName="test.certificate",
|
||||
certificatePrivateKey="testPrivateKey",
|
||||
)
|
||||
|
||||
response = client.create_rest_api(name="my_api", description="this is my api")
|
||||
api_id = response["id"]
|
||||
stage_name = "dev"
|
||||
create_method_integration(client, api_id)
|
||||
client.create_deployment(
|
||||
restApiId=api_id, stageName=stage_name, description="1.0.1"
|
||||
)
|
||||
|
||||
response = client.create_base_path_mapping(domainName=domain_name, restApiId=api_id)
|
||||
|
||||
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(201)
|
||||
response["basePath"].should.equal("(none)")
|
||||
response["restApiId"].should.equal(api_id)
|
||||
response.should_not.have.key("stage")
|
||||
|
||||
response = client.create_base_path_mapping(
|
||||
domainName=domain_name, restApiId=api_id, stage=stage_name
|
||||
)
|
||||
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(201)
|
||||
response["basePath"].should.equal("(none)")
|
||||
response["restApiId"].should.equal(api_id)
|
||||
response["stage"].should.equal(stage_name)
|
||||
|
||||
response = client.create_base_path_mapping(
|
||||
domainName=domain_name, restApiId=api_id, stage=stage_name, basePath="v1"
|
||||
)
|
||||
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(201)
|
||||
response["basePath"].should.equal("v1")
|
||||
response["restApiId"].should.equal(api_id)
|
||||
response["stage"].should.equal(stage_name)
|
||||
|
||||
|
||||
@mock_apigateway
|
||||
def test_create_base_path_mapping_with_unknown_api():
|
||||
client = boto3.client("apigateway", region_name="us-west-2")
|
||||
domain_name = "testDomain"
|
||||
client.create_domain_name(
|
||||
domainName=domain_name,
|
||||
certificateName="test.certificate",
|
||||
certificatePrivateKey="testPrivateKey",
|
||||
)
|
||||
|
||||
with pytest.raises(ClientError) as ex:
|
||||
client.create_base_path_mapping(
|
||||
domainName=domain_name, restApiId="none-exists-api"
|
||||
)
|
||||
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"Invalid REST API identifier specified"
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("BadRequestException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
|
||||
|
||||
@mock_apigateway
|
||||
def test_create_base_path_mapping_with_invalid_base_path():
|
||||
client = boto3.client("apigateway", region_name="us-west-2")
|
||||
domain_name = "testDomain"
|
||||
client.create_domain_name(
|
||||
domainName=domain_name,
|
||||
certificateName="test.certificate",
|
||||
certificatePrivateKey="testPrivateKey",
|
||||
)
|
||||
|
||||
response = client.create_rest_api(name="my_api", description="this is my api")
|
||||
api_id = response["id"]
|
||||
stage_name = "dev"
|
||||
create_method_integration(client, api_id)
|
||||
client.create_deployment(
|
||||
restApiId=api_id, stageName=stage_name, description="1.0.1"
|
||||
)
|
||||
|
||||
with pytest.raises(ClientError) as ex:
|
||||
client.create_base_path_mapping(
|
||||
domainName=domain_name, restApiId=api_id, basePath="/v1"
|
||||
)
|
||||
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"API Gateway V1 doesn't support the slash character (/) in base path mappings. "
|
||||
"To create a multi-level base path mapping, use API Gateway V2."
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("BadRequestException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
|
||||
|
||||
@mock_apigateway
|
||||
def test_create_base_path_mapping_with_unknown_stage():
|
||||
client = boto3.client("apigateway", region_name="us-west-2")
|
||||
domain_name = "testDomain"
|
||||
client.create_domain_name(
|
||||
domainName=domain_name,
|
||||
certificateName="test.certificate",
|
||||
certificatePrivateKey="testPrivateKey",
|
||||
)
|
||||
|
||||
response = client.create_rest_api(name="my_api", description="this is my api")
|
||||
api_id = response["id"]
|
||||
stage_name = "dev"
|
||||
create_method_integration(client, api_id)
|
||||
client.create_deployment(
|
||||
restApiId=api_id, stageName=stage_name, description="1.0.1"
|
||||
)
|
||||
|
||||
with pytest.raises(ClientError) as ex:
|
||||
client.create_base_path_mapping(
|
||||
domainName=domain_name, restApiId=api_id, stage="unknown-stage"
|
||||
)
|
||||
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"Invalid stage identifier specified"
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("BadRequestException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
|
||||
|
||||
@mock_apigateway
|
||||
def test_create_base_path_mapping_with_duplicate_base_path():
|
||||
client = boto3.client("apigateway", region_name="us-west-2")
|
||||
domain_name = "testDomain"
|
||||
client.create_domain_name(
|
||||
domainName=domain_name,
|
||||
certificateName="test.certificate",
|
||||
certificatePrivateKey="testPrivateKey",
|
||||
)
|
||||
|
||||
response = client.create_rest_api(name="my_api", description="this is my api")
|
||||
api_id = response["id"]
|
||||
base_path = "v1"
|
||||
client.create_base_path_mapping(
|
||||
domainName=domain_name, restApiId=api_id, basePath=base_path
|
||||
)
|
||||
with pytest.raises(ClientError) as ex:
|
||||
client.create_base_path_mapping(
|
||||
domainName=domain_name, restApiId=api_id, basePath=base_path
|
||||
)
|
||||
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"Base path already exists for this domain name"
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("ConflictException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(409)
|
||||
|
Loading…
Reference in New Issue
Block a user