Skip to content

Commit 6b6b270

Browse files
authored
feat: allow explicit listing of function ids for local start-lambda command (#8392)
* feat: allow explicit listing of function ids for local start-lambda command * Update samcli/commands/local/cli_common/invoke_context.py * Update samcli/commands/local/cli_common/invoke_context.py * address comment * fix integration test failures * adress comment: clean up tests * Apply suggestions from code review
1 parent 952cf63 commit 6b6b270

13 files changed

Lines changed: 643 additions & 67 deletions

File tree

samcli/commands/local/cli_common/invoke_context.py

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from samcli.local.docker.exceptions import PortAlreadyInUse
2828
from samcli.local.docker.lambda_image import LambdaImage
2929
from samcli.local.docker.manager import ContainerManager
30+
from samcli.local.lambdafn.exceptions import FunctionNotFound
3031
from samcli.local.lambdafn.runtime import LambdaRuntime, WarmLambdaRuntime
3132
from samcli.local.layers.layer_downloader import LayerDownloader
3233

@@ -98,6 +99,7 @@ def __init__(
9899
invoke_images: Optional[str] = None,
99100
mount_symlinks: Optional[bool] = False,
100101
no_mem_limit: Optional[bool] = False,
102+
function_logical_ids: Optional[Tuple[str, ...]] = None,
101103
) -> None:
102104
"""
103105
Initialize the context
@@ -107,7 +109,7 @@ def __init__(
107109
template_file str
108110
Name or path to template
109111
function_identifier str
110-
Identifier of the function to invoke
112+
Identifier of the single function to invoke (used by 'sam local invoke' command)
111113
env_vars_file str
112114
Path to a file containing values for environment variables
113115
docker_volume_basedir str
@@ -154,10 +156,27 @@ def __init__(
154156
Optional. A dictionary that defines the custom invoke image URI of each function
155157
mount_symlinks bool
156158
Optional. Indicates if symlinks should be mounted inside the container
159+
function_logical_ids tuple(str)
160+
Optional. Tuple of function logical IDs to filter and make available for local execution.
161+
Used by 'sam local start-api' and 'sam local start-lambda' commands to limit which
162+
functions from the template are exposed. If not provided, all functions are available.
157163
"""
158164

159165
self._template_file = template_file
160166
self._function_identifier = function_identifier
167+
self._function_logical_ids = function_logical_ids
168+
169+
# Validate that function_identifier and function_logical_ids aren't both provided
170+
# function_identifier is for 'sam local invoke' (single function)
171+
# function_logical_ids is for 'sam local start-lambda' commands (multiple function filter)
172+
if self._function_identifier and self._function_logical_ids:
173+
LOG.warning(
174+
"Both function_identifier and function_logical_ids were provided. "
175+
"function_identifier is used for 'sam local invoke' to specify a single function, "
176+
"while function_logical_ids is used for 'sam local start-lambda' to filter functions. "
177+
"function_identifier will take precedence for single function invocation."
178+
)
179+
161180
self._env_vars_file = env_vars_file
162181
self._docker_volume_basedir = docker_volume_basedir
163182
self._docker_network = docker_network
@@ -241,10 +260,18 @@ def __enter__(self) -> "InvokeContext":
241260
if self._docker_volume_basedir:
242261
_function_providers_args[self._containers_mode].append(True)
243262

263+
_function_providers_kwargs: Dict[str, Any] = {}
264+
265+
if self._function_logical_ids:
266+
_function_providers_kwargs["function_logical_ids"] = self._function_logical_ids
267+
244268
self._function_provider = _function_providers_class[self._containers_mode](
245-
*_function_providers_args[self._containers_mode]
269+
*_function_providers_args[self._containers_mode], **_function_providers_kwargs
246270
)
247271

272+
# Validate function logical IDs after provider is initialized
273+
self._validate_function_logical_ids()
274+
248275
self._env_vars_value = self._get_env_vars_value(self._env_vars_file)
249276
self._container_env_vars_value = self._get_env_vars_value(self._container_env_vars_file)
250277
self._log_file_handle = self._setup_log_file(self._log_file)
@@ -448,6 +475,38 @@ def _clean_running_containers_and_related_resources(self) -> None:
448475
cast(WarmLambdaRuntime, self.lambda_runtime).clean_running_containers_and_related_resources()
449476
cast(RefreshableSamFunctionProvider, self._function_provider).stop_observer()
450477

478+
def _validate_function_logical_ids(self) -> None:
479+
"""
480+
Validates that all provided function logical IDs exist in the template.
481+
Raises FunctionNotFound with helpful error message if validation fails.
482+
"""
483+
if not self._function_logical_ids:
484+
return # No filtering requested
485+
486+
# Get all available function names/IDs from the provider
487+
all_functions_set = set()
488+
for func in self._function_provider.get_all():
489+
all_functions_set.add(func.name)
490+
all_functions_set.add(func.function_id)
491+
if func.functionname:
492+
all_functions_set.add(func.functionname)
493+
494+
# Check for invalid IDs
495+
invalid_ids = set(self._function_logical_ids) - all_functions_set
496+
497+
if invalid_ids:
498+
# Get all available function full paths, matching sam local invoke pattern
499+
all_function_full_paths = [f.full_path for f in self._function_provider.get_all()]
500+
501+
# Format message to match sam local invoke pattern exactly
502+
invalid_functions_str = ", ".join(sorted(invalid_ids))
503+
available_function_message = "{} not found. Possible options in your template: {}".format(
504+
invalid_functions_str, all_function_full_paths
505+
)
506+
LOG.info(available_function_message)
507+
508+
raise FunctionNotFound("Unable to find Function(s) with name(s) '{}'".format(invalid_functions_str))
509+
451510
def _add_account_id_to_global(self) -> None:
452511
"""
453512
Attempts to get the Account ID from the current session
@@ -556,6 +615,21 @@ def initialized_container_ids(self) -> List[str]:
556615
"""
557616
return self._initialized_container_ids
558617

618+
@property
619+
def function_logical_ids(self) -> Optional[Tuple[str, ...]]:
620+
"""
621+
Returns the tuple of function logical IDs to filter, if provided.
622+
623+
This is used by 'sam local start-lambda' commands
624+
to limit which functions from the template are made available for local execution.
625+
This is different from function_identifier which is used by 'sam local invoke'
626+
to specify a single function to invoke.
627+
628+
Returns:
629+
Optional[Tuple[str, ...]]: Tuple of function logical IDs or None if no filter
630+
"""
631+
return self._function_logical_ids
632+
559633
@property
560634
def stdout(self) -> StreamWriter:
561635
"""

samcli/commands/local/start_api/core/command.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,36 @@ class CustomFormatterContext(Context):
1919
@staticmethod
2020
def format_examples(ctx: Context, formatter: InvokeStartAPICommandHelpTextFormatter):
2121
with formatter.indented_section(name="Examples", extra_indents=1):
22-
formatter.write_rd(
23-
[
24-
RowDefinition(
25-
text="\n",
26-
),
27-
RowDefinition(
28-
name=style(f"$ {ctx.command_path}"),
29-
extra_row_modifiers=[ShowcaseRowModifier()],
30-
),
31-
]
32-
)
22+
with formatter.indented_section(name="Setup", extra_indents=1):
23+
formatter.write_rd(
24+
[
25+
RowDefinition(
26+
text="\n",
27+
),
28+
RowDefinition(
29+
name="Start the local lambda with Amazon API Gateway endpoint",
30+
),
31+
RowDefinition(
32+
name=style(f"$ {ctx.command_path}"),
33+
extra_row_modifiers=[ShowcaseRowModifier()],
34+
),
35+
]
36+
)
37+
with formatter.indented_section(name="Invoke local Lambda endpoint", extra_indents=1):
38+
formatter.write_rd(
39+
[
40+
RowDefinition(
41+
text="\n",
42+
),
43+
RowDefinition(
44+
name="Invoke Lambda function locally using curl",
45+
),
46+
RowDefinition(
47+
name=style("$ curl http://127.0.0.1:3000/hello"),
48+
extra_row_modifiers=[ShowcaseRowModifier()],
49+
),
50+
]
51+
)
3352

3453
def format_options(self, ctx: Context, formatter: InvokeStartAPICommandHelpTextFormatter) -> None: # type:ignore
3554
# NOTE(sriram-mv): `ignore` is put in place here for mypy even though it is the correct behavior,

samcli/commands/local/start_lambda/cli.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
requires_credentials=False,
5353
context_settings={"max_content_width": 120},
5454
)
55+
@click.argument("function_logical_ids", nargs=-1, required=False)
5556
@configuration_option(provider=ConfigProvider(section="parameters"))
5657
@terraform_plan_file_option
5758
@hook_name_click_option(
@@ -71,6 +72,7 @@
7172
@print_cmdline_args
7273
def cli(
7374
ctx, # pylint: disable=R0914
75+
function_logical_ids,
7476
# start-lambda Specific Options
7577
host,
7678
port,
@@ -110,6 +112,7 @@ def cli(
110112

111113
do_cli(
112114
ctx,
115+
function_logical_ids,
113116
host,
114117
port,
115118
template_file,
@@ -139,6 +142,7 @@ def cli(
139142

140143
def do_cli( # pylint: disable=R0914
141144
ctx,
145+
function_logical_ids,
142146
host,
143147
port,
144148
template,
@@ -208,6 +212,7 @@ def do_cli( # pylint: disable=R0914
208212
container_host_interface=container_host_interface,
209213
add_host=add_host,
210214
invoke_images=processed_invoke_images,
215+
function_logical_ids=function_logical_ids,
211216
no_mem_limit=no_mem_limit,
212217
) as invoke_context:
213218
service = LocalLambdaService(lambda_invoke_context=invoke_context, port=port, host=host)

samcli/commands/local/start_lambda/core/command.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,50 @@ def format_examples(ctx: Context, formatter: InvokeStartLambdaCommandHelpTextFor
3737
text="\n",
3838
),
3939
RowDefinition(
40-
name="Start the local lambda endpoint.",
40+
name="Start the local lambda endpoint for all functions",
4141
),
4242
RowDefinition(
4343
name=style(f"$ {ctx.command_path}"),
4444
extra_row_modifiers=[ShowcaseRowModifier()],
4545
),
4646
]
4747
)
48-
with formatter.indented_section(name="Using AWS CLI", extra_indents=1):
4948
formatter.write_rd(
5049
[
5150
RowDefinition(
5251
text="\n",
5352
),
5453
RowDefinition(
55-
name="Invoke Lambda function locally using the AWS CLI.",
54+
name="Start the local lambda endpoint for one function",
55+
),
56+
RowDefinition(
57+
name=style(f"$ {ctx.command_path} HelloWorldFunction"),
58+
extra_row_modifiers=[ShowcaseRowModifier()],
59+
),
60+
]
61+
)
62+
formatter.write_rd(
63+
[
64+
RowDefinition(
65+
text="\n",
66+
),
67+
RowDefinition(
68+
name="Start the local lambda endpoint for multiple functions",
69+
),
70+
RowDefinition(
71+
name=style(f"$ {ctx.command_path} HelloWorldFunctionOne HelloWorldFunctionTwo"),
72+
extra_row_modifiers=[ShowcaseRowModifier()],
73+
),
74+
]
75+
)
76+
with formatter.indented_section(name="Invoke local Lambda endpoint", extra_indents=1):
77+
formatter.write_rd(
78+
[
79+
RowDefinition(
80+
text="\n",
81+
),
82+
RowDefinition(
83+
name="Use the AWS CLI.",
5684
),
5785
RowDefinition(
5886
name=style(
@@ -63,7 +91,6 @@ def format_examples(ctx: Context, formatter: InvokeStartLambdaCommandHelpTextFor
6391
),
6492
]
6593
)
66-
with formatter.indented_section(name="Using AWS SDK", extra_indents=1):
6794
formatter.write_rd(
6895
[
6996
RowDefinition(

0 commit comments

Comments
 (0)