202 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			202 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from abc import abstractmethod
 | |
| from typing import Any, Dict, List, Optional, Tuple
 | |
| from .base_backend import InstanceTrackerMeta
 | |
| 
 | |
| 
 | |
| class BaseModel(metaclass=InstanceTrackerMeta):
 | |
|     def __new__(
 | |
|         cls, *args: Any, **kwargs: Any  # pylint: disable=unused-argument
 | |
|     ) -> "BaseModel":
 | |
|         instance = super(BaseModel, cls).__new__(cls)
 | |
|         cls.instances.append(instance)  # type: ignore[attr-defined]
 | |
|         return instance
 | |
| 
 | |
| 
 | |
| # Parent class for every Model that can be instantiated by CloudFormation
 | |
| # On subclasses, implement all methods as @staticmethod to ensure correct behaviour of the CF parser
 | |
| class CloudFormationModel(BaseModel):
 | |
|     @staticmethod
 | |
|     @abstractmethod
 | |
|     def cloudformation_name_type() -> str:
 | |
|         # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-name.html
 | |
|         # This must be implemented as a staticmethod with no parameters
 | |
|         # Return "" for resources that do not have a name property
 | |
|         return ""
 | |
| 
 | |
|     @staticmethod
 | |
|     @abstractmethod
 | |
|     def cloudformation_type() -> str:
 | |
|         # This must be implemented as a staticmethod with no parameters
 | |
|         # See for example https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html
 | |
|         return "AWS::SERVICE::RESOURCE"
 | |
| 
 | |
|     @classmethod
 | |
|     @abstractmethod
 | |
|     def has_cfn_attr(cls, attr: str) -> bool:  # pylint: disable=unused-argument
 | |
|         # Used for validation
 | |
|         # If a template creates an Output for an attribute that does not exist, an error should be thrown
 | |
|         return True
 | |
| 
 | |
|     @classmethod
 | |
|     @abstractmethod
 | |
|     def create_from_cloudformation_json(  # type: ignore[misc]
 | |
|         cls,
 | |
|         resource_name: str,
 | |
|         cloudformation_json: Dict[str, Any],
 | |
|         account_id: str,
 | |
|         region_name: str,
 | |
|         **kwargs: Any
 | |
|     ) -> Any:
 | |
|         # This must be implemented as a classmethod with parameters:
 | |
|         # cls, resource_name, cloudformation_json, account_id, region_name
 | |
|         # Extract the resource parameters from the cloudformation json
 | |
|         # and return an instance of the resource class
 | |
|         ...
 | |
| 
 | |
|     @classmethod
 | |
|     @abstractmethod
 | |
|     def update_from_cloudformation_json(  # type: ignore[misc]
 | |
|         cls,
 | |
|         original_resource: Any,
 | |
|         new_resource_name: str,
 | |
|         cloudformation_json: Dict[str, Any],
 | |
|         account_id: str,
 | |
|         region_name: str,
 | |
|     ) -> Any:
 | |
|         # This must be implemented as a classmethod with parameters:
 | |
|         # cls, original_resource, new_resource_name, cloudformation_json, account_id, region_name
 | |
|         # Extract the resource parameters from the cloudformation json,
 | |
|         # delete the old resource and return the new one. Optionally inspect
 | |
|         # the change in parameters and no-op when nothing has changed.
 | |
|         ...
 | |
| 
 | |
|     @classmethod
 | |
|     @abstractmethod
 | |
|     def delete_from_cloudformation_json(  # type: ignore[misc]
 | |
|         cls,
 | |
|         resource_name: str,
 | |
|         cloudformation_json: Dict[str, Any],
 | |
|         account_id: str,
 | |
|         region_name: str,
 | |
|     ) -> None:
 | |
|         # This must be implemented as a classmethod with parameters:
 | |
|         # cls, resource_name, cloudformation_json, account_id, region_name
 | |
|         # Extract the resource parameters from the cloudformation json
 | |
|         # and delete the resource. Do not include a return statement.
 | |
|         ...
 | |
| 
 | |
|     @abstractmethod
 | |
|     def is_created(self) -> bool:
 | |
|         # Verify whether the resource was created successfully
 | |
|         # Assume True after initialization
 | |
|         # Custom resources may need time after init before they are created successfully
 | |
|         return True
 | |
| 
 | |
| 
 | |
| class ConfigQueryModel:
 | |
|     def __init__(self, backends: Any):
 | |
|         """Inits based on the resource type's backends (1 for each region if applicable)"""
 | |
|         self.backends = backends
 | |
| 
 | |
|     def list_config_service_resources(
 | |
|         self,
 | |
|         account_id: str,
 | |
|         resource_ids: Optional[List[str]],
 | |
|         resource_name: Optional[str],
 | |
|         limit: int,
 | |
|         next_token: Optional[str],
 | |
|         backend_region: Optional[str] = None,
 | |
|         resource_region: Optional[str] = None,
 | |
|         aggregator: Optional[Dict[str, Any]] = None,
 | |
|     ) -> Tuple[List[Dict[str, Any]], Optional[str]]:
 | |
|         """For AWS Config. This will list all of the resources of the given type and optional resource name and region.
 | |
| 
 | |
|         This supports both aggregated and non-aggregated listing. The following notes the difference:
 | |
| 
 | |
|         - Non-Aggregated Listing -
 | |
|         This only lists resources within a region. The way that this is implemented in moto is based on the region
 | |
|         for the resource backend.
 | |
| 
 | |
|         You must set the `backend_region` to the region that the API request arrived from. resource_region can be set to `None`.
 | |
| 
 | |
|         - Aggregated Listing -
 | |
|         This lists resources from all potential regional backends. For non-global resource types, this should collect a full
 | |
|         list of resources from all the backends, and then be able to filter from the resource region. This is because an
 | |
|         aggregator can aggregate resources from multiple regions. In moto, aggregated regions will *assume full aggregation
 | |
|         from all resources in all regions for a given resource type*.
 | |
| 
 | |
|         The `backend_region` should be set to `None` for these queries, and the `resource_region` should optionally be set to
 | |
|         the `Filters` region parameter to filter out resources that reside in a specific region.
 | |
| 
 | |
|         For aggregated listings, pagination logic should be set such that the next page can properly span all the region backends.
 | |
|         As such, the proper way to implement is to first obtain a full list of results from all the region backends, and then filter
 | |
|         from there. It may be valuable to make this a concatenation of the region and resource name.
 | |
| 
 | |
|         :param account_id: The account number
 | |
|         :param resource_ids:  A list of resource IDs
 | |
|         :param resource_name: The individual name of a resource
 | |
|         :param limit: How many per page
 | |
|         :param next_token: The item that will page on
 | |
|         :param backend_region: The region for the backend to pull results from. Set to `None` if this is an aggregated query.
 | |
|         :param resource_region: The region for where the resources reside to pull results from. Set to `None` if this is a
 | |
|                                 non-aggregated query.
 | |
|         :param aggregator: If the query is an aggregated query, *AND* the resource has "non-standard" aggregation logic (mainly, IAM),
 | |
|                                 you'll need to pass aggregator used. In most cases, this should be omitted/set to `None`. See the
 | |
|                                 conditional logic under `if aggregator` in the moto/iam/config.py for the IAM example.
 | |
| 
 | |
|         :return: This should return a list of Dicts that have the following fields:
 | |
|             [
 | |
|                 {
 | |
|                     'type': 'AWS::The AWS Config data type',
 | |
|                     'name': 'The name of the resource',
 | |
|                     'id': 'The ID of the resource',
 | |
|                     'region': 'The region of the resource -- if global, then you may want to have the calling logic pass in the
 | |
|                                aggregator region in for the resource region -- or just us-east-1 :P'
 | |
|                 }
 | |
|                 , ...
 | |
|             ]
 | |
|         """
 | |
|         raise NotImplementedError()
 | |
| 
 | |
|     def get_config_resource(
 | |
|         self,
 | |
|         account_id: str,
 | |
|         resource_id: str,
 | |
|         resource_name: Optional[str] = None,
 | |
|         backend_region: Optional[str] = None,
 | |
|         resource_region: Optional[str] = None,
 | |
|     ) -> Optional[Dict[str, Any]]:
 | |
|         """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
 | |
|         will call this function N times to fetch the N objects needing to be fetched.
 | |
| 
 | |
|         - Non-Aggregated Fetching -
 | |
|         This only fetches a resource config within a region. The way that this is implemented in moto is based on the region
 | |
|         for the resource backend.
 | |
| 
 | |
|         You must set the `backend_region` to the region that the API request arrived from. `resource_region` should be set to `None`.
 | |
| 
 | |
|         - Aggregated Fetching -
 | |
|         This fetches resources from all potential regional backends. For non-global resource types, this should collect a full
 | |
|         list of resources from all the backends, and then be able to filter from the resource region. This is because an
 | |
|         aggregator can aggregate resources from multiple regions. In moto, aggregated regions will *assume full aggregation
 | |
|         from all resources in all regions for a given resource type*.
 | |
| 
 | |
|         ...
 | |
|         :param account_id:
 | |
|         :param resource_id:
 | |
|         :param resource_name:
 | |
|         :param backend_region:
 | |
|         :param resource_region:
 | |
|         :return:
 | |
|         """
 | |
|         raise NotImplementedError()
 | |
| 
 | |
| 
 | |
| class CloudWatchMetricProvider:
 | |
|     @staticmethod
 | |
|     @abstractmethod
 | |
|     def get_cloudwatch_metrics(account_id: str) -> Any:  # type: ignore[misc]
 | |
|         pass
 |