"""Retrieve a value from CloudFormation Stack Outputs.The query syntax for this lookup is ``<stack-name>.<output-name>``.When specifying the output name, be sure to use the *Logical ID* ofthe output; not the *Export.Name*."""from__future__importannotationsimportjsonimportloggingfromtypingimportTYPE_CHECKING,Any,ClassVar,NamedTuple,castfrombotocore.exceptionsimportClientErrorfrom...cfngin.exceptionsimportStackDoesNotExistfrom...exceptionsimportOutputDoesNotExistfrom.baseimportLookupHandlerifTYPE_CHECKING:frommypy_boto3_cloudformation.clientimportCloudFormationClientfrom...cfngin.providers.aws.defaultimportProviderfrom...contextimportCfnginContext,RunwayContextfrom.baseimportParsedArgsTypeDefLOGGER=logging.getLogger(__name__)
[docs]classCfnLookup(LookupHandler["CfnginContext | RunwayContext"]):"""CloudFormation Stack Output lookup."""TYPE_NAME:ClassVar[str]="cfn""""Name that the Lookup is registered as."""
[docs]@staticmethoddefshould_use_provider(args:ParsedArgsTypeDef,provider:Provider|None)->bool:"""Determine if the provider should be used for the lookup. This will open happen when the lookup is used with CFNgin. Args: args: Parsed arguments provided to the lookup. provider: CFNgin provider. """ifprovider:if"region"inargsandprovider.region!=args["region"]:LOGGER.debug("not using provider; requested region does not match")returnFalseLOGGER.debug("using provider")returnTruereturnFalse
[docs]@staticmethoddefget_stack_output(client:CloudFormationClient,query:OutputQuery)->str:"""Get CloudFormation Stack output. Args: client: Boto3 CloudFormation client. query: What to get. """LOGGER.debug("describing stack: %s",query.stack_name)stack=client.describe_stacks(StackName=query.stack_name)["Stacks"][0]outputs={# these should always exist even though the schema says they are not requiredoutput["OutputKey"]:output["OutputValue"]# type: ignoreforoutputinstack.get("Outputs",[])}LOGGER.debug("%s stack outputs: %s",stack["StackName"],json.dumps(outputs))returnoutputs[query.output_name]
[docs]@classmethoddefhandle(cls,value:str,context:CfnginContext|RunwayContext,*,provider:Provider|None=None,**_:Any,)->Any:"""Retrieve a value from CloudFormation Stack outputs. Args: value: The value passed to the Lookup. context: The current context object. provider: AWS provider. Returns: Result of the query. Raises: OutputDoesNotExist: Output does not exist on the Stack provided and default was not provided. """raw_query,args=cls.parse(value)try:query=OutputQuery(*raw_query.split("."))exceptTypeErrorasexc:raiseValueError(f'query must be <stack-name>.<output-name>; got "{raw_query}"')fromexctry:# dict is not preserved in mock call so it must be a copy of# args for testing to function correctlyifcls.should_use_provider(args.copy(),provider):# this will only happen when used from cfnginresult=cast("Provider",provider).get_output(query.stack_name,query.output_name)else:cfn_client=context.get_session(region=cast("str | None",args.get("region"))).client("cloudformation")result=cls.get_stack_output(cfn_client,query)except(ClientError,KeyError,StackDoesNotExist)asexc:# StackDoesNotExist is only raised by providerif"default"inargs:LOGGER.debug('unable to resolve lookup for CloudFormation Stack output "%s"; using default',raw_query,exc_info=True,)args.pop("load",None)# don't load a default valueresult=args.pop("default")elifisinstance(exc,(ClientError,StackDoesNotExist)):raiseelse:raiseOutputDoesNotExist(query.stack_name,query.output_name)fromexcreturncls.format_results(result,**args)