Skip to content

Commit

Permalink
feat: add option --container-host to commands local start-api, local …
Browse files Browse the repository at this point in the history
…start-lambda and local invoke (#2700)
  • Loading branch information
xazhao authored Mar 23, 2021
1 parent ad83b84 commit e653fe2
Show file tree
Hide file tree
Showing 17 changed files with 214 additions and 13 deletions.
6 changes: 6 additions & 0 deletions samcli/commands/local/cli_common/invoke_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def __init__(
warm_container_initialization_mode: Optional[str] = None,
debug_function: Optional[str] = None,
shutdown: bool = False,
container_host: Optional[str] = None,
) -> None:
"""
Initialize the context
Expand Down Expand Up @@ -121,6 +122,8 @@ 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
"""
self._template_file = template_file
self._function_identifier = function_identifier
Expand All @@ -146,6 +149,8 @@ def __init__(
self._aws_profile = aws_profile
self._shutdown = shutdown

self._container_host = container_host

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

Expand Down Expand Up @@ -327,6 +332,7 @@ 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,
)
return self._local_lambda_runner

Expand Down
11 changes: 10 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,16 @@ 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",
),
]

# Reverse the list to maintain ordering of options in help text printed with --help
Expand Down
4 changes: 4 additions & 0 deletions samcli/commands/local/invoke/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def cli(
parameter_overrides,
config_file,
config_env,
container_host,
):
"""
`sam local invoke` command entry point
Expand All @@ -97,6 +98,7 @@ def cli(
force_image_build,
shutdown,
parameter_overrides,
container_host,
) # pragma: no cover


Expand All @@ -119,6 +121,7 @@ def do_cli( # pylint: disable=R0914
force_image_build,
shutdown,
parameter_overrides,
container_host,
):
"""
Implementation of the ``cli`` method, just separated out for unit testing purposes
Expand Down Expand Up @@ -161,6 +164,7 @@ def do_cli( # pylint: disable=R0914
aws_region=ctx.region,
aws_profile=ctx.profile,
shutdown=shutdown,
container_host=container_host,
) as context:

# Invoke the function
Expand Down
12 changes: 11 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,7 @@ 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,
) -> None:
"""
Initializes the class
Expand All @@ -55,6 +56,7 @@ 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
"""

self.local_runtime = local_runtime
Expand All @@ -66,6 +68,7 @@ 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

def invoke(
self,
Expand Down Expand Up @@ -121,7 +124,14 @@ 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,
)
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
4 changes: 4 additions & 0 deletions samcli/commands/local/start_api/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def cli(
warm_containers,
shutdown,
debug_function,
container_host,
):
"""
`sam local start-api` command entry point
Expand Down Expand Up @@ -108,6 +109,7 @@ def cli(
warm_containers,
shutdown,
debug_function,
container_host,
) # pragma: no cover


Expand All @@ -132,6 +134,7 @@ def do_cli( # pylint: disable=R0914
warm_containers,
shutdown,
debug_function,
container_host,
):
"""
Implementation of the ``cli`` method, just separated out for unit testing purposes
Expand Down Expand Up @@ -172,6 +175,7 @@ def do_cli( # pylint: disable=R0914
warm_container_initialization_mode=warm_containers,
debug_function=debug_function,
shutdown=shutdown,
container_host=container_host,
) as invoke_context:

service = LocalApiService(lambda_invoke_context=invoke_context, port=port, host=host, static_dir=static_dir)
Expand Down
4 changes: 4 additions & 0 deletions samcli/commands/local/start_lambda/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def cli(
warm_containers,
shutdown,
debug_function,
container_host,
):
"""
`sam local start-lambda` command entry point
Expand All @@ -118,6 +119,7 @@ def cli(
warm_containers,
shutdown,
debug_function,
container_host,
) # pragma: no cover


Expand All @@ -141,6 +143,7 @@ def do_cli( # pylint: disable=R0914
warm_containers,
shutdown,
debug_function,
container_host,
):
"""
Implementation of the ``cli`` method, just separated out for unit testing purposes
Expand Down Expand Up @@ -180,6 +183,7 @@ def do_cli( # pylint: disable=R0914
warm_container_initialization_mode=warm_containers,
debug_function=debug_function,
shutdown=shutdown,
container_host=container_host,
) as invoke_context:

service = LocalLambdaService(lambda_invoke_context=invoke_context, port=port, host=host)
Expand Down
10 changes: 8 additions & 2 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,7 @@ def __init__(
docker_client=None,
container_opts=None,
additional_volumes=None,
container_host="localhost",
):
"""
Initializes the class with given configuration. This does not automatically create or run the container.
Expand All @@ -71,6 +72,7 @@ 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
"""

self._image = image
Expand All @@ -96,6 +98,9 @@ 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

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 @@ -266,8 +271,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
4 changes: 4 additions & 0 deletions samcli/local/docker/lambda_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def __init__(
memory_mb=128,
env_vars=None,
debug_options=None,
container_host=None,
):
"""
Initializes the class
Expand Down Expand Up @@ -74,6 +75,8 @@ 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
"""
if not Runtime.has_value(runtime) and not packagetype == IMAGE:
raise ValueError("Unsupported Lambda runtime {}".format(runtime))
Expand Down Expand Up @@ -119,6 +122,7 @@ def __init__(
env_vars=env_vars,
container_opts=additional_options,
additional_volumes=additional_volumes,
container_host=container_host,
)

@staticmethod
Expand Down
14 changes: 10 additions & 4 deletions samcli/local/lambdafn/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def __init__(self, container_manager, image_builder):
self._image_builder = image_builder
self._temp_uncompressed_paths_to_be_cleaned = []

def create(self, function_config, debug_context=None):
def create(self, function_config, debug_context=None, container_host=None):
"""
Create a new Container for the passed function, then store it in a dictionary using the function name,
so it can be retrieved later and used in the other functions. Make sure to use the debug_context only
Expand All @@ -56,6 +56,8 @@ def create(self, function_config, debug_context=None):
Configuration of the function to create a new Container for it.
debug_context DebugContext
Debugging context for the function (includes port, args, and path)
container_host string
Host of locally emulated Lambda container
Returns
-------
Expand All @@ -78,6 +80,7 @@ def create(self, function_config, debug_context=None):
memory_mb=function_config.memory,
env_vars=env_vars,
debug_options=debug_context,
container_host=container_host,
)
try:
# create the container.
Expand Down Expand Up @@ -132,6 +135,7 @@ def invoke(
debug_context=None,
stdout: Optional[StreamWriter] = None,
stderr: Optional[StreamWriter] = None,
container_host=None,
):
"""
Invoke the given Lambda function locally.
Expand All @@ -150,13 +154,15 @@ def invoke(
StreamWriter that receives stdout text from container.
:param samcli.lib.utils.stream_writer.StreamWriter stderr: Optional.
StreamWriter that receives stderr text from container.
:param string container_host: Optional.
Host of locally emulated Lambda container
:raises Keyboard
"""
timer = None
container = None
try:
# Start the container. This call returns immediately after the container starts
container = self.create(function_config, debug_context)
container = self.create(function_config, debug_context, container_host)
container = self.run(container, function_config, debug_context)
# Setup appropriate interrupt - timeout or Ctrl+C - before function starts executing.
#
Expand Down Expand Up @@ -301,7 +307,7 @@ def __init__(self, container_manager, image_builder):

super().__init__(container_manager, image_builder)

def create(self, function_config, debug_context=None):
def create(self, function_config, debug_context=None, container_host=None):
"""
Create a new Container for the passed function, then store it in a dictionary using the function name,
so it can be retrieved later and used in the other functions. Make sure to use the debug_context only
Expand Down Expand Up @@ -336,7 +342,7 @@ def create(self, function_config, debug_context=None):
)
debug_context = None

container = super().create(function_config, debug_context)
container = super().create(function_config, debug_context, container_host)
self._containers[function_config.name] = container

self._observer.watch(function_config)
Expand Down
Loading

0 comments on commit e653fe2

Please sign in to comment.