"""CFNgin blueprint representing raw template module."""from__future__importannotationsimporthashlibimportjsonimportloggingimportsysfrompathlibimportPathfromtypingimportTYPE_CHECKING,Anyfromjinja2importEnvironment,FileSystemLoaderfrom...compatimportcached_propertyfrom..exceptionsimportInvalidConfig,UnresolvedBlueprintVariablefrom..utilsimportparse_cloudformation_templatefrom.baseimportBlueprintifTYPE_CHECKING:from...contextimportCfnginContextfrom...variablesimportVariableLOGGER=logging.getLogger(__name__)
[docs]defget_template_path(file_path:Path)->Path|None:"""Find raw template in working directory or in sys.path. template_path from config may refer to templates co-located with the CFNgin config, or files in remote package_sources. Here, we emulate python module loading to find the path to the template. Args: file_path: Template path. Returns: Path to file, or None if no file found """iffile_path.is_file():returnfile_pathforiinsys.path:test_path=Path(i)/file_path.nameiftest_path.is_file():returntest_pathreturnNone
[docs]defresolve_variable(provided_variable:Variable|None,blueprint_name:str)->Any:"""Resolve a provided variable value against the variable definition. This acts as a subset of resolve_variable logic in the base module, leaving out everything that doesn't apply to CFN parameters. Args: provided_variable: The variable value provided to the blueprint. blueprint_name: The name of the blueprint that the variable is being applied to. Raises: UnresolvedBlueprintVariable: Raised when the provided variable is not already resolved. """value=Noneifprovided_variable:ifnotprovided_variable.resolved:raiseUnresolvedBlueprintVariable(blueprint_name,provided_variable)value=provided_variable.valuereturnvalue
[docs]classRawTemplateBlueprint(Blueprint):"""Blueprint class for blueprints auto-generated from raw templates. Attributes: context: CFNgin context object. description: The description of the CloudFormation template that will be generated by this Blueprint. mappings: CloudFormation Mappings to be added to the template during the rendering process. name: Name of the Stack that will be created by the Blueprint. raw_template_path: Path to the raw CloudFormation template file. """raw_template_path:Path
[docs]def__init__(self,name:str,context:CfnginContext,*,description:str|None=None,mappings:dict[str,Any]|None=None,raw_template_path:Path,**_:Any,)->None:"""Instantiate class. .. versionchanged:: 2.0.0 Class only takes 2 positional arguments. The rest are now keyword arguments. """self._rendered=Noneself._resolved_variables=Noneself._version=Noneself.context=contextself.description=descriptionself.mappings=mappingsself.name=nameself.raw_template_path=raw_template_path
@propertydefoutput_definitions(self)->dict[str,dict[str,Any]]:"""Get the output definitions. .. versionadded:: 2.0.0 Returns: Output definitions. Keys are output names, the values are dicts containing key/values for various output properties. """returnself.to_dict().get("Outputs",{})@cached_propertydefparameter_definitions(self)->dict[str,Any]:"""Get the parameter definitions to submit to CloudFormation. .. versionadded:: 2.0.0 Returns: Parameter definitions. Keys are parameter names, the values are dicts containing key/values for various parameter properties. """returnself.to_dict().get("Parameters",{})@cached_propertydefparameter_values(self)->dict[str,list[Any]|str]:"""Return a dict of variables with type :class:`~runway.cfngin.blueprints.variables.types.CFNType`. .. versionadded:: 2.0.0 Returns: Variables that need to be submitted as CloudFormation Parameters. Will be a dictionary of ``<parameter name>: <parameter value>``. """returnself._resolved_variablesor{}@propertydefrendered(self)->str:"""Return (generating first if needed) rendered Template."""ifnotself._rendered:template_path=get_template_path(self.raw_template_path)iftemplate_path:iftemplate_path.suffix==".j2":self._rendered=(Environment(# noqa: S701loader=FileSystemLoader(searchpath=template_path.parent)).get_template(template_path.name).render(context=self.context,mappings=self.mappings,name=self.name,variables=self._resolved_variables,))else:withtemplate_path.open(encoding="utf-8")astemplate:self._rendered=template.read()else:raiseInvalidConfig(f"Could not find Template {self.raw_template_path}")# clear cached properties that rely on this propertyself._del_cached_property("parameter_definitions")returnself._rendered@propertydefrequires_change_set(self)->bool:"""Return True if the underlying template has transforms."""returnbool("Transform"inself.to_dict())@propertydefversion(self)->str:"""Return (generating first if needed) version hash."""ifnotself._version:self._version=hashlib.md5(self.rendered.encode()).hexdigest()[:8]# noqa: S324returnself._version
[docs]defto_dict(self)->dict[str,Any]:"""Return the template as a python dictionary. Returns: dict: the loaded template as a python dictionary """returnparse_cloudformation_template(self.rendered)
[docs]defto_json(self,variables:dict[str,Any]|None=None)->str:# noqa: ARG002"""Return the template in JSON. Args: variables: Unused in this subclass (variables won't affect the template). """# load -> dumps will produce json from json or yaml templatesreturnjson.dumps(self.to_dict(),sort_keys=True,indent=4)
[docs]defrender_template(self)->tuple[str,str]:"""Load template and generate its md5 hash."""return(self.version,self.rendered)
[docs]defresolve_variables(self,provided_variables:list[Variable])->None:"""Resolve the values of the blueprint variables. This will resolve the values of the template parameters with values from the env file, the config, and any lookups resolved. The resolution is run twice, in case the blueprint is jinja2 templated and requires provided variables to render. Args: provided_variables: List of provided variables. """# Pass 1 to set resolved_variables to provided variablesself._resolved_variables={}variable_dict={var.name:varforvarinprovided_variables}forvar_nameinvariable_dict:value=resolve_variable(variable_dict.get(var_name),self.name)ifvalueisnotNone:self._resolved_variables[var_name]=value# Pass 2 to render the blueprint and set resolved_variables according# to defined variables# save a copy of param defs before clearing resolved var dictdefined_variables=self.parameter_definitions.copy()self._resolved_variables={}variable_dict={var.name:varforvarinprovided_variables}forvar_nameindefined_variables:value=resolve_variable(variable_dict.get(var_name),self.name)ifvalueisnotNone:self._resolved_variables[var_name]=value# clear cached properties that rely on the property set by thisself._del_cached_property("parameter_values")