localstack-core 4.3.1.dev6__py3-none-any.whl → 4.3.1.dev27__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- localstack/services/cloudformation/engine/entities.py +18 -1
- localstack/services/cloudformation/engine/template_deployer.py +0 -9
- localstack/services/cloudformation/engine/v2/change_set_model.py +164 -35
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +143 -69
- localstack/services/cloudformation/engine/v2/change_set_model_executor.py +170 -0
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +8 -0
- localstack/services/cloudformation/v2/provider.py +72 -6
- localstack/services/ec2/patches.py +31 -3
- localstack/services/kms/models.py +1 -1
- localstack/services/lambda_/event_source_mapping/pollers/dynamodb_poller.py +2 -0
- localstack/services/lambda_/event_source_mapping/pollers/kinesis_poller.py +2 -0
- localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +4 -2
- localstack/services/lambda_/invocation/assignment.py +4 -2
- localstack/services/lambda_/invocation/execution_environment.py +16 -4
- localstack/services/lambda_/invocation/logs.py +28 -4
- localstack/services/lambda_/provider.py +18 -3
- localstack/services/lambda_/runtimes.py +15 -2
- localstack/services/s3/presigned_url.py +15 -11
- localstack/services/secretsmanager/provider.py +13 -4
- localstack/services/sqs/models.py +22 -3
- localstack/services/sqs/utils.py +16 -7
- localstack/services/ssm/resource_providers/aws_ssm_parameter.py +1 -5
- localstack/services/stepfunctions/asl/utils/json_path.py +9 -0
- localstack/testing/snapshots/transformer_utility.py +13 -0
- localstack/utils/aws/client_types.py +8 -0
- localstack/utils/docker_utils.py +2 -2
- localstack/version.py +2 -2
- {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev27.dist-info}/METADATA +3 -3
- {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev27.dist-info}/RECORD +37 -36
- localstack_core-4.3.1.dev27.dist-info/plux.json +1 -0
- localstack_core-4.3.1.dev6.dist-info/plux.json +0 -1
- {localstack_core-4.3.1.dev6.data → localstack_core-4.3.1.dev27.data}/scripts/localstack +0 -0
- {localstack_core-4.3.1.dev6.data → localstack_core-4.3.1.dev27.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.3.1.dev6.data → localstack_core-4.3.1.dev27.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev27.dist-info}/WHEEL +0 -0
- {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev27.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev27.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev27.dist-info}/top_level.txt +0 -0
@@ -297,6 +297,10 @@ class Stack:
|
|
297
297
|
"""Return dict of resources"""
|
298
298
|
return dict(self.template_resources)
|
299
299
|
|
300
|
+
@resources.setter
|
301
|
+
def resources(self, resources: dict):
|
302
|
+
self.template["Resources"] = resources
|
303
|
+
|
300
304
|
@property
|
301
305
|
def template_resources(self):
|
302
306
|
return self.template.setdefault("Resources", {})
|
@@ -370,8 +374,17 @@ class Stack:
|
|
370
374
|
# TODO: what functionality of the Stack object do we rely on here?
|
371
375
|
class StackChangeSet(Stack):
|
372
376
|
update_graph: NodeTemplate | None
|
377
|
+
change_set_type: ChangeSetType | None
|
373
378
|
|
374
|
-
def __init__(
|
379
|
+
def __init__(
|
380
|
+
self,
|
381
|
+
account_id: str,
|
382
|
+
region_name: str,
|
383
|
+
stack: Stack,
|
384
|
+
params=None,
|
385
|
+
template=None,
|
386
|
+
change_set_type: ChangeSetType | None = None,
|
387
|
+
):
|
375
388
|
if template is None:
|
376
389
|
template = {}
|
377
390
|
if params is None:
|
@@ -389,6 +402,7 @@ class StackChangeSet(Stack):
|
|
389
402
|
self.stack = stack
|
390
403
|
self.metadata["StackId"] = stack.stack_id
|
391
404
|
self.metadata["Status"] = "CREATE_PENDING"
|
405
|
+
self.change_set_type = change_set_type
|
392
406
|
|
393
407
|
@property
|
394
408
|
def change_set_id(self):
|
@@ -412,5 +426,8 @@ class StackChangeSet(Stack):
|
|
412
426
|
change_set_model = ChangeSetModel(
|
413
427
|
before_template=before_template,
|
414
428
|
after_template=after_template,
|
429
|
+
# TODO
|
430
|
+
before_parameters=None,
|
431
|
+
after_parameters=None,
|
415
432
|
)
|
416
433
|
self.update_graph = change_set_model.get_update_model()
|
@@ -1409,15 +1409,6 @@ class TemplateDeployer:
|
|
1409
1409
|
) # TODO: why is there a fallback?
|
1410
1410
|
resource["ResourceType"] = get_resource_type(resource)
|
1411
1411
|
|
1412
|
-
def _safe_lookup_is_deleted(r_id):
|
1413
|
-
"""handles the case where self.stack.resource_status(..) fails for whatever reason"""
|
1414
|
-
try:
|
1415
|
-
return self.stack.resource_status(r_id).get("ResourceStatus") == "DELETE_COMPLETE"
|
1416
|
-
except Exception:
|
1417
|
-
if config.CFN_VERBOSE_ERRORS:
|
1418
|
-
LOG.exception("failed to lookup if resource %s is deleted", r_id)
|
1419
|
-
return True # just an assumption
|
1420
|
-
|
1421
1412
|
ordered_resource_ids = list(
|
1422
1413
|
order_resources(
|
1423
1414
|
resources=original_resources,
|
@@ -7,6 +7,7 @@ from typing import Any, Final, Generator, Optional, Union, cast
|
|
7
7
|
|
8
8
|
from typing_extensions import TypeVar
|
9
9
|
|
10
|
+
from localstack.aws.api.cloudformation import ChangeAction
|
10
11
|
from localstack.utils.strings import camel_to_snake_case
|
11
12
|
|
12
13
|
T = TypeVar("T")
|
@@ -66,6 +67,15 @@ class ChangeType(enum.Enum):
|
|
66
67
|
def __str__(self):
|
67
68
|
return self.value
|
68
69
|
|
70
|
+
def to_action(self) -> ChangeAction | None:
|
71
|
+
match self:
|
72
|
+
case self.CREATED:
|
73
|
+
return ChangeAction.Add
|
74
|
+
case self.MODIFIED:
|
75
|
+
return ChangeAction.Modify
|
76
|
+
case self.REMOVED:
|
77
|
+
return ChangeAction.Remove
|
78
|
+
|
69
79
|
def for_child(self, child_change_type: ChangeType) -> ChangeType:
|
70
80
|
if child_change_type == self:
|
71
81
|
return self
|
@@ -117,6 +127,7 @@ class NodeTemplate(ChangeSetNode):
|
|
117
127
|
parameters: Final[NodeParameters]
|
118
128
|
conditions: Final[NodeConditions]
|
119
129
|
resources: Final[NodeResources]
|
130
|
+
outputs: Final[NodeOutputs]
|
120
131
|
|
121
132
|
def __init__(
|
122
133
|
self,
|
@@ -126,12 +137,14 @@ class NodeTemplate(ChangeSetNode):
|
|
126
137
|
parameters: NodeParameters,
|
127
138
|
conditions: NodeConditions,
|
128
139
|
resources: NodeResources,
|
140
|
+
outputs: NodeOutputs,
|
129
141
|
):
|
130
142
|
super().__init__(scope=scope, change_type=change_type)
|
131
143
|
self.mappings = mappings
|
132
144
|
self.parameters = parameters
|
133
145
|
self.conditions = conditions
|
134
146
|
self.resources = resources
|
147
|
+
self.outputs = outputs
|
135
148
|
|
136
149
|
|
137
150
|
class NodeDivergence(ChangeSetNode):
|
@@ -189,6 +202,36 @@ class NodeMappings(ChangeSetNode):
|
|
189
202
|
self.mappings = mappings
|
190
203
|
|
191
204
|
|
205
|
+
class NodeOutput(ChangeSetNode):
|
206
|
+
name: Final[str]
|
207
|
+
value: Final[ChangeSetEntity]
|
208
|
+
export: Final[Optional[ChangeSetEntity]]
|
209
|
+
condition_reference: Final[Optional[TerminalValue]]
|
210
|
+
|
211
|
+
def __init__(
|
212
|
+
self,
|
213
|
+
scope: Scope,
|
214
|
+
change_type: ChangeType,
|
215
|
+
name: str,
|
216
|
+
value: ChangeSetEntity,
|
217
|
+
export: Optional[ChangeSetEntity],
|
218
|
+
conditional_reference: Optional[TerminalValue],
|
219
|
+
):
|
220
|
+
super().__init__(scope=scope, change_type=change_type)
|
221
|
+
self.name = name
|
222
|
+
self.value = value
|
223
|
+
self.export = export
|
224
|
+
self.condition_reference = conditional_reference
|
225
|
+
|
226
|
+
|
227
|
+
class NodeOutputs(ChangeSetNode):
|
228
|
+
outputs: Final[list[NodeOutput]]
|
229
|
+
|
230
|
+
def __init__(self, scope: Scope, change_type: ChangeType, outputs: list[NodeOutput]):
|
231
|
+
super().__init__(scope=scope, change_type=change_type)
|
232
|
+
self.outputs = outputs
|
233
|
+
|
234
|
+
|
192
235
|
class NodeCondition(ChangeSetNode):
|
193
236
|
name: Final[str]
|
194
237
|
body: Final[ChangeSetEntity]
|
@@ -218,7 +261,7 @@ class NodeResources(ChangeSetNode):
|
|
218
261
|
class NodeResource(ChangeSetNode):
|
219
262
|
name: Final[str]
|
220
263
|
type_: Final[ChangeSetTerminal]
|
221
|
-
condition_reference: Final[TerminalValue]
|
264
|
+
condition_reference: Final[Optional[TerminalValue]]
|
222
265
|
properties: Final[NodeProperties]
|
223
266
|
|
224
267
|
def __init__(
|
@@ -325,6 +368,9 @@ MappingsKey: Final[str] = "Mappings"
|
|
325
368
|
ResourcesKey: Final[str] = "Resources"
|
326
369
|
PropertiesKey: Final[str] = "Properties"
|
327
370
|
ParametersKey: Final[str] = "Parameters"
|
371
|
+
ValueKey: Final[str] = "Value"
|
372
|
+
ExportKey: Final[str] = "Export"
|
373
|
+
OutputsKey: Final[str] = "Outputs"
|
328
374
|
# TODO: expand intrinsic functions set.
|
329
375
|
RefKey: Final[str] = "Ref"
|
330
376
|
FnIf: Final[str] = "Fn::If"
|
@@ -567,17 +613,9 @@ class ChangeSetModel:
|
|
567
613
|
binding_scope, (before_value, after_value) = self._safe_access_in(
|
568
614
|
scope, binding_name, before_object, after_object
|
569
615
|
)
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
intrinsic_function=binding_name,
|
574
|
-
before_arguments=before_value,
|
575
|
-
after_arguments=after_value,
|
576
|
-
)
|
577
|
-
else:
|
578
|
-
value = self._visit_value(
|
579
|
-
scope=binding_scope, before_value=before_value, after_value=after_value
|
580
|
-
)
|
616
|
+
value = self._visit_value(
|
617
|
+
scope=binding_scope, before_value=before_value, after_value=after_value
|
618
|
+
)
|
581
619
|
bindings[binding_name] = value
|
582
620
|
change_type = change_type.for_child(value.change_type)
|
583
621
|
node_object = NodeObject(scope=scope, change_type=change_type, bindings=bindings)
|
@@ -601,8 +639,11 @@ class ChangeSetModel:
|
|
601
639
|
value = self._visited_scopes.get(scope)
|
602
640
|
if isinstance(value, ChangeSetEntity):
|
603
641
|
return value
|
642
|
+
|
643
|
+
before_type_name = self._type_name_of(before_value)
|
644
|
+
after_type_name = self._type_name_of(after_value)
|
604
645
|
unset = object()
|
605
|
-
if
|
646
|
+
if before_type_name == after_type_name:
|
606
647
|
dominant_value = before_value
|
607
648
|
elif self._is_created(before=before_value, after=after_value):
|
608
649
|
dominant_value = after_value
|
@@ -611,6 +652,7 @@ class ChangeSetModel:
|
|
611
652
|
else:
|
612
653
|
dominant_value = unset
|
613
654
|
if dominant_value is not unset:
|
655
|
+
dominant_type_name = self._type_name_of(dominant_value)
|
614
656
|
if self._is_terminal(value=dominant_value):
|
615
657
|
value = self._visit_terminal_value(
|
616
658
|
scope=scope, before_value=before_value, after_value=after_value
|
@@ -623,6 +665,16 @@ class ChangeSetModel:
|
|
623
665
|
value = self._visit_array(
|
624
666
|
scope=scope, before_array=before_value, after_array=after_value
|
625
667
|
)
|
668
|
+
elif self._is_intrinsic_function_name(dominant_type_name):
|
669
|
+
intrinsic_function_scope, (before_arguments, after_arguments) = (
|
670
|
+
self._safe_access_in(scope, dominant_type_name, before_value, after_value)
|
671
|
+
)
|
672
|
+
value = self._visit_intrinsic_function(
|
673
|
+
scope=scope,
|
674
|
+
intrinsic_function=dominant_type_name,
|
675
|
+
before_arguments=before_arguments,
|
676
|
+
after_arguments=after_arguments,
|
677
|
+
)
|
626
678
|
else:
|
627
679
|
raise RuntimeError(f"Unsupported type {type(dominant_value)}")
|
628
680
|
# Case: type divergence.
|
@@ -644,24 +696,24 @@ class ChangeSetModel:
|
|
644
696
|
if isinstance(node_property, NodeProperty):
|
645
697
|
return node_property
|
646
698
|
|
699
|
+
value = self._visit_value(
|
700
|
+
scope=scope, before_value=before_property, after_value=after_property
|
701
|
+
)
|
647
702
|
if self._is_created(before=before_property, after=after_property):
|
648
703
|
node_property = NodeProperty(
|
649
704
|
scope=scope,
|
650
705
|
change_type=ChangeType.CREATED,
|
651
706
|
name=property_name,
|
652
|
-
value=
|
707
|
+
value=value,
|
653
708
|
)
|
654
709
|
elif self._is_removed(before=before_property, after=after_property):
|
655
710
|
node_property = NodeProperty(
|
656
711
|
scope=scope,
|
657
712
|
change_type=ChangeType.REMOVED,
|
658
713
|
name=property_name,
|
659
|
-
value=
|
714
|
+
value=value,
|
660
715
|
)
|
661
716
|
else:
|
662
|
-
value = self._visit_value(
|
663
|
-
scope=scope, before_value=before_property, after_value=after_property
|
664
|
-
)
|
665
717
|
node_property = NodeProperty(
|
666
718
|
scope=scope, change_type=value.change_type, name=property_name, value=value
|
667
719
|
)
|
@@ -715,14 +767,17 @@ class ChangeSetModel:
|
|
715
767
|
change_type = ChangeType.UNCHANGED
|
716
768
|
|
717
769
|
# TODO: investigate behaviour with type changes, for now this is filler code.
|
718
|
-
_, type_str = self._safe_access_in(scope, TypeKey,
|
770
|
+
_, type_str = self._safe_access_in(scope, TypeKey, after_resource)
|
719
771
|
|
772
|
+
condition_reference = None
|
720
773
|
scope_condition, (before_condition, after_condition) = self._safe_access_in(
|
721
774
|
scope, ConditionKey, before_resource, after_resource
|
722
775
|
)
|
723
|
-
|
724
|
-
|
725
|
-
|
776
|
+
# TODO: condition references should be resolved for the condition's change_type?
|
777
|
+
if before_condition or after_condition:
|
778
|
+
condition_reference = self._visit_terminal_value(
|
779
|
+
scope_condition, before_condition, after_condition
|
780
|
+
)
|
726
781
|
|
727
782
|
scope_properties, (before_properties, after_properties) = self._safe_access_in(
|
728
783
|
scope, PropertiesKey, before_resource, after_resource
|
@@ -887,18 +942,9 @@ class ChangeSetModel:
|
|
887
942
|
node_condition = self._visited_scopes.get(scope)
|
888
943
|
if isinstance(node_condition, NodeCondition):
|
889
944
|
return node_condition
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
if len(function_names) == 1:
|
894
|
-
body = self._visit_object(
|
895
|
-
scope=scope, before_object=before_condition, after_object=after_condition
|
896
|
-
)
|
897
|
-
else:
|
898
|
-
body = self._visit_divergence(
|
899
|
-
scope=scope, before_value=before_condition, after_value=after_condition
|
900
|
-
)
|
901
|
-
|
945
|
+
body = self._visit_value(
|
946
|
+
scope=scope, before_value=before_condition, after_value=after_condition
|
947
|
+
)
|
902
948
|
node_condition = NodeCondition(
|
903
949
|
scope=scope, change_type=body.change_type, name=condition_name, body=body
|
904
950
|
)
|
@@ -932,6 +978,64 @@ class ChangeSetModel:
|
|
932
978
|
self._visited_scopes[scope] = node_conditions
|
933
979
|
return node_conditions
|
934
980
|
|
981
|
+
def _visit_output(
|
982
|
+
self, scope: Scope, name: str, before_output: Maybe[dict], after_output: Maybe[dict]
|
983
|
+
) -> NodeOutput:
|
984
|
+
change_type = ChangeType.UNCHANGED
|
985
|
+
scope_value, (before_value, after_value) = self._safe_access_in(
|
986
|
+
scope, ValueKey, before_output, after_output
|
987
|
+
)
|
988
|
+
value = self._visit_value(scope_value, before_value, after_value)
|
989
|
+
change_type = change_type.for_child(value.change_type)
|
990
|
+
|
991
|
+
export: Optional[ChangeSetEntity] = None
|
992
|
+
scope_export, (before_export, after_export) = self._safe_access_in(
|
993
|
+
scope, ExportKey, before_output, after_output
|
994
|
+
)
|
995
|
+
if before_export or after_export:
|
996
|
+
export = self._visit_value(scope_export, before_export, after_export)
|
997
|
+
change_type = change_type.for_child(export.change_type)
|
998
|
+
|
999
|
+
# TODO: condition references should be resolved for the condition's change_type?
|
1000
|
+
condition_reference: Optional[TerminalValue] = None
|
1001
|
+
scope_condition, (before_condition, after_condition) = self._safe_access_in(
|
1002
|
+
scope, ConditionKey, before_output, after_output
|
1003
|
+
)
|
1004
|
+
if before_condition or after_condition:
|
1005
|
+
condition_reference = self._visit_terminal_value(
|
1006
|
+
scope_condition, before_condition, after_condition
|
1007
|
+
)
|
1008
|
+
change_type = change_type.for_child(condition_reference.change_type)
|
1009
|
+
|
1010
|
+
return NodeOutput(
|
1011
|
+
scope=scope,
|
1012
|
+
change_type=change_type,
|
1013
|
+
name=name,
|
1014
|
+
value=value,
|
1015
|
+
export=export,
|
1016
|
+
conditional_reference=condition_reference,
|
1017
|
+
)
|
1018
|
+
|
1019
|
+
def _visit_outputs(
|
1020
|
+
self, scope: Scope, before_outputs: Maybe[dict], after_outputs: Maybe[dict]
|
1021
|
+
) -> NodeOutputs:
|
1022
|
+
change_type = ChangeType.UNCHANGED
|
1023
|
+
outputs: list[NodeOutput] = list()
|
1024
|
+
output_names: list[str] = self._safe_keys_of(before_outputs, after_outputs)
|
1025
|
+
for output_name in output_names:
|
1026
|
+
scope_output, (before_output, after_output) = self._safe_access_in(
|
1027
|
+
scope, output_name, before_outputs, after_outputs
|
1028
|
+
)
|
1029
|
+
output = self._visit_output(
|
1030
|
+
scope=scope_output,
|
1031
|
+
name=output_name,
|
1032
|
+
before_output=before_output,
|
1033
|
+
after_output=after_output,
|
1034
|
+
)
|
1035
|
+
outputs.append(output)
|
1036
|
+
change_type = change_type.for_child(output.change_type)
|
1037
|
+
return NodeOutputs(scope=scope, change_type=change_type, outputs=outputs)
|
1038
|
+
|
935
1039
|
def _model(self, before_template: Maybe[dict], after_template: Maybe[dict]) -> NodeTemplate:
|
936
1040
|
root_scope = Scope()
|
937
1041
|
# TODO: visit other child types
|
@@ -970,6 +1074,13 @@ class ChangeSetModel:
|
|
970
1074
|
after_resources=after_resources,
|
971
1075
|
)
|
972
1076
|
|
1077
|
+
outputs_scope, (before_outputs, after_outputs) = self._safe_access_in(
|
1078
|
+
root_scope, OutputsKey, before_template, after_template
|
1079
|
+
)
|
1080
|
+
outputs = self._visit_outputs(
|
1081
|
+
scope=outputs_scope, before_outputs=before_outputs, after_outputs=after_outputs
|
1082
|
+
)
|
1083
|
+
|
973
1084
|
# TODO: compute the change_type of the template properly.
|
974
1085
|
return NodeTemplate(
|
975
1086
|
scope=root_scope,
|
@@ -978,6 +1089,7 @@ class ChangeSetModel:
|
|
978
1089
|
parameters=parameters,
|
979
1090
|
conditions=conditions,
|
980
1091
|
resources=resources,
|
1092
|
+
outputs=outputs,
|
981
1093
|
)
|
982
1094
|
|
983
1095
|
def _retrieve_condition_if_exists(self, condition_name: str) -> Optional[NodeCondition]:
|
@@ -1090,13 +1202,30 @@ class ChangeSetModel:
|
|
1090
1202
|
break
|
1091
1203
|
return parent_change_type
|
1092
1204
|
|
1205
|
+
@staticmethod
|
1206
|
+
def _name_if_intrinsic_function(value: Maybe[Any]) -> Optional[str]:
|
1207
|
+
if isinstance(value, dict):
|
1208
|
+
keys = ChangeSetModel._safe_keys_of(value)
|
1209
|
+
if len(keys) == 1:
|
1210
|
+
key_name = keys[0]
|
1211
|
+
if ChangeSetModel._is_intrinsic_function_name(key_name):
|
1212
|
+
return key_name
|
1213
|
+
return None
|
1214
|
+
|
1215
|
+
@staticmethod
|
1216
|
+
def _type_name_of(value: Maybe[Any]) -> str:
|
1217
|
+
maybe_intrinsic_function_name = ChangeSetModel._name_if_intrinsic_function(value)
|
1218
|
+
if maybe_intrinsic_function_name is not None:
|
1219
|
+
return maybe_intrinsic_function_name
|
1220
|
+
return type(value).__name__
|
1221
|
+
|
1093
1222
|
@staticmethod
|
1094
1223
|
def _is_terminal(value: Any) -> bool:
|
1095
1224
|
return type(value) in {int, float, bool, str, None, NothingType}
|
1096
1225
|
|
1097
1226
|
@staticmethod
|
1098
1227
|
def _is_object(value: Any) -> bool:
|
1099
|
-
return isinstance(value, dict)
|
1228
|
+
return isinstance(value, dict) and ChangeSetModel._name_if_intrinsic_function(value) is None
|
1100
1229
|
|
1101
1230
|
@staticmethod
|
1102
1231
|
def _is_array(value: Any) -> bool:
|