diff --git a/CHANGES.rst b/CHANGES.rst index ee8f9ca8..1e77fa17 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,10 +4,17 @@ Changelog Unreleased Changes ------------------ +This release **requires new IAM permissions**: + +* ``apigateway:GET`` +* ``apigateway:HEAD`` +* ``apigateway:OPTIONS`` + * `Issue #254 `_ - Officially adopt SemVer for this project, and document our :ref:`versioning policy `. * `Issue #294 `_ - Ignore NAT Gateways that are not in "available" or "pending" state. * `Issue #253 `_ - Check latest awslimitchecker version on PyPI at class instantiation; log warning if a newer version is available. Add Python API and CLI options to disable this. * Pin `tox `_ version to 2.7.0 as workaround for parsing change. +* `Issue #292 `_ - Add support for API Gateway limits. 0.11.0 (2017-08-06) ------------------- diff --git a/README.rst b/README.rst index d059eed2..5157c79b 100644 --- a/README.rst +++ b/README.rst @@ -79,8 +79,7 @@ Full project documentation for the latest release is available at `http://awslim Status ------ -It's gotten a bunch of downloads on PyPi (when the download counters used to work). As far as I know, -it's stable and in use by some pretty large organizations. +awslimitchecker is mature software, with approximately 9,000 downloads per month and in daily use at numerous organizations. Development status is being tracked on a board at waffle.io: https://waffle.io/jantman/awslimitchecker diff --git a/awslimitchecker/services/__init__.py b/awslimitchecker/services/__init__.py index 70a4a408..dec50314 100644 --- a/awslimitchecker/services/__init__.py +++ b/awslimitchecker/services/__init__.py @@ -53,6 +53,7 @@ from awslimitchecker.services.cloudformation import _CloudformationService from awslimitchecker.services.firehose import _FirehoseService from awslimitchecker.services.redshift import _RedshiftService +from awslimitchecker.services.apigateway import _ApigatewayService # dynamically generate the service name to class dict _services = {} diff --git a/awslimitchecker/services/apigateway.py b/awslimitchecker/services/apigateway.py new file mode 100644 index 00000000..8f431a05 --- /dev/null +++ b/awslimitchecker/services/apigateway.py @@ -0,0 +1,266 @@ +""" +awslimitchecker/services/apigateway.py + +The latest version of this package is available at: + + +################################################################################ +Copyright 2015-2017 Jason Antman + + This file is part of awslimitchecker, also known as awslimitchecker. + + awslimitchecker is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + awslimitchecker is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with awslimitchecker. If not, see . + +The Copyright and Authors attributions contained herein may not be removed or +otherwise altered, except to add the Author attribution of a contributor to +this work. (Additional Terms pursuant to Section 7b of the AGPL v3) +################################################################################ +While not legally required, I sincerely request that anyone who finds +bugs please submit them at or +to me via email, and that you send any contributions or improvements +either as a pull request on GitHub, or to me via email. +################################################################################ + +AUTHORS: +Jason Antman +################################################################################ +""" + +import abc # noqa +import logging + +from .base import _AwsService +from ..limit import AwsLimit +from awslimitchecker.utils import paginate_dict + +logger = logging.getLogger(__name__) + + +class _ApigatewayService(_AwsService): + + service_name = 'ApiGateway' + api_name = 'apigateway' # AWS API name to connect to (boto3.client) + + def find_usage(self): + """ + Determine the current usage for each limit of this service, + and update corresponding Limit via + :py:meth:`~.AwsLimit._add_current_usage`. + """ + logger.debug("Checking usage for service %s", self.service_name) + self.connect() + for lim in self.limits.values(): + lim._reset_usage() + self._find_usage_apis() + self._find_usage_api_keys() + self._find_usage_certs() + self._find_usage_plans() + self._have_usage = True + logger.debug("Done checking usage.") + + def _find_usage_apis(self): + """ + Find usage on APIs / RestAPIs, and resources that are limited per-API. + Update `self.limits`. + """ + api_ids = [] + logger.debug('Finding usage for APIs') + paginator = self.conn.get_paginator('get_rest_apis') + for resp in paginator.paginate(): + for api in resp['items']: + api_ids.append(api['id']) + logger.debug('Found %d APIs', len(api_ids)) + self.limits['APIs per account']._add_current_usage( + len(api_ids), aws_type='AWS::ApiGateway::RestApi' + ) + # now the per-API limits... + warn_stages_paginated = None + logger.debug('Finding usage for per-API limits') + for api_id in api_ids: + res_count = 0 + paginator = self.conn.get_paginator('get_resources') + for resp in paginator.paginate(restApiId=api_id): + res_count += len(resp['items']) + self.limits['Resources per API']._add_current_usage( + res_count, resource_id=api_id, + aws_type='AWS::ApiGateway::Resource' + ) + doc_parts = paginate_dict( + self.conn.get_documentation_parts, + restApiId=api_id, + alc_marker_path=['position'], + alc_data_path=['items'], + alc_marker_param='position' + ) + self.limits['Documentation parts per API']._add_current_usage( + len(doc_parts), resource_id=api_id, + aws_type='AWS::ApiGateway::DocumentationPart' + ) + # note that per the boto3 docs, there's no pagination of this... + stages = self.conn.get_stages(restApiId=api_id) + if len(set(stages.keys()) - set(['item', 'ResponseMetadata'])) > 0: + warn_stages_paginated = stages.keys() + self.limits['Stages per API']._add_current_usage( + len(stages['item']), resource_id=api_id, + aws_type='AWS::ApiGateway::Stage' + ) + authorizers = paginate_dict( + self.conn.get_authorizers, + restApiId=api_id, + alc_marker_path=['position'], + alc_data_path=['items'], + alc_marker_param='position' + ) + self.limits['Custom authorizers per API']._add_current_usage( + len(authorizers), resource_id=api_id, + aws_type='AWS::ApiGateway::Authorizer' + ) + if warn_stages_paginated is not None: + logger.warning( + 'APIGateway get_stages returned more keys than present in ' + 'boto3 docs: %s', sorted(warn_stages_paginated) + ) + + def _find_usage_api_keys(self): + """ + Find usage on API Keys. + Update `self.limits`. + """ + logger.debug('Finding usage for API Keys') + key_count = 0 + paginator = self.conn.get_paginator('get_api_keys') + for resp in paginator.paginate(): + key_count += len(resp['items']) + self.limits['API keys per account']._add_current_usage( + key_count, aws_type='AWS::ApiGateway::ApiKey' + ) + + def _find_usage_certs(self): + """ + Find usage on Client Certificates. Update `self.limits`. + """ + logger.debug('Finding usage for Client Certificates') + cert_count = 0 + paginator = self.conn.get_paginator('get_client_certificates') + for resp in paginator.paginate(): + cert_count += len(resp['items']) + self.limits['Client certificates per account']._add_current_usage( + cert_count, aws_type='AWS::ApiGateway::ClientCertificate' + ) + + def _find_usage_plans(self): + """ + Find usage on Usage Plans and plans per API Key. Update `self.limits`. + """ + logger.debug('Finding usage for Usage Plans') + plan_count = 0 + paginator = self.conn.get_paginator('get_usage_plans') + for resp in paginator.paginate(): + plan_count += len(resp['items']) + self.limits['Usage plans per account']._add_current_usage( + plan_count, aws_type='AWS::ApiGateway::UsagePlan' + ) + + def get_limits(self): + """ + Return all known limits for this service, as a dict of their names + to :py:class:`~.AwsLimit` objects. + + :returns: dict of limit names to :py:class:`~.AwsLimit` objects + :rtype: dict + """ + if self.limits != {}: + return self.limits + limits = {} + limits['APIs per account'] = AwsLimit( + 'APIs per account', + self, + 60, + self.warning_threshold, + self.critical_threshold, + limit_type='AWS::ApiGateway::RestApi' + ) + limits['API keys per account'] = AwsLimit( + 'API keys per account', + self, + 500, + self.warning_threshold, + self.critical_threshold, + limit_type='AWS::ApiGateway::ApiKey' + ) + limits['Custom authorizers per API'] = AwsLimit( + 'Custom authorizers per API', + self, + 10, + self.warning_threshold, + self.critical_threshold, + limit_type='AWS::ApiGateway::Authorizer' + ) + limits['Client certificates per account'] = AwsLimit( + 'Client certificates per account', + self, + 60, + self.warning_threshold, + self.critical_threshold, + limit_type='AWS::ApiGateway::ClientCertificate' + ) + limits['Documentation parts per API'] = AwsLimit( + 'Documentation parts per API', + self, + 2000, + self.warning_threshold, + self.critical_threshold, + limit_type='AWS::ApiGateway::DocumentationPart' + ) + limits['Resources per API'] = AwsLimit( + 'Resources per API', + self, + 300, + self.warning_threshold, + self.critical_threshold, + limit_type='AWS::ApiGateway::Resource' + ) + limits['Stages per API'] = AwsLimit( + 'Stages per API', + self, + 10, + self.warning_threshold, + self.critical_threshold, + limit_type='AWS::ApiGateway::Stage' + ) + limits['Usage plans per account'] = AwsLimit( + 'Usage plans per account', + self, + 300, + self.warning_threshold, + self.critical_threshold, + limit_type='AWS::ApiGateway::UsagePlan' + ) + self.limits = limits + return limits + + def required_iam_permissions(self): + """ + Return a list of IAM Actions required for this Service to function + properly. All Actions will be shown with an Effect of "Allow" + and a Resource of "*". + + :returns: list of IAM Action strings + :rtype: list + """ + return [ + "apigateway:GET", + "apigateway:HEAD", + "apigateway:OPTIONS" + ] diff --git a/awslimitchecker/tests/services/result_fixtures.py b/awslimitchecker/tests/services/result_fixtures.py index 638faabf..96b872aa 100644 --- a/awslimitchecker/tests/services/result_fixtures.py +++ b/awslimitchecker/tests/services/result_fixtures.py @@ -2392,3 +2392,792 @@ class Redshift(object): } ] } + + +class ApiGateway(object): + + get_rest_apis = [ + { + 'items': [ + { + 'id': 'api3', + 'name': 'api3name', + 'description': 'api3desc', + 'createdDate': datetime(2015, 1, 1), + 'version': 'api3ver', + 'warnings': [ + 'string', + ], + 'binaryMediaTypes': [ + 'string', + ] + }, + { + 'id': 'api2', + 'name': 'api2name', + 'description': 'api2desc', + 'createdDate': datetime(2016, 1, 1), + 'version': 'api2ver', + 'warnings': [], + 'binaryMediaTypes': [] + } + ], + 'NextToken': 'string' + }, + { + 'items': [ + { + 'id': 'api1', + 'name': 'api1name', + 'description': 'api1desc', + 'createdDate': datetime(2017, 1, 1), + 'version': 'api1ver', + 'warnings': [ + 'string', + ], + 'binaryMediaTypes': [ + 'string', + ] + }, + ] + } + ] + + resources_api1 = [ + { + 'items': [ + { + 'id': 'api1res1', + 'parentId': 'string', + 'pathPart': 'string', + 'path': 'string', + 'resourceMethods': { + 'string': { + 'httpMethod': 'string', + 'authorizationType': 'string', + 'authorizerId': 'string', + 'apiKeyRequired': False, + 'requestValidatorId': 'string', + 'operationName': 'string', + 'requestParameters': { + 'string': False + }, + 'requestModels': { + 'string': 'string' + }, + 'methodResponses': { + 'string': { + 'statusCode': 'string', + 'responseParameters': { + 'string': True + }, + 'responseModels': { + 'string': 'string' + } + } + }, + 'methodIntegration': { + 'type': 'HTTP', + 'httpMethod': 'string', + 'uri': 'string', + 'credentials': 'string', + 'requestParameters': { + 'string': 'string' + }, + 'requestTemplates': { + 'string': 'string' + }, + 'passthroughBehavior': 'string', + 'contentHandling': 'CONVERT_TO_BINARY', + 'cacheNamespace': 'string', + 'cacheKeyParameters': [ + 'string', + ], + 'integrationResponses': { + 'string': { + 'statusCode': 'string', + 'selectionPattern': 'string', + 'responseParameters': { + 'string': 'string' + }, + 'responseTemplates': { + 'string': 'string' + }, + 'contentHandling': 'CONVERT_TO_BINARY' + } + } + } + } + } + }, + { + 'id': 'api1res2', + 'parentId': 'string', + 'pathPart': 'string', + 'path': 'string', + 'resourceMethods': { + 'string': { + 'httpMethod': 'string', + 'authorizationType': 'string', + 'authorizerId': 'string', + 'apiKeyRequired': False, + 'requestValidatorId': 'string', + 'operationName': 'string', + 'requestParameters': { + 'string': False + }, + 'requestModels': { + 'string': 'string' + }, + 'methodResponses': { + 'string': { + 'statusCode': 'string', + 'responseParameters': { + 'string': True + }, + 'responseModels': { + 'string': 'string' + } + } + }, + 'methodIntegration': { + 'type': 'AWS_PROXY', + 'httpMethod': 'string', + 'uri': 'string', + 'credentials': 'string', + 'requestParameters': { + 'string': 'string' + }, + 'requestTemplates': { + 'string': 'string' + }, + 'passthroughBehavior': 'string', + 'contentHandling': 'CONVERT_TO_TEXT', + 'cacheNamespace': 'string', + 'cacheKeyParameters': [ + 'string', + ], + 'integrationResponses': { + 'string': { + 'statusCode': 'string', + 'selectionPattern': 'string', + 'responseParameters': { + 'string': 'string' + }, + 'responseTemplates': { + 'string': 'string' + }, + 'contentHandling': 'CONVERT_TO_TEXT' + } + } + } + } + } + } + ], + 'NextToken': 'string' + }, + { + 'items': [ + { + 'id': 'api1res3', + 'parentId': 'string', + 'pathPart': 'string', + 'path': 'string', + 'resourceMethods': { + 'string': { + 'httpMethod': 'string', + 'authorizationType': 'string', + 'authorizerId': 'string', + 'apiKeyRequired': False, + 'requestValidatorId': 'string', + 'operationName': 'string', + 'requestParameters': { + 'string': False + }, + 'requestModels': { + 'string': 'string' + }, + 'methodResponses': { + 'string': { + 'statusCode': 'string', + 'responseParameters': { + 'string': True + }, + 'responseModels': { + 'string': 'string' + } + } + }, + 'methodIntegration': { + 'type': 'HTTP', + 'httpMethod': 'string', + 'uri': 'string', + 'credentials': 'string', + 'requestParameters': { + 'string': 'string' + }, + 'requestTemplates': { + 'string': 'string' + }, + 'passthroughBehavior': 'string', + 'contentHandling': 'CONVERT_TO_TEXT', + 'cacheNamespace': 'string', + 'cacheKeyParameters': [ + 'string', + ], + 'integrationResponses': { + 'string': { + 'statusCode': 'string', + 'selectionPattern': 'string', + 'responseParameters': { + 'string': 'string' + }, + 'responseTemplates': { + 'string': 'string' + }, + 'contentHandling': 'CONVERT_TO_TEXT' + } + } + } + } + } + } + ] + } + ] + + resources_api2 = [ + { + 'items': [ + { + 'id': 'api2res1', + 'parentId': 'string', + 'pathPart': 'string', + 'path': 'string', + 'resourceMethods': { + 'string': { + 'httpMethod': 'string', + 'authorizationType': 'string', + 'authorizerId': 'string', + 'apiKeyRequired': False, + 'requestValidatorId': 'string', + 'operationName': 'string', + 'requestParameters': { + 'string': False + }, + 'requestModels': { + 'string': 'string' + }, + 'methodResponses': { + 'string': { + 'statusCode': 'string', + 'responseParameters': { + 'string': True + }, + 'responseModels': { + 'string': 'string' + } + } + }, + 'methodIntegration': { + 'type': 'HTTP', + 'httpMethod': 'string', + 'uri': 'string', + 'credentials': 'string', + 'requestParameters': { + 'string': 'string' + }, + 'requestTemplates': { + 'string': 'string' + }, + 'passthroughBehavior': 'string', + 'contentHandling': 'CONVERT_TO_BINARY', + 'cacheNamespace': 'string', + 'cacheKeyParameters': [ + 'string', + ], + 'integrationResponses': { + 'string': { + 'statusCode': 'string', + 'selectionPattern': 'string', + 'responseParameters': { + 'string': 'string' + }, + 'responseTemplates': { + 'string': 'string' + }, + 'contentHandling': 'CONVERT_TO_TEXT' + } + } + } + } + } + }, + ], + 'NextToken': 'string' + }, + { + 'items': [ + { + 'id': 'api2res2', + 'parentId': 'string', + 'pathPart': 'string', + 'path': 'string', + 'resourceMethods': { + 'string': { + 'httpMethod': 'string', + 'authorizationType': 'string', + 'authorizerId': 'string', + 'apiKeyRequired': False, + 'requestValidatorId': 'string', + 'operationName': 'string', + 'requestParameters': { + 'string': False + }, + 'requestModels': { + 'string': 'string' + }, + 'methodResponses': { + 'string': { + 'statusCode': 'string', + 'responseParameters': { + 'string': True + }, + 'responseModels': { + 'string': 'string' + } + } + }, + 'methodIntegration': { + 'type': 'AWS_PROXY', + 'httpMethod': 'string', + 'uri': 'string', + 'credentials': 'string', + 'requestParameters': { + 'string': 'string' + }, + 'requestTemplates': { + 'string': 'string' + }, + 'passthroughBehavior': 'string', + 'contentHandling': 'CONVERT_TO_TEXT', + 'cacheNamespace': 'string', + 'cacheKeyParameters': [ + 'string', + ], + 'integrationResponses': { + 'string': { + 'statusCode': 'string', + 'selectionPattern': 'string', + 'responseParameters': { + 'string': 'string' + }, + 'responseTemplates': { + 'string': 'string' + }, + 'contentHandling': 'CONVERT_TO_TEXT' + } + } + } + } + } + } + ] + } + ] + + resources_api3 = [{'items': []}] + + get_resources = { + 'api1': resources_api1, + 'api2': resources_api2, + 'api3': resources_api3 + } + + doc_parts = { + 'api1': [ + { + 'id': 'string', + 'location': { + 'type': 'API', + 'path': 'string', + 'method': 'string', + 'statusCode': 'string', + 'name': 'string' + }, + 'properties': 'string' + }, + { + 'id': 'string', + 'location': { + 'type': 'AUTHORIZER', + 'path': 'string', + 'method': 'string', + 'statusCode': 'string', + 'name': 'string' + }, + 'properties': 'string' + }, + { + 'id': 'string', + 'location': { + 'type': 'MODEL', + 'path': 'string', + 'method': 'string', + 'statusCode': 'string', + 'name': 'string' + }, + 'properties': 'string' + }, + { + 'id': 'string', + 'location': { + 'type': 'RESPONSE_BODY', + 'path': 'string', + 'method': 'string', + 'statusCode': 'string', + 'name': 'string' + }, + 'properties': 'string' + }, + ], + 'api2': [ + { + 'id': 'string', + 'location': { + 'type': 'API', + 'path': 'string', + 'method': 'string', + 'statusCode': 'string', + 'name': 'string' + }, + 'properties': 'string' + } + ], + 'api3': [ + { + 'id': 'string', + 'location': { + 'type': 'API', + 'path': 'string', + 'method': 'string', + 'statusCode': 'string', + 'name': 'string' + }, + 'properties': 'string' + }, + { + 'id': 'string', + 'location': { + 'type': 'RESPONSE_BODY', + 'path': 'string', + 'method': 'string', + 'statusCode': 'string', + 'name': 'string' + }, + 'properties': 'string' + } + ] + } + + stages = { + 'api1': {'item': [ + { + 'deploymentId': 'string', + 'clientCertificateId': 'string', + 'stageName': 'string', + 'description': 'string', + 'cacheClusterEnabled': True, + 'cacheClusterSize': '0.5', + 'cacheClusterStatus': 'AVAILABLE', + 'methodSettings': { + 'string': { + 'metricsEnabled': True, + 'loggingLevel': 'string', + 'dataTraceEnabled': True, + 'throttlingBurstLimit': 123, + 'throttlingRateLimit': 123.0, + 'cachingEnabled': True, + 'cacheTtlInSeconds': 123, + 'cacheDataEncrypted': True, + 'requireAuthorizationForCacheControl': True, + 'unauthorizedCacheControlHeaderStrategy': 'FAIL_WITH_40' + } + }, + 'variables': { + 'string': 'string' + }, + 'documentationVersion': 'string', + 'createdDate': datetime(2015, 1, 1), + 'lastUpdatedDate': datetime(2015, 1, 1) + }, + { + 'deploymentId': 'foo' + }, + { + 'deploymentId': 'bar' + } + ]}, + 'api2': { + 'item': [ + {'deploymentId': 'baz'} + ] + }, + 'api3': { + 'item': [ + {'deploymentId': 'blam'}, + {'deploymentId': 'blarg'} + ] + } + } + + authorizers = { + 'api1': [ + { + 'id': 'string', + 'name': 'string', + 'type': 'TOKEN', + 'providerARNs': [ + 'string', + ], + 'authType': 'string', + 'authorizerUri': 'string', + 'authorizerCredentials': 'string', + 'identitySource': 'string', + 'identityValidationExpression': 'string', + 'authorizerResultTtlInSeconds': 123 + } + ], + 'api2': [ + { + 'id': 'string', + 'name': 'string', + 'type': 'REQUEST', + 'providerARNs': [ + 'string', + ], + 'authType': 'string', + 'authorizerUri': 'string', + 'authorizerCredentials': 'string', + 'identitySource': 'string', + 'identityValidationExpression': 'string', + 'authorizerResultTtlInSeconds': 123 + }, + { + 'id': 'string', + 'name': 'string', + 'type': 'TOKEN', + 'providerARNs': [ + 'string', + ], + 'authType': 'string', + 'authorizerUri': 'string', + 'authorizerCredentials': 'string', + 'identitySource': 'string', + 'identityValidationExpression': 'string', + 'authorizerResultTtlInSeconds': 123 + } + ], + 'api3': [] + } + + plans = [ + { + 'items': [ + { + 'id': 'string', + 'name': 'string', + 'description': 'string', + 'apiStages': [ + { + 'apiId': 'string', + 'stage': 'string' + }, + ], + 'throttle': { + 'burstLimit': 123, + 'rateLimit': 123.0 + }, + 'quota': { + 'limit': 123, + 'offset': 123, + 'period': 'DAY' + }, + 'productCode': 'string' + }, + { + 'id': 'string', + 'name': 'string', + 'description': 'string', + 'apiStages': [ + { + 'apiId': 'string', + 'stage': 'string' + }, + ], + 'throttle': { + 'burstLimit': 123, + 'rateLimit': 123.0 + }, + 'quota': { + 'limit': 123, + 'offset': 123, + 'period': 'WEEK' + }, + 'productCode': 'string' + }, + { + 'id': 'string', + 'name': 'string', + 'description': 'string', + 'apiStages': [ + { + 'apiId': 'string', + 'stage': 'string' + }, + ], + 'throttle': { + 'burstLimit': 123, + 'rateLimit': 123.0 + }, + 'quota': { + 'limit': 123, + 'offset': 123, + 'period': 'MONTH' + }, + 'productCode': 'string' + } + ], + 'NextToken': 'foo' + }, + { + 'items': [ + { + 'id': 'string', + 'name': 'string', + 'description': 'string', + 'apiStages': [ + { + 'apiId': 'string', + 'stage': 'string' + }, + ], + 'throttle': { + 'burstLimit': 123, + 'rateLimit': 123.0 + }, + 'quota': { + 'limit': 123, + 'offset': 123, + 'period': 'DAY' + }, + 'productCode': 'string' + } + ] + } + ] + + certs = [ + { + 'items': [ + { + 'clientCertificateId': 'string', + 'description': 'string', + 'pemEncodedCertificate': 'string', + 'createdDate': datetime(2015, 1, 1), + 'expirationDate': datetime(2015, 1, 1) + }, + ], + 'NextToken': 'string' + }, + { + 'items': [ + { + 'clientCertificateId': 'string', + 'description': 'string', + 'pemEncodedCertificate': 'string', + 'createdDate': datetime(2015, 1, 1), + 'expirationDate': datetime(2015, 1, 1) + }, + ] + } + ] + + api_keys = [ + { + 'warnings': [ + 'string', + ], + 'items': [ + { + 'id': 'string', + 'value': 'string', + 'name': 'string', + 'customerId': 'string', + 'description': 'string', + 'enabled': True, + 'createdDate': datetime(2015, 1, 1), + 'lastUpdatedDate': datetime(2015, 1, 1), + 'stageKeys': [ + 'string', + ] + }, + { + 'id': 'string', + 'value': 'string', + 'name': 'string', + 'customerId': 'string', + 'description': 'string', + 'enabled': True, + 'createdDate': datetime(2015, 1, 1), + 'lastUpdatedDate': datetime(2015, 1, 1), + 'stageKeys': [ + 'string', + ] + } + ], + 'NextToken': 'string' + }, + { + 'warnings': [ + 'string', + ], + 'items': [ + { + 'id': 'string', + 'value': 'string', + 'name': 'string', + 'customerId': 'string', + 'description': 'string', + 'enabled': False, + 'createdDate': datetime(2015, 1, 1), + 'lastUpdatedDate': datetime(2015, 1, 1), + 'stageKeys': [ + 'string', + ] + }, + ], + 'NextToken': 'string' + }, + { + 'warnings': [ + 'string', + ], + 'items': [ + { + 'id': 'string', + 'value': 'string', + 'name': 'string', + 'customerId': 'string', + 'description': 'string', + 'enabled': True, + 'createdDate': datetime(2015, 1, 1), + 'lastUpdatedDate': datetime(2015, 1, 1), + 'stageKeys': [ + 'string', + ] + }, + ] + } + ] diff --git a/awslimitchecker/tests/services/test_apigateway.py b/awslimitchecker/tests/services/test_apigateway.py new file mode 100644 index 00000000..985ea7ad --- /dev/null +++ b/awslimitchecker/tests/services/test_apigateway.py @@ -0,0 +1,389 @@ +""" +awslimitchecker/tests/services/test_apigateway.py + +The latest version of this package is available at: + + +################################################################################ +Copyright 2015-2017 Jason Antman + + This file is part of awslimitchecker, also known as awslimitchecker. + + awslimitchecker is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + awslimitchecker is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with awslimitchecker. If not, see . + +The Copyright and Authors attributions contained herein may not be removed or +otherwise altered, except to add the Author attribution of a contributor to +this work. (Additional Terms pursuant to Section 7b of the AGPL v3) +################################################################################ +While not legally required, I sincerely request that anyone who finds +bugs please submit them at or +to me via email, and that you send any contributions or improvements +either as a pull request on GitHub, or to me via email. +################################################################################ + +AUTHORS: +Jason Antman +################################################################################ +""" + +import sys +from copy import deepcopy +from awslimitchecker.tests.services import result_fixtures +from awslimitchecker.services.apigateway import _ApigatewayService + +# https://code.google.com/p/mock/issues/detail?id=249 +# py>=3.4 should use unittest.mock not the mock package on pypi +if ( + sys.version_info[0] < 3 or + sys.version_info[0] == 3 and sys.version_info[1] < 4 +): + from mock import patch, call, Mock, DEFAULT +else: + from unittest.mock import patch, call, Mock, DEFAULT + + +pbm = 'awslimitchecker.services.apigateway' # module patch base +pb = '%s._ApigatewayService' % pbm # class patch pase + + +class Test_ApigatewayService(object): + + def test_init(self): + """test __init__()""" + cls = _ApigatewayService(21, 43) + assert cls.service_name == 'ApiGateway' + assert cls.api_name == 'apigateway' + assert cls.conn is None + assert cls.warning_threshold == 21 + assert cls.critical_threshold == 43 + + def test_get_limits(self): + cls = _ApigatewayService(21, 43) + cls.limits = {} + res = cls.get_limits() + assert sorted(res.keys()) == sorted([ + 'API keys per account', + 'APIs per account', + 'Client certificates per account', + 'Custom authorizers per API', + 'Documentation parts per API', + 'Resources per API', + 'Stages per API', + 'Usage plans per account' + ]) + for name, limit in res.items(): + assert limit.service == cls + assert limit.def_warning_threshold == 21 + assert limit.def_critical_threshold == 43 + + def test_get_limits_again(self): + """test that existing limits dict is returned on subsequent calls""" + mock_limits = Mock() + cls = _ApigatewayService(21, 43) + cls.limits = mock_limits + res = cls.get_limits() + assert res == mock_limits + + def test_find_usage(self): + mock_conn = Mock() + with patch('%s.connect' % pb) as mock_connect: + with patch.multiple( + pb, + autospec=True, + _find_usage_apis=DEFAULT, + _find_usage_api_keys=DEFAULT, + _find_usage_certs=DEFAULT, + _find_usage_plans=DEFAULT + ) as mocks: + cls = _ApigatewayService(21, 43) + cls.conn = mock_conn + assert cls._have_usage is False + cls.find_usage() + assert mock_connect.mock_calls == [call()] + assert cls._have_usage is True + assert mock_conn.mock_calls == [] + assert mocks['_find_usage_apis'].mock_calls == [call(cls)] + assert mocks['_find_usage_api_keys'].mock_calls == [call(cls)] + assert mocks['_find_usage_certs'].mock_calls == [call(cls)] + assert mocks['_find_usage_plans'].mock_calls == [call(cls)] + + def test_find_usage_apis(self): + mock_conn = Mock() + res = result_fixtures.ApiGateway.get_rest_apis + mock_paginator = Mock() + mock_paginator.paginate.return_value = res + + def se_res_paginate(restApiId=None): + return result_fixtures.ApiGateway.get_resources[restApiId] + + mock_res_paginator = Mock() + mock_res_paginator.paginate.side_effect = se_res_paginate + + def se_get_paginator(api_name): + if api_name == 'get_rest_apis': + return mock_paginator + elif api_name == 'get_resources': + return mock_res_paginator + + def se_paginate_dict(*args, **kwargs): + if args[0] == mock_conn.get_documentation_parts: + return result_fixtures.ApiGateway.doc_parts[kwargs['restApiId']] + if args[0] == mock_conn.get_authorizers: + return result_fixtures.ApiGateway.authorizers[ + kwargs['restApiId'] + ] + + def se_get_stages(restApiId=None): + return result_fixtures.ApiGateway.stages[restApiId] + + mock_conn.get_paginator.side_effect = se_get_paginator + mock_conn.get_stages.side_effect = se_get_stages + cls = _ApigatewayService(21, 43) + cls.conn = mock_conn + with patch('%s.paginate_dict' % pbm, autospec=True) as mock_pd: + with patch('%s.logger' % pbm) as mock_logger: + mock_pd.side_effect = se_paginate_dict + cls._find_usage_apis() + # APIs usage + usage = cls.limits['APIs per account'].get_current_usage() + assert len(usage) == 1 + assert usage[0].get_value() == 3 + # Resources usage + usage = cls.limits['Resources per API'].get_current_usage() + assert len(usage) == 3 + assert usage[0].resource_id == 'api3' + assert usage[0].get_value() == 0 + assert usage[1].resource_id == 'api2' + assert usage[1].get_value() == 2 + assert usage[2].resource_id == 'api1' + assert usage[2].get_value() == 3 + usage = cls.limits['Documentation parts per API'].get_current_usage() + assert len(usage) == 3 + assert usage[0].resource_id == 'api3' + assert usage[0].get_value() == 2 + assert usage[1].resource_id == 'api2' + assert usage[1].get_value() == 1 + assert usage[2].resource_id == 'api1' + assert usage[2].get_value() == 4 + usage = cls.limits['Stages per API'].get_current_usage() + assert len(usage) == 3 + assert usage[0].resource_id == 'api3' + assert usage[0].get_value() == 2 + assert usage[1].resource_id == 'api2' + assert usage[1].get_value() == 1 + assert usage[2].resource_id == 'api1' + assert usage[2].get_value() == 3 + usage = cls.limits['Custom authorizers per API'].get_current_usage() + assert len(usage) == 3 + assert usage[0].resource_id == 'api3' + assert usage[0].get_value() == 0 + assert usage[1].resource_id == 'api2' + assert usage[1].get_value() == 2 + assert usage[2].resource_id == 'api1' + assert usage[2].get_value() == 1 + assert mock_conn.mock_calls == [ + call.get_paginator('get_rest_apis'), + call.get_paginator('get_resources'), + call.get_stages(restApiId='api3'), + call.get_paginator('get_resources'), + call.get_stages(restApiId='api2'), + call.get_paginator('get_resources'), + call.get_stages(restApiId='api1') + ] + assert mock_paginator.mock_calls == [call.paginate()] + assert mock_res_paginator.mock_calls == [ + call.paginate(restApiId='api3'), + call.paginate(restApiId='api2'), + call.paginate(restApiId='api1') + ] + assert mock_pd.mock_calls == [ + call( + mock_conn.get_documentation_parts, + restApiId='api3', + alc_marker_path=['position'], + alc_data_path=['items'], + alc_marker_param='position' + ), + call( + mock_conn.get_authorizers, + restApiId='api3', + alc_marker_path=['position'], + alc_data_path=['items'], + alc_marker_param='position' + ), + call( + mock_conn.get_documentation_parts, + restApiId='api2', + alc_marker_path=['position'], + alc_data_path=['items'], + alc_marker_param='position' + ), + call( + mock_conn.get_authorizers, + restApiId='api2', + alc_marker_path=['position'], + alc_data_path=['items'], + alc_marker_param='position' + ), + call( + mock_conn.get_documentation_parts, + restApiId='api1', + alc_marker_path=['position'], + alc_data_path=['items'], + alc_marker_param='position' + ), + call( + mock_conn.get_authorizers, + restApiId='api1', + alc_marker_path=['position'], + alc_data_path=['items'], + alc_marker_param='position' + ), + ] + assert mock_logger.mock_calls == [ + call.debug('Finding usage for APIs'), + call.debug('Found %d APIs', 3), + call.debug('Finding usage for per-API limits') + ] + + def test_find_usage_apis_stages_now_paginated(self): + mock_conn = Mock() + res = result_fixtures.ApiGateway.get_rest_apis + mock_paginator = Mock() + mock_paginator.paginate.return_value = res + + def se_res_paginate(restApiId=None): + return result_fixtures.ApiGateway.get_resources[restApiId] + + mock_res_paginator = Mock() + mock_res_paginator.paginate.side_effect = se_res_paginate + + def se_get_paginator(api_name): + if api_name == 'get_rest_apis': + return mock_paginator + elif api_name == 'get_resources': + return mock_res_paginator + + def se_paginate_dict(*args, **kwargs): + if args[0] == mock_conn.get_documentation_parts: + return result_fixtures.ApiGateway.doc_parts[kwargs['restApiId']] + if args[0] == mock_conn.get_authorizers: + return result_fixtures.ApiGateway.authorizers[ + kwargs['restApiId'] + ] + + def se_get_stages(restApiId=None): + r = deepcopy(result_fixtures.ApiGateway.stages[restApiId]) + r['position'] = 'foo' + return r + + mock_conn.get_paginator.side_effect = se_get_paginator + mock_conn.get_stages.side_effect = se_get_stages + cls = _ApigatewayService(21, 43) + cls.conn = mock_conn + with patch('%s.paginate_dict' % pbm, autospec=True) as mock_pd: + with patch('%s.logger' % pbm) as mock_logger: + mock_pd.side_effect = se_paginate_dict + cls._find_usage_apis() + assert mock_logger.mock_calls == [ + call.debug('Finding usage for APIs'), + call.debug('Found %d APIs', 3), + call.debug('Finding usage for per-API limits'), + call.warning( + 'APIGateway get_stages returned more keys than present in ' + 'boto3 docs: %s', ['item', 'position'] + ) + ] + + def test_find_usage_plans(self): + mock_conn = Mock() + res = result_fixtures.ApiGateway.plans + mock_paginator = Mock() + mock_paginator.paginate.return_value = res + + mock_conn.get_paginator.return_value = mock_paginator + cls = _ApigatewayService(21, 43) + cls.conn = mock_conn + with patch('%s.logger' % pbm) as mock_logger: + cls._find_usage_plans() + # APIs usage + usage = cls.limits['Usage plans per account'].get_current_usage() + assert len(usage) == 1 + assert usage[0].get_value() == 4 + assert mock_conn.mock_calls == [ + call.get_paginator('get_usage_plans'), + call.get_paginator().paginate() + ] + assert mock_paginator.mock_calls == [call.paginate()] + assert mock_logger.mock_calls == [ + call.debug('Finding usage for Usage Plans') + ] + + def test_find_usage_certs(self): + mock_conn = Mock() + res = result_fixtures.ApiGateway.certs + mock_paginator = Mock() + mock_paginator.paginate.return_value = res + + mock_conn.get_paginator.return_value = mock_paginator + cls = _ApigatewayService(21, 43) + cls.conn = mock_conn + with patch('%s.logger' % pbm) as mock_logger: + cls._find_usage_certs() + # APIs usage + usage = cls.limits[ + 'Client certificates per account'].get_current_usage() + assert len(usage) == 1 + assert usage[0].get_value() == 2 + assert mock_conn.mock_calls == [ + call.get_paginator('get_client_certificates'), + call.get_paginator().paginate() + ] + assert mock_paginator.mock_calls == [call.paginate()] + assert mock_logger.mock_calls == [ + call.debug('Finding usage for Client Certificates') + ] + + def test_find_usage_api_keys(self): + mock_conn = Mock() + res = result_fixtures.ApiGateway.api_keys + mock_paginator = Mock() + mock_paginator.paginate.return_value = res + + mock_conn.get_paginator.return_value = mock_paginator + cls = _ApigatewayService(21, 43) + cls.conn = mock_conn + with patch('%s.logger' % pbm) as mock_logger: + cls._find_usage_api_keys() + # API Keys usage + usage = cls.limits[ + 'API keys per account'].get_current_usage() + assert len(usage) == 1 + assert usage[0].get_value() == 4 + assert mock_conn.mock_calls == [ + call.get_paginator('get_api_keys'), + call.get_paginator().paginate() + ] + assert mock_paginator.mock_calls == [call.paginate()] + assert mock_logger.mock_calls == [ + call.debug('Finding usage for API Keys') + ] + + def test_required_iam_permissions(self): + cls = _ApigatewayService(21, 43) + assert cls.required_iam_permissions() == [ + "apigateway:GET", + "apigateway:HEAD", + "apigateway:OPTIONS" + ] diff --git a/docs/source/awslimitchecker.services.apigateway.rst b/docs/source/awslimitchecker.services.apigateway.rst new file mode 100644 index 00000000..facbcc83 --- /dev/null +++ b/docs/source/awslimitchecker.services.apigateway.rst @@ -0,0 +1,7 @@ +awslimitchecker\.services\.apigateway module +============================================ + +.. automodule:: awslimitchecker.services.apigateway + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/awslimitchecker.services.rst b/docs/source/awslimitchecker.services.rst index ec2f0c6f..e04b390d 100644 --- a/docs/source/awslimitchecker.services.rst +++ b/docs/source/awslimitchecker.services.rst @@ -11,6 +11,7 @@ Submodules .. toctree:: + awslimitchecker.services.apigateway awslimitchecker.services.autoscaling awslimitchecker.services.base awslimitchecker.services.cloudformation diff --git a/docs/source/cli_usage.rst b/docs/source/cli_usage.rst index b7f0628f..b439295a 100644 --- a/docs/source/cli_usage.rst +++ b/docs/source/cli_usage.rst @@ -34,7 +34,7 @@ use as a Nagios-compatible plugin). [--skip-ta] [--ta-refresh-wait | --ta-refresh-trigger | --ta-refresh-older TA_REFRESH_OLDER] [--ta-refresh-timeout TA_REFRESH_TIMEOUT] [--no-color] - [-v] [-V] + [--no-check-version] [-v] [-V] Report on AWS service limits and usage via boto3, optionally warn about any services with usage nearing or exceeding their limits. For further help, see @@ -103,6 +103,7 @@ use as a Nagios-compatible plugin). If waiting for TA checks to refresh, wait up to this number of seconds before continuing on anyway. --no-color do not colorize output + --no-check-version do not check latest version at startup -v, --verbose verbose output. specify twice for debug-level output. -V, --version print version number and exit. awslimitchecker is AGPLv3-licensed Free Software. Anyone using this program, @@ -128,11 +129,11 @@ View the AWS services currently supported by ``awslimitchecker`` with the .. code-block:: console (venv)$ awslimitchecker -s + ApiGateway AutoScaling CloudFormation EBS EC2 - EFS (...) Redshift S3 @@ -150,11 +151,11 @@ or Trusted Advisor data, run with ``--list-defaults``: .. code-block:: console (venv)$ awslimitchecker --list-defaults - AutoScaling/Auto Scaling groups 20 - AutoScaling/Launch configurations 100 - CloudFormation/Stacks 200 - EBS/Active snapshots 10000 - EBS/Active volumes 5000 + ApiGateway/API keys per account 500 + ApiGateway/APIs per account 60 + ApiGateway/Client certificates per account 60 + ApiGateway/Custom authorizers per API 10 + ApiGateway/Documentation parts per API 2000 (...) VPC/Rules per network ACL 20 VPC/Subnets per VPC 200 @@ -174,11 +175,13 @@ and limits followed by ``(API)`` have been obtained from the service's API. .. code-block:: console (venv)$ awslimitchecker -l + ApiGateway/API keys per account 500 + ApiGateway/APIs per account 60 + ApiGateway/Client certificates per account 60 + ApiGateway/Custom authorizers per API 10 + ApiGateway/Documentation parts per API 2000 + (...) AutoScaling/Auto Scaling groups 1000 (API) - AutoScaling/Launch configurations 1400 (API) - CloudFormation/Stacks 1600 (API) - EBS/Active snapshots 30000 (TA) - EBS/Active volumes 10000 (TA) (...) VPC/Rules per network ACL 20 VPC/Subnets per VPC 200 @@ -195,11 +198,13 @@ from Trusted Advisor for all commands. .. code-block:: console (venv)$ awslimitchecker -l --skip-ta + ApiGateway/API keys per account 500 + ApiGateway/APIs per account 60 + ApiGateway/Client certificates per account 60 + ApiGateway/Custom authorizers per API 10 + ApiGateway/Documentation parts per API 2000 + (...) AutoScaling/Auto Scaling groups 1000 (API) - AutoScaling/Launch configurations 1400 (API) - CloudFormation/Stacks 1600 (API) - EBS/Active snapshots 10000 - EBS/Active volumes 5000 (...) VPC/Rules per network ACL 20 VPC/Subnets per VPC 200 @@ -238,14 +243,14 @@ using their IDs). .. code-block:: console (venv)$ awslimitchecker -u - AutoScaling/Auto Scaling groups 762 - AutoScaling/Launch configurations 899 - CloudFormation/Stacks 1449 - EBS/Active snapshots 20932 - EBS/Active volumes 1851 + ApiGateway/API keys per account 19 + ApiGateway/APIs per account 54 + ApiGateway/Client certificates per account 2 + ApiGateway/Custom authorizers per API max: 0bdkl1u8vk=2 (0bdkl1u8vk=2, 0cyhj26jhb=2 (...) + ApiGateway/Documentation parts per API max: 0bdkl1u8vk=2 (0bdkl1u8vk=2, 0cyhj26jhb=2 (...) (...) VPC/Rules per network ACL max: acl-bde47dd9=6 (acl-4bd96a2e=4, acl-9703 (...) - VPC/Subnets per VPC max: vpc-c89074a9=40 (vpc-ae7bc5cb=1, vpc-1e5 (...) + VPC/Subnets per VPC max: vpc-c89074a9=40 (vpc-1e5e3c7b=1, vpc-ae7 (...) VPC/VPCs 17 @@ -267,11 +272,13 @@ For example, to override the limits of EC2's "EC2-Classic Elastic IPs" and .. code-block:: console (venv)$ awslimitchecker -L "AutoScaling/Auto Scaling groups"=321 --limit="AutoScaling/Launch configurations"=456 -l - AutoScaling/Auto Scaling groups 321 - AutoScaling/Launch configurations 456 - CloudFormation/Stacks 1600 (API) - EBS/Active snapshots 30000 (TA) - EBS/Active volumes 10000 (TA) + ApiGateway/API keys per account 500 + ApiGateway/APIs per account 60 + ApiGateway/Client certificates per account 60 + ApiGateway/Custom authorizers per API 10 + ApiGateway/Documentation parts per API 2000 + (...) + CloudFormation/Stacks 2000 (API) (...) VPC/Rules per network ACL 20 VPC/Subnets per VPC 200 @@ -307,14 +314,14 @@ threshold only, and another has crossed the critical threshold): .. code-block:: console (venv)$ awslimitchecker --no-color - CloudFormation/Stacks (limit 1600) WARNING: 1449 - EC2/Security groups per VPC (limit 500) CRITICAL: vpc-36f22951=533, vpc-c (...) + ApiGateway/APIs per account (limit 60) WARNING: 54 + EC2/Security groups per VPC (limit 500) CRITICAL: vpc-c89074a9=793 WARNIN (...) EC2/VPC security groups per elastic network interface (limit 5) CRITICAL: eni-8226ce61=5 WARNING: e (...) - EFS/File systems (limit 10) CRITICAL: 20 - ElastiCache/Parameter Groups (limit 20) WARNING: 18 + EFS/File systems (limit 10) CRITICAL: 25 + ElastiCache/Nodes (limit 100) WARNING: 82 (...) RDS/VPC Security Groups (limit 5) CRITICAL: 5 - S3/Buckets (limit 100) CRITICAL: 483 + S3/Buckets (limit 100) CRITICAL: 526 VPC/NAT Gateways per AZ (limit 5) CRITICAL: us-east-1d=9, us-east-1b= (...) @@ -327,14 +334,14 @@ To set the warning threshold of 50% and a critical threshold of 75% when checkin .. code-block:: console (venv)$ awslimitchecker -W 97 --critical=98 --no-color - EC2/Security groups per VPC (limit 500) CRITICAL: vpc-36f22951=533, vpc-c (...) + EC2/Security groups per VPC (limit 500) CRITICAL: vpc-c89074a9=793 EC2/VPC security groups per elastic network interface (limit 5) CRITICAL: eni-8226ce61=5 - EFS/File systems (limit 10) CRITICAL: 20 - ElasticBeanstalk/Application versions (limit 500) CRITICAL: 3265 - ElasticBeanstalk/Applications (limit 25) CRITICAL: 235 - ElasticBeanstalk/Environments (limit 200) CRITICAL: 589 + EFS/File systems (limit 10) CRITICAL: 25 + ElasticBeanstalk/Application versions (limit 500) CRITICAL: 3768 + ElasticBeanstalk/Applications (limit 25) CRITICAL: 251 + ElasticBeanstalk/Environments (limit 200) CRITICAL: 585 RDS/VPC Security Groups (limit 5) CRITICAL: 5 - S3/Buckets (limit 100) CRITICAL: 483 + S3/Buckets (limit 100) CRITICAL: 526 VPC/NAT Gateways per AZ (limit 5) CRITICAL: us-east-1d=9, us-east-1b= (...) @@ -353,7 +360,7 @@ permissions for it to perform all limit checks. This can be viewed with the "Statement": [ { "Action": [ - "autoscaling:DescribeAccountLimits", + "apigateway:GET", (...) } ], diff --git a/docs/source/iam_policy.rst b/docs/source/iam_policy.rst index 2444f97c..2018f1ac 100644 --- a/docs/source/iam_policy.rst +++ b/docs/source/iam_policy.rst @@ -18,6 +18,9 @@ permissions required for it to function correctly: "Statement": [ { "Action": [ + "apigateway:GET", + "apigateway:HEAD", + "apigateway:OPTIONS", "autoscaling:DescribeAccountLimits", "autoscaling:DescribeAutoScalingGroups", "autoscaling:DescribeLaunchConfigurations", diff --git a/docs/source/limits.rst b/docs/source/limits.rst index d8a6bae6..a5d8d050 100644 --- a/docs/source/limits.rst +++ b/docs/source/limits.rst @@ -60,8 +60,6 @@ updated from Trusted Advisor: * Running On-Demand c4.xlarge instances - * Running On-Demand m1.small instances - * Running On-Demand m3.2xlarge instances * Running On-Demand m3.large instances @@ -262,6 +260,24 @@ The section below lists every limit that this version of awslimitchecker knows how to check, and its hard-coded default value (per AWS documentation). Limits marked with :sup:`(TA)` are comfirmed as being updated by Trusted Advisor. +.. _limits.ApiGateway: + +ApiGateway ++++++++++++ + +=============================== ==== +Limit Default +=============================== ==== +API keys per account 500 +APIs per account 60 +Client certificates per account 60 +Custom authorizers per API 10 +Documentation parts per API 2000 +Resources per API 300 +Stages per API 10 +Usage plans per account 300 +=============================== ==== + .. _limits.AutoScaling: AutoScaling @@ -366,7 +382,7 @@ Running On-Demand i3.large instances 2 Running On-Demand i3.xlarge instances 2 Running On-Demand m1.large instances 20 Running On-Demand m1.medium instances 20 -Running On-Demand m1.small instances :sup:`(TA)` 20 +Running On-Demand m1.small instances 20 Running On-Demand m1.xlarge instances 20 Running On-Demand m2.2xlarge instances 20 Running On-Demand m2.4xlarge instances 20