23
23
fix_boto_parameters_based_on_report ,
24
24
remove_none_values ,
25
25
)
26
+ from localstack .services .cloudformation .engine .quirks import PHYSICAL_RESOURCE_ID_SPECIAL_CASES
26
27
from localstack .services .cloudformation .service_models import KEY_RESOURCE_STATE , GenericBaseModel
27
28
from localstack .utils .aws import aws_stack
28
29
@@ -537,6 +538,21 @@ class NoResourceProvider(Exception):
537
538
pass
538
539
539
540
541
+ def resolve_json_pointer (resource_props : Properties , primary_id_path : str ) -> str :
542
+ primary_id_path = primary_id_path .replace ("/properties" , "" )
543
+ parts = [p for p in primary_id_path .split ("/" ) if p ]
544
+
545
+ resolved_part = resource_props .copy ()
546
+ for i in range (len (parts )):
547
+ part = parts [i ]
548
+ resolved_part = resolved_part .get (part )
549
+ if i == len (parts ) - 1 :
550
+ # last part
551
+ return resolved_part
552
+
553
+ raise Exception (f"Resource properties is missing field: { part } " )
554
+
555
+
540
556
class ResourceProviderExecutor :
541
557
"""
542
558
Point of abstraction between our integration with generic base models, and the new providers.
@@ -561,19 +577,32 @@ def deploy_loop(
561
577
) -> ProgressEvent [Properties ]:
562
578
payload = copy .deepcopy (raw_payload )
563
579
564
- for _ in range (max_iterations ):
565
- event = self .execute_action (payload )
580
+ for current_iteration in range (max_iterations ):
581
+ resource_type = get_resource_type (
582
+ {"Type" : raw_payload ["resourceType" ]}
583
+ ) # TODO: simplify signature of get_resource_type to just take the type
584
+ resource_provider = self .load_resource_provider (resource_type )
585
+ event = self .execute_action (resource_provider , payload )
566
586
567
587
if event .status == OperationStatus .SUCCESS :
568
- # TODO: validate physical_resource_id is not None
569
588
logical_resource_id = raw_payload ["requestData" ]["logicalResourceId" ]
570
589
resource = self .resources [logical_resource_id ]
571
590
if "PhysicalResourceId" not in resource :
572
591
# branch for non-legacy providers
573
592
# TODO: move out of if? (physical res id can be set earlier possibly)
574
- resource_type_schema = self .load_resource_schema (raw_payload ["resourceType" ])
593
+ if isinstance (resource_provider , LegacyResourceProvider ):
594
+ raise Exception (
595
+ "A GenericBaseModel should always have a PhysicalResourceId set after deployment"
596
+ )
597
+
598
+ if not hasattr (resource_provider , "SCHEMA" ):
599
+ raise Exception (
600
+ "A ResourceProvider should always have a SCHEMA property defined."
601
+ )
602
+
603
+ resource_type_schema = resource_provider .SCHEMA
575
604
physical_resource_id = self .extract_physical_resource_id_from_model_with_schema (
576
- event .resource_model , resource_type_schema
605
+ event .resource_model , raw_payload [ "resourceType" ], resource_type_schema
577
606
)
578
607
579
608
resource ["PhysicalResourceId" ] = physical_resource_id
@@ -585,34 +614,30 @@ def deploy_loop(
585
614
payload ["callbackContext" ] = context
586
615
payload ["requestData" ]["resourceProperties" ] = event .resource_model
587
616
588
- time .sleep (sleep_time )
617
+ if current_iteration == 0 :
618
+ time .sleep (0 )
619
+ else :
620
+ time .sleep (sleep_time )
589
621
else :
590
622
raise TimeoutError ("Could not perform deploy loop action" )
591
623
592
- def execute_action (self , raw_payload : ResourceProviderPayload ) -> ProgressEvent [Properties ]:
593
- resource_type = get_resource_type (
594
- {"Type" : raw_payload ["resourceType" ]}
595
- ) # TODO: simplify signature of get_resource_type to just take the type
596
- resource_provider = self .load_resource_provider (resource_type )
597
- if resource_provider :
598
- change_type = raw_payload ["action" ]
599
- request = convert_payload (
600
- stack_name = self .stack_name , stack_id = self .stack_id , payload = raw_payload
601
- )
602
-
603
- match change_type :
604
- case "Add" :
605
- return resource_provider .create (request )
606
- case "Dynamic" | "Modify" :
607
- return resource_provider .update (request )
608
- case "Remove" :
609
- return resource_provider .delete (request )
610
- case _:
611
- raise NotImplementedError (change_type )
624
+ def execute_action (
625
+ self , resource_provider : ResourceProvider , raw_payload : ResourceProviderPayload
626
+ ) -> ProgressEvent [Properties ]:
627
+ change_type = raw_payload ["action" ]
628
+ request = convert_payload (
629
+ stack_name = self .stack_name , stack_id = self .stack_id , payload = raw_payload
630
+ )
612
631
613
- else :
614
- # custom provider
615
- raise NoResourceProvider
632
+ match change_type :
633
+ case "Add" :
634
+ return resource_provider .create (request )
635
+ case "Dynamic" | "Modify" :
636
+ return resource_provider .update (request )
637
+ case "Remove" :
638
+ return resource_provider .delete (request )
639
+ case _:
640
+ raise NotImplementedError (change_type )
616
641
617
642
def load_resource_provider (self , resource_type : str ) -> Optional [ResourceProvider ]:
618
643
# TODO: unify behavior here in regards to raising NoResourceProvider
@@ -643,13 +668,22 @@ def _load_legacy_resource_provider(self, resource_type: str) -> LegacyResourcePr
643
668
raise NoResourceProvider
644
669
645
670
def extract_physical_resource_id_from_model_with_schema (
646
- self , resource_model : Properties , resource_type_schema : dict
671
+ self , resource_model : Properties , resource_type : str , resource_type_schema : dict
647
672
) -> str :
648
- # id_path = resource_type_schema['primaryIdentifier'][0]
649
- return resource_model ["Id" ]
673
+ if resource_type in PHYSICAL_RESOURCE_ID_SPECIAL_CASES :
674
+ primary_id_path = PHYSICAL_RESOURCE_ID_SPECIAL_CASES [resource_type ]
675
+ physical_resource_id = resolve_json_pointer (resource_model , primary_id_path )
676
+ else :
677
+ primary_id_paths = resource_type_schema ["primaryIdentifier" ]
678
+ if len (primary_id_paths ) > 1 :
679
+ # TODO: auto-merge. Verify logic here with AWS
680
+ physical_resource_id = "-" .join (
681
+ [resolve_json_pointer (resource_model , pip ) for pip in primary_id_paths ]
682
+ )
683
+ else :
684
+ physical_resource_id = resolve_json_pointer (resource_model , primary_id_paths [0 ])
650
685
651
- def load_resource_schema (self , resource_type : str ) -> dict :
652
- return {}
686
+ return physical_resource_id
653
687
654
688
655
689
plugin_manager = PluginManager (CloudFormationResourceProviderPlugin .namespace )
0 commit comments