@@ -568,6 +568,17 @@ def _init_transforms(transforms: Any) -> Transforms:
568568 return Transforms ([])
569569
570570
571+ def _inject (
572+ resources : dict [str , Resource ], logical_id : str , resource_type : str
573+ ) -> None :
574+ """Add a synthetic resource if it doesn't already exist."""
575+ if logical_id not in resources :
576+ try :
577+ resources [logical_id ] = Resource ({"Type" : resource_type })
578+ except ValueError :
579+ pass
580+
581+
571582def _inject_sam_implicit_resources (
572583 template_resources : Any , resources : dict [str , Resource ]
573584) -> None :
@@ -596,92 +607,95 @@ def _inject_sam_implicit_resources(
596607 "AWS::Serverless::Function" ,
597608 "AWS::Serverless::StateMachine" ,
598609 ):
599- role_id = f"{ resource_id } Role"
600- if "Role" not in props and role_id not in resources :
601- try :
602- resources [role_id ] = Resource ({"Type" : "AWS::IAM::Role" })
603- except ValueError :
604- pass
610+ if "Role" not in props :
611+ _inject (resources , f"{ resource_id } Role" , "AWS::IAM::Role" )
605612
606613 if resource_type == "AWS::Serverless::Function" :
607- # SAM generates Version/Alias resources when AutoPublishAlias
608- # or DeploymentPreference is set. SAM supports !Ref {Id}.Version
609- # and !Ref {Id}.Alias as special syntax.
614+ # Version/Alias when AutoPublishAlias or DeploymentPreference
610615 has_alias = "AutoPublishAlias" in props or "DeploymentPreference" in props
611616 if has_alias :
612- version_id = f"{ resource_id } .Version"
613- if version_id not in resources :
614- try :
615- resources [version_id ] = Resource (
616- {"Type" : "AWS::Lambda::Version" }
617- )
618- except ValueError :
619- pass
620- alias_id = f"{ resource_id } .Alias"
621- if alias_id not in resources :
622- try :
623- resources [alias_id ] = Resource ({"Type" : "AWS::Lambda::Alias" })
624- except ValueError :
625- pass
626-
627- # SAM generates a Url resource when FunctionUrlConfig is set
617+ for suffix , rtype in (
618+ (f"{ resource_id } .Version" , "AWS::Lambda::Version" ),
619+ (f"{ resource_id } .Alias" , "AWS::Lambda::Alias" ),
620+ ):
621+ if suffix not in resources :
622+ try :
623+ resources [suffix ] = Resource ({"Type" : rtype })
624+ except ValueError :
625+ pass
626+
627+ # Url when FunctionUrlConfig is set
628628 if "FunctionUrlConfig" in props :
629- url_id = f"{ resource_id } Url"
630- if url_id not in resources :
631- try :
632- resources [url_id ] = Resource ({"Type" : "AWS::Lambda::Url" })
633- except ValueError :
634- pass
635-
636- # SAM Api/HttpApi always generate Stage resources
629+ _inject (resources , f"{ resource_id } Url" , "AWS::Lambda::Url" )
630+
631+ # DeploymentPreference generates CodeDeploy resources
632+ dp = props .get ("DeploymentPreference" , {})
633+ if isinstance (dp , dict ) and dp .get ("Enabled" , True ):
634+ _inject (
635+ resources ,
636+ "ServerlessDeploymentApplication" ,
637+ "AWS::CodeDeploy::Application" ,
638+ )
639+ _inject (
640+ resources ,
641+ f"{ resource_id } DeploymentGroup" ,
642+ "AWS::CodeDeploy::DeploymentGroup" ,
643+ )
644+ if "Role" not in dp :
645+ _inject (resources , "CodeDeployServiceRole" , "AWS::IAM::Role" )
646+
647+ # Per-event permissions and implicit API detection
648+ events = props .get ("Events" , {})
649+ if isinstance (events , dict ):
650+ for event_name , event in events .items ():
651+ if not isinstance (event , dict ):
652+ continue
653+ _inject (
654+ resources ,
655+ f"{ resource_id } { event_name } Permission" ,
656+ "AWS::Lambda::Permission" ,
657+ )
658+ event_type = event .get ("Type" )
659+ if event_type == "Api" :
660+ event_props = event .get ("Properties" , {})
661+ if (
662+ not isinstance (event_props , dict )
663+ or "RestApiId" not in event_props
664+ ):
665+ needs_rest_api = True
666+ elif event_type == "HttpApi" :
667+ event_props = event .get ("Properties" , {})
668+ if (
669+ not isinstance (event_props , dict )
670+ or "ApiId" not in event_props
671+ ):
672+ needs_http_api = True
673+
637674 if resource_type == "AWS::Serverless::Api" :
638- stage_id = f"{ resource_id } Stage"
639- if stage_id not in resources :
640- try :
641- resources [stage_id ] = Resource ({"Type" : "AWS::ApiGateway::Stage" })
642- except ValueError :
643- pass
675+ _inject (resources , f"{ resource_id } Stage" , "AWS::ApiGateway::Stage" )
676+ if "Domain" in props :
677+ _inject (
678+ resources ,
679+ f"{ resource_id } DomainName" ,
680+ "AWS::ApiGateway::DomainName" ,
681+ )
682+ if "Auth" in props :
683+ _inject (
684+ resources ,
685+ f"{ resource_id } UsagePlan" ,
686+ "AWS::ApiGateway::UsagePlan" ,
687+ )
644688
645689 if resource_type == "AWS::Serverless::HttpApi" :
646- stage_id = f"{ resource_id } Stage"
647- if stage_id not in resources :
648- try :
649- resources [stage_id ] = Resource ({"Type" : "AWS::ApiGatewayV2::Stage" })
650- except ValueError :
651- pass
652-
653- if resource_type != "AWS::Serverless::Function" :
654- continue
690+ _inject (resources , f"{ resource_id } Stage" , "AWS::ApiGatewayV2::Stage" )
655691
656- events = props .get ("Events" , {})
657- if not isinstance (events , dict ):
658- continue
659- for event in events .values ():
660- if not isinstance (event , dict ):
661- continue
662- event_type = event .get ("Type" )
663- if event_type == "Api" :
664- event_props = event .get ("Properties" , {})
665- if not isinstance (event_props , dict ) or "RestApiId" not in event_props :
666- needs_rest_api = True
667- elif event_type == "HttpApi" :
668- event_props = event .get ("Properties" , {})
669- if not isinstance (event_props , dict ) or "ApiId" not in event_props :
670- needs_http_api = True
671-
672- if needs_rest_api and "ServerlessRestApi" not in resources :
673- try :
674- resources ["ServerlessRestApi" ] = Resource ({"Type" : "AWS::Serverless::Api" })
675- except ValueError :
676- pass
692+ if needs_rest_api :
693+ _inject (resources , "ServerlessRestApi" , "AWS::Serverless::Api" )
694+ _inject (resources , "ServerlessRestApiStage" , "AWS::ApiGateway::Stage" )
677695
678- if needs_http_api and "ServerlessHttpApi" not in resources :
679- try :
680- resources ["ServerlessHttpApi" ] = Resource (
681- {"Type" : "AWS::Serverless::HttpApi" }
682- )
683- except ValueError :
684- pass
696+ if needs_http_api :
697+ _inject (resources , "ServerlessHttpApi" , "AWS::Serverless::HttpApi" )
698+ _inject (resources , "ServerlessHttpApiStage" , "AWS::ApiGatewayV2::Stage" )
685699
686700
687701def create_context_for_template (
0 commit comments