localstack-core 4.4.1.dev59__py3-none-any.whl → 4.4.1.dev65__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/aws/api/ec2/__init__.py +70 -2
- localstack/aws/api/s3/__init__.py +2 -0
- localstack/services/cloudformation/engine/v2/change_set_model.py +107 -146
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +41 -22
- localstack/services/cloudformation/engine/v2/change_set_model_executor.py +51 -23
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +244 -123
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +16 -1
- localstack/services/s3/provider.py +3 -2
- localstack/services/sns/provider.py +27 -9
- localstack/testing/pytest/cloudformation/fixtures.py +13 -1
- localstack/version.py +2 -2
- {localstack_core-4.4.1.dev59.dist-info → localstack_core-4.4.1.dev65.dist-info}/METADATA +3 -3
- {localstack_core-4.4.1.dev59.dist-info → localstack_core-4.4.1.dev65.dist-info}/RECORD +21 -21
- localstack_core-4.4.1.dev65.dist-info/plux.json +1 -0
- localstack_core-4.4.1.dev59.dist-info/plux.json +0 -1
- {localstack_core-4.4.1.dev59.data → localstack_core-4.4.1.dev65.data}/scripts/localstack +0 -0
- {localstack_core-4.4.1.dev59.data → localstack_core-4.4.1.dev65.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.4.1.dev59.data → localstack_core-4.4.1.dev65.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.4.1.dev59.dist-info → localstack_core-4.4.1.dev65.dist-info}/WHEEL +0 -0
- {localstack_core-4.4.1.dev59.dist-info → localstack_core-4.4.1.dev65.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.4.1.dev59.dist-info → localstack_core-4.4.1.dev65.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.4.1.dev59.dist-info → localstack_core-4.4.1.dev65.dist-info}/top_level.txt +0 -0
@@ -3,9 +3,15 @@ from __future__ import annotations
|
|
3
3
|
import re
|
4
4
|
from typing import Any, Final, Generic, Optional, TypeVar
|
5
5
|
|
6
|
+
from localstack.services.cloudformation.engine.transformers import (
|
7
|
+
Transformer,
|
8
|
+
execute_macro,
|
9
|
+
transformers,
|
10
|
+
)
|
6
11
|
from localstack.services.cloudformation.engine.v2.change_set_model import (
|
7
12
|
ChangeSetEntity,
|
8
13
|
ChangeType,
|
14
|
+
Maybe,
|
9
15
|
NodeArray,
|
10
16
|
NodeCondition,
|
11
17
|
NodeDependsOn,
|
@@ -20,16 +26,19 @@ from localstack.services.cloudformation.engine.v2.change_set_model import (
|
|
20
26
|
NodeProperty,
|
21
27
|
NodeResource,
|
22
28
|
NodeTemplate,
|
29
|
+
Nothing,
|
23
30
|
Scope,
|
24
31
|
TerminalValue,
|
25
32
|
TerminalValueCreated,
|
26
33
|
TerminalValueModified,
|
27
34
|
TerminalValueRemoved,
|
28
35
|
TerminalValueUnchanged,
|
36
|
+
is_nothing,
|
29
37
|
)
|
30
38
|
from localstack.services.cloudformation.engine.v2.change_set_model_visitor import (
|
31
39
|
ChangeSetModelVisitor,
|
32
40
|
)
|
41
|
+
from localstack.services.cloudformation.stores import get_cloudformation_store
|
33
42
|
from localstack.services.cloudformation.v2.entities import ChangeSet
|
34
43
|
from localstack.utils.aws.arns import get_partition
|
35
44
|
from localstack.utils.urls import localstack_host
|
@@ -52,10 +61,10 @@ TAfter = TypeVar("TAfter")
|
|
52
61
|
|
53
62
|
|
54
63
|
class PreprocEntityDelta(Generic[TBefore, TAfter]):
|
55
|
-
before:
|
56
|
-
after:
|
64
|
+
before: Maybe[TBefore]
|
65
|
+
after: Maybe[TAfter]
|
57
66
|
|
58
|
-
def __init__(self, before:
|
67
|
+
def __init__(self, before: Maybe[TBefore] = Nothing, after: Maybe[TAfter] = Nothing):
|
59
68
|
self.before = before
|
60
69
|
self.after = after
|
61
70
|
|
@@ -168,6 +177,7 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
168
177
|
# TODO: this could be improved with hashmap lookups if the Node contained bindings and not lists.
|
169
178
|
for node_resource in node_template.resources.resources:
|
170
179
|
if node_resource.name == resource_name:
|
180
|
+
self.visit(node_resource)
|
171
181
|
return node_resource
|
172
182
|
raise RuntimeError(f"No resource '{resource_name}' was found")
|
173
183
|
|
@@ -177,6 +187,7 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
177
187
|
# TODO: this could be improved with hashmap lookups if the Node contained bindings and not lists.
|
178
188
|
for node_property in node_resource.properties.properties:
|
179
189
|
if node_property.name == property_name:
|
190
|
+
self.visit(node_property)
|
180
191
|
return node_property
|
181
192
|
return None
|
182
193
|
|
@@ -189,11 +200,9 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
189
200
|
# process the resource if this wasn't processed already. Ideally, values should only
|
190
201
|
# be accessible through delta objects, to ensure computation is always complete at
|
191
202
|
# every level.
|
192
|
-
|
203
|
+
_ = self._get_node_resource_for(
|
193
204
|
resource_name=resource_logical_id, node_template=self._node_template
|
194
205
|
)
|
195
|
-
self.visit(node_resource)
|
196
|
-
|
197
206
|
resolved_resource = resolved_resources.get(resource_logical_id)
|
198
207
|
if resolved_resource is None:
|
199
208
|
raise RuntimeError(
|
@@ -228,25 +237,27 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
228
237
|
# TODO: another scenarios suggesting property lookups might be preferable.
|
229
238
|
for mapping in mappings:
|
230
239
|
if mapping.name == map_name:
|
240
|
+
self.visit(mapping)
|
231
241
|
return mapping
|
232
|
-
|
233
|
-
raise RuntimeError()
|
242
|
+
raise RuntimeError(f"Undefined '{map_name}' mapping")
|
234
243
|
|
235
|
-
def _get_node_parameter_if_exists(self, parameter_name: str) ->
|
244
|
+
def _get_node_parameter_if_exists(self, parameter_name: str) -> Maybe[NodeParameter]:
|
236
245
|
parameters: list[NodeParameter] = self._node_template.parameters.parameters
|
237
246
|
# TODO: another scenarios suggesting property lookups might be preferable.
|
238
247
|
for parameter in parameters:
|
239
248
|
if parameter.name == parameter_name:
|
249
|
+
self.visit(parameter)
|
240
250
|
return parameter
|
241
|
-
return
|
251
|
+
return Nothing
|
242
252
|
|
243
|
-
def _get_node_condition_if_exists(self, condition_name: str) ->
|
253
|
+
def _get_node_condition_if_exists(self, condition_name: str) -> Maybe[NodeCondition]:
|
244
254
|
conditions: list[NodeCondition] = self._node_template.conditions.conditions
|
245
255
|
# TODO: another scenarios suggesting property lookups might be preferable.
|
246
256
|
for condition in conditions:
|
247
257
|
if condition.name == condition_name:
|
258
|
+
self.visit(condition)
|
248
259
|
return condition
|
249
|
-
return
|
260
|
+
return Nothing
|
250
261
|
|
251
262
|
def _resolve_condition(self, logical_id: str) -> PreprocEntityDelta:
|
252
263
|
node_condition = self._get_node_condition_if_exists(condition_name=logical_id)
|
@@ -270,14 +281,9 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
270
281
|
case "AWS::URLSuffix":
|
271
282
|
return _AWS_URL_SUFFIX
|
272
283
|
case "AWS::NoValue":
|
273
|
-
|
274
|
-
raise NotImplementedError("The use of AWS:NoValue is currently unsupported")
|
275
|
-
case "AWS::NotificationARNs":
|
276
|
-
raise NotImplementedError(
|
277
|
-
"The use of AWS::NotificationARNs is currently unsupported"
|
278
|
-
)
|
284
|
+
return None
|
279
285
|
case _:
|
280
|
-
raise RuntimeError(f"
|
286
|
+
raise RuntimeError(f"The use of '{pseudo_parameter_name}' is currently unsupported")
|
281
287
|
|
282
288
|
def _resolve_reference(self, logical_id: str) -> PreprocEntityDelta:
|
283
289
|
if logical_id in _PSEUDO_PARAMETERS:
|
@@ -313,11 +319,12 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
313
319
|
return mapping_value_delta
|
314
320
|
|
315
321
|
def visit(self, change_set_entity: ChangeSetEntity) -> PreprocEntityDelta:
|
316
|
-
|
317
|
-
if
|
322
|
+
scope = change_set_entity.scope
|
323
|
+
if scope in self._processed:
|
324
|
+
delta = self._processed[scope]
|
318
325
|
return delta
|
319
326
|
delta = super().visit(change_set_entity=change_set_entity)
|
320
|
-
self._processed[
|
327
|
+
self._processed[scope] = delta
|
321
328
|
return delta
|
322
329
|
|
323
330
|
def visit_terminal_value_modified(
|
@@ -352,35 +359,35 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
352
359
|
return PreprocEntityDelta(before=before_delta.before, after=after_delta.after)
|
353
360
|
|
354
361
|
def visit_node_object(self, node_object: NodeObject) -> PreprocEntityDelta:
|
355
|
-
|
356
|
-
|
362
|
+
node_change_type = node_object.change_type
|
363
|
+
before = dict() if node_change_type != ChangeType.CREATED else Nothing
|
364
|
+
after = dict() if node_change_type != ChangeType.REMOVED else Nothing
|
357
365
|
for name, change_set_entity in node_object.bindings.items():
|
358
366
|
delta: PreprocEntityDelta = self.visit(change_set_entity=change_set_entity)
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
case ChangeType.REMOVED:
|
366
|
-
before[name] = delta.before
|
367
|
-
case ChangeType.UNCHANGED:
|
368
|
-
before[name] = delta.before
|
369
|
-
after[name] = delta.before
|
367
|
+
delta_before = delta.before
|
368
|
+
delta_after = delta.after
|
369
|
+
if not is_nothing(before) and not is_nothing(delta_before) and delta_before is not None:
|
370
|
+
before[name] = delta_before
|
371
|
+
if not is_nothing(after) and not is_nothing(delta_after) and delta_after is not None:
|
372
|
+
after[name] = delta_after
|
370
373
|
return PreprocEntityDelta(before=before, after=after)
|
371
374
|
|
372
375
|
def visit_node_intrinsic_function_fn_get_att(
|
373
376
|
self, node_intrinsic_function: NodeIntrinsicFunction
|
374
377
|
) -> PreprocEntityDelta:
|
375
|
-
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
376
378
|
# TODO: validate the return value according to the spec.
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
379
|
+
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
380
|
+
before_argument: Maybe[list[str]] = arguments_delta.before
|
381
|
+
if isinstance(before_argument, str):
|
382
|
+
before_argument = before_argument.split(".")
|
383
|
+
after_argument: Maybe[list[str]] = arguments_delta.after
|
384
|
+
if isinstance(after_argument, str):
|
385
|
+
after_argument = after_argument.split(".")
|
386
|
+
|
387
|
+
before = Nothing
|
388
|
+
if before_argument:
|
389
|
+
before_logical_name_of_resource = before_argument[0]
|
390
|
+
before_attribute_name = before_argument[1]
|
384
391
|
|
385
392
|
before_node_resource = self._get_node_resource_for(
|
386
393
|
resource_name=before_logical_name_of_resource, node_template=self._node_template
|
@@ -400,10 +407,10 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
400
407
|
property_name=before_attribute_name,
|
401
408
|
)
|
402
409
|
|
403
|
-
after =
|
404
|
-
if
|
405
|
-
after_logical_name_of_resource =
|
406
|
-
after_attribute_name =
|
410
|
+
after = Nothing
|
411
|
+
if after_argument:
|
412
|
+
after_logical_name_of_resource = after_argument[0]
|
413
|
+
after_attribute_name = after_argument[1]
|
407
414
|
after_node_resource = self._get_node_resource_for(
|
408
415
|
resource_name=after_logical_name_of_resource, node_template=self._node_template
|
409
416
|
)
|
@@ -430,10 +437,10 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
430
437
|
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
431
438
|
before_values = arguments_delta.before
|
432
439
|
after_values = arguments_delta.after
|
433
|
-
before =
|
440
|
+
before = Nothing
|
434
441
|
if before_values:
|
435
442
|
before = before_values[0] == before_values[1]
|
436
|
-
after =
|
443
|
+
after = Nothing
|
437
444
|
if after_values:
|
438
445
|
after = after_values[0] == after_values[1]
|
439
446
|
return PreprocEntityDelta(before=before, after=after)
|
@@ -442,6 +449,8 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
442
449
|
self, node_intrinsic_function: NodeIntrinsicFunction
|
443
450
|
) -> PreprocEntityDelta:
|
444
451
|
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
452
|
+
arguments_before = arguments_delta.before
|
453
|
+
arguments_after = arguments_delta.after
|
445
454
|
|
446
455
|
def _compute_delta_for_if_statement(args: list[Any]) -> PreprocEntityDelta:
|
447
456
|
condition_name = args[0]
|
@@ -452,10 +461,14 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
452
461
|
)
|
453
462
|
|
454
463
|
# TODO: add support for this being created or removed.
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
464
|
+
before = Nothing
|
465
|
+
if not is_nothing(arguments_before):
|
466
|
+
before_outcome_delta = _compute_delta_for_if_statement(arguments_before)
|
467
|
+
before = before_outcome_delta.before
|
468
|
+
after = Nothing
|
469
|
+
if not is_nothing(arguments_after):
|
470
|
+
after_outcome_delta = _compute_delta_for_if_statement(arguments_after)
|
471
|
+
after = after_outcome_delta.after
|
459
472
|
return PreprocEntityDelta(before=before, after=after)
|
460
473
|
|
461
474
|
def visit_node_intrinsic_function_fn_not(
|
@@ -464,20 +477,89 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
464
477
|
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
465
478
|
before_condition = arguments_delta.before
|
466
479
|
after_condition = arguments_delta.after
|
467
|
-
|
480
|
+
before = Nothing
|
481
|
+
if not is_nothing(before_condition):
|
468
482
|
before_condition_outcome = before_condition[0]
|
469
483
|
before = not before_condition_outcome
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
if after_condition:
|
484
|
+
after = Nothing
|
485
|
+
if not is_nothing(after_condition):
|
474
486
|
after_condition_outcome = after_condition[0]
|
475
487
|
after = not after_condition_outcome
|
476
|
-
else:
|
477
|
-
after = None
|
478
488
|
# Implicit change type computation.
|
479
489
|
return PreprocEntityDelta(before=before, after=after)
|
480
490
|
|
491
|
+
def _compute_fn_transform(self, args: dict[str, Any]) -> Any:
|
492
|
+
# TODO: add typing to arguments before this level.
|
493
|
+
# TODO: add schema validation
|
494
|
+
# TODO: add support for other transform types
|
495
|
+
|
496
|
+
account_id = self._change_set.account_id
|
497
|
+
region_name = self._change_set.region_name
|
498
|
+
transform_name: str = args.get("Name")
|
499
|
+
if not isinstance(transform_name, str):
|
500
|
+
raise RuntimeError("Invalid or missing Fn::Transform 'Name' argument")
|
501
|
+
transform_parameters: dict = args.get("Parameters")
|
502
|
+
if not isinstance(transform_parameters, dict):
|
503
|
+
raise RuntimeError("Invalid or missing Fn::Transform 'Parameters' argument")
|
504
|
+
|
505
|
+
if transform_name in transformers:
|
506
|
+
# TODO: port and refactor this 'transformers' logic to this package.
|
507
|
+
builtin_transformer_class = transformers[transform_name]
|
508
|
+
builtin_transformer: Transformer = builtin_transformer_class()
|
509
|
+
transform_output: Any = builtin_transformer.transform(
|
510
|
+
account_id=account_id, region_name=region_name, parameters=transform_parameters
|
511
|
+
)
|
512
|
+
return transform_output
|
513
|
+
|
514
|
+
macros_store = get_cloudformation_store(
|
515
|
+
account_id=account_id, region_name=region_name
|
516
|
+
).macros
|
517
|
+
if transform_name in macros_store:
|
518
|
+
# TODO: this formatting of stack parameters is odd but required to integrate with v1 execute_macro util.
|
519
|
+
# consider porting this utils and passing the plain list of parameters instead.
|
520
|
+
stack_parameters = {
|
521
|
+
parameter["ParameterKey"]: parameter
|
522
|
+
for parameter in self._change_set.stack.parameters
|
523
|
+
}
|
524
|
+
transform_output: Any = execute_macro(
|
525
|
+
account_id=account_id,
|
526
|
+
region_name=region_name,
|
527
|
+
parsed_template=dict(), # TODO: review the requirements for this argument.
|
528
|
+
macro=args, # TODO: review support for non dict bindings (v1).
|
529
|
+
stack_parameters=stack_parameters,
|
530
|
+
transformation_parameters=transform_parameters,
|
531
|
+
is_intrinsic=True,
|
532
|
+
)
|
533
|
+
return transform_output
|
534
|
+
|
535
|
+
raise RuntimeError(
|
536
|
+
f"Unsupported transform function '{transform_name}' in '{self._change_set.stack.stack_name}'"
|
537
|
+
)
|
538
|
+
|
539
|
+
def visit_node_intrinsic_function_fn_transform(
|
540
|
+
self, node_intrinsic_function: NodeIntrinsicFunction
|
541
|
+
) -> PreprocEntityDelta:
|
542
|
+
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
543
|
+
arguments_before = arguments_delta.before
|
544
|
+
arguments_after = arguments_delta.after
|
545
|
+
|
546
|
+
# TODO: review the use of cache in self.precessed from the 'before' run to
|
547
|
+
# ensure changes to the lambda (such as after UpdateFunctionCode) do not
|
548
|
+
# generalise tot he before value at this depth (thus making it seems as
|
549
|
+
# though for this transformation before==after). Another options may be to
|
550
|
+
# have specialised caching for transformations.
|
551
|
+
|
552
|
+
# TODO: add tests to review the behaviour of CFN with changes to transformation
|
553
|
+
# function code and no changes to the template.
|
554
|
+
|
555
|
+
before = Nothing
|
556
|
+
if not is_nothing(arguments_before):
|
557
|
+
before = self._compute_fn_transform(args=arguments_before)
|
558
|
+
after = Nothing
|
559
|
+
if not is_nothing(arguments_after):
|
560
|
+
after = self._compute_fn_transform(args=arguments_after)
|
561
|
+
return PreprocEntityDelta(before=before, after=after)
|
562
|
+
|
481
563
|
def visit_node_intrinsic_function_fn_sub(
|
482
564
|
self, node_intrinsic_function: NodeIntrinsicFunction
|
483
565
|
) -> PreprocEntityDelta:
|
@@ -516,10 +598,12 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
516
598
|
template_variable_value = sub_parameters[template_variable_name]
|
517
599
|
else:
|
518
600
|
try:
|
519
|
-
|
601
|
+
resource_delta = self._resolve_reference(logical_id=template_variable_name)
|
520
602
|
template_variable_value = (
|
521
|
-
|
603
|
+
resource_delta.before if select_before else resource_delta.after
|
522
604
|
)
|
605
|
+
if isinstance(template_variable_value, PreprocResource):
|
606
|
+
template_variable_value = template_variable_value.logical_id
|
523
607
|
except RuntimeError:
|
524
608
|
raise RuntimeError(
|
525
609
|
f"Undefined variable name in Fn::Sub string template '{template_variable_name}'"
|
@@ -529,19 +613,11 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
529
613
|
)
|
530
614
|
return sub_string
|
531
615
|
|
532
|
-
before =
|
533
|
-
if (
|
534
|
-
isinstance(arguments_before, str)
|
535
|
-
or isinstance(arguments_before, list)
|
536
|
-
and len(arguments_before) == 2
|
537
|
-
):
|
616
|
+
before = Nothing
|
617
|
+
if not is_nothing(arguments_before):
|
538
618
|
before = _compute_sub(args=arguments_before, select_before=True)
|
539
|
-
after =
|
540
|
-
if (
|
541
|
-
isinstance(arguments_after, str)
|
542
|
-
or isinstance(arguments_after, list)
|
543
|
-
and len(arguments_after) == 2
|
544
|
-
):
|
619
|
+
after = Nothing
|
620
|
+
if not is_nothing(arguments_after):
|
545
621
|
after = _compute_sub(args=arguments_after)
|
546
622
|
return PreprocEntityDelta(before=before, after=after)
|
547
623
|
|
@@ -558,18 +634,47 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
558
634
|
delimiter: str = str(args[0])
|
559
635
|
values: list[Any] = args[1]
|
560
636
|
if not isinstance(values, list):
|
561
|
-
raise RuntimeError("Invalid arguments list definition for Fn::Join")
|
637
|
+
raise RuntimeError(f"Invalid arguments list definition for Fn::Join: '{args}'")
|
562
638
|
join_result = delimiter.join(map(str, values))
|
563
639
|
return join_result
|
564
640
|
|
565
|
-
before =
|
641
|
+
before = Nothing
|
566
642
|
if isinstance(arguments_before, list) and len(arguments_before) == 2:
|
567
643
|
before = _compute_join(arguments_before)
|
568
|
-
after =
|
644
|
+
after = Nothing
|
569
645
|
if isinstance(arguments_after, list) and len(arguments_after) == 2:
|
570
646
|
after = _compute_join(arguments_after)
|
571
647
|
return PreprocEntityDelta(before=before, after=after)
|
572
648
|
|
649
|
+
def visit_node_intrinsic_function_fn_select(
|
650
|
+
self, node_intrinsic_function: NodeIntrinsicFunction
|
651
|
+
):
|
652
|
+
# TODO: add further support for schema validation
|
653
|
+
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
654
|
+
arguments_before = arguments_delta.before
|
655
|
+
arguments_after = arguments_delta.after
|
656
|
+
|
657
|
+
def _compute_fn_select(args: list[Any]) -> Any:
|
658
|
+
values: list[Any] = args[1]
|
659
|
+
if not isinstance(values, list) or not values:
|
660
|
+
raise RuntimeError(f"Invalid arguments list value for Fn::Select: '{values}'")
|
661
|
+
values_len = len(values)
|
662
|
+
index: int = int(args[0])
|
663
|
+
if not isinstance(index, int) or index < 0 or index > values_len:
|
664
|
+
raise RuntimeError(f"Invalid or out of range index value for Fn::Select: '{index}'")
|
665
|
+
selection = values[index]
|
666
|
+
return selection
|
667
|
+
|
668
|
+
before = Nothing
|
669
|
+
if not is_nothing(arguments_before):
|
670
|
+
before = _compute_fn_select(arguments_before)
|
671
|
+
|
672
|
+
after = Nothing
|
673
|
+
if not is_nothing(arguments_after):
|
674
|
+
after = _compute_fn_select(arguments_after)
|
675
|
+
|
676
|
+
return PreprocEntityDelta(before=before, after=after)
|
677
|
+
|
573
678
|
def visit_node_intrinsic_function_fn_find_in_map(
|
574
679
|
self, node_intrinsic_function: NodeIntrinsicFunction
|
575
680
|
) -> PreprocEntityDelta:
|
@@ -577,16 +682,14 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
577
682
|
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
578
683
|
before_arguments = arguments_delta.before
|
579
684
|
after_arguments = arguments_delta.after
|
685
|
+
before = Nothing
|
580
686
|
if before_arguments:
|
581
687
|
before_value_delta = self._resolve_mapping(*before_arguments)
|
582
688
|
before = before_value_delta.before
|
583
|
-
|
584
|
-
before = None
|
689
|
+
after = Nothing
|
585
690
|
if after_arguments:
|
586
691
|
after_value_delta = self._resolve_mapping(*after_arguments)
|
587
692
|
after = after_value_delta.after
|
588
|
-
else:
|
589
|
-
after = None
|
590
693
|
return PreprocEntityDelta(before=before, after=after)
|
591
694
|
|
592
695
|
def visit_node_mapping(self, node_mapping: NodeMapping) -> PreprocEntityDelta:
|
@@ -641,15 +744,15 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
641
744
|
after_logical_id = arguments_delta.after
|
642
745
|
|
643
746
|
# TODO: extend this to support references to other types.
|
644
|
-
before =
|
645
|
-
if
|
747
|
+
before = Nothing
|
748
|
+
if not is_nothing(before_logical_id):
|
646
749
|
before_delta = self._resolve_reference(logical_id=before_logical_id)
|
647
750
|
before = before_delta.before
|
648
751
|
if isinstance(before, PreprocResource):
|
649
752
|
before = before.physical_resource_id
|
650
753
|
|
651
|
-
after =
|
652
|
-
if
|
754
|
+
after = Nothing
|
755
|
+
if not is_nothing(after_logical_id):
|
653
756
|
after_delta = self._resolve_reference(logical_id=after_logical_id)
|
654
757
|
after = after_delta.after
|
655
758
|
if isinstance(after, PreprocResource):
|
@@ -658,14 +761,17 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
658
761
|
return PreprocEntityDelta(before=before, after=after)
|
659
762
|
|
660
763
|
def visit_node_array(self, node_array: NodeArray) -> PreprocEntityDelta:
|
661
|
-
|
662
|
-
|
764
|
+
node_change_type = node_array.change_type
|
765
|
+
before = list() if node_change_type != ChangeType.CREATED else Nothing
|
766
|
+
after = list() if node_change_type != ChangeType.REMOVED else Nothing
|
663
767
|
for change_set_entity in node_array.array:
|
664
768
|
delta: PreprocEntityDelta = self.visit(change_set_entity=change_set_entity)
|
665
|
-
|
666
|
-
|
667
|
-
if
|
668
|
-
|
769
|
+
delta_before = delta.before
|
770
|
+
delta_after = delta.after
|
771
|
+
if not is_nothing(before) and not is_nothing(delta_before):
|
772
|
+
before.append(delta_before)
|
773
|
+
if not is_nothing(after) and not is_nothing(delta_after):
|
774
|
+
after.append(delta_after)
|
669
775
|
return PreprocEntityDelta(before=before, after=after)
|
670
776
|
|
671
777
|
def visit_node_property(self, node_property: NodeProperty) -> PreprocEntityDelta:
|
@@ -674,29 +780,44 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
674
780
|
def visit_node_properties(
|
675
781
|
self, node_properties: NodeProperties
|
676
782
|
) -> PreprocEntityDelta[PreprocProperties, PreprocProperties]:
|
677
|
-
|
678
|
-
|
783
|
+
node_change_type = node_properties.change_type
|
784
|
+
before_bindings = dict() if node_change_type != ChangeType.CREATED else Nothing
|
785
|
+
after_bindings = dict() if node_change_type != ChangeType.REMOVED else Nothing
|
679
786
|
for node_property in node_properties.properties:
|
680
|
-
delta = self.visit(node_property)
|
681
787
|
property_name = node_property.name
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
788
|
+
delta = self.visit(node_property)
|
789
|
+
delta_before = delta.before
|
790
|
+
delta_after = delta.after
|
791
|
+
if (
|
792
|
+
not is_nothing(before_bindings)
|
793
|
+
and not is_nothing(delta_before)
|
794
|
+
and delta_before is not None
|
795
|
+
):
|
796
|
+
before_bindings[property_name] = delta_before
|
797
|
+
if (
|
798
|
+
not is_nothing(after_bindings)
|
799
|
+
and not is_nothing(delta_after)
|
800
|
+
and delta_after is not None
|
801
|
+
):
|
802
|
+
after_bindings[property_name] = delta_after
|
803
|
+
before = Nothing
|
804
|
+
if not is_nothing(before_bindings):
|
805
|
+
before = PreprocProperties(properties=before_bindings)
|
806
|
+
after = Nothing
|
807
|
+
if not is_nothing(after_bindings):
|
808
|
+
after = PreprocProperties(properties=after_bindings)
|
688
809
|
return PreprocEntityDelta(before=before, after=after)
|
689
810
|
|
690
811
|
def _resolve_resource_condition_reference(self, reference: TerminalValue) -> PreprocEntityDelta:
|
691
812
|
reference_delta = self.visit(reference)
|
692
813
|
before_reference = reference_delta.before
|
693
|
-
before =
|
694
|
-
if before_reference
|
814
|
+
before = Nothing
|
815
|
+
if isinstance(before_reference, str):
|
695
816
|
before_delta = self._resolve_condition(logical_id=before_reference)
|
696
817
|
before = before_delta.before
|
697
|
-
after =
|
818
|
+
after = Nothing
|
698
819
|
after_reference = reference_delta.after
|
699
|
-
if after_reference
|
820
|
+
if isinstance(after_reference, str):
|
700
821
|
after_delta = self._resolve_condition(logical_id=after_reference)
|
701
822
|
after = after_delta.after
|
702
823
|
return PreprocEntityDelta(before=before, after=after)
|
@@ -705,19 +826,19 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
705
826
|
self, node_resource: NodeResource
|
706
827
|
) -> PreprocEntityDelta[PreprocResource, PreprocResource]:
|
707
828
|
change_type = node_resource.change_type
|
708
|
-
condition_before =
|
709
|
-
condition_after =
|
710
|
-
if node_resource.condition_reference
|
829
|
+
condition_before = Nothing
|
830
|
+
condition_after = Nothing
|
831
|
+
if not is_nothing(node_resource.condition_reference):
|
711
832
|
condition_delta = self._resolve_resource_condition_reference(
|
712
833
|
node_resource.condition_reference
|
713
834
|
)
|
714
835
|
condition_before = condition_delta.before
|
715
836
|
condition_after = condition_delta.after
|
716
837
|
|
717
|
-
depends_on_before =
|
718
|
-
depends_on_after =
|
719
|
-
if node_resource.depends_on
|
720
|
-
depends_on_delta = self.
|
838
|
+
depends_on_before = Nothing
|
839
|
+
depends_on_after = Nothing
|
840
|
+
if not is_nothing(node_resource.depends_on):
|
841
|
+
depends_on_delta = self.visit(node_resource.depends_on)
|
721
842
|
depends_on_before = depends_on_delta.before
|
722
843
|
depends_on_after = depends_on_delta.after
|
723
844
|
|
@@ -726,9 +847,9 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
726
847
|
node_resource.properties
|
727
848
|
)
|
728
849
|
|
729
|
-
before =
|
730
|
-
after =
|
731
|
-
if change_type != ChangeType.CREATED and condition_before
|
850
|
+
before = Nothing
|
851
|
+
after = Nothing
|
852
|
+
if change_type != ChangeType.CREATED and is_nothing(condition_before) or condition_before:
|
732
853
|
logical_resource_id = node_resource.name
|
733
854
|
before_physical_resource_id = self._before_resource_physical_id(
|
734
855
|
resource_logical_id=logical_resource_id
|
@@ -741,7 +862,7 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
741
862
|
properties=properties_delta.before,
|
742
863
|
depends_on=depends_on_before,
|
743
864
|
)
|
744
|
-
if change_type != ChangeType.REMOVED and condition_after
|
865
|
+
if change_type != ChangeType.REMOVED and is_nothing(condition_after) or condition_after:
|
745
866
|
logical_resource_id = node_resource.name
|
746
867
|
try:
|
747
868
|
after_physical_resource_id = self._after_resource_physical_id(
|
@@ -765,8 +886,8 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
765
886
|
change_type = node_output.change_type
|
766
887
|
value_delta = self.visit(node_output.value)
|
767
888
|
|
768
|
-
condition_delta =
|
769
|
-
if node_output.condition_reference
|
889
|
+
condition_delta = Nothing
|
890
|
+
if not is_nothing(node_output.condition_reference):
|
770
891
|
condition_delta = self._resolve_resource_condition_reference(
|
771
892
|
node_output.condition_reference
|
772
893
|
)
|
@@ -777,11 +898,11 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
777
898
|
elif condition_before and not condition_after:
|
778
899
|
change_type = ChangeType.REMOVED
|
779
900
|
|
780
|
-
export_delta =
|
781
|
-
if node_output.export
|
901
|
+
export_delta = Nothing
|
902
|
+
if not is_nothing(node_output.export):
|
782
903
|
export_delta = self.visit(node_output.export)
|
783
904
|
|
784
|
-
before:
|
905
|
+
before: Maybe[PreprocOutput] = Nothing
|
785
906
|
if change_type != ChangeType.CREATED:
|
786
907
|
before = PreprocOutput(
|
787
908
|
name=node_output.name,
|
@@ -789,7 +910,7 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
789
910
|
export=export_delta.before if export_delta else None,
|
790
911
|
condition=condition_delta.before if condition_delta else None,
|
791
912
|
)
|
792
|
-
after:
|
913
|
+
after: Maybe[PreprocOutput] = Nothing
|
793
914
|
if change_type != ChangeType.REMOVED:
|
794
915
|
after = PreprocOutput(
|
795
916
|
name=node_output.name,
|
@@ -808,8 +929,8 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
808
929
|
output_delta: PreprocEntityDelta[PreprocOutput, PreprocOutput] = self.visit(node_output)
|
809
930
|
output_before = output_delta.before
|
810
931
|
output_after = output_delta.after
|
811
|
-
if output_before:
|
932
|
+
if not is_nothing(output_before):
|
812
933
|
before.append(output_before)
|
813
|
-
if output_after:
|
934
|
+
if not is_nothing(output_after):
|
814
935
|
after.append(output_after)
|
815
936
|
return PreprocEntityDelta(before=before, after=after)
|