Source code for runway.cfngin.lookups.handlers.awslambda

"""Dedicated lookup for use with :class:`~runway.cfngin.hooks.awslambda.base_classes.AwsLambdaHook` based hooks.

To use this hook, there must be a
:class:`~runway.cfngin.hooks.awslambda.base_classes.AwsLambdaHook` based hook defined
in the :attr:`~cfngin.config.pre_deploy` section of the CFNgin configuration file.
This hook must also define a :attr:`~cfngin.hook.data_key` that is unique within
the CFNgin configuration file (it can be reused in other CFNgin configuration files).
The :attr:`~cfngin.hook.data_key` is then passed to the lookup as it's input/query.
This allows the lookup to function during a ``runway plan``.

"""  # noqa
from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any, List, Optional, Union, cast

from pydantic import ValidationError
from troposphere.awslambda import Code, Content
from typing_extensions import Final, Literal

from ....lookups.handlers.base import LookupHandler
from ....utils import load_object_from_string
from ...exceptions import CfnginOnlyLookupError

if TYPE_CHECKING:
    from ....config import CfnginConfig
    from ....config.models.cfngin import CfnginHookDefinitionModel
    from ....context import CfnginContext, RunwayContext
    from ...hooks.awslambda.base_classes import AwsLambdaHook
    from ...hooks.awslambda.models.responses import AwsLambdaHookDeployResponse

LOGGER = logging.getLogger(__name__)


[docs]class AwsLambdaLookup(LookupHandler): """Lookup for AwsLambdaHook responses.""" TYPE_NAME: Final[Literal["awslambda"]] = "awslambda"
[docs] @classmethod def get_deployment_package_data( cls, context: CfnginContext, data_key: str ) -> AwsLambdaHookDeployResponse: """Get the response of an AwsLambdaHook run. Args: context: CFNgin context object. data_key: The value of the ``data_key`` field as assigned in a Hook definition. Returns: The :class:`~runway.cfngin.hooks.awslambda.base_classes.AwsLambdaHook` response parsed into a data model. This will come from hook data if it exists or it will be calculated and added to hook data for future use. Raises: TypeError: The data stored in hook data does not align with the expected data model. """ # needs to be imported here to avoid cyclic imports for conditional code # caused by import of runway.cfngin.actions.deploy in runway.cfngin.hooks.base # pylint: disable=import-outside-toplevel from ...hooks.awslambda.models.responses import ( AwsLambdaHookDeployResponse as _AwsLambdaHookDeployResponse, ) if data_key not in context.hook_data: LOGGER.debug("%s missing from hook_data; attempting to get value", data_key) hook = cls.init_hook_class( context, cls.get_required_hook_definition(context.config, data_key) ) context.set_hook_data(data_key, hook.plan()) try: return _AwsLambdaHookDeployResponse.parse_obj(context.hook_data[data_key]) except ValidationError: raise TypeError( "expected AwsLambdaHookDeployResponseTypedDict, " f"not {context.hook_data[data_key]}" ) from None
[docs] @staticmethod def get_required_hook_definition( config: CfnginConfig, data_key: str ) -> CfnginHookDefinitionModel: """Get the required Hook definition from the CFNgin config. Currently, this only supports finding the data_key pre_deploy. Args: config: CFNgin config being processed. data_key: The value of the ``data_key`` field as assigned in a Hook definition. Returns: The Hook definition set to use the provided ``data_key``. Raises: ValueError: Either a Hook definition was not found for the provided ``data_key`` or, more than one was found. """ hooks_with_data_key = [ hook_def for hook_def in config.pre_deploy if hook_def.data_key == data_key ] if not hooks_with_data_key: raise ValueError(f"no hook definition found with data_key {data_key}") if len(hooks_with_data_key) > 1: raise ValueError( f"more than one hook definition found with data_key {data_key}" ) return hooks_with_data_key.pop()
[docs] @classmethod def handle( # pylint: disable=arguments-differ cls, value: str, context: Union[CfnginContext, RunwayContext], *_args: Any, **_kwargs: Any, ) -> AwsLambdaHookDeployResponse: """Retrieve metadata for an AWS Lambda deployment package. Args: value: Value to resolve. context: The current context object. Returns: The full :class:`~awslambda.models.response.AwsLambdaHookDeployResponse` data model. """ # `if isinstance(context, _RunwayContext)` without needing to import candidate # importing candidate causes cyclic import if "RunwayContext" in type(context).__name__: raise CfnginOnlyLookupError(cls.TYPE_NAME) query, _ = cls.parse(value) return cls.get_deployment_package_data(cast("CfnginContext", context), query)
[docs] @staticmethod def init_hook_class( context: CfnginContext, hook_def: CfnginHookDefinitionModel ) -> AwsLambdaHook[Any]: """Initialize AwsLambdaHook subclass instance. Args: context: CFNgin context object. hook_def: The :class:`~runway.cfngin.hooks.awslambda.base_classes.AwsLambdaHook` definition. Returns: The loaded AwsLambdaHook object. """ # needs to be imported here to avoid cyclic imports for conditional code # caused by import of runway.cfngin.actions.deploy in runway.cfngin.hooks.base # pylint: disable=import-outside-toplevel from ...hooks.awslambda.base_classes import AwsLambdaHook as _AwsLambdaHook kls = load_object_from_string(hook_def.path) if ( not isinstance(kls, type) or not hasattr(kls, "__subclasscheck__") or not issubclass(kls, _AwsLambdaHook) ): raise TypeError( f"hook path {hook_def.path} for hook with data_key {hook_def.data_key} " "must be a subclass of AwsLambdaHook to use this lookup" ) return cast("AwsLambdaHook[Any]", kls(context, **hook_def.args))
[docs] class Code(LookupHandler): """Lookup for AwsLambdaHook responses.""" TYPE_NAME: Final[Literal["awslambda.Code"]] = "awslambda.Code"
[docs] @classmethod def handle( # pylint: disable=arguments-differ cls, value: str, context: Union[CfnginContext, RunwayContext], *args: Any, **kwargs: Any, ) -> Code: """Retrieve metadata for an AWS Lambda deployment package. Args: value: Value to resolve. context: The current context object. Returns: Value that can be passed into CloudFormation property ``AWS::Lambda::Function.Code``. """ return Code( **AwsLambdaLookup.handle(value, context, *args, **kwargs).dict( by_alias=True, exclude_none=True, include={"bucket_name", "object_key", "object_version_id"}, ) )
[docs] class CodeSha256(LookupHandler): """Lookup for AwsLambdaHook responses.""" TYPE_NAME: Final[Literal["awslambda.CodeSha256"]] = "awslambda.CodeSha256"
[docs] @classmethod def handle( # pylint: disable=arguments-differ cls, value: str, context: Union[CfnginContext, RunwayContext], *args: Any, **kwargs: Any, ) -> str: """Retrieve metadata for an AWS Lambda deployment package. Args: value: Value to resolve. context: The current context object. Returns: Value that can be passed into CloudFormation property ``AWS::Lambda::Version.CodeSha256``. """ return AwsLambdaLookup.handle(value, context, *args, **kwargs).code_sha256
[docs] class CompatibleArchitectures(LookupHandler): """Lookup for AwsLambdaHook responses.""" TYPE_NAME: Final[ Literal["awslambda.CompatibleArchitectures"] ] = "awslambda.CompatibleArchitectures"
[docs] @classmethod def handle( # pylint: disable=arguments-differ cls, value: str, context: Union[CfnginContext, RunwayContext], *args: Any, **kwargs: Any, ) -> Optional[List[str]]: """Retrieve metadata for an AWS Lambda deployment package. Args: value: Value to resolve. context: The current context object. Returns: Value that can be passed into CloudFormation property ``AWS::Lambda::LayerVersion.CompatibleArchitectures``. """ _query, lookup_args = cls.parse(value) return cls.format_results( AwsLambdaLookup.handle( value, context, *args, **kwargs ).compatible_architectures, **lookup_args, )
[docs] class CompatibleRuntimes(LookupHandler): """Lookup for AwsLambdaHook responses.""" TYPE_NAME: Final[ Literal["awslambda.CompatibleRuntimes"] ] = "awslambda.CompatibleRuntimes"
[docs] @classmethod def handle( # pylint: disable=arguments-differ cls, value: str, context: Union[CfnginContext, RunwayContext], *args: Any, **kwargs: Any, ) -> Any: """Retrieve metadata for an AWS Lambda deployment package. Args: value: Value to resolve. context: The current context object. Returns: Value that can be passed into CloudFormation property ``AWS::Lambda::LayerVersion.CompatibleRuntimes``. """ _query, lookup_args = cls.parse(value) return cls.format_results( AwsLambdaLookup.handle( value, context, *args, **kwargs ).compatible_runtimes, **lookup_args, )
[docs] class Content(LookupHandler): """Lookup for AwsLambdaHook responses.""" TYPE_NAME: Final[Literal["awslambda.Content"]] = "awslambda.Content"
[docs] @classmethod def handle( # pylint: disable=arguments-differ cls, value: str, context: Union[CfnginContext, RunwayContext], *args: Any, **kwargs: Any, ) -> Content: """Retrieve metadata for an AWS Lambda deployment package. Args: value: Value to resolve. context: The current context object. Returns: Value that can be passed into CloudFormation property ``AWS::Lambda::LayerVersion.Content``. """ return Content( **AwsLambdaLookup.handle(value, context, *args, **kwargs).dict( by_alias=True, exclude_none=True, include={"bucket_name", "object_key", "object_version_id"}, ) )
[docs] class LicenseInfo(LookupHandler): """Lookup for AwsLambdaHook responses.""" TYPE_NAME: Final[Literal["awslambda.LicenseInfo"]] = "awslambda.LicenseInfo"
[docs] @classmethod def handle( # pylint: disable=arguments-differ cls, value: str, context: Union[CfnginContext, RunwayContext], *args: Any, **kwargs: Any, ) -> Optional[str]: """Retrieve metadata for an AWS Lambda deployment package. Args: value: Value to resolve. context: The current context object. Returns: Value that can be passed into CloudFormation property ``AWS::Lambda::LayerVersion.LicenseInfo``. """ _query, lookup_args = cls.parse(value) return cls.format_results( AwsLambdaLookup.handle(value, context, *args, **kwargs).license, **lookup_args, )
[docs] class Runtime(LookupHandler): """Lookup for AwsLambdaHook responses.""" TYPE_NAME: Final[Literal["awslambda.Runtime"]] = "awslambda.Runtime"
[docs] @classmethod def handle( # pylint: disable=arguments-differ cls, value: str, context: Union[CfnginContext, RunwayContext], *args: Any, **kwargs: Any, ) -> str: """Retrieve metadata for an AWS Lambda deployment package. Args: value: Value to resolve. context: The current context object. Returns: Value that can be passed into CloudFormation property ``AWS::Lambda::Function.Runtime``. """ return AwsLambdaLookup.handle(value, context, *args, **kwargs).runtime
[docs] class S3Bucket(LookupHandler): """Lookup for AwsLambdaHook responses.""" TYPE_NAME: Final[Literal["awslambda.S3Bucket"]] = "awslambda.S3Bucket"
[docs] @classmethod def handle( # pylint: disable=arguments-differ cls, value: str, context: Union[CfnginContext, RunwayContext], *args: Any, **kwargs: Any, ) -> str: """Retrieve metadata for an AWS Lambda deployment package. Args: value: Value to resolve. context: The current context object. Returns: Value that can be passed into CloudFormation property ``AWS::Lambda::Function.Code.S3Bucket`` or ``AWS::Lambda::LayerVersion.Content.S3Bucket``. """ return AwsLambdaLookup.handle(value, context, *args, **kwargs).bucket_name
[docs] class S3Key(LookupHandler): """Lookup for AwsLambdaHook responses.""" TYPE_NAME: Final[Literal["awslambda.S3Key"]] = "awslambda.S3Key"
[docs] @classmethod def handle( # pylint: disable=arguments-differ cls, value: str, context: Union[CfnginContext, RunwayContext], *args: Any, **kwargs: Any, ) -> str: """Retrieve metadata for an AWS Lambda deployment package. Args: value: Value to resolve. context: The current context object. Returns: Value that can be passed into CloudFormation property ``AWS::Lambda::Function.Code.S3Key`` or ``AWS::Lambda::LayerVersion.Content.S3Key``. """ return AwsLambdaLookup.handle(value, context, *args, **kwargs).object_key
[docs] class S3ObjectVersion(LookupHandler): """Lookup for AwsLambdaHook responses.""" TYPE_NAME: Final[ Literal["awslambda.S3ObjectVersion"] ] = "awslambda.S3ObjectVersion"
[docs] @classmethod def handle( # pylint: disable=arguments-differ cls, value: str, context: Union[CfnginContext, RunwayContext], *args: Any, **kwargs: Any, ) -> Optional[str]: """Retrieve metadata for an AWS Lambda deployment package. Args: value: Value to resolve. context: The current context object. Returns: Value that can be passed into CloudFormation property ``AWS::Lambda::Function.Code.S3ObjectVersion`` or ``AWS::Lambda::LayerVersion.Content.S3ObjectVersion``. """ return AwsLambdaLookup.handle( value, context, *args, **kwargs ).object_version_id