TechDebt: MyPy CloudFormation (#5603)
This commit is contained in:
parent
dc368fbe79
commit
03f5518703
@ -1075,10 +1075,6 @@ class EventSourceMapping(CloudFormationModel):
|
|||||||
lambda_backend = lambda_backends[account_id][region_name]
|
lambda_backend = lambda_backends[account_id][region_name]
|
||||||
lambda_backend.delete_event_source_mapping(self.uuid)
|
lambda_backend.delete_event_source_mapping(self.uuid)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def cloudformation_name_type() -> None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cloudformation_type() -> str:
|
def cloudformation_type() -> str:
|
||||||
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html
|
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html
|
||||||
@ -1142,10 +1138,6 @@ class LambdaVersion(CloudFormationModel):
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return str(self.logical_resource_id) # type: ignore[attr-defined]
|
return str(self.logical_resource_id) # type: ignore[attr-defined]
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def cloudformation_name_type() -> None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cloudformation_type() -> str:
|
def cloudformation_type() -> str:
|
||||||
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-version.html
|
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-version.html
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import threading
|
import threading
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
from moto import settings
|
from moto import settings
|
||||||
from moto.core import CloudFormationModel
|
from moto.core import CloudFormationModel
|
||||||
@ -8,33 +9,40 @@ from moto.moto_api._internal import mock_random
|
|||||||
|
|
||||||
|
|
||||||
class CustomModel(CloudFormationModel):
|
class CustomModel(CloudFormationModel):
|
||||||
def __init__(self, region_name, request_id, logical_id, resource_name):
|
def __init__(
|
||||||
|
self, region_name: str, request_id: str, logical_id: str, resource_name: str
|
||||||
|
):
|
||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
self.request_id = request_id
|
self.request_id = request_id
|
||||||
self.logical_id = logical_id
|
self.logical_id = logical_id
|
||||||
self.resource_name = resource_name
|
self.resource_name = resource_name
|
||||||
self.data = dict()
|
self.data: Dict[str, Any] = dict()
|
||||||
self._finished = False
|
self._finished = False
|
||||||
|
|
||||||
def set_data(self, data):
|
def set_data(self, data: Dict[str, Any]) -> None:
|
||||||
self.data = data
|
self.data = data
|
||||||
self._finished = True
|
self._finished = True
|
||||||
|
|
||||||
def is_created(self):
|
def is_created(self) -> bool:
|
||||||
return self._finished
|
return self._finished
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def physical_resource_id(self):
|
def physical_resource_id(self) -> str:
|
||||||
return self.resource_name
|
return self.resource_name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cloudformation_type():
|
def cloudformation_type() -> str:
|
||||||
return "?"
|
return "?"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_cloudformation_json(
|
def create_from_cloudformation_json( # type: ignore[misc]
|
||||||
cls, resource_name, cloudformation_json, account_id, region_name, **kwargs
|
cls,
|
||||||
):
|
resource_name: str,
|
||||||
|
cloudformation_json: Dict[str, Any],
|
||||||
|
account_id: str,
|
||||||
|
region_name: str,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> "CustomModel":
|
||||||
logical_id = kwargs["LogicalId"]
|
logical_id = kwargs["LogicalId"]
|
||||||
stack_id = kwargs["StackId"]
|
stack_id = kwargs["StackId"]
|
||||||
resource_type = kwargs["ResourceType"]
|
resource_type = kwargs["ResourceType"]
|
||||||
@ -85,11 +93,11 @@ class CustomModel(CloudFormationModel):
|
|||||||
return custom_resource
|
return custom_resource
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def has_cfn_attr(cls, attr): # pylint: disable=unused-argument
|
def has_cfn_attr(cls, attr: str) -> bool: # pylint: disable=unused-argument
|
||||||
# We don't know which attributes are supported for third-party resources
|
# We don't know which attributes are supported for third-party resources
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_cfn_attribute(self, attribute_name):
|
def get_cfn_attribute(self, attribute_name: str) -> Any:
|
||||||
if attribute_name in self.data:
|
if attribute_name in self.data:
|
||||||
return self.data[attribute_name]
|
return self.data[attribute_name]
|
||||||
return None
|
return None
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from moto.core.exceptions import RESTError
|
from moto.core.exceptions import RESTError
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class UnformattedGetAttTemplateException(Exception):
|
class UnformattedGetAttTemplateException(Exception):
|
||||||
@ -10,7 +11,7 @@ class UnformattedGetAttTemplateException(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class ValidationError(RESTError):
|
class ValidationError(RESTError):
|
||||||
def __init__(self, name_or_id=None, message=None):
|
def __init__(self, name_or_id: Optional[str] = None, message: Optional[str] = None):
|
||||||
if message is None:
|
if message is None:
|
||||||
message = "Stack with id {0} does not exist".format(name_or_id)
|
message = "Stack with id {0} does not exist".format(name_or_id)
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ class ValidationError(RESTError):
|
|||||||
|
|
||||||
|
|
||||||
class MissingParameterError(RESTError):
|
class MissingParameterError(RESTError):
|
||||||
def __init__(self, parameter_name):
|
def __init__(self, parameter_name: str):
|
||||||
template = Template(ERROR_RESPONSE)
|
template = Template(ERROR_RESPONSE)
|
||||||
message = "Missing parameter {0}".format(parameter_name)
|
message = "Missing parameter {0}".format(parameter_name)
|
||||||
super().__init__(error_type="ValidationError", message=message)
|
super().__init__(error_type="ValidationError", message=message)
|
||||||
@ -30,7 +31,7 @@ class MissingParameterError(RESTError):
|
|||||||
class ExportNotFound(RESTError):
|
class ExportNotFound(RESTError):
|
||||||
"""Exception to raise if a template tries to import a non-existent export"""
|
"""Exception to raise if a template tries to import a non-existent export"""
|
||||||
|
|
||||||
def __init__(self, export_name):
|
def __init__(self, export_name: str):
|
||||||
template = Template(ERROR_RESPONSE)
|
template = Template(ERROR_RESPONSE)
|
||||||
message = "No export named {0} found.".format(export_name)
|
message = "No export named {0} found.".format(export_name)
|
||||||
super().__init__(error_type="ExportNotFound", message=message)
|
super().__init__(error_type="ExportNotFound", message=message)
|
||||||
@ -38,7 +39,7 @@ class ExportNotFound(RESTError):
|
|||||||
|
|
||||||
|
|
||||||
class UnsupportedAttribute(ValidationError):
|
class UnsupportedAttribute(ValidationError):
|
||||||
def __init__(self, resource, attr):
|
def __init__(self, resource: str, attr: str):
|
||||||
template = Template(ERROR_RESPONSE)
|
template = Template(ERROR_RESPONSE)
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.description = template.render(
|
self.description = template.render(
|
||||||
|
@ -3,10 +3,11 @@ import json
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from typing import Any, Dict, List, Optional, Iterable, Tuple, Union, Type
|
||||||
from yaml.parser import ParserError # pylint:disable=c-extension-no-member
|
from yaml.parser import ParserError # pylint:disable=c-extension-no-member
|
||||||
from yaml.scanner import ScannerError # pylint:disable=c-extension-no-member
|
from yaml.scanner import ScannerError # pylint:disable=c-extension-no-member
|
||||||
|
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel, CloudFormationModel
|
||||||
from moto.core.utils import (
|
from moto.core.utils import (
|
||||||
iso_8601_datetime_with_milliseconds,
|
iso_8601_datetime_with_milliseconds,
|
||||||
iso_8601_datetime_without_milliseconds,
|
iso_8601_datetime_without_milliseconds,
|
||||||
@ -15,7 +16,8 @@ from moto.core.utils import (
|
|||||||
from moto.moto_api._internal import mock_random
|
from moto.moto_api._internal import mock_random
|
||||||
from moto.sns.models import sns_backends
|
from moto.sns.models import sns_backends
|
||||||
|
|
||||||
from .parsing import ResourceMap, OutputMap
|
from .custom_model import CustomModel
|
||||||
|
from .parsing import ResourceMap, Output, OutputMap, Export
|
||||||
from .utils import (
|
from .utils import (
|
||||||
generate_changeset_id,
|
generate_changeset_id,
|
||||||
generate_stack_id,
|
generate_stack_id,
|
||||||
@ -30,17 +32,16 @@ from .exceptions import ValidationError
|
|||||||
class FakeStackSet(BaseModel):
|
class FakeStackSet(BaseModel):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
stackset_id,
|
stackset_id: str,
|
||||||
account_id,
|
account_id: str,
|
||||||
name,
|
name: str,
|
||||||
template,
|
template: str,
|
||||||
region="us-east-1",
|
region: str,
|
||||||
status="ACTIVE",
|
description: Optional[str],
|
||||||
description=None,
|
parameters: Dict[str, str],
|
||||||
parameters=None,
|
tags: Optional[Dict[str, str]] = None,
|
||||||
tags=None,
|
admin_role: str = "AWSCloudFormationStackSetAdministrationRole",
|
||||||
admin_role="AWSCloudFormationStackSetAdministrationRole",
|
execution_role: str = "AWSCloudFormationStackSetExecutionRole",
|
||||||
execution_role="AWSCloudFormationStackSetExecutionRole",
|
|
||||||
):
|
):
|
||||||
self.id = stackset_id
|
self.id = stackset_id
|
||||||
self.arn = generate_stackset_arn(stackset_id, region, account_id)
|
self.arn = generate_stackset_arn(stackset_id, region, account_id)
|
||||||
@ -52,18 +53,23 @@ class FakeStackSet(BaseModel):
|
|||||||
self.admin_role = admin_role
|
self.admin_role = admin_role
|
||||||
self.admin_role_arn = f"arn:aws:iam::{account_id}:role/{self.admin_role}"
|
self.admin_role_arn = f"arn:aws:iam::{account_id}:role/{self.admin_role}"
|
||||||
self.execution_role = execution_role
|
self.execution_role = execution_role
|
||||||
self.status = status
|
self.status = "ACTIVE"
|
||||||
self.instances = FakeStackInstances(parameters, self.id, self.name)
|
self.instances = FakeStackInstances(parameters, self.id, self.name)
|
||||||
self.stack_instances = self.instances.stack_instances
|
self.stack_instances = self.instances.stack_instances
|
||||||
self.operations = []
|
self.operations: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
def _create_operation(
|
def _create_operation(
|
||||||
self, operation_id, action, status, accounts=None, regions=None
|
self,
|
||||||
):
|
operation_id: str,
|
||||||
|
action: str,
|
||||||
|
status: str,
|
||||||
|
accounts: Optional[List[str]] = None,
|
||||||
|
regions: Optional[List[str]] = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
accounts = accounts or []
|
accounts = accounts or []
|
||||||
regions = regions or []
|
regions = regions or []
|
||||||
operation = {
|
operation = {
|
||||||
"OperationId": str(operation_id),
|
"OperationId": operation_id,
|
||||||
"Action": action,
|
"Action": action,
|
||||||
"Status": status,
|
"Status": status,
|
||||||
"CreationTimestamp": datetime.now(),
|
"CreationTimestamp": datetime.now(),
|
||||||
@ -76,41 +82,39 @@ class FakeStackSet(BaseModel):
|
|||||||
self.operations += [operation]
|
self.operations += [operation]
|
||||||
return operation
|
return operation
|
||||||
|
|
||||||
def get_operation(self, operation_id):
|
def get_operation(self, operation_id: str) -> Dict[str, Any]:
|
||||||
for operation in self.operations:
|
for operation in self.operations:
|
||||||
if operation_id == operation["OperationId"]:
|
if operation_id == operation["OperationId"]:
|
||||||
return operation
|
return operation
|
||||||
raise ValidationError(operation_id)
|
raise ValidationError(operation_id)
|
||||||
|
|
||||||
def update_operation(self, operation_id, status):
|
def update_operation(self, operation_id: str, status: str) -> str:
|
||||||
operation = self.get_operation(operation_id)
|
operation = self.get_operation(operation_id)
|
||||||
operation["Status"] = status
|
operation["Status"] = status
|
||||||
return operation_id
|
return operation_id
|
||||||
|
|
||||||
def delete(self):
|
def delete(self) -> None:
|
||||||
self.status = "DELETED"
|
self.status = "DELETED"
|
||||||
|
|
||||||
def update(
|
def update(
|
||||||
self,
|
self,
|
||||||
template,
|
template: str,
|
||||||
description,
|
description: str,
|
||||||
parameters,
|
parameters: Dict[str, str],
|
||||||
tags,
|
tags: Dict[str, str],
|
||||||
admin_role,
|
admin_role: str,
|
||||||
execution_role,
|
execution_role: str,
|
||||||
accounts,
|
accounts: List[str],
|
||||||
regions,
|
regions: List[str],
|
||||||
operation_id=None,
|
operation_id: str,
|
||||||
):
|
) -> Dict[str, Any]:
|
||||||
if not operation_id:
|
|
||||||
operation_id = mock_random.uuid4()
|
|
||||||
|
|
||||||
self.template = template if template else self.template
|
self.template = template or self.template
|
||||||
self.description = description if description is not None else self.description
|
self.description = description if description is not None else self.description
|
||||||
self.parameters = parameters if parameters else self.parameters
|
self.parameters = parameters or self.parameters
|
||||||
self.tags = tags if tags else self.tags
|
self.tags = tags or self.tags
|
||||||
self.admin_role = admin_role if admin_role else self.admin_role
|
self.admin_role = admin_role or self.admin_role
|
||||||
self.execution_role = execution_role if execution_role else self.execution_role
|
self.execution_role = execution_role or self.execution_role
|
||||||
|
|
||||||
if accounts and regions:
|
if accounts and regions:
|
||||||
self.update_instances(accounts, regions, self.parameters)
|
self.update_instances(accounts, regions, self.parameters)
|
||||||
@ -124,11 +128,12 @@ class FakeStackSet(BaseModel):
|
|||||||
)
|
)
|
||||||
return operation
|
return operation
|
||||||
|
|
||||||
def create_stack_instances(self, accounts, regions, parameters, operation_id=None):
|
def create_stack_instances(
|
||||||
if not operation_id:
|
self, accounts: List[str], regions: List[str], parameters: List[Dict[str, Any]]
|
||||||
operation_id = mock_random.uuid4()
|
) -> None:
|
||||||
|
operation_id = str(mock_random.uuid4())
|
||||||
if not parameters:
|
if not parameters:
|
||||||
parameters = self.parameters
|
parameters = self.parameters # type: ignore[assignment]
|
||||||
|
|
||||||
self.instances.create_instances(accounts, regions, parameters)
|
self.instances.create_instances(accounts, regions, parameters)
|
||||||
self._create_operation(
|
self._create_operation(
|
||||||
@ -139,24 +144,23 @@ class FakeStackSet(BaseModel):
|
|||||||
regions=regions,
|
regions=regions,
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete_stack_instances(self, accounts, regions, operation_id=None):
|
def delete_stack_instances(self, accounts: List[str], regions: List[str]) -> None:
|
||||||
if not operation_id:
|
operation_id = str(mock_random.uuid4())
|
||||||
operation_id = mock_random.uuid4()
|
|
||||||
|
|
||||||
self.instances.delete(accounts, regions)
|
self.instances.delete(accounts, regions)
|
||||||
|
|
||||||
operation = self._create_operation(
|
self._create_operation(
|
||||||
operation_id=operation_id,
|
operation_id=operation_id,
|
||||||
action="DELETE",
|
action="DELETE",
|
||||||
status="SUCCEEDED",
|
status="SUCCEEDED",
|
||||||
accounts=accounts,
|
accounts=accounts,
|
||||||
regions=regions,
|
regions=regions,
|
||||||
)
|
)
|
||||||
return operation
|
|
||||||
|
|
||||||
def update_instances(self, accounts, regions, parameters, operation_id=None):
|
def update_instances(
|
||||||
if not operation_id:
|
self, accounts: List[str], regions: List[str], parameters: Dict[str, str]
|
||||||
operation_id = mock_random.uuid4()
|
) -> Dict[str, Any]:
|
||||||
|
operation_id = str(mock_random.uuid4())
|
||||||
|
|
||||||
self.instances.update(accounts, regions, parameters)
|
self.instances.update(accounts, regions, parameters)
|
||||||
operation = self._create_operation(
|
operation = self._create_operation(
|
||||||
@ -170,14 +174,21 @@ class FakeStackSet(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class FakeStackInstances(BaseModel):
|
class FakeStackInstances(BaseModel):
|
||||||
def __init__(self, parameters, stackset_id, stackset_name):
|
def __init__(
|
||||||
self.parameters = parameters if parameters else {}
|
self, parameters: Dict[str, str], stackset_id: str, stackset_name: str
|
||||||
|
):
|
||||||
|
self.parameters = parameters or {}
|
||||||
self.stackset_id = stackset_id
|
self.stackset_id = stackset_id
|
||||||
self.stack_name = "StackSet-{}".format(stackset_id)
|
self.stack_name = "StackSet-{}".format(stackset_id)
|
||||||
self.stackset_name = stackset_name
|
self.stackset_name = stackset_name
|
||||||
self.stack_instances = []
|
self.stack_instances: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
def create_instances(self, accounts, regions, parameters):
|
def create_instances(
|
||||||
|
self,
|
||||||
|
accounts: List[str],
|
||||||
|
regions: List[str],
|
||||||
|
parameters: Optional[List[Dict[str, Any]]],
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
new_instances = []
|
new_instances = []
|
||||||
for region in regions:
|
for region in regions:
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
@ -187,13 +198,18 @@ class FakeStackInstances(BaseModel):
|
|||||||
"Region": region,
|
"Region": region,
|
||||||
"Account": account,
|
"Account": account,
|
||||||
"Status": "CURRENT",
|
"Status": "CURRENT",
|
||||||
"ParameterOverrides": parameters if parameters else [],
|
"ParameterOverrides": parameters or [],
|
||||||
}
|
}
|
||||||
new_instances.append(instance)
|
new_instances.append(instance)
|
||||||
self.stack_instances += new_instances
|
self.stack_instances += new_instances
|
||||||
return new_instances
|
return new_instances
|
||||||
|
|
||||||
def update(self, accounts, regions, parameters):
|
def update(
|
||||||
|
self,
|
||||||
|
accounts: List[str],
|
||||||
|
regions: List[str],
|
||||||
|
parameters: Optional[Dict[str, str]],
|
||||||
|
) -> Any:
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
for region in regions:
|
for region in regions:
|
||||||
instance = self.get_instance(account, region)
|
instance = self.get_instance(account, region)
|
||||||
@ -202,12 +218,12 @@ class FakeStackInstances(BaseModel):
|
|||||||
else:
|
else:
|
||||||
instance["ParameterOverrides"] = []
|
instance["ParameterOverrides"] = []
|
||||||
|
|
||||||
def delete(self, accounts, regions):
|
def delete(self, accounts: List[str], regions: List[str]) -> None:
|
||||||
for i, instance in enumerate(self.stack_instances):
|
for i, instance in enumerate(self.stack_instances):
|
||||||
if instance["Region"] in regions and instance["Account"] in accounts:
|
if instance["Region"] in regions and instance["Account"] in accounts:
|
||||||
self.stack_instances.pop(i)
|
self.stack_instances.pop(i)
|
||||||
|
|
||||||
def get_instance(self, account, region):
|
def get_instance(self, account: str, region: str) -> Dict[str, Any]: # type: ignore[return]
|
||||||
for i, instance in enumerate(self.stack_instances):
|
for i, instance in enumerate(self.stack_instances):
|
||||||
if instance["Region"] == region and instance["Account"] == account:
|
if instance["Region"] == region and instance["Account"] == account:
|
||||||
return self.stack_instances[i]
|
return self.stack_instances[i]
|
||||||
@ -216,16 +232,16 @@ class FakeStackInstances(BaseModel):
|
|||||||
class FakeStack(BaseModel):
|
class FakeStack(BaseModel):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
stack_id,
|
stack_id: str,
|
||||||
name,
|
name: str,
|
||||||
template,
|
template: Union[str, Dict[str, Any]],
|
||||||
parameters,
|
parameters: Dict[str, str],
|
||||||
account_id,
|
account_id: str,
|
||||||
region_name,
|
region_name: str,
|
||||||
notification_arns=None,
|
notification_arns: Optional[List[str]] = None,
|
||||||
tags=None,
|
tags: Optional[Dict[str, str]] = None,
|
||||||
role_arn=None,
|
role_arn: Optional[str] = None,
|
||||||
cross_stack_resources=None,
|
cross_stack_resources: Optional[Dict[str, Export]] = None,
|
||||||
):
|
):
|
||||||
self.stack_id = stack_id
|
self.stack_id = stack_id
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -235,26 +251,26 @@ class FakeStack(BaseModel):
|
|||||||
self._parse_template()
|
self._parse_template()
|
||||||
self.description = self.template_dict.get("Description")
|
self.description = self.template_dict.get("Description")
|
||||||
else:
|
else:
|
||||||
self.template_dict = {}
|
self.template_dict: Dict[str, Any] = {}
|
||||||
self.description = None
|
self.description = None
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
self.notification_arns = notification_arns if notification_arns else []
|
self.notification_arns = notification_arns if notification_arns else []
|
||||||
self.role_arn = role_arn
|
self.role_arn = role_arn
|
||||||
self.tags = tags if tags else {}
|
self.tags = tags if tags else {}
|
||||||
self.events = []
|
self.events: List[FakeEvent] = []
|
||||||
self.policy = ""
|
self.policy = ""
|
||||||
|
|
||||||
self.cross_stack_resources = cross_stack_resources or {}
|
self.cross_stack_resources: Dict[str, Export] = cross_stack_resources or {}
|
||||||
self.resource_map = self._create_resource_map()
|
self.resource_map = self._create_resource_map()
|
||||||
|
|
||||||
self.custom_resources = dict()
|
self.custom_resources: Dict[str, CustomModel] = dict()
|
||||||
|
|
||||||
self.output_map = self._create_output_map()
|
self.output_map = self._create_output_map()
|
||||||
self.creation_time = datetime.utcnow()
|
self.creation_time = datetime.utcnow()
|
||||||
self.status = "CREATE_PENDING"
|
self.status = "CREATE_PENDING"
|
||||||
|
|
||||||
def has_template(self, other_template):
|
def has_template(self, other_template: str) -> bool:
|
||||||
our_template = (
|
our_template = (
|
||||||
self.template
|
self.template
|
||||||
if isinstance(self.template, dict)
|
if isinstance(self.template, dict)
|
||||||
@ -262,10 +278,10 @@ class FakeStack(BaseModel):
|
|||||||
)
|
)
|
||||||
return our_template == json.loads(other_template)
|
return our_template == json.loads(other_template)
|
||||||
|
|
||||||
def has_parameters(self, other_parameters):
|
def has_parameters(self, other_parameters: Dict[str, Any]) -> bool:
|
||||||
return self.parameters == other_parameters
|
return self.parameters == other_parameters
|
||||||
|
|
||||||
def _create_resource_map(self):
|
def _create_resource_map(self) -> ResourceMap:
|
||||||
resource_map = ResourceMap(
|
resource_map = ResourceMap(
|
||||||
self.stack_id,
|
self.stack_id,
|
||||||
self.name,
|
self.name,
|
||||||
@ -279,16 +295,19 @@ class FakeStack(BaseModel):
|
|||||||
resource_map.load()
|
resource_map.load()
|
||||||
return resource_map
|
return resource_map
|
||||||
|
|
||||||
def _create_output_map(self):
|
def _create_output_map(self) -> OutputMap:
|
||||||
return OutputMap(self.resource_map, self.template_dict, self.stack_id)
|
return OutputMap(self.resource_map, self.template_dict, self.stack_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def creation_time_iso_8601(self):
|
def creation_time_iso_8601(self) -> str:
|
||||||
return iso_8601_datetime_without_milliseconds(self.creation_time)
|
return iso_8601_datetime_without_milliseconds(self.creation_time) # type: ignore[return-value]
|
||||||
|
|
||||||
def _add_stack_event(
|
def _add_stack_event(
|
||||||
self, resource_status, resource_status_reason=None, resource_properties=None
|
self,
|
||||||
):
|
resource_status: str,
|
||||||
|
resource_status_reason: Optional[str] = None,
|
||||||
|
resource_properties: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
|
||||||
event = FakeEvent(
|
event = FakeEvent(
|
||||||
stack_id=self.stack_id,
|
stack_id=self.stack_id,
|
||||||
@ -304,58 +323,36 @@ class FakeStack(BaseModel):
|
|||||||
event.sendToSns(self.account_id, self.region_name, self.notification_arns)
|
event.sendToSns(self.account_id, self.region_name, self.notification_arns)
|
||||||
self.events.append(event)
|
self.events.append(event)
|
||||||
|
|
||||||
def _add_resource_event(
|
def _parse_template(self) -> None:
|
||||||
self,
|
|
||||||
logical_resource_id,
|
|
||||||
resource_status,
|
|
||||||
resource_status_reason=None,
|
|
||||||
resource_properties=None,
|
|
||||||
):
|
|
||||||
# not used yet... feel free to help yourself
|
|
||||||
resource = self.resource_map[logical_resource_id]
|
|
||||||
self.events.append(
|
|
||||||
FakeEvent(
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
stack_name=self.name,
|
|
||||||
logical_resource_id=logical_resource_id,
|
|
||||||
physical_resource_id=resource.physical_resource_id,
|
|
||||||
resource_type=resource.type,
|
|
||||||
resource_status=resource_status,
|
|
||||||
resource_status_reason=resource_status_reason,
|
|
||||||
resource_properties=resource_properties,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _parse_template(self):
|
|
||||||
yaml.add_multi_constructor("", yaml_tag_constructor)
|
yaml.add_multi_constructor("", yaml_tag_constructor)
|
||||||
try:
|
try:
|
||||||
self.template_dict = yaml.load(self.template, Loader=yaml.Loader)
|
self.template_dict = yaml.load(self.template, Loader=yaml.Loader) # type: ignore[arg-type]
|
||||||
except (ParserError, ScannerError):
|
except (ParserError, ScannerError):
|
||||||
self.template_dict = json.loads(self.template)
|
self.template_dict = json.loads(self.template) # type: ignore[arg-type]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stack_parameters(self):
|
def stack_parameters(self) -> Dict[str, Any]: # type: ignore[misc]
|
||||||
return self.resource_map.resolved_parameters
|
return self.resource_map.resolved_parameters
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stack_resources(self):
|
def stack_resources(self) -> Iterable[Type[CloudFormationModel]]:
|
||||||
return self.resource_map.values()
|
return self.resource_map.values()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stack_outputs(self):
|
def stack_outputs(self) -> List[Output]:
|
||||||
return [v for v in self.output_map.values() if v]
|
return [v for v in self.output_map.values() if v]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def exports(self):
|
def exports(self) -> List[Export]:
|
||||||
return self.output_map.exports
|
return self.output_map.exports
|
||||||
|
|
||||||
def add_custom_resource(self, custom_resource):
|
def add_custom_resource(self, custom_resource: CustomModel) -> None:
|
||||||
self.custom_resources[custom_resource.logical_id] = custom_resource
|
self.custom_resources[custom_resource.logical_id] = custom_resource
|
||||||
|
|
||||||
def get_custom_resource(self, custom_resource):
|
def get_custom_resource(self, custom_resource: str) -> CustomModel:
|
||||||
return self.custom_resources[custom_resource]
|
return self.custom_resources[custom_resource]
|
||||||
|
|
||||||
def create_resources(self):
|
def create_resources(self) -> None:
|
||||||
self.status = "CREATE_IN_PROGRESS"
|
self.status = "CREATE_IN_PROGRESS"
|
||||||
all_resources_ready = self.resource_map.create(self.template_dict)
|
all_resources_ready = self.resource_map.create(self.template_dict)
|
||||||
# Set the description of the stack
|
# Set the description of the stack
|
||||||
@ -363,15 +360,21 @@ class FakeStack(BaseModel):
|
|||||||
if all_resources_ready:
|
if all_resources_ready:
|
||||||
self.mark_creation_complete()
|
self.mark_creation_complete()
|
||||||
|
|
||||||
def verify_readiness(self):
|
def verify_readiness(self) -> None:
|
||||||
if self.resource_map.creation_complete():
|
if self.resource_map.creation_complete():
|
||||||
self.mark_creation_complete()
|
self.mark_creation_complete()
|
||||||
|
|
||||||
def mark_creation_complete(self):
|
def mark_creation_complete(self) -> None:
|
||||||
self.status = "CREATE_COMPLETE"
|
self.status = "CREATE_COMPLETE"
|
||||||
self._add_stack_event("CREATE_COMPLETE")
|
self._add_stack_event("CREATE_COMPLETE")
|
||||||
|
|
||||||
def update(self, template, role_arn=None, parameters=None, tags=None):
|
def update(
|
||||||
|
self,
|
||||||
|
template: str,
|
||||||
|
role_arn: Optional[str] = None,
|
||||||
|
parameters: Optional[Dict[str, Any]] = None,
|
||||||
|
tags: Optional[Dict[str, str]] = None,
|
||||||
|
) -> None:
|
||||||
self._add_stack_event(
|
self._add_stack_event(
|
||||||
"UPDATE_IN_PROGRESS", resource_status_reason="User Initiated"
|
"UPDATE_IN_PROGRESS", resource_status_reason="User Initiated"
|
||||||
)
|
)
|
||||||
@ -387,7 +390,7 @@ class FakeStack(BaseModel):
|
|||||||
self.tags = tags
|
self.tags = tags
|
||||||
# TODO: update tags in the resource map
|
# TODO: update tags in the resource map
|
||||||
|
|
||||||
def delete(self):
|
def delete(self) -> None:
|
||||||
self._add_stack_event(
|
self._add_stack_event(
|
||||||
"DELETE_IN_PROGRESS", resource_status_reason="User Initiated"
|
"DELETE_IN_PROGRESS", resource_status_reason="User Initiated"
|
||||||
)
|
)
|
||||||
@ -397,7 +400,7 @@ class FakeStack(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class FakeChange(BaseModel):
|
class FakeChange(BaseModel):
|
||||||
def __init__(self, action, logical_resource_id, resource_type):
|
def __init__(self, action: str, logical_resource_id: str, resource_type: str):
|
||||||
self.action = action
|
self.action = action
|
||||||
self.logical_resource_id = logical_resource_id
|
self.logical_resource_id = logical_resource_id
|
||||||
self.resource_type = resource_type
|
self.resource_type = resource_type
|
||||||
@ -406,16 +409,16 @@ class FakeChange(BaseModel):
|
|||||||
class FakeChangeSet(BaseModel):
|
class FakeChangeSet(BaseModel):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
change_set_type,
|
change_set_type: str,
|
||||||
change_set_id,
|
change_set_id: str,
|
||||||
change_set_name,
|
change_set_name: str,
|
||||||
stack,
|
stack: FakeStack,
|
||||||
template,
|
template: str,
|
||||||
parameters,
|
parameters: Dict[str, str],
|
||||||
description,
|
description: str,
|
||||||
notification_arns=None,
|
notification_arns: Optional[List[str]] = None,
|
||||||
tags=None,
|
tags: Optional[Dict[str, str]] = None,
|
||||||
role_arn=None,
|
role_arn: Optional[str] = None,
|
||||||
):
|
):
|
||||||
self.change_set_type = change_set_type
|
self.change_set_type = change_set_type
|
||||||
self.change_set_id = change_set_id
|
self.change_set_id = change_set_id
|
||||||
@ -435,7 +438,11 @@ class FakeChangeSet(BaseModel):
|
|||||||
self.creation_time = datetime.utcnow()
|
self.creation_time = datetime.utcnow()
|
||||||
self.changes = self.diff()
|
self.changes = self.diff()
|
||||||
|
|
||||||
def _parse_template(self):
|
self.status: Optional[str] = None
|
||||||
|
self.execution_status: Optional[str] = None
|
||||||
|
self.status_reason: Optional[str] = None
|
||||||
|
|
||||||
|
def _parse_template(self) -> None:
|
||||||
yaml.add_multi_constructor("", yaml_tag_constructor)
|
yaml.add_multi_constructor("", yaml_tag_constructor)
|
||||||
try:
|
try:
|
||||||
self.template_dict = yaml.load(self.template, Loader=yaml.Loader)
|
self.template_dict = yaml.load(self.template, Loader=yaml.Loader)
|
||||||
@ -443,13 +450,13 @@ class FakeChangeSet(BaseModel):
|
|||||||
self.template_dict = json.loads(self.template)
|
self.template_dict = json.loads(self.template)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def creation_time_iso_8601(self):
|
def creation_time_iso_8601(self) -> str:
|
||||||
return iso_8601_datetime_without_milliseconds(self.creation_time)
|
return iso_8601_datetime_without_milliseconds(self.creation_time) # type: ignore[return-value]
|
||||||
|
|
||||||
def diff(self):
|
def diff(self) -> List[FakeChange]:
|
||||||
changes = []
|
changes = []
|
||||||
resources_by_action = self.stack.resource_map.build_change_set_actions(
|
resources_by_action = self.stack.resource_map.build_change_set_actions(
|
||||||
self.template_dict, self.parameters
|
self.template_dict
|
||||||
)
|
)
|
||||||
for action, resources in resources_by_action.items():
|
for action, resources in resources_by_action.items():
|
||||||
for resource_name, resource in resources.items():
|
for resource_name, resource in resources.items():
|
||||||
@ -462,22 +469,21 @@ class FakeChangeSet(BaseModel):
|
|||||||
)
|
)
|
||||||
return changes
|
return changes
|
||||||
|
|
||||||
def apply(self):
|
def apply(self) -> None:
|
||||||
self.stack.resource_map.update(self.template_dict, self.parameters)
|
self.stack.resource_map.update(self.template_dict, self.parameters)
|
||||||
|
|
||||||
|
|
||||||
class FakeEvent(BaseModel):
|
class FakeEvent(BaseModel):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
stack_id,
|
stack_id: str,
|
||||||
stack_name,
|
stack_name: str,
|
||||||
logical_resource_id,
|
logical_resource_id: str,
|
||||||
physical_resource_id,
|
physical_resource_id: str,
|
||||||
resource_type,
|
resource_type: str,
|
||||||
resource_status,
|
resource_status: str,
|
||||||
resource_status_reason=None,
|
resource_status_reason: Optional[str],
|
||||||
resource_properties=None,
|
resource_properties: Optional[str],
|
||||||
client_request_token=None,
|
|
||||||
):
|
):
|
||||||
self.stack_id = stack_id
|
self.stack_id = stack_id
|
||||||
self.stack_name = stack_name
|
self.stack_name = stack_name
|
||||||
@ -489,9 +495,11 @@ class FakeEvent(BaseModel):
|
|||||||
self.resource_properties = resource_properties
|
self.resource_properties = resource_properties
|
||||||
self.timestamp = datetime.utcnow()
|
self.timestamp = datetime.utcnow()
|
||||||
self.event_id = mock_random.uuid4()
|
self.event_id = mock_random.uuid4()
|
||||||
self.client_request_token = client_request_token
|
self.client_request_token = None
|
||||||
|
|
||||||
def sendToSns(self, account_id, region, sns_topic_arns):
|
def sendToSns(
|
||||||
|
self, account_id: str, region: str, sns_topic_arns: List[str]
|
||||||
|
) -> None:
|
||||||
message = """StackId='{stack_id}'
|
message = """StackId='{stack_id}'
|
||||||
Timestamp='{timestamp}'
|
Timestamp='{timestamp}'
|
||||||
EventId='{event_id}'
|
EventId='{event_id}'
|
||||||
@ -522,7 +530,9 @@ ClientRequestToken='{client_request_token}'""".format(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def filter_stacks(all_stacks, status_filter):
|
def filter_stacks(
|
||||||
|
all_stacks: List[FakeStack], status_filter: Optional[List[str]]
|
||||||
|
) -> List[FakeStack]:
|
||||||
filtered_stacks = []
|
filtered_stacks = []
|
||||||
if not status_filter:
|
if not status_filter:
|
||||||
return all_stacks
|
return all_stacks
|
||||||
@ -539,22 +549,28 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
This means it has to run inside a Docker-container, or be started using `moto_server -h 0.0.0.0`.
|
This means it has to run inside a Docker-container, or be started using `moto_server -h 0.0.0.0`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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.stacks = OrderedDict()
|
self.stacks: Dict[str, FakeStack] = OrderedDict()
|
||||||
self.stacksets = OrderedDict()
|
self.stacksets: Dict[str, FakeStackSet] = OrderedDict()
|
||||||
self.deleted_stacks = {}
|
self.deleted_stacks: Dict[str, FakeStack] = {}
|
||||||
self.exports = OrderedDict()
|
self.exports: Dict[str, Export] = OrderedDict()
|
||||||
self.change_sets = OrderedDict()
|
self.change_sets: Dict[str, FakeChangeSet] = OrderedDict()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default_vpc_endpoint_service(service_region, zones):
|
def default_vpc_endpoint_service(
|
||||||
|
service_region: str, zones: List[str]
|
||||||
|
) -> List[Dict[str, str]]:
|
||||||
"""Default VPC endpoint service."""
|
"""Default VPC endpoint service."""
|
||||||
return BaseBackend.default_vpc_endpoint_service_factory(
|
return BaseBackend.default_vpc_endpoint_service_factory(
|
||||||
service_region, zones, "cloudformation", policy_supported=False
|
service_region, zones, "cloudformation", policy_supported=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def _resolve_update_parameters(self, instance, incoming_params):
|
def _resolve_update_parameters(
|
||||||
|
self,
|
||||||
|
instance: Union[FakeStack, FakeStackSet],
|
||||||
|
incoming_params: List[Dict[str, str]],
|
||||||
|
) -> Dict[str, str]:
|
||||||
parameters = dict(
|
parameters = dict(
|
||||||
[
|
[
|
||||||
(parameter["parameter_key"], parameter["parameter_value"])
|
(parameter["parameter_key"], parameter["parameter_value"])
|
||||||
@ -578,30 +594,26 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
|
|
||||||
def create_stack_set(
|
def create_stack_set(
|
||||||
self,
|
self,
|
||||||
name,
|
name: str,
|
||||||
template,
|
template: str,
|
||||||
parameters,
|
parameters: Dict[str, str],
|
||||||
tags=None,
|
tags: Dict[str, str],
|
||||||
description=None,
|
) -> FakeStackSet:
|
||||||
admin_role=None,
|
|
||||||
execution_role=None,
|
|
||||||
):
|
|
||||||
stackset_id = generate_stackset_id(name)
|
stackset_id = generate_stackset_id(name)
|
||||||
new_stackset = FakeStackSet(
|
new_stackset = FakeStackSet(
|
||||||
stackset_id=stackset_id,
|
stackset_id=stackset_id,
|
||||||
account_id=self.account_id,
|
account_id=self.account_id,
|
||||||
name=name,
|
name=name,
|
||||||
|
region=self.region_name,
|
||||||
template=template,
|
template=template,
|
||||||
parameters=parameters,
|
parameters=parameters,
|
||||||
description=description,
|
description=None,
|
||||||
tags=tags,
|
tags=tags,
|
||||||
admin_role=admin_role,
|
|
||||||
execution_role=execution_role,
|
|
||||||
)
|
)
|
||||||
self.stacksets[stackset_id] = new_stackset
|
self.stacksets[stackset_id] = new_stackset
|
||||||
return new_stackset
|
return new_stackset
|
||||||
|
|
||||||
def get_stack_set(self, name):
|
def get_stack_set(self, name: str) -> FakeStackSet:
|
||||||
stacksets = self.stacksets.keys()
|
stacksets = self.stacksets.keys()
|
||||||
if name in stacksets:
|
if name in stacksets:
|
||||||
return self.stacksets[name]
|
return self.stacksets[name]
|
||||||
@ -610,7 +622,7 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
return self.stacksets[stackset]
|
return self.stacksets[stackset]
|
||||||
raise ValidationError(name)
|
raise ValidationError(name)
|
||||||
|
|
||||||
def delete_stack_set(self, name):
|
def delete_stack_set(self, name: str) -> None:
|
||||||
stacksets = self.stacksets.keys()
|
stacksets = self.stacksets.keys()
|
||||||
if name in stacksets:
|
if name in stacksets:
|
||||||
self.stacksets[name].delete()
|
self.stacksets[name].delete()
|
||||||
@ -619,31 +631,44 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
self.stacksets[stackset].delete()
|
self.stacksets[stackset].delete()
|
||||||
|
|
||||||
def create_stack_instances(
|
def create_stack_instances(
|
||||||
self, stackset_name, accounts, regions, parameters, operation_id=None
|
self,
|
||||||
):
|
stackset_name: str,
|
||||||
|
accounts: List[str],
|
||||||
|
regions: List[str],
|
||||||
|
parameters: List[Dict[str, str]],
|
||||||
|
) -> FakeStackSet:
|
||||||
stackset = self.get_stack_set(stackset_name)
|
stackset = self.get_stack_set(stackset_name)
|
||||||
|
|
||||||
stackset.create_stack_instances(
|
stackset.create_stack_instances(
|
||||||
accounts=accounts,
|
accounts=accounts,
|
||||||
regions=regions,
|
regions=regions,
|
||||||
parameters=parameters,
|
parameters=parameters,
|
||||||
operation_id=operation_id,
|
|
||||||
)
|
)
|
||||||
return stackset
|
return stackset
|
||||||
|
|
||||||
|
def update_stack_instances(
|
||||||
|
self,
|
||||||
|
stackset_name: str,
|
||||||
|
accounts: List[str],
|
||||||
|
regions: List[str],
|
||||||
|
parameters: Dict[str, str],
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
stack_set = self.get_stack_set(stackset_name)
|
||||||
|
return stack_set.update_instances(accounts, regions, parameters)
|
||||||
|
|
||||||
def update_stack_set(
|
def update_stack_set(
|
||||||
self,
|
self,
|
||||||
stackset_name,
|
stackset_name: str,
|
||||||
template=None,
|
template: str,
|
||||||
description=None,
|
description: str,
|
||||||
parameters=None,
|
parameters: List[Dict[str, str]],
|
||||||
tags=None,
|
tags: Dict[str, str],
|
||||||
admin_role=None,
|
admin_role: str,
|
||||||
execution_role=None,
|
execution_role: str,
|
||||||
accounts=None,
|
accounts: List[str],
|
||||||
regions=None,
|
regions: List[str],
|
||||||
operation_id=None,
|
operation_id: str,
|
||||||
):
|
) -> Dict[str, Any]:
|
||||||
stackset = self.get_stack_set(stackset_name)
|
stackset = self.get_stack_set(stackset_name)
|
||||||
resolved_parameters = self._resolve_update_parameters(
|
resolved_parameters = self._resolve_update_parameters(
|
||||||
instance=stackset, incoming_params=parameters
|
instance=stackset, incoming_params=parameters
|
||||||
@ -662,21 +687,21 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
return update
|
return update
|
||||||
|
|
||||||
def delete_stack_instances(
|
def delete_stack_instances(
|
||||||
self, stackset_name, accounts, regions, operation_id=None
|
self, stackset_name: str, accounts: List[str], regions: List[str]
|
||||||
):
|
) -> FakeStackSet:
|
||||||
stackset = self.get_stack_set(stackset_name)
|
stackset = self.get_stack_set(stackset_name)
|
||||||
stackset.delete_stack_instances(accounts, regions, operation_id)
|
stackset.delete_stack_instances(accounts, regions)
|
||||||
return stackset
|
return stackset
|
||||||
|
|
||||||
def create_stack(
|
def create_stack(
|
||||||
self,
|
self,
|
||||||
name,
|
name: str,
|
||||||
template,
|
template: str,
|
||||||
parameters,
|
parameters: Dict[str, Any],
|
||||||
notification_arns=None,
|
notification_arns: Optional[List[str]] = None,
|
||||||
tags=None,
|
tags: Optional[Dict[str, str]] = None,
|
||||||
role_arn=None,
|
role_arn: Optional[str] = None,
|
||||||
):
|
) -> FakeStack:
|
||||||
stack_id = generate_stack_id(name, self.region_name, self.account_id)
|
stack_id = generate_stack_id(name, self.region_name, self.account_id)
|
||||||
new_stack = FakeStack(
|
new_stack = FakeStack(
|
||||||
stack_id=stack_id,
|
stack_id=stack_id,
|
||||||
@ -702,16 +727,16 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
|
|
||||||
def create_change_set(
|
def create_change_set(
|
||||||
self,
|
self,
|
||||||
stack_name,
|
stack_name: str,
|
||||||
change_set_name,
|
change_set_name: str,
|
||||||
template,
|
template: str,
|
||||||
parameters,
|
parameters: Dict[str, str],
|
||||||
description,
|
description: str,
|
||||||
change_set_type,
|
change_set_type: str,
|
||||||
notification_arns=None,
|
notification_arns: Optional[List[str]] = None,
|
||||||
tags=None,
|
tags: Optional[Dict[str, str]] = None,
|
||||||
role_arn=None,
|
role_arn: Optional[str] = None,
|
||||||
):
|
) -> Tuple[str, str]:
|
||||||
if change_set_type == "UPDATE":
|
if change_set_type == "UPDATE":
|
||||||
for stack in self.stacks.values():
|
for stack in self.stacks.values():
|
||||||
if stack.name == stack_name:
|
if stack.name == stack_name:
|
||||||
@ -768,7 +793,7 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
self.change_sets[change_set_id] = new_change_set
|
self.change_sets[change_set_id] = new_change_set
|
||||||
return change_set_id, stack.stack_id
|
return change_set_id, stack.stack_id
|
||||||
|
|
||||||
def delete_change_set(self, change_set_name):
|
def delete_change_set(self, change_set_name: str) -> None:
|
||||||
if change_set_name in self.change_sets:
|
if change_set_name in self.change_sets:
|
||||||
# This means arn was passed in
|
# This means arn was passed in
|
||||||
del self.change_sets[change_set_name]
|
del self.change_sets[change_set_name]
|
||||||
@ -779,7 +804,7 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
break
|
break
|
||||||
del self.change_sets[to_delete]
|
del self.change_sets[to_delete]
|
||||||
|
|
||||||
def describe_change_set(self, change_set_name):
|
def describe_change_set(self, change_set_name: str) -> Optional[FakeChangeSet]:
|
||||||
change_set = None
|
change_set = None
|
||||||
if change_set_name in self.change_sets:
|
if change_set_name in self.change_sets:
|
||||||
# This means arn was passed in
|
# This means arn was passed in
|
||||||
@ -792,7 +817,9 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
raise ValidationError(change_set_name)
|
raise ValidationError(change_set_name)
|
||||||
return change_set
|
return change_set
|
||||||
|
|
||||||
def execute_change_set(self, change_set_name, stack_name=None):
|
def execute_change_set(
|
||||||
|
self, change_set_name: str, stack_name: Optional[str] = None
|
||||||
|
) -> None:
|
||||||
if change_set_name in self.change_sets:
|
if change_set_name in self.change_sets:
|
||||||
# This means arn was passed in
|
# This means arn was passed in
|
||||||
change_set = self.change_sets[change_set_name]
|
change_set = self.change_sets[change_set_name]
|
||||||
@ -823,9 +850,8 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
# set the status of the stack
|
# set the status of the stack
|
||||||
stack.status = f"{change_set.change_set_type}_COMPLETE"
|
stack.status = f"{change_set.change_set_type}_COMPLETE"
|
||||||
stack.template = change_set.template
|
stack.template = change_set.template
|
||||||
return True
|
|
||||||
|
|
||||||
def describe_stacks(self, name_or_stack_id):
|
def describe_stacks(self, name_or_stack_id: str) -> List[FakeStack]:
|
||||||
stacks = self.stacks.values()
|
stacks = self.stacks.values()
|
||||||
if name_or_stack_id:
|
if name_or_stack_id:
|
||||||
for stack in stacks:
|
for stack in stacks:
|
||||||
@ -840,16 +866,16 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
else:
|
else:
|
||||||
return list(stacks)
|
return list(stacks)
|
||||||
|
|
||||||
def list_change_sets(self):
|
def list_change_sets(self) -> Iterable[FakeChangeSet]:
|
||||||
return self.change_sets.values()
|
return self.change_sets.values()
|
||||||
|
|
||||||
def list_stacks(self, status_filter=None):
|
def list_stacks(self, status_filter: Optional[List[str]] = None) -> List[FakeStack]:
|
||||||
total_stacks = [v for v in self.stacks.values()] + [
|
total_stacks = [v for v in self.stacks.values()] + [
|
||||||
v for v in self.deleted_stacks.values()
|
v for v in self.deleted_stacks.values()
|
||||||
]
|
]
|
||||||
return filter_stacks(total_stacks, status_filter)
|
return filter_stacks(total_stacks, status_filter)
|
||||||
|
|
||||||
def get_stack(self, name_or_stack_id):
|
def get_stack(self, name_or_stack_id: str) -> FakeStack:
|
||||||
all_stacks = dict(self.deleted_stacks, **self.stacks)
|
all_stacks = dict(self.deleted_stacks, **self.stacks)
|
||||||
if name_or_stack_id in all_stacks:
|
if name_or_stack_id in all_stacks:
|
||||||
# Lookup by stack id - deleted stacks incldued
|
# Lookup by stack id - deleted stacks incldued
|
||||||
@ -861,7 +887,14 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
return stack
|
return stack
|
||||||
raise ValidationError(name_or_stack_id)
|
raise ValidationError(name_or_stack_id)
|
||||||
|
|
||||||
def update_stack(self, name, template, role_arn=None, parameters=None, tags=None):
|
def update_stack(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
template: str,
|
||||||
|
role_arn: Optional[str],
|
||||||
|
parameters: List[Dict[str, Any]],
|
||||||
|
tags: Optional[Dict[str, str]],
|
||||||
|
) -> FakeStack:
|
||||||
stack = self.get_stack(name)
|
stack = self.get_stack(name)
|
||||||
resolved_parameters = self._resolve_update_parameters(
|
resolved_parameters = self._resolve_update_parameters(
|
||||||
instance=stack, incoming_params=parameters
|
instance=stack, incoming_params=parameters
|
||||||
@ -869,14 +902,14 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
stack.update(template, role_arn, parameters=resolved_parameters, tags=tags)
|
stack.update(template, role_arn, parameters=resolved_parameters, tags=tags)
|
||||||
return stack
|
return stack
|
||||||
|
|
||||||
def get_stack_policy(self, stack_name):
|
def get_stack_policy(self, stack_name: str) -> str:
|
||||||
try:
|
try:
|
||||||
stack = self.get_stack(stack_name)
|
stack = self.get_stack(stack_name)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
raise ValidationError(message=f"Stack: {stack_name} does not exist")
|
raise ValidationError(message=f"Stack: {stack_name} does not exist")
|
||||||
return stack.policy
|
return stack.policy
|
||||||
|
|
||||||
def set_stack_policy(self, stack_name, policy_body):
|
def set_stack_policy(self, stack_name: str, policy_body: str) -> None:
|
||||||
"""
|
"""
|
||||||
Note that Moto does no validation/parsing/enforcement of this policy - we simply persist it.
|
Note that Moto does no validation/parsing/enforcement of this policy - we simply persist it.
|
||||||
"""
|
"""
|
||||||
@ -886,41 +919,45 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
raise ValidationError(message=f"Stack: {stack_name} does not exist")
|
raise ValidationError(message=f"Stack: {stack_name} does not exist")
|
||||||
stack.policy = policy_body
|
stack.policy = policy_body
|
||||||
|
|
||||||
def list_stack_resources(self, stack_name_or_id):
|
def list_stack_resources(
|
||||||
|
self, stack_name_or_id: str
|
||||||
|
) -> Iterable[Type[CloudFormationModel]]:
|
||||||
stack = self.get_stack(stack_name_or_id)
|
stack = self.get_stack(stack_name_or_id)
|
||||||
return stack.stack_resources
|
return stack.stack_resources
|
||||||
|
|
||||||
def delete_stack(self, name_or_stack_id):
|
def delete_stack(self, name_or_stack_id: str) -> None:
|
||||||
if name_or_stack_id in self.stacks:
|
if name_or_stack_id in self.stacks:
|
||||||
# Delete by stack id
|
# Delete by stack id
|
||||||
stack = self.stacks.pop(name_or_stack_id, None)
|
stack = self.stacks.pop(name_or_stack_id)
|
||||||
export_names = [export.name for export in stack.exports]
|
export_names = [export.name for export in stack.exports]
|
||||||
stack.delete()
|
stack.delete()
|
||||||
self.deleted_stacks[stack.stack_id] = stack
|
self.deleted_stacks[stack.stack_id] = stack
|
||||||
for export_name in export_names:
|
for export_name in export_names:
|
||||||
self.exports.pop(export_name)
|
self.exports.pop(export_name)
|
||||||
return self.stacks.pop(name_or_stack_id, None)
|
self.stacks.pop(name_or_stack_id, None)
|
||||||
else:
|
else:
|
||||||
# Delete by stack name
|
# Delete by stack name
|
||||||
for stack in list(self.stacks.values()):
|
for stack in list(self.stacks.values()):
|
||||||
if stack.name == name_or_stack_id:
|
if stack.name == name_or_stack_id:
|
||||||
self.delete_stack(stack.stack_id)
|
self.delete_stack(stack.stack_id)
|
||||||
|
|
||||||
def list_exports(self, token):
|
def list_exports(
|
||||||
|
self, tokenstr: Optional[str]
|
||||||
|
) -> Tuple[List[Export], Optional[str]]:
|
||||||
all_exports = list(self.exports.values())
|
all_exports = list(self.exports.values())
|
||||||
if token is None:
|
if tokenstr is None:
|
||||||
exports = all_exports[0:100]
|
exports = all_exports[0:100]
|
||||||
next_token = "100" if len(all_exports) > 100 else None
|
next_token = "100" if len(all_exports) > 100 else None
|
||||||
else:
|
else:
|
||||||
token = int(token)
|
token = int(tokenstr)
|
||||||
exports = all_exports[token : token + 100]
|
exports = all_exports[token : token + 100]
|
||||||
next_token = str(token + 100) if len(all_exports) > token + 100 else None
|
next_token = str(token + 100) if len(all_exports) > token + 100 else None
|
||||||
return exports, next_token
|
return exports, next_token
|
||||||
|
|
||||||
def validate_template(self, template):
|
def validate_template(self, template: str) -> List[Any]:
|
||||||
return validate_template_cfn_lint(template)
|
return validate_template_cfn_lint(template)
|
||||||
|
|
||||||
def _validate_export_uniqueness(self, stack):
|
def _validate_export_uniqueness(self, stack: FakeStack) -> None:
|
||||||
new_stack_export_names = [x.name for x in stack.exports]
|
new_stack_export_names = [x.name for x in stack.exports]
|
||||||
export_names = self.exports.keys()
|
export_names = self.exports.keys()
|
||||||
if not set(export_names).isdisjoint(new_stack_export_names):
|
if not set(export_names).isdisjoint(new_stack_export_names):
|
||||||
|
@ -6,6 +6,18 @@ import warnings
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
import collections.abc as collections_abc
|
import collections.abc as collections_abc
|
||||||
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Union,
|
||||||
|
Iterable,
|
||||||
|
Iterator,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
|
TypeVar,
|
||||||
|
Type,
|
||||||
|
)
|
||||||
|
|
||||||
# This ugly section of imports is necessary because we
|
# This ugly section of imports is necessary because we
|
||||||
# build the list of CloudFormationModel subclasses using
|
# build the list of CloudFormationModel subclasses using
|
||||||
@ -14,35 +26,34 @@ import collections.abc as collections_abc
|
|||||||
# the subclass's module hasn't been imported yet - then that subclass
|
# the subclass's module hasn't been imported yet - then that subclass
|
||||||
# doesn't exist yet, and __subclasses__ won't find it.
|
# doesn't exist yet, and __subclasses__ won't find it.
|
||||||
# So we import here to populate the list of subclasses.
|
# So we import here to populate the list of subclasses.
|
||||||
from moto.apigateway import models # noqa # pylint: disable=all
|
from moto.apigateway import models as apigw_models # noqa # pylint: disable=all
|
||||||
from moto.autoscaling import models # noqa # pylint: disable=all
|
from moto.autoscaling import models as as_models # noqa # pylint: disable=all
|
||||||
from moto.awslambda import models # noqa # pylint: disable=all
|
from moto.awslambda import models as lambda_models # noqa # pylint: disable=all
|
||||||
from moto.batch import models # noqa # pylint: disable=all
|
from moto.batch import models as batch_models # noqa # pylint: disable=all
|
||||||
from moto.cloudformation.custom_model import CustomModel
|
from moto.cloudformation.custom_model import CustomModel
|
||||||
from moto.cloudwatch import models # noqa # pylint: disable=all
|
from moto.cloudwatch import models as cw_models # noqa # pylint: disable=all
|
||||||
from moto.datapipeline import models # noqa # pylint: disable=all
|
from moto.datapipeline import models as data_models # noqa # pylint: disable=all
|
||||||
from moto.dynamodb import models # noqa # pylint: disable=all
|
from moto.dynamodb import models as ddb_models # noqa # pylint: disable=all
|
||||||
from moto.ec2 import models as ec2_models
|
from moto.ec2 import models as ec2_models
|
||||||
from moto.ec2.models.core import TaggedEC2Resource
|
from moto.ec2.models.core import TaggedEC2Resource
|
||||||
from moto.ecr import models # noqa # pylint: disable=all
|
from moto.ecr import models as ecr_models # noqa # pylint: disable=all
|
||||||
from moto.ecs import models # noqa # pylint: disable=all
|
from moto.ecs import models as ecs_models # noqa # pylint: disable=all
|
||||||
from moto.efs import models # noqa # pylint: disable=all
|
from moto.efs import models as efs_models # noqa # pylint: disable=all
|
||||||
from moto.elb import models # noqa # pylint: disable=all
|
from moto.elb import models as elb_models # noqa # pylint: disable=all
|
||||||
from moto.elbv2 import models # noqa # pylint: disable=all
|
from moto.elbv2 import models as elbv2_models # noqa # pylint: disable=all
|
||||||
from moto.events import models # noqa # pylint: disable=all
|
from moto.events import models as events_models # noqa # pylint: disable=all
|
||||||
from moto.iam import models # noqa # pylint: disable=all
|
from moto.iam import models as iam_models # noqa # pylint: disable=all
|
||||||
from moto.kinesis import models # noqa # pylint: disable=all
|
from moto.kinesis import models as kinesis_models # noqa # pylint: disable=all
|
||||||
from moto.kms import models # noqa # pylint: disable=all
|
from moto.kms import models as kms_models # noqa # pylint: disable=all
|
||||||
from moto.rds import models # noqa # pylint: disable=all
|
from moto.rds import models as rds_models # noqa # pylint: disable=all
|
||||||
from moto.rds import models # noqa # pylint: disable=all
|
from moto.redshift import models as redshift_models # noqa # pylint: disable=all
|
||||||
from moto.redshift import models # noqa # pylint: disable=all
|
from moto.route53 import models as route53_models # noqa # pylint: disable=all
|
||||||
from moto.route53 import models # noqa # pylint: disable=all
|
from moto.s3 import models as s3_models # noqa # pylint: disable=all
|
||||||
from moto.s3 import models # noqa # pylint: disable=all
|
from moto.sagemaker import models as sagemaker_models # noqa # pylint: disable=all
|
||||||
from moto.sagemaker import models # noqa # pylint: disable=all
|
from moto.sns import models as sns_models # noqa # pylint: disable=all
|
||||||
from moto.sns import models # noqa # pylint: disable=all
|
from moto.sqs import models as sqs_models # noqa # pylint: disable=all
|
||||||
from moto.sqs import models # noqa # pylint: disable=all
|
from moto.stepfunctions import models as sfn_models # noqa # pylint: disable=all
|
||||||
from moto.stepfunctions import models # noqa # pylint: disable=all
|
from moto.ssm import models as ssm_models # noqa # pylint: disable=all
|
||||||
from moto.ssm import models # noqa # pylint: disable=all
|
|
||||||
|
|
||||||
# End ugly list of imports
|
# End ugly list of imports
|
||||||
|
|
||||||
@ -66,6 +77,7 @@ NAME_TYPE_MAP = {
|
|||||||
model.cloudformation_type(): model.cloudformation_name_type()
|
model.cloudformation_type(): model.cloudformation_name_type()
|
||||||
for model in MODEL_LIST
|
for model in MODEL_LIST
|
||||||
}
|
}
|
||||||
|
CF_MODEL = TypeVar("CF_MODEL", bound=CloudFormationModel)
|
||||||
|
|
||||||
# Just ignore these models types for now
|
# Just ignore these models types for now
|
||||||
NULL_MODELS = [
|
NULL_MODELS = [
|
||||||
@ -79,18 +91,17 @@ logger = logging.getLogger("moto")
|
|||||||
|
|
||||||
|
|
||||||
class Output(object):
|
class Output(object):
|
||||||
def __init__(self, connection=None):
|
def __init__(self, key: str, value: str, description: str):
|
||||||
self.connection = connection
|
self.description = description
|
||||||
self.description = None
|
self.key = key
|
||||||
self.key = None
|
self.value = value
|
||||||
self.value = None
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return 'Output:"%s"="%s"' % (self.key, self.value)
|
return 'Output:"%s"="%s"' % (self.key, self.value)
|
||||||
|
|
||||||
|
|
||||||
class LazyDict(dict):
|
class LazyDict(Dict[str, Any]):
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key: str) -> Any:
|
||||||
val = dict.__getitem__(self, key)
|
val = dict.__getitem__(self, key)
|
||||||
if callable(val):
|
if callable(val):
|
||||||
val = val()
|
val = val()
|
||||||
@ -98,7 +109,7 @@ class LazyDict(dict):
|
|||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
def clean_json(resource_json, resources_map):
|
def clean_json(resource_json: Any, resources_map: "ResourceMap") -> Any:
|
||||||
"""
|
"""
|
||||||
Cleanup the a resource dict. For now, this just means replacing any Ref node
|
Cleanup the a resource dict. For now, this just means replacing any Ref node
|
||||||
with the corresponding physical_resource_id.
|
with the corresponding physical_resource_id.
|
||||||
@ -110,7 +121,7 @@ def clean_json(resource_json, resources_map):
|
|||||||
# Parse resource reference
|
# Parse resource reference
|
||||||
resource = resources_map[resource_json["Ref"]]
|
resource = resources_map[resource_json["Ref"]]
|
||||||
if hasattr(resource, "physical_resource_id"):
|
if hasattr(resource, "physical_resource_id"):
|
||||||
return resource.physical_resource_id
|
return resource.physical_resource_id # type: ignore[attr-defined]
|
||||||
else:
|
else:
|
||||||
return resource
|
return resource
|
||||||
|
|
||||||
@ -119,10 +130,10 @@ def clean_json(resource_json, resources_map):
|
|||||||
map_path = resource_json["Fn::FindInMap"][1:]
|
map_path = resource_json["Fn::FindInMap"][1:]
|
||||||
result = resources_map[map_name]
|
result = resources_map[map_name]
|
||||||
for path in map_path:
|
for path in map_path:
|
||||||
if "Fn::Transform" in result:
|
if "Fn::Transform" in result: # type: ignore[operator]
|
||||||
result = resources_map[clean_json(path, resources_map)]
|
result = resources_map[clean_json(path, resources_map)]
|
||||||
else:
|
else:
|
||||||
result = result[clean_json(path, resources_map)]
|
result = result[clean_json(path, resources_map)] # type: ignore[index]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
if "Fn::GetAtt" in resource_json:
|
if "Fn::GetAtt" in resource_json:
|
||||||
@ -201,7 +212,7 @@ def clean_json(resource_json, resources_map):
|
|||||||
cleaned_val = clean_json(resource_json["Fn::ImportValue"], resources_map)
|
cleaned_val = clean_json(resource_json["Fn::ImportValue"], resources_map)
|
||||||
values = [
|
values = [
|
||||||
x.value
|
x.value
|
||||||
for x in resources_map.cross_stack_resources.values()
|
for x in resources_map.cross_stack_resources.values() # type: ignore[union-attr]
|
||||||
if x.name == cleaned_val
|
if x.name == cleaned_val
|
||||||
]
|
]
|
||||||
if any(values):
|
if any(values):
|
||||||
@ -231,26 +242,26 @@ def clean_json(resource_json, resources_map):
|
|||||||
return resource_json
|
return resource_json
|
||||||
|
|
||||||
|
|
||||||
def resource_class_from_type(resource_type):
|
def resource_class_from_type(resource_type: str) -> Type[CloudFormationModel]:
|
||||||
if resource_type in NULL_MODELS:
|
if resource_type in NULL_MODELS:
|
||||||
return None
|
return None # type: ignore[return-value]
|
||||||
if resource_type.startswith("Custom::"):
|
if resource_type.startswith("Custom::"):
|
||||||
return CustomModel
|
return CustomModel
|
||||||
if resource_type not in MODEL_MAP:
|
if resource_type not in MODEL_MAP:
|
||||||
logger.warning("No Moto CloudFormation support for %s", resource_type)
|
logger.warning("No Moto CloudFormation support for %s", resource_type)
|
||||||
return None
|
return None # type: ignore[return-value]
|
||||||
|
|
||||||
return MODEL_MAP.get(resource_type)
|
return MODEL_MAP.get(resource_type) # type: ignore[return-value]
|
||||||
|
|
||||||
|
|
||||||
def resource_name_property_from_type(resource_type):
|
def resource_name_property_from_type(resource_type: str) -> Optional[str]:
|
||||||
for model in MODEL_LIST:
|
for model in MODEL_LIST:
|
||||||
if model.cloudformation_type() == resource_type:
|
if model.cloudformation_type() == resource_type:
|
||||||
return model.cloudformation_name_type()
|
return model.cloudformation_name_type()
|
||||||
return NAME_TYPE_MAP.get(resource_type)
|
return NAME_TYPE_MAP.get(resource_type)
|
||||||
|
|
||||||
|
|
||||||
def generate_resource_name(resource_type, stack_name, logical_id):
|
def generate_resource_name(resource_type: str, stack_name: str, logical_id: str) -> str:
|
||||||
if resource_type in [
|
if resource_type in [
|
||||||
"AWS::ElasticLoadBalancingV2::TargetGroup",
|
"AWS::ElasticLoadBalancingV2::TargetGroup",
|
||||||
"AWS::ElasticLoadBalancingV2::LoadBalancer",
|
"AWS::ElasticLoadBalancingV2::LoadBalancer",
|
||||||
@ -277,7 +288,9 @@ def generate_resource_name(resource_type, stack_name, logical_id):
|
|||||||
return "{0}-{1}-{2}".format(stack_name, logical_id, random_suffix())
|
return "{0}-{1}-{2}".format(stack_name, logical_id, random_suffix())
|
||||||
|
|
||||||
|
|
||||||
def parse_resource(resource_json, resources_map):
|
def parse_resource(
|
||||||
|
resource_json: Dict[str, Any], resources_map: "ResourceMap"
|
||||||
|
) -> Tuple[Type[CloudFormationModel], Any, str]:
|
||||||
resource_type = resource_json["Type"]
|
resource_type = resource_json["Type"]
|
||||||
resource_class = resource_class_from_type(resource_type)
|
resource_class = resource_class_from_type(resource_type)
|
||||||
if not resource_class:
|
if not resource_class:
|
||||||
@ -286,7 +299,7 @@ def parse_resource(resource_json, resources_map):
|
|||||||
resource_type
|
resource_type
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return None
|
return None # type: ignore[return-value]
|
||||||
|
|
||||||
if "Properties" not in resource_json:
|
if "Properties" not in resource_json:
|
||||||
resource_json["Properties"] = {}
|
resource_json["Properties"] = {}
|
||||||
@ -296,14 +309,18 @@ def parse_resource(resource_json, resources_map):
|
|||||||
return resource_class, resource_json, resource_type
|
return resource_class, resource_json, resource_type
|
||||||
|
|
||||||
|
|
||||||
def parse_resource_and_generate_name(logical_id, resource_json, resources_map):
|
def parse_resource_and_generate_name(
|
||||||
resource_tuple = parse_resource(resource_json, resources_map)
|
logical_id: str, resource_json: Dict[str, Any], resources_map: "ResourceMap"
|
||||||
|
) -> Tuple[Type[CloudFormationModel], Dict[str, Any], str]:
|
||||||
|
resource_tuple: Tuple[
|
||||||
|
Type[CloudFormationModel], Dict[str, Any], str
|
||||||
|
] = parse_resource(resource_json, resources_map)
|
||||||
if not resource_tuple:
|
if not resource_tuple:
|
||||||
return None
|
return None
|
||||||
resource_class, resource_json, resource_type = resource_tuple
|
resource_class, resource_json, resource_type = resource_tuple
|
||||||
|
|
||||||
generated_resource_name = generate_resource_name(
|
generated_resource_name = generate_resource_name(
|
||||||
resource_type, resources_map.get("AWS::StackName"), logical_id
|
resource_type, resources_map["AWS::StackName"], logical_id # type: ignore[arg-type]
|
||||||
)
|
)
|
||||||
|
|
||||||
resource_name_property = resource_name_property_from_type(resource_type)
|
resource_name_property = resource_name_property_from_type(resource_type)
|
||||||
@ -322,17 +339,21 @@ def parse_resource_and_generate_name(logical_id, resource_json, resources_map):
|
|||||||
|
|
||||||
|
|
||||||
def parse_and_create_resource(
|
def parse_and_create_resource(
|
||||||
logical_id, resource_json, resources_map, account_id, region_name
|
logical_id: str,
|
||||||
):
|
resource_json: Dict[str, Any],
|
||||||
|
resources_map: "ResourceMap",
|
||||||
|
account_id: str,
|
||||||
|
region_name: str,
|
||||||
|
) -> Optional[CF_MODEL]:
|
||||||
condition = resource_json.get("Condition")
|
condition = resource_json.get("Condition")
|
||||||
if condition and not resources_map.lazy_condition_map[condition]:
|
if condition and not resources_map.lazy_condition_map[condition]:
|
||||||
# If this has a False condition, don't create the resource
|
# If this has a False condition, don't create the resource
|
||||||
return None
|
return None
|
||||||
|
|
||||||
resource_type = resource_json["Type"]
|
resource_type = resource_json["Type"]
|
||||||
resource_tuple = parse_resource_and_generate_name(
|
resource_tuple: Tuple[
|
||||||
logical_id, resource_json, resources_map
|
Type[CloudFormationModel], Dict[str, Any], str
|
||||||
)
|
] = parse_resource_and_generate_name(logical_id, resource_json, resources_map)
|
||||||
if not resource_tuple:
|
if not resource_tuple:
|
||||||
return None
|
return None
|
||||||
resource_class, resource_json, resource_physical_name = resource_tuple
|
resource_class, resource_json, resource_physical_name = resource_tuple
|
||||||
@ -350,11 +371,15 @@ def parse_and_create_resource(
|
|||||||
|
|
||||||
|
|
||||||
def parse_and_update_resource(
|
def parse_and_update_resource(
|
||||||
logical_id, resource_json, resources_map, account_id, region_name
|
logical_id: str,
|
||||||
):
|
resource_json: Dict[str, Any],
|
||||||
resource_tuple = parse_resource_and_generate_name(
|
resources_map: "ResourceMap",
|
||||||
logical_id, resource_json, resources_map
|
account_id: str,
|
||||||
)
|
region_name: str,
|
||||||
|
) -> Optional[CF_MODEL]:
|
||||||
|
resource_tuple: Optional[
|
||||||
|
Tuple[Type[CloudFormationModel], Dict[str, Any], str]
|
||||||
|
] = parse_resource_and_generate_name(logical_id, resource_json, resources_map)
|
||||||
if not resource_tuple:
|
if not resource_tuple:
|
||||||
return None
|
return None
|
||||||
resource_class, resource_json, new_resource_name = resource_tuple
|
resource_class, resource_json, new_resource_name = resource_tuple
|
||||||
@ -376,7 +401,9 @@ def parse_and_update_resource(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def parse_and_delete_resource(resource_name, resource_json, account_id, region_name):
|
def parse_and_delete_resource(
|
||||||
|
resource_name: str, resource_json: Dict[str, Any], account_id: str, region_name: str
|
||||||
|
) -> None:
|
||||||
resource_type = resource_json["Type"]
|
resource_type = resource_json["Type"]
|
||||||
resource_class = resource_class_from_type(resource_type)
|
resource_class = resource_class_from_type(resource_type)
|
||||||
if not hasattr(
|
if not hasattr(
|
||||||
@ -387,7 +414,7 @@ def parse_and_delete_resource(resource_name, resource_json, account_id, region_n
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_condition(condition, resources_map, condition_map):
|
def parse_condition(condition: Union[Dict[str, Any], bool], resources_map: "ResourceMap", condition_map: Dict[str, Any]) -> bool: # type: ignore[return]
|
||||||
if isinstance(condition, bool):
|
if isinstance(condition, bool):
|
||||||
return condition
|
return condition
|
||||||
|
|
||||||
@ -423,18 +450,21 @@ def parse_condition(condition, resources_map, condition_map):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_output(output_logical_id, output_json, resources_map):
|
def parse_output(
|
||||||
|
output_logical_id: str, output_json: Any, resources_map: "ResourceMap"
|
||||||
|
) -> Optional[Output]:
|
||||||
output_json = clean_json(output_json, resources_map)
|
output_json = clean_json(output_json, resources_map)
|
||||||
if "Value" not in output_json:
|
if "Value" not in output_json:
|
||||||
return None
|
return None
|
||||||
output = Output()
|
output = Output(
|
||||||
output.key = output_logical_id
|
key=output_logical_id,
|
||||||
output.value = clean_json(output_json["Value"], resources_map)
|
value=clean_json(output_json["Value"], resources_map),
|
||||||
output.description = output_json.get("Description")
|
description=output_json.get("Description"),
|
||||||
|
)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
class ResourceMap(collections_abc.Mapping):
|
class ResourceMap(collections_abc.Mapping): # type: ignore[type-arg]
|
||||||
"""
|
"""
|
||||||
This is a lazy loading map for resources. This allows us to create resources
|
This is a lazy loading map for resources. This allows us to create resources
|
||||||
without needing to create a full dependency tree. Upon creation, each
|
without needing to create a full dependency tree. Upon creation, each
|
||||||
@ -443,27 +473,29 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
stack_id,
|
stack_id: str,
|
||||||
stack_name,
|
stack_name: str,
|
||||||
parameters,
|
parameters: Dict[str, Any],
|
||||||
tags,
|
tags: Dict[str, Any],
|
||||||
region_name,
|
region_name: str,
|
||||||
account_id,
|
account_id: str,
|
||||||
template,
|
template: Dict[str, Any],
|
||||||
cross_stack_resources,
|
cross_stack_resources: Optional[Dict[str, "Export"]],
|
||||||
):
|
):
|
||||||
self._template = template
|
self._template = template
|
||||||
self._resource_json_map = template["Resources"] if template != {} else {}
|
self._resource_json_map: Dict[str, Any] = (
|
||||||
|
template["Resources"] if template != {} else {}
|
||||||
|
)
|
||||||
self._account_id = account_id
|
self._account_id = account_id
|
||||||
self._region_name = region_name
|
self._region_name = region_name
|
||||||
self.input_parameters = parameters
|
self.input_parameters = parameters
|
||||||
self.tags = copy.deepcopy(tags)
|
self.tags = copy.deepcopy(tags)
|
||||||
self.resolved_parameters = {}
|
self.resolved_parameters: Dict[str, Any] = {}
|
||||||
self.cross_stack_resources = cross_stack_resources
|
self.cross_stack_resources = cross_stack_resources
|
||||||
self.stack_id = stack_id
|
self.stack_id = stack_id
|
||||||
|
|
||||||
# Create the default resources
|
# Create the default resources
|
||||||
self._parsed_resources = {
|
self._parsed_resources: Dict[str, Any] = {
|
||||||
"AWS::AccountId": account_id,
|
"AWS::AccountId": account_id,
|
||||||
"AWS::Region": self._region_name,
|
"AWS::Region": self._region_name,
|
||||||
"AWS::StackId": stack_id,
|
"AWS::StackId": stack_id,
|
||||||
@ -473,7 +505,7 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
"AWS::Partition": "aws",
|
"AWS::Partition": "aws",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key: str) -> Optional[CF_MODEL]:
|
||||||
resource_logical_id = key
|
resource_logical_id = key
|
||||||
|
|
||||||
if resource_logical_id in self._parsed_resources:
|
if resource_logical_id in self._parsed_resources:
|
||||||
@ -494,17 +526,17 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
self._parsed_resources[resource_logical_id] = new_resource
|
self._parsed_resources[resource_logical_id] = new_resource
|
||||||
return new_resource
|
return new_resource
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self) -> Iterator[str]:
|
||||||
return iter(self.resources)
|
return iter(self.resources)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self) -> int:
|
||||||
return len(self._resource_json_map)
|
return len(self._resource_json_map)
|
||||||
|
|
||||||
def __get_resources_in_dependency_order(self):
|
def __get_resources_in_dependency_order(self) -> List[str]:
|
||||||
resource_map = copy.deepcopy(self._resource_json_map)
|
resource_map = copy.deepcopy(self._resource_json_map)
|
||||||
resources_in_dependency_order = []
|
resources_in_dependency_order = []
|
||||||
|
|
||||||
def recursively_get_dependencies(resource):
|
def recursively_get_dependencies(resource: str) -> None:
|
||||||
resource_info = resource_map[resource]
|
resource_info = resource_map[resource]
|
||||||
|
|
||||||
if "DependsOn" not in resource_info:
|
if "DependsOn" not in resource_info:
|
||||||
@ -529,13 +561,13 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
return resources_in_dependency_order
|
return resources_in_dependency_order
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def resources(self):
|
def resources(self) -> Iterable[str]:
|
||||||
return self._resource_json_map.keys()
|
return self._resource_json_map.keys()
|
||||||
|
|
||||||
def load_mapping(self):
|
def load_mapping(self) -> None:
|
||||||
self._parsed_resources.update(self._template.get("Mappings", {}))
|
self._parsed_resources.update(self._template.get("Mappings", {}))
|
||||||
|
|
||||||
def transform_mapping(self):
|
def transform_mapping(self) -> None:
|
||||||
for v in self._template.get("Mappings", {}).values():
|
for v in self._template.get("Mappings", {}).values():
|
||||||
if "Fn::Transform" in v:
|
if "Fn::Transform" in v:
|
||||||
name = v["Fn::Transform"]["Name"]
|
name = v["Fn::Transform"]["Name"]
|
||||||
@ -548,7 +580,7 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
)
|
)
|
||||||
self._parsed_resources.update(json.loads(key.value))
|
self._parsed_resources.update(json.loads(key.value))
|
||||||
|
|
||||||
def parse_ssm_parameter(self, value, value_type):
|
def parse_ssm_parameter(self, value: str, value_type: str) -> str:
|
||||||
# The Value in SSM parameters is the SSM parameter path
|
# The Value in SSM parameters is the SSM parameter path
|
||||||
# we need to use ssm_backend to retrieve the
|
# we need to use ssm_backend to retrieve the
|
||||||
# actual value from parameter store
|
# actual value from parameter store
|
||||||
@ -560,7 +592,7 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
return actual_value.split(",")
|
return actual_value.split(",")
|
||||||
return actual_value
|
return actual_value
|
||||||
|
|
||||||
def load_parameters(self):
|
def load_parameters(self) -> None:
|
||||||
parameter_slots = self._template.get("Parameters", {})
|
parameter_slots = self._template.get("Parameters", {})
|
||||||
for parameter_name, parameter in parameter_slots.items():
|
for parameter_name, parameter in parameter_slots.items():
|
||||||
# Set the default values.
|
# Set the default values.
|
||||||
@ -582,7 +614,7 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
if value_type == "CommaDelimitedList" or value_type.startswith("List"):
|
if value_type == "CommaDelimitedList" or value_type.startswith("List"):
|
||||||
value = value.split(",")
|
value = value.split(",")
|
||||||
|
|
||||||
def _parse_number_parameter(num_string):
|
def _parse_number_parameter(num_string: str) -> Union[int, float]:
|
||||||
"""CloudFormation NUMBER types can be an int or float.
|
"""CloudFormation NUMBER types can be an int or float.
|
||||||
Try int first and then fall back to float if that fails
|
Try int first and then fall back to float if that fails
|
||||||
"""
|
"""
|
||||||
@ -612,7 +644,7 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
|
|
||||||
self._parsed_resources.update(self.resolved_parameters)
|
self._parsed_resources.update(self.resolved_parameters)
|
||||||
|
|
||||||
def load_conditions(self):
|
def load_conditions(self) -> None:
|
||||||
conditions = self._template.get("Conditions", {})
|
conditions = self._template.get("Conditions", {})
|
||||||
self.lazy_condition_map = LazyDict()
|
self.lazy_condition_map = LazyDict()
|
||||||
for condition_name, condition in conditions.items():
|
for condition_name, condition in conditions.items():
|
||||||
@ -626,12 +658,12 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
for condition_name in self.lazy_condition_map:
|
for condition_name in self.lazy_condition_map:
|
||||||
self.lazy_condition_map[condition_name]
|
self.lazy_condition_map[condition_name]
|
||||||
|
|
||||||
def validate_outputs(self):
|
def validate_outputs(self) -> None:
|
||||||
outputs = self._template.get("Outputs") or {}
|
outputs = self._template.get("Outputs") or {}
|
||||||
for value in outputs.values():
|
for value in outputs.values():
|
||||||
value = value.get("Value", {})
|
value = value.get("Value", {})
|
||||||
if "Fn::GetAtt" in value:
|
if "Fn::GetAtt" in value:
|
||||||
resource_type = self._resource_json_map.get(value["Fn::GetAtt"][0])[
|
resource_type = self._resource_json_map.get(value["Fn::GetAtt"][0])[ # type: ignore[index]
|
||||||
"Type"
|
"Type"
|
||||||
]
|
]
|
||||||
attr = value["Fn::GetAtt"][1]
|
attr = value["Fn::GetAtt"][1]
|
||||||
@ -641,14 +673,14 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
short_type = resource_type[resource_type.rindex(":") + 1 :]
|
short_type = resource_type[resource_type.rindex(":") + 1 :]
|
||||||
raise UnsupportedAttribute(resource=short_type, attr=attr)
|
raise UnsupportedAttribute(resource=short_type, attr=attr)
|
||||||
|
|
||||||
def load(self):
|
def load(self) -> None:
|
||||||
self.load_mapping()
|
self.load_mapping()
|
||||||
self.transform_mapping()
|
self.transform_mapping()
|
||||||
self.load_parameters()
|
self.load_parameters()
|
||||||
self.load_conditions()
|
self.load_conditions()
|
||||||
self.validate_outputs()
|
self.validate_outputs()
|
||||||
|
|
||||||
def create(self, template):
|
def create(self, template: Dict[str, Any]) -> bool:
|
||||||
# Since this is a lazy map, to create every object we just need to
|
# Since this is a lazy map, to create every object we just need to
|
||||||
# iterate through self.
|
# iterate through self.
|
||||||
# Assumes that self.load() has been called before
|
# Assumes that self.load() has been called before
|
||||||
@ -656,8 +688,8 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
self._resource_json_map = template["Resources"]
|
self._resource_json_map = template["Resources"]
|
||||||
self.tags.update(
|
self.tags.update(
|
||||||
{
|
{
|
||||||
"aws:cloudformation:stack-name": self.get("AWS::StackName"),
|
"aws:cloudformation:stack-name": self["AWS::StackName"],
|
||||||
"aws:cloudformation:stack-id": self.get("AWS::StackId"),
|
"aws:cloudformation:stack-id": self["AWS::StackId"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
all_resources_ready = True
|
all_resources_ready = True
|
||||||
@ -667,12 +699,14 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
self.tags["aws:cloudformation:logical-id"] = resource
|
self.tags["aws:cloudformation:logical-id"] = resource
|
||||||
ec2_models.ec2_backends[self._account_id][
|
ec2_models.ec2_backends[self._account_id][
|
||||||
self._region_name
|
self._region_name
|
||||||
].create_tags([instance.physical_resource_id], self.tags)
|
].create_tags(
|
||||||
|
[instance.physical_resource_id], self.tags
|
||||||
|
) # type: ignore[attr-defined]
|
||||||
if instance and not instance.is_created():
|
if instance and not instance.is_created():
|
||||||
all_resources_ready = False
|
all_resources_ready = False
|
||||||
return all_resources_ready
|
return all_resources_ready
|
||||||
|
|
||||||
def creation_complete(self):
|
def creation_complete(self) -> bool:
|
||||||
all_resources_ready = True
|
all_resources_ready = True
|
||||||
for resource in self.__get_resources_in_dependency_order():
|
for resource in self.__get_resources_in_dependency_order():
|
||||||
instance = self[resource]
|
instance = self[resource]
|
||||||
@ -680,7 +714,7 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
all_resources_ready = False
|
all_resources_ready = False
|
||||||
return all_resources_ready
|
return all_resources_ready
|
||||||
|
|
||||||
def build_resource_diff(self, other_template):
|
def build_resource_diff(self, other_template: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
|
||||||
old = self._resource_json_map
|
old = self._resource_json_map
|
||||||
new = other_template["Resources"]
|
new = other_template["Resources"]
|
||||||
@ -695,11 +729,17 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
|
|
||||||
return resource_names_by_action
|
return resource_names_by_action
|
||||||
|
|
||||||
def build_change_set_actions(self, template, parameters):
|
def build_change_set_actions(
|
||||||
|
self, template: Dict[str, Any]
|
||||||
|
) -> Dict[str, Dict[str, Dict[str, str]]]:
|
||||||
|
|
||||||
resource_names_by_action = self.build_resource_diff(template)
|
resource_names_by_action = self.build_resource_diff(template)
|
||||||
|
|
||||||
resources_by_action = {"Add": {}, "Modify": {}, "Remove": {}}
|
resources_by_action: Dict[str, Dict[str, Dict[str, str]]] = {
|
||||||
|
"Add": {},
|
||||||
|
"Modify": {},
|
||||||
|
"Remove": {},
|
||||||
|
}
|
||||||
|
|
||||||
for resource_name in resource_names_by_action["Add"]:
|
for resource_name in resource_names_by_action["Add"]:
|
||||||
resources_by_action["Add"][resource_name] = {
|
resources_by_action["Add"][resource_name] = {
|
||||||
@ -721,7 +761,9 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
|
|
||||||
return resources_by_action
|
return resources_by_action
|
||||||
|
|
||||||
def update(self, template, parameters=None):
|
def update(
|
||||||
|
self, template: Dict[str, Any], parameters: Optional[Dict[str, Any]] = None
|
||||||
|
) -> None:
|
||||||
|
|
||||||
resource_names_by_action = self.build_resource_diff(template)
|
resource_names_by_action = self.build_resource_diff(template)
|
||||||
|
|
||||||
@ -778,7 +820,7 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
if tries == 5:
|
if tries == 5:
|
||||||
raise last_exception
|
raise last_exception
|
||||||
|
|
||||||
def delete(self):
|
def delete(self) -> None:
|
||||||
remaining_resources = set(self.resources)
|
remaining_resources = set(self.resources)
|
||||||
tries = 1
|
tries = 1
|
||||||
while remaining_resources and tries < 5:
|
while remaining_resources and tries < 5:
|
||||||
@ -820,8 +862,8 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
raise last_exception
|
raise last_exception
|
||||||
|
|
||||||
|
|
||||||
class OutputMap(collections_abc.Mapping):
|
class OutputMap(collections_abc.Mapping): # type: ignore[type-arg]
|
||||||
def __init__(self, resources, template, stack_id):
|
def __init__(self, resources: ResourceMap, template: Dict[str, Any], stack_id: str):
|
||||||
self._template = template
|
self._template = template
|
||||||
self._stack_id = stack_id
|
self._stack_id = stack_id
|
||||||
|
|
||||||
@ -831,13 +873,13 @@ class OutputMap(collections_abc.Mapping):
|
|||||||
message="[/Outputs] 'null' values are not allowed in templates",
|
message="[/Outputs] 'null' values are not allowed in templates",
|
||||||
)
|
)
|
||||||
|
|
||||||
self._output_json_map = template.get("Outputs")
|
self._output_json_map: Dict[str, Any] = template.get("Outputs") # type: ignore[assignment]
|
||||||
|
|
||||||
# Create the default resources
|
# Create the default resources
|
||||||
self._resource_map = resources
|
self._resource_map = resources
|
||||||
self._parsed_outputs = dict()
|
self._parsed_outputs: Dict[str, Output] = dict()
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key: str) -> Optional[Output]:
|
||||||
output_logical_id = key
|
output_logical_id = key
|
||||||
|
|
||||||
if output_logical_id in self._parsed_outputs:
|
if output_logical_id in self._parsed_outputs:
|
||||||
@ -851,21 +893,21 @@ class OutputMap(collections_abc.Mapping):
|
|||||||
self._parsed_outputs[output_logical_id] = new_output
|
self._parsed_outputs[output_logical_id] = new_output
|
||||||
return new_output
|
return new_output
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self) -> Iterator[str]:
|
||||||
return iter(self.outputs)
|
return iter(self.outputs)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self) -> int:
|
||||||
return len(self._output_json_map)
|
return len(self._output_json_map) # type: ignore[arg-type]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def outputs(self):
|
def outputs(self) -> Iterable[str]:
|
||||||
return self._output_json_map.keys() if self._output_json_map else []
|
return self._output_json_map.keys() if self._output_json_map else []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def exports(self):
|
def exports(self) -> List["Export"]:
|
||||||
exports = []
|
exports = []
|
||||||
if self.outputs:
|
if self.outputs:
|
||||||
for value in self._output_json_map.values():
|
for value in self._output_json_map.values(): # type: ignore[union-attr]
|
||||||
if value.get("Export"):
|
if value.get("Export"):
|
||||||
cleaned_name = clean_json(
|
cleaned_name = clean_json(
|
||||||
value["Export"].get("Name"), self._resource_map
|
value["Export"].get("Name"), self._resource_map
|
||||||
@ -876,19 +918,19 @@ class OutputMap(collections_abc.Mapping):
|
|||||||
|
|
||||||
|
|
||||||
class Export(object):
|
class Export(object):
|
||||||
def __init__(self, exporting_stack_id, name, value):
|
def __init__(self, exporting_stack_id: str, name: str, value: str):
|
||||||
self._exporting_stack_id = exporting_stack_id
|
self._exporting_stack_id = exporting_stack_id
|
||||||
self._name = name
|
self._name = name
|
||||||
self._value = value
|
self._value = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def exporting_stack_id(self):
|
def exporting_stack_id(self) -> str:
|
||||||
return self._exporting_stack_id
|
return self._exporting_stack_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self) -> str:
|
||||||
return self._value
|
return self._value
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import yaml
|
import yaml
|
||||||
|
from typing import Any, Dict, Tuple, List, Optional, Union
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from yaml.parser import ParserError # pylint:disable=c-extension-no-member
|
from yaml.parser import ParserError # pylint:disable=c-extension-no-member
|
||||||
from yaml.scanner import ScannerError # pylint:disable=c-extension-no-member
|
from yaml.scanner import ScannerError # pylint:disable=c-extension-no-member
|
||||||
@ -8,13 +9,13 @@ from moto.core.responses import BaseResponse
|
|||||||
from moto.s3.models import s3_backends
|
from moto.s3.models import s3_backends
|
||||||
from moto.s3.exceptions import S3ClientError
|
from moto.s3.exceptions import S3ClientError
|
||||||
from moto.utilities.aws_headers import amzn_request_id
|
from moto.utilities.aws_headers import amzn_request_id
|
||||||
from .models import cloudformation_backends
|
from .models import cloudformation_backends, CloudFormationBackend, FakeStack
|
||||||
from .exceptions import ValidationError, MissingParameterError
|
from .exceptions import ValidationError, MissingParameterError
|
||||||
from .utils import yaml_tag_constructor
|
from .utils import yaml_tag_constructor
|
||||||
|
|
||||||
|
|
||||||
def get_template_summary_response_from_template(template_body):
|
def get_template_summary_response_from_template(template_body: str) -> Dict[str, Any]:
|
||||||
def get_resource_types(template_dict):
|
def get_resource_types(template_dict: Dict[str, Any]) -> List[Any]:
|
||||||
resources = {}
|
resources = {}
|
||||||
for key, value in template_dict.items():
|
for key, value in template_dict.items():
|
||||||
if key == "Resources":
|
if key == "Resources":
|
||||||
@ -38,20 +39,20 @@ def get_template_summary_response_from_template(template_body):
|
|||||||
|
|
||||||
|
|
||||||
class CloudFormationResponse(BaseResponse):
|
class CloudFormationResponse(BaseResponse):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__(service_name="cloudformation")
|
super().__init__(service_name="cloudformation")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cloudformation_backend(self):
|
def cloudformation_backend(self) -> CloudFormationBackend:
|
||||||
return cloudformation_backends[self.current_account][self.region]
|
return cloudformation_backends[self.current_account][self.region]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def cfnresponse(cls, *args, **kwargs): # pylint: disable=unused-argument
|
def cfnresponse(cls, *args: Any, **kwargs: Any) -> Any: # type: ignore[misc] # pylint: disable=unused-argument
|
||||||
request, full_url, headers = args
|
request, full_url, headers = args
|
||||||
full_url += "&Action=ProcessCfnResponse"
|
full_url += "&Action=ProcessCfnResponse"
|
||||||
return cls.dispatch(request=request, full_url=full_url, headers=headers)
|
return cls.dispatch(request=request, full_url=full_url, headers=headers)
|
||||||
|
|
||||||
def _get_stack_from_s3_url(self, template_url):
|
def _get_stack_from_s3_url(self, template_url: str) -> str:
|
||||||
template_url_parts = urlparse(template_url)
|
template_url_parts = urlparse(template_url)
|
||||||
if "localhost" in template_url:
|
if "localhost" in template_url:
|
||||||
bucket_name, key_name = template_url_parts.path.lstrip("/").split("/", 1)
|
bucket_name, key_name = template_url_parts.path.lstrip("/").split("/", 1)
|
||||||
@ -75,7 +76,9 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
)
|
)
|
||||||
return key.value.decode("utf-8")
|
return key.value.decode("utf-8")
|
||||||
|
|
||||||
def _get_params_from_list(self, parameters_list):
|
def _get_params_from_list(
|
||||||
|
self, parameters_list: List[Dict[str, Any]]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
# Hack dict-comprehension
|
# Hack dict-comprehension
|
||||||
return dict(
|
return dict(
|
||||||
[
|
[
|
||||||
@ -84,7 +87,9 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_param_values(self, parameters_list, existing_params):
|
def _get_param_values(
|
||||||
|
self, parameters_list: List[Dict[str, str]], existing_params: Dict[str, str]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
result = {}
|
result = {}
|
||||||
for parameter in parameters_list:
|
for parameter in parameters_list:
|
||||||
if parameter.keys() >= {"parameter_key", "parameter_value"}:
|
if parameter.keys() >= {"parameter_key", "parameter_value"}:
|
||||||
@ -100,7 +105,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
raise MissingParameterError(parameter["parameter_key"])
|
raise MissingParameterError(parameter["parameter_key"])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def process_cfn_response(self):
|
def process_cfn_response(self) -> Tuple[int, Dict[str, int], str]:
|
||||||
status = self._get_param("Status")
|
status = self._get_param("Status")
|
||||||
if status == "SUCCESS":
|
if status == "SUCCESS":
|
||||||
stack_id = self._get_param("StackId")
|
stack_id = self._get_param("StackId")
|
||||||
@ -113,7 +118,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
|
|
||||||
return 200, {"status": 200}, json.dumps("{}")
|
return 200, {"status": 200}, json.dumps("{}")
|
||||||
|
|
||||||
def create_stack(self):
|
def create_stack(self) -> Union[str, Tuple[int, Dict[str, int], str]]:
|
||||||
stack_name = self._get_param("StackName")
|
stack_name = self._get_param("StackName")
|
||||||
stack_body = self._get_param("TemplateBody")
|
stack_body = self._get_param("TemplateBody")
|
||||||
template_url = self._get_param("TemplateURL")
|
template_url = self._get_param("TemplateURL")
|
||||||
@ -156,14 +161,14 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(CREATE_STACK_RESPONSE_TEMPLATE)
|
template = self.response_template(CREATE_STACK_RESPONSE_TEMPLATE)
|
||||||
return template.render(stack=stack)
|
return template.render(stack=stack)
|
||||||
|
|
||||||
def stack_name_exists(self, new_stack_name):
|
def stack_name_exists(self, new_stack_name: str) -> bool:
|
||||||
for stack in self.cloudformation_backend.stacks.values():
|
for stack in self.cloudformation_backend.stacks.values():
|
||||||
if stack.name == new_stack_name:
|
if stack.name == new_stack_name:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@amzn_request_id
|
@amzn_request_id
|
||||||
def create_change_set(self):
|
def create_change_set(self) -> str:
|
||||||
stack_name = self._get_param("StackName")
|
stack_name = self._get_param("StackName")
|
||||||
change_set_name = self._get_param("ChangeSetName")
|
change_set_name = self._get_param("ChangeSetName")
|
||||||
stack_body = self._get_param("TemplateBody")
|
stack_body = self._get_param("TemplateBody")
|
||||||
@ -209,7 +214,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(CREATE_CHANGE_SET_RESPONSE_TEMPLATE)
|
template = self.response_template(CREATE_CHANGE_SET_RESPONSE_TEMPLATE)
|
||||||
return template.render(stack_id=stack_id, change_set_id=change_set_id)
|
return template.render(stack_id=stack_id, change_set_id=change_set_id)
|
||||||
|
|
||||||
def delete_change_set(self):
|
def delete_change_set(self) -> str:
|
||||||
change_set_name = self._get_param("ChangeSetName")
|
change_set_name = self._get_param("ChangeSetName")
|
||||||
|
|
||||||
self.cloudformation_backend.delete_change_set(change_set_name=change_set_name)
|
self.cloudformation_backend.delete_change_set(change_set_name=change_set_name)
|
||||||
@ -221,7 +226,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(DELETE_CHANGE_SET_RESPONSE_TEMPLATE)
|
template = self.response_template(DELETE_CHANGE_SET_RESPONSE_TEMPLATE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def describe_change_set(self):
|
def describe_change_set(self) -> str:
|
||||||
change_set_name = self._get_param("ChangeSetName")
|
change_set_name = self._get_param("ChangeSetName")
|
||||||
change_set = self.cloudformation_backend.describe_change_set(
|
change_set = self.cloudformation_backend.describe_change_set(
|
||||||
change_set_name=change_set_name
|
change_set_name=change_set_name
|
||||||
@ -230,7 +235,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
return template.render(change_set=change_set)
|
return template.render(change_set=change_set)
|
||||||
|
|
||||||
@amzn_request_id
|
@amzn_request_id
|
||||||
def execute_change_set(self):
|
def execute_change_set(self) -> str:
|
||||||
stack_name = self._get_param("StackName")
|
stack_name = self._get_param("StackName")
|
||||||
change_set_name = self._get_param("ChangeSetName")
|
change_set_name = self._get_param("ChangeSetName")
|
||||||
self.cloudformation_backend.execute_change_set(
|
self.cloudformation_backend.execute_change_set(
|
||||||
@ -244,10 +249,8 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(EXECUTE_CHANGE_SET_RESPONSE_TEMPLATE)
|
template = self.response_template(EXECUTE_CHANGE_SET_RESPONSE_TEMPLATE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def describe_stacks(self):
|
def describe_stacks(self) -> str:
|
||||||
stack_name_or_id = None
|
stack_name_or_id = self._get_param("StackName")
|
||||||
if self._get_param("StackName"):
|
|
||||||
stack_name_or_id = self.querystring.get("StackName")[0]
|
|
||||||
token = self._get_param("NextToken")
|
token = self._get_param("NextToken")
|
||||||
stacks = self.cloudformation_backend.describe_stacks(stack_name_or_id)
|
stacks = self.cloudformation_backend.describe_stacks(stack_name_or_id)
|
||||||
stack_ids = [stack.stack_id for stack in stacks]
|
stack_ids = [stack.stack_id for stack in stacks]
|
||||||
@ -263,14 +266,14 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(DESCRIBE_STACKS_TEMPLATE)
|
template = self.response_template(DESCRIBE_STACKS_TEMPLATE)
|
||||||
return template.render(stacks=stacks_resp, next_token=next_token)
|
return template.render(stacks=stacks_resp, next_token=next_token)
|
||||||
|
|
||||||
def describe_stack_resource(self):
|
def describe_stack_resource(self) -> str:
|
||||||
stack_name = self._get_param("StackName")
|
stack_name = self._get_param("StackName")
|
||||||
stack = self.cloudformation_backend.get_stack(stack_name)
|
stack = self.cloudformation_backend.get_stack(stack_name)
|
||||||
logical_resource_id = self._get_param("LogicalResourceId")
|
logical_resource_id = self._get_param("LogicalResourceId")
|
||||||
|
|
||||||
resource = None
|
resource = None
|
||||||
for stack_resource in stack.stack_resources:
|
for stack_resource in stack.stack_resources:
|
||||||
if stack_resource.logical_resource_id == logical_resource_id:
|
if stack_resource.logical_resource_id == logical_resource_id: # type: ignore[attr-defined]
|
||||||
resource = stack_resource
|
resource = stack_resource
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -283,40 +286,40 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(DESCRIBE_STACK_RESOURCE_RESPONSE_TEMPLATE)
|
template = self.response_template(DESCRIBE_STACK_RESOURCE_RESPONSE_TEMPLATE)
|
||||||
return template.render(stack=stack, resource=resource)
|
return template.render(stack=stack, resource=resource)
|
||||||
|
|
||||||
def describe_stack_resources(self):
|
def describe_stack_resources(self) -> str:
|
||||||
stack_name = self._get_param("StackName")
|
stack_name = self._get_param("StackName")
|
||||||
stack = self.cloudformation_backend.get_stack(stack_name)
|
stack = self.cloudformation_backend.get_stack(stack_name)
|
||||||
|
|
||||||
template = self.response_template(DESCRIBE_STACK_RESOURCES_RESPONSE)
|
template = self.response_template(DESCRIBE_STACK_RESOURCES_RESPONSE)
|
||||||
return template.render(stack=stack)
|
return template.render(stack=stack)
|
||||||
|
|
||||||
def describe_stack_events(self):
|
def describe_stack_events(self) -> str:
|
||||||
stack_name = self._get_param("StackName")
|
stack_name = self._get_param("StackName")
|
||||||
stack = self.cloudformation_backend.get_stack(stack_name)
|
stack = self.cloudformation_backend.get_stack(stack_name)
|
||||||
|
|
||||||
template = self.response_template(DESCRIBE_STACK_EVENTS_RESPONSE)
|
template = self.response_template(DESCRIBE_STACK_EVENTS_RESPONSE)
|
||||||
return template.render(stack=stack)
|
return template.render(stack=stack)
|
||||||
|
|
||||||
def list_change_sets(self):
|
def list_change_sets(self) -> str:
|
||||||
change_sets = self.cloudformation_backend.list_change_sets()
|
change_sets = self.cloudformation_backend.list_change_sets()
|
||||||
template = self.response_template(LIST_CHANGE_SETS_RESPONSE)
|
template = self.response_template(LIST_CHANGE_SETS_RESPONSE)
|
||||||
return template.render(change_sets=change_sets)
|
return template.render(change_sets=change_sets)
|
||||||
|
|
||||||
def list_stacks(self):
|
def list_stacks(self) -> str:
|
||||||
status_filter = self._get_multi_param("StackStatusFilter.member")
|
status_filter = self._get_multi_param("StackStatusFilter.member")
|
||||||
stacks = self.cloudformation_backend.list_stacks(status_filter)
|
stacks = self.cloudformation_backend.list_stacks(status_filter)
|
||||||
template = self.response_template(LIST_STACKS_RESPONSE)
|
template = self.response_template(LIST_STACKS_RESPONSE)
|
||||||
return template.render(stacks=stacks)
|
return template.render(stacks=stacks)
|
||||||
|
|
||||||
def list_stack_resources(self):
|
def list_stack_resources(self) -> str:
|
||||||
stack_name_or_id = self._get_param("StackName")
|
stack_name_or_id = self._get_param("StackName")
|
||||||
resources = self.cloudformation_backend.list_stack_resources(stack_name_or_id)
|
resources = self.cloudformation_backend.list_stack_resources(stack_name_or_id)
|
||||||
|
|
||||||
template = self.response_template(LIST_STACKS_RESOURCES_RESPONSE)
|
template = self.response_template(LIST_STACKS_RESOURCES_RESPONSE)
|
||||||
return template.render(resources=resources)
|
return template.render(resources=resources)
|
||||||
|
|
||||||
def get_template(self):
|
def get_template(self) -> str:
|
||||||
name_or_stack_id = self.querystring.get("StackName")[0]
|
name_or_stack_id = self.querystring.get("StackName")[0] # type: ignore[index]
|
||||||
stack = self.cloudformation_backend.get_stack(name_or_stack_id)
|
stack = self.cloudformation_backend.get_stack(name_or_stack_id)
|
||||||
|
|
||||||
if self.request_json:
|
if self.request_json:
|
||||||
@ -336,7 +339,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(GET_TEMPLATE_RESPONSE_TEMPLATE)
|
template = self.response_template(GET_TEMPLATE_RESPONSE_TEMPLATE)
|
||||||
return template.render(stack=stack)
|
return template.render(stack=stack)
|
||||||
|
|
||||||
def get_template_summary(self):
|
def get_template_summary(self) -> str:
|
||||||
stack_name = self._get_param("StackName")
|
stack_name = self._get_param("StackName")
|
||||||
template_url = self._get_param("TemplateURL")
|
template_url = self._get_param("TemplateURL")
|
||||||
stack_body = self._get_param("TemplateBody")
|
stack_body = self._get_param("TemplateBody")
|
||||||
@ -355,7 +358,12 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(GET_TEMPLATE_SUMMARY_TEMPLATE)
|
template = self.response_template(GET_TEMPLATE_SUMMARY_TEMPLATE)
|
||||||
return template.render(template_summary=template_summary)
|
return template.render(template_summary=template_summary)
|
||||||
|
|
||||||
def _validate_different_update(self, incoming_params, stack_body, old_stack):
|
def _validate_different_update(
|
||||||
|
self,
|
||||||
|
incoming_params: Optional[List[Dict[str, Any]]],
|
||||||
|
stack_body: str,
|
||||||
|
old_stack: FakeStack,
|
||||||
|
) -> None:
|
||||||
if incoming_params and stack_body:
|
if incoming_params and stack_body:
|
||||||
new_params = self._get_param_values(incoming_params, old_stack.parameters)
|
new_params = self._get_param_values(incoming_params, old_stack.parameters)
|
||||||
if old_stack.template == stack_body and old_stack.parameters == new_params:
|
if old_stack.template == stack_body and old_stack.parameters == new_params:
|
||||||
@ -363,7 +371,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
old_stack.name, message=f"Stack [{old_stack.name}] already exists"
|
old_stack.name, message=f"Stack [{old_stack.name}] already exists"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _validate_status(self, stack):
|
def _validate_status(self, stack: FakeStack) -> None:
|
||||||
if stack.status == "ROLLBACK_COMPLETE":
|
if stack.status == "ROLLBACK_COMPLETE":
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
stack.stack_id,
|
stack.stack_id,
|
||||||
@ -371,7 +379,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
"be updated.".format(stack.stack_id),
|
"be updated.".format(stack.stack_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_stack(self):
|
def update_stack(self) -> str:
|
||||||
stack_name = self._get_param("StackName")
|
stack_name = self._get_param("StackName")
|
||||||
role_arn = self._get_param("RoleARN")
|
role_arn = self._get_param("RoleARN")
|
||||||
template_url = self._get_param("TemplateURL")
|
template_url = self._get_param("TemplateURL")
|
||||||
@ -386,7 +394,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
# boto3 is supposed to let you clear the tags by passing an empty value, but the request body doesn't
|
# boto3 is supposed to let you clear the tags by passing an empty value, but the request body doesn't
|
||||||
# end up containing anything we can use to differentiate between passing an empty value versus not
|
# end up containing anything we can use to differentiate between passing an empty value versus not
|
||||||
# passing anything. so until that changes, moto won't be able to clear tags, only update them.
|
# passing anything. so until that changes, moto won't be able to clear tags, only update them.
|
||||||
tags = dict(
|
tags: Optional[Dict[str, str]] = dict(
|
||||||
(item["key"], item["value"])
|
(item["key"], item["value"])
|
||||||
for item in self._get_list_prefix("Tags.member")
|
for item in self._get_list_prefix("Tags.member")
|
||||||
)
|
)
|
||||||
@ -414,8 +422,8 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(UPDATE_STACK_RESPONSE_TEMPLATE)
|
template = self.response_template(UPDATE_STACK_RESPONSE_TEMPLATE)
|
||||||
return template.render(stack=stack)
|
return template.render(stack=stack)
|
||||||
|
|
||||||
def delete_stack(self):
|
def delete_stack(self) -> str:
|
||||||
name_or_stack_id = self.querystring.get("StackName")[0]
|
name_or_stack_id = self.querystring.get("StackName")[0] # type: ignore[index]
|
||||||
|
|
||||||
self.cloudformation_backend.delete_stack(name_or_stack_id)
|
self.cloudformation_backend.delete_stack(name_or_stack_id)
|
||||||
if self.request_json:
|
if self.request_json:
|
||||||
@ -424,13 +432,13 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(DELETE_STACK_RESPONSE_TEMPLATE)
|
template = self.response_template(DELETE_STACK_RESPONSE_TEMPLATE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def list_exports(self):
|
def list_exports(self) -> str:
|
||||||
token = self._get_param("NextToken")
|
token = self._get_param("NextToken")
|
||||||
exports, next_token = self.cloudformation_backend.list_exports(token=token)
|
exports, next_token = self.cloudformation_backend.list_exports(tokenstr=token)
|
||||||
template = self.response_template(LIST_EXPORTS_RESPONSE)
|
template = self.response_template(LIST_EXPORTS_RESPONSE)
|
||||||
return template.render(exports=exports, next_token=next_token)
|
return template.render(exports=exports, next_token=next_token)
|
||||||
|
|
||||||
def validate_template(self):
|
def validate_template(self) -> str:
|
||||||
template_body = self._get_param("TemplateBody")
|
template_body = self._get_param("TemplateBody")
|
||||||
template_url = self._get_param("TemplateURL")
|
template_url = self._get_param("TemplateURL")
|
||||||
if template_url:
|
if template_url:
|
||||||
@ -451,7 +459,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(VALIDATE_STACK_RESPONSE_TEMPLATE)
|
template = self.response_template(VALIDATE_STACK_RESPONSE_TEMPLATE)
|
||||||
return template.render(description=description)
|
return template.render(description=description)
|
||||||
|
|
||||||
def create_stack_set(self):
|
def create_stack_set(self) -> str:
|
||||||
stackset_name = self._get_param("StackSetName")
|
stackset_name = self._get_param("StackSetName")
|
||||||
stack_body = self._get_param("TemplateBody")
|
stack_body = self._get_param("TemplateBody")
|
||||||
template_url = self._get_param("TemplateURL")
|
template_url = self._get_param("TemplateURL")
|
||||||
@ -486,7 +494,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(CREATE_STACK_SET_RESPONSE_TEMPLATE)
|
template = self.response_template(CREATE_STACK_SET_RESPONSE_TEMPLATE)
|
||||||
return template.render(stackset=stackset)
|
return template.render(stackset=stackset)
|
||||||
|
|
||||||
def create_stack_instances(self):
|
def create_stack_instances(self) -> str:
|
||||||
stackset_name = self._get_param("StackSetName")
|
stackset_name = self._get_param("StackSetName")
|
||||||
accounts = self._get_multi_param("Accounts.member")
|
accounts = self._get_multi_param("Accounts.member")
|
||||||
regions = self._get_multi_param("Regions.member")
|
regions = self._get_multi_param("Regions.member")
|
||||||
@ -497,13 +505,13 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(CREATE_STACK_INSTANCES_TEMPLATE)
|
template = self.response_template(CREATE_STACK_INSTANCES_TEMPLATE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def delete_stack_set(self):
|
def delete_stack_set(self) -> str:
|
||||||
stackset_name = self._get_param("StackSetName")
|
stackset_name = self._get_param("StackSetName")
|
||||||
self.cloudformation_backend.delete_stack_set(stackset_name)
|
self.cloudformation_backend.delete_stack_set(stackset_name)
|
||||||
template = self.response_template(DELETE_STACK_SET_RESPONSE_TEMPLATE)
|
template = self.response_template(DELETE_STACK_SET_RESPONSE_TEMPLATE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def delete_stack_instances(self):
|
def delete_stack_instances(self) -> str:
|
||||||
stackset_name = self._get_param("StackSetName")
|
stackset_name = self._get_param("StackSetName")
|
||||||
accounts = self._get_multi_param("Accounts.member")
|
accounts = self._get_multi_param("Accounts.member")
|
||||||
regions = self._get_multi_param("Regions.member")
|
regions = self._get_multi_param("Regions.member")
|
||||||
@ -514,7 +522,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(DELETE_STACK_INSTANCES_TEMPLATE)
|
template = self.response_template(DELETE_STACK_INSTANCES_TEMPLATE)
|
||||||
return template.render(operation=operation)
|
return template.render(operation=operation)
|
||||||
|
|
||||||
def describe_stack_set(self):
|
def describe_stack_set(self) -> str:
|
||||||
stackset_name = self._get_param("StackSetName")
|
stackset_name = self._get_param("StackSetName")
|
||||||
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
||||||
|
|
||||||
@ -526,7 +534,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(DESCRIBE_STACK_SET_RESPONSE_TEMPLATE)
|
template = self.response_template(DESCRIBE_STACK_SET_RESPONSE_TEMPLATE)
|
||||||
return template.render(stackset=stackset)
|
return template.render(stackset=stackset)
|
||||||
|
|
||||||
def describe_stack_instance(self):
|
def describe_stack_instance(self) -> str:
|
||||||
stackset_name = self._get_param("StackSetName")
|
stackset_name = self._get_param("StackSetName")
|
||||||
account = self._get_param("StackInstanceAccount")
|
account = self._get_param("StackInstanceAccount")
|
||||||
region = self._get_param("StackInstanceRegion")
|
region = self._get_param("StackInstanceRegion")
|
||||||
@ -538,24 +546,24 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
rendered = template.render(instance=instance)
|
rendered = template.render(instance=instance)
|
||||||
return rendered
|
return rendered
|
||||||
|
|
||||||
def list_stack_sets(self):
|
def list_stack_sets(self) -> str:
|
||||||
stacksets = self.cloudformation_backend.stacksets
|
stacksets = self.cloudformation_backend.stacksets
|
||||||
template = self.response_template(LIST_STACK_SETS_TEMPLATE)
|
template = self.response_template(LIST_STACK_SETS_TEMPLATE)
|
||||||
return template.render(stacksets=stacksets)
|
return template.render(stacksets=stacksets)
|
||||||
|
|
||||||
def list_stack_instances(self):
|
def list_stack_instances(self) -> str:
|
||||||
stackset_name = self._get_param("StackSetName")
|
stackset_name = self._get_param("StackSetName")
|
||||||
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
||||||
template = self.response_template(LIST_STACK_INSTANCES_TEMPLATE)
|
template = self.response_template(LIST_STACK_INSTANCES_TEMPLATE)
|
||||||
return template.render(stackset=stackset)
|
return template.render(stackset=stackset)
|
||||||
|
|
||||||
def list_stack_set_operations(self):
|
def list_stack_set_operations(self) -> str:
|
||||||
stackset_name = self._get_param("StackSetName")
|
stackset_name = self._get_param("StackSetName")
|
||||||
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
||||||
template = self.response_template(LIST_STACK_SET_OPERATIONS_RESPONSE_TEMPLATE)
|
template = self.response_template(LIST_STACK_SET_OPERATIONS_RESPONSE_TEMPLATE)
|
||||||
return template.render(stackset=stackset)
|
return template.render(stackset=stackset)
|
||||||
|
|
||||||
def stop_stack_set_operation(self):
|
def stop_stack_set_operation(self) -> str:
|
||||||
stackset_name = self._get_param("StackSetName")
|
stackset_name = self._get_param("StackSetName")
|
||||||
operation_id = self._get_param("OperationId")
|
operation_id = self._get_param("OperationId")
|
||||||
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
||||||
@ -563,7 +571,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(STOP_STACK_SET_OPERATION_RESPONSE_TEMPLATE)
|
template = self.response_template(STOP_STACK_SET_OPERATION_RESPONSE_TEMPLATE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def describe_stack_set_operation(self):
|
def describe_stack_set_operation(self) -> str:
|
||||||
stackset_name = self._get_param("StackSetName")
|
stackset_name = self._get_param("StackSetName")
|
||||||
operation_id = self._get_param("OperationId")
|
operation_id = self._get_param("OperationId")
|
||||||
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
||||||
@ -571,7 +579,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(DESCRIBE_STACKSET_OPERATION_RESPONSE_TEMPLATE)
|
template = self.response_template(DESCRIBE_STACKSET_OPERATION_RESPONSE_TEMPLATE)
|
||||||
return template.render(stackset=stackset, operation=operation)
|
return template.render(stackset=stackset, operation=operation)
|
||||||
|
|
||||||
def list_stack_set_operation_results(self):
|
def list_stack_set_operation_results(self) -> str:
|
||||||
stackset_name = self._get_param("StackSetName")
|
stackset_name = self._get_param("StackSetName")
|
||||||
operation_id = self._get_param("OperationId")
|
operation_id = self._get_param("OperationId")
|
||||||
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
||||||
@ -581,7 +589,7 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
)
|
)
|
||||||
return template.render(operation=operation)
|
return template.render(operation=operation)
|
||||||
|
|
||||||
def update_stack_set(self):
|
def update_stack_set(self) -> str:
|
||||||
stackset_name = self._get_param("StackSetName")
|
stackset_name = self._get_param("StackSetName")
|
||||||
operation_id = self._get_param("OperationId")
|
operation_id = self._get_param("OperationId")
|
||||||
description = self._get_param("Description")
|
description = self._get_param("Description")
|
||||||
@ -615,24 +623,24 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(UPDATE_STACK_SET_RESPONSE_TEMPLATE)
|
template = self.response_template(UPDATE_STACK_SET_RESPONSE_TEMPLATE)
|
||||||
return template.render(operation=operation)
|
return template.render(operation=operation)
|
||||||
|
|
||||||
def update_stack_instances(self):
|
def update_stack_instances(self) -> str:
|
||||||
stackset_name = self._get_param("StackSetName")
|
stackset_name = self._get_param("StackSetName")
|
||||||
accounts = self._get_multi_param("Accounts.member")
|
accounts = self._get_multi_param("Accounts.member")
|
||||||
regions = self._get_multi_param("Regions.member")
|
regions = self._get_multi_param("Regions.member")
|
||||||
parameters = self._get_multi_param("ParameterOverrides.member")
|
parameters = self._get_multi_param("ParameterOverrides.member")
|
||||||
operation = self.cloudformation_backend.get_stack_set(
|
operation = self.cloudformation_backend.update_stack_instances(
|
||||||
stackset_name
|
stackset_name, accounts, regions, parameters
|
||||||
).update_instances(accounts, regions, parameters)
|
)
|
||||||
template = self.response_template(UPDATE_STACK_INSTANCES_RESPONSE_TEMPLATE)
|
template = self.response_template(UPDATE_STACK_INSTANCES_RESPONSE_TEMPLATE)
|
||||||
return template.render(operation=operation)
|
return template.render(operation=operation)
|
||||||
|
|
||||||
def get_stack_policy(self):
|
def get_stack_policy(self) -> str:
|
||||||
stack_name = self._get_param("StackName")
|
stack_name = self._get_param("StackName")
|
||||||
policy = self.cloudformation_backend.get_stack_policy(stack_name)
|
policy = self.cloudformation_backend.get_stack_policy(stack_name)
|
||||||
template = self.response_template(GET_STACK_POLICY_RESPONSE)
|
template = self.response_template(GET_STACK_POLICY_RESPONSE)
|
||||||
return template.render(policy=policy)
|
return template.render(policy=policy)
|
||||||
|
|
||||||
def set_stack_policy(self):
|
def set_stack_policy(self) -> str:
|
||||||
stack_name = self._get_param("StackName")
|
stack_name = self._get_param("StackName")
|
||||||
policy_url = self._get_param("StackPolicyURL")
|
policy_url = self._get_param("StackPolicyURL")
|
||||||
policy_body = self._get_param("StackPolicyBody")
|
policy_body = self._get_param("StackPolicyBody")
|
||||||
|
@ -2,37 +2,40 @@ import yaml
|
|||||||
import os
|
import os
|
||||||
import string
|
import string
|
||||||
from moto.moto_api._internal import mock_random as random
|
from moto.moto_api._internal import mock_random as random
|
||||||
|
from typing import Any, List
|
||||||
|
|
||||||
|
|
||||||
def generate_stack_id(stack_name, region, account):
|
def generate_stack_id(stack_name: str, region: str, account: str) -> str:
|
||||||
random_id = random.uuid4()
|
random_id = random.uuid4()
|
||||||
return f"arn:aws:cloudformation:{region}:{account}:stack/{stack_name}/{random_id}"
|
return f"arn:aws:cloudformation:{region}:{account}:stack/{stack_name}/{random_id}"
|
||||||
|
|
||||||
|
|
||||||
def generate_changeset_id(changeset_name, region_name, account_id):
|
def generate_changeset_id(
|
||||||
|
changeset_name: str, region_name: str, account_id: str
|
||||||
|
) -> str:
|
||||||
random_id = random.uuid4()
|
random_id = random.uuid4()
|
||||||
return f"arn:aws:cloudformation:{region_name}:{account_id}:changeSet/{changeset_name}/{random_id}"
|
return f"arn:aws:cloudformation:{region_name}:{account_id}:changeSet/{changeset_name}/{random_id}"
|
||||||
|
|
||||||
|
|
||||||
def generate_stackset_id(stackset_name):
|
def generate_stackset_id(stackset_name: str) -> str:
|
||||||
random_id = random.uuid4()
|
random_id = random.uuid4()
|
||||||
return "{}:{}".format(stackset_name, random_id)
|
return "{}:{}".format(stackset_name, random_id)
|
||||||
|
|
||||||
|
|
||||||
def generate_stackset_arn(stackset_id, region_name, account_id):
|
def generate_stackset_arn(stackset_id: str, region_name: str, account_id: str) -> str:
|
||||||
return f"arn:aws:cloudformation:{region_name}:{account_id}:stackset/{stackset_id}"
|
return f"arn:aws:cloudformation:{region_name}:{account_id}:stackset/{stackset_id}"
|
||||||
|
|
||||||
|
|
||||||
def random_suffix():
|
def random_suffix() -> str:
|
||||||
size = 12
|
size = 12
|
||||||
chars = list(range(10)) + list(string.ascii_uppercase)
|
chars = list(range(10)) + list(string.ascii_uppercase)
|
||||||
return "".join(str(random.choice(chars)) for x in range(size))
|
return "".join(str(random.choice(chars)) for x in range(size))
|
||||||
|
|
||||||
|
|
||||||
def yaml_tag_constructor(loader, tag, node):
|
def yaml_tag_constructor(loader: Any, tag: Any, node: Any) -> Any:
|
||||||
"""convert shorthand intrinsic function to full name"""
|
"""convert shorthand intrinsic function to full name"""
|
||||||
|
|
||||||
def _f(loader, tag, node):
|
def _f(loader: Any, tag: Any, node: Any) -> Any:
|
||||||
if tag == "!GetAtt":
|
if tag == "!GetAtt":
|
||||||
if isinstance(node.value, list):
|
if isinstance(node.value, list):
|
||||||
return node.value
|
return node.value
|
||||||
@ -50,7 +53,7 @@ def yaml_tag_constructor(loader, tag, node):
|
|||||||
return {key: _f(loader, tag, node)}
|
return {key: _f(loader, tag, node)}
|
||||||
|
|
||||||
|
|
||||||
def validate_template_cfn_lint(template):
|
def validate_template_cfn_lint(template: str) -> List[Any]:
|
||||||
# Importing cfnlint adds a significant overhead, so we keep it local
|
# Importing cfnlint adds a significant overhead, so we keep it local
|
||||||
from cfnlint import decode, core
|
from cfnlint import decode, core
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
from typing import Any, Dict
|
||||||
from .base_backend import InstanceTrackerMeta
|
from .base_backend import InstanceTrackerMeta
|
||||||
|
|
||||||
|
|
||||||
@ -14,22 +15,22 @@ class BaseModel(metaclass=InstanceTrackerMeta):
|
|||||||
class CloudFormationModel(BaseModel):
|
class CloudFormationModel(BaseModel):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def cloudformation_name_type():
|
def cloudformation_name_type() -> str:
|
||||||
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-name.html
|
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-name.html
|
||||||
# This must be implemented as a staticmethod with no parameters
|
# This must be implemented as a staticmethod with no parameters
|
||||||
# Return None for resources that do not have a name property
|
# Return "" for resources that do not have a name property
|
||||||
pass
|
return ""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def cloudformation_type():
|
def cloudformation_type() -> str:
|
||||||
# This must be implemented as a staticmethod with no parameters
|
# 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
|
# See for example https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html
|
||||||
return "AWS::SERVICE::RESOURCE"
|
return "AWS::SERVICE::RESOURCE"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def has_cfn_attr(cls, attr): # pylint: disable=unused-argument
|
def has_cfn_attr(cls, attr: str) -> bool: # pylint: disable=unused-argument
|
||||||
# Used for validation
|
# Used for validation
|
||||||
# If a template creates an Output for an attribute that does not exist, an error should be thrown
|
# If a template creates an Output for an attribute that does not exist, an error should be thrown
|
||||||
return True
|
return True
|
||||||
@ -37,44 +38,53 @@ class CloudFormationModel(BaseModel):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def create_from_cloudformation_json(
|
def create_from_cloudformation_json(
|
||||||
cls, resource_name, cloudformation_json, account_id, region_name, **kwargs
|
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:
|
# This must be implemented as a classmethod with parameters:
|
||||||
# cls, resource_name, cloudformation_json, account_id, region_name
|
# cls, resource_name, cloudformation_json, account_id, region_name
|
||||||
# Extract the resource parameters from the cloudformation json
|
# Extract the resource parameters from the cloudformation json
|
||||||
# and return an instance of the resource class
|
# and return an instance of the resource class
|
||||||
pass
|
...
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def update_from_cloudformation_json(
|
def update_from_cloudformation_json(
|
||||||
cls,
|
cls,
|
||||||
original_resource,
|
original_resource: Any,
|
||||||
new_resource_name,
|
new_resource_name: str,
|
||||||
cloudformation_json,
|
cloudformation_json: Dict[str, Any],
|
||||||
account_id,
|
account_id: str,
|
||||||
region_name,
|
region_name: str,
|
||||||
):
|
) -> Any:
|
||||||
# This must be implemented as a classmethod with parameters:
|
# This must be implemented as a classmethod with parameters:
|
||||||
# cls, original_resource, new_resource_name, cloudformation_json, account_id, region_name
|
# cls, original_resource, new_resource_name, cloudformation_json, account_id, region_name
|
||||||
# Extract the resource parameters from the cloudformation json,
|
# Extract the resource parameters from the cloudformation json,
|
||||||
# delete the old resource and return the new one. Optionally inspect
|
# delete the old resource and return the new one. Optionally inspect
|
||||||
# the change in parameters and no-op when nothing has changed.
|
# the change in parameters and no-op when nothing has changed.
|
||||||
pass
|
...
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def delete_from_cloudformation_json(
|
def delete_from_cloudformation_json(
|
||||||
cls, resource_name, cloudformation_json, account_id, region_name
|
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:
|
# This must be implemented as a classmethod with parameters:
|
||||||
# cls, resource_name, cloudformation_json, account_id, region_name
|
# cls, resource_name, cloudformation_json, account_id, region_name
|
||||||
# Extract the resource parameters from the cloudformation json
|
# Extract the resource parameters from the cloudformation json
|
||||||
# and delete the resource. Do not include a return statement.
|
# and delete the resource. Do not include a return statement.
|
||||||
pass
|
...
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def is_created(self):
|
def is_created(self) -> bool:
|
||||||
# Verify whether the resource was created successfully
|
# Verify whether the resource was created successfully
|
||||||
# Assume True after initialization
|
# Assume True after initialization
|
||||||
# Custom resources may need time after init before they are created successfully
|
# Custom resources may need time after init before they are created successfully
|
||||||
|
@ -218,7 +218,7 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
|
|||||||
self.service_name = service_name
|
self.service_name = service_name
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def dispatch(cls, *args, **kwargs):
|
def dispatch(cls, *args: Any, **kwargs: Any) -> Any:
|
||||||
return cls()._dispatch(*args, **kwargs)
|
return cls()._dispatch(*args, **kwargs)
|
||||||
|
|
||||||
def setup_class(
|
def setup_class(
|
||||||
|
@ -152,16 +152,18 @@ def iso_8601_datetime_with_milliseconds(value: datetime) -> str:
|
|||||||
|
|
||||||
|
|
||||||
# Even Python does not support nanoseconds, other languages like Go do (needed for Terraform)
|
# Even Python does not support nanoseconds, other languages like Go do (needed for Terraform)
|
||||||
def iso_8601_datetime_with_nanoseconds(value):
|
def iso_8601_datetime_with_nanoseconds(value: datetime.datetime) -> str:
|
||||||
return value.strftime("%Y-%m-%dT%H:%M:%S.%f000Z")
|
return value.strftime("%Y-%m-%dT%H:%M:%S.%f000Z")
|
||||||
|
|
||||||
|
|
||||||
def iso_8601_datetime_without_milliseconds(value):
|
def iso_8601_datetime_without_milliseconds(value: datetime.datetime) -> Optional[str]:
|
||||||
return None if value is None else value.strftime("%Y-%m-%dT%H:%M:%SZ")
|
return value.strftime("%Y-%m-%dT%H:%M:%SZ") if value else None
|
||||||
|
|
||||||
|
|
||||||
def iso_8601_datetime_without_milliseconds_s3(value):
|
def iso_8601_datetime_without_milliseconds_s3(
|
||||||
return None if value is None else value.strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
value: datetime.datetime,
|
||||||
|
) -> Optional[str]:
|
||||||
|
return value.strftime("%Y-%m-%dT%H:%M:%S.000Z") if value else None
|
||||||
|
|
||||||
|
|
||||||
RFC1123 = "%a, %d %b %Y %H:%M:%S GMT"
|
RFC1123 = "%a, %d %b %Y %H:%M:%S GMT"
|
||||||
|
@ -3,6 +3,7 @@ import logging
|
|||||||
import re
|
import re
|
||||||
from urllib.parse import urlparse, unquote, quote
|
from urllib.parse import urlparse, unquote, quote
|
||||||
from requests.structures import CaseInsensitiveDict
|
from requests.structures import CaseInsensitiveDict
|
||||||
|
from typing import Union, Tuple
|
||||||
import sys
|
import sys
|
||||||
from moto.settings import S3_IGNORE_SUBDOMAIN_BUCKETNAME
|
from moto.settings import S3_IGNORE_SUBDOMAIN_BUCKETNAME
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ def bucket_name_from_url(url):
|
|||||||
|
|
||||||
|
|
||||||
# 'owi-common-cf', 'snippets/test.json' = bucket_and_name_from_url('s3://owi-common-cf/snippets/test.json')
|
# 'owi-common-cf', 'snippets/test.json' = bucket_and_name_from_url('s3://owi-common-cf/snippets/test.json')
|
||||||
def bucket_and_name_from_url(url):
|
def bucket_and_name_from_url(url: str) -> Union[Tuple[str, str], Tuple[None, None]]:
|
||||||
prefix = "s3://"
|
prefix = "s3://"
|
||||||
if url.startswith(prefix):
|
if url.startswith(prefix):
|
||||||
bucket_name = url[len(prefix) : url.index("/", len(prefix))]
|
bucket_name = url[len(prefix) : url.index("/", len(prefix))]
|
||||||
|
@ -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
|
files= moto/a*,moto/b*,moto/ce,moto/cloudformation
|
||||||
show_column_numbers=True
|
show_column_numbers=True
|
||||||
show_error_codes = True
|
show_error_codes = True
|
||||||
disable_error_code=abstract
|
disable_error_code=abstract
|
||||||
|
Loading…
Reference in New Issue
Block a user