Skip to content

Commit

Permalink
feat: add option --container-host and --container-host-interface to s…
Browse files Browse the repository at this point in the history
…am local commands (aws#2806)
  • Loading branch information
xazhao authored and moelasmar committed Aug 5, 2021
1 parent 3da51e8 commit f15a16f
Show file tree
Hide file tree
Showing 17 changed files with 336 additions and 20 deletions.
15 changes: 14 additions & 1 deletion samcli/commands/local/cli_common/invoke_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ def __init__(
warm_container_initialization_mode: Optional[str] = None,
debug_function: Optional[str] = None,
shutdown: bool = False,
container_host: Optional[str] = None,
container_host_interface: Optional[str] = None,
) -> None:
"""
Initialize the context
Expand Down Expand Up @@ -124,6 +126,10 @@ def __init__(
option is enabled
shutdown bool
Optional. If True, perform a SHUTDOWN event when tearing down containers. Default False.
container_host string
Optional. Host of locally emulated Lambda container
container_host_interface string
Optional. Interface that Docker host binds ports to
"""
self._template_file = template_file
self._function_identifier = function_identifier
Expand Down Expand Up @@ -151,6 +157,9 @@ def __init__(
self._aws_profile = aws_profile
self._shutdown = shutdown

self._container_host = container_host
self._container_host_interface = container_host_interface

self._containers_mode = ContainersMode.COLD
self._containers_initializing_mode = ContainersInitializationMode.LAZY

Expand Down Expand Up @@ -251,7 +260,9 @@ def _initialize_all_functions_containers(self) -> None:

def initialize_function_container(function: Function) -> None:
function_config = self.local_lambda_runner.get_invoke_config(function)
self.lambda_runtime.run(None, function_config, self._debug_context)
self.lambda_runtime.run(
None, function_config, self._debug_context, self._container_host, self._container_host_interface
)

try:
async_context = AsyncContext()
Expand Down Expand Up @@ -335,6 +346,8 @@ def local_lambda_runner(self) -> LocalLambdaRunner:
aws_region=self._aws_region,
env_vars_values=self._env_vars_value,
debug_context=self._debug_context,
container_host=self._container_host,
container_host_interface=self._container_host_interface,
)
return self._local_lambda_runner

Expand Down
18 changes: 17 additions & 1 deletion samcli/commands/local/cli_common/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,23 @@ def local_common_options(f):
default=False,
help="If set, will emulate a shutdown event after the invoke completes, "
"in order to test extension handling of shutdown behavior.",
)
),
click.option(
"--container-host",
default="localhost",
show_default=True,
help="Host of locally emulated Lambda container. "
"This option is useful when the container runs on a different host than SAM CLI. "
"For example, if you want to run SAM CLI in a Docker container on macOS, "
"use this option with host.docker.internal",
),
click.option(
"--container-host-interface",
default="127.0.0.1",
show_default=True,
help="IP address of the host network interface that container ports should bind to. "
"Use 0.0.0.0 to bind to all interfaces.",
),
]

# Reverse the list to maintain ordering of options in help text printed with --help
Expand Down
8 changes: 8 additions & 0 deletions samcli/commands/local/invoke/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ def cli(
parameter_overrides,
config_file,
config_env,
container_host,
container_host_interface,
cdk_app,
cdk_context,
project_type: str,
Expand Down Expand Up @@ -109,6 +111,8 @@ def cli(
force_image_build,
shutdown,
parameter_overrides,
container_host,
container_host_interface,
project_type,
iac,
project,
Expand All @@ -134,6 +138,8 @@ def do_cli( # pylint: disable=R0914
force_image_build,
shutdown,
parameter_overrides,
container_host,
container_host_interface,
project_type: str,
iac: IacPlugin,
project: Project,
Expand Down Expand Up @@ -179,6 +185,8 @@ def do_cli( # pylint: disable=R0914
aws_region=ctx.region,
aws_profile=ctx.profile,
shutdown=shutdown,
container_host=container_host,
container_host_interface=container_host_interface,
iac=iac,
project=project,
) as context:
Expand Down
16 changes: 15 additions & 1 deletion samcli/commands/local/lib/local_lambda.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def __init__(
aws_region: Optional[str] = None,
env_vars_values: Optional[Dict[Any, Any]] = None,
debug_context: Optional[DebugContext] = None,
container_host: Optional[str] = None,
container_host_interface: Optional[str] = None,
) -> None:
"""
Initializes the class
Expand All @@ -55,6 +57,8 @@ def __init__(
:param string aws_region: Optional. AWS Region to use.
:param dict env_vars_values: Optional. Dictionary containing values of environment variables.
:param DebugContext debug_context: Optional. Debug context for the function (includes port, args, and path).
:param string container_host: Optional. Host of locally emulated Lambda container
:param string container_host_interface: Optional. Interface that Docker host binds ports to
"""

self.local_runtime = local_runtime
Expand All @@ -66,6 +70,8 @@ def __init__(
self.debug_context = debug_context
self._boto3_session_creds: Optional[Dict[str, str]] = None
self._boto3_region: Optional[str] = None
self.container_host = container_host
self.container_host_interface = container_host_interface

def invoke(
self,
Expand Down Expand Up @@ -120,7 +126,15 @@ def invoke(

# Invoke the function
try:
self.local_runtime.invoke(config, event, debug_context=self.debug_context, stdout=stdout, stderr=stderr)
self.local_runtime.invoke(
config,
event,
debug_context=self.debug_context,
stdout=stdout,
stderr=stderr,
container_host=self.container_host,
container_host_interface=self.container_host_interface,
)
except ContainerResponseException:
# NOTE(sriram-mv): This should still result in a exit code zero to avoid regressions.
LOG.info("No response from invoke container for %s", function.name)
Expand Down
8 changes: 8 additions & 0 deletions samcli/commands/local/start_api/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ def cli(
warm_containers,
shutdown,
debug_function,
container_host,
container_host_interface,
project_type,
cdk_context,
cdk_app,
Expand Down Expand Up @@ -119,6 +121,8 @@ def cli(
warm_containers,
shutdown,
debug_function,
container_host,
container_host_interface,
project_type,
iac,
project,
Expand Down Expand Up @@ -146,6 +150,8 @@ def do_cli( # pylint: disable=R0914
warm_containers,
shutdown,
debug_function,
container_host,
container_host_interface,
project_type,
iac,
project,
Expand Down Expand Up @@ -189,6 +195,8 @@ def do_cli( # pylint: disable=R0914
warm_container_initialization_mode=warm_containers,
debug_function=debug_function,
shutdown=shutdown,
container_host=container_host,
container_host_interface=container_host_interface,
iac=iac,
project=project,
) as invoke_context:
Expand Down
8 changes: 8 additions & 0 deletions samcli/commands/local/start_lambda/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ def cli(
warm_containers,
shutdown,
debug_function,
container_host,
container_host_interface,
cdk_context,
project_type,
cdk_app,
Expand Down Expand Up @@ -130,6 +132,8 @@ def cli(
warm_containers,
shutdown,
debug_function,
container_host,
container_host_interface,
iac,
project,
) # pragma: no cover
Expand All @@ -155,6 +159,8 @@ def do_cli( # pylint: disable=R0914
warm_containers,
shutdown,
debug_function,
container_host,
container_host_interface,
iac: IacPlugin,
project: Project,
):
Expand Down Expand Up @@ -196,6 +202,8 @@ def do_cli( # pylint: disable=R0914
warm_container_initialization_mode=warm_containers,
debug_function=debug_function,
shutdown=shutdown,
container_host=container_host,
container_host_interface=container_host_interface,
iac=iac,
project=project,
) as invoke_context:
Expand Down
20 changes: 16 additions & 4 deletions samcli/local/docker/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Container:
_STDOUT_FRAME_TYPE = 1
_STDERR_FRAME_TYPE = 2
RAPID_PORT_CONTAINER = "8080"
URL = "http://localhost:{port}/2015-03-31/functions/{function_name}/invocations"
URL = "http://{host}:{port}/2015-03-31/functions/{function_name}/invocations"
# Set connection timeout to 1 sec to support the large input.
RAPID_CONNECTION_TIMEOUT = 1

Expand All @@ -55,6 +55,8 @@ def __init__(
docker_client=None,
container_opts=None,
additional_volumes=None,
container_host="localhost",
container_host_interface="127.0.0.1",
):
"""
Initializes the class with given configuration. This does not automatically create or run the container.
Expand All @@ -71,6 +73,8 @@ def __init__(
:param docker_client: Optional, a docker client to replace the default one loaded from env
:param container_opts: Optional, a dictionary containing the container options
:param additional_volumes: Optional list of additional volumes
:param string container_host: Optional. Host of locally emulated Lambda container
:param string container_host_interface: Optional. Interface that Docker host binds ports to
"""

self._image = image
Expand All @@ -96,6 +100,10 @@ def __init__(
# selecting the first free port in a range that's not ephemeral.
self._start_port_range = 5000
self._end_port_range = 9000

self._container_host = container_host
self._container_host_interface = container_host_interface

try:
self.rapid_port_host = find_free_port(start=self._start_port_range, end=self._end_port_range)
except NoFreePortsError as ex:
Expand Down Expand Up @@ -150,11 +158,14 @@ def create(self):
if self._env_vars:
kwargs["environment"] = self._env_vars

kwargs["ports"] = {self.RAPID_PORT_CONTAINER: ("127.0.0.1", self.rapid_port_host)}
kwargs["ports"] = {self.RAPID_PORT_CONTAINER: (self._container_host_interface, self.rapid_port_host)}

if self._exposed_ports:
kwargs["ports"].update(
{container_port: ("127.0.0.1", host_port) for container_port, host_port in self._exposed_ports.items()}
{
container_port: (self._container_host_interface, host_port)
for container_port, host_port in self._exposed_ports.items()
}
)

if self._entrypoint:
Expand Down Expand Up @@ -266,8 +277,9 @@ def wait_for_http_response(self, name, event, stdout):
# TODO(sriram-mv): `aws-lambda-rie` is in a mode where the function_name is always "function"
# NOTE(sriram-mv): There is a connection timeout set on the http call to `aws-lambda-rie`, however there is not
# a read time out for the response received from the server.

resp = requests.post(
self.URL.format(port=self.rapid_port_host, function_name="function"),
self.URL.format(host=self._container_host, port=self.rapid_port_host, function_name="function"),
data=event.encode("utf-8"),
timeout=(self.RAPID_CONNECTION_TIMEOUT, None),
)
Expand Down
8 changes: 8 additions & 0 deletions samcli/local/docker/lambda_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def __init__(
memory_mb=128,
env_vars=None,
debug_options=None,
container_host=None,
container_host_interface=None,
):
"""
Initializes the class
Expand Down Expand Up @@ -74,6 +76,10 @@ def __init__(
Optional. Dictionary containing environment variables passed to container
debug_options DebugContext
Optional. Contains container debugging info (port, debugger path)
container_host string
Optional. Host of locally emulated Lambda container
container_host_interface
Optional. Interface that Docker host binds ports to
"""
if not Runtime.has_value(runtime) and not packagetype == IMAGE:
raise ValueError("Unsupported Lambda runtime {}".format(runtime))
Expand Down Expand Up @@ -119,6 +125,8 @@ def __init__(
env_vars=env_vars,
container_opts=additional_options,
additional_volumes=additional_volumes,
container_host=container_host,
container_host_interface=container_host_interface,
)

@staticmethod
Expand Down
Loading

0 comments on commit f15a16f

Please sign in to comment.