Skip to content

Commit 225c9f6

Browse files
Merge from aws/aws-sam-cli/develop
2 parents 229140a + 9f08b90 commit 225c9f6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+919
-62
lines changed

samcli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
SAM CLI version
33
"""
44

5-
__version__ = "1.147.0"
5+
__version__ = "1.147.1"

samcli/cli/types.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,3 +662,40 @@ def restore_spaces(self):
662662
text_list[pos] = " "
663663

664664
return "".join(text_list)
665+
666+
667+
class TenantIdType(click.ParamType):
668+
"""
669+
Custom Click parameter type for tenant-id validation.
670+
Validates tenant-id format according to AWS Lambda multi-tenancy requirements.
671+
"""
672+
673+
name = "string"
674+
675+
# Tenant-id validation pattern
676+
TENANT_ID_PATTERN = re.compile(r"^[a-zA-Z0-9\._:\/=+\-@ ]+$")
677+
MIN_LENGTH = 1
678+
MAX_LENGTH = 256
679+
680+
def convert(self, value, param, ctx):
681+
if value is None:
682+
return None
683+
684+
# Check length constraints
685+
if len(value) < self.MIN_LENGTH or len(value) > self.MAX_LENGTH:
686+
raise click.BadParameter(
687+
f"{param.opts[0]} must be between {self.MIN_LENGTH} and {self.MAX_LENGTH} characters"
688+
)
689+
690+
# Check for empty or whitespace-only strings
691+
if not value.strip():
692+
raise click.BadParameter(f"{param.opts[0]} cannot be empty or contain only whitespace")
693+
694+
# Check format pattern
695+
if not self.TENANT_ID_PATTERN.match(value):
696+
raise click.BadParameter(
697+
f"{param.opts[0]} contains invalid characters. "
698+
"Allowed characters are a-z, A-Z, numbers, spaces, and the characters _ . : / = + - @"
699+
)
700+
701+
return value

samcli/commands/local/invoke/cli.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from samcli.cli.cli_config_file import ConfigProvider, configuration_option, save_params_option
1010
from samcli.cli.main import aws_creds_options, pass_context, print_cmdline_args
1111
from samcli.cli.main import common_options as cli_framework_options
12+
from samcli.cli.types import TenantIdType
1213
from samcli.commands._utils.option_value_processor import process_image_options
1314
from samcli.commands._utils.options import (
1415
hook_name_click_option,
@@ -74,6 +75,14 @@
7475
help="Lambda runtime used to invoke the function."
7576
+ click.style(f"\n\nRuntimes: {', '.join(get_sorted_runtimes(INIT_RUNTIMES))}", bold=True),
7677
)
78+
@click.option(
79+
"--tenant-id",
80+
type=TenantIdType(),
81+
help="Tenant ID for multi-tenant Lambda functions. "
82+
"Used to ensure compute isolation between different tenants. "
83+
"Must be 1-256 characters, the allowed characters are a-z and A-Z, "
84+
"numbers, spaces, and the characters _ . : / = + - @",
85+
)
7786
@mount_symlinks_option
7887
@invoke_common_options
7988
@local_common_options
@@ -117,6 +126,7 @@ def cli(
117126
runtime,
118127
mount_symlinks,
119128
no_memory_limit,
129+
tenant_id,
120130
):
121131
"""
122132
`sam local invoke` command entry point
@@ -150,6 +160,7 @@ def cli(
150160
runtime,
151161
mount_symlinks,
152162
no_memory_limit,
163+
tenant_id,
153164
) # pragma: no cover
154165

155166

@@ -180,6 +191,7 @@ def do_cli( # pylint: disable=R0914
180191
runtime,
181192
mount_symlinks,
182193
no_mem_limit,
194+
tenant_id,
183195
):
184196
"""
185197
Implementation of the ``cli`` method, just separated out for unit testing purposes
@@ -236,6 +248,7 @@ def do_cli( # pylint: disable=R0914
236248
context.local_lambda_runner.invoke(
237249
context.function_identifier,
238250
event=event_data,
251+
tenant_id=tenant_id,
239252
stdout=context.stdout,
240253
stderr=context.stderr,
241254
override_runtime=runtime,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"add_host",
3737
"invoke_image",
3838
"runtime",
39+
"tenant_id",
3940
"mount_symlinks",
4041
"no_memory_limit",
4142
]

samcli/commands/local/lib/exceptions.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,10 @@ class InvalidHandlerPathError(UserException):
4646
"""
4747
Raises when the handler is in an unexpected format and can't be parsed
4848
"""
49+
50+
51+
class TenantIdValidationError(UserException):
52+
"""
53+
Raised when there's a tenant-id validation error (missing tenant-id for multi-tenant functions
54+
or providing tenant-id for non-multi-tenant functions)
55+
"""

samcli/commands/local/lib/local_lambda.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
InvalidIntermediateImageError,
1717
NoPrivilegeException,
1818
OverridesNotWellDefinedError,
19+
TenantIdValidationError,
1920
UnsupportedInlineCodeError,
2021
)
2122
from samcli.lib.providers.provider import Function
@@ -99,6 +100,7 @@ def invoke(
99100
self,
100101
function_identifier: str,
101102
event: str,
103+
tenant_id: Optional[str] = None,
102104
stdout: Optional[StreamWriter] = None,
103105
stderr: Optional[StreamWriter] = None,
104106
override_runtime: Optional[str] = None,
@@ -158,6 +160,20 @@ def invoke(
158160
LOG.info("Invoking Container created from %s", function.imageuri)
159161

160162
validate_architecture_runtime(function)
163+
164+
# Validate tenant-id for multi-tenant functions
165+
if function.tenancy_config and isinstance(function.tenancy_config, dict):
166+
if not tenant_id:
167+
raise TenantIdValidationError(
168+
"The invoked function is enabled with tenancy configuration. "
169+
"Add a valid tenant ID in your request and try again."
170+
)
171+
elif tenant_id:
172+
raise TenantIdValidationError(
173+
"The invoked function is not enabled with tenancy configuration. "
174+
"Remove the tenant ID from your request and try again."
175+
)
176+
161177
config = self.get_invoke_config(function, override_runtime)
162178

163179
if (
@@ -173,6 +189,7 @@ def invoke(
173189
self.local_runtime.invoke(
174190
config,
175191
event,
192+
tenant_id,
176193
debug_context=self.debug_context,
177194
stdout=stdout,
178195
stderr=stderr,

samcli/commands/local/start_api/cli.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@
4848
a interpreted language, local changes will be available immediately in Docker container on every invoke. For more
4949
compiled languages or projects requiring complex packing support, it is recommended to run custom building solution
5050
and point AWS SAM CLI to the directory or file containing build artifacts.
51+
52+
For testing multi-tenant functions, pass tenant-id via X-Amz-Tenant-Id header. By default, each request uses a new
53+
container providing tenant isolation. When using --warm-containers, containers are reused and do not
54+
provide tenant isolation like production Lambda.
5155
"""
5256

5357

samcli/commands/local/start_lambda/cli.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@
3939
Programmatically invoke your Lambda function locally using the AWS CLI or SDKs.
4040
Start a local endpoint that emulates the AWS Lambda service, and one can run their automated
4141
tests against this local Lambda endpoint. Invokes to this endpoint can be sent using the AWS CLI or
42-
SDK and they will in turn locally execute the Lambda function specified in the request.\n
42+
SDK and they will in turn locally execute the Lambda function specified in the request.
43+
44+
For testing multi-tenant functions, pass tenant-id via the TenantId parameter in Lambda API calls. By default, each
45+
invocation uses a new container providing tenant isolation. When using --warm-containers, containers are reused
46+
and do not provide tenant isolation like production Lambda.
4347
"""
4448

4549

samcli/commands/remote/invoke/cli.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from samcli.cli.cli_config_file import ConfigProvider, configuration_option, save_params_option
99
from samcli.cli.context import Context
1010
from samcli.cli.main import aws_creds_options, common_options, pass_context, print_cmdline_args
11-
from samcli.cli.types import RemoteInvokeOutputFormatType
11+
from samcli.cli.types import RemoteInvokeOutputFormatType, TenantIdType
1212
from samcli.commands._utils.command_exception_handler import command_exception_handler
1313
from samcli.commands._utils.options import remote_invoke_parameter_option
1414
from samcli.commands.remote.invoke.core.command import RemoteInvokeCommand
@@ -66,6 +66,14 @@
6666
type=click.File("r", encoding="utf-8"),
6767
help="The file that contains the event that will be sent to the resource.",
6868
)
69+
@click.option(
70+
"--tenant-id",
71+
type=TenantIdType(),
72+
help="Tenant ID for multi-tenant Lambda functions. "
73+
"Used to ensure compute isolation between different tenants. "
74+
"Must be 1-256 characters, the allowed characters are a-z and A-Z, "
75+
"numbers, spaces, and the characters _ . : / = + - @",
76+
)
6977
@click.option(
7078
"--test-event-name",
7179
help="Name of the remote test event to send to the resource",
@@ -94,6 +102,7 @@ def cli(
94102
resource_id: str,
95103
event: str,
96104
event_file: TextIOWrapper,
105+
tenant_id: str,
97106
output: RemoteInvokeOutputFormat,
98107
test_event_name: str,
99108
parameter: dict,
@@ -110,6 +119,7 @@ def cli(
110119
resource_id,
111120
event,
112121
event_file,
122+
tenant_id,
113123
output,
114124
parameter,
115125
test_event_name,
@@ -125,6 +135,7 @@ def do_cli(
125135
resource_id: str,
126136
event: str,
127137
event_file: TextIOWrapper,
138+
tenant_id: str,
128139
output: RemoteInvokeOutputFormat,
129140
parameter: dict,
130141
test_event_name: str,
@@ -187,7 +198,7 @@ def do_cli(
187198
EventTracker.track_event("RemoteInvokeEventType", event_type)
188199

189200
remote_invoke_input = RemoteInvokeExecutionInfo(
190-
payload=event, payload_file=event_file, parameters=parameter, output_format=output
201+
payload=event, payload_file=event_file, tenant_id=tenant_id, parameters=parameter, output_format=output
191202
)
192203

193204
remote_invoke_context.run(remote_invoke_input=remote_invoke_input)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
INPUT_EVENT_OPTIONS: List[str] = ["event", "event_file", "test_event_name"]
1616

17-
ADDITIONAL_OPTIONS: List[str] = ["parameter", "output"]
17+
ADDITIONAL_OPTIONS: List[str] = ["parameter", "output", "tenant_id"]
1818

1919
AWS_CREDENTIAL_OPTION_NAMES: List[str] = ["region", "profile"]
2020

0 commit comments

Comments
 (0)