@@ -582,6 +582,17 @@ def _init_transforms(transforms: Any) -> Transforms:
582582 return Transforms ([])
583583
584584
585+ def _inject (
586+ resources : dict [str , Resource ], logical_id : str , resource_type : str
587+ ) -> None :
588+ """Add a synthetic resource if it doesn't already exist."""
589+ if logical_id not in resources :
590+ try :
591+ resources [logical_id ] = Resource ({"Type" : resource_type })
592+ except ValueError :
593+ pass
594+
595+
585596def _inject_sam_implicit_resources (
586597 template_resources : Any , resources : dict [str , Resource ]
587598) -> None :
@@ -610,92 +621,95 @@ def _inject_sam_implicit_resources(
610621 "AWS::Serverless::Function" ,
611622 "AWS::Serverless::StateMachine" ,
612623 ):
613- role_id = f"{ resource_id } Role"
614- if "Role" not in props and role_id not in resources :
615- try :
616- resources [role_id ] = Resource ({"Type" : "AWS::IAM::Role" })
617- except ValueError :
618- pass
624+ if "Role" not in props :
625+ _inject (resources , f"{ resource_id } Role" , "AWS::IAM::Role" )
619626
620627 if resource_type == "AWS::Serverless::Function" :
621- # SAM generates Version/Alias resources when AutoPublishAlias
622- # or DeploymentPreference is set. SAM supports !Ref {Id}.Version
623- # and !Ref {Id}.Alias as special syntax.
628+ # Version/Alias when AutoPublishAlias or DeploymentPreference
624629 has_alias = "AutoPublishAlias" in props or "DeploymentPreference" in props
625630 if has_alias :
626- version_id = f"{ resource_id } .Version"
627- if version_id not in resources :
628- try :
629- resources [version_id ] = Resource (
630- {"Type" : "AWS::Lambda::Version" }
631- )
632- except ValueError :
633- pass
634- alias_id = f"{ resource_id } .Alias"
635- if alias_id not in resources :
636- try :
637- resources [alias_id ] = Resource ({"Type" : "AWS::Lambda::Alias" })
638- except ValueError :
639- pass
640-
641- # SAM generates a Url resource when FunctionUrlConfig is set
631+ for suffix , rtype in (
632+ (f"{ resource_id } .Version" , "AWS::Lambda::Version" ),
633+ (f"{ resource_id } .Alias" , "AWS::Lambda::Alias" ),
634+ ):
635+ if suffix not in resources :
636+ try :
637+ resources [suffix ] = Resource ({"Type" : rtype })
638+ except ValueError :
639+ pass
640+
641+ # Url when FunctionUrlConfig is set
642642 if "FunctionUrlConfig" in props :
643- url_id = f"{ resource_id } Url"
644- if url_id not in resources :
645- try :
646- resources [url_id ] = Resource ({"Type" : "AWS::Lambda::Url" })
647- except ValueError :
648- pass
649-
650- # SAM Api/HttpApi always generate Stage resources
643+ _inject (resources , f"{ resource_id } Url" , "AWS::Lambda::Url" )
644+
645+ # DeploymentPreference generates CodeDeploy resources
646+ dp = props .get ("DeploymentPreference" , {})
647+ if isinstance (dp , dict ) and dp .get ("Enabled" , True ):
648+ _inject (
649+ resources ,
650+ "ServerlessDeploymentApplication" ,
651+ "AWS::CodeDeploy::Application" ,
652+ )
653+ _inject (
654+ resources ,
655+ f"{ resource_id } DeploymentGroup" ,
656+ "AWS::CodeDeploy::DeploymentGroup" ,
657+ )
658+ if "Role" not in dp :
659+ _inject (resources , "CodeDeployServiceRole" , "AWS::IAM::Role" )
660+
661+ # Per-event permissions and implicit API detection
662+ events = props .get ("Events" , {})
663+ if isinstance (events , dict ):
664+ for event_name , event in events .items ():
665+ if not isinstance (event , dict ):
666+ continue
667+ _inject (
668+ resources ,
669+ f"{ resource_id } { event_name } Permission" ,
670+ "AWS::Lambda::Permission" ,
671+ )
672+ event_type = event .get ("Type" )
673+ if event_type == "Api" :
674+ event_props = event .get ("Properties" , {})
675+ if (
676+ not isinstance (event_props , dict )
677+ or "RestApiId" not in event_props
678+ ):
679+ needs_rest_api = True
680+ elif event_type == "HttpApi" :
681+ event_props = event .get ("Properties" , {})
682+ if (
683+ not isinstance (event_props , dict )
684+ or "ApiId" not in event_props
685+ ):
686+ needs_http_api = True
687+
651688 if resource_type == "AWS::Serverless::Api" :
652- stage_id = f"{ resource_id } Stage"
653- if stage_id not in resources :
654- try :
655- resources [stage_id ] = Resource ({"Type" : "AWS::ApiGateway::Stage" })
656- except ValueError :
657- pass
689+ _inject (resources , f"{ resource_id } Stage" , "AWS::ApiGateway::Stage" )
690+ if "Domain" in props :
691+ _inject (
692+ resources ,
693+ f"{ resource_id } DomainName" ,
694+ "AWS::ApiGateway::DomainName" ,
695+ )
696+ if "Auth" in props :
697+ _inject (
698+ resources ,
699+ f"{ resource_id } UsagePlan" ,
700+ "AWS::ApiGateway::UsagePlan" ,
701+ )
658702
659703 if resource_type == "AWS::Serverless::HttpApi" :
660- stage_id = f"{ resource_id } Stage"
661- if stage_id not in resources :
662- try :
663- resources [stage_id ] = Resource ({"Type" : "AWS::ApiGatewayV2::Stage" })
664- except ValueError :
665- pass
666-
667- if resource_type != "AWS::Serverless::Function" :
668- continue
704+ _inject (resources , f"{ resource_id } Stage" , "AWS::ApiGatewayV2::Stage" )
669705
670- events = props .get ("Events" , {})
671- if not isinstance (events , dict ):
672- continue
673- for event in events .values ():
674- if not isinstance (event , dict ):
675- continue
676- event_type = event .get ("Type" )
677- if event_type == "Api" :
678- event_props = event .get ("Properties" , {})
679- if not isinstance (event_props , dict ) or "RestApiId" not in event_props :
680- needs_rest_api = True
681- elif event_type == "HttpApi" :
682- event_props = event .get ("Properties" , {})
683- if not isinstance (event_props , dict ) or "ApiId" not in event_props :
684- needs_http_api = True
685-
686- if needs_rest_api and "ServerlessRestApi" not in resources :
687- try :
688- resources ["ServerlessRestApi" ] = Resource ({"Type" : "AWS::Serverless::Api" })
689- except ValueError :
690- pass
706+ if needs_rest_api :
707+ _inject (resources , "ServerlessRestApi" , "AWS::Serverless::Api" )
708+ _inject (resources , "ServerlessRestApiStage" , "AWS::ApiGateway::Stage" )
691709
692- if needs_http_api and "ServerlessHttpApi" not in resources :
693- try :
694- resources ["ServerlessHttpApi" ] = Resource (
695- {"Type" : "AWS::Serverless::HttpApi" }
696- )
697- except ValueError :
698- pass
710+ if needs_http_api :
711+ _inject (resources , "ServerlessHttpApi" , "AWS::Serverless::HttpApi" )
712+ _inject (resources , "ServerlessHttpApiStage" , "AWS::ApiGatewayV2::Stage" )
699713
700714
701715def create_context_for_template (
0 commit comments