[docs]classArgsDataModel(BaseModel):"""Arguments data model. Any other arguments specified are sent as filters to the AWS API. For example, ``architecture:x86_64`` will add a filter. """executable_users:list[str]|None=None"""List of executable users."""owners:list[str]"""At least one owner is required. Should be ``amazon``, ``self``, or an AWS account ID. """region:str|None=None"""AWS region."""@field_validator("executable_users","owners",mode="before")@classmethoddef_convert_str_to_list(cls,v:list[str]|str)->list[str]:"""Convert str to list."""ifisinstance(v,str):returnv.split(",")returnv# cov: ignore
[docs]classImageNotFound(Exception):"""Image not found."""search_string:str
[docs]def__init__(self,search_string:str)->None:"""Instantiate class."""self.search_string=search_stringsuper().__init__(f"Unable to find ec2 image with search string: {search_string}")
[docs]classAmiLookup(LookupHandler["CfnginContext"]):"""AMI lookup."""TYPE_NAME:ClassVar[str]="ami""""Name that the Lookup is registered as."""
[docs]@classmethoddefparse_query(cls,value:str)->tuple[str,dict[str,str]]:"""Parse the value passed to the lookup. This overrides the default parsing to account for special requirements. Args: value: The raw value passed to a lookup. Returns: The lookup query and a dict of arguments """raw_value=read_value_from_path(value)args:dict[str,str]={}if"@"inraw_value:args["region"],raw_value=raw_value.split("@",1)# now find any other arguments that can be filtersmatches=re.findall(r"([0-9a-zA-z_-]+:[^\s$]+)",raw_value)formatchinmatches:k,v=match.split(":",1)args[k]=vreturnargs.pop("name_regex"),args
[docs]@classmethoddefhandle(cls,value:str,context:CfnginContext,**_kwargs:Any)->str:"""Fetch the most recent AMI Id using a filter. Args: value: Parameter(s) given to this lookup. context: Context instance. Example: .. code-block: ${ami [<region>@]owners:self,account,amazon name_regex:serverX-[0-9]+ architecture:x64,i386} The above fetches the most recent AMI where owner is self account or amazon and the ami name matches the regex described, the architecture will be either x64 or i386 You can also optionally specify the region in which to perform the AMI lookup. """query,raw_args=cls.parse_query(value)args=ArgsDataModel.model_validate(raw_args)ec2=context.get_session(region=args.region).client("ec2")describe_args:dict[str,Any]={"Filters":[{"Name":key,"Values":val.split(",")ifvalelseval}forkey,valin{k:vfork,vinraw_args.items()ifknotinArgsDataModel.model_fields}.items()],"Owners":args.owners,}ifargs.executable_users:describe_args["ExecutableUsers"]=args.executable_usersresult=ec2.describe_images(**describe_args)images=sorted(result.get("Images",[]),key=operator.itemgetter("CreationDate"),reverse=True,)forimageinimages:# sometimes we get ARI/AKI in response - these don't have a 'Name'ifre.match(f"^{query}$",image.get("Name",""))and"ImageId"inimage:returnimage["ImageId"]raiseImageNotFound(value)