diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model.py index 557ca7ad59a2a..18633c94fcb50 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model.py @@ -23,6 +23,9 @@ def __new__(cls): cls._singleton = super().__new__(cls) return cls._singleton + def __eq__(self, other): + return is_nothing(other) + def __str__(self): return repr(self) @@ -35,11 +38,46 @@ def __bool__(self): def __iter__(self): return iter(()) + def __contains__(self, item): + return False + Maybe = Union[T, NothingType] Nothing = NothingType() +def is_nothing(value: Any) -> bool: + return isinstance(value, NothingType) + + +def is_created(before: Maybe[Any], after: Maybe[Any]) -> bool: + return is_nothing(before) and not is_nothing(after) + + +def is_removed(before: Maybe[Any], after: Maybe[Any]) -> bool: + return not is_nothing(before) and is_nothing(after) + + +def parent_change_type_of(children: list[Maybe[ChangeSetEntity]]): + change_types = [c.change_type for c in children if not is_nothing(c)] + if not change_types: + return ChangeType.UNCHANGED + first_type = change_types[0] + if all(ct == first_type for ct in change_types): + return first_type + return ChangeType.MODIFIED + + +def change_type_of(before: Maybe[Any], after: Maybe[Any], children: list[Maybe[ChangeSetEntity]]): + if is_created(before, after): + change_type = ChangeType.CREATED + elif is_removed(before, after): + change_type = ChangeType.REMOVED + else: + change_type = parent_change_type_of(children) + return change_type + + class Scope(str): _ROOT_SCOPE: Final[str] = str() _SEPARATOR: Final[str] = "/" @@ -66,14 +104,6 @@ class ChangeType(enum.Enum): def __str__(self): return self.value - def for_child(self, child_change_type: ChangeType) -> ChangeType: - if child_change_type == self: - return self - elif self == ChangeType.UNCHANGED: - return child_change_type - else: - return ChangeType.MODIFIED - class ChangeSetEntity(abc.ABC): scope: Final[Scope] @@ -122,13 +152,13 @@ class NodeTemplate(ChangeSetNode): def __init__( self, scope: Scope, - change_type: ChangeType, mappings: NodeMappings, parameters: NodeParameters, conditions: NodeConditions, resources: NodeResources, outputs: NodeOutputs, ): + change_type = parent_change_type_of([resources, outputs]) super().__init__(scope=scope, change_type=change_type) self.mappings = mappings self.parameters = parameters @@ -151,17 +181,17 @@ class NodeParameter(ChangeSetNode): name: Final[str] type_: Final[ChangeSetEntity] dynamic_value: Final[ChangeSetEntity] - default_value: Final[Optional[ChangeSetEntity]] + default_value: Final[Maybe[ChangeSetEntity]] def __init__( self, scope: Scope, - change_type: ChangeType, name: str, type_: ChangeSetEntity, dynamic_value: ChangeSetEntity, - default_value: Optional[ChangeSetEntity], + default_value: Maybe[ChangeSetEntity], ): + change_type = parent_change_type_of([type_, default_value, dynamic_value]) super().__init__(scope=scope, change_type=change_type) self.name = name self.type_ = type_ @@ -172,7 +202,8 @@ def __init__( class NodeParameters(ChangeSetNode): parameters: Final[list[NodeParameter]] - def __init__(self, scope: Scope, change_type: ChangeType, parameters: list[NodeParameter]): + def __init__(self, scope: Scope, parameters: list[NodeParameter]): + change_type = parent_change_type_of(parameters) super().__init__(scope=scope, change_type=change_type) self.parameters = parameters @@ -181,8 +212,8 @@ class NodeMapping(ChangeSetNode): name: Final[str] bindings: Final[NodeObject] - def __init__(self, scope: Scope, change_type: ChangeType, name: str, bindings: NodeObject): - super().__init__(scope=scope, change_type=change_type) + def __init__(self, scope: Scope, name: str, bindings: NodeObject): + super().__init__(scope=scope, change_type=bindings.change_type) self.name = name self.bindings = bindings @@ -190,7 +221,8 @@ def __init__(self, scope: Scope, change_type: ChangeType, name: str, bindings: N class NodeMappings(ChangeSetNode): mappings: Final[list[NodeMapping]] - def __init__(self, scope: Scope, change_type: ChangeType, mappings: list[NodeMapping]): + def __init__(self, scope: Scope, mappings: list[NodeMapping]): + change_type = parent_change_type_of(mappings) super().__init__(scope=scope, change_type=change_type) self.mappings = mappings @@ -198,18 +230,18 @@ def __init__(self, scope: Scope, change_type: ChangeType, mappings: list[NodeMap class NodeOutput(ChangeSetNode): name: Final[str] value: Final[ChangeSetEntity] - export: Final[Optional[ChangeSetEntity]] - condition_reference: Final[Optional[TerminalValue]] + export: Final[Maybe[ChangeSetEntity]] + condition_reference: Final[Maybe[TerminalValue]] def __init__( self, scope: Scope, - change_type: ChangeType, name: str, value: ChangeSetEntity, - export: Optional[ChangeSetEntity], - conditional_reference: Optional[TerminalValue], + export: Maybe[ChangeSetEntity], + conditional_reference: Maybe[TerminalValue], ): + change_type = parent_change_type_of([value, export, conditional_reference]) super().__init__(scope=scope, change_type=change_type) self.name = name self.value = value @@ -220,7 +252,8 @@ def __init__( class NodeOutputs(ChangeSetNode): outputs: Final[list[NodeOutput]] - def __init__(self, scope: Scope, change_type: ChangeType, outputs: list[NodeOutput]): + def __init__(self, scope: Scope, outputs: list[NodeOutput]): + change_type = parent_change_type_of(outputs) super().__init__(scope=scope, change_type=change_type) self.outputs = outputs @@ -229,8 +262,8 @@ class NodeCondition(ChangeSetNode): name: Final[str] body: Final[ChangeSetEntity] - def __init__(self, scope: Scope, change_type: ChangeType, name: str, body: ChangeSetEntity): - super().__init__(scope=scope, change_type=change_type) + def __init__(self, scope: Scope, name: str, body: ChangeSetEntity): + super().__init__(scope=scope, change_type=body.change_type) self.name = name self.body = body @@ -238,7 +271,8 @@ def __init__(self, scope: Scope, change_type: ChangeType, name: str, body: Chang class NodeConditions(ChangeSetNode): conditions: Final[list[NodeCondition]] - def __init__(self, scope: Scope, change_type: ChangeType, conditions: list[NodeCondition]): + def __init__(self, scope: Scope, conditions: list[NodeCondition]): + change_type = parent_change_type_of(conditions) super().__init__(scope=scope, change_type=change_type) self.conditions = conditions @@ -246,7 +280,8 @@ def __init__(self, scope: Scope, change_type: ChangeType, conditions: list[NodeC class NodeResources(ChangeSetNode): resources: Final[list[NodeResource]] - def __init__(self, scope: Scope, change_type: ChangeType, resources: list[NodeResource]): + def __init__(self, scope: Scope, resources: list[NodeResource]): + change_type = parent_change_type_of(resources) super().__init__(scope=scope, change_type=change_type) self.resources = resources @@ -254,9 +289,9 @@ def __init__(self, scope: Scope, change_type: ChangeType, resources: list[NodeRe class NodeResource(ChangeSetNode): name: Final[str] type_: Final[ChangeSetTerminal] - condition_reference: Final[Optional[TerminalValue]] properties: Final[NodeProperties] - depends_on: Final[Optional[NodeDependsOn]] + condition_reference: Final[Maybe[TerminalValue]] + depends_on: Final[Maybe[NodeDependsOn]] def __init__( self, @@ -265,8 +300,8 @@ def __init__( name: str, type_: ChangeSetTerminal, properties: NodeProperties, - condition_reference: Optional[TerminalValue], - depends_on: Optional[NodeDependsOn], + condition_reference: Maybe[TerminalValue], + depends_on: Maybe[NodeDependsOn], ): super().__init__(scope=scope, change_type=change_type) self.name = name @@ -279,7 +314,8 @@ def __init__( class NodeProperties(ChangeSetNode): properties: Final[list[NodeProperty]] - def __init__(self, scope: Scope, change_type: ChangeType, properties: list[NodeProperty]): + def __init__(self, scope: Scope, properties: list[NodeProperty]): + change_type = parent_change_type_of(properties) super().__init__(scope=scope, change_type=change_type) self.properties = properties @@ -287,8 +323,8 @@ def __init__(self, scope: Scope, change_type: ChangeType, properties: list[NodeP class NodeDependsOn(ChangeSetNode): depends_on: Final[NodeArray] - def __init__(self, scope: Scope, change_type: ChangeType, depends_on: NodeArray): - super().__init__(scope=scope, change_type=change_type) + def __init__(self, scope: Scope, depends_on: NodeArray): + super().__init__(scope=scope, change_type=depends_on.change_type) self.depends_on = depends_on @@ -296,8 +332,8 @@ class NodeProperty(ChangeSetNode): name: Final[str] value: Final[ChangeSetEntity] - def __init__(self, scope: Scope, change_type: ChangeType, name: str, value: ChangeSetEntity): - super().__init__(scope=scope, change_type=change_type) + def __init__(self, scope: Scope, name: str, value: ChangeSetEntity): + super().__init__(scope=scope, change_type=value.change_type) self.name = name self.value = value @@ -442,9 +478,9 @@ def _visit_terminal_value( terminal_value = self._visited_scopes.get(scope) if isinstance(terminal_value, TerminalValue): return terminal_value - if self._is_created(before=before_value, after=after_value): + if is_created(before=before_value, after=after_value): terminal_value = TerminalValueCreated(scope=scope, value=after_value) - elif self._is_removed(before=before_value, after=after_value): + elif is_removed(before=before_value, after=after_value): terminal_value = TerminalValueRemoved(scope=scope, value=before_value) elif before_value == after_value: terminal_value = TerminalValueUnchanged(scope=scope, value=before_value) @@ -468,9 +504,9 @@ def _visit_intrinsic_function( arguments = self._visit_value( scope=scope, before_value=before_arguments, after_value=after_arguments ) - if self._is_created(before=before_arguments, after=after_arguments): + if is_created(before=before_arguments, after=after_arguments): change_type = ChangeType.CREATED - elif self._is_removed(before=before_arguments, after=after_arguments): + elif is_removed(before=before_arguments, after=after_arguments): change_type = ChangeType.REMOVED else: function_name = intrinsic_function.replace("::", "_") @@ -588,8 +624,7 @@ def _resolve_intrinsic_function_fn_if(self, arguments: ChangeSetEntity) -> Chang ) if not isinstance(node_condition, NodeCondition): raise RuntimeError() - change_types = [node_condition.change_type, *arguments.array[1:]] - change_type = self._change_type_for_parent_of(change_types=change_types) + change_type = parent_change_type_of([node_condition, *arguments[1:]]) return change_type def _visit_array( @@ -604,12 +639,7 @@ def _visit_array( scope=value_scope, before_value=before_value, after_value=after_value ) array.append(value) - if self._is_created(before=before_array, after=after_array): - change_type = ChangeType.CREATED - elif self._is_removed(before=before_array, after=after_array): - change_type = ChangeType.REMOVED - else: - change_type = self._change_type_for_parent_of([value.change_type for value in array]) + change_type = change_type_of(before_array, after_array, array) return NodeArray(scope=scope, change_type=change_type, array=array) def _visit_object( @@ -618,12 +648,6 @@ def _visit_object( node_object = self._visited_scopes.get(scope) if isinstance(node_object, NodeObject): return node_object - if self._is_created(before=before_object, after=after_object): - change_type = ChangeType.CREATED - elif self._is_removed(before=before_object, after=after_object): - change_type = ChangeType.REMOVED - else: - change_type = ChangeType.UNCHANGED binding_names = self._safe_keys_of(before_object, after_object) bindings: dict[str, ChangeSetEntity] = dict() for binding_name in binding_names: @@ -634,7 +658,7 @@ def _visit_object( scope=binding_scope, before_value=before_value, after_value=after_value ) bindings[binding_name] = value - change_type = change_type.for_child(value.change_type) + change_type = change_type_of(before_object, after_object, list(bindings.values())) node_object = NodeObject(scope=scope, change_type=change_type, bindings=bindings) self._visited_scopes[scope] = node_object return node_object @@ -662,9 +686,9 @@ def _visit_value( unset = object() if before_type_name == after_type_name: dominant_value = before_value - elif self._is_created(before=before_value, after=after_value): + elif is_created(before=before_value, after=after_value): dominant_value = after_value - elif self._is_removed(before=before_value, after=after_value): + elif is_removed(before=before_value, after=after_value): dominant_value = before_value else: dominant_value = unset @@ -715,9 +739,7 @@ def _visit_property( value = self._visit_value( scope=scope, before_value=before_property, after_value=after_property ) - node_property = NodeProperty( - scope=scope, change_type=value.change_type, name=property_name, value=value - ) + node_property = NodeProperty(scope=scope, name=property_name, value=value) self._visited_scopes[scope] = node_property return node_property @@ -727,10 +749,8 @@ def _visit_properties( node_properties = self._visited_scopes.get(scope) if isinstance(node_properties, NodeProperties): return node_properties - # TODO: double check we are sure not to have this be a NodeObject property_names: list[str] = self._safe_keys_of(before_properties, after_properties) properties: list[NodeProperty] = list() - change_type = ChangeType.UNCHANGED for property_name in property_names: property_scope, (before_property, after_property) = self._safe_access_in( scope, property_name, before_properties, after_properties @@ -742,10 +762,7 @@ def _visit_properties( after_property=after_property, ) properties.append(property_) - change_type = change_type.for_child(property_.change_type) - node_properties = NodeProperties( - scope=scope, change_type=change_type, properties=properties - ) + node_properties = NodeProperties(scope=scope, properties=properties) self._visited_scopes[scope] = node_properties return node_properties @@ -767,13 +784,6 @@ def _visit_resource( if isinstance(node_resource, NodeResource): return node_resource - if self._is_created(before=before_resource, after=after_resource): - change_type = ChangeType.CREATED - elif self._is_removed(before=before_resource, after=after_resource): - change_type = ChangeType.REMOVED - else: - change_type = ChangeType.UNCHANGED - scope_type, (before_type, after_type) = self._safe_access_in( scope, TypeKey, before_resource, after_resource ) @@ -781,7 +791,7 @@ def _visit_resource( scope=scope_type, before_type=before_type, after_type=after_type ) - condition_reference = None + condition_reference = Nothing scope_condition, (before_condition, after_condition) = self._safe_access_in( scope, ConditionKey, before_resource, after_resource ) @@ -790,7 +800,7 @@ def _visit_resource( scope_condition, before_condition, after_condition ) - depends_on = None + depends_on = Nothing scope_depends_on, (before_depends_on, after_depends_on) = self._safe_access_in( scope, DependsOnKey, before_resource, after_resource ) @@ -807,10 +817,10 @@ def _visit_resource( before_properties=before_properties, after_properties=after_properties, ) - if properties.properties: - # Properties were defined in the before or after template, thus must play a role - # in affecting the change type of this resource. - change_type = change_type.for_child(properties.change_type) + + change_type = change_type_of( + before_resource, after_resource, [properties, condition_reference, depends_on] + ) node_resource = NodeResource( scope=scope, change_type=change_type, @@ -827,7 +837,6 @@ def _visit_resources( self, scope: Scope, before_resources: Maybe[dict], after_resources: Maybe[dict] ) -> NodeResources: # TODO: investigate type changes behavior. - change_type = ChangeType.UNCHANGED resources: list[NodeResource] = list() resource_names = self._safe_keys_of(before_resources, after_resources) for resource_name in resource_names: @@ -841,8 +850,7 @@ def _visit_resources( after_resource=after_resource, ) resources.append(resource) - change_type = change_type.for_child(resource.change_type) - return NodeResources(scope=scope, change_type=change_type, resources=resources) + return NodeResources(scope=scope, resources=resources) def _visit_mapping( self, scope: Scope, name: str, before_mapping: Maybe[dict], after_mapping: Maybe[dict] @@ -850,14 +858,11 @@ def _visit_mapping( bindings = self._visit_object( scope=scope, before_object=before_mapping, after_object=after_mapping ) - return NodeMapping( - scope=scope, change_type=bindings.change_type, name=name, bindings=bindings - ) + return NodeMapping(scope=scope, name=name, bindings=bindings) def _visit_mappings( self, scope: Scope, before_mappings: Maybe[dict], after_mappings: Maybe[dict] ) -> NodeMappings: - change_type = ChangeType.UNCHANGED mappings: list[NodeMapping] = list() mapping_names = self._safe_keys_of(before_mappings, after_mappings) for mapping_name in mapping_names: @@ -871,8 +876,7 @@ def _visit_mappings( after_mapping=after_mapping, ) mappings.append(mapping) - change_type = change_type.for_child(mapping.change_type) - return NodeMappings(scope=scope, change_type=change_type, mappings=mappings) + return NodeMappings(scope=scope, mappings=mappings) def _visit_dynamic_parameter(self, parameter_name: str) -> ChangeSetEntity: scope = Scope("Dynamic").open_scope("Parameters") @@ -907,13 +911,8 @@ def _visit_parameter( dynamic_value = self._visit_dynamic_parameter(parameter_name=parameter_name) - change_type = self._change_type_for_parent_of( - change_types=[type_.change_type, default_value.change_type, dynamic_value.change_type] - ) - node_parameter = NodeParameter( scope=scope, - change_type=change_type, name=parameter_name, type_=type_, default_value=default_value, @@ -930,7 +929,6 @@ def _visit_parameters( return node_parameters parameter_names: list[str] = self._safe_keys_of(before_parameters, after_parameters) parameters: list[NodeParameter] = list() - change_type = ChangeType.UNCHANGED for parameter_name in parameter_names: parameter_scope, (before_parameter, after_parameter) = self._safe_access_in( scope, parameter_name, before_parameters, after_parameters @@ -942,10 +940,7 @@ def _visit_parameters( after_parameter=after_parameter, ) parameters.append(parameter) - change_type = change_type.for_child(parameter.change_type) - node_parameters = NodeParameters( - scope=scope, change_type=change_type, parameters=parameters - ) + node_parameters = NodeParameters(scope=scope, parameters=parameters) self._visited_scopes[scope] = node_parameters return node_parameters @@ -976,9 +971,7 @@ def _visit_depends_on( node_array = self._visit_array( scope=scope, before_array=before_depends_on, after_array=after_depends_on ) - node_depends_on = NodeDependsOn( - scope=scope, change_type=node_array.change_type, depends_on=node_array - ) + node_depends_on = NodeDependsOn(scope=scope, depends_on=node_array) return node_depends_on def _visit_condition( @@ -994,9 +987,7 @@ def _visit_condition( body = self._visit_value( scope=scope, before_value=before_condition, after_value=after_condition ) - node_condition = NodeCondition( - scope=scope, change_type=body.change_type, name=condition_name, body=body - ) + node_condition = NodeCondition(scope=scope, name=condition_name, body=body) self._visited_scopes[scope] = node_condition return node_condition @@ -1008,7 +999,6 @@ def _visit_conditions( return node_conditions condition_names: list[str] = self._safe_keys_of(before_conditions, after_conditions) conditions: list[NodeCondition] = list() - change_type = ChangeType.UNCHANGED for condition_name in condition_names: condition_scope, (before_condition, after_condition) = self._safe_access_in( scope, condition_name, before_conditions, after_conditions @@ -1020,33 +1010,27 @@ def _visit_conditions( after_condition=after_condition, ) conditions.append(condition) - change_type = change_type.for_child(child_change_type=condition.change_type) - node_conditions = NodeConditions( - scope=scope, change_type=change_type, conditions=conditions - ) + node_conditions = NodeConditions(scope=scope, conditions=conditions) self._visited_scopes[scope] = node_conditions return node_conditions def _visit_output( self, scope: Scope, name: str, before_output: Maybe[dict], after_output: Maybe[dict] ) -> NodeOutput: - change_type = ChangeType.UNCHANGED scope_value, (before_value, after_value) = self._safe_access_in( scope, ValueKey, before_output, after_output ) value = self._visit_value(scope_value, before_value, after_value) - change_type = change_type.for_child(value.change_type) - export: Optional[ChangeSetEntity] = None + export: Maybe[ChangeSetEntity] = Nothing scope_export, (before_export, after_export) = self._safe_access_in( scope, ExportKey, before_output, after_output ) if before_export or after_export: export = self._visit_value(scope_export, before_export, after_export) - change_type = change_type.for_child(export.change_type) # TODO: condition references should be resolved for the condition's change_type? - condition_reference: Optional[TerminalValue] = None + condition_reference: Maybe[TerminalValue] = Nothing scope_condition, (before_condition, after_condition) = self._safe_access_in( scope, ConditionKey, before_output, after_output ) @@ -1054,11 +1038,9 @@ def _visit_output( condition_reference = self._visit_terminal_value( scope_condition, before_condition, after_condition ) - change_type = change_type.for_child(condition_reference.change_type) return NodeOutput( scope=scope, - change_type=change_type, name=name, value=value, export=export, @@ -1068,7 +1050,6 @@ def _visit_output( def _visit_outputs( self, scope: Scope, before_outputs: Maybe[dict], after_outputs: Maybe[dict] ) -> NodeOutputs: - change_type = ChangeType.UNCHANGED outputs: list[NodeOutput] = list() output_names: list[str] = self._safe_keys_of(before_outputs, after_outputs) for output_name in output_names: @@ -1082,8 +1063,7 @@ def _visit_outputs( after_output=after_output, ) outputs.append(output) - change_type = change_type.for_child(output.change_type) - return NodeOutputs(scope=scope, change_type=change_type, outputs=outputs) + return NodeOutputs(scope=scope, outputs=outputs) def _model(self, before_template: Maybe[dict], after_template: Maybe[dict]) -> NodeTemplate: root_scope = Scope() @@ -1133,7 +1113,6 @@ def _model(self, before_template: Maybe[dict], after_template: Maybe[dict]) -> N # TODO: compute the change_type of the template properly. return NodeTemplate( scope=root_scope, - change_type=resources.change_type, mappings=mappings, parameters=parameters, conditions=conditions, @@ -1141,7 +1120,7 @@ def _model(self, before_template: Maybe[dict], after_template: Maybe[dict]) -> N outputs=outputs, ) - def _retrieve_condition_if_exists(self, condition_name: str) -> Optional[NodeCondition]: + def _retrieve_condition_if_exists(self, condition_name: str) -> Maybe[NodeCondition]: conditions_scope, (before_conditions, after_conditions) = self._safe_access_in( Scope(), ConditionsKey, self._before_template, self._after_template ) @@ -1158,14 +1137,12 @@ def _retrieve_condition_if_exists(self, condition_name: str) -> Optional[NodeCon after_condition=after_condition, ) return node_condition - return None + return Nothing - def _retrieve_parameter_if_exists(self, parameter_name: str) -> Optional[NodeParameter]: + def _retrieve_parameter_if_exists(self, parameter_name: str) -> Maybe[NodeParameter]: parameters_scope, (before_parameters, after_parameters) = self._safe_access_in( Scope(), ParametersKey, self._before_template, self._after_template ) - before_parameters = before_parameters or dict() - after_parameters = after_parameters or dict() if parameter_name in before_parameters or parameter_name in after_parameters: parameter_scope, (before_parameter, after_parameter) = self._safe_access_in( parameters_scope, parameter_name, before_parameters, after_parameters @@ -1177,15 +1154,13 @@ def _retrieve_parameter_if_exists(self, parameter_name: str) -> Optional[NodePar after_parameter=after_parameter, ) return node_parameter - return None + return Nothing def _retrieve_mapping(self, mapping_name) -> NodeMapping: # TODO: add caching mechanism, and raise appropriate error if missing. scope_mappings, (before_mappings, after_mappings) = self._safe_access_in( Scope(), MappingsKey, self._before_template, self._after_template ) - before_mappings = before_mappings or dict() - after_mappings = after_mappings or dict() if mapping_name in before_mappings or mapping_name in after_mappings: scope_mapping, (before_mapping, after_mapping) = self._safe_access_in( scope_mappings, mapping_name, before_mappings, after_mappings @@ -1242,15 +1217,6 @@ def _safe_keys_of(*objects: Maybe[dict]) -> list[str]: keys = sorted(key_set) return keys - @staticmethod - def _change_type_for_parent_of(change_types: list[ChangeType]) -> ChangeType: - parent_change_type = ChangeType.UNCHANGED - for child_change_type in change_types: - parent_change_type = parent_change_type.for_child(child_change_type) - if parent_change_type == ChangeType.MODIFIED: - break - return parent_change_type - @staticmethod def _name_if_intrinsic_function(value: Maybe[Any]) -> Optional[str]: if isinstance(value, dict): @@ -1279,11 +1245,3 @@ def _is_object(value: Any) -> bool: @staticmethod def _is_array(value: Any) -> bool: return isinstance(value, list) - - @staticmethod - def _is_created(before: Maybe[Any], after: Maybe[Any]) -> bool: - return isinstance(before, NothingType) and not isinstance(after, NothingType) - - @staticmethod - def _is_removed(before: Maybe[Any], after: Maybe[Any]) -> bool: - return not isinstance(before, NothingType) and isinstance(after, NothingType) diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_describer.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_describer.py index d7291ca44864d..e58c71f6a4757 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_describer.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_describer.py @@ -8,7 +8,9 @@ NodeIntrinsicFunction, NodeProperty, NodeResource, + Nothing, PropertiesKey, + is_nothing, ) from localstack.services.cloudformation.engine.v2.change_set_model_preproc import ( ChangeSetModelPreproc, @@ -53,8 +55,8 @@ def visit_node_intrinsic_function_fn_get_att( if isinstance(after_argument, str): after_argument = after_argument.split(".") - before = None - if before_argument: + before = Nothing + if not is_nothing(before_argument): before_logical_name_of_resource = before_argument[0] before_attribute_name = before_argument[1] before_node_resource = self._get_node_resource_for( @@ -72,8 +74,8 @@ def visit_node_intrinsic_function_fn_get_att( property_name=before_attribute_name, ) - after = None - if after_argument: + after = Nothing + if not is_nothing(after_argument): after_logical_name_of_resource = after_argument[0] after_attribute_name = after_argument[1] after_node_resource = self._get_node_resource_for( @@ -154,7 +156,7 @@ def _describe_resource_change( if before == after: # unchanged: nothing to do. return - if before is not None and after is not None: + if not is_nothing(before) and not is_nothing(after): # Case: change on same type. if before.resource_type == after.resource_type: # Register a Modified if changed. @@ -184,7 +186,7 @@ def _describe_resource_change( before_properties=None, after_properties=after.properties, ) - elif before is not None: + elif not is_nothing(before): # Case: removal self._register_resource_change( logical_id=name, @@ -193,7 +195,7 @@ def _describe_resource_change( before_properties=before.properties, after_properties=None, ) - elif after is not None: + elif not is_nothing(after): # Case: addition self._register_resource_change( logical_id=name, diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_executor.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_executor.py index 93f2902ddc979..8388e678d207c 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_executor.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_executor.py @@ -11,6 +11,7 @@ NodeOutput, NodeParameter, NodeResource, + is_nothing, ) from localstack.services.cloudformation.engine.v2.change_set_model_preproc import ( ChangeSetModelPreproc, @@ -113,13 +114,13 @@ def visit_node_resource( # There are no updates for this resource; iff the resource was previously # deployed, then the resolved details are copied in the current state for # references or other downstream operations. - if before is not None: + if not is_nothing(before): before_logical_id = delta.before.logical_id before_resource = self._before_resolved_resources.get(before_logical_id, dict()) self.resources[before_logical_id] = before_resource # Update the latest version of this resource for downstream references. - if after is not None: + if not is_nothing(after): after_logical_id = after.logical_id after_physical_id: str = self._after_resource_physical_id( resource_logical_id=after_logical_id @@ -132,7 +133,7 @@ def visit_node_output( ) -> PreprocEntityDelta[PreprocOutput, PreprocOutput]: delta = super().visit_node_output(node_output=node_output) after = delta.after - if after is None or (isinstance(after, PreprocOutput) and after.condition is False): + if is_nothing(after) or (isinstance(after, PreprocOutput) and after.condition is False): return delta self.outputs[delta.after.name] = delta.after.value return delta @@ -142,7 +143,7 @@ def _execute_resource_change( ) -> None: # Changes are to be made about this resource. # TODO: this logic is a POC and should be revised. - if before is not None and after is not None: + if not is_nothing(before) and not is_nothing(after): # Case: change on same type. if before.resource_type == after.resource_type: # Register a Modified if changed. @@ -177,7 +178,7 @@ def _execute_resource_change( before_properties=None, after_properties=after.properties, ) - elif before is not None: + elif not is_nothing(before): # Case: removal # XXX hacky, stick the previous resources' properties into the payload # XXX hacky, stick the previous resources' properties into the payload @@ -190,7 +191,7 @@ def _execute_resource_change( before_properties=before_properties, after_properties=None, ) - elif after is not None: + elif not is_nothing(after): # Case: addition self._execute_resource_action( action=ChangeAction.Add, diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py index 8b8aebca21cac..77fc70a352dc4 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py @@ -11,6 +11,7 @@ from localstack.services.cloudformation.engine.v2.change_set_model import ( ChangeSetEntity, ChangeType, + Maybe, NodeArray, NodeCondition, NodeDependsOn, @@ -25,12 +26,14 @@ NodeProperty, NodeResource, NodeTemplate, + Nothing, Scope, TerminalValue, TerminalValueCreated, TerminalValueModified, TerminalValueRemoved, TerminalValueUnchanged, + is_nothing, ) from localstack.services.cloudformation.engine.v2.change_set_model_visitor import ( ChangeSetModelVisitor, @@ -58,10 +61,10 @@ class PreprocEntityDelta(Generic[TBefore, TAfter]): - before: Optional[TBefore] - after: Optional[TAfter] + before: Maybe[TBefore] + after: Maybe[TAfter] - def __init__(self, before: Optional[TBefore] = None, after: Optional[TAfter] = None): + def __init__(self, before: Maybe[TBefore] = Nothing, after: Maybe[TAfter] = Nothing): self.before = before self.after = after @@ -200,7 +203,6 @@ def _deployed_property_value_of( _ = self._get_node_resource_for( resource_name=resource_logical_id, node_template=self._node_template ) - resolved_resource = resolved_resources.get(resource_logical_id) if resolved_resource is None: raise RuntimeError( @@ -237,26 +239,25 @@ def _get_node_mapping(self, map_name: str) -> NodeMapping: if mapping.name == map_name: self.visit(mapping) return mapping - # TODO - raise RuntimeError() + raise RuntimeError(f"Undefined '{map_name}' mapping") - def _get_node_parameter_if_exists(self, parameter_name: str) -> Optional[NodeParameter]: + def _get_node_parameter_if_exists(self, parameter_name: str) -> Maybe[NodeParameter]: parameters: list[NodeParameter] = self._node_template.parameters.parameters # TODO: another scenarios suggesting property lookups might be preferable. for parameter in parameters: if parameter.name == parameter_name: self.visit(parameter) return parameter - return None + return Nothing - def _get_node_condition_if_exists(self, condition_name: str) -> Optional[NodeCondition]: + def _get_node_condition_if_exists(self, condition_name: str) -> Maybe[NodeCondition]: conditions: list[NodeCondition] = self._node_template.conditions.conditions # TODO: another scenarios suggesting property lookups might be preferable. for condition in conditions: if condition.name == condition_name: self.visit(condition) return condition - return None + return Nothing def _resolve_condition(self, logical_id: str) -> PreprocEntityDelta: node_condition = self._get_node_condition_if_exists(condition_name=logical_id) @@ -280,14 +281,9 @@ def _resolve_pseudo_parameter(self, pseudo_parameter_name: str) -> Any: case "AWS::URLSuffix": return _AWS_URL_SUFFIX case "AWS::NoValue": - # TODO: add support for NoValue, None cannot be used to communicate a Null value in preproc classes. - raise NotImplementedError("The use of AWS:NoValue is currently unsupported") - case "AWS::NotificationARNs": - raise NotImplementedError( - "The use of AWS::NotificationARNs is currently unsupported" - ) + return None case _: - raise RuntimeError(f"Unknown pseudo parameter value '{pseudo_parameter_name}'") + raise RuntimeError(f"The use of '{pseudo_parameter_name}' is currently unsupported") def _resolve_reference(self, logical_id: str) -> PreprocEntityDelta: if logical_id in _PSEUDO_PARAMETERS: @@ -323,11 +319,12 @@ def _resolve_mapping( return mapping_value_delta def visit(self, change_set_entity: ChangeSetEntity) -> PreprocEntityDelta: - delta = self._processed.get(change_set_entity.scope) - if delta is not None: + scope = change_set_entity.scope + if scope in self._processed: + delta = self._processed[scope] return delta delta = super().visit(change_set_entity=change_set_entity) - self._processed[change_set_entity.scope] = delta + self._processed[scope] = delta return delta def visit_terminal_value_modified( @@ -362,21 +359,17 @@ def visit_node_divergence(self, node_divergence: NodeDivergence) -> PreprocEntit return PreprocEntityDelta(before=before_delta.before, after=after_delta.after) def visit_node_object(self, node_object: NodeObject) -> PreprocEntityDelta: - before = dict() - after = dict() + node_change_type = node_object.change_type + before = dict() if node_change_type != ChangeType.CREATED else Nothing + after = dict() if node_change_type != ChangeType.REMOVED else Nothing for name, change_set_entity in node_object.bindings.items(): delta: PreprocEntityDelta = self.visit(change_set_entity=change_set_entity) - match change_set_entity.change_type: - case ChangeType.MODIFIED: - before[name] = delta.before - after[name] = delta.after - case ChangeType.CREATED: - after[name] = delta.after - case ChangeType.REMOVED: - before[name] = delta.before - case ChangeType.UNCHANGED: - before[name] = delta.before - after[name] = delta.before + delta_before = delta.before + delta_after = delta.after + if not is_nothing(before) and not is_nothing(delta_before) and delta_before is not None: + before[name] = delta_before + if not is_nothing(after) and not is_nothing(delta_after) and delta_after is not None: + after[name] = delta_after return PreprocEntityDelta(before=before, after=after) def visit_node_intrinsic_function_fn_get_att( @@ -384,14 +377,14 @@ def visit_node_intrinsic_function_fn_get_att( ) -> PreprocEntityDelta: # TODO: validate the return value according to the spec. arguments_delta = self.visit(node_intrinsic_function.arguments) - before_argument: Optional[list[str]] = arguments_delta.before + before_argument: Maybe[list[str]] = arguments_delta.before if isinstance(before_argument, str): before_argument = before_argument.split(".") - after_argument: Optional[list[str]] = arguments_delta.after + after_argument: Maybe[list[str]] = arguments_delta.after if isinstance(after_argument, str): after_argument = after_argument.split(".") - before = None + before = Nothing if before_argument: before_logical_name_of_resource = before_argument[0] before_attribute_name = before_argument[1] @@ -414,7 +407,7 @@ def visit_node_intrinsic_function_fn_get_att( property_name=before_attribute_name, ) - after = None + after = Nothing if after_argument: after_logical_name_of_resource = after_argument[0] after_attribute_name = after_argument[1] @@ -444,10 +437,10 @@ def visit_node_intrinsic_function_fn_equals( arguments_delta = self.visit(node_intrinsic_function.arguments) before_values = arguments_delta.before after_values = arguments_delta.after - before = None + before = Nothing if before_values: before = before_values[0] == before_values[1] - after = None + after = Nothing if after_values: after = after_values[0] == after_values[1] return PreprocEntityDelta(before=before, after=after) @@ -456,6 +449,8 @@ def visit_node_intrinsic_function_fn_if( self, node_intrinsic_function: NodeIntrinsicFunction ) -> PreprocEntityDelta: arguments_delta = self.visit(node_intrinsic_function.arguments) + arguments_before = arguments_delta.before + arguments_after = arguments_delta.after def _compute_delta_for_if_statement(args: list[Any]) -> PreprocEntityDelta: condition_name = args[0] @@ -466,13 +461,13 @@ def _compute_delta_for_if_statement(args: list[Any]) -> PreprocEntityDelta: ) # TODO: add support for this being created or removed. - before = None - if arguments_delta.before: - before_outcome_delta = _compute_delta_for_if_statement(arguments_delta.before) + before = Nothing + if not is_nothing(arguments_before): + before_outcome_delta = _compute_delta_for_if_statement(arguments_before) before = before_outcome_delta.before - after = None - if arguments_delta.after: - after_outcome_delta = _compute_delta_for_if_statement(arguments_delta.after) + after = Nothing + if not is_nothing(arguments_after): + after_outcome_delta = _compute_delta_for_if_statement(arguments_after) after = after_outcome_delta.after return PreprocEntityDelta(before=before, after=after) @@ -482,17 +477,14 @@ def visit_node_intrinsic_function_fn_not( arguments_delta = self.visit(node_intrinsic_function.arguments) before_condition = arguments_delta.before after_condition = arguments_delta.after - if before_condition: + before = Nothing + if not is_nothing(before_condition): before_condition_outcome = before_condition[0] before = not before_condition_outcome - else: - before = None - - if after_condition: + after = Nothing + if not is_nothing(after_condition): after_condition_outcome = after_condition[0] after = not after_condition_outcome - else: - after = None # Implicit change type computation. return PreprocEntityDelta(before=before, after=after) @@ -560,11 +552,11 @@ def visit_node_intrinsic_function_fn_transform( # TODO: add tests to review the behaviour of CFN with changes to transformation # function code and no changes to the template. - before = None - if arguments_before: + before = Nothing + if not is_nothing(arguments_before): before = self._compute_fn_transform(args=arguments_before) - after = None - if arguments_after: + after = Nothing + if not is_nothing(arguments_after): after = self._compute_fn_transform(args=arguments_after) return PreprocEntityDelta(before=before, after=after) @@ -606,9 +598,9 @@ def _compute_sub(args: str | list[Any], select_before: bool = False) -> str: template_variable_value = sub_parameters[template_variable_name] else: try: - reference_delta = self._resolve_reference(logical_id=template_variable_name) + resource_delta = self._resolve_reference(logical_id=template_variable_name) template_variable_value = ( - reference_delta.before if select_before else reference_delta.after + resource_delta.before if select_before else resource_delta.after ) if isinstance(template_variable_value, PreprocResource): template_variable_value = template_variable_value.logical_id @@ -621,19 +613,11 @@ def _compute_sub(args: str | list[Any], select_before: bool = False) -> str: ) return sub_string - before = None - if ( - isinstance(arguments_before, str) - or isinstance(arguments_before, list) - and len(arguments_before) == 2 - ): + before = Nothing + if not is_nothing(arguments_before): before = _compute_sub(args=arguments_before, select_before=True) - after = None - if ( - isinstance(arguments_after, str) - or isinstance(arguments_after, list) - and len(arguments_after) == 2 - ): + after = Nothing + if not is_nothing(arguments_after): after = _compute_sub(args=arguments_after) return PreprocEntityDelta(before=before, after=after) @@ -654,10 +638,10 @@ def _compute_join(args: list[Any]) -> str: join_result = delimiter.join(map(str, values)) return join_result - before = None + before = Nothing if isinstance(arguments_before, list) and len(arguments_before) == 2: before = _compute_join(arguments_before) - after = None + after = Nothing if isinstance(arguments_after, list) and len(arguments_after) == 2: after = _compute_join(arguments_after) return PreprocEntityDelta(before=before, after=after) @@ -669,16 +653,14 @@ def visit_node_intrinsic_function_fn_find_in_map( arguments_delta = self.visit(node_intrinsic_function.arguments) before_arguments = arguments_delta.before after_arguments = arguments_delta.after + before = Nothing if before_arguments: before_value_delta = self._resolve_mapping(*before_arguments) before = before_value_delta.before - else: - before = None + after = Nothing if after_arguments: after_value_delta = self._resolve_mapping(*after_arguments) after = after_value_delta.after - else: - after = None return PreprocEntityDelta(before=before, after=after) def visit_node_mapping(self, node_mapping: NodeMapping) -> PreprocEntityDelta: @@ -733,15 +715,15 @@ def visit_node_intrinsic_function_ref( after_logical_id = arguments_delta.after # TODO: extend this to support references to other types. - before = None - if before_logical_id is not None: + before = Nothing + if not is_nothing(before_logical_id): before_delta = self._resolve_reference(logical_id=before_logical_id) before = before_delta.before if isinstance(before, PreprocResource): before = before.physical_resource_id - after = None - if after_logical_id is not None: + after = Nothing + if not is_nothing(after_logical_id): after_delta = self._resolve_reference(logical_id=after_logical_id) after = after_delta.after if isinstance(after, PreprocResource): @@ -750,14 +732,17 @@ def visit_node_intrinsic_function_ref( return PreprocEntityDelta(before=before, after=after) def visit_node_array(self, node_array: NodeArray) -> PreprocEntityDelta: - before = list() - after = list() + node_change_type = node_array.change_type + before = list() if node_change_type != ChangeType.CREATED else Nothing + after = list() if node_change_type != ChangeType.REMOVED else Nothing for change_set_entity in node_array.array: delta: PreprocEntityDelta = self.visit(change_set_entity=change_set_entity) - if delta.before is not None: - before.append(delta.before) - if delta.after is not None: - after.append(delta.after) + delta_before = delta.before + delta_after = delta.after + if not is_nothing(before) and not is_nothing(delta_before): + before.append(delta_before) + if not is_nothing(after) and not is_nothing(delta_after): + after.append(delta_after) return PreprocEntityDelta(before=before, after=after) def visit_node_property(self, node_property: NodeProperty) -> PreprocEntityDelta: @@ -766,29 +751,44 @@ def visit_node_property(self, node_property: NodeProperty) -> PreprocEntityDelta def visit_node_properties( self, node_properties: NodeProperties ) -> PreprocEntityDelta[PreprocProperties, PreprocProperties]: - before_bindings: dict[str, Any] = dict() - after_bindings: dict[str, Any] = dict() + node_change_type = node_properties.change_type + before_bindings = dict() if node_change_type != ChangeType.CREATED else Nothing + after_bindings = dict() if node_change_type != ChangeType.REMOVED else Nothing for node_property in node_properties.properties: - delta = self.visit(node_property) property_name = node_property.name - if node_property.change_type != ChangeType.CREATED: - before_bindings[property_name] = delta.before - if node_property.change_type != ChangeType.REMOVED: - after_bindings[property_name] = delta.after - before = PreprocProperties(properties=before_bindings) - after = PreprocProperties(properties=after_bindings) + delta = self.visit(node_property) + delta_before = delta.before + delta_after = delta.after + if ( + not is_nothing(before_bindings) + and not is_nothing(delta_before) + and delta_before is not None + ): + before_bindings[property_name] = delta_before + if ( + not is_nothing(after_bindings) + and not is_nothing(delta_after) + and delta_after is not None + ): + after_bindings[property_name] = delta_after + before = Nothing + if not is_nothing(before_bindings): + before = PreprocProperties(properties=before_bindings) + after = Nothing + if not is_nothing(after_bindings): + after = PreprocProperties(properties=after_bindings) return PreprocEntityDelta(before=before, after=after) def _resolve_resource_condition_reference(self, reference: TerminalValue) -> PreprocEntityDelta: reference_delta = self.visit(reference) before_reference = reference_delta.before - before = None - if before_reference is not None: + before = Nothing + if isinstance(before_reference, str): before_delta = self._resolve_condition(logical_id=before_reference) before = before_delta.before - after = None + after = Nothing after_reference = reference_delta.after - if after_reference is not None: + if isinstance(after_reference, str): after_delta = self._resolve_condition(logical_id=after_reference) after = after_delta.after return PreprocEntityDelta(before=before, after=after) @@ -797,19 +797,19 @@ def visit_node_resource( self, node_resource: NodeResource ) -> PreprocEntityDelta[PreprocResource, PreprocResource]: change_type = node_resource.change_type - condition_before = None - condition_after = None - if node_resource.condition_reference is not None: + condition_before = Nothing + condition_after = Nothing + if not is_nothing(node_resource.condition_reference): condition_delta = self._resolve_resource_condition_reference( node_resource.condition_reference ) condition_before = condition_delta.before condition_after = condition_delta.after - depends_on_before = None - depends_on_after = None - if node_resource.depends_on is not None: - depends_on_delta = self.visit_node_depends_on(node_resource.depends_on) + depends_on_before = Nothing + depends_on_after = Nothing + if not is_nothing(node_resource.depends_on): + depends_on_delta = self.visit(node_resource.depends_on) depends_on_before = depends_on_delta.before depends_on_after = depends_on_delta.after @@ -818,9 +818,9 @@ def visit_node_resource( node_resource.properties ) - before = None - after = None - if change_type != ChangeType.CREATED and condition_before is None or condition_before: + before = Nothing + after = Nothing + if change_type != ChangeType.CREATED and is_nothing(condition_before) or condition_before: logical_resource_id = node_resource.name before_physical_resource_id = self._before_resource_physical_id( resource_logical_id=logical_resource_id @@ -833,7 +833,7 @@ def visit_node_resource( properties=properties_delta.before, depends_on=depends_on_before, ) - if change_type != ChangeType.REMOVED and condition_after is None or condition_after: + if change_type != ChangeType.REMOVED and is_nothing(condition_after) or condition_after: logical_resource_id = node_resource.name try: after_physical_resource_id = self._after_resource_physical_id( @@ -857,8 +857,8 @@ def visit_node_output( change_type = node_output.change_type value_delta = self.visit(node_output.value) - condition_delta = None - if node_output.condition_reference is not None: + condition_delta = Nothing + if not is_nothing(node_output.condition_reference): condition_delta = self._resolve_resource_condition_reference( node_output.condition_reference ) @@ -869,11 +869,11 @@ def visit_node_output( elif condition_before and not condition_after: change_type = ChangeType.REMOVED - export_delta = None - if node_output.export is not None: + export_delta = Nothing + if not is_nothing(node_output.export): export_delta = self.visit(node_output.export) - before: Optional[PreprocOutput] = None + before: Maybe[PreprocOutput] = Nothing if change_type != ChangeType.CREATED: before = PreprocOutput( name=node_output.name, @@ -881,7 +881,7 @@ def visit_node_output( export=export_delta.before if export_delta else None, condition=condition_delta.before if condition_delta else None, ) - after: Optional[PreprocOutput] = None + after: Maybe[PreprocOutput] = Nothing if change_type != ChangeType.REMOVED: after = PreprocOutput( name=node_output.name, @@ -900,8 +900,8 @@ def visit_node_outputs( output_delta: PreprocEntityDelta[PreprocOutput, PreprocOutput] = self.visit(node_output) output_before = output_delta.before output_after = output_delta.after - if output_before: + if not is_nothing(output_before): before.append(output_before) - if output_after: + if not is_nothing(output_after): after.append(output_after) return PreprocEntityDelta(before=before, after=after) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py index 8005d1a711607..acc7589c2f192 100644 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py +++ b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py @@ -68,7 +68,6 @@ def test_simple_condition_evaluation_doesnt_deploy_resource( t for t in aws_client.sns.list_topics()["Topics"] if topic_name in t["TopicArn"] ] == [] - @pytest.mark.skip(reason="CFNV2:AWS::NoValue") @pytest.mark.parametrize( "should_set_custom_name", ["yep", "nope"], diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py index de1b0029fb703..c327159aa958d 100644 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py +++ b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py @@ -249,6 +249,7 @@ def test_mapping_ref_map_key(self, deploy_cfn_template, aws_client, map_key, sho aws_client.sns.get_topic_attributes(TopicArn=topic_arn) + # @pytest.mark.skip(reason="CFNV2:Mappings") @markers.aws.validated def test_aws_refs_in_mappings(self, deploy_cfn_template, account_id): """ diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py index 3b86d1132c224..3310beaca3f7d 100644 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py +++ b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py @@ -16,7 +16,7 @@ class TestCdkInit: - @pytest.mark.skip(reason="CFNV2:Fn::Join on empty string args; CFNV2:AWS::NoValue unsupported") + @pytest.mark.skip(reason="CFNV2:Fn::Join on empty string args") @pytest.mark.parametrize("bootstrap_version", ["10", "11", "12"]) @markers.aws.validated def test_cdk_bootstrap(self, deploy_cfn_template, bootstrap_version, aws_client): diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py index cdf24c4c46dee..0f9248f73f2f7 100644 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py +++ b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py @@ -94,7 +94,6 @@ def test_default_name_for_table(deploy_cfn_template, snapshot, aws_client): snapshot.match("list_tags_of_resource", list_tags) -@pytest.mark.skip(reason="CFNV2:AWS::NoValue") @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py index 12ee65980140a..fbed82fbf69e9 100644 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py +++ b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py @@ -63,7 +63,6 @@ def test_cfn_handle_secretsmanager_secret(deploy_cfn_template, aws_client, snaps # snapshot.match("exception", ex.value.response) -@pytest.mark.skip(reason="CFNV2:AWS::NoValue") @markers.aws.validated @pytest.mark.parametrize("block_public_policy", ["true", "default"]) def test_cfn_secret_policy(deploy_cfn_template, block_public_policy, aws_client, snapshot): diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py index a9e88cbc24d96..5719f42f24081 100644 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py +++ b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py @@ -84,7 +84,7 @@ def test_sns_subscription(deploy_cfn_template, aws_client): assert len(subscriptions["Subscriptions"]) > 0 -@pytest.mark.skip(reason="CFNV2:AWS::NoValue") +@pytest.mark.skip(reason="CFNV2:Other") @markers.aws.validated def test_deploy_stack_with_sns_topic(deploy_cfn_template, aws_client): stack = deploy_cfn_template( diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py index b7a53e27a498f..0f76b40282c52 100644 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py +++ b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py @@ -27,7 +27,6 @@ def test_sqs_queue_policy(deploy_cfn_template, aws_client, snapshot): snapshot.add_transformer(snapshot.transform.key_value("Resource")) -@pytest.mark.skip(reason="CFNV2:AWS::NoValue") @markers.aws.validated def test_sqs_fifo_queue_generates_valid_name(deploy_cfn_template): result = deploy_cfn_template( @@ -40,7 +39,6 @@ def test_sqs_fifo_queue_generates_valid_name(deploy_cfn_template): assert ".fifo" in result.outputs["FooQueueName"] -@pytest.mark.skip(reason="CFNV2:AWS::NoValue") @markers.aws.validated def test_sqs_non_fifo_queue_generates_valid_name(deploy_cfn_template): result = deploy_cfn_template( diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py b/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py index a07843e3b9e5e..57f293f9f3c35 100644 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py +++ b/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py @@ -277,7 +277,7 @@ def test_sub_number_type(self, deploy_cfn_template): assert stack.outputs["Threshold"] == threshold assert stack.outputs["Period"] == period - @pytest.mark.skip(reason="CFNV2:AWS::NoValue") + @pytest.mark.skip(reason="CFNV2:Fn::Join") @markers.aws.validated def test_join_no_value_construct(self, deploy_cfn_template, snapshot, aws_client): stack = deploy_cfn_template(