Skip to content

Commit e3fa341

Browse files
jonifevicheeymbfreder
authored
Update sam local invoke to support new runtime options (#7885)
* Update sam local invoke to support new runtime options * fix lint issue * fix schema * fix failing tests * Update help text * Update schema --------- Co-authored-by: vicheey <[email protected]> Co-authored-by: Frederic Mbea <[email protected]> Co-authored-by: mbfreder <[email protected]>
1 parent 5fd0909 commit e3fa341

File tree

7 files changed

+205
-7
lines changed

7 files changed

+205
-7
lines changed

samcli/commands/local/invoke/cli.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
skip_prepare_infra_option,
1717
terraform_plan_file_option,
1818
)
19+
from samcli.commands.init.init_flow_helpers import get_sorted_runtimes
1920
from samcli.commands.local.cli_common.options import invoke_common_options, local_common_options
2021
from samcli.commands.local.invoke.core.command import InvokeCommand
2122
from samcli.commands.local.lib.exceptions import InvalidIntermediateImageError
2223
from samcli.lib.telemetry.metric import track_command
2324
from samcli.lib.utils.version_checker import check_newer_version
25+
from samcli.local.common.runtime_template import INIT_RUNTIMES
2426
from samcli.local.docker.exceptions import (
2527
ContainerNotStartableException,
2628
DockerContainerCreationFailedException,
@@ -65,6 +67,13 @@
6567
"is not specified, no event is assumed. Pass in the value '-' to input JSON via stdin",
6668
)
6769
@click.option("--no-event", is_flag=True, default=True, help="DEPRECATED: By default no event is assumed.", hidden=True)
70+
@click.option(
71+
"-r",
72+
"--runtime",
73+
type=click.Choice(get_sorted_runtimes(INIT_RUNTIMES)),
74+
help="Lambda runtime used to invoke the function."
75+
+ click.style(f"\n\nRuntimes: {', '.join(get_sorted_runtimes(INIT_RUNTIMES))}", bold=True),
76+
)
6877
@mount_symlinks_option
6978
@invoke_common_options
7079
@local_common_options
@@ -105,6 +114,7 @@ def cli(
105114
hook_name,
106115
skip_prepare_infra,
107116
terraform_plan_file,
117+
runtime,
108118
mount_symlinks,
109119
no_memory_limit,
110120
):
@@ -137,6 +147,7 @@ def cli(
137147
add_host,
138148
invoke_image,
139149
hook_name,
150+
runtime,
140151
mount_symlinks,
141152
no_memory_limit,
142153
) # pragma: no cover
@@ -166,6 +177,7 @@ def do_cli( # pylint: disable=R0914
166177
add_host,
167178
invoke_image,
168179
hook_name,
180+
runtime,
169181
mount_symlinks,
170182
no_mem_limit,
171183
):
@@ -221,7 +233,11 @@ def do_cli( # pylint: disable=R0914
221233
) as context:
222234
# Invoke the function
223235
context.local_lambda_runner.invoke(
224-
context.function_identifier, event=event_data, stdout=context.stdout, stderr=context.stderr
236+
context.function_identifier,
237+
event=event_data,
238+
stdout=context.stdout,
239+
stderr=context.stderr,
240+
override_runtime=runtime,
225241
)
226242

227243
except FunctionNotFound as ex:

samcli/commands/local/invoke/core/options.py

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"container_host_interface",
3636
"add_host",
3737
"invoke_image",
38+
"runtime",
3839
"mount_symlinks",
3940
"no_memory_limit",
4041
]

samcli/commands/local/lib/local_lambda.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def invoke(
100100
event: str,
101101
stdout: Optional[StreamWriter] = None,
102102
stderr: Optional[StreamWriter] = None,
103+
override_runtime: Optional[str] = None,
103104
) -> None:
104105
"""
105106
Find the Lambda function with given name and invoke it. Pass the given event to the function and return
@@ -117,6 +118,8 @@ def invoke(
117118
Stream writer to write the output of the Lambda function to.
118119
stderr samcli.lib.utils.stream_writer.StreamWriter
119120
Stream writer to write the Lambda runtime logs to.
121+
Runtime: str
122+
To use instead of the runtime specified in the function configuration
120123
121124
Raises
122125
------
@@ -151,7 +154,7 @@ def invoke(
151154
LOG.info("Invoking Container created from %s", function.imageuri)
152155

153156
validate_architecture_runtime(function)
154-
config = self.get_invoke_config(function)
157+
config = self.get_invoke_config(function, override_runtime)
155158

156159
if (
157160
function.metadata
@@ -204,7 +207,7 @@ def is_debugging(self) -> bool:
204207
"""
205208
return bool(self.debug_context)
206209

207-
def get_invoke_config(self, function: Function) -> FunctionConfig:
210+
def get_invoke_config(self, function: Function, override_runtime: Optional[str] = None) -> FunctionConfig:
208211
"""
209212
Returns invoke configuration to pass to Lambda Runtime to invoke the given function
210213
@@ -232,7 +235,9 @@ def get_invoke_config(self, function: Function) -> FunctionConfig:
232235
return FunctionConfig(
233236
name=function.name,
234237
full_path=function.full_path,
235-
runtime=function.runtime,
238+
# override_runtime allows testing Lambda functions with a different
239+
# runtime than specified in the function configuration
240+
runtime=override_runtime if override_runtime else function.runtime,
236241
handler=function.handler,
237242
imageuri=function.imageuri,
238243
imageconfig=function.imageconfig,

schema/samcli.json

+30-1
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@
406406
"properties": {
407407
"parameters": {
408408
"title": "Parameters for the local invoke command",
409-
"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* mount_symlinks:\nSpecify if symlinks at the top level of the code should be mounted inside the container. Activating this flag could allow access to locations outside of your workspace by using a symbolic link. By default symlinks are not mounted.\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* no_memory_limit:\nRemoves the Memory limit during emulation. With this parameter, the underlying container will run without a --memory parameter\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.",
409+
"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 used to invoke the function.\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* mount_symlinks:\nSpecify if symlinks at the top level of the code should be mounted inside the container. Activating this flag could allow access to locations outside of your workspace by using a symbolic link. By default symlinks are not mounted.\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* no_memory_limit:\nRemoves the Memory limit during emulation. With this parameter, the underlying container will run without a --memory parameter\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.",
410410
"type": "object",
411411
"properties": {
412412
"terraform_plan_file": {
@@ -435,6 +435,35 @@
435435
"description": "DEPRECATED: By default no event is assumed.",
436436
"default": true
437437
},
438+
"runtime": {
439+
"title": "runtime",
440+
"type": "string",
441+
"description": "Lambda runtime used to invoke the function.\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",
442+
"enum": [
443+
"dotnet6",
444+
"dotnet8",
445+
"go1.x",
446+
"java11",
447+
"java17",
448+
"java21",
449+
"java8.al2",
450+
"nodejs16.x",
451+
"nodejs18.x",
452+
"nodejs20.x",
453+
"nodejs22.x",
454+
"provided",
455+
"provided.al2",
456+
"provided.al2023",
457+
"python3.10",
458+
"python3.11",
459+
"python3.12",
460+
"python3.13",
461+
"python3.8",
462+
"python3.9",
463+
"ruby3.2",
464+
"ruby3.3"
465+
]
466+
},
438467
"mount_symlinks": {
439468
"title": "mount_symlinks",
440469
"type": "boolean",

tests/unit/commands/local/invoke/test_cli.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def setUp(self):
5050
self.add_host = (["prod-na.host:10.11.12.13"],)
5151
self.invoke_image = ("amazon/aws-sam-cli-emulation-image-python3.9",)
5252
self.hook_name = None
53+
self.overide_runtime = None
5354
self.mount_symlinks = False
5455
self.no_mem_limit = False
5556

@@ -82,6 +83,7 @@ def call_cli(self):
8283
add_host=self.add_host,
8384
invoke_image=self.invoke_image,
8485
hook_name=self.hook_name,
86+
runtime=self.overide_runtime,
8587
mount_symlinks=self.mount_symlinks,
8688
no_mem_limit=self.no_mem_limit,
8789
)
@@ -125,7 +127,11 @@ def test_cli_must_setup_context_and_invoke(self, get_event_mock, InvokeContextMo
125127
)
126128

127129
context_mock.local_lambda_runner.invoke.assert_called_with(
128-
context_mock.function_identifier, event=event_data, stdout=context_mock.stdout, stderr=context_mock.stderr
130+
context_mock.function_identifier,
131+
event=event_data,
132+
stdout=context_mock.stdout,
133+
stderr=context_mock.stderr,
134+
override_runtime=None,
129135
)
130136
get_event_mock.assert_called_with(self.eventfile, exception_class=UserException)
131137

@@ -168,7 +174,11 @@ def test_cli_must_invoke_with_no_event(self, get_event_mock, InvokeContextMock):
168174

169175
get_event_mock.assert_not_called()
170176
context_mock.local_lambda_runner.invoke.assert_called_with(
171-
context_mock.function_identifier, event="{}", stdout=context_mock.stdout, stderr=context_mock.stderr
177+
context_mock.function_identifier,
178+
event="{}",
179+
stdout=context_mock.stdout,
180+
stderr=context_mock.stderr,
181+
override_runtime=None,
172182
)
173183

174184
@parameterized.expand(

tests/unit/commands/local/lib/test_local_lambda.py

+68
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,74 @@ def test_must_work(self, FunctionConfigMock, is_debugging_mock, resolve_code_pat
470470
resolve_code_path_patch.assert_called_with(self.real_path, function.codeuri)
471471
self.local_lambda._make_env_vars.assert_called_with(function)
472472

473+
@patch("samcli.commands.local.lib.local_lambda.resolve_code_path")
474+
@patch("samcli.commands.local.lib.local_lambda.LocalLambdaRunner.is_debugging")
475+
@patch("samcli.commands.local.lib.local_lambda.FunctionConfig")
476+
def test_must_work_with_runtime_option(self, FunctionConfigMock, is_debugging_mock, resolve_code_path_patch):
477+
is_debugging_mock.return_value = False
478+
479+
env_vars = "envvars"
480+
self.local_lambda._make_env_vars = Mock()
481+
self.local_lambda._make_env_vars.return_value = env_vars
482+
483+
codepath = "codepath"
484+
resolve_code_path_patch.return_value = codepath
485+
486+
layers = ["layer1", "layer2"]
487+
488+
function = Function(
489+
stack_path="",
490+
function_id="function_name",
491+
name="function_name",
492+
functionname="function_name",
493+
runtime="runtime",
494+
memory=1234,
495+
timeout=12,
496+
handler="handler",
497+
codeuri="codeuri",
498+
environment=None,
499+
rolearn=None,
500+
layers=layers,
501+
events=None,
502+
metadata=None,
503+
inlinecode=None,
504+
imageuri=None,
505+
imageconfig=None,
506+
packagetype=ZIP,
507+
architectures=[ARM64],
508+
codesign_config_arn=None,
509+
function_url_config=None,
510+
runtime_management_config=None,
511+
function_build_info=FunctionBuildInfo.BuildableZip,
512+
)
513+
514+
config = "someconfig"
515+
override_runtime = "python3.11"
516+
FunctionConfigMock.return_value = config
517+
actual = self.local_lambda.get_invoke_config(function, override_runtime)
518+
self.assertEqual(actual, config)
519+
520+
FunctionConfigMock.assert_called_with(
521+
imageconfig=function.imageconfig,
522+
imageuri=function.imageuri,
523+
name=function.functionname,
524+
packagetype=function.packagetype,
525+
runtime=override_runtime,
526+
handler=function.handler,
527+
code_abs_path=codepath,
528+
layers=layers,
529+
memory=function.memory,
530+
timeout=function.timeout,
531+
env_vars=env_vars,
532+
architecture=ARM64,
533+
full_path=function.full_path,
534+
runtime_management_config=function.runtime_management_config,
535+
code_real_path=codepath,
536+
)
537+
538+
resolve_code_path_patch.assert_called_with(self.real_path, function.codeuri)
539+
self.local_lambda._make_env_vars.assert_called_with(function)
540+
473541
@patch("samcli.commands.local.lib.local_lambda.resolve_code_path")
474542
@patch("samcli.commands.local.lib.local_lambda.LocalLambdaRunner.is_debugging")
475543
@patch("samcli.commands.local.lib.local_lambda.FunctionConfig")

0 commit comments

Comments
 (0)