localstack-core 4.3.1.dev35__py3-none-any.whl → 4.3.1.dev37__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/config.py +0 -6
- localstack/deprecations.py +14 -0
- localstack/services/cloudformation/engine/entities.py +9 -4
- localstack/services/cloudformation/engine/v2/change_set_model.py +32 -67
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +119 -487
- localstack/services/cloudformation/engine/v2/change_set_model_executor.py +107 -70
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +574 -0
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +6 -6
- localstack/services/cloudformation/v2/provider.py +39 -5
- localstack/services/cloudformation/v2/utils.py +5 -0
- localstack/services/sns/resource_providers/aws_sns_topic.py +1 -0
- localstack/testing/pytest/cloudformation/__init__.py +0 -0
- localstack/testing/pytest/cloudformation/fixtures.py +169 -0
- localstack/version.py +2 -2
- {localstack_core-4.3.1.dev35.dist-info → localstack_core-4.3.1.dev37.dist-info}/METADATA +1 -1
- {localstack_core-4.3.1.dev35.dist-info → localstack_core-4.3.1.dev37.dist-info}/RECORD +24 -20
- localstack_core-4.3.1.dev37.dist-info/plux.json +1 -0
- localstack_core-4.3.1.dev35.dist-info/plux.json +0 -1
- {localstack_core-4.3.1.dev35.data → localstack_core-4.3.1.dev37.data}/scripts/localstack +0 -0
- {localstack_core-4.3.1.dev35.data → localstack_core-4.3.1.dev37.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.3.1.dev35.data → localstack_core-4.3.1.dev37.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.3.1.dev35.dist-info → localstack_core-4.3.1.dev37.dist-info}/WHEEL +0 -0
- {localstack_core-4.3.1.dev35.dist-info → localstack_core-4.3.1.dev37.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.3.1.dev35.dist-info → localstack_core-4.3.1.dev37.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.3.1.dev35.dist-info → localstack_core-4.3.1.dev37.dist-info}/top_level.txt +0 -0
@@ -1,230 +1,50 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import
|
4
|
-
from typing import
|
3
|
+
import json
|
4
|
+
from typing import Final, Optional
|
5
5
|
|
6
6
|
import localstack.aws.api.cloudformation as cfn_api
|
7
7
|
from localstack.services.cloudformation.engine.v2.change_set_model import (
|
8
|
-
ChangeSetEntity,
|
9
|
-
ChangeType,
|
10
|
-
ConditionKey,
|
11
|
-
ExportKey,
|
12
|
-
NodeArray,
|
13
|
-
NodeCondition,
|
14
|
-
NodeDivergence,
|
15
8
|
NodeIntrinsicFunction,
|
16
|
-
NodeMapping,
|
17
|
-
NodeObject,
|
18
|
-
NodeOutput,
|
19
|
-
NodeOutputs,
|
20
|
-
NodeParameter,
|
21
|
-
NodeProperties,
|
22
|
-
NodeProperty,
|
23
9
|
NodeResource,
|
24
10
|
NodeTemplate,
|
25
|
-
NothingType,
|
26
11
|
PropertiesKey,
|
27
|
-
Scope,
|
28
|
-
TerminalValue,
|
29
|
-
TerminalValueCreated,
|
30
|
-
TerminalValueModified,
|
31
|
-
TerminalValueRemoved,
|
32
|
-
TerminalValueUnchanged,
|
33
|
-
ValueKey,
|
34
12
|
)
|
35
|
-
from localstack.services.cloudformation.engine.v2.
|
36
|
-
|
13
|
+
from localstack.services.cloudformation.engine.v2.change_set_model_preproc import (
|
14
|
+
ChangeSetModelPreproc,
|
15
|
+
PreprocEntityDelta,
|
16
|
+
PreprocProperties,
|
17
|
+
PreprocResource,
|
37
18
|
)
|
38
19
|
|
39
20
|
CHANGESET_KNOWN_AFTER_APPLY: Final[str] = "{{changeSet:KNOWN_AFTER_APPLY}}"
|
40
21
|
|
41
22
|
|
42
|
-
class
|
43
|
-
|
44
|
-
after_context: Optional[Any] = None
|
45
|
-
|
46
|
-
def __init__(self, before_context: Optional[Any] = None, after_context: Optional[Any] = None):
|
47
|
-
self.before_context = before_context
|
48
|
-
self.after_context = after_context
|
49
|
-
|
50
|
-
|
51
|
-
class ChangeSetModelDescriber(ChangeSetModelVisitor):
|
52
|
-
_node_template: Final[NodeTemplate]
|
23
|
+
class ChangeSetModelDescriber(ChangeSetModelPreproc):
|
24
|
+
_include_property_values: Final[bool]
|
53
25
|
_changes: Final[cfn_api.Changes]
|
54
|
-
_describe_unit_cache: dict[Scope, DescribeUnit]
|
55
|
-
_include_property_values: Final[cfn_api.IncludePropertyValues | None]
|
56
26
|
|
57
|
-
def __init__(
|
58
|
-
|
59
|
-
node_template: NodeTemplate,
|
60
|
-
include_property_values: cfn_api.IncludePropertyValues | None = None,
|
61
|
-
):
|
62
|
-
self._node_template = node_template
|
63
|
-
self._changes = list()
|
64
|
-
self._describe_unit_cache = dict()
|
27
|
+
def __init__(self, node_template: NodeTemplate, include_property_values: bool):
|
28
|
+
super().__init__(node_template=node_template)
|
65
29
|
self._include_property_values = include_property_values
|
30
|
+
self._changes = list()
|
66
31
|
|
67
32
|
def get_changes(self) -> cfn_api.Changes:
|
68
|
-
self.
|
33
|
+
self._changes.clear()
|
34
|
+
self.process()
|
69
35
|
return self._changes
|
70
36
|
|
71
|
-
@staticmethod
|
72
|
-
def _get_node_resource_for(resource_name: str, node_template: NodeTemplate) -> NodeResource:
|
73
|
-
# TODO: this could be improved with hashmap lookups if the Node contained bindings and not lists.
|
74
|
-
for node_resource in node_template.resources.resources:
|
75
|
-
if node_resource.name == resource_name:
|
76
|
-
return node_resource
|
77
|
-
# TODO
|
78
|
-
raise RuntimeError()
|
79
|
-
|
80
|
-
@staticmethod
|
81
|
-
def _get_node_property_for(property_name: str, node_resource: NodeResource) -> NodeProperty:
|
82
|
-
# TODO: this could be improved with hashmap lookups if the Node contained bindings and not lists.
|
83
|
-
for node_property in node_resource.properties.properties:
|
84
|
-
if node_property.name == property_name:
|
85
|
-
return node_property
|
86
|
-
# TODO
|
87
|
-
raise RuntimeError()
|
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
|
-
|
98
|
-
def _get_node_parameter_if_exists(self, parameter_name: str) -> Optional[NodeParameter]:
|
99
|
-
parameters: list[NodeParameter] = self._node_template.parameters.parameters
|
100
|
-
# TODO: another scenarios suggesting property lookups might be preferable.
|
101
|
-
for parameter in parameters:
|
102
|
-
if parameter.name == parameter_name:
|
103
|
-
return parameter
|
104
|
-
return None
|
105
|
-
|
106
|
-
def _get_node_condition_if_exists(self, condition_name: str) -> Optional[NodeCondition]:
|
107
|
-
conditions: list[NodeCondition] = self._node_template.conditions.conditions
|
108
|
-
# TODO: another scenarios suggesting property lookups might be preferable.
|
109
|
-
for condition in conditions:
|
110
|
-
if condition.name == condition_name:
|
111
|
-
return condition
|
112
|
-
return None
|
113
|
-
|
114
|
-
def _resolve_reference(self, logica_id: str) -> DescribeUnit:
|
115
|
-
node_condition = self._get_node_condition_if_exists(condition_name=logica_id)
|
116
|
-
if isinstance(node_condition, NodeCondition):
|
117
|
-
condition_unit = self.visit(node_condition)
|
118
|
-
return condition_unit
|
119
|
-
|
120
|
-
node_parameter = self._get_node_parameter_if_exists(parameter_name=logica_id)
|
121
|
-
if isinstance(node_parameter, NodeParameter):
|
122
|
-
parameter_unit = self.visit(node_parameter)
|
123
|
-
return parameter_unit
|
124
|
-
|
125
|
-
# TODO: check for KNOWN AFTER APPLY values for logical ids coming from intrinsic functions as arguments.
|
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
|
143
|
-
|
144
|
-
def _resolve_reference_binding(
|
145
|
-
self, before_logical_id: str, after_logical_id: str
|
146
|
-
) -> DescribeUnit:
|
147
|
-
before_unit = self._resolve_reference(logica_id=before_logical_id)
|
148
|
-
after_unit = self._resolve_reference(logica_id=after_logical_id)
|
149
|
-
return DescribeUnit(
|
150
|
-
before_context=before_unit.before_context, after_context=after_unit.after_context
|
151
|
-
)
|
152
|
-
|
153
|
-
def visit(self, change_set_entity: ChangeSetEntity) -> DescribeUnit:
|
154
|
-
describe_unit = self._describe_unit_cache.get(change_set_entity.scope)
|
155
|
-
if describe_unit is not None:
|
156
|
-
return describe_unit
|
157
|
-
describe_unit = super().visit(change_set_entity=change_set_entity)
|
158
|
-
self._describe_unit_cache[change_set_entity.scope] = describe_unit
|
159
|
-
return describe_unit
|
160
|
-
|
161
|
-
def visit_terminal_value_modified(
|
162
|
-
self, terminal_value_modified: TerminalValueModified
|
163
|
-
) -> DescribeUnit:
|
164
|
-
return DescribeUnit(
|
165
|
-
before_context=terminal_value_modified.value,
|
166
|
-
after_context=terminal_value_modified.modified_value,
|
167
|
-
)
|
168
|
-
|
169
|
-
def visit_terminal_value_created(
|
170
|
-
self, terminal_value_created: TerminalValueCreated
|
171
|
-
) -> DescribeUnit:
|
172
|
-
return DescribeUnit(after_context=terminal_value_created.value)
|
173
|
-
|
174
|
-
def visit_terminal_value_removed(
|
175
|
-
self, terminal_value_removed: TerminalValueRemoved
|
176
|
-
) -> DescribeUnit:
|
177
|
-
return DescribeUnit(before_context=terminal_value_removed.value)
|
178
|
-
|
179
|
-
def visit_terminal_value_unchanged(
|
180
|
-
self, terminal_value_unchanged: TerminalValueUnchanged
|
181
|
-
) -> DescribeUnit:
|
182
|
-
return DescribeUnit(
|
183
|
-
before_context=terminal_value_unchanged.value,
|
184
|
-
after_context=terminal_value_unchanged.value,
|
185
|
-
)
|
186
|
-
|
187
|
-
def visit_node_divergence(self, node_divergence: NodeDivergence) -> DescribeUnit:
|
188
|
-
before_unit = self.visit(node_divergence.value)
|
189
|
-
after_unit = self.visit(node_divergence.divergence)
|
190
|
-
return DescribeUnit(
|
191
|
-
before_context=before_unit.before_context, after_context=after_unit.after_context
|
192
|
-
)
|
193
|
-
|
194
|
-
def visit_node_object(self, node_object: NodeObject) -> DescribeUnit:
|
195
|
-
# TODO: improve check syntax
|
196
|
-
if len(node_object.bindings) == 1:
|
197
|
-
binding_values = list(node_object.bindings.values())
|
198
|
-
unique_value = binding_values[0]
|
199
|
-
if isinstance(unique_value, NodeIntrinsicFunction):
|
200
|
-
return self.visit(unique_value)
|
201
|
-
|
202
|
-
before_context = dict()
|
203
|
-
after_context = dict()
|
204
|
-
for name, change_set_entity in node_object.bindings.items():
|
205
|
-
describe_unit: DescribeUnit = self.visit(change_set_entity=change_set_entity)
|
206
|
-
match change_set_entity.change_type:
|
207
|
-
case ChangeType.MODIFIED:
|
208
|
-
before_context[name] = describe_unit.before_context
|
209
|
-
after_context[name] = describe_unit.after_context
|
210
|
-
case ChangeType.CREATED:
|
211
|
-
after_context[name] = describe_unit.after_context
|
212
|
-
case ChangeType.REMOVED:
|
213
|
-
before_context[name] = describe_unit.before_context
|
214
|
-
case ChangeType.UNCHANGED:
|
215
|
-
before_context[name] = describe_unit.before_context
|
216
|
-
after_context[name] = describe_unit.before_context
|
217
|
-
return DescribeUnit(before_context=before_context, after_context=after_context)
|
218
|
-
|
219
37
|
def visit_node_intrinsic_function_fn_get_att(
|
220
38
|
self, node_intrinsic_function: NodeIntrinsicFunction
|
221
|
-
) ->
|
222
|
-
|
223
|
-
#
|
224
|
-
before_argument_list = arguments_unit.before_context
|
225
|
-
after_argument_list = arguments_unit.after_context
|
39
|
+
) -> PreprocEntityDelta:
|
40
|
+
# TODO: If we can properly compute the before and after value, why should we
|
41
|
+
# artificially limit the precision of our output to match AWS's?
|
226
42
|
|
227
|
-
|
43
|
+
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
44
|
+
before_argument_list = arguments_delta.before
|
45
|
+
after_argument_list = arguments_delta.after
|
46
|
+
|
47
|
+
before = None
|
228
48
|
if before_argument_list:
|
229
49
|
before_logical_name_of_resource = before_argument_list[0]
|
230
50
|
before_attribute_name = before_argument_list[1]
|
@@ -234,300 +54,112 @@ class ChangeSetModelDescriber(ChangeSetModelVisitor):
|
|
234
54
|
before_node_property = self._get_node_property_for(
|
235
55
|
property_name=before_attribute_name, node_resource=before_node_resource
|
236
56
|
)
|
237
|
-
|
238
|
-
|
57
|
+
before_property_delta = self.visit(before_node_property)
|
58
|
+
before = before_property_delta.before
|
239
59
|
|
240
|
-
|
60
|
+
after = None
|
241
61
|
if after_argument_list:
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
255
|
-
|
256
|
-
return DescribeUnit(before_context=before_context, after_context=after_context)
|
257
|
-
|
258
|
-
def visit_node_intrinsic_function_fn_equals(
|
259
|
-
self, node_intrinsic_function: NodeIntrinsicFunction
|
260
|
-
) -> DescribeUnit:
|
261
|
-
# TODO: check for KNOWN AFTER APPLY values for logical ids coming from intrinsic functions as arguments.
|
262
|
-
arguments_unit = self.visit(node_intrinsic_function.arguments)
|
263
|
-
before_values = arguments_unit.before_context
|
264
|
-
after_values = arguments_unit.after_context
|
265
|
-
before_context = None
|
266
|
-
if before_values:
|
267
|
-
before_context = before_values[0] == before_values[1]
|
268
|
-
after_context = None
|
269
|
-
if after_values:
|
270
|
-
after_context = after_values[0] == after_values[1]
|
271
|
-
match node_intrinsic_function.change_type:
|
272
|
-
case ChangeType.MODIFIED:
|
273
|
-
return DescribeUnit(before_context=before_context, after_context=after_context)
|
274
|
-
case ChangeType.CREATED:
|
275
|
-
return DescribeUnit(after_context=after_context)
|
276
|
-
case ChangeType.REMOVED:
|
277
|
-
return DescribeUnit(before_context=before_context)
|
278
|
-
# Unchanged
|
279
|
-
return DescribeUnit(before_context=before_context, after_context=after_context)
|
280
|
-
|
281
|
-
def visit_node_intrinsic_function_fn_if(
|
282
|
-
self, node_intrinsic_function: NodeIntrinsicFunction
|
283
|
-
) -> DescribeUnit:
|
284
|
-
# TODO: check for KNOWN AFTER APPLY values for logical ids coming from intrinsic functions as arguments.
|
285
|
-
arguments_unit = self.visit(node_intrinsic_function.arguments)
|
286
|
-
|
287
|
-
def _compute_unit_for_if_statement(args: list[Any]) -> DescribeUnit:
|
288
|
-
condition_name = args[0]
|
289
|
-
boolean_expression_unit = self._resolve_reference(logica_id=condition_name)
|
290
|
-
return DescribeUnit(
|
291
|
-
before_context=args[1] if boolean_expression_unit.before_context else args[2],
|
292
|
-
after_context=args[1] if boolean_expression_unit.after_context else args[2],
|
62
|
+
after_logical_name_of_resource = after_argument_list[0]
|
63
|
+
after_attribute_name = after_argument_list[1]
|
64
|
+
after_node_resource = self._get_node_resource_for(
|
65
|
+
resource_name=after_logical_name_of_resource, node_template=self._node_template
|
293
66
|
)
|
294
|
-
|
295
|
-
|
296
|
-
before_outcome_unit = _compute_unit_for_if_statement(arguments_unit.before_context)
|
297
|
-
before_context = before_outcome_unit.before_context
|
298
|
-
after_outcome_unit = _compute_unit_for_if_statement(arguments_unit.after_context)
|
299
|
-
after_context = after_outcome_unit.after_context
|
300
|
-
return DescribeUnit(before_context=before_context, after_context=after_context)
|
301
|
-
|
302
|
-
def visit_node_intrinsic_function_fn_not(
|
303
|
-
self, node_intrinsic_function: NodeIntrinsicFunction
|
304
|
-
) -> DescribeUnit:
|
305
|
-
# TODO: check for KNOWN AFTER APPLY values for logical ids coming from intrinsic functions as arguments.
|
306
|
-
# TODO: add type checking/validation for result unit?
|
307
|
-
arguments_unit = self.visit(node_intrinsic_function.arguments)
|
308
|
-
before_condition = arguments_unit.before_context
|
309
|
-
after_condition = arguments_unit.after_context
|
310
|
-
if before_condition:
|
311
|
-
before_condition_outcome = before_condition[0]
|
312
|
-
before_context = not before_condition_outcome
|
313
|
-
else:
|
314
|
-
before_context = None
|
315
|
-
|
316
|
-
if after_condition:
|
317
|
-
after_condition_outcome = after_condition[0]
|
318
|
-
after_context = not after_condition_outcome
|
319
|
-
else:
|
320
|
-
after_context = None
|
321
|
-
# Implicit change type computation.
|
322
|
-
return DescribeUnit(before_context=before_context, after_context=after_context)
|
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
|
-
|
348
|
-
def visit_node_parameter(self, node_parameter: NodeParameter) -> DescribeUnit:
|
349
|
-
# TODO: add support for default value sampling
|
350
|
-
dynamic_value = node_parameter.dynamic_value
|
351
|
-
describe_unit = self.visit(dynamic_value)
|
352
|
-
return describe_unit
|
353
|
-
|
354
|
-
def visit_node_condition(self, node_condition: NodeCondition) -> DescribeUnit:
|
355
|
-
describe_unit = self.visit(node_condition.body)
|
356
|
-
return describe_unit
|
357
|
-
|
358
|
-
def visit_node_intrinsic_function_ref(
|
359
|
-
self, node_intrinsic_function: NodeIntrinsicFunction
|
360
|
-
) -> DescribeUnit:
|
361
|
-
arguments_unit = self.visit(node_intrinsic_function.arguments)
|
362
|
-
|
363
|
-
# TODO: add tests with created and deleted parameters and verify this logic holds.
|
364
|
-
before_logical_id = arguments_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
|
369
|
-
|
370
|
-
after_logical_id = arguments_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
|
375
|
-
|
376
|
-
return DescribeUnit(before_context=before_context, after_context=after_context)
|
377
|
-
|
378
|
-
def visit_node_array(self, node_array: NodeArray) -> DescribeUnit:
|
379
|
-
before_context = list()
|
380
|
-
after_context = list()
|
381
|
-
for change_set_entity in node_array.array:
|
382
|
-
describe_unit: DescribeUnit = self.visit(change_set_entity=change_set_entity)
|
383
|
-
match change_set_entity.change_type:
|
384
|
-
case ChangeType.MODIFIED:
|
385
|
-
before_context.append(describe_unit.before_context)
|
386
|
-
after_context.append(describe_unit.after_context)
|
387
|
-
case ChangeType.CREATED:
|
388
|
-
after_context.append(describe_unit.after_context)
|
389
|
-
case ChangeType.REMOVED:
|
390
|
-
before_context.append(describe_unit.before_context)
|
391
|
-
case ChangeType.UNCHANGED:
|
392
|
-
before_context.append(describe_unit.before_context)
|
393
|
-
after_context.append(describe_unit.before_context)
|
394
|
-
return DescribeUnit(before_context=before_context, after_context=after_context)
|
395
|
-
|
396
|
-
def visit_node_properties(self, node_properties: NodeProperties) -> DescribeUnit:
|
397
|
-
before_context: dict[str, Any] = dict()
|
398
|
-
after_context: dict[str, Any] = dict()
|
399
|
-
for node_property in node_properties.properties:
|
400
|
-
describe_unit = self.visit(node_property.value)
|
401
|
-
property_name = node_property.name
|
402
|
-
match node_property.change_type:
|
403
|
-
case ChangeType.MODIFIED:
|
404
|
-
before_context[property_name] = describe_unit.before_context
|
405
|
-
after_context[property_name] = describe_unit.after_context
|
406
|
-
case ChangeType.CREATED:
|
407
|
-
after_context[property_name] = describe_unit.after_context
|
408
|
-
case ChangeType.REMOVED:
|
409
|
-
before_context[property_name] = describe_unit.before_context
|
410
|
-
case ChangeType.UNCHANGED:
|
411
|
-
before_context[property_name] = describe_unit.before_context
|
412
|
-
after_context[property_name] = describe_unit.before_context
|
413
|
-
# TODO: this object can probably be well-typed instead of a free dict(?)
|
414
|
-
before_context = {PropertiesKey: before_context}
|
415
|
-
after_context = {PropertiesKey: after_context}
|
416
|
-
return DescribeUnit(before_context=before_context, after_context=after_context)
|
417
|
-
|
418
|
-
def _resolve_resource_condition_reference(self, reference: TerminalValue) -> DescribeUnit:
|
419
|
-
reference_unit = self.visit(reference)
|
420
|
-
before_reference = reference_unit.before_context
|
421
|
-
after_reference = reference_unit.after_context
|
422
|
-
condition_unit = self._resolve_reference_binding(
|
423
|
-
before_logical_id=before_reference, after_logical_id=after_reference
|
424
|
-
)
|
425
|
-
before_context = (
|
426
|
-
condition_unit.before_context if not isinstance(before_reference, NothingType) else True
|
427
|
-
)
|
428
|
-
after_context = (
|
429
|
-
condition_unit.after_context if not isinstance(after_reference, NothingType) else True
|
430
|
-
)
|
431
|
-
return DescribeUnit(before_context=before_context, after_context=after_context)
|
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
|
67
|
+
after_node_property = self._get_node_property_for(
|
68
|
+
property_name=after_attribute_name, node_resource=after_node_resource
|
443
69
|
)
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
change_type = ChangeType.REMOVED
|
70
|
+
after_property_delta = self.visit(after_node_property)
|
71
|
+
if after_property_delta.before == after_property_delta.after:
|
72
|
+
after = after_property_delta.after
|
73
|
+
else:
|
74
|
+
after = CHANGESET_KNOWN_AFTER_APPLY
|
450
75
|
|
451
|
-
|
452
|
-
if node_output.export is not None:
|
453
|
-
export_unit = self.visit(node_output.export)
|
76
|
+
return PreprocEntityDelta(before=before, after=after)
|
454
77
|
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
if
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
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
|
-
|
486
|
-
def visit_node_resource(self, node_resource: NodeResource) -> DescribeUnit:
|
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
|
78
|
+
def _register_resource_change(
|
79
|
+
self,
|
80
|
+
logical_id: str,
|
81
|
+
type_: str,
|
82
|
+
before_properties: Optional[PreprocProperties],
|
83
|
+
after_properties: Optional[PreprocProperties],
|
84
|
+
) -> None:
|
85
|
+
# unchanged: nothing to do.
|
86
|
+
if before_properties == after_properties:
|
87
|
+
return
|
88
|
+
|
89
|
+
action = cfn_api.ChangeAction.Modify
|
90
|
+
if before_properties is None:
|
91
|
+
action = cfn_api.ChangeAction.Add
|
92
|
+
elif after_properties is None:
|
93
|
+
action = cfn_api.ChangeAction.Remove
|
498
94
|
|
499
95
|
resource_change = cfn_api.ResourceChange()
|
500
|
-
resource_change["
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
96
|
+
resource_change["Action"] = action
|
97
|
+
resource_change["LogicalResourceId"] = logical_id
|
98
|
+
resource_change["ResourceType"] = type_
|
99
|
+
if self._include_property_values and before_properties is not None:
|
100
|
+
before_context_properties = {PropertiesKey: before_properties.properties}
|
101
|
+
before_context_properties_json_str = json.dumps(before_context_properties)
|
102
|
+
resource_change["BeforeContext"] = before_context_properties_json_str
|
103
|
+
if self._include_property_values and after_properties is not None:
|
104
|
+
after_context_properties = {PropertiesKey: after_properties.properties}
|
105
|
+
after_context_properties_json_str = json.dumps(after_context_properties)
|
106
|
+
resource_change["AfterContext"] = after_context_properties_json_str
|
107
|
+
self._changes.append(
|
108
|
+
cfn_api.Change(Type=cfn_api.ChangeType.Resource, ResourceChange=resource_change)
|
506
109
|
)
|
507
110
|
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
111
|
+
def _describe_resource_change(
|
112
|
+
self, name: str, before: Optional[PreprocResource], after: Optional[PreprocResource]
|
113
|
+
) -> None:
|
114
|
+
if before is not None and after is not None:
|
115
|
+
# Case: change on same type.
|
116
|
+
if before.resource_type == after.resource_type:
|
117
|
+
# Register a Modified if changed.
|
118
|
+
self._register_resource_change(
|
119
|
+
logical_id=name,
|
120
|
+
type_=before.resource_type,
|
121
|
+
before_properties=before.properties,
|
122
|
+
after_properties=after.properties,
|
123
|
+
)
|
124
|
+
# Case: type migration.
|
125
|
+
# TODO: Add test to assert that on type change the resources are replaced.
|
126
|
+
else:
|
127
|
+
# Register a Removed for the previous type.
|
128
|
+
self._register_resource_change(
|
129
|
+
logical_id=name,
|
130
|
+
type_=before.resource_type,
|
131
|
+
before_properties=before.properties,
|
132
|
+
after_properties=None,
|
133
|
+
)
|
134
|
+
# Register a Create for the next type.
|
135
|
+
self._register_resource_change(
|
136
|
+
logical_id=name,
|
137
|
+
type_=after.resource_type,
|
138
|
+
before_properties=None,
|
139
|
+
after_properties=after.properties,
|
140
|
+
)
|
141
|
+
elif before is not None:
|
142
|
+
# Case: removal
|
143
|
+
self._register_resource_change(
|
144
|
+
logical_id=name,
|
145
|
+
type_=before.resource_type,
|
146
|
+
before_properties=before.properties,
|
147
|
+
after_properties=None,
|
148
|
+
)
|
149
|
+
elif after is not None:
|
150
|
+
# Case: addition
|
151
|
+
self._register_resource_change(
|
152
|
+
logical_id=name,
|
153
|
+
type_=after.resource_type,
|
154
|
+
before_properties=None,
|
155
|
+
after_properties=after.properties,
|
524
156
|
)
|
525
157
|
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
return
|
158
|
+
def visit_node_resource(
|
159
|
+
self, node_resource: NodeResource
|
160
|
+
) -> PreprocEntityDelta[PreprocResource, PreprocResource]:
|
161
|
+
delta = super().visit_node_resource(node_resource=node_resource)
|
162
|
+
self._describe_resource_change(
|
163
|
+
name=node_resource.name, before=delta.before, after=delta.after
|
164
|
+
)
|
165
|
+
return delta
|