Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update sam local invoke to support new runtime options #7885

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion samcli/commands/local/invoke/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
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, 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,
Expand Down Expand Up @@ -60,6 +62,13 @@
"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 for application."
+ click.style(f"\n\nRuntimes: {', '.join(get_sorted_runtimes(INIT_RUNTIMES))}", bold=True),
)
@invoke_common_options
@local_common_options
@cli_framework_options
Expand Down Expand Up @@ -99,6 +108,7 @@ def cli(
hook_name,
skip_prepare_infra,
terraform_plan_file,
runtime,
):
"""
`sam local invoke` command entry point
Expand Down Expand Up @@ -129,6 +139,7 @@ def cli(
add_host,
invoke_image,
hook_name,
runtime,
) # pragma: no cover


Expand Down Expand Up @@ -156,6 +167,7 @@ def do_cli( # pylint: disable=R0914
add_host,
invoke_image,
hook_name,
runtime,
):
"""
Implementation of the ``cli`` method, just separated out for unit testing purposes
Expand Down Expand Up @@ -207,7 +219,11 @@ def do_cli( # pylint: disable=R0914
) as context:
# Invoke the function
context.local_lambda_runner.invoke(
context.function_identifier, event=event_data, stdout=context.stdout, stderr=context.stderr
context.function_identifier,
event=event_data,
stdout=context.stdout,
stderr=context.stderr,
override_runtime=runtime,
)

except FunctionNotFound as ex:
Expand Down
1 change: 1 addition & 0 deletions samcli/commands/local/invoke/core/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"container_host_interface",
"add_host",
"invoke_image",
"runtime",
]

CONFIGURATION_OPTION_NAMES: List[str] = ["config_env", "config_file"] + SAVE_PARAMS_OPTIONS
Expand Down
11 changes: 8 additions & 3 deletions samcli/commands/local/lib/local_lambda.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def invoke(
event: str,
stdout: Optional[StreamWriter] = None,
stderr: Optional[StreamWriter] = None,
override_runtime: Optional[str] = None,
) -> None:
"""
Find the Lambda function with given name and invoke it. Pass the given event to the function and return
Expand All @@ -117,6 +118,8 @@ def invoke(
Stream writer to write the output of the Lambda function to.
stderr samcli.lib.utils.stream_writer.StreamWriter
Stream writer to write the Lambda runtime logs to.
Runtime: str
To use instead of the runtime specified in the function configuration

Raises
------
Expand Down Expand Up @@ -151,7 +154,7 @@ def invoke(
LOG.info("Invoking Container created from %s", function.imageuri)

validate_architecture_runtime(function)
config = self.get_invoke_config(function)
config = self.get_invoke_config(function, override_runtime)

if (
function.metadata
Expand Down Expand Up @@ -204,7 +207,7 @@ def is_debugging(self) -> bool:
"""
return bool(self.debug_context)

def get_invoke_config(self, function: Function) -> FunctionConfig:
def get_invoke_config(self, function: Function, override_runtime: Optional[str] = None) -> FunctionConfig:
"""
Returns invoke configuration to pass to Lambda Runtime to invoke the given function

Expand Down Expand Up @@ -232,7 +235,9 @@ def get_invoke_config(self, function: Function) -> FunctionConfig:
return FunctionConfig(
name=function.name,
full_path=function.full_path,
runtime=function.runtime,
# override_runtime allows testing Lambda functions with a different
# runtime than specified in the function configuration
runtime=override_runtime if override_runtime else function.runtime,
handler=function.handler,
imageuri=function.imageuri,
imageconfig=function.imageconfig,
Expand Down
31 changes: 30 additions & 1 deletion schema/samcli.json
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@
"properties": {
"parameters": {
"title": "Parameters for the local invoke command",
"description": "Available parameters for the local invoke command:\n* terraform_plan_file:\nUsed for passing a custom plan file when executing the Terraform hook.\n* hook_name:\nHook package id to extend AWS SAM CLI commands functionality. \n\nExample: `terraform` to extend AWS SAM CLI commands functionality to support terraform applications. \n\nAvailable Hook Names: ['terraform']\n* skip_prepare_infra:\nSkip preparation stage when there are no infrastructure changes. Only used in conjunction with --hook-name.\n* event:\nJSON 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\n* no_event:\nDEPRECATED: By default no event is assumed.\n* template_file:\nAWS SAM template which references built artifacts for resources in the template. (if applicable)\n* env_vars:\nJSON file containing values for Lambda function's environment variables.\n* parameter_overrides:\nString that contains AWS CloudFormation parameter overrides encoded as key=value pairs.\n* debug_port:\nWhen specified, Lambda function container will start in debug mode and will expose this port on localhost.\n* debugger_path:\nHost path to a debugger that will be mounted into the Lambda container.\n* debug_args:\nAdditional arguments to be passed to the debugger.\n* container_env_vars:\nJSON file containing additional environment variables to be set within the container when used in a debugging session locally.\n* docker_volume_basedir:\nSpecify the location basedir where the SAM template exists. If Docker is running on a remote machine, Path of the SAM template must be mounted on the Docker machine and modified to match the remote machine.\n* log_file:\nFile to capture output logs.\n* layer_cache_basedir:\nSpecify the location basedir where the lambda layers used by the template will be downloaded to.\n* skip_pull_image:\nSkip pulling down the latest Docker image for Lambda runtime.\n* docker_network:\nName or ID of an existing docker network for AWS Lambda docker containers to connect to, along with the default bridge network. If not specified, the Lambda containers will only connect to the default bridge docker network.\n* force_image_build:\nForce rebuilding the image used for invoking functions with layers.\n* shutdown:\nEmulate a shutdown event after invoke completes, to test extension handling of shutdown behavior.\n* container_host:\nHost of locally emulated Lambda container. This option is useful when the container runs on a different host than AWS SAM CLI. For example, if one wants to run AWS SAM CLI in a Docker container on macOS, this option could specify `host.docker.internal`\n* container_host_interface:\nIP address of the host network interface that container ports should bind to. Use 0.0.0.0 to bind to all interfaces.\n* add_host:\nPasses a hostname to IP address mapping to the Docker container's host file. This parameter can be passed multiple times.Example:--add-host example.com:127.0.0.1\n* invoke_image:\nContainer image URIs for invoking functions or starting api and function. One can specify the image URI used for the local function invocation (--invoke-image public.ecr.aws/sam/build-nodejs20.x:latest). One can also specify for each individual function with (--invoke-image Function1=public.ecr.aws/sam/build-nodejs20.x:latest). If a function does not have invoke image specified, the default AWS SAM CLI emulation image will be used.\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)\n* save_params:\nSave the parameters provided via the command line to the configuration file.",
"description": "Available parameters for the local invoke command:\n* terraform_plan_file:\nUsed for passing a custom plan file when executing the Terraform hook.\n* hook_name:\nHook package id to extend AWS SAM CLI commands functionality. \n\nExample: `terraform` to extend AWS SAM CLI commands functionality to support terraform applications. \n\nAvailable Hook Names: ['terraform']\n* skip_prepare_infra:\nSkip preparation stage when there are no infrastructure changes. Only used in conjunction with --hook-name.\n* event:\nJSON 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\n* no_event:\nDEPRECATED: By default no event is assumed.\n* runtime:\nLambda runtime for application.\n\nRuntimes: dotnet8, dotnet6, go1.x, java21, java17, java11, java8.al2, nodejs22.x, nodejs20.x, nodejs18.x, nodejs16.x, provided, provided.al2, provided.al2023, python3.9, python3.8, python3.13, python3.12, python3.11, python3.10, ruby3.3, ruby3.2\n* template_file:\nAWS SAM template which references built artifacts for resources in the template. (if applicable)\n* env_vars:\nJSON file containing values for Lambda function's environment variables.\n* parameter_overrides:\nString that contains AWS CloudFormation parameter overrides encoded as key=value pairs.\n* debug_port:\nWhen specified, Lambda function container will start in debug mode and will expose this port on localhost.\n* debugger_path:\nHost path to a debugger that will be mounted into the Lambda container.\n* debug_args:\nAdditional arguments to be passed to the debugger.\n* container_env_vars:\nJSON file containing additional environment variables to be set within the container when used in a debugging session locally.\n* docker_volume_basedir:\nSpecify the location basedir where the SAM template exists. If Docker is running on a remote machine, Path of the SAM template must be mounted on the Docker machine and modified to match the remote machine.\n* log_file:\nFile to capture output logs.\n* layer_cache_basedir:\nSpecify the location basedir where the lambda layers used by the template will be downloaded to.\n* skip_pull_image:\nSkip pulling down the latest Docker image for Lambda runtime.\n* docker_network:\nName or ID of an existing docker network for AWS Lambda docker containers to connect to, along with the default bridge network. If not specified, the Lambda containers will only connect to the default bridge docker network.\n* force_image_build:\nForce rebuilding the image used for invoking functions with layers.\n* shutdown:\nEmulate a shutdown event after invoke completes, to test extension handling of shutdown behavior.\n* container_host:\nHost of locally emulated Lambda container. This option is useful when the container runs on a different host than AWS SAM CLI. For example, if one wants to run AWS SAM CLI in a Docker container on macOS, this option could specify `host.docker.internal`\n* container_host_interface:\nIP address of the host network interface that container ports should bind to. Use 0.0.0.0 to bind to all interfaces.\n* add_host:\nPasses a hostname to IP address mapping to the Docker container's host file. This parameter can be passed multiple times.Example:--add-host example.com:127.0.0.1\n* invoke_image:\nContainer image URIs for invoking functions or starting api and function. One can specify the image URI used for the local function invocation (--invoke-image public.ecr.aws/sam/build-nodejs20.x:latest). One can also specify for each individual function with (--invoke-image Function1=public.ecr.aws/sam/build-nodejs20.x:latest). If a function does not have invoke image specified, the default AWS SAM CLI emulation image will be used.\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)\n* save_params:\nSave the parameters provided via the command line to the configuration file.",
"type": "object",
"properties": {
"terraform_plan_file": {
Expand Down Expand Up @@ -430,6 +430,35 @@
"description": "DEPRECATED: By default no event is assumed.",
"default": true
},
"runtime": {
"title": "runtime",
"type": "string",
"description": "Lambda runtime for application.\n\nRuntimes: dotnet8, dotnet6, go1.x, java21, java17, java11, java8.al2, nodejs22.x, nodejs20.x, nodejs18.x, nodejs16.x, provided, provided.al2, provided.al2023, python3.9, python3.8, python3.13, python3.12, python3.11, python3.10, ruby3.3, ruby3.2",
"enum": [
"dotnet6",
"dotnet8",
"go1.x",
"java11",
"java17",
"java21",
"java8.al2",
"nodejs16.x",
"nodejs18.x",
"nodejs20.x",
"nodejs22.x",
"provided",
"provided.al2",
"provided.al2023",
"python3.10",
"python3.11",
"python3.12",
"python3.13",
"python3.8",
"python3.9",
"ruby3.2",
"ruby3.3"
]
},
"template_file": {
"title": "template_file",
"type": "string",
Expand Down
14 changes: 12 additions & 2 deletions tests/unit/commands/local/invoke/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def setUp(self):
self.add_host = (["prod-na.host:10.11.12.13"],)
self.invoke_image = ("amazon/aws-sam-cli-emulation-image-python3.9",)
self.hook_name = None
self.overide_runtime = None

self.ctx_mock = Mock()
self.ctx_mock.region = self.region_name
Expand Down Expand Up @@ -80,6 +81,7 @@ def call_cli(self):
add_host=self.add_host,
invoke_image=self.invoke_image,
hook_name=self.hook_name,
runtime=self.overide_runtime,
)

@patch("samcli.commands.local.cli_common.invoke_context.InvokeContext")
Expand Down Expand Up @@ -119,7 +121,11 @@ def test_cli_must_setup_context_and_invoke(self, get_event_mock, InvokeContextMo
)

context_mock.local_lambda_runner.invoke.assert_called_with(
context_mock.function_identifier, event=event_data, stdout=context_mock.stdout, stderr=context_mock.stderr
context_mock.function_identifier,
event=event_data,
stdout=context_mock.stdout,
stderr=context_mock.stderr,
override_runtime=None,
)
get_event_mock.assert_called_with(self.eventfile, exception_class=UserException)

Expand Down Expand Up @@ -160,7 +166,11 @@ def test_cli_must_invoke_with_no_event(self, get_event_mock, InvokeContextMock):

get_event_mock.assert_not_called()
context_mock.local_lambda_runner.invoke.assert_called_with(
context_mock.function_identifier, event="{}", stdout=context_mock.stdout, stderr=context_mock.stderr
context_mock.function_identifier,
event="{}",
stdout=context_mock.stdout,
stderr=context_mock.stderr,
override_runtime=None,
)

@parameterized.expand(
Expand Down
68 changes: 68 additions & 0 deletions tests/unit/commands/local/lib/test_local_lambda.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,74 @@ def test_must_work(self, FunctionConfigMock, is_debugging_mock, resolve_code_pat
resolve_code_path_patch.assert_called_with(self.real_path, function.codeuri)
self.local_lambda._make_env_vars.assert_called_with(function)

@patch("samcli.commands.local.lib.local_lambda.resolve_code_path")
@patch("samcli.commands.local.lib.local_lambda.LocalLambdaRunner.is_debugging")
@patch("samcli.commands.local.lib.local_lambda.FunctionConfig")
def test_must_work_with_runtime_option(self, FunctionConfigMock, is_debugging_mock, resolve_code_path_patch):
is_debugging_mock.return_value = False

env_vars = "envvars"
self.local_lambda._make_env_vars = Mock()
self.local_lambda._make_env_vars.return_value = env_vars

codepath = "codepath"
resolve_code_path_patch.return_value = codepath

layers = ["layer1", "layer2"]

function = Function(
stack_path="",
function_id="function_name",
name="function_name",
functionname="function_name",
runtime="runtime",
memory=1234,
timeout=12,
handler="handler",
codeuri="codeuri",
environment=None,
rolearn=None,
layers=layers,
events=None,
metadata=None,
inlinecode=None,
imageuri=None,
imageconfig=None,
packagetype=ZIP,
architectures=[ARM64],
codesign_config_arn=None,
function_url_config=None,
runtime_management_config=None,
function_build_info=FunctionBuildInfo.BuildableZip,
)

config = "someconfig"
override_runtime = "python3.11"
FunctionConfigMock.return_value = config
actual = self.local_lambda.get_invoke_config(function, override_runtime)
self.assertEqual(actual, config)

FunctionConfigMock.assert_called_with(
imageconfig=function.imageconfig,
imageuri=function.imageuri,
name=function.functionname,
packagetype=function.packagetype,
runtime=override_runtime,
handler=function.handler,
code_abs_path=codepath,
layers=layers,
memory=function.memory,
timeout=function.timeout,
env_vars=env_vars,
architecture=ARM64,
full_path=function.full_path,
runtime_management_config=function.runtime_management_config,
code_real_path=codepath,
)

resolve_code_path_patch.assert_called_with(self.real_path, function.codeuri)
self.local_lambda._make_env_vars.assert_called_with(function)

@patch("samcli.commands.local.lib.local_lambda.resolve_code_path")
@patch("samcli.commands.local.lib.local_lambda.LocalLambdaRunner.is_debugging")
@patch("samcli.commands.local.lib.local_lambda.FunctionConfig")
Expand Down
Loading
Loading