Merge pull request #699 from tootedom/apigateway

added more api gateway coverage
This commit is contained in:
Steve Pulec 2016-09-16 18:34:27 -04:00 committed by GitHub
commit ea0b587791
5 changed files with 536 additions and 20 deletions

View file

@ -0,0 +1,12 @@
from __future__ import unicode_literals
from moto.core.exceptions import RESTError
class StageNotFoundException(RESTError):
code = 404
def __init__(self):
super(StageNotFoundException, self).__init__(
"NotFoundException", "Invalid stage identifier specified")

View file

@ -7,15 +7,18 @@ import requests
from moto.core import BaseBackend
from moto.core.utils import iso_8601_datetime_with_milliseconds
from .utils import create_id
from .exceptions import StageNotFoundException
STAGE_URL = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}"
class Deployment(dict):
def __init__(self, deployment_id, name):
def __init__(self, deployment_id, name, description=""):
super(Deployment, self).__init__()
self['id'] = deployment_id
self['stageName'] = name
self['description'] = description
self['createdDate'] = iso_8601_datetime_with_milliseconds(datetime.datetime.now())
class IntegrationResponse(dict):
@ -150,24 +153,133 @@ class Resource(object):
class Stage(dict):
def __init__(self, name=None, deployment_id=None):
def __init__(self, name=None, deployment_id=None, variables=None,
description='',cacheClusterEnabled=False,cacheClusterSize=None):
super(Stage, self).__init__()
if variables is None:
variables = {}
self['stageName'] = name
self['deploymentId'] = deployment_id
self['methodSettings'] = {}
self['variables'] = {}
self['description'] = ''
self['variables'] = variables
self['description'] = description
self['cacheClusterEnabled'] = cacheClusterEnabled
if self['cacheClusterEnabled']:
self['cacheClusterSize'] = str(0.5)
if cacheClusterSize is not None:
self['cacheClusterSize'] = str(cacheClusterSize)
def apply_operations(self, patch_operations):
for op in patch_operations:
if op['op'] == 'replace':
# TODO: match the path against the values hash
if 'variables/' in op['path']:
self._apply_operation_to_variables(op)
elif '/cacheClusterEnabled' in op['path']:
self['cacheClusterEnabled'] = self._str2bool(op['value'])
if 'cacheClusterSize' not in self and self['cacheClusterEnabled']:
self['cacheClusterSize'] = str(0.5)
elif '/cacheClusterSize' in op['path']:
self['cacheClusterSize'] = str(float(op['value']))
elif '/description' in op['path']:
self['description'] = op['value']
elif '/deploymentId' in op['path']:
self['deploymentId'] = op['value']
elif op['op'] == 'replace':
# Method Settings drop into here
# (e.g., path could be '/*/*/logging/loglevel')
self[op['path']] = op['value']
split_path = op['path'].split('/',3)
if len(split_path)!=4:
continue
self._patch_method_setting('/'.join(split_path[1:3]),split_path[3],op['value'])
else:
raise Exception('Patch operation "%s" not implemented' % op['op'])
return self
def _patch_method_setting(self,resource_path_and_method,key,value):
updated_key = self._method_settings_translations(key)
if updated_key is not None:
if resource_path_and_method not in self['methodSettings']:
self['methodSettings'][resource_path_and_method] = self._get_default_method_settings()
self['methodSettings'][resource_path_and_method][updated_key] = self._convert_to_type(updated_key,value)
def _get_default_method_settings(self):
return {
"throttlingRateLimit": 1000.0,
"dataTraceEnabled": False,
"metricsEnabled": False,
"unauthorizedCacheControlHeaderStrategy": "SUCCEED_WITH_RESPONSE_HEADER",
"cacheTtlInSeconds": 300,
"cacheDataEncrypted": True,
"cachingEnabled": False,
"throttlingBurstLimit": 2000,
"requireAuthorizationForCacheControl": True
}
def _method_settings_translations(self,key):
mappings = {
'metrics/enabled' :'metricsEnabled',
'logging/loglevel' : 'loggingLevel',
'logging/dataTrace' : 'dataTraceEnabled' ,
'throttling/burstLimit' : 'throttlingBurstLimit',
'throttling/rateLimit' : 'throttlingRateLimit',
'caching/enabled' : 'cachingEnabled',
'caching/ttlInSeconds' : 'cacheTtlInSeconds',
'caching/dataEncrypted' : 'cacheDataEncrypted',
'caching/requireAuthorizationForCacheControl' : 'requireAuthorizationForCacheControl',
'caching/unauthorizedCacheControlHeaderStrategy' : 'unauthorizedCacheControlHeaderStrategy'
}
if key in mappings:
return mappings[key]
else:
None
def _str2bool(self,v):
return v.lower() == "true"
def _convert_to_type(self,key,val):
type_mappings = {
'metricsEnabled' : 'bool',
'loggingLevel' : 'str',
'dataTraceEnabled' : 'bool',
'throttlingBurstLimit' : 'int',
'throttlingRateLimit' : 'float',
'cachingEnabled' : 'bool',
'cacheTtlInSeconds' : 'int',
'cacheDataEncrypted' : 'bool',
'requireAuthorizationForCacheControl' :'bool',
'unauthorizedCacheControlHeaderStrategy' : 'str'
}
if key in type_mappings:
type_value = type_mappings[key]
if type_value == 'bool':
return self._str2bool(val)
elif type_value == 'int':
return int(val)
elif type_value == 'float':
return float(val)
else:
return str(val)
else:
return str(val)
def _apply_operation_to_variables(self,op):
key = op['path'][op['path'].rindex("variables/")+10:]
if op['op'] == 'remove':
self['variables'].pop(key, None)
elif op['op'] == 'replace':
self['variables'][key] = op['value']
else:
raise Exception('Patch operation "%s" not implemented' % op['op'])
class RestAPI(object):
def __init__(self, id, region_name, name, description):
@ -219,12 +331,22 @@ class RestAPI(object):
for method in httpretty.httpretty.METHODS:
httpretty.register_uri(method, stage_url, body=self.resource_callback)
def create_deployment(self, name):
deployment_id = create_id()
deployment = Deployment(deployment_id, name)
self.deployments[deployment_id] = deployment
self.stages[name] = Stage(name=name, deployment_id=deployment_id)
def create_stage(self, name, deployment_id,variables=None,description='',cacheClusterEnabled=None,cacheClusterSize=None):
if variables is None:
variables = {}
stage = Stage(name=name, deployment_id=deployment_id,variables=variables,
description=description,cacheClusterSize=cacheClusterSize,cacheClusterEnabled=cacheClusterEnabled)
self.stages[name] = stage
self.update_integration_mocks(name)
return stage
def create_deployment(self, name, description="",stage_variables=None):
if stage_variables is None:
stage_variables = {}
deployment_id = create_id()
deployment = Deployment(deployment_id, name, description)
self.deployments[deployment_id] = deployment
self.stages[name] = Stage(name=name, deployment_id=deployment_id,variables=stage_variables)
self.update_integration_mocks(name)
return deployment
@ -232,6 +354,9 @@ class RestAPI(object):
def get_deployment(self, deployment_id):
return self.deployments[deployment_id]
def get_stages(self):
return list(self.stages.values())
def get_deployments(self):
return list(self.deployments.values())
@ -300,6 +425,25 @@ class APIGatewayBackend(BaseBackend):
def get_stage(self, function_id, stage_name):
api = self.get_rest_api(function_id)
stage = api.stages.get(stage_name)
if stage is None:
raise StageNotFoundException()
else:
return stage
def get_stages(self, function_id):
api = self.get_rest_api(function_id)
return api.get_stages()
def create_stage(self, function_id, stage_name, deploymentId,
variables=None,description='',cacheClusterEnabled=None,cacheClusterSize=None):
if variables is None:
variables = {}
api = self.get_rest_api(function_id)
api.create_stage(stage_name,deploymentId,variables=variables,
description=description,cacheClusterEnabled=cacheClusterEnabled,cacheClusterSize=cacheClusterSize)
return api.stages.get(stage_name)
def update_stage(self, function_id, stage_name, patch_operations):
@ -354,9 +498,11 @@ class APIGatewayBackend(BaseBackend):
integration_response = integration.delete_integration_response(status_code)
return integration_response
def create_deployment(self, function_id, name):
def create_deployment(self, function_id, name, description ="", stage_variables=None):
if stage_variables is None:
stage_variables = {}
api = self.get_rest_api(function_id)
deployment = api.create_deployment(name)
deployment = api.create_deployment(name, description,stage_variables)
return deployment
def get_deployment(self, function_id, deployment_id):

View file

@ -4,6 +4,7 @@ import json
from moto.core.responses import BaseResponse
from .models import apigateway_backends
from .exceptions import StageNotFoundException
class APIGatewayResponse(BaseResponse):
@ -11,6 +12,15 @@ class APIGatewayResponse(BaseResponse):
def _get_param(self, key):
return json.loads(self.body.decode("ascii")).get(key)
def _get_param_with_default_value(self, key, default):
jsonbody = json.loads(self.body.decode("ascii"))
if key in jsonbody:
return jsonbody.get(key)
else:
return default
@property
def backend(self):
return apigateway_backends[self.region]
@ -95,6 +105,28 @@ class APIGatewayResponse(BaseResponse):
method_response = self.backend.delete_method_response(function_id, resource_id, method_type, response_code)
return 200, headers, json.dumps(method_response)
def restapis_stages(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
function_id = url_path_parts[2]
if self.method == 'POST':
stage_name = self._get_param("stageName")
deployment_id = self._get_param("deploymentId")
stage_variables = self._get_param_with_default_value('variables',{})
description = self._get_param_with_default_value('description','')
cacheClusterEnabled = self._get_param_with_default_value('cacheClusterEnabled',False)
cacheClusterSize = self._get_param_with_default_value('cacheClusterSize',None)
stage_response = self.backend.create_stage(function_id, stage_name, deployment_id,
variables=stage_variables, description=description,
cacheClusterEnabled=cacheClusterEnabled, cacheClusterSize=cacheClusterSize)
elif self.method == 'GET':
stages = self.backend.get_stages(function_id)
return 200, headers, json.dumps({"item": stages})
return 200, headers, json.dumps(stage_response)
def stages(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
@ -102,10 +134,13 @@ class APIGatewayResponse(BaseResponse):
stage_name = url_path_parts[4]
if self.method == 'GET':
stage_response = self.backend.get_stage(function_id, stage_name)
try:
stage_response = self.backend.get_stage(function_id, stage_name)
except StageNotFoundException as error:
return error.code, headers,'{{"message":"{0}","code":"{1}"}}'.format(error.message,error.error_type)
elif self.method == 'PATCH':
path_operations = self._get_param('patchOperations')
stage_response = self.backend.update_stage(function_id, stage_name, path_operations)
patch_operations = self._get_param('patchOperations')
stage_response = self.backend.update_stage(function_id, stage_name, patch_operations)
return 200, headers, json.dumps(stage_response)
def integrations(self, request, full_url, headers):
@ -158,7 +193,9 @@ class APIGatewayResponse(BaseResponse):
return 200, headers, json.dumps({"item": deployments})
elif self.method == 'POST':
name = self._get_param("stageName")
deployment = self.backend.create_deployment(function_id, name)
description = self._get_param_with_default_value("description","")
stage_variables = self._get_param_with_default_value('variables',{})
deployment = self.backend.create_deployment(function_id, name, description,stage_variables)
return 200, headers, json.dumps(deployment)
def individual_deployment(self, request, full_url, headers):

View file

@ -9,6 +9,7 @@ url_paths = {
'{0}/restapis$': APIGatewayResponse().restapis,
'{0}/restapis/(?P<function_id>[^/]+)/?$': APIGatewayResponse().restapis_individual,
'{0}/restapis/(?P<function_id>[^/]+)/resources$': APIGatewayResponse().resources,
'{0}/restapis/(?P<function_id>[^/]+)/stages$': APIGatewayResponse().restapis_stages,
'{0}/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)/?$': APIGatewayResponse().stages,
'{0}/restapis/(?P<function_id>[^/]+)/deployments$': APIGatewayResponse().deployments,
'{0}/restapis/(?P<function_id>[^/]+)/deployments/(?P<deployment_id>[^/]+)/?$': APIGatewayResponse().individual_deployment,