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.
Files changed (38) hide show
  1. localstack/services/cloudformation/engine/entities.py +18 -1
  2. localstack/services/cloudformation/engine/template_deployer.py +0 -9
  3. localstack/services/cloudformation/engine/v2/change_set_model.py +164 -35
  4. localstack/services/cloudformation/engine/v2/change_set_model_describer.py +143 -69
  5. localstack/services/cloudformation/engine/v2/change_set_model_executor.py +170 -0
  6. localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +8 -0
  7. localstack/services/cloudformation/v2/provider.py +72 -6
  8. localstack/services/ec2/patches.py +31 -3
  9. localstack/services/kms/models.py +1 -1
  10. localstack/services/lambda_/event_source_mapping/pollers/dynamodb_poller.py +2 -0
  11. localstack/services/lambda_/event_source_mapping/pollers/kinesis_poller.py +2 -0
  12. localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +4 -2
  13. localstack/services/lambda_/invocation/assignment.py +4 -2
  14. localstack/services/lambda_/invocation/execution_environment.py +16 -4
  15. localstack/services/lambda_/invocation/logs.py +28 -4
  16. localstack/services/lambda_/provider.py +18 -3
  17. localstack/services/lambda_/runtimes.py +15 -2
  18. localstack/services/s3/presigned_url.py +15 -11
  19. localstack/services/secretsmanager/provider.py +13 -4
  20. localstack/services/sqs/models.py +22 -3
  21. localstack/services/sqs/utils.py +16 -7
  22. localstack/services/ssm/resource_providers/aws_ssm_parameter.py +1 -5
  23. localstack/services/stepfunctions/asl/utils/json_path.py +9 -0
  24. localstack/testing/snapshots/transformer_utility.py +13 -0
  25. localstack/utils/aws/client_types.py +8 -0
  26. localstack/utils/docker_utils.py +2 -2
  27. localstack/version.py +2 -2
  28. {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev27.dist-info}/METADATA +3 -3
  29. {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev27.dist-info}/RECORD +37 -36
  30. localstack_core-4.3.1.dev27.dist-info/plux.json +1 -0
  31. localstack_core-4.3.1.dev6.dist-info/plux.json +0 -1
  32. {localstack_core-4.3.1.dev6.data → localstack_core-4.3.1.dev27.data}/scripts/localstack +0 -0
  33. {localstack_core-4.3.1.dev6.data → localstack_core-4.3.1.dev27.data}/scripts/localstack-supervisor +0 -0
  34. {localstack_core-4.3.1.dev6.data → localstack_core-4.3.1.dev27.data}/scripts/localstack.bat +0 -0
  35. {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev27.dist-info}/WHEEL +0 -0
  36. {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev27.dist-info}/entry_points.txt +0 -0
  37. {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev27.dist-info}/licenses/LICENSE.txt +0 -0
  38. {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__(self, account_id: str, region_name: str, stack: Stack, params=None, template=None):
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
- if self._is_intrinsic_function_name(function_name=binding_name):
571
- value = self._visit_intrinsic_function(
572
- scope=binding_scope,
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 type(before_value) is type(after_value):
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=TerminalValueCreated(scope=scope, value=after_property),
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=TerminalValueRemoved(scope=scope, value=before_property),
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, before_resource)
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
- condition_reference = self._visit_terminal_value(
724
- scope_condition, before_condition, after_condition
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
- # TODO: is schema validation/check necessary or can we trust the input at this point?
892
- function_names: list[str] = self._safe_keys_of(before_condition, after_condition)
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: