|
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,
|
|
30 | 35 | from localstack.services.cloudformation.engine.v2.change_set_model_visitor import (
|
31 | 36 | ChangeSetModelVisitor,
|
32 | 37 | )
|
| 38 | +from localstack.services.cloudformation.stores import get_cloudformation_store |
33 | 39 | from localstack.services.cloudformation.v2.entities import ChangeSet
|
34 | 40 | from localstack.utils.aws.arns import get_partition
|
35 | 41 | from localstack.utils.urls import localstack_host
|
@@ -489,6 +495,78 @@ def visit_node_intrinsic_function_fn_not(
|
489 | 495 | # Implicit change type computation.
|
490 | 496 | return PreprocEntityDelta(before=before, after=after)
|
491 | 497 |
|
| 498 | + def _compute_fn_transform(self, args: dict[str, Any]) -> Any: |
| 499 | + # TODO: add typing to arguments before this level. |
| 500 | + # TODO: add schema validation |
| 501 | + # TODO: add support for other transform types |
| 502 | + |
| 503 | + account_id = self._change_set.account_id |
| 504 | + region_name = self._change_set.region_name |
| 505 | + transform_name: str = args.get("Name") |
| 506 | + if not isinstance(transform_name, str): |
| 507 | + raise RuntimeError("Invalid or missing Fn::Transform 'Name' argument") |
| 508 | + transform_parameters: dict = args.get("Parameters") |
| 509 | + if not isinstance(transform_parameters, dict): |
| 510 | + raise RuntimeError("Invalid or missing Fn::Transform 'Parameters' argument") |
| 511 | + |
| 512 | + if transform_name in transformers: |
| 513 | + # TODO: port and refactor this 'transformers' logic to this package. |
| 514 | + builtin_transformer_class = transformers[transform_name] |
| 515 | + builtin_transformer: Transformer = builtin_transformer_class() |
| 516 | + transform_output: Any = builtin_transformer.transform( |
| 517 | + account_id=account_id, region_name=region_name, parameters=transform_parameters |
| 518 | + ) |
| 519 | + return transform_output |
| 520 | + |
| 521 | + macros_store = get_cloudformation_store( |
| 522 | + account_id=account_id, region_name=region_name |
| 523 | + ).macros |
| 524 | + if transform_name in macros_store: |
| 525 | + # TODO: this formatting of stack parameters is odd but required to integrate with v1 execute_macro util. |
| 526 | + # consider porting this utils and passing the plain list of parameters instead. |
| 527 | + stack_parameters = { |
| 528 | + parameter["ParameterKey"]: parameter |
| 529 | + for parameter in self._change_set.stack.parameters |
| 530 | + } |
| 531 | + transform_output: Any = execute_macro( |
| 532 | + account_id=account_id, |
| 533 | + region_name=region_name, |
| 534 | + parsed_template=dict(), # TODO: review the requirements for this argument. |
| 535 | + macro=args, # TODO: review support for non dict bindings (v1). |
| 536 | + stack_parameters=stack_parameters, |
| 537 | + transformation_parameters=transform_parameters, |
| 538 | + is_intrinsic=True, |
| 539 | + ) |
| 540 | + return transform_output |
| 541 | + |
| 542 | + raise RuntimeError( |
| 543 | + f"Unsupported transform function '{transform_name}' in '{self._change_set.stack.stack_name}'" |
| 544 | + ) |
| 545 | + |
| 546 | + def visit_node_intrinsic_function_fn_transform( |
| 547 | + self, node_intrinsic_function: NodeIntrinsicFunction |
| 548 | + ) -> PreprocEntityDelta: |
| 549 | + arguments_delta = self.visit(node_intrinsic_function.arguments) |
| 550 | + arguments_before = arguments_delta.before |
| 551 | + arguments_after = arguments_delta.after |
| 552 | + |
| 553 | + # TODO: review the use of cache in self.precessed from the 'before' run to |
| 554 | + # ensure changes to the lambda (such as after UpdateFunctionCode) do not |
| 555 | + # generalise tot he before value at this depth (thus making it seems as |
| 556 | + # though for this transformation before==after). Another options may be to |
| 557 | + # have specialised caching for transformations. |
| 558 | + |
| 559 | + # TODO: add tests to review the behaviour of CFN with changes to transformation |
| 560 | + # function code and no changes to the template. |
| 561 | + |
| 562 | + before = None |
| 563 | + if arguments_before: |
| 564 | + before = self._compute_fn_transform(args=arguments_before) |
| 565 | + after = None |
| 566 | + if arguments_after: |
| 567 | + after = self._compute_fn_transform(args=arguments_after) |
| 568 | + return PreprocEntityDelta(before=before, after=after) |
| 569 | + |
492 | 570 | def visit_node_intrinsic_function_fn_sub(
|
493 | 571 | self, node_intrinsic_function: NodeIntrinsicFunction
|
494 | 572 | ) -> PreprocEntityDelta:
|
|
0 commit comments