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.
- 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 +281 -36
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +187 -70
- localstack/services/cloudformation/engine/v2/change_set_model_executor.py +170 -0
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +21 -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.dev5.dist-info → localstack_core-4.3.1.dev27.dist-info}/METADATA +3 -3
- {localstack_core-4.3.1.dev5.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.dev5.dist-info/plux.json +0 -1
- {localstack_core-4.3.1.dev5.data → localstack_core-4.3.1.dev27.data}/scripts/localstack +0 -0
- {localstack_core-4.3.1.dev5.data → localstack_core-4.3.1.dev27.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.3.1.dev5.data → localstack_core-4.3.1.dev27.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.3.1.dev5.dist-info → localstack_core-4.3.1.dev27.dist-info}/WHEEL +0 -0
- {localstack_core-4.3.1.dev5.dist-info → localstack_core-4.3.1.dev27.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.3.1.dev5.dist-info → localstack_core-4.3.1.dev27.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.3.1.dev5.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
|
@@ -113,22 +123,28 @@ class ChangeSetTerminal(ChangeSetEntity, abc.ABC): ...
|
|
113
123
|
|
114
124
|
|
115
125
|
class NodeTemplate(ChangeSetNode):
|
126
|
+
mappings: Final[NodeMappings]
|
116
127
|
parameters: Final[NodeParameters]
|
117
128
|
conditions: Final[NodeConditions]
|
118
129
|
resources: Final[NodeResources]
|
130
|
+
outputs: Final[NodeOutputs]
|
119
131
|
|
120
132
|
def __init__(
|
121
133
|
self,
|
122
134
|
scope: Scope,
|
123
135
|
change_type: ChangeType,
|
136
|
+
mappings: NodeMappings,
|
124
137
|
parameters: NodeParameters,
|
125
138
|
conditions: NodeConditions,
|
126
139
|
resources: NodeResources,
|
140
|
+
outputs: NodeOutputs,
|
127
141
|
):
|
128
142
|
super().__init__(scope=scope, change_type=change_type)
|
143
|
+
self.mappings = mappings
|
129
144
|
self.parameters = parameters
|
130
145
|
self.conditions = conditions
|
131
146
|
self.resources = resources
|
147
|
+
self.outputs = outputs
|
132
148
|
|
133
149
|
|
134
150
|
class NodeDivergence(ChangeSetNode):
|
@@ -168,6 +184,54 @@ class NodeParameters(ChangeSetNode):
|
|
168
184
|
self.parameters = parameters
|
169
185
|
|
170
186
|
|
187
|
+
class NodeMapping(ChangeSetNode):
|
188
|
+
name: Final[str]
|
189
|
+
bindings: Final[NodeObject]
|
190
|
+
|
191
|
+
def __init__(self, scope: Scope, change_type: ChangeType, name: str, bindings: NodeObject):
|
192
|
+
super().__init__(scope=scope, change_type=change_type)
|
193
|
+
self.name = name
|
194
|
+
self.bindings = bindings
|
195
|
+
|
196
|
+
|
197
|
+
class NodeMappings(ChangeSetNode):
|
198
|
+
mappings: Final[list[NodeMapping]]
|
199
|
+
|
200
|
+
def __init__(self, scope: Scope, change_type: ChangeType, mappings: list[NodeMapping]):
|
201
|
+
super().__init__(scope=scope, change_type=change_type)
|
202
|
+
self.mappings = mappings
|
203
|
+
|
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
|
+
|
171
235
|
class NodeCondition(ChangeSetNode):
|
172
236
|
name: Final[str]
|
173
237
|
body: Final[ChangeSetEntity]
|
@@ -197,7 +261,7 @@ class NodeResources(ChangeSetNode):
|
|
197
261
|
class NodeResource(ChangeSetNode):
|
198
262
|
name: Final[str]
|
199
263
|
type_: Final[ChangeSetTerminal]
|
200
|
-
condition_reference: Final[TerminalValue]
|
264
|
+
condition_reference: Final[Optional[TerminalValue]]
|
201
265
|
properties: Final[NodeProperties]
|
202
266
|
|
203
267
|
def __init__(
|
@@ -300,16 +364,28 @@ class TerminalValueUnchanged(TerminalValue):
|
|
300
364
|
TypeKey: Final[str] = "Type"
|
301
365
|
ConditionKey: Final[str] = "Condition"
|
302
366
|
ConditionsKey: Final[str] = "Conditions"
|
367
|
+
MappingsKey: Final[str] = "Mappings"
|
303
368
|
ResourcesKey: Final[str] = "Resources"
|
304
369
|
PropertiesKey: Final[str] = "Properties"
|
305
370
|
ParametersKey: Final[str] = "Parameters"
|
371
|
+
ValueKey: Final[str] = "Value"
|
372
|
+
ExportKey: Final[str] = "Export"
|
373
|
+
OutputsKey: Final[str] = "Outputs"
|
306
374
|
# TODO: expand intrinsic functions set.
|
307
375
|
RefKey: Final[str] = "Ref"
|
308
376
|
FnIf: Final[str] = "Fn::If"
|
309
377
|
FnNot: Final[str] = "Fn::Not"
|
310
378
|
FnGetAttKey: Final[str] = "Fn::GetAtt"
|
311
379
|
FnEqualsKey: Final[str] = "Fn::Equals"
|
312
|
-
|
380
|
+
FnFindInMapKey: Final[str] = "Fn::FindInMap"
|
381
|
+
INTRINSIC_FUNCTIONS: Final[set[str]] = {
|
382
|
+
RefKey,
|
383
|
+
FnIf,
|
384
|
+
FnNot,
|
385
|
+
FnEqualsKey,
|
386
|
+
FnGetAttKey,
|
387
|
+
FnFindInMapKey,
|
388
|
+
}
|
313
389
|
|
314
390
|
|
315
391
|
class ChangeSetModel:
|
@@ -455,6 +531,36 @@ class ChangeSetModel:
|
|
455
531
|
node_resource = self._retrieve_or_visit_resource(resource_name=logical_id)
|
456
532
|
return node_resource.change_type
|
457
533
|
|
534
|
+
def _resolve_intrinsic_function_fn_find_in_map(self, arguments: ChangeSetEntity) -> ChangeType:
|
535
|
+
if arguments.change_type != ChangeType.UNCHANGED:
|
536
|
+
return arguments.change_type
|
537
|
+
# TODO: validate arguments structure and type.
|
538
|
+
# TODO: add support for nested functions, here we assume the arguments are string literals.
|
539
|
+
|
540
|
+
if not isinstance(arguments, NodeArray) or not arguments.array:
|
541
|
+
raise RuntimeError()
|
542
|
+
argument_mapping_name = arguments.array[0]
|
543
|
+
if not isinstance(argument_mapping_name, TerminalValue):
|
544
|
+
raise NotImplementedError()
|
545
|
+
argument_top_level_key = arguments.array[1]
|
546
|
+
if not isinstance(argument_top_level_key, TerminalValue):
|
547
|
+
raise NotImplementedError()
|
548
|
+
argument_second_level_key = arguments.array[2]
|
549
|
+
if not isinstance(argument_second_level_key, TerminalValue):
|
550
|
+
raise NotImplementedError()
|
551
|
+
mapping_name = argument_mapping_name.value
|
552
|
+
top_level_key = argument_top_level_key.value
|
553
|
+
second_level_key = argument_second_level_key.value
|
554
|
+
|
555
|
+
node_mapping = self._retrieve_mapping(mapping_name=mapping_name)
|
556
|
+
# TODO: a lookup would be beneficial in this scenario too;
|
557
|
+
# consider implications downstream and for replication.
|
558
|
+
top_level_object = node_mapping.bindings.bindings.get(top_level_key)
|
559
|
+
if not isinstance(top_level_object, NodeObject):
|
560
|
+
raise RuntimeError()
|
561
|
+
target_map_value = top_level_object.bindings.get(second_level_key)
|
562
|
+
return target_map_value.change_type
|
563
|
+
|
458
564
|
def _resolve_intrinsic_function_fn_if(self, arguments: ChangeSetEntity) -> ChangeType:
|
459
565
|
# TODO: validate arguments structure and type.
|
460
566
|
if not isinstance(arguments, NodeArray) or not arguments.array:
|
@@ -507,17 +613,9 @@ class ChangeSetModel:
|
|
507
613
|
binding_scope, (before_value, after_value) = self._safe_access_in(
|
508
614
|
scope, binding_name, before_object, after_object
|
509
615
|
)
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
intrinsic_function=binding_name,
|
514
|
-
before_arguments=before_value,
|
515
|
-
after_arguments=after_value,
|
516
|
-
)
|
517
|
-
else:
|
518
|
-
value = self._visit_value(
|
519
|
-
scope=binding_scope, before_value=before_value, after_value=after_value
|
520
|
-
)
|
616
|
+
value = self._visit_value(
|
617
|
+
scope=binding_scope, before_value=before_value, after_value=after_value
|
618
|
+
)
|
521
619
|
bindings[binding_name] = value
|
522
620
|
change_type = change_type.for_child(value.change_type)
|
523
621
|
node_object = NodeObject(scope=scope, change_type=change_type, bindings=bindings)
|
@@ -541,8 +639,11 @@ class ChangeSetModel:
|
|
541
639
|
value = self._visited_scopes.get(scope)
|
542
640
|
if isinstance(value, ChangeSetEntity):
|
543
641
|
return value
|
642
|
+
|
643
|
+
before_type_name = self._type_name_of(before_value)
|
644
|
+
after_type_name = self._type_name_of(after_value)
|
544
645
|
unset = object()
|
545
|
-
if
|
646
|
+
if before_type_name == after_type_name:
|
546
647
|
dominant_value = before_value
|
547
648
|
elif self._is_created(before=before_value, after=after_value):
|
548
649
|
dominant_value = after_value
|
@@ -551,6 +652,7 @@ class ChangeSetModel:
|
|
551
652
|
else:
|
552
653
|
dominant_value = unset
|
553
654
|
if dominant_value is not unset:
|
655
|
+
dominant_type_name = self._type_name_of(dominant_value)
|
554
656
|
if self._is_terminal(value=dominant_value):
|
555
657
|
value = self._visit_terminal_value(
|
556
658
|
scope=scope, before_value=before_value, after_value=after_value
|
@@ -563,6 +665,16 @@ class ChangeSetModel:
|
|
563
665
|
value = self._visit_array(
|
564
666
|
scope=scope, before_array=before_value, after_array=after_value
|
565
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
|
+
)
|
566
678
|
else:
|
567
679
|
raise RuntimeError(f"Unsupported type {type(dominant_value)}")
|
568
680
|
# Case: type divergence.
|
@@ -584,24 +696,24 @@ class ChangeSetModel:
|
|
584
696
|
if isinstance(node_property, NodeProperty):
|
585
697
|
return node_property
|
586
698
|
|
699
|
+
value = self._visit_value(
|
700
|
+
scope=scope, before_value=before_property, after_value=after_property
|
701
|
+
)
|
587
702
|
if self._is_created(before=before_property, after=after_property):
|
588
703
|
node_property = NodeProperty(
|
589
704
|
scope=scope,
|
590
705
|
change_type=ChangeType.CREATED,
|
591
706
|
name=property_name,
|
592
|
-
value=
|
707
|
+
value=value,
|
593
708
|
)
|
594
709
|
elif self._is_removed(before=before_property, after=after_property):
|
595
710
|
node_property = NodeProperty(
|
596
711
|
scope=scope,
|
597
712
|
change_type=ChangeType.REMOVED,
|
598
713
|
name=property_name,
|
599
|
-
value=
|
714
|
+
value=value,
|
600
715
|
)
|
601
716
|
else:
|
602
|
-
value = self._visit_value(
|
603
|
-
scope=scope, before_value=before_property, after_value=after_property
|
604
|
-
)
|
605
717
|
node_property = NodeProperty(
|
606
718
|
scope=scope, change_type=value.change_type, name=property_name, value=value
|
607
719
|
)
|
@@ -655,14 +767,17 @@ class ChangeSetModel:
|
|
655
767
|
change_type = ChangeType.UNCHANGED
|
656
768
|
|
657
769
|
# TODO: investigate behaviour with type changes, for now this is filler code.
|
658
|
-
_, type_str = self._safe_access_in(scope, TypeKey,
|
770
|
+
_, type_str = self._safe_access_in(scope, TypeKey, after_resource)
|
659
771
|
|
772
|
+
condition_reference = None
|
660
773
|
scope_condition, (before_condition, after_condition) = self._safe_access_in(
|
661
774
|
scope, ConditionKey, before_resource, after_resource
|
662
775
|
)
|
663
|
-
|
664
|
-
|
665
|
-
|
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
|
+
)
|
666
781
|
|
667
782
|
scope_properties, (before_properties, after_properties) = self._safe_access_in(
|
668
783
|
scope, PropertiesKey, before_resource, after_resource
|
@@ -705,6 +820,36 @@ class ChangeSetModel:
|
|
705
820
|
change_type = change_type.for_child(resource.change_type)
|
706
821
|
return NodeResources(scope=scope, change_type=change_type, resources=resources)
|
707
822
|
|
823
|
+
def _visit_mapping(
|
824
|
+
self, scope: Scope, name: str, before_mapping: Maybe[dict], after_mapping: Maybe[dict]
|
825
|
+
) -> NodeMapping:
|
826
|
+
bindings = self._visit_object(
|
827
|
+
scope=scope, before_object=before_mapping, after_object=after_mapping
|
828
|
+
)
|
829
|
+
return NodeMapping(
|
830
|
+
scope=scope, change_type=bindings.change_type, name=name, bindings=bindings
|
831
|
+
)
|
832
|
+
|
833
|
+
def _visit_mappings(
|
834
|
+
self, scope: Scope, before_mappings: Maybe[dict], after_mappings: Maybe[dict]
|
835
|
+
) -> NodeMappings:
|
836
|
+
change_type = ChangeType.UNCHANGED
|
837
|
+
mappings: list[NodeMapping] = list()
|
838
|
+
mapping_names = self._safe_keys_of(before_mappings, after_mappings)
|
839
|
+
for mapping_name in mapping_names:
|
840
|
+
scope_mapping, (before_mapping, after_mapping) = self._safe_access_in(
|
841
|
+
scope, mapping_name, before_mappings, after_mappings
|
842
|
+
)
|
843
|
+
mapping = self._visit_mapping(
|
844
|
+
scope=scope,
|
845
|
+
name=mapping_name,
|
846
|
+
before_mapping=before_mapping,
|
847
|
+
after_mapping=after_mapping,
|
848
|
+
)
|
849
|
+
mappings.append(mapping)
|
850
|
+
change_type = change_type.for_child(mapping.change_type)
|
851
|
+
return NodeMappings(scope=scope, change_type=change_type, mappings=mappings)
|
852
|
+
|
708
853
|
def _visit_dynamic_parameter(self, parameter_name: str) -> ChangeSetEntity:
|
709
854
|
scope = Scope("Dynamic").open_scope("Parameters")
|
710
855
|
scope_parameter, (before_parameter, after_parameter) = self._safe_access_in(
|
@@ -797,18 +942,9 @@ class ChangeSetModel:
|
|
797
942
|
node_condition = self._visited_scopes.get(scope)
|
798
943
|
if isinstance(node_condition, NodeCondition):
|
799
944
|
return node_condition
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
if len(function_names) == 1:
|
804
|
-
body = self._visit_object(
|
805
|
-
scope=scope, before_object=before_condition, after_object=after_condition
|
806
|
-
)
|
807
|
-
else:
|
808
|
-
body = self._visit_divergence(
|
809
|
-
scope=scope, before_value=before_condition, after_value=after_condition
|
810
|
-
)
|
811
|
-
|
945
|
+
body = self._visit_value(
|
946
|
+
scope=scope, before_value=before_condition, after_value=after_condition
|
947
|
+
)
|
812
948
|
node_condition = NodeCondition(
|
813
949
|
scope=scope, change_type=body.change_type, name=condition_name, body=body
|
814
950
|
)
|
@@ -842,9 +978,75 @@ class ChangeSetModel:
|
|
842
978
|
self._visited_scopes[scope] = node_conditions
|
843
979
|
return node_conditions
|
844
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
|
+
|
845
1039
|
def _model(self, before_template: Maybe[dict], after_template: Maybe[dict]) -> NodeTemplate:
|
846
1040
|
root_scope = Scope()
|
847
1041
|
# TODO: visit other child types
|
1042
|
+
|
1043
|
+
mappings_scope, (before_mappings, after_mappings) = self._safe_access_in(
|
1044
|
+
root_scope, MappingsKey, before_template, after_template
|
1045
|
+
)
|
1046
|
+
mappings = self._visit_mappings(
|
1047
|
+
scope=mappings_scope, before_mappings=before_mappings, after_mappings=after_mappings
|
1048
|
+
)
|
1049
|
+
|
848
1050
|
parameters_scope, (before_parameters, after_parameters) = self._safe_access_in(
|
849
1051
|
root_scope, ParametersKey, before_template, after_template
|
850
1052
|
)
|
@@ -872,13 +1074,22 @@ class ChangeSetModel:
|
|
872
1074
|
after_resources=after_resources,
|
873
1075
|
)
|
874
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
|
+
|
875
1084
|
# TODO: compute the change_type of the template properly.
|
876
1085
|
return NodeTemplate(
|
877
1086
|
scope=root_scope,
|
878
1087
|
change_type=resources.change_type,
|
1088
|
+
mappings=mappings,
|
879
1089
|
parameters=parameters,
|
880
1090
|
conditions=conditions,
|
881
1091
|
resources=resources,
|
1092
|
+
outputs=outputs,
|
882
1093
|
)
|
883
1094
|
|
884
1095
|
def _retrieve_condition_if_exists(self, condition_name: str) -> Optional[NodeCondition]:
|
@@ -919,6 +1130,23 @@ class ChangeSetModel:
|
|
919
1130
|
return node_parameter
|
920
1131
|
return None
|
921
1132
|
|
1133
|
+
def _retrieve_mapping(self, mapping_name) -> NodeMapping:
|
1134
|
+
# TODO: add caching mechanism, and raise appropriate error if missing.
|
1135
|
+
scope_mappings, (before_mappings, after_mappings) = self._safe_access_in(
|
1136
|
+
Scope(), MappingsKey, self._before_template, self._after_template
|
1137
|
+
)
|
1138
|
+
before_mappings = before_mappings or dict()
|
1139
|
+
after_mappings = after_mappings or dict()
|
1140
|
+
if mapping_name in before_mappings or mapping_name in after_mappings:
|
1141
|
+
scope_mapping, (before_mapping, after_mapping) = self._safe_access_in(
|
1142
|
+
scope_mappings, mapping_name, before_mappings, after_mappings
|
1143
|
+
)
|
1144
|
+
node_mapping = self._visit_mapping(
|
1145
|
+
scope_mapping, mapping_name, before_mapping, after_mapping
|
1146
|
+
)
|
1147
|
+
return node_mapping
|
1148
|
+
raise RuntimeError()
|
1149
|
+
|
922
1150
|
def _retrieve_or_visit_resource(self, resource_name: str) -> NodeResource:
|
923
1151
|
resources_scope, (before_resources, after_resources) = self._safe_access_in(
|
924
1152
|
Scope(),
|
@@ -974,13 +1202,30 @@ class ChangeSetModel:
|
|
974
1202
|
break
|
975
1203
|
return parent_change_type
|
976
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
|
+
|
977
1222
|
@staticmethod
|
978
1223
|
def _is_terminal(value: Any) -> bool:
|
979
1224
|
return type(value) in {int, float, bool, str, None, NothingType}
|
980
1225
|
|
981
1226
|
@staticmethod
|
982
1227
|
def _is_object(value: Any) -> bool:
|
983
|
-
return isinstance(value, dict)
|
1228
|
+
return isinstance(value, dict) and ChangeSetModel._name_if_intrinsic_function(value) is None
|
984
1229
|
|
985
1230
|
@staticmethod
|
986
1231
|
def _is_array(value: Any) -> bool:
|