samcli/commands/local/invoke/cli.py (231 lines of code) (raw):
"""
CLI command for "local invoke" command
"""
import logging
import click
from samcli.cli.cli_config_file import ConfigProvider, configuration_option, save_params_option
from samcli.cli.main import aws_creds_options, pass_context, print_cmdline_args
from samcli.cli.main import common_options as cli_framework_options
from samcli.commands._utils.option_value_processor import process_image_options
from samcli.commands._utils.options import (
hook_name_click_option,
mount_symlinks_option,
skip_prepare_infra_option,
terraform_plan_file_option,
)
from samcli.commands.init.init_flow_helpers import get_sorted_runtimes
from samcli.commands.local.cli_common.options import invoke_common_options, local_common_options
from samcli.commands.local.invoke.core.command import InvokeCommand
from samcli.commands.local.lib.exceptions import InvalidIntermediateImageError
from samcli.lib.telemetry.metric import track_command
from samcli.lib.utils.version_checker import check_newer_version
from samcli.local.common.runtime_template import INIT_RUNTIMES
from samcli.local.docker.exceptions import (
ContainerNotStartableException,
DockerContainerCreationFailedException,
PortAlreadyInUse,
)
LOG = logging.getLogger(__name__)
HELP_TEXT = """
Invoke AWS serverless functions locally.
"""
DESCRIPTION = """
Invoke lambda functions in a Lambda-like environment locally.
An event body can be passed using the -e (--event) parameter.
Logs from the Lambda function will be written to stdout.
"""
STDIN_FILE_NAME = "-"
@click.command(
"invoke",
cls=InvokeCommand,
help=HELP_TEXT,
description=DESCRIPTION,
requires_credentials=False,
short_help=HELP_TEXT,
context_settings={"max_content_width": 120},
)
@configuration_option(provider=ConfigProvider(section="parameters"))
@terraform_plan_file_option
@hook_name_click_option(
force_prepare=False, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"]
)
@skip_prepare_infra_option
@click.option(
"--event",
"-e",
type=click.Path(),
help="JSON file containing event data passed to the Lambda function during invoke. If this option "
"is not specified, no event is assumed. Pass in the value '-' to input JSON via stdin",
)
@click.option("--no-event", is_flag=True, default=True, help="DEPRECATED: By default no event is assumed.", hidden=True)
@click.option(
"-r",
"--runtime",
type=click.Choice(get_sorted_runtimes(INIT_RUNTIMES)),
help="Lambda runtime used to invoke the function."
+ click.style(f"\n\nRuntimes: {', '.join(get_sorted_runtimes(INIT_RUNTIMES))}", bold=True),
)
@mount_symlinks_option
@invoke_common_options
@local_common_options
@cli_framework_options
@aws_creds_options
@click.argument("function_logical_id", required=False)
@save_params_option
@pass_context
@track_command # pylint: disable=R0914
@check_newer_version
@print_cmdline_args
def cli(
ctx,
function_logical_id,
template_file,
event,
no_event,
env_vars,
debug_port,
debug_args,
debugger_path,
container_env_vars,
docker_volume_basedir,
docker_network,
log_file,
layer_cache_basedir,
skip_pull_image,
force_image_build,
shutdown,
parameter_overrides,
save_params,
config_file,
config_env,
container_host,
container_host_interface,
add_host,
invoke_image,
hook_name,
skip_prepare_infra,
terraform_plan_file,
runtime,
mount_symlinks,
no_memory_limit,
):
"""
`sam local invoke` command entry point
"""
# All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing
do_cli(
ctx,
function_logical_id,
template_file,
event,
no_event,
env_vars,
debug_port,
debug_args,
debugger_path,
container_env_vars,
docker_volume_basedir,
docker_network,
log_file,
layer_cache_basedir,
skip_pull_image,
force_image_build,
shutdown,
parameter_overrides,
container_host,
container_host_interface,
add_host,
invoke_image,
hook_name,
runtime,
mount_symlinks,
no_memory_limit,
) # pragma: no cover
def do_cli( # pylint: disable=R0914
ctx,
function_identifier,
template,
event,
no_event,
env_vars,
debug_port,
debug_args,
debugger_path,
container_env_vars,
docker_volume_basedir,
docker_network,
log_file,
layer_cache_basedir,
skip_pull_image,
force_image_build,
shutdown,
parameter_overrides,
container_host,
container_host_interface,
add_host,
invoke_image,
hook_name,
runtime,
mount_symlinks,
no_mem_limit,
):
"""
Implementation of the ``cli`` method, just separated out for unit testing purposes
"""
from samcli.commands.exceptions import UserException
from samcli.commands.local.cli_common.invoke_context import InvokeContext
from samcli.commands.local.lib.exceptions import NoPrivilegeException, OverridesNotWellDefinedError
from samcli.commands.validate.lib.exceptions import InvalidSamDocumentException
from samcli.lib.providers.exceptions import InvalidLayerReference
from samcli.local.docker.lambda_debug_settings import DebuggingNotSupported
from samcli.local.docker.manager import DockerImagePullFailedException
from samcli.local.lambdafn.exceptions import FunctionNotFound
LOG.debug("local invoke command is called")
if event:
event_data = _get_event(event, exception_class=UserException)
else:
event_data = "{}"
processed_invoke_images = process_image_options(invoke_image)
# Pass all inputs to setup necessary context to invoke function locally.
# Handler exception raised by the processor for invalid args and print errors
try:
with InvokeContext(
template_file=template,
function_identifier=function_identifier,
env_vars_file=env_vars,
docker_volume_basedir=docker_volume_basedir,
docker_network=docker_network,
log_file=log_file,
skip_pull_image=skip_pull_image,
debug_ports=debug_port,
debug_args=debug_args,
debugger_path=debugger_path,
container_env_vars_file=container_env_vars,
parameter_overrides=parameter_overrides,
layer_cache_basedir=layer_cache_basedir,
force_image_build=force_image_build,
aws_region=ctx.region,
aws_profile=ctx.profile,
shutdown=shutdown,
container_host=container_host,
container_host_interface=container_host_interface,
add_host=add_host,
invoke_images=processed_invoke_images,
mount_symlinks=mount_symlinks,
no_mem_limit=no_mem_limit,
) as context:
# Invoke the function
context.local_lambda_runner.invoke(
context.function_identifier,
event=event_data,
stdout=context.stdout,
stderr=context.stderr,
override_runtime=runtime,
)
except FunctionNotFound as ex:
raise UserException(
"Function {} not found in template".format(function_identifier), wrapped_from=ex.__class__.__name__
) from ex
except (
InvalidSamDocumentException,
OverridesNotWellDefinedError,
InvalidLayerReference,
InvalidIntermediateImageError,
DebuggingNotSupported,
NoPrivilegeException,
PortAlreadyInUse,
) as ex:
raise UserException(str(ex), wrapped_from=ex.__class__.__name__) from ex
except (DockerImagePullFailedException, DockerContainerCreationFailedException) as ex:
raise UserException(str(ex), wrapped_from=ex.__class__.__name__) from ex
except ContainerNotStartableException as ex:
raise UserException(str(ex), wrapped_from=ex.__class__.__name__) from ex
def _get_event(event_file_name, exception_class):
"""
Read the event JSON data from the given file. If no file is provided, read the event from stdin.
:param string event_file_name: Path to event file, or '-' for stdin
:return string: Contents of the event file or stdin
"""
if event_file_name == STDIN_FILE_NAME:
# If event is empty, listen to stdin for event data until EOF
LOG.info("Reading invoke payload from stdin (you can also pass it from file with --event)")
# click.open_file knows to open stdin when filename is '-'. This is safer than manually opening streams, and
# accidentally closing a standard stream
try:
with click.open_file(event_file_name, "r", encoding="utf-8") as fp:
return fp.read()
except FileNotFoundError as ex:
raise exception_class(str(ex), wrapped_from=ex.__class__.__name__) from ex