localstack-core 4.3.1.dev5__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 +281 -36
  4. localstack/services/cloudformation/engine/v2/change_set_model_describer.py +187 -70
  5. localstack/services/cloudformation/engine/v2/change_set_model_executor.py +170 -0
  6. localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +21 -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.dev5.dist-info → localstack_core-4.3.1.dev27.dist-info}/METADATA +3 -3
  29. {localstack_core-4.3.1.dev5.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.dev5.dist-info/plux.json +0 -1
  32. {localstack_core-4.3.1.dev5.data → localstack_core-4.3.1.dev27.data}/scripts/localstack +0 -0
  33. {localstack_core-4.3.1.dev5.data → localstack_core-4.3.1.dev27.data}/scripts/localstack-supervisor +0 -0
  34. {localstack_core-4.3.1.dev5.data → localstack_core-4.3.1.dev27.data}/scripts/localstack.bat +0 -0
  35. {localstack_core-4.3.1.dev5.dist-info → localstack_core-4.3.1.dev27.dist-info}/WHEEL +0 -0
  36. {localstack_core-4.3.1.dev5.dist-info → localstack_core-4.3.1.dev27.dist-info}/entry_points.txt +0 -0
  37. {localstack_core-4.3.1.dev5.dist-info → localstack_core-4.3.1.dev27.dist-info}/licenses/LICENSE.txt +0 -0
  38. {localstack_core-4.3.1.dev5.dist-info → localstack_core-4.3.1.dev27.dist-info}/top_level.txt +0 -0
@@ -7,11 +7,16 @@ import localstack.aws.api.cloudformation as cfn_api
7
7
  from localstack.services.cloudformation.engine.v2.change_set_model import (
8
8
  ChangeSetEntity,
9
9
  ChangeType,
10
+ ConditionKey,
11
+ ExportKey,
10
12
  NodeArray,
11
13
  NodeCondition,
12
14
  NodeDivergence,
13
15
  NodeIntrinsicFunction,
16
+ NodeMapping,
14
17
  NodeObject,
18
+ NodeOutput,
19
+ NodeOutputs,
15
20
  NodeParameter,
16
21
  NodeProperties,
17
22
  NodeProperty,
@@ -25,6 +30,7 @@ from localstack.services.cloudformation.engine.v2.change_set_model import (
25
30
  TerminalValueModified,
26
31
  TerminalValueRemoved,
27
32
  TerminalValueUnchanged,
33
+ ValueKey,
28
34
  )
29
35
  from localstack.services.cloudformation.engine.v2.change_set_model_visitor import (
30
36
  ChangeSetModelVisitor,
@@ -46,14 +52,20 @@ class ChangeSetModelDescriber(ChangeSetModelVisitor):
46
52
  _node_template: Final[NodeTemplate]
47
53
  _changes: Final[cfn_api.Changes]
48
54
  _describe_unit_cache: dict[Scope, DescribeUnit]
55
+ _include_property_values: Final[cfn_api.IncludePropertyValues | None]
49
56
 
50
- def __init__(self, node_template: NodeTemplate):
57
+ def __init__(
58
+ self,
59
+ node_template: NodeTemplate,
60
+ include_property_values: cfn_api.IncludePropertyValues | None = None,
61
+ ):
51
62
  self._node_template = node_template
52
63
  self._changes = list()
53
64
  self._describe_unit_cache = dict()
54
- self.visit(self._node_template)
65
+ self._include_property_values = include_property_values
55
66
 
56
67
  def get_changes(self) -> cfn_api.Changes:
68
+ self.visit(self._node_template)
57
69
  return self._changes
58
70
 
59
71
  @staticmethod
@@ -74,6 +86,15 @@ class ChangeSetModelDescriber(ChangeSetModelVisitor):
74
86
  # TODO
75
87
  raise RuntimeError()
76
88
 
89
+ def _get_node_mapping(self, map_name: str) -> NodeMapping:
90
+ mappings: list[NodeMapping] = self._node_template.mappings.mappings
91
+ # TODO: another scenarios suggesting property lookups might be preferable.
92
+ for mapping in mappings:
93
+ if mapping.name == map_name:
94
+ return mapping
95
+ # TODO
96
+ raise RuntimeError()
97
+
77
98
  def _get_node_parameter_if_exists(self, parameter_name: str) -> Optional[NodeParameter]:
78
99
  parameters: list[NodeParameter] = self._node_template.parameters.parameters
79
100
  # TODO: another scenarios suggesting property lookups might be preferable.
@@ -102,12 +123,23 @@ class ChangeSetModelDescriber(ChangeSetModelVisitor):
102
123
  return parameter_unit
103
124
 
104
125
  # TODO: check for KNOWN AFTER APPLY values for logical ids coming from intrinsic functions as arguments.
105
- # node_resource = self._get_node_resource_for(
106
- # resource_name=logica_id, node_template=self._node_template
107
- # )
108
- limitation_str = "Cannot yet compute Ref values for Resources"
109
- resource_unit = DescribeUnit(before_context=limitation_str, after_context=limitation_str)
110
- return resource_unit
126
+ node_resource = self._get_node_resource_for(
127
+ resource_name=logica_id, node_template=self._node_template
128
+ )
129
+ resource_unit = self.visit(node_resource)
130
+ before_context = resource_unit.before_context
131
+ after_context = resource_unit.after_context
132
+ return DescribeUnit(before_context=before_context, after_context=after_context)
133
+
134
+ def _resolve_mapping(self, map_name: str, top_level_key: str, second_level_key) -> DescribeUnit:
135
+ # TODO: add support for nested intrinsic functions, and KNOWN AFTER APPLY logical ids.
136
+ node_mapping: NodeMapping = self._get_node_mapping(map_name=map_name)
137
+ top_level_value = node_mapping.bindings.bindings.get(top_level_key)
138
+ if not isinstance(top_level_value, NodeObject):
139
+ raise RuntimeError()
140
+ second_level_value = top_level_value.bindings.get(second_level_key)
141
+ mapping_value_unit = self.visit(second_level_value)
142
+ return mapping_value_unit
111
143
 
112
144
  def _resolve_reference_binding(
113
145
  self, before_logical_id: str, after_logical_id: str
@@ -190,29 +222,37 @@ class ChangeSetModelDescriber(ChangeSetModelVisitor):
190
222
  arguments_unit = self.visit(node_intrinsic_function.arguments)
191
223
  # TODO: validate the return value according to the spec.
192
224
  before_argument_list = arguments_unit.before_context
193
- before_logical_name_of_resource = before_argument_list[0]
194
- before_attribute_name = before_argument_list[1]
195
- before_node_resource = self._get_node_resource_for(
196
- resource_name=before_logical_name_of_resource, node_template=self._node_template
197
- )
198
- node_property: TerminalValue = self._get_node_property_for(
199
- property_name=before_attribute_name, node_resource=before_node_resource
200
- )
225
+ after_argument_list = arguments_unit.after_context
201
226
 
202
- before_context = node_property.value.value
203
- if node_property.change_type != ChangeType.UNCHANGED:
227
+ before_context = None
228
+ if before_argument_list:
229
+ before_logical_name_of_resource = before_argument_list[0]
230
+ before_attribute_name = before_argument_list[1]
231
+ before_node_resource = self._get_node_resource_for(
232
+ resource_name=before_logical_name_of_resource, node_template=self._node_template
233
+ )
234
+ before_node_property = self._get_node_property_for(
235
+ property_name=before_attribute_name, node_resource=before_node_resource
236
+ )
237
+ before_property_unit = self.visit(before_node_property)
238
+ before_context = before_property_unit.before_context
239
+
240
+ after_context = None
241
+ if after_argument_list:
204
242
  after_context = CHANGESET_KNOWN_AFTER_APPLY
205
- else:
206
- after_context = node_property.value.value
243
+ # TODO: the following is the logic to resolve the attribute in the `after` template
244
+ # this should be moved to the new base class and then be masked in this describer.
245
+ # after_logical_name_of_resource = after_argument_list[0]
246
+ # after_attribute_name = after_argument_list[1]
247
+ # after_node_resource = self._get_node_resource_for(
248
+ # resource_name=after_logical_name_of_resource, node_template=self._node_template
249
+ # )
250
+ # after_node_property = self._get_node_property_for(
251
+ # property_name=after_attribute_name, node_resource=after_node_resource
252
+ # )
253
+ # after_property_unit = self.visit(after_node_property)
254
+ # after_context = after_property_unit.after_context
207
255
 
208
- match node_intrinsic_function.change_type:
209
- case ChangeType.MODIFIED:
210
- return DescribeUnit(before_context=before_context, after_context=after_context)
211
- case ChangeType.CREATED:
212
- return DescribeUnit(after_context=after_context)
213
- case ChangeType.REMOVED:
214
- return DescribeUnit(before_context=before_context)
215
- # Unchanged
216
256
  return DescribeUnit(before_context=before_context, after_context=after_context)
217
257
 
218
258
  def visit_node_intrinsic_function_fn_equals(
@@ -281,8 +321,31 @@ class ChangeSetModelDescriber(ChangeSetModelVisitor):
281
321
  # Implicit change type computation.
282
322
  return DescribeUnit(before_context=before_context, after_context=after_context)
283
323
 
324
+ def visit_node_intrinsic_function_fn_find_in_map(
325
+ self, node_intrinsic_function: NodeIntrinsicFunction
326
+ ) -> DescribeUnit:
327
+ # TODO: check for KNOWN AFTER APPLY values for logical ids coming from intrinsic functions as arguments.
328
+ # TODO: add type checking/validation for result unit?
329
+ arguments_unit = self.visit(node_intrinsic_function.arguments)
330
+ before_arguments = arguments_unit.before_context
331
+ after_arguments = arguments_unit.after_context
332
+ if before_arguments:
333
+ before_value_unit = self._resolve_mapping(*before_arguments)
334
+ before_context = before_value_unit.before_context
335
+ else:
336
+ before_context = None
337
+ if after_arguments:
338
+ after_value_unit = self._resolve_mapping(*after_arguments)
339
+ after_context = after_value_unit.after_context
340
+ else:
341
+ after_context = None
342
+ return DescribeUnit(before_context=before_context, after_context=after_context)
343
+
344
+ def visit_node_mapping(self, node_mapping: NodeMapping) -> DescribeUnit:
345
+ bindings_unit = self.visit(node_mapping.bindings)
346
+ return bindings_unit
347
+
284
348
  def visit_node_parameter(self, node_parameter: NodeParameter) -> DescribeUnit:
285
- # TODO: add caching for these operation, parameters may be referenced more than once.
286
349
  # TODO: add support for default value sampling
287
350
  dynamic_value = node_parameter.dynamic_value
288
351
  describe_unit = self.visit(dynamic_value)
@@ -299,12 +362,16 @@ class ChangeSetModelDescriber(ChangeSetModelVisitor):
299
362
 
300
363
  # TODO: add tests with created and deleted parameters and verify this logic holds.
301
364
  before_logical_id = arguments_unit.before_context
302
- before_unit = self._resolve_reference(logica_id=before_logical_id)
303
- before_context = before_unit.before_context
365
+ before_context = None
366
+ if before_logical_id is not None:
367
+ before_unit = self._resolve_reference(logica_id=before_logical_id)
368
+ before_context = before_unit.before_context
304
369
 
305
370
  after_logical_id = arguments_unit.after_context
306
- after_unit = self._resolve_reference(logica_id=after_logical_id)
307
- after_context = after_unit.after_context
371
+ after_context = None
372
+ if after_logical_id is not None:
373
+ after_unit = self._resolve_reference(logica_id=after_logical_id)
374
+ after_context = after_unit.after_context
308
375
 
309
376
  return DescribeUnit(before_context=before_context, after_context=after_context)
310
377
 
@@ -363,21 +430,71 @@ class ChangeSetModelDescriber(ChangeSetModelVisitor):
363
430
  )
364
431
  return DescribeUnit(before_context=before_context, after_context=after_context)
365
432
 
433
+ def visit_node_output(self, node_output: NodeOutput) -> DescribeUnit:
434
+ # This logic is not required for Describe operations,
435
+ # and should be ported a new base for this class type.
436
+ change_type = node_output.change_type
437
+ value_unit = self.visit(node_output.value)
438
+
439
+ condition_unit = None
440
+ if node_output.condition_reference is not None:
441
+ condition_unit = self._resolve_resource_condition_reference(
442
+ node_output.condition_reference
443
+ )
444
+ condition_before = condition_unit.before_context
445
+ condition_after = condition_unit.after_context
446
+ if not condition_before and condition_after:
447
+ change_type = ChangeType.CREATED
448
+ elif condition_before and not condition_after:
449
+ change_type = ChangeType.REMOVED
450
+
451
+ export_unit = None
452
+ if node_output.export is not None:
453
+ export_unit = self.visit(node_output.export)
454
+
455
+ before_context = None
456
+ after_context = None
457
+ if change_type != ChangeType.REMOVED:
458
+ after_context = {"Name": node_output.name, ValueKey: value_unit.after_context}
459
+ if export_unit:
460
+ after_context[ExportKey] = export_unit.after_context
461
+ if condition_unit:
462
+ after_context[ConditionKey] = condition_unit.after_context
463
+ if change_type != ChangeType.CREATED:
464
+ before_context = {"Name": node_output.name, ValueKey: value_unit.before_context}
465
+ if export_unit:
466
+ before_context[ExportKey] = export_unit.before_context
467
+ if condition_unit:
468
+ before_context[ConditionKey] = condition_unit.before_context
469
+ return DescribeUnit(before_context=before_context, after_context=after_context)
470
+
471
+ def visit_node_outputs(self, node_outputs: NodeOutputs) -> DescribeUnit:
472
+ # This logic is not required for Describe operations,
473
+ # and should be ported a new base for this class type.
474
+ before_context = list()
475
+ after_context = list()
476
+ for node_output in node_outputs.outputs:
477
+ output_unit = self.visit(node_output)
478
+ output_before = output_unit.before_context
479
+ output_after = output_unit.after_context
480
+ if output_before:
481
+ before_context.append(output_before)
482
+ if output_after:
483
+ after_context.append(output_after)
484
+ return DescribeUnit(before_context=before_context, after_context=after_context)
485
+
366
486
  def visit_node_resource(self, node_resource: NodeResource) -> DescribeUnit:
367
- condition_unit = self._resolve_resource_condition_reference(
368
- node_resource.condition_reference
369
- )
370
- condition_before = condition_unit.before_context
371
- condition_after = condition_unit.after_context
372
- if not condition_before and condition_after:
373
- change_type = ChangeType.CREATED
374
- elif condition_before and not condition_after:
375
- change_type = ChangeType.REMOVED
376
- else:
377
- change_type = node_resource.change_type
378
- if change_type == ChangeType.UNCHANGED:
379
- # TODO
380
- return None
487
+ change_type = node_resource.change_type
488
+ if node_resource.condition_reference is not None:
489
+ condition_unit = self._resolve_resource_condition_reference(
490
+ node_resource.condition_reference
491
+ )
492
+ condition_before = condition_unit.before_context
493
+ condition_after = condition_unit.after_context
494
+ if not condition_before and condition_after:
495
+ change_type = ChangeType.CREATED
496
+ elif condition_before and not condition_after:
497
+ change_type = ChangeType.REMOVED
381
498
 
382
499
  resource_change = cfn_api.ResourceChange()
383
500
  resource_change["LogicalResourceId"] = node_resource.name
@@ -389,28 +506,28 @@ class ChangeSetModelDescriber(ChangeSetModelVisitor):
389
506
  )
390
507
 
391
508
  properties_describe_unit = self.visit(node_resource.properties)
392
- match change_type:
393
- case ChangeType.MODIFIED:
394
- resource_change["Action"] = cfn_api.ChangeAction.Modify
395
- resource_change["BeforeContext"] = properties_describe_unit.before_context
396
- resource_change["AfterContext"] = properties_describe_unit.after_context
397
- case ChangeType.CREATED:
398
- resource_change["Action"] = cfn_api.ChangeAction.Add
399
- resource_change["AfterContext"] = properties_describe_unit.after_context
400
- case ChangeType.REMOVED:
401
- resource_change["Action"] = cfn_api.ChangeAction.Remove
402
- resource_change["BeforeContext"] = properties_describe_unit.before_context
403
-
404
- self._changes.append(
405
- cfn_api.Change(Type=cfn_api.ChangeType.Resource, ResourceChange=resource_change)
406
- )
407
509
 
408
- # TODO
409
- return None
510
+ if change_type != ChangeType.UNCHANGED:
511
+ match change_type:
512
+ case ChangeType.MODIFIED:
513
+ resource_change["Action"] = cfn_api.ChangeAction.Modify
514
+ resource_change["BeforeContext"] = properties_describe_unit.before_context
515
+ resource_change["AfterContext"] = properties_describe_unit.after_context
516
+ case ChangeType.CREATED:
517
+ resource_change["Action"] = cfn_api.ChangeAction.Add
518
+ resource_change["AfterContext"] = properties_describe_unit.after_context
519
+ case ChangeType.REMOVED:
520
+ resource_change["Action"] = cfn_api.ChangeAction.Remove
521
+ resource_change["BeforeContext"] = properties_describe_unit.before_context
522
+ self._changes.append(
523
+ cfn_api.Change(Type=cfn_api.ChangeType.Resource, ResourceChange=resource_change)
524
+ )
410
525
 
411
- # def visit_node_resources(self, node_resources: NodeResources) -> DescribeUnit:
412
- # for node_resource in node_resources.resources:
413
- # if node_resource.change_type != ChangeType.UNCHANGED:
414
- # self.visit_node_resource(node_resource=node_resource)
415
- # # TODO
416
- # return None
526
+ before_context = None
527
+ after_context = None
528
+ # TODO: reconsider what is the describe unit return value for a resource type.
529
+ if change_type != ChangeType.CREATED:
530
+ before_context = node_resource.name
531
+ if change_type != ChangeType.REMOVED:
532
+ after_context = node_resource.name
533
+ return DescribeUnit(before_context=before_context, after_context=after_context)
@@ -0,0 +1,170 @@
1
+ import logging
2
+ import uuid
3
+ from typing import Final
4
+
5
+ from localstack.aws.api.cloudformation import ChangeAction
6
+ from localstack.constants import INTERNAL_AWS_SECRET_ACCESS_KEY
7
+ from localstack.services.cloudformation.engine.v2.change_set_model import (
8
+ NodeIntrinsicFunction,
9
+ NodeResource,
10
+ NodeTemplate,
11
+ TerminalValue,
12
+ )
13
+ from localstack.services.cloudformation.engine.v2.change_set_model_describer import (
14
+ ChangeSetModelDescriber,
15
+ DescribeUnit,
16
+ )
17
+ from localstack.services.cloudformation.resource_provider import (
18
+ Credentials,
19
+ OperationStatus,
20
+ ProgressEvent,
21
+ ResourceProviderExecutor,
22
+ ResourceProviderPayload,
23
+ get_resource_type,
24
+ )
25
+
26
+ LOG = logging.getLogger(__name__)
27
+
28
+
29
+ class ChangeSetModelExecutor(ChangeSetModelDescriber):
30
+ account_id: Final[str]
31
+ region: Final[str]
32
+
33
+ def __init__(
34
+ self,
35
+ node_template: NodeTemplate,
36
+ account_id: str,
37
+ region: str,
38
+ stack_name: str,
39
+ stack_id: str,
40
+ ):
41
+ super().__init__(node_template)
42
+ self.account_id = account_id
43
+ self.region = region
44
+ self.stack_name = stack_name
45
+ self.stack_id = stack_id
46
+ self.resources = {}
47
+
48
+ def execute(self) -> dict:
49
+ self.visit(self._node_template)
50
+ return self.resources
51
+
52
+ def visit_node_resource(self, node_resource: NodeResource) -> DescribeUnit:
53
+ resource_provider_executor = ResourceProviderExecutor(
54
+ stack_name=self.stack_name, stack_id=self.stack_id
55
+ )
56
+
57
+ # TODO: investigate effects on type changes
58
+ properties_describe_unit = self.visit_node_properties(node_resource.properties)
59
+ LOG.info("SRW: describe unit: %s", properties_describe_unit)
60
+
61
+ action = node_resource.change_type.to_action()
62
+ if action is None:
63
+ raise RuntimeError(
64
+ f"Action should always be present, got change type: {node_resource.change_type}"
65
+ )
66
+
67
+ # TODO
68
+ resource_type = get_resource_type({"Type": "AWS::SSM::Parameter"})
69
+ payload = self.create_resource_provider_payload(
70
+ properties_describe_unit,
71
+ action,
72
+ node_resource.name,
73
+ resource_type,
74
+ )
75
+ resource_provider = resource_provider_executor.try_load_resource_provider(resource_type)
76
+
77
+ extra_resource_properties = {}
78
+ if resource_provider is not None:
79
+ # TODO: stack events
80
+ event = resource_provider_executor.deploy_loop(
81
+ resource_provider, extra_resource_properties, payload
82
+ )
83
+ else:
84
+ event = ProgressEvent(OperationStatus.SUCCESS, resource_model={})
85
+
86
+ self.resources.setdefault(node_resource.name, {"Properties": {}})
87
+ match event.status:
88
+ case OperationStatus.SUCCESS:
89
+ # merge the resources state with the external state
90
+ # TODO: this is likely a duplicate of updating from extra_resource_properties
91
+ self.resources[node_resource.name]["Properties"].update(event.resource_model)
92
+ self.resources[node_resource.name].update(extra_resource_properties)
93
+ # XXX for legacy delete_stack compatibility
94
+ self.resources[node_resource.name]["LogicalResourceId"] = node_resource.name
95
+ self.resources[node_resource.name]["Type"] = resource_type
96
+ case any:
97
+ raise NotImplementedError(f"Event status '{any}' not handled")
98
+
99
+ return DescribeUnit(before_context=None, after_context={})
100
+
101
+ def visit_node_intrinsic_function_fn_get_att(
102
+ self, node_intrinsic_function: NodeIntrinsicFunction
103
+ ) -> DescribeUnit:
104
+ arguments_unit = self.visit(node_intrinsic_function.arguments)
105
+ before_arguments_list = arguments_unit.before_context
106
+ after_arguments_list = arguments_unit.after_context
107
+ if before_arguments_list:
108
+ logical_name_of_resource = before_arguments_list[0]
109
+ attribute_name = before_arguments_list[1]
110
+ before_node_resource = self._get_node_resource_for(
111
+ resource_name=logical_name_of_resource, node_template=self._node_template
112
+ )
113
+ node_property: TerminalValue = self._get_node_property_for(
114
+ property_name=attribute_name, node_resource=before_node_resource
115
+ )
116
+ before_context = self.visit(node_property.value).before_context
117
+ else:
118
+ before_context = None
119
+
120
+ if after_arguments_list:
121
+ logical_name_of_resource = after_arguments_list[0]
122
+ attribute_name = after_arguments_list[1]
123
+ after_node_resource = self._get_node_resource_for(
124
+ resource_name=logical_name_of_resource, node_template=self._node_template
125
+ )
126
+ node_property: TerminalValue = self._get_node_property_for(
127
+ property_name=attribute_name, node_resource=after_node_resource
128
+ )
129
+ after_context = self.visit(node_property.value).after_context
130
+ else:
131
+ after_context = None
132
+
133
+ return DescribeUnit(before_context=before_context, after_context=after_context)
134
+
135
+ def create_resource_provider_payload(
136
+ self,
137
+ describe_unit: DescribeUnit,
138
+ action: ChangeAction,
139
+ logical_resource_id: str,
140
+ resource_type: str,
141
+ ) -> ResourceProviderPayload:
142
+ # FIXME: use proper credentials
143
+ creds: Credentials = {
144
+ "accessKeyId": self.account_id,
145
+ "secretAccessKey": INTERNAL_AWS_SECRET_ACCESS_KEY,
146
+ "sessionToken": "",
147
+ }
148
+ resource_provider_payload: ResourceProviderPayload = {
149
+ "awsAccountId": self.account_id,
150
+ "callbackContext": {},
151
+ "stackId": self.stack_name,
152
+ "resourceType": resource_type,
153
+ "resourceTypeVersion": "000000",
154
+ # TODO: not actually a UUID
155
+ "bearerToken": str(uuid.uuid4()),
156
+ "region": self.region,
157
+ "action": str(action),
158
+ "requestData": {
159
+ "logicalResourceId": logical_resource_id,
160
+ "resourceProperties": describe_unit.after_context["Properties"],
161
+ "previousResourceProperties": describe_unit.before_context["Properties"],
162
+ "callerCredentials": creds,
163
+ "providerCredentials": creds,
164
+ "systemTags": {},
165
+ "previousSystemTags": {},
166
+ "stackTags": {},
167
+ "previousStackTags": {},
168
+ },
169
+ }
170
+ return resource_provider_payload
@@ -7,7 +7,11 @@ from localstack.services.cloudformation.engine.v2.change_set_model import (
7
7
  NodeConditions,
8
8
  NodeDivergence,
9
9
  NodeIntrinsicFunction,
10
+ NodeMapping,
11
+ NodeMappings,
10
12
  NodeObject,
13
+ NodeOutput,
14
+ NodeOutputs,
11
15
  NodeParameter,
12
16
  NodeParameters,
13
17
  NodeProperties,
@@ -45,6 +49,18 @@ class ChangeSetModelVisitor(abc.ABC):
45
49
  def visit_node_template(self, node_template: NodeTemplate):
46
50
  self.visit_children(node_template)
47
51
 
52
+ def visit_node_mapping(self, node_mapping: NodeMapping):
53
+ self.visit_children(node_mapping)
54
+
55
+ def visit_node_mappings(self, node_mappings: NodeMappings):
56
+ self.visit_children(node_mappings)
57
+
58
+ def visit_node_outputs(self, node_outputs: NodeOutputs):
59
+ self.visit_children(node_outputs)
60
+
61
+ def visit_node_output(self, node_output: NodeOutput):
62
+ self.visit_children(node_output)
63
+
48
64
  def visit_node_parameters(self, node_parameters: NodeParameters):
49
65
  self.visit_children(node_parameters)
50
66
 
@@ -94,6 +110,11 @@ class ChangeSetModelVisitor(abc.ABC):
94
110
  def visit_node_intrinsic_function_fn_not(self, node_intrinsic_function: NodeIntrinsicFunction):
95
111
  self.visit_children(node_intrinsic_function)
96
112
 
113
+ def visit_node_intrinsic_function_fn_find_in_map(
114
+ self, node_intrinsic_function: NodeIntrinsicFunction
115
+ ):
116
+ self.visit_children(node_intrinsic_function)
117
+
97
118
  def visit_node_intrinsic_function_ref(self, node_intrinsic_function: NodeIntrinsicFunction):
98
119
  self.visit_children(node_intrinsic_function)
99
120