|
8 | 8 | from samtranslator.translator.transform import transform as transform_sam
|
9 | 9 |
|
10 | 10 | from localstack.aws.connect import connect_to
|
| 11 | +from localstack.services.cloudformation.engine import transformers |
11 | 12 | from localstack.services.cloudformation.engine.policy_loader import create_policy_loader
|
12 | 13 | from localstack.services.cloudformation.engine.template_preparer import parse_template
|
13 | 14 | from localstack.services.cloudformation.engine.transformers import (
|
14 | 15 | FailedTransformationException,
|
| 16 | + Transformer, |
15 | 17 | execute_macro,
|
16 | 18 | )
|
17 | 19 | from localstack.services.cloudformation.engine.v2.change_set_model import (
|
18 | 20 | ChangeType,
|
19 | 21 | Maybe,
|
20 | 22 | NodeGlobalTransform,
|
| 23 | + NodeIntrinsicFunctionFnTransform, |
21 | 24 | NodeParameter,
|
22 | 25 | NodeTransform,
|
23 | 26 | Nothing,
|
@@ -313,3 +316,148 @@ def visit_node_transform(
|
313 | 316 | if not is_nothing(after) and not is_nothing(delta_after):
|
314 | 317 | after.append(delta_after)
|
315 | 318 | return PreprocEntityDelta(before=before, after=after)
|
| 319 | + |
| 320 | + def _compute_fn_transform( |
| 321 | + self, macro_definition: Any, siblings: Any, resolved_parameters: Any |
| 322 | + ) -> Any: |
| 323 | + # TODO: add typing to arguments before this level. |
| 324 | + # TODO: add schema validation |
| 325 | + # TODO: add support for other transform types |
| 326 | + |
| 327 | + account_id = self._change_set.account_id |
| 328 | + region_name = self._change_set.region_name |
| 329 | + |
| 330 | + def _normalize_transform(obj): |
| 331 | + transforms = [] |
| 332 | + |
| 333 | + if isinstance(obj, str): |
| 334 | + transforms.append({"Name": obj, "Parameters": {}}) |
| 335 | + |
| 336 | + if isinstance(obj, dict): |
| 337 | + transforms.append(obj) |
| 338 | + |
| 339 | + if isinstance(obj, list): |
| 340 | + for v in obj: |
| 341 | + if isinstance(v, str): |
| 342 | + transforms.append({"Name": v, "Parameters": {}}) |
| 343 | + |
| 344 | + if isinstance(v, dict): |
| 345 | + transforms.append(v) |
| 346 | + |
| 347 | + return transforms |
| 348 | + |
| 349 | + transforms = _normalize_transform(macro_definition) |
| 350 | + transform_output = copy.deepcopy(siblings) |
| 351 | + transform_output.pop("Fn::Transform", "") |
| 352 | + for transform in transforms: |
| 353 | + transform_name = transform["Name"] |
| 354 | + if transform_name in transforms: |
| 355 | + builtin_transformer_class = transformers[transform_name] |
| 356 | + builtin_transformer: Transformer = builtin_transformer_class() |
| 357 | + transform_output.update( |
| 358 | + builtin_transformer.transform( |
| 359 | + account_id=account_id, |
| 360 | + region_name=region_name, |
| 361 | + parameters=transform["Parameters"], |
| 362 | + ) |
| 363 | + ) |
| 364 | + else: |
| 365 | + macros_store = get_cloudformation_store( |
| 366 | + account_id=account_id, region_name=region_name |
| 367 | + ).macros |
| 368 | + |
| 369 | + if transform["Name"] not in macros_store: |
| 370 | + raise RuntimeError("Unsupported transform ") |
| 371 | + |
| 372 | + stack_parameters = { |
| 373 | + **self._change_set.stack.resolved_parameters, |
| 374 | + **self.visit_node_parameters( |
| 375 | + self._change_set.update_model.node_template.parameters |
| 376 | + ).after, |
| 377 | + } |
| 378 | + |
| 379 | + transform_output: Any = execute_macro( |
| 380 | + account_id=account_id, |
| 381 | + region_name=region_name, |
| 382 | + parsed_template=transform_output, # TODO: review the requirements for this argument. |
| 383 | + macro=transform, # TODO: review support for non dict bindings (v1). |
| 384 | + stack_parameters=stack_parameters, |
| 385 | + transformation_parameters=transform.get("Parameters"), |
| 386 | + is_intrinsic=True, |
| 387 | + ) |
| 388 | + |
| 389 | + return transform_output |
| 390 | + |
| 391 | + def _replace_at_jsonpath(self, template, node, result): |
| 392 | + path = node.scope.to_jsonpath() |
| 393 | + parent_path = ".".join(path.split(".")[:-1]) |
| 394 | + import jsonpath_ng |
| 395 | + |
| 396 | + pattern = jsonpath_ng.parse(parent_path) |
| 397 | + result_template = pattern.update(template, result) |
| 398 | + |
| 399 | + return result_template |
| 400 | + |
| 401 | + def visit_node_intrinsic_function_fn_transform( |
| 402 | + self, node_intrinsic_function: NodeIntrinsicFunctionFnTransform |
| 403 | + ) -> PreprocEntityDelta: |
| 404 | + arguments_delta = self.visit(node_intrinsic_function.arguments) |
| 405 | + parameters_delta = self.visit_node_parameters( |
| 406 | + self._change_set.update_model.node_template.parameters |
| 407 | + ) |
| 408 | + |
| 409 | + if not is_nothing(arguments_delta.before): |
| 410 | + before = self._compute_fn_transform( |
| 411 | + arguments_delta.before, |
| 412 | + node_intrinsic_function.before_siblings, |
| 413 | + parameters_delta.before, |
| 414 | + ) |
| 415 | + updated_before_template = self._replace_at_jsonpath( |
| 416 | + self._before_template, node_intrinsic_function, before |
| 417 | + ) |
| 418 | + self._after_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] = updated_before_template |
| 419 | + else: |
| 420 | + before = Nothing |
| 421 | + |
| 422 | + if not is_nothing(arguments_delta.after): |
| 423 | + after = self._compute_fn_transform( |
| 424 | + arguments_delta.after, |
| 425 | + node_intrinsic_function.after_siblings, |
| 426 | + parameters_delta.after, |
| 427 | + ) |
| 428 | + updated_after_template = self._replace_at_jsonpath( |
| 429 | + self._after_template, node_intrinsic_function, after |
| 430 | + ) |
| 431 | + self._after_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] = updated_after_template |
| 432 | + else: |
| 433 | + after = Nothing |
| 434 | + |
| 435 | + self._save_runtime_cache() |
| 436 | + return PreprocEntityDelta(before=before, after=after) |
| 437 | + |
| 438 | + # def visit_node_properties( |
| 439 | + # self, node_properties: NodeProperties |
| 440 | + # ) -> PreprocEntityDelta[PreprocProperties, PreprocProperties]: |
| 441 | + # |
| 442 | + # before = node_properties |
| 443 | + # for property in node_properties.properties: |
| 444 | + # if property.name == "Fn::Transform": |
| 445 | + # path = "$" + ".".join(node_properties.scope.split("/")[:-1]) |
| 446 | + # before_siblings = extract_jsonpath(self._before_template, path) |
| 447 | + # after_siblings = extract_jsonpath(self._after_template, path) |
| 448 | + # intrinsic_transform = NodeIntrinsicFunctionFnTransform( |
| 449 | + # change_type=property.change_type, |
| 450 | + # intrinsic_function=property.name, |
| 451 | + # scope=node_properties.scope, |
| 452 | + # arguments=property.value, |
| 453 | + # before_siblings=before_siblings, |
| 454 | + # after_siblings=after_siblings, |
| 455 | + # ) |
| 456 | + # delta = self.visit(intrinsic_transform) |
| 457 | + # node_properties = delta.after |
| 458 | + # |
| 459 | + # |
| 460 | + # |
| 461 | + # return PreprocEntityDelta(before=super().visit(node_properties), after=Nothing) |
| 462 | + # |
| 463 | + # |
0 commit comments