basic implementation of update rest api (#3951)

* basic implementation of update rest api

* basic implementation of update rest api

* basic implementation of update rest api

* review comments from bblommers

Co-authored-by: rajinder saini <rajinder.saini@c02vt5k2htd6.corp.climate.com>
This commit is contained in:
rajinder 2021-05-23 09:09:02 -07:00 committed by GitHub
parent 290f6585c2
commit fbbc8fc472
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 148 additions and 29 deletions

View File

@ -376,7 +376,7 @@
- [ ] update_model
- [ ] update_request_validator
- [ ] update_resource
- [ ] update_rest_api
- [X] update_rest_api
- [X] update_stage
- [ ] update_usage
- [X] update_usage_plan

View File

@ -595,6 +595,8 @@ class RestAPI(CloudFormationModel):
self.region_name = region_name
self.name = name
self.description = description
self.version = kwargs.get("version") or "V1"
self.binaryMediaTypes = kwargs.get("binaryMediaTypes") or []
self.create_date = int(time.time())
self.api_key_source = kwargs.get("api_key_source") or "HEADER"
self.policy = kwargs.get("policy") or None
@ -602,7 +604,9 @@ class RestAPI(CloudFormationModel):
"types": ["EDGE"]
}
self.tags = kwargs.get("tags") or {}
self.disableExecuteApiEndpoint = (
kwargs.get("disableExecuteApiEndpoint") or False
)
self.deployments = {}
self.authorizers = {}
self.stages = {}
@ -618,13 +622,32 @@ class RestAPI(CloudFormationModel):
"id": self.id,
"name": self.name,
"description": self.description,
"version": self.version,
"binaryMediaTypes": self.binaryMediaTypes,
"createdDate": int(time.time()),
"apiKeySource": self.api_key_source,
"endpointConfiguration": self.endpoint_configuration,
"tags": self.tags,
"policy": self.policy,
"disableExecuteApiEndpoint": self.disableExecuteApiEndpoint,
}
def apply_patch_operations(self, patch_operations):
for op in patch_operations:
path = op["path"]
value = op["value"]
if op["op"] == "replace":
if "/name" in path:
self.name = value
if "/description" in path:
self.description = value
if "/apiKeySource" in path:
self.api_key_source = value
if "/binaryMediaTypes" in path:
self.binaryMediaTypes = value
if "/disableExecuteApiEndpoint" in path:
self.disableExecuteApiEndpoint = bool(value)
def get_cfn_attribute(self, attribute_name):
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
@ -905,6 +928,13 @@ class APIGatewayBackend(BaseBackend):
raise RestAPINotFound()
return rest_api
def update_rest_api(self, function_id, patch_operations):
rest_api = self.apis.get(function_id)
if rest_api is None:
raise RestAPINotFound()
self.apis[function_id].apply_patch_operations(patch_operations)
return self.apis[function_id]
def list_apis(self):
return self.apis.values()

View File

@ -39,6 +39,34 @@ class APIGatewayResponse(BaseResponse):
def backend(self):
return apigateway_backends[self.region]
def __validate_api_key_source(self, api_key_source):
if api_key_source and api_key_source not in API_KEY_SOURCES:
return self.error(
"ValidationException",
(
"1 validation error detected: "
"Value '{api_key_source}' at 'createRestApiInput.apiKeySource' failed "
"to satisfy constraint: Member must satisfy enum value set: "
"[AUTHORIZER, HEADER]"
).format(api_key_source=api_key_source),
)
def __validate_endpoint_configuration(self, endpoint_configuration):
if endpoint_configuration and "types" in endpoint_configuration:
invalid_types = list(
set(endpoint_configuration["types"]) - set(ENDPOINT_CONFIGURATION_TYPES)
)
if invalid_types:
return self.error(
"ValidationException",
(
"1 validation error detected: Value '{endpoint_type}' "
"at 'createRestApiInput.endpointConfiguration.types' failed "
"to satisfy constraint: Member must satisfy enum value set: "
"[PRIVATE, EDGE, REGIONAL]"
).format(endpoint_type=invalid_types[0]),
)
def restapis(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
@ -54,32 +82,13 @@ class APIGatewayResponse(BaseResponse):
policy = self._get_param("policy")
# Param validation
if api_key_source and api_key_source not in API_KEY_SOURCES:
return self.error(
"ValidationException",
(
"1 validation error detected: "
"Value '{api_key_source}' at 'createRestApiInput.apiKeySource' failed "
"to satisfy constraint: Member must satisfy enum value set: "
"[AUTHORIZER, HEADER]"
).format(api_key_source=api_key_source),
)
response = self.__validate_api_key_source(api_key_source)
if response is not None:
return response
if endpoint_configuration and "types" in endpoint_configuration:
invalid_types = list(
set(endpoint_configuration["types"])
- set(ENDPOINT_CONFIGURATION_TYPES)
)
if invalid_types:
return self.error(
"ValidationException",
(
"1 validation error detected: Value '{endpoint_type}' "
"at 'createRestApiInput.endpointConfiguration.types' failed "
"to satisfy constraint: Member must satisfy enum value set: "
"[PRIVATE, EDGE, REGIONAL]"
).format(endpoint_type=invalid_types[0]),
)
response = self.__validate_endpoint_configuration(endpoint_configuration)
if response is not None:
return response
rest_api = self.backend.create_rest_api(
name,
@ -91,16 +100,38 @@ class APIGatewayResponse(BaseResponse):
)
return 200, {}, json.dumps(rest_api.to_dict())
def __validte_rest_patch_operations(self, patch_operations):
for op in patch_operations:
path = op["path"]
value = op["value"]
if "apiKeySource" in path:
return self.__validate_api_key_source(value)
def restapis_individual(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
if self.method == "GET":
rest_api = self.backend.get_rest_api(function_id)
return 200, {}, json.dumps(rest_api.to_dict())
elif self.method == "DELETE":
rest_api = self.backend.delete_rest_api(function_id)
return 200, {}, json.dumps(rest_api.to_dict())
elif self.method == "PATCH":
patch_operations = self._get_param("patchOperations")
response = self.__validte_rest_patch_operations(patch_operations)
if response is not None:
return response
try:
rest_api = self.backend.update_rest_api(function_id, patch_operations)
except RestAPINotFound as error:
return (
error.code,
{},
'{{"message":"{0}","code":"{1}"}}'.format(
error.message, error.error_type
),
)
return 200, {}, json.dumps(rest_api.to_dict())
def resources(self, request, full_url, headers):
self.setup_class(request, full_url, headers)

View File

@ -31,13 +31,71 @@ def test_create_and_get_rest_api():
"id": api_id,
"name": "my_api",
"description": "this is my api",
"version": "V1",
"binaryMediaTypes": [],
"apiKeySource": "HEADER",
"endpointConfiguration": {"types": ["EDGE"]},
"tags": {},
"disableExecuteApiEndpoint": False,
}
)
@mock_apigateway
def test_upate_rest_api():
client = boto3.client("apigateway", region_name="us-west-2")
response = client.create_rest_api(name="my_api", description="this is my api")
api_id = response["id"]
patchOperations = [
{"op": "replace", "path": "/name", "value": "new-name"},
{"op": "replace", "path": "/description", "value": "new-description"},
{"op": "replace", "path": "/apiKeySource", "value": "AUTHORIZER"},
{"op": "replace", "path": "/binaryMediaTypes", "value": "image/jpeg"},
{"op": "replace", "path": "/disableExecuteApiEndpoint", "value": "True"},
]
response = client.update_rest_api(restApiId=api_id, patchOperations=patchOperations)
response.pop("ResponseMetadata")
response.pop("createdDate")
response.pop("binaryMediaTypes")
response.should.equal(
{
"id": api_id,
"name": "new-name",
"version": "V1",
"description": "new-description",
"apiKeySource": "AUTHORIZER",
"endpointConfiguration": {"types": ["EDGE"]},
"tags": {},
"disableExecuteApiEndpoint": True,
}
)
# should fail with wrong apikeysoruce
patchOperations = [
{"op": "replace", "path": "/apiKeySource", "value": "Wrong-value-AUTHORIZER"}
]
with pytest.raises(ClientError) as ex:
response = client.update_rest_api(
restApiId=api_id, patchOperations=patchOperations
)
ex.value.response["Error"]["Message"].should.equal(
"1 validation error detected: Value 'Wrong-value-AUTHORIZER' at 'createRestApiInput.apiKeySource' failed to satisfy constraint: Member must satisfy enum value set: [AUTHORIZER, HEADER]"
)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
@mock_apigateway
def test_upate_rest_api_invalid_api_id():
client = boto3.client("apigateway", region_name="us-west-2")
patchOperations = [
{"op": "replace", "path": "/apiKeySource", "value": "AUTHORIZER"}
]
with pytest.raises(ClientError) as ex:
client.update_rest_api(restApiId="api_id", patchOperations=patchOperations)
ex.value.response["Error"]["Code"].should.equal("NotFoundException")
@mock_apigateway
def test_list_and_delete_apis():
client = boto3.client("apigateway", region_name="us-west-2")