Adding support for querying AWS Config for supported configurations.

At this time, only adding support for S3.
This commit is contained in:
Mike Grima 2019-09-23 17:16:20 -07:00
commit c4b310d7a5
10 changed files with 675 additions and 2 deletions

View file

@ -230,3 +230,27 @@ class TooManyTags(JsonRESTError):
super(TooManyTags, self).__init__(
'ValidationException', "1 validation error detected: Value '{}' at '{}' failed to satisfy "
"constraint: Member must have length less than or equal to 50.".format(tags, param))
class InvalidResourceParameters(JsonRESTError):
code = 400
def __init__(self):
super(InvalidResourceParameters, self).__init__('ValidationException', 'Both Resource ID and Resource Name '
'cannot be specified in the request')
class InvalidLimit(JsonRESTError):
code = 400
def __init__(self, value):
super(InvalidLimit, self).__init__('ValidationException', 'Value \'{value}\' at \'limit\' failed to satisify constraint: Member'
' must have value less than or equal to 100'.format(value=value))
class TooManyResourceIds(JsonRESTError):
code = 400
def __init__(self):
super(TooManyResourceIds, self).__init__('ValidationException', "The specified list had more than 20 resource ID's. "
"It must have '20' or less items")

View file

@ -17,11 +17,12 @@ from moto.config.exceptions import InvalidResourceTypeException, InvalidDelivery
InvalidSNSTopicARNException, MaxNumberOfDeliveryChannelsExceededException, NoAvailableDeliveryChannelException, \
NoSuchDeliveryChannelException, LastDeliveryChannelDeleteFailedException, TagKeyTooBig, \
TooManyTags, TagValueTooBig, TooManyAccountSources, InvalidParameterValueException, InvalidNextTokenException, \
NoSuchConfigurationAggregatorException, InvalidTagCharacters, DuplicateTags
NoSuchConfigurationAggregatorException, InvalidTagCharacters, DuplicateTags, InvalidLimit, InvalidResourceParameters, TooManyResourceIds
from moto.core import BaseBackend, BaseModel
from moto.s3.config import s3_config_query
DEFAULT_ACCOUNT_ID = 123456789012
DEFAULT_ACCOUNT_ID = '123456789012'
POP_STRINGS = [
'capitalizeStart',
'CapitalizeStart',
@ -32,6 +33,11 @@ POP_STRINGS = [
]
DEFAULT_PAGE_SIZE = 100
# Map the Config resource type to a backend:
RESOURCE_MAP = {
'AWS::S3::Bucket': s3_config_query
}
def datetime2int(date):
return int(time.mktime(date.timetuple()))
@ -680,6 +686,110 @@ class ConfigBackend(BaseBackend):
del self.delivery_channels[channel_name]
def list_discovered_resources(self, resource_type, backend_region, resource_ids, resource_name, limit, next_token):
"""This will query against the mocked AWS Config listing function that must exist for the resource backend.
:param resource_type:
:param backend_region:
:param ids:
:param name:
:param limit:
:param next_token:
:return:
"""
identifiers = []
new_token = None
limit = limit or DEFAULT_PAGE_SIZE
if limit > DEFAULT_PAGE_SIZE:
raise InvalidLimit(limit)
if resource_ids and resource_name:
raise InvalidResourceParameters()
# Only 20 maximum Resource IDs:
if resource_ids and len(resource_ids) > 20:
raise TooManyResourceIds()
# If the resource type exists and the backend region is implemented in moto, then
# call upon the resource type's Config Query class to retrieve the list of resources that match the criteria:
if RESOURCE_MAP.get(resource_type, {}):
# Is this a global resource type? -- if so, re-write the region to 'global':
if RESOURCE_MAP[resource_type].backends.get('global'):
backend_region = 'global'
# For non-aggregated queries, the we only care about the backend_region. Need to verify that moto has implemented
# the region for the given backend:
if RESOURCE_MAP[resource_type].backends.get(backend_region):
# Fetch the resources for the backend's region:
identifiers, new_token = \
RESOURCE_MAP[resource_type].list_config_service_resources(resource_ids, resource_name, limit, next_token)
result = {'resourceIdentifiers': [
{
'resourceType': identifier['type'],
'resourceId': identifier['id'],
'resourceName': identifier['name']
}
for identifier in identifiers]
}
if new_token:
result['nextToken'] = new_token
return result
def list_aggregate_discovered_resources(self, aggregator_name, resource_type, filters, limit, next_token):
"""This will query against the mocked AWS Config listing function that must exist for the resource backend.
As far a moto goes -- the only real difference between this function and the `list_discovered_resources` function is that
this will require a Config Aggregator be set up a priori and can search based on resource regions.
:param aggregator_name:
:param resource_type:
:param filters:
:param limit:
:param next_token:
:return:
"""
if not self.config_aggregators.get(aggregator_name):
raise NoSuchConfigurationAggregatorException()
identifiers = []
new_token = None
filters = filters or {}
limit = limit or DEFAULT_PAGE_SIZE
if limit > DEFAULT_PAGE_SIZE:
raise InvalidLimit(limit)
# If the resource type exists and the backend region is implemented in moto, then
# call upon the resource type's Config Query class to retrieve the list of resources that match the criteria:
if RESOURCE_MAP.get(resource_type, {}):
# We only care about a filter's Region, Resource Name, and Resource ID:
resource_region = filters.get('Region')
resource_id = [filters['ResourceId']] if filters.get('ResourceId') else None
resource_name = filters.get('ResourceName')
identifiers, new_token = \
RESOURCE_MAP[resource_type].list_config_service_resources(resource_id, resource_name, limit, next_token,
resource_region=resource_region)
result = {'ResourceIdentifiers': [
{
'SourceAccountId': DEFAULT_ACCOUNT_ID,
'SourceRegion': identifier['region'],
'ResourceType': identifier['type'],
'ResourceId': identifier['id'],
'ResourceName': identifier['name']
}
for identifier in identifiers]
}
if new_token:
result['NextToken'] = new_token
return result
config_backends = {}
boto3_session = Session()

View file

@ -84,3 +84,34 @@ class ConfigResponse(BaseResponse):
def stop_configuration_recorder(self):
self.config_backend.stop_configuration_recorder(self._get_param('ConfigurationRecorderName'))
return ""
def list_discovered_resources(self):
schema = self.config_backend.list_discovered_resources(self._get_param('resourceType'),
self.region,
self._get_param('resourceIds'),
self._get_param('resourceName'),
self._get_param('limit'),
self._get_param('nextToken'))
return json.dumps(schema)
def list_aggregate_discovered_resources(self):
schema = self.config_backend.list_aggregate_discovered_resources(self._get_param('ConfigurationAggregatorName'),
self._get_param('ResourceType'),
self._get_param('Filters'),
self._get_param('Limit'),
self._get_param('NextToken'))
return json.dumps(schema)
"""
def batch_get_resource_config(self):
# TODO implement me!
return ""
def batch_get_aggregate_resource_config(self):
# TODO implement me!
return ""
def get_resource_config_history(self):
# TODO implement me!
return ""
"""