diff --git a/designs/debug_mode_multiple_exposed_ports.md b/designs/debug_mode_multiple_exposed_ports.md new file mode 100644 index 0000000000..c6486b3925 --- /dev/null +++ b/designs/debug_mode_multiple_exposed_ports.md @@ -0,0 +1,121 @@ +Title: Template for design documents +==================================== + +What is the problem? +-------------------- +Currently, there is one port is exposed from Docker instance when running lambda in debug mode. +This port is used to connect a debugger. In my case, I need two ports to be exposed due to Debugger +implementation specific (the Debugger connect to two sockets to collect different information). + +What will be changed? +--------------------- +SAM CLI has a ``--debug-port`` parameter that provide a port. This parameter is stored in DebugContext object. +``DebugContext`` should store an array of ports instead of a single port. This array should be transformed +into a map containing each stored port when passing to docker container arguments. + +Success criteria for the change +------------------------------- +All ports specified via single or multiple ``--debug-port`` SAM CLI options should be exposed by docker container. + +Out-of-Scope +------------ + +User Experience Walkthrough +--------------------------- +From the user perspective, it should only provide an ability to specify multiple ``--debug-port`` options: +``--debug-port 5600 --debug-port 5601`` + +Implementation +============== + +CLI Changes +----------- + +SAM CLI provide an option to specify multiple ports ``--debug-port 5600 --debug-port 5601``. + +### Breaking Change + +No changes. + +Design +------ + +Update ``--debug-port`` option to allow to use it multiple times in SAM CLI. +The option type should take only integer values. The value is stored in ``DebugContext``. +This value should be converted into a map of ``{ container_port : host_port }`` +that is passed to ``ports`` argument when creating a docker container. + +`.samrc` Changes +---------------- + +No changes. + +Security +-------- + +No changes. + +**What new dependencies (libraries/cli) does this change require?** + +**What other Docker container images are you using?** + +**Are you creating a new HTTP endpoint? If so explain how it will be +created & used** + +**Are you connecting to a remote API? If so explain how is this +connection secured** + +**Are you reading/writing to a temporary folder? If so, what is this +used for and when do you clean up?** + +**How do you validate new .samrc configuration?** + +What is your Testing Plan (QA)? +=============================== + +Goal +---- +Make sure SAM CLI users can specify multiple ports and those ports are exposed +after creating a docker container in debug mode: + +``sam local invoke --template /template.yaml --event /event.json --debugger-path --debug-port 5600 --debug-port 5601`` + +Pre-requesites +-------------- +Running SAM CLI with debug mode. + +Test Scenarios/Cases +-------------------- +1. Single port is specified: ``--debug-port 5600`` +2. Multiple ports are specified: ``--debug-port 5600 --debug-port 5601`` +3. No ports specified: ``--debug-port `` +4. No ``--debug-port`` parameter is specified + +Expected Results +---------------- +1. Single port is exposed in docker container +2. All specified ports are exposed in docker container +3. No ports exposed. +4. No ports exposed. + +Pass/Fail +--------- + +Documentation Changes +===================== + +Open Issues +============ +- [1463](https://github.com/awslabs/aws-sam-cli/issues/1463) + +Task Breakdown +============== + +- \[x\] Send a Pull Request with this design document +- \[ \] Build the command line interface +- \[ \] Build the underlying library +- \[x\] Unit tests +- \[x\] Functional Tests +- \[x\] Integration tests +- \[ \] Run all tests on Windows +- \[x\] Update documentation diff --git a/samcli/commands/local/cli_common/invoke_context.py b/samcli/commands/local/cli_common/invoke_context.py index 6fdbb0584e..cf7821146e 100644 --- a/samcli/commands/local/cli_common/invoke_context.py +++ b/samcli/commands/local/cli_common/invoke_context.py @@ -45,7 +45,7 @@ def __init__( docker_network=None, log_file=None, skip_pull_image=None, - debug_port=None, + debug_ports=None, debug_args=None, debugger_path=None, parameter_overrides=None, @@ -76,8 +76,8 @@ def __init__( Should we skip pulling the Docker container image? aws_profile str Name of the profile to fetch AWS credentials from - debug_port int - Port to bind the debugger to + debug_ports tuple(int) + Ports to bind the debugger to debug_args str Additional arguments passed to the debugger debugger_path str @@ -98,7 +98,7 @@ def __init__( self._docker_network = docker_network self._log_file = log_file self._skip_pull_image = skip_pull_image - self._debug_port = debug_port + self._debug_ports = debug_ports self._debug_args = debug_args self._debugger_path = debugger_path self._parameter_overrides = parameter_overrides or {} @@ -129,7 +129,7 @@ def __enter__(self): self._env_vars_value = self._get_env_vars_value(self._env_vars_file) self._log_file_handle = self._setup_log_file(self._log_file) - self._debug_context = self._get_debug_context(self._debug_port, self._debug_args, self._debugger_path) + self._debug_context = self._get_debug_context(self._debug_ports, self._debug_args, self._debugger_path) self._container_manager = self._get_container_manager(self._docker_network, self._skip_pull_image) @@ -314,14 +314,14 @@ def _setup_log_file(log_file): return open(log_file, "wb") @staticmethod - def _get_debug_context(debug_port, debug_args, debugger_path): + def _get_debug_context(debug_ports, debug_args, debugger_path): """ Creates a DebugContext if the InvokeContext is in a debugging mode Parameters ---------- - debug_port int - Port to bind the debugger to + debug_ports tuple(int) + Ports to bind the debugger to debug_args str Additional arguments passed to the debugger debugger_path str @@ -337,7 +337,7 @@ def _get_debug_context(debug_port, debug_args, debugger_path): samcli.commands.local.cli_common.user_exceptions.DebugContext When the debugger_path is not valid """ - if debug_port and debugger_path: + if debug_ports and debugger_path: try: debugger = Path(debugger_path).resolve(strict=True) except OSError as error: @@ -350,7 +350,7 @@ def _get_debug_context(debug_port, debug_args, debugger_path): raise DebugContextException("'{}' should be a directory with the debugger in it.".format(debugger_path)) debugger_path = str(debugger) - return DebugContext(debug_port=debug_port, debug_args=debug_args, debugger_path=debugger_path) + return DebugContext(debug_ports=debug_ports, debug_args=debug_args, debugger_path=debugger_path) @staticmethod def _get_container_manager(docker_network, skip_pull_image): diff --git a/samcli/commands/local/cli_common/options.py b/samcli/commands/local/cli_common/options.py index d98633a418..af36a1d963 100644 --- a/samcli/commands/local/cli_common/options.py +++ b/samcli/commands/local/cli_common/options.py @@ -92,6 +92,8 @@ def invoke_common_options(f): help="When specified, Lambda function container will start in debug mode and will expose this " "port on localhost.", envvar="SAM_DEBUG_PORT", + type=click.INT, + multiple=True, ), click.option( "--debugger-path", help="Host path to a debugger that will be mounted into the Lambda container." diff --git a/samcli/commands/local/invoke/cli.py b/samcli/commands/local/invoke/cli.py index 95b97f27b8..18b1a36d2b 100644 --- a/samcli/commands/local/invoke/cli.py +++ b/samcli/commands/local/invoke/cli.py @@ -136,7 +136,7 @@ def do_cli( # pylint: disable=R0914 docker_network=docker_network, log_file=log_file, skip_pull_image=skip_pull_image, - debug_port=debug_port, + debug_ports=debug_port, debug_args=debug_args, debugger_path=debugger_path, parameter_overrides=parameter_overrides, diff --git a/samcli/commands/local/lib/debug_context.py b/samcli/commands/local/lib/debug_context.py index 053b580255..a1b8fda3f6 100644 --- a/samcli/commands/local/lib/debug_context.py +++ b/samcli/commands/local/lib/debug_context.py @@ -4,14 +4,21 @@ class DebugContext: - def __init__(self, debug_port=None, debugger_path=None, debug_args=None): + def __init__(self, debug_ports=None, debugger_path=None, debug_args=None): + """ + Initialize the Debug Context with Lambda debugger options - self.debug_port = debug_port + :param tuple(int) debug_ports: Collection of debugger ports to be exposed from a docker container + :param Path debugger_path: Path to a debugger to be launched + :param string debug_args: Additional arguments to be passed to the debugger + """ + + self.debug_ports = debug_ports self.debugger_path = debugger_path self.debug_args = debug_args def __bool__(self): - return bool(self.debug_port) + return bool(self.debug_ports) def __nonzero__(self): return self.__bool__() diff --git a/samcli/commands/local/lib/local_lambda.py b/samcli/commands/local/lib/local_lambda.py index 199f253a95..c1ddbb4888 100644 --- a/samcli/commands/local/lib/local_lambda.py +++ b/samcli/commands/local/lib/local_lambda.py @@ -40,9 +40,10 @@ def __init__( :param samcli.commands.local.lib.provider.FunctionProvider function_provider: Provider that can return a Lambda function :param string cwd: Current working directory. We will resolve all function CodeURIs relative to this directory. - :param dict env_vars_values: Optional. Dictionary containing values of environment variables - :param integer debug_port: Optional. Port to bind the debugger to - :param string debug_args: Optional. Additional arguments passed to the debugger + :param string aws_profile: Optional. Name of the profile to fetch AWS credentials from. + :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). """ self.local_runtime = local_runtime diff --git a/samcli/commands/local/start_api/cli.py b/samcli/commands/local/start_api/cli.py index 8063101359..83f699e33b 100644 --- a/samcli/commands/local/start_api/cli.py +++ b/samcli/commands/local/start_api/cli.py @@ -129,7 +129,7 @@ def do_cli( # pylint: disable=R0914 docker_network=docker_network, log_file=log_file, skip_pull_image=skip_pull_image, - debug_port=debug_port, + debug_ports=debug_port, debug_args=debug_args, debugger_path=debugger_path, parameter_overrides=parameter_overrides, diff --git a/samcli/commands/local/start_lambda/cli.py b/samcli/commands/local/start_lambda/cli.py index 0004df846b..b607febe2e 100644 --- a/samcli/commands/local/start_lambda/cli.py +++ b/samcli/commands/local/start_lambda/cli.py @@ -138,7 +138,7 @@ def do_cli( # pylint: disable=R0914 docker_network=docker_network, log_file=log_file, skip_pull_image=skip_pull_image, - debug_port=debug_port, + debug_ports=debug_port, debug_args=debug_args, debugger_path=debugger_path, parameter_overrides=parameter_overrides, diff --git a/samcli/local/docker/lambda_container.py b/samcli/local/docker/lambda_container.py index 8fa711d93d..2c17a3a195 100644 --- a/samcli/local/docker/lambda_container.py +++ b/samcli/local/docker/lambda_container.py @@ -92,20 +92,25 @@ def __init__( @staticmethod def _get_exposed_ports(debug_options): """ - Return Docker container port binding information. If a debug port is given, then we will ask Docker to - bind to same port both inside and outside the container ie. Runtime process is started in debug mode with + Return Docker container port binding information. If a debug port tuple is given, then we will ask Docker to + bind every given port to same port both inside and outside the container ie. Runtime process is started in debug mode with at given port inside the container and exposed to the host machine at the same port - :param int debug_port: Optional, integer value of debug port + :param DebugContext debug_options: Debugging options for the function (includes debug port, args, and path) :return dict: Dictionary containing port binding information. None, if debug_port was not given """ if not debug_options: return None - return { - # container port : host port - debug_options.debug_port: debug_options.debug_port - } + if not debug_options.debug_ports: + return None + + # container port : host port + ports_map = {} + for port in debug_options.debug_ports: + ports_map[port] = port + + return ports_map @staticmethod def _get_additional_options(runtime, debug_options): @@ -169,9 +174,8 @@ def _get_entry_point(runtime, debug_options=None): # pylint: disable=too-many-b Dockerfile. We override this default specifically when enabling debugging. The overridden entry point includes a few extra flags to start the runtime in debug mode. - :param string runtime: Lambda function runtime name - :param int debug_port: Optional, port for debugger - :param string debug_args: Optional additional arguments passed to the entry point. + :param string runtime: Lambda function runtime name. + :param DebugContext debug_options: Optional. Debug context for the function (includes port, args, and path). :return list: List containing the new entry points. Each element in the list is one portion of the command. ie. if command is ``node index.js arg1 arg2``, then this list will be ["node", "index.js", "arg1", "arg2"] """ @@ -179,7 +183,11 @@ def _get_entry_point(runtime, debug_options=None): # pylint: disable=too-many-b if not debug_options: return None - debug_port = debug_options.debug_port + debug_ports = debug_options.debug_ports + if not debug_ports: + return None + + debug_port = debug_ports[0] debug_args_list = [] if debug_options.debug_args: diff --git a/tests/functional/local/docker/test_lambda_container.py b/tests/functional/local/docker/test_lambda_container.py index 178487d142..b5e65f4e22 100644 --- a/tests/functional/local/docker/test_lambda_container.py +++ b/tests/functional/local/docker/test_lambda_container.py @@ -51,8 +51,8 @@ def setUp(self): self.expected_docker_image = self.IMAGE_NAME self.handler = "index.handler" self.layers = [] - self.debug_port = _rand_port() - self.debug_context = DebugContext(debug_port=self.debug_port, debugger_path=None, debug_args=None) + self.debug_port = [_rand_port()] + self.debug_context = DebugContext(debug_ports=self.debug_port, debugger_path=None, debug_args=None) self.code_dir = nodejs_lambda(self.HELLO_WORLD_CODE) self.network_prefix = "sam_cli_test_network" diff --git a/tests/unit/commands/local/cli_common/test_invoke_context.py b/tests/unit/commands/local/cli_common/test_invoke_context.py index ccc95d112b..2de5942c45 100644 --- a/tests/unit/commands/local/cli_common/test_invoke_context.py +++ b/tests/unit/commands/local/cli_common/test_invoke_context.py @@ -30,7 +30,7 @@ def test_must_read_from_necessary_files(self, SamFunctionProviderMock): docker_network="network", log_file=log_file, skip_pull_image=True, - debug_port=1111, + debug_ports=[1111], debugger_path="path-to-debugger", debug_args="args", parameter_overrides={}, @@ -73,7 +73,7 @@ def test_must_read_from_necessary_files(self, SamFunctionProviderMock): SamFunctionProviderMock.assert_called_with(template_dict, {"AWS::Region": "region"}) invoke_context._get_env_vars_value.assert_called_with(env_vars_file) invoke_context._setup_log_file.assert_called_with(log_file) - invoke_context._get_debug_context.assert_called_once_with(1111, "args", "path-to-debugger") + invoke_context._get_debug_context.assert_called_once_with([1111], "args", "path-to-debugger") invoke_context._get_container_manager.assert_called_once_with("network", True) @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider") @@ -171,7 +171,7 @@ def test_must_work_in_with_statement(self, ExitMock, EnterMock): docker_network="network", log_file="log_file", skip_pull_image=True, - debug_port=1111, + debug_ports=[1111], debugger_path="path-to-debugger", debug_args="args", aws_profile="profile", @@ -220,7 +220,7 @@ def setUp(self): log_file="log_file", skip_pull_image=True, force_image_build=True, - debug_port=1111, + debug_ports=[1111], debugger_path="path-to-debugger", debug_args="args", aws_profile="profile", @@ -287,7 +287,7 @@ def test_must_enable_auto_flush_if_debug( self, SamFunctionProviderMock, StreamWriterMock, osutils_stdout_mock, ExitMock ): - context = InvokeContext(template_file="template", debug_port=6000) + context = InvokeContext(template_file="template", debug_ports=[6000]) context._get_template_data = Mock() context._get_env_vars_value = Mock() @@ -391,7 +391,7 @@ def test_must_enable_auto_flush_if_debug( self, SamFunctionProviderMock, StreamWriterMock, osutils_stderr_mock, ExitMock ): - context = InvokeContext(template_file="template", debug_port=6000) + context = InvokeContext(template_file="template", debug_ports=[6000]) context._get_template_data = Mock() context._get_env_vars_value = Mock() @@ -571,7 +571,7 @@ def test_debugger_path_not_found(self, pathlib_mock): pathlib_mock.side_effect = error with self.assertRaises(DebugContextException): - InvokeContext._get_debug_context(debug_port=1111, debug_args=None, debugger_path="somepath") + InvokeContext._get_debug_context(debug_ports=[1111], debug_args=None, debugger_path="somepath") @patch("samcli.commands.local.cli_common.invoke_context.Path") def test_debugger_path_not_dir(self, pathlib_mock): @@ -582,13 +582,13 @@ def test_debugger_path_not_dir(self, pathlib_mock): pathlib_mock.return_value = pathlib_path_mock with self.assertRaises(DebugContextException): - InvokeContext._get_debug_context(debug_port=1111, debug_args=None, debugger_path="somepath") + InvokeContext._get_debug_context(debug_ports=1111, debug_args=None, debugger_path="somepath") def test_no_debug_port(self): debug_context = InvokeContext._get_debug_context(None, None, None) self.assertEqual(debug_context.debugger_path, None) - self.assertEqual(debug_context.debug_port, None) + self.assertEqual(debug_context.debug_ports, None) self.assertEqual(debug_context.debug_args, None) @patch("samcli.commands.local.cli_common.invoke_context.Path") @@ -596,17 +596,59 @@ def test_non_path_not_found_oserror_is_thrown(self, pathlib_mock): pathlib_mock.side_effect = OSError() with self.assertRaises(OSError): - InvokeContext._get_debug_context(debug_port=1111, debug_args=None, debugger_path="somepath") + InvokeContext._get_debug_context(debug_ports=1111, debug_args=None, debugger_path="somepath") @patch("samcli.commands.local.cli_common.invoke_context.DebugContext") def test_debug_port_given_without_debugger_path(self, debug_context_mock): debug_context_mock.return_value = "I am the DebugContext" - - debug_context = InvokeContext._get_debug_context(1111, None, None) + debug_context = InvokeContext._get_debug_context(debug_ports=1111, debug_args=None, debugger_path=None) self.assertEqual(debug_context, "I am the DebugContext") + debug_context_mock.assert_called_once_with(debug_ports=1111, debug_args=None, debugger_path=None) + + @patch("samcli.commands.local.cli_common.invoke_context.Path") + def test_debug_port_not_specified(self, pathlib_mock): + pathlib_path_mock = Mock() + pathlib_mock.return_value = pathlib_path_mock + + debug_context = InvokeContext._get_debug_context(debug_ports=None, debug_args=None, debugger_path="somepath") + self.assertEqual(None, debug_context.debug_ports) + + @patch("samcli.commands.local.cli_common.invoke_context.Path") + def test_debug_port_single_value_int(self, pathlib_mock): + pathlib_path_mock = Mock() + pathlib_mock.return_value = pathlib_path_mock + + debug_context = InvokeContext._get_debug_context(debug_ports=1111, debug_args=None, debugger_path="somepath") + self.assertEqual(1111, debug_context.debug_ports) - debug_context_mock.assert_called_once_with(debug_port=1111, debug_args=None, debugger_path=None) + @patch("samcli.commands.local.cli_common.invoke_context.Path") + def test_debug_port_single_value_string(self, pathlib_mock): + pathlib_path_mock = Mock() + pathlib_mock.return_value = pathlib_path_mock + + debug_context = InvokeContext._get_debug_context(debug_ports="1111", debug_args=None, debugger_path="somepath") + self.assertEqual("1111", debug_context.debug_ports) + + @patch("samcli.commands.local.cli_common.invoke_context.Path") + def test_debug_port_multiple_values_string(self, pathlib_mock): + pathlib_path_mock = Mock() + pathlib_mock.return_value = pathlib_path_mock + + debug_context = InvokeContext._get_debug_context( + debug_ports=["1111", "1112"], debug_args=None, debugger_path="somepath" + ) + self.assertEqual(["1111", "1112"], debug_context.debug_ports) + + @patch("samcli.commands.local.cli_common.invoke_context.Path") + def test_debug_port_multiple_values_int(self, pathlib_mock): + pathlib_path_mock = Mock() + pathlib_mock.return_value = pathlib_path_mock + + debug_context = InvokeContext._get_debug_context( + debug_ports=[1111, 1112], debug_args=None, debugger_path="somepath" + ) + self.assertEqual([1111, 1112], debug_context.debug_ports) @patch("samcli.commands.local.cli_common.invoke_context.DebugContext") @patch("samcli.commands.local.cli_common.invoke_context.Path") @@ -625,7 +667,7 @@ def test_debugger_path_resolves(self, pathlib_mock, debug_context_mock): self.assertEqual(debug_context, "I am the DebugContext") - debug_context_mock.assert_called_once_with(debug_port=1111, debug_args="args", debugger_path="full/path") + debug_context_mock.assert_called_once_with(debug_ports=1111, debug_args="args", debugger_path="full/path") resolve_path_mock.is_dir.assert_called_once() pathlib_path_mock.resolve.assert_called_once_with(strict=True) pathlib_mock.assert_called_once_with("./path") diff --git a/tests/unit/commands/local/invoke/test_cli.py b/tests/unit/commands/local/invoke/test_cli.py index d38e12b338..643f223294 100644 --- a/tests/unit/commands/local/invoke/test_cli.py +++ b/tests/unit/commands/local/invoke/test_cli.py @@ -25,7 +25,7 @@ def setUp(self): self.template = "template" self.eventfile = "eventfile" self.env_vars = "env-vars" - self.debug_port = 123 + self.debug_ports = [123] self.debug_args = "args" self.debugger_path = "/test/path" self.docker_volume_basedir = "basedir" @@ -60,7 +60,7 @@ def test_cli_must_setup_context_and_invoke(self, get_event_mock, InvokeContextMo event=self.eventfile, no_event=self.no_event, env_vars=self.env_vars, - debug_port=self.debug_port, + debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, docker_volume_basedir=self.docker_volume_basedir, @@ -80,7 +80,7 @@ def test_cli_must_setup_context_and_invoke(self, get_event_mock, InvokeContextMo docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, - debug_port=self.debug_port, + debug_ports=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, parameter_overrides=self.parameter_overrides, @@ -114,7 +114,7 @@ def test_cli_must_invoke_with_no_event(self, get_event_mock, InvokeContextMock): event=STDIN_FILE_NAME, no_event=self.no_event, env_vars=self.env_vars, - debug_port=self.debug_port, + debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, docker_volume_basedir=self.docker_volume_basedir, @@ -134,7 +134,7 @@ def test_cli_must_invoke_with_no_event(self, get_event_mock, InvokeContextMock): docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, - debug_port=self.debug_port, + debug_ports=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, parameter_overrides=self.parameter_overrides, @@ -167,7 +167,7 @@ def test_must_raise_user_exception_on_no_event_and_event(self, get_event_mock, I event=self.eventfile, no_event=self.no_event, env_vars=self.env_vars, - debug_port=self.debug_port, + debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, docker_volume_basedir=self.docker_volume_basedir, @@ -215,7 +215,7 @@ def test_must_raise_user_exception_on_function_not_found( event=self.eventfile, no_event=self.no_event, env_vars=self.env_vars, - debug_port=self.debug_port, + debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, docker_volume_basedir=self.docker_volume_basedir, @@ -263,7 +263,7 @@ def test_must_raise_user_exception_on_invalid_sam_template( event=self.eventfile, no_event=self.no_event, env_vars=self.env_vars, - debug_port=self.debug_port, + debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, docker_volume_basedir=self.docker_volume_basedir, @@ -299,7 +299,7 @@ def test_must_raise_user_exception_on_invalid_env_vars(self, get_event_mock, Inv event=self.eventfile, no_event=self.no_event, env_vars=self.env_vars, - debug_port=self.debug_port, + debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, docker_volume_basedir=self.docker_volume_basedir, diff --git a/tests/unit/commands/local/lib/test_debug_context.py b/tests/unit/commands/local/lib/test_debug_context.py index 9c19346e5a..9eb0a4d5de 100644 --- a/tests/unit/commands/local/lib/test_debug_context.py +++ b/tests/unit/commands/local/lib/test_debug_context.py @@ -9,20 +9,21 @@ class TestDebugContext(TestCase): def test_init(self): context = DebugContext("port", "debuggerpath", "debug_args") - self.assertEqual(context.debug_port, "port") + self.assertEqual(context.debug_ports, "port") self.assertEqual(context.debugger_path, "debuggerpath") self.assertEqual(context.debug_args, "debug_args") @parameterized.expand( [ ("1000", "debuggerpath", "debug_args"), - ("1000", None, None), - ("1000", None, "debug_args"), - ("1000", "debuggerpath", None), + (["1000"], "debuggerpath", "debug_args"), + (["1000", "1001"], "debuggerpath", "debug_args"), (1000, "debuggerpath", "debug_args"), - (1000, None, None), - (1000, None, "debug_args"), - (1000, "debuggerpath", None), + ([1000], "debuggerpath", "debug_args"), + ([1000, 1001], "debuggerpath", "debug_args"), + ([1000], None, None), + ([1000], None, "debug_args"), + ([1000], "debuggerpath", None), ] ) def test_bool_truthy(self, port, debug_path, debug_ars): @@ -46,13 +47,14 @@ def test_bool_falsy(self, port, debug_path, debug_ars): @parameterized.expand( [ ("1000", "debuggerpath", "debug_args"), - ("1000", None, None), - ("1000", None, "debug_args"), - ("1000", "debuggerpath", None), + (["1000"], "debuggerpath", "debug_args"), + (["1000", "1001"], "debuggerpath", "debug_args"), (1000, "debuggerpath", "debug_args"), - (1000, None, None), - (1000, None, "debug_args"), - (1000, "debuggerpath", None), + ([1000], "debuggerpath", "debug_args"), + ([1000, 1001], "debuggerpath", "debug_args"), + ([1000], None, None), + ([1000], None, "debug_args"), + ([1000], "debuggerpath", None), ] ) def test_nonzero_thruthy(self, port, debug_path, debug_ars): diff --git a/tests/unit/commands/local/start_api/test_cli.py b/tests/unit/commands/local/start_api/test_cli.py index e904944437..830cdbee1e 100644 --- a/tests/unit/commands/local/start_api/test_cli.py +++ b/tests/unit/commands/local/start_api/test_cli.py @@ -19,7 +19,7 @@ class TestCli(TestCase): def setUp(self): self.template = "template" self.env_vars = "env-vars" - self.debug_port = 123 + self.debug_ports = [123] self.debug_args = "args" self.debugger_path = "/test/path" self.docker_volume_basedir = "basedir" @@ -60,7 +60,7 @@ def test_cli_must_setup_context_and_start_service(self, local_api_service_mock, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, - debug_port=self.debug_port, + debug_ports=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, parameter_overrides=self.parameter_overrides, @@ -138,7 +138,7 @@ def call_cli(self): static_dir=self.static_dir, template=self.template, env_vars=self.env_vars, - debug_port=self.debug_port, + debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, docker_volume_basedir=self.docker_volume_basedir, diff --git a/tests/unit/commands/local/start_lambda/test_cli.py b/tests/unit/commands/local/start_lambda/test_cli.py index 5dff100199..d34b5d1c09 100644 --- a/tests/unit/commands/local/start_lambda/test_cli.py +++ b/tests/unit/commands/local/start_lambda/test_cli.py @@ -15,7 +15,7 @@ class TestCli(TestCase): def setUp(self): self.template = "template" self.env_vars = "env-vars" - self.debug_port = 123 + self.debug_ports = [123] self.debug_args = "args" self.debugger_path = "/test/path" self.docker_volume_basedir = "basedir" @@ -55,7 +55,7 @@ def test_cli_must_setup_context_and_start_service(self, local_lambda_service_moc docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, - debug_port=self.debug_port, + debug_ports=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, parameter_overrides=self.parameter_overrides, @@ -110,7 +110,7 @@ def call_cli(self): port=self.port, template=self.template, env_vars=self.env_vars, - debug_port=self.debug_port, + debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, docker_volume_basedir=self.docker_volume_basedir, diff --git a/tests/unit/local/docker/test_lambda_container.py b/tests/unit/local/docker/test_lambda_container.py index 30db16c83a..c20eb1de43 100644 --- a/tests/unit/local/docker/test_lambda_container.py +++ b/tests/unit/local/docker/test_lambda_container.py @@ -36,7 +36,7 @@ def setUp(self): self.code_dir = "codedir" self.env_var = {"var": "value"} self.memory_mb = 1024 - self.debug_options = DebugContext(debug_args="a=b c=d e=f", debug_port=1235) + self.debug_options = DebugContext(debug_args="a=b c=d e=f", debug_ports=[1235]) @patch.object(LambdaContainer, "_get_image") @patch.object(LambdaContainer, "_get_exposed_ports") @@ -108,12 +108,34 @@ def test_must_fail_for_unsupported_runtime(self): class TestLambdaContainer_get_exposed_ports(TestCase): def test_must_map_same_port_on_host_and_container(self): - debug_options = DebugContext(debug_port=12345) - expected = {debug_options.debug_port: debug_options.debug_port} + debug_options = DebugContext(debug_ports=[12345]) + expected = {port: port for port in debug_options.debug_ports} result = LambdaContainer._get_exposed_ports(debug_options) self.assertEqual(expected, result) + def test_must_map_multiple_ports_on_host_and_container(self): + + debug_options = DebugContext(debug_ports=[12345, 67890]) + expected = {port: port for port in debug_options.debug_ports} + result = LambdaContainer._get_exposed_ports(debug_options) + + self.assertEqual(expected, result) + + def test_empty_ports_list(self): + + debug_options = DebugContext(debug_ports=[]) + result = LambdaContainer._get_exposed_ports(debug_options) + + self.assertEqual(None, result) + + def test_none_ports_specified(self): + + debug_options = DebugContext(debug_ports=None) + result = LambdaContainer._get_exposed_ports(debug_options) + + self.assertEqual(None, result) + def test_must_skip_if_port_is_not_given(self): self.assertIsNone(LambdaContainer._get_exposed_ports(None), "No ports should be exposed") @@ -133,9 +155,9 @@ def test_must_return_lambci_image(self): class TestLambdaContainer_get_entry_point(TestCase): def setUp(self): - self.debug_port = 1235 + self.debug_ports = [1235] self.debug_args = "a=b c=d e=f" - self.debug_options = DebugContext(debug_port=1235, debug_args="a=b c=d e=f") + self.debug_options = DebugContext(debug_ports=[1235], debug_args="a=b c=d e=f") def test_must_skip_if_debug_port_is_not_specified(self): self.assertIsNone( @@ -176,7 +198,7 @@ def test_debug_arg_must_be_split_by_spaces_and_appended_to_bootstrap_based_entry @parameterized.expand([param(r) for r in RUNTIMES_WITH_ENTRYPOINT]) def test_must_provide_entrypoint_even_without_debug_args(self, runtime): - debug_options = DebugContext(debug_port=1235, debug_args=None) + debug_options = DebugContext(debug_ports=[1235], debug_args=None) result = LambdaContainer._get_entry_point(runtime, debug_options) self.assertIsNotNone(result) @@ -184,14 +206,14 @@ def test_must_provide_entrypoint_even_without_debug_args(self, runtime): class TestLambdaContainer_get_additional_options(TestCase): def test_no_additional_options_when_debug_options_is_none(self): - debug_options = DebugContext(debug_port=None) + debug_options = DebugContext(debug_ports=None) result = LambdaContainer._get_additional_options("runtime", debug_options) self.assertIsNone(result) @parameterized.expand([param(r) for r in RUNTIMES_WITH_ENTRYPOINT if not r.startswith("go")]) def test_default_value_returned_for_non_go_runtimes(self, runtime): - debug_options = DebugContext(debug_port=1235) + debug_options = DebugContext(debug_ports=[1235]) result = LambdaContainer._get_additional_options(runtime, debug_options) self.assertEqual(result, {}) @@ -200,7 +222,7 @@ def test_default_value_returned_for_non_go_runtimes(self, runtime): def test_go_runtime_returns_additional_options(self, runtime): expected = {"security_opt": ["seccomp:unconfined"], "cap_add": ["SYS_PTRACE"]} - debug_options = DebugContext(debug_port=1235) + debug_options = DebugContext(debug_ports=[1235]) result = LambdaContainer._get_additional_options(runtime, debug_options) self.assertEqual(result, expected) @@ -208,13 +230,13 @@ def test_go_runtime_returns_additional_options(self, runtime): class TestLambdaContainer_get_additional_volumes(TestCase): def test_no_additional_volumes_when_debug_options_is_none(self): - debug_options = DebugContext(debug_port=None) + debug_options = DebugContext(debug_ports=None) result = LambdaContainer._get_additional_volumes(debug_options) self.assertIsNone(result) def test_no_additional_volumes_when_debuggr_path_is_none(self): - debug_options = DebugContext(debug_port=1234) + debug_options = DebugContext(debug_ports=[1234]) result = LambdaContainer._get_additional_volumes(debug_options) self.assertIsNone(result) @@ -222,7 +244,7 @@ def test_no_additional_volumes_when_debuggr_path_is_none(self): def test_additional_volumes_returns_volume_with_debugger_path_is_set(self): expected = {"/somepath": {"bind": "/tmp/lambci_debug_files", "mode": "ro"}} - debug_options = DebugContext(debug_port=1234, debugger_path="/somepath") + debug_options = DebugContext(debug_ports=[1234], debugger_path="/somepath") result = LambdaContainer._get_additional_volumes(debug_options) self.assertEqual(result, expected)