TechDebt: MyPy Config (#5633)

This commit is contained in:
Bert Blommers 2022-11-03 18:32:00 -01:00 committed by GitHub
parent 79aaf2bc03
commit 2500affb1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 358 additions and 282 deletions

View File

@ -1,10 +1,11 @@
from moto.core.exceptions import JsonRESTError from moto.core.exceptions import JsonRESTError
from typing import Any, List, Optional
class NameTooLongException(JsonRESTError): class NameTooLongException(JsonRESTError):
code = 400 code = 400
def __init__(self, name, location, max_limit=256): def __init__(self, name: str, location: str, max_limit: int = 256):
message = ( message = (
f"1 validation error detected: Value '{name}' at '{location}' " f"1 validation error detected: Value '{name}' at '{location}' "
f"failed to satisfy constraint: Member must have length less " f"failed to satisfy constraint: Member must have length less "
@ -16,7 +17,7 @@ class NameTooLongException(JsonRESTError):
class InvalidConfigurationRecorderNameException(JsonRESTError): class InvalidConfigurationRecorderNameException(JsonRESTError):
code = 400 code = 400
def __init__(self, name): def __init__(self, name: Optional[str]):
message = ( message = (
f"The configuration recorder name '{name}' is not valid, blank string." f"The configuration recorder name '{name}' is not valid, blank string."
) )
@ -26,7 +27,7 @@ class InvalidConfigurationRecorderNameException(JsonRESTError):
class MaxNumberOfConfigurationRecordersExceededException(JsonRESTError): class MaxNumberOfConfigurationRecordersExceededException(JsonRESTError):
code = 400 code = 400
def __init__(self, name): def __init__(self, name: str):
message = ( message = (
f"Failed to put configuration recorder '{name}' because the maximum number of " f"Failed to put configuration recorder '{name}' because the maximum number of "
"configuration recorders: 1 is reached." "configuration recorders: 1 is reached."
@ -37,7 +38,7 @@ class MaxNumberOfConfigurationRecordersExceededException(JsonRESTError):
class InvalidRecordingGroupException(JsonRESTError): class InvalidRecordingGroupException(JsonRESTError):
code = 400 code = 400
def __init__(self): def __init__(self) -> None:
message = "The recording group provided is not valid" message = "The recording group provided is not valid"
super().__init__("InvalidRecordingGroupException", message) super().__init__("InvalidRecordingGroupException", message)
@ -45,7 +46,7 @@ class InvalidRecordingGroupException(JsonRESTError):
class InvalidResourceTypeException(JsonRESTError): class InvalidResourceTypeException(JsonRESTError):
code = 400 code = 400
def __init__(self, bad_list, good_list): def __init__(self, bad_list: List[str], good_list: Any):
message = ( message = (
f"{len(bad_list)} validation error detected: Value '{bad_list}' at " f"{len(bad_list)} validation error detected: Value '{bad_list}' at "
"'configurationRecorder.recordingGroup.resourceTypes' failed to satisfy constraint: " "'configurationRecorder.recordingGroup.resourceTypes' failed to satisfy constraint: "
@ -58,7 +59,7 @@ class InvalidResourceTypeException(JsonRESTError):
class NoSuchConfigurationAggregatorException(JsonRESTError): class NoSuchConfigurationAggregatorException(JsonRESTError):
code = 400 code = 400
def __init__(self, number=1): def __init__(self, number: int = 1):
if number == 1: if number == 1:
message = "The configuration aggregator does not exist. Check the configuration aggregator name and try again." message = "The configuration aggregator does not exist. Check the configuration aggregator name and try again."
else: else:
@ -72,7 +73,7 @@ class NoSuchConfigurationAggregatorException(JsonRESTError):
class NoSuchConfigurationRecorderException(JsonRESTError): class NoSuchConfigurationRecorderException(JsonRESTError):
code = 400 code = 400
def __init__(self, name): def __init__(self, name: str):
message = ( message = (
f"Cannot find configuration recorder with the specified name '{name}'." f"Cannot find configuration recorder with the specified name '{name}'."
) )
@ -82,7 +83,7 @@ class NoSuchConfigurationRecorderException(JsonRESTError):
class InvalidDeliveryChannelNameException(JsonRESTError): class InvalidDeliveryChannelNameException(JsonRESTError):
code = 400 code = 400
def __init__(self, name): def __init__(self, name: Optional[str]):
message = f"The delivery channel name '{name}' is not valid, blank string." message = f"The delivery channel name '{name}' is not valid, blank string."
super().__init__("InvalidDeliveryChannelNameException", message) super().__init__("InvalidDeliveryChannelNameException", message)
@ -92,7 +93,7 @@ class NoSuchBucketException(JsonRESTError):
code = 400 code = 400
def __init__(self): def __init__(self) -> None:
message = "Cannot find a S3 bucket with an empty bucket name." message = "Cannot find a S3 bucket with an empty bucket name."
super().__init__("NoSuchBucketException", message) super().__init__("NoSuchBucketException", message)
@ -100,7 +101,7 @@ class NoSuchBucketException(JsonRESTError):
class InvalidNextTokenException(JsonRESTError): class InvalidNextTokenException(JsonRESTError):
code = 400 code = 400
def __init__(self): def __init__(self) -> None:
message = "The nextToken provided is invalid" message = "The nextToken provided is invalid"
super().__init__("InvalidNextTokenException", message) super().__init__("InvalidNextTokenException", message)
@ -108,7 +109,7 @@ class InvalidNextTokenException(JsonRESTError):
class InvalidS3KeyPrefixException(JsonRESTError): class InvalidS3KeyPrefixException(JsonRESTError):
code = 400 code = 400
def __init__(self): def __init__(self) -> None:
message = "The s3 key prefix '' is not valid, empty s3 key prefix." message = "The s3 key prefix '' is not valid, empty s3 key prefix."
super().__init__("InvalidS3KeyPrefixException", message) super().__init__("InvalidS3KeyPrefixException", message)
@ -118,7 +119,7 @@ class InvalidSNSTopicARNException(JsonRESTError):
code = 400 code = 400
def __init__(self): def __init__(self) -> None:
message = "The sns topic arn '' is not valid." message = "The sns topic arn '' is not valid."
super().__init__("InvalidSNSTopicARNException", message) super().__init__("InvalidSNSTopicARNException", message)
@ -126,7 +127,7 @@ class InvalidSNSTopicARNException(JsonRESTError):
class InvalidDeliveryFrequency(JsonRESTError): class InvalidDeliveryFrequency(JsonRESTError):
code = 400 code = 400
def __init__(self, value, good_list): def __init__(self, value: str, good_list: Any):
message = ( message = (
f"1 validation error detected: Value '{value}' at " f"1 validation error detected: Value '{value}' at "
"'deliveryChannel.configSnapshotDeliveryProperties.deliveryFrequency' failed to satisfy " "'deliveryChannel.configSnapshotDeliveryProperties.deliveryFrequency' failed to satisfy "
@ -138,7 +139,7 @@ class InvalidDeliveryFrequency(JsonRESTError):
class MaxNumberOfDeliveryChannelsExceededException(JsonRESTError): class MaxNumberOfDeliveryChannelsExceededException(JsonRESTError):
code = 400 code = 400
def __init__(self, name): def __init__(self, name: str):
message = f"Failed to put delivery channel '{name}' because the maximum number of delivery channels: 1 is reached." message = f"Failed to put delivery channel '{name}' because the maximum number of delivery channels: 1 is reached."
super().__init__("MaxNumberOfDeliveryChannelsExceededException", message) super().__init__("MaxNumberOfDeliveryChannelsExceededException", message)
@ -146,7 +147,7 @@ class MaxNumberOfDeliveryChannelsExceededException(JsonRESTError):
class NoSuchDeliveryChannelException(JsonRESTError): class NoSuchDeliveryChannelException(JsonRESTError):
code = 400 code = 400
def __init__(self, name): def __init__(self, name: str):
message = f"Cannot find delivery channel with specified name '{name}'." message = f"Cannot find delivery channel with specified name '{name}'."
super().__init__("NoSuchDeliveryChannelException", message) super().__init__("NoSuchDeliveryChannelException", message)
@ -154,7 +155,7 @@ class NoSuchDeliveryChannelException(JsonRESTError):
class NoAvailableConfigurationRecorderException(JsonRESTError): class NoAvailableConfigurationRecorderException(JsonRESTError):
code = 400 code = 400
def __init__(self): def __init__(self) -> None:
message = "Configuration recorder is not available to put delivery channel." message = "Configuration recorder is not available to put delivery channel."
super().__init__("NoAvailableConfigurationRecorderException", message) super().__init__("NoAvailableConfigurationRecorderException", message)
@ -162,7 +163,7 @@ class NoAvailableConfigurationRecorderException(JsonRESTError):
class NoAvailableDeliveryChannelException(JsonRESTError): class NoAvailableDeliveryChannelException(JsonRESTError):
code = 400 code = 400
def __init__(self): def __init__(self) -> None:
message = "Delivery channel is not available to start configuration recorder." message = "Delivery channel is not available to start configuration recorder."
super().__init__("NoAvailableDeliveryChannelException", message) super().__init__("NoAvailableDeliveryChannelException", message)
@ -170,7 +171,7 @@ class NoAvailableDeliveryChannelException(JsonRESTError):
class LastDeliveryChannelDeleteFailedException(JsonRESTError): class LastDeliveryChannelDeleteFailedException(JsonRESTError):
code = 400 code = 400
def __init__(self, name): def __init__(self, name: str):
message = ( message = (
f"Failed to delete last specified delivery channel with name '{name}', because there, " f"Failed to delete last specified delivery channel with name '{name}', because there, "
"because there is a running configuration recorder." "because there is a running configuration recorder."
@ -181,7 +182,7 @@ class LastDeliveryChannelDeleteFailedException(JsonRESTError):
class TooManyAccountSources(JsonRESTError): class TooManyAccountSources(JsonRESTError):
code = 400 code = 400
def __init__(self, length): def __init__(self, length: int):
locations = ["com.amazonaws.xyz"] * length locations = ["com.amazonaws.xyz"] * length
message = ( message = (
@ -196,7 +197,7 @@ class TooManyAccountSources(JsonRESTError):
class DuplicateTags(JsonRESTError): class DuplicateTags(JsonRESTError):
code = 400 code = 400
def __init__(self): def __init__(self) -> None:
super().__init__( super().__init__(
"InvalidInput", "InvalidInput",
"Duplicate tag keys found. Please note that Tag keys are case insensitive.", "Duplicate tag keys found. Please note that Tag keys are case insensitive.",
@ -206,7 +207,7 @@ class DuplicateTags(JsonRESTError):
class TagKeyTooBig(JsonRESTError): class TagKeyTooBig(JsonRESTError):
code = 400 code = 400
def __init__(self, tag, param="tags.X.member.key"): def __init__(self, tag: str, param: str = "tags.X.member.key"):
super().__init__( super().__init__(
"ValidationException", "ValidationException",
f"1 validation error detected: Value '{tag}' at '{param}' failed to satisfy " f"1 validation error detected: Value '{tag}' at '{param}' failed to satisfy "
@ -217,7 +218,7 @@ class TagKeyTooBig(JsonRESTError):
class TagValueTooBig(JsonRESTError): class TagValueTooBig(JsonRESTError):
code = 400 code = 400
def __init__(self, tag, param="tags.X.member.value"): def __init__(self, tag: str, param: str = "tags.X.member.value"):
super().__init__( super().__init__(
"ValidationException", "ValidationException",
f"1 validation error detected: Value '{tag}' at '{param}' failed to satisfy " f"1 validation error detected: Value '{tag}' at '{param}' failed to satisfy "
@ -228,14 +229,14 @@ class TagValueTooBig(JsonRESTError):
class InvalidParameterValueException(JsonRESTError): class InvalidParameterValueException(JsonRESTError):
code = 400 code = 400
def __init__(self, message): def __init__(self, message: str):
super().__init__("InvalidParameterValueException", message) super().__init__("InvalidParameterValueException", message)
class InvalidTagCharacters(JsonRESTError): class InvalidTagCharacters(JsonRESTError):
code = 400 code = 400
def __init__(self, tag, param="tags.X.member.key"): def __init__(self, tag: str, param: str = "tags.X.member.key"):
message = f"1 validation error detected: Value '{tag}' at '{param}' failed to satisfy " message = f"1 validation error detected: Value '{tag}' at '{param}' failed to satisfy "
message += "constraint: Member must satisfy regular expression pattern: [\\\\p{L}\\\\p{Z}\\\\p{N}_.:/=+\\\\-@]+" message += "constraint: Member must satisfy regular expression pattern: [\\\\p{L}\\\\p{Z}\\\\p{N}_.:/=+\\\\-@]+"
@ -245,7 +246,7 @@ class InvalidTagCharacters(JsonRESTError):
class TooManyTags(JsonRESTError): class TooManyTags(JsonRESTError):
code = 400 code = 400
def __init__(self, tags, param="tags"): def __init__(self, tags: Any, param: str = "tags"):
super().__init__( super().__init__(
"ValidationException", "ValidationException",
f"1 validation error detected: Value '{tags}' at '{param}' failed to satisfy " f"1 validation error detected: Value '{tags}' at '{param}' failed to satisfy "
@ -256,7 +257,7 @@ class TooManyTags(JsonRESTError):
class InvalidResourceParameters(JsonRESTError): class InvalidResourceParameters(JsonRESTError):
code = 400 code = 400
def __init__(self): def __init__(self) -> None:
super().__init__( super().__init__(
"ValidationException", "ValidationException",
"Both Resource ID and Resource Name " "cannot be specified in the request", "Both Resource ID and Resource Name " "cannot be specified in the request",
@ -266,7 +267,7 @@ class InvalidResourceParameters(JsonRESTError):
class InvalidLimitException(JsonRESTError): class InvalidLimitException(JsonRESTError):
code = 400 code = 400
def __init__(self, value): def __init__(self, value: int):
super().__init__( super().__init__(
"InvalidLimitException", "InvalidLimitException",
f"Value '{value}' at 'limit' failed to satisfy constraint: Member" f"Value '{value}' at 'limit' failed to satisfy constraint: Member"
@ -277,7 +278,7 @@ class InvalidLimitException(JsonRESTError):
class TooManyResourceIds(JsonRESTError): class TooManyResourceIds(JsonRESTError):
code = 400 code = 400
def __init__(self): def __init__(self) -> None:
super().__init__( super().__init__(
"ValidationException", "ValidationException",
"The specified list had more than 20 resource ID's. " "The specified list had more than 20 resource ID's. "
@ -288,7 +289,7 @@ class TooManyResourceIds(JsonRESTError):
class ResourceNotDiscoveredException(JsonRESTError): class ResourceNotDiscoveredException(JsonRESTError):
code = 400 code = 400
def __init__(self, resource_type, resource): def __init__(self, resource_type: str, resource: str):
super().__init__( super().__init__(
"ResourceNotDiscoveredException", "ResourceNotDiscoveredException",
f"Resource {resource} of resourceType:{resource_type} is unknown or has not been discovered", f"Resource {resource} of resourceType:{resource_type} is unknown or has not been discovered",
@ -298,7 +299,7 @@ class ResourceNotDiscoveredException(JsonRESTError):
class ResourceNotFoundException(JsonRESTError): class ResourceNotFoundException(JsonRESTError):
code = 400 code = 400
def __init__(self, resource_arn): def __init__(self, resource_arn: str):
super().__init__( super().__init__(
"ResourceNotFoundException", f"ResourceArn '{resource_arn}' does not exist" "ResourceNotFoundException", f"ResourceArn '{resource_arn}' does not exist"
) )
@ -307,7 +308,7 @@ class ResourceNotFoundException(JsonRESTError):
class TooManyResourceKeys(JsonRESTError): class TooManyResourceKeys(JsonRESTError):
code = 400 code = 400
def __init__(self, bad_list): def __init__(self, bad_list: List[str]):
message = ( message = (
f"1 validation error detected: Value '{bad_list}' at " f"1 validation error detected: Value '{bad_list}' at "
"'resourceKeys' failed to satisfy constraint: " "'resourceKeys' failed to satisfy constraint: "
@ -319,7 +320,7 @@ class TooManyResourceKeys(JsonRESTError):
class InvalidResultTokenException(JsonRESTError): class InvalidResultTokenException(JsonRESTError):
code = 400 code = 400
def __init__(self): def __init__(self) -> None:
message = "The resultToken provided is invalid" message = "The resultToken provided is invalid"
super().__init__("InvalidResultTokenException", message) super().__init__("InvalidResultTokenException", message)
@ -327,21 +328,21 @@ class InvalidResultTokenException(JsonRESTError):
class ValidationException(JsonRESTError): class ValidationException(JsonRESTError):
code = 400 code = 400
def __init__(self, message): def __init__(self, message: str):
super().__init__("ValidationException", message) super().__init__("ValidationException", message)
class NoSuchOrganizationConformancePackException(JsonRESTError): class NoSuchOrganizationConformancePackException(JsonRESTError):
code = 400 code = 400
def __init__(self, message): def __init__(self, message: str):
super().__init__("NoSuchOrganizationConformancePackException", message) super().__init__("NoSuchOrganizationConformancePackException", message)
class MaxNumberOfConfigRulesExceededException(JsonRESTError): class MaxNumberOfConfigRulesExceededException(JsonRESTError):
code = 400 code = 400
def __init__(self, name, max_limit): def __init__(self, name: str, max_limit: int):
message = f"Failed to put config rule '{name}' because the maximum number of config rules: {max_limit} is reached." message = f"Failed to put config rule '{name}' because the maximum number of config rules: {max_limit} is reached."
super().__init__("MaxNumberOfConfigRulesExceededException", message) super().__init__("MaxNumberOfConfigRulesExceededException", message)
@ -349,21 +350,21 @@ class MaxNumberOfConfigRulesExceededException(JsonRESTError):
class ResourceInUseException(JsonRESTError): class ResourceInUseException(JsonRESTError):
code = 400 code = 400
def __init__(self, message): def __init__(self, message: str):
super().__init__("ResourceInUseException", message) super().__init__("ResourceInUseException", message)
class InsufficientPermissionsException(JsonRESTError): class InsufficientPermissionsException(JsonRESTError):
code = 400 code = 400
def __init__(self, message): def __init__(self, message: str):
super().__init__("InsufficientPermissionsException", message) super().__init__("InsufficientPermissionsException", message)
class NoSuchConfigRuleException(JsonRESTError): class NoSuchConfigRuleException(JsonRESTError):
code = 400 code = 400
def __init__(self, rule_name): def __init__(self, rule_name: str):
message = f"The ConfigRule '{rule_name}' provided in the request is invalid. Please check the configRule name" message = f"The ConfigRule '{rule_name}' provided in the request is invalid. Please check the configRule name"
super().__init__("NoSuchConfigRuleException", message) super().__init__("NoSuchConfigRuleException", message)
@ -371,5 +372,5 @@ class NoSuchConfigRuleException(JsonRESTError):
class MissingRequiredConfigRuleParameterException(JsonRESTError): class MissingRequiredConfigRuleParameterException(JsonRESTError):
code = 400 code = 400
def __init__(self, message): def __init__(self, message: str):
super().__init__("ParamValidationError", message) super().__init__("ParamValidationError", message)

View File

@ -4,6 +4,7 @@ import re
import time import time
from datetime import datetime from datetime import datetime
from typing import Any, Dict, List, Optional, Union
from moto.config.exceptions import ( from moto.config.exceptions import (
InvalidResourceTypeException, InvalidResourceTypeException,
@ -48,6 +49,7 @@ from moto.config.exceptions import (
) )
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
from moto.core.common_models import ConfigQueryModel
from moto.core.responses import AWSServiceSpec from moto.core.responses import AWSServiceSpec
from moto.core.utils import BackendDict from moto.core.utils import BackendDict
from moto.iam.config import role_config_query, policy_config_query from moto.iam.config import role_config_query, policy_config_query
@ -69,7 +71,7 @@ DEFAULT_PAGE_SIZE = 100
CONFIG_RULE_PAGE_SIZE = 25 CONFIG_RULE_PAGE_SIZE = 25
# Map the Config resource type to a backend: # Map the Config resource type to a backend:
RESOURCE_MAP = { RESOURCE_MAP: Dict[str, ConfigQueryModel] = {
"AWS::S3::Bucket": s3_config_query, "AWS::S3::Bucket": s3_config_query,
"AWS::S3::AccountPublicAccessBlock": s3_account_public_access_block_query, "AWS::S3::AccountPublicAccessBlock": s3_account_public_access_block_query,
"AWS::IAM::Role": role_config_query, "AWS::IAM::Role": role_config_query,
@ -81,14 +83,14 @@ CAMEL_TO_SNAKE_REGEX = re.compile(r"(?<!^)(?=[A-Z])")
MAX_TAGS_IN_ARG = 50 MAX_TAGS_IN_ARG = 50
MANAGED_RULES = load_resource(__name__, "resources/aws_managed_rules.json") MANAGED_RULES = load_resource(__name__, "resources/aws_managed_rules.json")
MANAGED_RULES_CONSTRAINTS = MANAGED_RULES["ManagedRules"] MANAGED_RULES_CONSTRAINTS = MANAGED_RULES["ManagedRules"] # type: ignore[index]
def datetime2int(date): def datetime2int(date: datetime) -> int:
return int(time.mktime(date.timetuple())) return int(time.mktime(date.timetuple()))
def snake_to_camels(original, cap_start, cap_arn): def snake_to_camels(original: str, cap_start: bool, cap_arn: bool) -> str:
parts = original.split("_") parts = original.split("_")
camel_cased = parts[0].lower() + "".join(p.title() for p in parts[1:]) camel_cased = parts[0].lower() + "".join(p.title() for p in parts[1:])
@ -104,12 +106,12 @@ def snake_to_camels(original, cap_start, cap_arn):
return camel_cased return camel_cased
def random_string(): def random_string() -> str:
"""Returns a random set of 8 lowercase letters for the Config Aggregator ARN""" """Returns a random set of 8 lowercase letters for the Config Aggregator ARN"""
return random.get_random_string(length=8, include_digits=False, lower_case=True) return random.get_random_string(length=8, include_digits=False, lower_case=True)
def validate_tag_key(tag_key, exception_param="tags.X.member.key"): def validate_tag_key(tag_key: str, exception_param: str = "tags.X.member.key") -> None:
"""Validates the tag key. """Validates the tag key.
:param tag_key: The tag key to check against. :param tag_key: The tag key to check against.
@ -131,7 +133,7 @@ def validate_tag_key(tag_key, exception_param="tags.X.member.key"):
raise InvalidTagCharacters(tag_key, param=exception_param) raise InvalidTagCharacters(tag_key, param=exception_param)
def check_tag_duplicate(all_tags, tag_key): def check_tag_duplicate(all_tags: Dict[str, str], tag_key: str) -> None:
"""Validates that a tag key is not a duplicate """Validates that a tag key is not a duplicate
:param all_tags: Dict to check if there is a duplicate tag. :param all_tags: Dict to check if there is a duplicate tag.
@ -142,8 +144,8 @@ def check_tag_duplicate(all_tags, tag_key):
raise DuplicateTags() raise DuplicateTags()
def validate_tags(tags): def validate_tags(tags: List[Dict[str, str]]) -> Dict[str, str]:
proper_tags = {} proper_tags: Dict[str, str] = {}
if len(tags) > MAX_TAGS_IN_ARG: if len(tags) > MAX_TAGS_IN_ARG:
raise TooManyTags(tags) raise TooManyTags(tags)
@ -162,7 +164,7 @@ def validate_tags(tags):
return proper_tags return proper_tags
def convert_to_class_args(dict_arg): def convert_to_class_args(dict_arg: Dict[str, Any]) -> Dict[str, Any]:
"""Return dict that can be used to instantiate it's representative class. """Return dict that can be used to instantiate it's representative class.
Given a dictionary in the incoming API request, convert the keys to Given a dictionary in the incoming API request, convert the keys to
@ -184,7 +186,7 @@ class ConfigEmptyDictable(BaseModel):
This assumes that the sub-class will NOT return 'None's in the JSON. This assumes that the sub-class will NOT return 'None's in the JSON.
""" """
def __init__(self, capitalize_start=False, capitalize_arn=True): def __init__(self, capitalize_start: bool = False, capitalize_arn: bool = True):
"""Assists with the serialization of the config object """Assists with the serialization of the config object
:param capitalize_start: For some Config services, the first letter :param capitalize_start: For some Config services, the first letter
is lowercase -- for others it's capital is lowercase -- for others it's capital
@ -194,8 +196,8 @@ class ConfigEmptyDictable(BaseModel):
self.capitalize_start = capitalize_start self.capitalize_start = capitalize_start
self.capitalize_arn = capitalize_arn self.capitalize_arn = capitalize_arn
def to_dict(self): def to_dict(self) -> Dict[str, Any]:
data = {} data: Dict[str, Any] = {}
for item, value in self.__dict__.items(): for item, value in self.__dict__.items():
# ignore private attributes # ignore private attributes
if not item.startswith("_") and value is not None: if not item.startswith("_") and value is not None:
@ -220,32 +222,32 @@ class ConfigEmptyDictable(BaseModel):
class ConfigRecorderStatus(ConfigEmptyDictable): class ConfigRecorderStatus(ConfigEmptyDictable):
def __init__(self, name): def __init__(self, name: str):
super().__init__() super().__init__()
self.name = name self.name = name
self.recording = False self.recording = False
self.last_start_time = None self.last_start_time: Optional[int] = None
self.last_stop_time = None self.last_stop_time: Optional[int] = None
self.last_status = None self.last_status: Optional[str] = None
self.last_error_code = None self.last_error_code: Optional[str] = None
self.last_error_message = None self.last_error_message: Optional[str] = None
self.last_status_change_time = None self.last_status_change_time: Optional[int] = None
def start(self): def start(self) -> None:
self.recording = True self.recording = True
self.last_status = "PENDING" self.last_status = "PENDING"
self.last_start_time = datetime2int(datetime.utcnow()) self.last_start_time = datetime2int(datetime.utcnow())
self.last_status_change_time = datetime2int(datetime.utcnow()) self.last_status_change_time = datetime2int(datetime.utcnow())
def stop(self): def stop(self) -> None:
self.recording = False self.recording = False
self.last_stop_time = datetime2int(datetime.utcnow()) self.last_stop_time = datetime2int(datetime.utcnow())
self.last_status_change_time = datetime2int(datetime.utcnow()) self.last_status_change_time = datetime2int(datetime.utcnow())
class ConfigDeliverySnapshotProperties(ConfigEmptyDictable): class ConfigDeliverySnapshotProperties(ConfigEmptyDictable):
def __init__(self, delivery_frequency): def __init__(self, delivery_frequency: str):
super().__init__() super().__init__()
self.delivery_frequency = delivery_frequency self.delivery_frequency = delivery_frequency
@ -253,7 +255,12 @@ class ConfigDeliverySnapshotProperties(ConfigEmptyDictable):
class ConfigDeliveryChannel(ConfigEmptyDictable): class ConfigDeliveryChannel(ConfigEmptyDictable):
def __init__( def __init__(
self, name, s3_bucket_name, prefix=None, sns_arn=None, snapshot_properties=None self,
name: str,
s3_bucket_name: str,
prefix: Optional[str] = None,
sns_arn: Optional[str] = None,
snapshot_properties: Optional[ConfigDeliverySnapshotProperties] = None,
): ):
super().__init__() super().__init__()
@ -267,9 +274,9 @@ class ConfigDeliveryChannel(ConfigEmptyDictable):
class RecordingGroup(ConfigEmptyDictable): class RecordingGroup(ConfigEmptyDictable):
def __init__( def __init__(
self, self,
all_supported=True, all_supported: bool = True,
include_global_resource_types=False, include_global_resource_types: bool = False,
resource_types=None, resource_types: Optional[List[str]] = None,
): ):
super().__init__() super().__init__()
@ -279,7 +286,13 @@ class RecordingGroup(ConfigEmptyDictable):
class ConfigRecorder(ConfigEmptyDictable): class ConfigRecorder(ConfigEmptyDictable):
def __init__(self, role_arn, recording_group, name="default", status=None): def __init__(
self,
role_arn: str,
recording_group: RecordingGroup,
name: str = "default",
status: Optional[ConfigRecorderStatus] = None,
):
super().__init__() super().__init__()
self.name = name self.name = name
@ -293,7 +306,12 @@ class ConfigRecorder(ConfigEmptyDictable):
class AccountAggregatorSource(ConfigEmptyDictable): class AccountAggregatorSource(ConfigEmptyDictable):
def __init__(self, account_ids, aws_regions=None, all_aws_regions=None): def __init__(
self,
account_ids: List[str],
aws_regions: Optional[List[str]] = None,
all_aws_regions: Optional[bool] = None,
):
super().__init__(capitalize_start=True) super().__init__(capitalize_start=True)
# Can't have both the regions and all_regions flag present -- also # Can't have both the regions and all_regions flag present -- also
@ -321,7 +339,12 @@ class AccountAggregatorSource(ConfigEmptyDictable):
class OrganizationAggregationSource(ConfigEmptyDictable): class OrganizationAggregationSource(ConfigEmptyDictable):
def __init__(self, role_arn, aws_regions=None, all_aws_regions=None): def __init__(
self,
role_arn: str,
aws_regions: Optional[List[str]] = None,
all_aws_regions: Optional[bool] = None,
):
super().__init__(capitalize_start=True, capitalize_arn=False) super().__init__(capitalize_start=True, capitalize_arn=False)
# Can't have both the regions and all_regions flag present -- also # Can't have both the regions and all_regions flag present -- also
@ -349,7 +372,13 @@ class OrganizationAggregationSource(ConfigEmptyDictable):
class ConfigAggregator(ConfigEmptyDictable): class ConfigAggregator(ConfigEmptyDictable):
def __init__( def __init__(
self, name, account_id, region, account_sources=None, org_source=None, tags=None self,
name: str,
account_id: str,
region: str,
account_sources: Optional[List[AccountAggregatorSource]] = None,
org_source: Optional[OrganizationAggregationSource] = None,
tags: Optional[Dict[str, str]] = None,
): ):
super().__init__(capitalize_start=True, capitalize_arn=False) super().__init__(capitalize_start=True, capitalize_arn=False)
@ -364,7 +393,7 @@ class ConfigAggregator(ConfigEmptyDictable):
self.tags = tags or {} self.tags = tags or {}
# Override the to_dict so that we can format the tags properly... # Override the to_dict so that we can format the tags properly...
def to_dict(self): def to_dict(self) -> Dict[str, Any]:
result = super().to_dict() result = super().to_dict()
# Override the account aggregation sources if present: # Override the account aggregation sources if present:
@ -384,11 +413,11 @@ class ConfigAggregator(ConfigEmptyDictable):
class ConfigAggregationAuthorization(ConfigEmptyDictable): class ConfigAggregationAuthorization(ConfigEmptyDictable):
def __init__( def __init__(
self, self,
account_id, account_id: str,
current_region, current_region: str,
authorized_account_id, authorized_account_id: str,
authorized_aws_region, authorized_aws_region: str,
tags=None, tags: Dict[str, str],
): ):
super().__init__(capitalize_start=True, capitalize_arn=False) super().__init__(capitalize_start=True, capitalize_arn=False)
@ -412,13 +441,13 @@ class ConfigAggregationAuthorization(ConfigEmptyDictable):
class OrganizationConformancePack(ConfigEmptyDictable): class OrganizationConformancePack(ConfigEmptyDictable):
def __init__( def __init__(
self, self,
account_id, account_id: str,
region, region: str,
name, name: str,
delivery_s3_bucket, delivery_s3_bucket: str,
delivery_s3_key_prefix=None, delivery_s3_key_prefix: Optional[str] = None,
input_parameters=None, input_parameters: Optional[List[Dict[str, Any]]] = None,
excluded_accounts=None, excluded_accounts: Optional[List[str]] = None,
): ):
super().__init__(capitalize_start=True, capitalize_arn=False) super().__init__(capitalize_start=True, capitalize_arn=False)
@ -435,11 +464,11 @@ class OrganizationConformancePack(ConfigEmptyDictable):
def update( def update(
self, self,
delivery_s3_bucket, delivery_s3_bucket: str,
delivery_s3_key_prefix, delivery_s3_key_prefix: str,
input_parameters, input_parameters: List[Dict[str, Any]],
excluded_accounts, excluded_accounts: List[str],
): ) -> None:
self._status = "UPDATE_SUCCESSFUL" self._status = "UPDATE_SUCCESSFUL"
self.conformance_pack_input_parameters = input_parameters self.conformance_pack_input_parameters = input_parameters
@ -464,10 +493,10 @@ class Scope(ConfigEmptyDictable):
def __init__( def __init__(
self, self,
compliance_resource_types=None, compliance_resource_types: Optional[List[str]] = None,
tag_key=None, tag_key: Optional[str] = None,
tag_value=None, tag_value: Optional[str] = None,
compliance_resource_id=None, compliance_resource_id: Optional[str] = None,
): ):
super().__init__(capitalize_start=True, capitalize_arn=False) super().__init__(capitalize_start=True, capitalize_arn=False)
self.tags = None self.tags = None
@ -489,7 +518,7 @@ class Scope(ConfigEmptyDictable):
"Scope cannot be applied to both resource and tag" "Scope cannot be applied to both resource and tag"
) )
if compliance_resource_id and len(compliance_resource_types) != 1: if compliance_resource_id and len(compliance_resource_types) != 1: # type: ignore[arg-type]
raise InvalidParameterValueException( raise InvalidParameterValueException(
"A single resourceType should be provided when resourceId " "A single resourceType should be provided when resourceId "
"is provided in scope" "is provided in scope"
@ -522,7 +551,10 @@ class SourceDetail(ConfigEmptyDictable):
EVENT_SOURCES = ["aws.config"] EVENT_SOURCES = ["aws.config"]
def __init__( def __init__(
self, event_source=None, message_type=None, maximum_execution_frequency=None self,
event_source: Optional[str] = None,
message_type: Optional[str] = None,
maximum_execution_frequency: Optional[str] = None,
): ):
super().__init__(capitalize_start=True, capitalize_arn=False) super().__init__(capitalize_start=True, capitalize_arn=False)
@ -599,7 +631,12 @@ class Source(ConfigEmptyDictable):
OWNERS = {"AWS", "CUSTOM_LAMBDA"} OWNERS = {"AWS", "CUSTOM_LAMBDA"}
def __init__( def __init__(
self, account_id, region, owner, source_identifier, source_details=None self,
account_id: str,
region: str,
owner: str,
source_identifier: str,
source_details: Optional[SourceDetail] = None,
): ):
super().__init__(capitalize_start=True, capitalize_arn=False) super().__init__(capitalize_start=True, capitalize_arn=False)
if owner not in Source.OWNERS: if owner not in Source.OWNERS:
@ -651,7 +688,7 @@ class Source(ConfigEmptyDictable):
) )
details = [] details = []
for detail in source_details: for detail in source_details: # type: ignore[attr-defined]
detail_dict = convert_to_class_args(detail) detail_dict = convert_to_class_args(detail)
details.append(SourceDetail(**detail_dict)) details.append(SourceDetail(**detail_dict))
@ -659,7 +696,7 @@ class Source(ConfigEmptyDictable):
self.owner = owner self.owner = owner
self.source_identifier = source_identifier self.source_identifier = source_identifier
def to_dict(self): def to_dict(self) -> Dict[str, Any]:
"""Format the SourceDetails properly.""" """Format the SourceDetails properly."""
result = super().to_dict() result = super().to_dict()
if self.source_details: if self.source_details:
@ -678,7 +715,13 @@ class ConfigRule(ConfigEmptyDictable):
MAX_RULES = 150 MAX_RULES = 150
RULE_STATES = {"ACTIVE", "DELETING", "DELETING_RESULTS", "EVALUATING"} RULE_STATES = {"ACTIVE", "DELETING", "DELETING_RESULTS", "EVALUATING"}
def __init__(self, account_id, region, config_rule, tags): def __init__(
self,
account_id: str,
region: str,
config_rule: Dict[str, Any],
tags: Dict[str, str],
):
super().__init__(capitalize_start=True, capitalize_arn=False) super().__init__(capitalize_start=True, capitalize_arn=False)
self.account_id = account_id self.account_id = account_id
self.config_rule_name = config_rule.get("ConfigRuleName") self.config_rule_name = config_rule.get("ConfigRuleName")
@ -697,7 +740,9 @@ class ConfigRule(ConfigEmptyDictable):
f"arn:aws:config:{region}:{account_id}:config-rule/{self.config_rule_id}" f"arn:aws:config:{region}:{account_id}:config-rule/{self.config_rule_id}"
) )
def modify_fields(self, region, config_rule, tags): def modify_fields(
self, region: str, config_rule: Dict[str, Any], tags: Dict[str, str]
) -> None:
"""Initialize or update ConfigRule fields.""" """Initialize or update ConfigRule fields."""
self.config_rule_state = config_rule.get("ConfigRuleState", "ACTIVE") self.config_rule_state = config_rule.get("ConfigRuleState", "ACTIVE")
if self.config_rule_state not in ConfigRule.RULE_STATES: if self.config_rule_state not in ConfigRule.RULE_STATES:
@ -787,23 +832,22 @@ class ConfigRule(ConfigEmptyDictable):
self.last_updated_time = datetime2int(datetime.utcnow()) self.last_updated_time = datetime2int(datetime.utcnow())
self.tags = tags self.tags = tags
def validate_managed_rule(self): def validate_managed_rule(self) -> None:
"""Validate parameters specific to managed rules.""" """Validate parameters specific to managed rules."""
rule_info = MANAGED_RULES_CONSTRAINTS[self.source.source_identifier] rule_info = MANAGED_RULES_CONSTRAINTS[self.source.source_identifier] # type: ignore[index]
param_names = self.input_parameters_dict.keys() param_names = self.input_parameters_dict.keys()
# Verify input parameter names are actual parameters for the rule ID. # Verify input parameter names are actual parameters for the rule ID.
if param_names: if param_names:
allowed_names = {x["Name"] for x in rule_info["Parameters"]} allowed_names = {x["Name"] for x in rule_info["Parameters"]} # type: ignore[index]
if not set(param_names).issubset(allowed_names): if not set(param_names).issubset(allowed_names):
raise InvalidParameterValueException( raise InvalidParameterValueException(
"Unknown parameters provided in the inputParameters: " f"Unknown parameters provided in the inputParameters: {self.input_parameters}"
+ self.input_parameters
) )
# Verify all the required parameters are specified. # Verify all the required parameters are specified.
required_names = { required_names = {
x["Name"] for x in rule_info["Parameters"] if not x["Optional"] x["Name"] for x in rule_info["Parameters"] if not x["Optional"] # type: ignore[index]
} }
diffs = required_names.difference(set(param_names)) diffs = required_names.difference(set(param_names))
if diffs: if diffs:
@ -849,24 +893,24 @@ class ConfigRule(ConfigEmptyDictable):
class ConfigBackend(BaseBackend): class ConfigBackend(BaseBackend):
def __init__(self, region_name, account_id): def __init__(self, region_name: str, account_id: str):
super().__init__(region_name, account_id) super().__init__(region_name, account_id)
self.recorders = {} self.recorders: Dict[str, ConfigRecorder] = {}
self.delivery_channels = {} self.delivery_channels: Dict[str, ConfigDeliveryChannel] = {}
self.config_aggregators = {} self.config_aggregators: Dict[str, ConfigAggregator] = {}
self.aggregation_authorizations = {} self.aggregation_authorizations: Dict[str, ConfigAggregationAuthorization] = {}
self.organization_conformance_packs = {} self.organization_conformance_packs: Dict[str, OrganizationConformancePack] = {}
self.config_rules = {} self.config_rules: Dict[str, ConfigRule] = {}
self.config_schema = None self.config_schema: Optional[AWSServiceSpec] = None
@staticmethod @staticmethod
def default_vpc_endpoint_service(service_region, zones): def default_vpc_endpoint_service(service_region: str, zones: List[str]) -> List[Dict[str, Any]]: # type: ignore[misc]
"""List of dicts representing default VPC endpoints for this service.""" """List of dicts representing default VPC endpoints for this service."""
return BaseBackend.default_vpc_endpoint_service_factory( return BaseBackend.default_vpc_endpoint_service_factory(
service_region, zones, "config" service_region, zones, "config"
) )
def _validate_resource_types(self, resource_list): def _validate_resource_types(self, resource_list: List[str]) -> None:
if not self.config_schema: if not self.config_schema:
self.config_schema = AWSServiceSpec( self.config_schema = AWSServiceSpec(
path="data/config/2014-11-12/service-2.json" path="data/config/2014-11-12/service-2.json"
@ -875,15 +919,17 @@ class ConfigBackend(BaseBackend):
# Verify that each entry exists in the supported list: # Verify that each entry exists in the supported list:
bad_list = [] bad_list = []
for resource in resource_list: for resource in resource_list:
if resource not in self.config_schema.shapes["ResourceType"]["enum"]: if resource not in self.config_schema.shapes["ResourceType"]["enum"]: # type: ignore[index]
bad_list.append(resource) bad_list.append(resource)
if bad_list: if bad_list:
raise InvalidResourceTypeException( raise InvalidResourceTypeException(
bad_list, self.config_schema.shapes["ResourceType"]["enum"] bad_list, self.config_schema.shapes["ResourceType"]["enum"] # type: ignore[index]
) )
def _validate_delivery_snapshot_properties(self, properties): def _validate_delivery_snapshot_properties(
self, properties: Dict[str, Any]
) -> None:
if not self.config_schema: if not self.config_schema:
self.config_schema = AWSServiceSpec( self.config_schema = AWSServiceSpec(
path="data/config/2014-11-12/service-2.json" path="data/config/2014-11-12/service-2.json"
@ -892,23 +938,23 @@ class ConfigBackend(BaseBackend):
# Verify that the deliveryFrequency is set to an acceptable value: # Verify that the deliveryFrequency is set to an acceptable value:
if ( if (
properties.get("deliveryFrequency", None) properties.get("deliveryFrequency", None)
not in self.config_schema.shapes["MaximumExecutionFrequency"]["enum"] not in self.config_schema.shapes["MaximumExecutionFrequency"]["enum"] # type: ignore[index]
): ):
raise InvalidDeliveryFrequency( raise InvalidDeliveryFrequency(
properties.get("deliveryFrequency", None), properties.get("deliveryFrequency", None),
self.config_schema.shapes["MaximumExecutionFrequency"]["enum"], self.config_schema.shapes["MaximumExecutionFrequency"]["enum"], # type: ignore[index]
) )
def put_configuration_aggregator(self, config_aggregator): def put_configuration_aggregator(
self, config_aggregator: Dict[str, Any]
) -> Dict[str, Any]:
# Validate the name: # Validate the name:
if len(config_aggregator["ConfigurationAggregatorName"]) > 256: config_aggr_name = config_aggregator["ConfigurationAggregatorName"]
raise NameTooLongException( if len(config_aggr_name) > 256:
config_aggregator["ConfigurationAggregatorName"], raise NameTooLongException(config_aggr_name, "configurationAggregatorName")
"configurationAggregatorName",
)
account_sources = None account_sources: Optional[List[AccountAggregatorSource]] = None
org_source = None org_source: Optional[OrganizationAggregationSource] = None
# Tag validation: # Tag validation:
tags = validate_tags(config_aggregator.get("Tags", [])) tags = validate_tags(config_aggregator.get("Tags", []))
@ -965,25 +1011,19 @@ class ConfigBackend(BaseBackend):
) )
# Grab the existing one if it exists and update it: # Grab the existing one if it exists and update it:
if not self.config_aggregators.get( if not self.config_aggregators.get(config_aggr_name):
config_aggregator["ConfigurationAggregatorName"]
):
aggregator = ConfigAggregator( aggregator = ConfigAggregator(
config_aggregator["ConfigurationAggregatorName"], config_aggr_name,
account_id=self.account_id, account_id=self.account_id,
region=self.region_name, region=self.region_name,
account_sources=account_sources, account_sources=account_sources,
org_source=org_source, org_source=org_source,
tags=tags, tags=tags,
) )
self.config_aggregators[ self.config_aggregators[config_aggr_name] = aggregator
config_aggregator["ConfigurationAggregatorName"]
] = aggregator
else: else:
aggregator = self.config_aggregators[ aggregator = self.config_aggregators[config_aggr_name]
config_aggregator["ConfigurationAggregatorName"]
]
aggregator.tags = tags aggregator.tags = tags
aggregator.account_aggregation_sources = account_sources aggregator.account_aggregation_sources = account_sources
aggregator.organization_aggregation_source = org_source aggregator.organization_aggregation_source = org_source
@ -991,10 +1031,12 @@ class ConfigBackend(BaseBackend):
return aggregator.to_dict() return aggregator.to_dict()
def describe_configuration_aggregators(self, names, token, limit): def describe_configuration_aggregators(
self, names: List[str], token: str, limit: Optional[int]
) -> Dict[str, Any]:
limit = DEFAULT_PAGE_SIZE if not limit or limit < 0 else limit limit = DEFAULT_PAGE_SIZE if not limit or limit < 0 else limit
agg_list = [] agg_list = []
result = {"ConfigurationAggregators": []} result: Dict[str, Any] = {"ConfigurationAggregators": []}
if names: if names:
for name in names: for name in names:
@ -1034,17 +1076,20 @@ class ConfigBackend(BaseBackend):
return result return result
def delete_configuration_aggregator(self, config_aggregator): def delete_configuration_aggregator(self, config_aggregator: str) -> None:
if not self.config_aggregators.get(config_aggregator): if not self.config_aggregators.get(config_aggregator):
raise NoSuchConfigurationAggregatorException() raise NoSuchConfigurationAggregatorException()
del self.config_aggregators[config_aggregator] del self.config_aggregators[config_aggregator]
def put_aggregation_authorization( def put_aggregation_authorization(
self, authorized_account, authorized_region, tags self,
): authorized_account: str,
authorized_region: str,
tags: List[Dict[str, str]],
) -> Dict[str, Any]:
# Tag validation: # Tag validation:
tags = validate_tags(tags or []) tag_dict = validate_tags(tags or [])
# Does this already exist? # Does this already exist?
key = "{}/{}".format(authorized_account, authorized_region) key = "{}/{}".format(authorized_account, authorized_region)
@ -1055,20 +1100,22 @@ class ConfigBackend(BaseBackend):
self.region_name, self.region_name,
authorized_account, authorized_account,
authorized_region, authorized_region,
tags=tags, tags=tag_dict,
) )
self.aggregation_authorizations[ self.aggregation_authorizations[
"{}/{}".format(authorized_account, authorized_region) "{}/{}".format(authorized_account, authorized_region)
] = agg_auth ] = agg_auth
else: else:
# Only update the tags: # Only update the tags:
agg_auth.tags = tags agg_auth.tags = tag_dict
return agg_auth.to_dict() return agg_auth.to_dict()
def describe_aggregation_authorizations(self, token, limit): def describe_aggregation_authorizations(
self, token: Optional[str], limit: Optional[int]
) -> Dict[str, Any]:
limit = DEFAULT_PAGE_SIZE if not limit or limit < 0 else limit limit = DEFAULT_PAGE_SIZE if not limit or limit < 0 else limit
result = {"AggregationAuthorizations": []} result: Dict[str, Any] = {"AggregationAuthorizations": []}
if not self.aggregation_authorizations: if not self.aggregation_authorizations:
return result return result
@ -1097,19 +1144,21 @@ class ConfigBackend(BaseBackend):
return result return result
def delete_aggregation_authorization(self, authorized_account, authorized_region): def delete_aggregation_authorization(
self, authorized_account: str, authorized_region: str
) -> None:
# This will always return a 200 -- regardless if there is or isn't an existing # This will always return a 200 -- regardless if there is or isn't an existing
# aggregation authorization. # aggregation authorization.
key = "{}/{}".format(authorized_account, authorized_region) key = "{}/{}".format(authorized_account, authorized_region)
self.aggregation_authorizations.pop(key, None) self.aggregation_authorizations.pop(key, None)
def put_configuration_recorder(self, config_recorder): def put_configuration_recorder(self, config_recorder: Dict[str, Any]) -> None:
# Validate the name: # Validate the name:
if not config_recorder.get("name"): if not config_recorder.get("name"):
raise InvalidConfigurationRecorderNameException(config_recorder.get("name")) raise InvalidConfigurationRecorderNameException(config_recorder.get("name"))
if len(config_recorder.get("name")) > 256: if len(config_recorder["name"]) > 256:
raise NameTooLongException( raise NameTooLongException(
config_recorder.get("name"), "configurationRecorder.name" config_recorder["name"], "configurationRecorder.name"
) )
# We're going to assume that the passed in Role ARN is correct. # We're going to assume that the passed in Role ARN is correct.
@ -1164,8 +1213,10 @@ class ConfigBackend(BaseBackend):
status=recorder_status, status=recorder_status,
) )
def describe_configuration_recorders(self, recorder_names): def describe_configuration_recorders(
recorders = [] self, recorder_names: Optional[List[str]]
) -> List[Dict[str, Any]]:
recorders: List[Dict[str, Any]] = []
if recorder_names: if recorder_names:
for rname in recorder_names: for rname in recorder_names:
@ -1181,8 +1232,10 @@ class ConfigBackend(BaseBackend):
return recorders return recorders
def describe_configuration_recorder_status(self, recorder_names): def describe_configuration_recorder_status(
recorders = [] self, recorder_names: List[str]
) -> List[Dict[str, Any]]:
recorders: List[Dict[str, Any]] = []
if recorder_names: if recorder_names:
for rname in recorder_names: for rname in recorder_names:
@ -1198,7 +1251,7 @@ class ConfigBackend(BaseBackend):
return recorders return recorders
def put_delivery_channel(self, delivery_channel): def put_delivery_channel(self, delivery_channel: Dict[str, Any]) -> None:
# Must have a configuration recorder: # Must have a configuration recorder:
if not self.recorders: if not self.recorders:
raise NoAvailableConfigurationRecorderException() raise NoAvailableConfigurationRecorderException()
@ -1206,10 +1259,8 @@ class ConfigBackend(BaseBackend):
# Validate the name: # Validate the name:
if not delivery_channel.get("name"): if not delivery_channel.get("name"):
raise InvalidDeliveryChannelNameException(delivery_channel.get("name")) raise InvalidDeliveryChannelNameException(delivery_channel.get("name"))
if len(delivery_channel.get("name")) > 256: if len(delivery_channel["name"]) > 256:
raise NameTooLongException( raise NameTooLongException(delivery_channel["name"], "deliveryChannel.name")
delivery_channel.get("name"), "deliveryChannel.name"
)
# We are going to assume that the bucket exists -- but will verify if # We are going to assume that the bucket exists -- but will verify if
# the bucket provided is blank: # the bucket provided is blank:
@ -1256,8 +1307,10 @@ class ConfigBackend(BaseBackend):
snapshot_properties=dprop, snapshot_properties=dprop,
) )
def describe_delivery_channels(self, channel_names): def describe_delivery_channels(
channels = [] self, channel_names: List[str]
) -> List[Dict[str, Any]]:
channels: List[Dict[str, Any]] = []
if channel_names: if channel_names:
for cname in channel_names: for cname in channel_names:
@ -1273,7 +1326,7 @@ class ConfigBackend(BaseBackend):
return channels return channels
def start_configuration_recorder(self, recorder_name): def start_configuration_recorder(self, recorder_name: str) -> None:
if not self.recorders.get(recorder_name): if not self.recorders.get(recorder_name):
raise NoSuchConfigurationRecorderException(recorder_name) raise NoSuchConfigurationRecorderException(recorder_name)
@ -1284,20 +1337,20 @@ class ConfigBackend(BaseBackend):
# Start recording: # Start recording:
self.recorders[recorder_name].status.start() self.recorders[recorder_name].status.start()
def stop_configuration_recorder(self, recorder_name): def stop_configuration_recorder(self, recorder_name: str) -> None:
if not self.recorders.get(recorder_name): if not self.recorders.get(recorder_name):
raise NoSuchConfigurationRecorderException(recorder_name) raise NoSuchConfigurationRecorderException(recorder_name)
# Stop recording: # Stop recording:
self.recorders[recorder_name].status.stop() self.recorders[recorder_name].status.stop()
def delete_configuration_recorder(self, recorder_name): def delete_configuration_recorder(self, recorder_name: str) -> None:
if not self.recorders.get(recorder_name): if not self.recorders.get(recorder_name):
raise NoSuchConfigurationRecorderException(recorder_name) raise NoSuchConfigurationRecorderException(recorder_name)
del self.recorders[recorder_name] del self.recorders[recorder_name]
def delete_delivery_channel(self, channel_name): def delete_delivery_channel(self, channel_name: str) -> None:
if not self.delivery_channels.get(channel_name): if not self.delivery_channels.get(channel_name):
raise NoSuchDeliveryChannelException(channel_name) raise NoSuchDeliveryChannelException(channel_name)
@ -1310,13 +1363,13 @@ class ConfigBackend(BaseBackend):
def list_discovered_resources( def list_discovered_resources(
self, self,
resource_type, resource_type: str,
backend_region, backend_region: str,
resource_ids, resource_ids: List[str],
resource_name, resource_name: str,
limit, limit: int,
next_token, next_token: str,
): ) -> Dict[str, Any]:
"""Queries against AWS Config (non-aggregated) listing function. """Queries against AWS Config (non-aggregated) listing function.
The listing function must exist for the resource backend. The listing function must exist for the resource backend.
@ -1392,8 +1445,13 @@ class ConfigBackend(BaseBackend):
return result return result
def list_aggregate_discovered_resources( def list_aggregate_discovered_resources(
self, aggregator_name, resource_type, filters, limit, next_token self,
): aggregator_name: str,
resource_type: str,
filters: Dict[str, str],
limit: Optional[int],
next_token: Optional[str],
) -> Dict[str, Any]:
"""Queries AWS Config listing function that must exist for resource backend. """Queries AWS Config listing function that must exist for resource backend.
As far a moto goes -- the only real difference between this function As far a moto goes -- the only real difference between this function
@ -1453,14 +1511,16 @@ class ConfigBackend(BaseBackend):
resource_identifiers.append(item) resource_identifiers.append(item)
result = {"ResourceIdentifiers": resource_identifiers} result: Dict[str, Any] = {"ResourceIdentifiers": resource_identifiers}
if new_token: if new_token:
result["NextToken"] = new_token result["NextToken"] = new_token
return result return result
def get_resource_config_history(self, resource_type, resource_id, backend_region): def get_resource_config_history(
self, resource_type: str, resource_id: str, backend_region: str
) -> Dict[str, Any]:
"""Returns configuration of resource for the current regional backend. """Returns configuration of resource for the current regional backend.
Item returned in AWS Config format. Item returned in AWS Config format.
@ -1502,7 +1562,9 @@ class ConfigBackend(BaseBackend):
return {"configurationItems": [item]} return {"configurationItems": [item]}
def batch_get_resource_config(self, resource_keys, backend_region): def batch_get_resource_config(
self, resource_keys: List[Dict[str, str]], backend_region: str
) -> Dict[str, Any]:
"""Returns configuration of resource for the current regional backend. """Returns configuration of resource for the current regional backend.
Item is returned in AWS Config format. Item is returned in AWS Config format.
@ -1562,8 +1624,8 @@ class ConfigBackend(BaseBackend):
} # At this time, moto is not adding unprocessed items. } # At this time, moto is not adding unprocessed items.
def batch_get_aggregate_resource_config( def batch_get_aggregate_resource_config(
self, aggregator_name, resource_identifiers self, aggregator_name: str, resource_identifiers: List[Dict[str, str]]
): ) -> Dict[str, Any]:
"""Returns configuration of resource for current regional backend. """Returns configuration of resource for current regional backend.
Item is returned in AWS Config format. Item is returned in AWS Config format.
@ -1621,7 +1683,12 @@ class ConfigBackend(BaseBackend):
"UnprocessedResourceIdentifiers": not_found, "UnprocessedResourceIdentifiers": not_found,
} }
def put_evaluations(self, evaluations=None, result_token=None, test_mode=False): def put_evaluations(
self,
evaluations: Optional[List[Dict[str, Any]]] = None,
result_token: Optional[str] = None,
test_mode: Optional[bool] = False,
) -> Dict[str, List[Any]]:
if not evaluations: if not evaluations:
raise InvalidParameterValueException( raise InvalidParameterValueException(
"The Evaluations object in your request cannot be null." "The Evaluations object in your request cannot be null."
@ -1644,14 +1711,14 @@ class ConfigBackend(BaseBackend):
def put_organization_conformance_pack( def put_organization_conformance_pack(
self, self,
name, name: str,
template_s3_uri, template_s3_uri: str,
template_body, template_body: str,
delivery_s3_bucket, delivery_s3_bucket: str,
delivery_s3_key_prefix, delivery_s3_key_prefix: str,
input_parameters, input_parameters: List[Dict[str, str]],
excluded_accounts, excluded_accounts: List[str],
): ) -> Dict[str, Any]:
# a real validation of the content of the template is missing at the moment # a real validation of the content of the template is missing at the moment
if not template_s3_uri and not template_body: if not template_s3_uri and not template_body:
raise ValidationException("Template body is invalid") raise ValidationException("Template body is invalid")
@ -1690,7 +1757,9 @@ class ConfigBackend(BaseBackend):
"OrganizationConformancePackArn": pack.organization_conformance_pack_arn "OrganizationConformancePackArn": pack.organization_conformance_pack_arn
} }
def describe_organization_conformance_packs(self, names): def describe_organization_conformance_packs(
self, names: List[str]
) -> Dict[str, Any]:
packs = [] packs = []
for name in names: for name in names:
@ -1707,7 +1776,9 @@ class ConfigBackend(BaseBackend):
return {"OrganizationConformancePacks": packs} return {"OrganizationConformancePacks": packs}
def describe_organization_conformance_pack_statuses(self, names): def describe_organization_conformance_pack_statuses(
self, names: List[str]
) -> Dict[str, Any]:
packs = [] packs = []
statuses = [] statuses = []
@ -1737,7 +1808,9 @@ class ConfigBackend(BaseBackend):
return {"OrganizationConformancePackStatuses": statuses} return {"OrganizationConformancePackStatuses": statuses}
def get_organization_conformance_pack_detailed_status(self, name): def get_organization_conformance_pack_detailed_status(
self, name: str
) -> Dict[str, Any]:
pack = self.organization_conformance_packs.get(name) pack = self.organization_conformance_packs.get(name)
if not pack: if not pack:
@ -1760,7 +1833,7 @@ class ConfigBackend(BaseBackend):
return {"OrganizationConformancePackDetailedStatuses": statuses} return {"OrganizationConformancePackDetailedStatuses": statuses}
def delete_organization_conformance_pack(self, name): def delete_organization_conformance_pack(self, name: str) -> None:
pack = self.organization_conformance_packs.get(name) pack = self.organization_conformance_packs.get(name)
if not pack: if not pack:
@ -1771,28 +1844,24 @@ class ConfigBackend(BaseBackend):
self.organization_conformance_packs.pop(name) self.organization_conformance_packs.pop(name)
def _match_arn(self, resource_arn): def _match_arn(
self, resource_arn: str
) -> Union[ConfigRule, ConfigAggregator, ConfigAggregationAuthorization]:
"""Return config instance that has a matching ARN.""" """Return config instance that has a matching ARN."""
# The allowed resources are ConfigRule, ConfigurationAggregator, # The allowed resources are ConfigRule, ConfigurationAggregator,
# and AggregatorAuthorization. # and AggregatorAuthorization.
allowed_resources = [ allowed_resources = {
{ "configuration_aggregator_arn": self.config_aggregators,
"configs": self.config_aggregators, "aggregation_authorization_arn": self.aggregation_authorizations,
"arn_attribute": "configuration_aggregator_arn", "config_rule_arn": self.config_rules,
}, }
{
"configs": self.aggregation_authorizations,
"arn_attribute": "aggregation_authorization_arn",
},
{"configs": self.config_rules, "arn_attribute": "config_rule_arn"},
]
# Find matching config for given resource_arn among all the # Find matching config for given resource_arn among all the
# allowed config resources. # allowed config resources.
matched_config = None matched_config = None
for resource in allowed_resources: for arn_attribute, configs in allowed_resources.items():
for config in resource["configs"].values(): for config in configs.values(): # type: ignore[attr-defined]
if resource_arn == getattr(config, resource["arn_attribute"]): if resource_arn == getattr(config, arn_attribute):
matched_config = config matched_config = config
break break
@ -1800,18 +1869,18 @@ class ConfigBackend(BaseBackend):
raise ResourceNotFoundException(resource_arn) raise ResourceNotFoundException(resource_arn)
return matched_config return matched_config
def tag_resource(self, resource_arn, tags): def tag_resource(self, resource_arn: str, tags: List[Dict[str, str]]) -> None:
"""Add tags in config with a matching ARN.""" """Add tags in config with a matching ARN."""
# Tag validation: # Tag validation:
tags = validate_tags(tags) tag_dict = validate_tags(tags)
# Find config with a matching ARN. # Find config with a matching ARN.
matched_config = self._match_arn(resource_arn) matched_config = self._match_arn(resource_arn)
# Merge the new tags with the existing tags. # Merge the new tags with the existing tags.
matched_config.tags.update(tags) matched_config.tags.update(tag_dict)
def untag_resource(self, resource_arn, tag_keys): def untag_resource(self, resource_arn: str, tag_keys: List[str]) -> None:
"""Remove tags in config with a matching ARN. """Remove tags in config with a matching ARN.
If the tags in the tag_keys don't match any keys for that If the tags in the tag_keys don't match any keys for that
@ -1827,8 +1896,8 @@ class ConfigBackend(BaseBackend):
matched_config.tags.pop(tag_key, None) matched_config.tags.pop(tag_key, None)
def list_tags_for_resource( def list_tags_for_resource(
self, resource_arn, limit, next_token self, resource_arn: str, limit: int
): # pylint: disable=unused-argument ) -> Dict[str, List[Dict[str, str]]]:
"""Return list of tags for AWS Config resource.""" """Return list of tags for AWS Config resource."""
# The limit argument is essentially ignored as a config instance # The limit argument is essentially ignored as a config instance
# can only have 50 tags, but we'll check the argument anyway. # can only have 50 tags, but we'll check the argument anyway.
@ -1845,7 +1914,9 @@ class ConfigBackend(BaseBackend):
] ]
} }
def put_config_rule(self, config_rule, tags=None): def put_config_rule(
self, config_rule: Dict[str, Any], tags: Optional[List[Dict[str, str]]] = None
) -> str:
"""Add/Update config rule for evaluating resource compliance. """Add/Update config rule for evaluating resource compliance.
TBD - Only the "accounting" of config rules are handled at the TBD - Only the "accounting" of config rules are handled at the
@ -1880,7 +1951,7 @@ class ConfigBackend(BaseBackend):
"Name or Id or Arn" "Name or Id or Arn"
) )
tags = validate_tags(tags or []) tag_dict = validate_tags(tags or [])
# With the rule_name, determine whether it's for an existing rule # With the rule_name, determine whether it's for an existing rule
# or whether a new rule should be created. # or whether a new rule should be created.
@ -1896,20 +1967,22 @@ class ConfigBackend(BaseBackend):
) )
# Update the current rule. # Update the current rule.
rule.modify_fields(self.region_name, config_rule, tags) rule.modify_fields(self.region_name, config_rule, tag_dict)
else: else:
# Create a new ConfigRule if the limit hasn't been reached. # Create a new ConfigRule if the limit hasn't been reached.
if len(self.config_rules) == ConfigRule.MAX_RULES: if len(self.config_rules) == ConfigRule.MAX_RULES:
raise MaxNumberOfConfigRulesExceededException( raise MaxNumberOfConfigRulesExceededException(
rule_name, ConfigRule.MAX_RULES rule_name, ConfigRule.MAX_RULES
) )
rule = ConfigRule(self.account_id, self.region_name, config_rule, tags) rule = ConfigRule(self.account_id, self.region_name, config_rule, tag_dict)
self.config_rules[rule_name] = rule self.config_rules[rule_name] = rule
return "" return ""
def describe_config_rules(self, config_rule_names, next_token): def describe_config_rules(
self, config_rule_names: Optional[List[str]], next_token: Optional[str]
) -> Dict[str, Any]:
"""Return details for the given ConfigRule names or for all rules.""" """Return details for the given ConfigRule names or for all rules."""
result = {"ConfigRules": []} result: Dict[str, Any] = {"ConfigRules": []}
if not self.config_rules: if not self.config_rules:
return result return result
@ -1937,7 +2010,7 @@ class ConfigBackend(BaseBackend):
result["NextToken"] = sorted_rules[start + CONFIG_RULE_PAGE_SIZE] result["NextToken"] = sorted_rules[start + CONFIG_RULE_PAGE_SIZE]
return result return result
def delete_config_rule(self, rule_name): def delete_config_rule(self, rule_name: str) -> None:
"""Delete config rule used for evaluating resource compliance.""" """Delete config rule used for evaluating resource compliance."""
rule = self.config_rules.get(rule_name) rule = self.config_rules.get(rule_name)
if not rule: if not rule:

View File

@ -1,30 +1,30 @@
import json import json
from moto.core.responses import BaseResponse from moto.core.responses import BaseResponse
from .models import config_backends from .models import config_backends, ConfigBackend
class ConfigResponse(BaseResponse): class ConfigResponse(BaseResponse):
def __init__(self): def __init__(self) -> None:
super().__init__(service_name="config") super().__init__(service_name="config")
@property @property
def config_backend(self): def config_backend(self) -> ConfigBackend:
return config_backends[self.current_account][self.region] return config_backends[self.current_account][self.region]
def put_configuration_recorder(self): def put_configuration_recorder(self) -> str:
self.config_backend.put_configuration_recorder( self.config_backend.put_configuration_recorder(
self._get_param("ConfigurationRecorder") self._get_param("ConfigurationRecorder")
) )
return "" return ""
def put_configuration_aggregator(self): def put_configuration_aggregator(self) -> str:
aggregator = self.config_backend.put_configuration_aggregator( aggregator = self.config_backend.put_configuration_aggregator(
json.loads(self.body) json.loads(self.body)
) )
schema = {"ConfigurationAggregator": aggregator} schema = {"ConfigurationAggregator": aggregator}
return json.dumps(schema) return json.dumps(schema)
def describe_configuration_aggregators(self): def describe_configuration_aggregators(self) -> str:
aggregators = self.config_backend.describe_configuration_aggregators( aggregators = self.config_backend.describe_configuration_aggregators(
self._get_param("ConfigurationAggregatorNames"), self._get_param("ConfigurationAggregatorNames"),
self._get_param("NextToken"), self._get_param("NextToken"),
@ -32,13 +32,13 @@ class ConfigResponse(BaseResponse):
) )
return json.dumps(aggregators) return json.dumps(aggregators)
def delete_configuration_aggregator(self): def delete_configuration_aggregator(self) -> str:
self.config_backend.delete_configuration_aggregator( self.config_backend.delete_configuration_aggregator(
self._get_param("ConfigurationAggregatorName") self._get_param("ConfigurationAggregatorName")
) )
return "" return ""
def put_aggregation_authorization(self): def put_aggregation_authorization(self) -> str:
agg_auth = self.config_backend.put_aggregation_authorization( agg_auth = self.config_backend.put_aggregation_authorization(
self._get_param("AuthorizedAccountId"), self._get_param("AuthorizedAccountId"),
self._get_param("AuthorizedAwsRegion"), self._get_param("AuthorizedAwsRegion"),
@ -47,14 +47,14 @@ class ConfigResponse(BaseResponse):
schema = {"AggregationAuthorization": agg_auth} schema = {"AggregationAuthorization": agg_auth}
return json.dumps(schema) return json.dumps(schema)
def describe_aggregation_authorizations(self): def describe_aggregation_authorizations(self) -> str:
authorizations = self.config_backend.describe_aggregation_authorizations( authorizations = self.config_backend.describe_aggregation_authorizations(
self._get_param("NextToken"), self._get_param("Limit") self._get_param("NextToken"), self._get_param("Limit")
) )
return json.dumps(authorizations) return json.dumps(authorizations)
def delete_aggregation_authorization(self): def delete_aggregation_authorization(self) -> str:
self.config_backend.delete_aggregation_authorization( self.config_backend.delete_aggregation_authorization(
self._get_param("AuthorizedAccountId"), self._get_param("AuthorizedAccountId"),
self._get_param("AuthorizedAwsRegion"), self._get_param("AuthorizedAwsRegion"),
@ -62,59 +62,59 @@ class ConfigResponse(BaseResponse):
return "" return ""
def describe_configuration_recorders(self): def describe_configuration_recorders(self) -> str:
recorders = self.config_backend.describe_configuration_recorders( recorders = self.config_backend.describe_configuration_recorders(
self._get_param("ConfigurationRecorderNames") self._get_param("ConfigurationRecorderNames")
) )
schema = {"ConfigurationRecorders": recorders} schema = {"ConfigurationRecorders": recorders}
return json.dumps(schema) return json.dumps(schema)
def describe_configuration_recorder_status(self): def describe_configuration_recorder_status(self) -> str:
recorder_statuses = self.config_backend.describe_configuration_recorder_status( recorder_statuses = self.config_backend.describe_configuration_recorder_status(
self._get_param("ConfigurationRecorderNames") self._get_param("ConfigurationRecorderNames")
) )
schema = {"ConfigurationRecordersStatus": recorder_statuses} schema = {"ConfigurationRecordersStatus": recorder_statuses}
return json.dumps(schema) return json.dumps(schema)
def put_delivery_channel(self): def put_delivery_channel(self) -> str:
self.config_backend.put_delivery_channel(self._get_param("DeliveryChannel")) self.config_backend.put_delivery_channel(self._get_param("DeliveryChannel"))
return "" return ""
def describe_delivery_channels(self): def describe_delivery_channels(self) -> str:
delivery_channels = self.config_backend.describe_delivery_channels( delivery_channels = self.config_backend.describe_delivery_channels(
self._get_param("DeliveryChannelNames") self._get_param("DeliveryChannelNames")
) )
schema = {"DeliveryChannels": delivery_channels} schema = {"DeliveryChannels": delivery_channels}
return json.dumps(schema) return json.dumps(schema)
def describe_delivery_channel_status(self): def describe_delivery_channel_status(self) -> str:
raise NotImplementedError() raise NotImplementedError()
def delete_delivery_channel(self): def delete_delivery_channel(self) -> str:
self.config_backend.delete_delivery_channel( self.config_backend.delete_delivery_channel(
self._get_param("DeliveryChannelName") self._get_param("DeliveryChannelName")
) )
return "" return ""
def delete_configuration_recorder(self): def delete_configuration_recorder(self) -> str:
self.config_backend.delete_configuration_recorder( self.config_backend.delete_configuration_recorder(
self._get_param("ConfigurationRecorderName") self._get_param("ConfigurationRecorderName")
) )
return "" return ""
def start_configuration_recorder(self): def start_configuration_recorder(self) -> str:
self.config_backend.start_configuration_recorder( self.config_backend.start_configuration_recorder(
self._get_param("ConfigurationRecorderName") self._get_param("ConfigurationRecorderName")
) )
return "" return ""
def stop_configuration_recorder(self): def stop_configuration_recorder(self) -> str:
self.config_backend.stop_configuration_recorder( self.config_backend.stop_configuration_recorder(
self._get_param("ConfigurationRecorderName") self._get_param("ConfigurationRecorderName")
) )
return "" return ""
def list_discovered_resources(self): def list_discovered_resources(self) -> str:
schema = self.config_backend.list_discovered_resources( schema = self.config_backend.list_discovered_resources(
self._get_param("resourceType"), self._get_param("resourceType"),
self.region, self.region,
@ -125,7 +125,7 @@ class ConfigResponse(BaseResponse):
) )
return json.dumps(schema) return json.dumps(schema)
def list_aggregate_discovered_resources(self): def list_aggregate_discovered_resources(self) -> str:
schema = self.config_backend.list_aggregate_discovered_resources( schema = self.config_backend.list_aggregate_discovered_resources(
self._get_param("ConfigurationAggregatorName"), self._get_param("ConfigurationAggregatorName"),
self._get_param("ResourceType"), self._get_param("ResourceType"),
@ -135,34 +135,32 @@ class ConfigResponse(BaseResponse):
) )
return json.dumps(schema) return json.dumps(schema)
def list_tags_for_resource(self): def list_tags_for_resource(self) -> str:
schema = self.config_backend.list_tags_for_resource( schema = self.config_backend.list_tags_for_resource(
self._get_param("ResourceArn"), self._get_param("ResourceArn"), self._get_param("Limit")
self._get_param("Limit"),
self._get_param("NextToken"),
) )
return json.dumps(schema) return json.dumps(schema)
def get_resource_config_history(self): def get_resource_config_history(self) -> str:
schema = self.config_backend.get_resource_config_history( schema = self.config_backend.get_resource_config_history(
self._get_param("resourceType"), self._get_param("resourceId"), self.region self._get_param("resourceType"), self._get_param("resourceId"), self.region
) )
return json.dumps(schema) return json.dumps(schema)
def batch_get_resource_config(self): def batch_get_resource_config(self) -> str:
schema = self.config_backend.batch_get_resource_config( schema = self.config_backend.batch_get_resource_config(
self._get_param("resourceKeys"), self.region self._get_param("resourceKeys"), self.region
) )
return json.dumps(schema) return json.dumps(schema)
def batch_get_aggregate_resource_config(self): def batch_get_aggregate_resource_config(self) -> str:
schema = self.config_backend.batch_get_aggregate_resource_config( schema = self.config_backend.batch_get_aggregate_resource_config(
self._get_param("ConfigurationAggregatorName"), self._get_param("ConfigurationAggregatorName"),
self._get_param("ResourceIdentifiers"), self._get_param("ResourceIdentifiers"),
) )
return json.dumps(schema) return json.dumps(schema)
def put_evaluations(self): def put_evaluations(self) -> str:
evaluations = self.config_backend.put_evaluations( evaluations = self.config_backend.put_evaluations(
self._get_param("Evaluations"), self._get_param("Evaluations"),
self._get_param("ResultToken"), self._get_param("ResultToken"),
@ -170,7 +168,7 @@ class ConfigResponse(BaseResponse):
) )
return json.dumps(evaluations) return json.dumps(evaluations)
def put_organization_conformance_pack(self): def put_organization_conformance_pack(self) -> str:
conformance_pack = self.config_backend.put_organization_conformance_pack( conformance_pack = self.config_backend.put_organization_conformance_pack(
name=self._get_param("OrganizationConformancePackName"), name=self._get_param("OrganizationConformancePackName"),
template_s3_uri=self._get_param("TemplateS3Uri"), template_s3_uri=self._get_param("TemplateS3Uri"),
@ -183,19 +181,19 @@ class ConfigResponse(BaseResponse):
return json.dumps(conformance_pack) return json.dumps(conformance_pack)
def describe_organization_conformance_packs(self): def describe_organization_conformance_packs(self) -> str:
conformance_packs = self.config_backend.describe_organization_conformance_packs( conformance_packs = self.config_backend.describe_organization_conformance_packs(
self._get_param("OrganizationConformancePackNames") self._get_param("OrganizationConformancePackNames")
) )
return json.dumps(conformance_packs) return json.dumps(conformance_packs)
def describe_organization_conformance_pack_statuses(self): def describe_organization_conformance_pack_statuses(self) -> str:
statuses = self.config_backend.describe_organization_conformance_pack_statuses( statuses = self.config_backend.describe_organization_conformance_pack_statuses(
self._get_param("OrganizationConformancePackNames") self._get_param("OrganizationConformancePackNames")
) )
return json.dumps(statuses) return json.dumps(statuses)
def get_organization_conformance_pack_detailed_status(self): def get_organization_conformance_pack_detailed_status(self) -> str:
# 'Filters' parameter is not implemented yet # 'Filters' parameter is not implemented yet
statuses = ( statuses = (
self.config_backend.get_organization_conformance_pack_detailed_status( self.config_backend.get_organization_conformance_pack_detailed_status(
@ -204,36 +202,36 @@ class ConfigResponse(BaseResponse):
) )
return json.dumps(statuses) return json.dumps(statuses)
def delete_organization_conformance_pack(self): def delete_organization_conformance_pack(self) -> str:
self.config_backend.delete_organization_conformance_pack( self.config_backend.delete_organization_conformance_pack(
self._get_param("OrganizationConformancePackName") self._get_param("OrganizationConformancePackName")
) )
return "" return ""
def tag_resource(self): def tag_resource(self) -> str:
self.config_backend.tag_resource( self.config_backend.tag_resource(
self._get_param("ResourceArn"), self._get_param("Tags") self._get_param("ResourceArn"), self._get_param("Tags")
) )
return "" return ""
def untag_resource(self): def untag_resource(self) -> str:
self.config_backend.untag_resource( self.config_backend.untag_resource(
self._get_param("ResourceArn"), self._get_param("TagKeys") self._get_param("ResourceArn"), self._get_param("TagKeys")
) )
return "" return ""
def put_config_rule(self): def put_config_rule(self) -> str:
self.config_backend.put_config_rule( self.config_backend.put_config_rule(
self._get_param("ConfigRule"), self._get_param("Tags") self._get_param("ConfigRule"), self._get_param("Tags")
) )
return "" return ""
def describe_config_rules(self): def describe_config_rules(self) -> str:
rules = self.config_backend.describe_config_rules( rules = self.config_backend.describe_config_rules(
self._get_param("ConfigRuleNames"), self._get_param("NextToken") self._get_param("ConfigRuleNames"), self._get_param("NextToken")
) )
return json.dumps(rules) return json.dumps(rules)
def delete_config_rule(self): def delete_config_rule(self) -> str:
self.config_backend.delete_config_rule(self._get_param("ConfigRuleName")) self.config_backend.delete_config_rule(self._get_param("ConfigRuleName"))
return "" return ""

View File

@ -1,5 +1,5 @@
from abc import abstractmethod from abc import abstractmethod
from typing import Any, Dict from typing import Any, Dict, List, Optional
from .base_backend import InstanceTrackerMeta from .base_backend import InstanceTrackerMeta
@ -98,14 +98,14 @@ class ConfigQueryModel:
def list_config_service_resources( def list_config_service_resources(
self, self,
account_id, account_id: str,
resource_ids, resource_ids: Optional[List[str]],
resource_name, resource_name: Optional[str],
limit, limit: int,
next_token, next_token: Optional[str],
backend_region=None, backend_region: Optional[str] = None,
resource_region=None, resource_region: Optional[str] = None,
aggregator=None, aggregator: Optional[Dict[str, Any]] = None,
): ):
"""For AWS Config. This will list all of the resources of the given type and optional resource name and region. """For AWS Config. This will list all of the resources of the given type and optional resource name and region.
@ -158,12 +158,12 @@ class ConfigQueryModel:
def get_config_resource( def get_config_resource(
self, self,
account_id, account_id: str,
resource_id, resource_id: str,
resource_name=None, resource_name: Optional[str] = None,
backend_region=None, backend_region: Optional[str] = None,
resource_region=None, resource_region: Optional[str] = None,
): ) -> Dict[str, Any]:
"""For AWS Config. This will query the backend for the specific resource type configuration. """For AWS Config. This will query the backend for the specific resource type configuration.
This supports both aggregated, and non-aggregated fetching -- for batched fetching -- the Config batching requests This supports both aggregated, and non-aggregated fetching -- for batched fetching -- the Config batching requests

View File

@ -870,7 +870,7 @@ class AWSServiceSpec(object):
""" """
def __init__(self, path): def __init__(self, path: str):
spec = load_resource("botocore", path) spec = load_resource("botocore", path)
self.metadata = spec["metadata"] self.metadata = spec["metadata"]

View File

@ -1,12 +1,14 @@
import pathlib import pathlib
from typing import Any, Dict
from os import listdir from os import listdir
from ..utils import generic_filter from ..utils import generic_filter
from moto.utilities.utils import load_resource from moto.utilities.utils import load_resource
from ..exceptions import InvalidFilter, InvalidInstanceTypeError from ..exceptions import InvalidFilter, InvalidInstanceTypeError
INSTANCE_TYPES = load_resource(__name__, "../resources/instance_types.json") INSTANCE_TYPES: Dict[str, Any] = load_resource(
__name__, "../resources/instance_types.json"
)
INSTANCE_FAMILIES = list(set([i.split(".")[0] for i in INSTANCE_TYPES.keys()])) INSTANCE_FAMILIES = list(set([i.split(".")[0] for i in INSTANCE_TYPES.keys()]))
root = pathlib.Path(__file__).parent root = pathlib.Path(__file__).parent

View File

@ -3,7 +3,7 @@ import hashlib
import pkgutil import pkgutil
from collections.abc import MutableMapping from collections.abc import MutableMapping
from typing import Any, Dict from typing import Any, Dict, Union
def str2bool(v): def str2bool(v):
@ -13,7 +13,9 @@ def str2bool(v):
return False return False
def load_resource(package, resource, as_json=True): def load_resource(
package: str, resource: str, as_json: bool = True
) -> Union[Dict[str, Any], str]:
""" """
Open a file, and return the contents as JSON. Open a file, and return the contents as JSON.
Usage: Usage:

View File

@ -18,7 +18,7 @@ disable = W,C,R,E
enable = anomalous-backslash-in-string, arguments-renamed, dangerous-default-value, deprecated-module, function-redefined, import-self, redefined-builtin, redefined-outer-name, reimported, pointless-statement, super-with-arguments, unused-argument, unused-import, unused-variable, useless-else-on-loop, wildcard-import enable = anomalous-backslash-in-string, arguments-renamed, dangerous-default-value, deprecated-module, function-redefined, import-self, redefined-builtin, redefined-outer-name, reimported, pointless-statement, super-with-arguments, unused-argument, unused-import, unused-variable, useless-else-on-loop, wildcard-import
[mypy] [mypy]
files= moto/a*,moto/b*,moto/ce,moto/cloudformation,moto/cloudfront,moto/cloudtrail,moto/codebuild,moto/cloudwatch,moto/codepipeline,moto/codecommit,moto/cognito*,moto/comprehend files= moto/a*,moto/b*,moto/ce,moto/cloudformation,moto/cloudfront,moto/cloudtrail,moto/codebuild,moto/cloudwatch,moto/codepipeline,moto/codecommit,moto/cognito*,moto/comprehend,moto/config
show_column_numbers=True show_column_numbers=True
show_error_codes = True show_error_codes = True
disable_error_code=abstract disable_error_code=abstract