From b314ccd54fa4d7203ddf4348f1086762148283f8 Mon Sep 17 00:00:00 2001 From: khkh_microsoft Date: Wed, 13 May 2026 17:04:51 -0500 Subject: [PATCH 1/4] Add --image and --entrypoint params to containerapp debug command Allow customers to specify a custom container image and entrypoint for the debug ephemeral container via 'az containerapp debug'. - Add --image and --entrypoint optional parameters - Append customDebugImageName and customDebugImageEntrypointCommand query params to the debug URL when provided - Client-side validation: --entrypoint requires --image - Add help examples for the new parameters --- src/containerapp/azext_containerapp/_help.py | 6 ++++++ src/containerapp/azext_containerapp/_params.py | 4 ++++ .../containerapp_debug_command_decorator.py | 18 ++++++++++++++++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index 81da699ede9..5efb2dfdf98 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -2447,6 +2447,12 @@ - name: Debug by executing a command inside a container app and exit text: | az containerapp debug -n MyContainerapp -g MyResourceGroup --revision MyRevision --replica MyReplica --container MyContainer --command "echo Hello World" + - name: Debug with a custom container image + text: | + az containerapp debug -n MyContainerapp -g MyResourceGroup --container MyContainer --image mcr.microsoft.com/dotnet/sdk:8.0 + - name: Debug with a custom container image and entrypoint + text: | + az containerapp debug -n MyContainerapp -g MyResourceGroup --container MyContainer --image mcr.microsoft.com/dotnet/sdk:8.0 --entrypoint /bin/bash """ helps['containerapp label-history'] = """ diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index b5f2b6d53ed..9a19e387fde 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -515,6 +515,10 @@ def load_arguments(self, _): help="The name of the container app revision. Default to the latest revision.") c.argument('name', name_type, id_part=None, help="The name of the Containerapp.") c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) + c.argument('custom_debug_image_name', options_list=['--image'], is_preview=True, + help="Custom container image for the debug ephemeral container (e.g., 'mcr.microsoft.com/dotnet/sdk:8.0'). If not specified, the platform default debug image is used.") + c.argument('custom_debug_image_entrypoint_command', options_list=['--entrypoint'], is_preview=True, + help="Custom entrypoint command for the debug container (e.g., '/bin/bash'). Requires --image to also be specified.") with self.argument_context('containerapp label-history') as c: c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None) diff --git a/src/containerapp/azext_containerapp/containerapp_debug_command_decorator.py b/src/containerapp/azext_containerapp/containerapp_debug_command_decorator.py index 19478bc1c62..abc4e42d149 100644 --- a/src/containerapp/azext_containerapp/containerapp_debug_command_decorator.py +++ b/src/containerapp/azext_containerapp/containerapp_debug_command_decorator.py @@ -39,6 +39,12 @@ def get_argument_container_name(self): def get_argument_command(self): return self.get_param("command") + def get_argument_custom_debug_image_name(self): + return self.get_param("custom_debug_image_name") + + def get_argument_custom_debug_image_entrypoint_command(self): + return self.get_param("custom_debug_image_entrypoint_command") + def validate_arguments(self): validate_basic_arguments( resource_group_name=self.get_argument_resource_group_name(), @@ -59,7 +65,7 @@ def _get_logstream_endpoint(self, cmd, resource_group_name, container_app_name, raise ValidationError(f"Error retrieving container in revision '{revision_name}' in the container app '{container_app_name}'.") return container_info[0]["logStreamEndpoint"] - def _get_url(self, cmd, resource_group_name, container_app_name, revision_name, replica_name, container_name, command): + def _get_url(self, cmd, resource_group_name, container_app_name, revision_name, replica_name, container_name, command, custom_debug_image_name=None, custom_debug_image_entrypoint_command=None): """Get the debug url for the specified container in the replica""" base_url = self._get_logstream_endpoint(cmd, resource_group_name, container_app_name, revision_name, replica_name, container_name) proxy_api_url = base_url[:base_url.index("/subscriptions/")] @@ -68,6 +74,10 @@ def _get_url(self, cmd, resource_group_name, container_app_name, revision_name, debug_url = (f"{proxy_api_url}/subscriptions/{sub}/resourceGroups/{resource_group_name}/containerApps/{container_app_name}" f"/revisions/{revision_name}/replicas/{replica_name}/debug" f"?targetContainer={container_name}&command={encoded_cmd}") + if custom_debug_image_name: + debug_url += f"&customDebugImageName={urllib.parse.quote_plus(custom_debug_image_name)}" + if custom_debug_image_entrypoint_command: + debug_url += f"&customDebugImageEntrypointCommand={urllib.parse.quote_plus(custom_debug_image_entrypoint_command)}" return debug_url def _get_auth_token(self, cmd, resource_group_name, container_app_name): @@ -82,7 +92,11 @@ def execute_Command(self, cmd): replica_name = self.get_argument_replica_name() container_name = self.get_argument_container_name() command = self.get_argument_command() - url = self._get_url(cmd, resource_group_name, container_app_name, revision_name, replica_name, container_name, command) + custom_debug_image_name = self.get_argument_custom_debug_image_name() + custom_debug_image_entrypoint_command = self.get_argument_custom_debug_image_entrypoint_command() + if custom_debug_image_entrypoint_command and not custom_debug_image_name: + raise ValidationError("--entrypoint requires --image to also be specified.") + url = self._get_url(cmd, resource_group_name, container_app_name, revision_name, replica_name, container_name, command, custom_debug_image_name, custom_debug_image_entrypoint_command) token = self._get_auth_token(cmd, resource_group_name, container_app_name) headers = [f"Authorization=Bearer {token}"] r = send_raw_request(cmd.cli_ctx, "GET", url, headers=headers) From 38bf5b1ba277b5dfea88ec69909fae167cbd9d05 Mon Sep 17 00:00:00 2001 From: khkh_microsoft Date: Wed, 13 May 2026 18:05:41 -0500 Subject: [PATCH 2/4] Add unit tests for custom debug image parameters Tests cover: - URL building without custom image params - URL building with --image only - URL building with --image and --entrypoint - URL encoding of special characters - Client-side validation: --entrypoint without --image raises error - --image without --entrypoint succeeds - No custom params succeeds - Getter methods return correct values - Getter methods return None when not set --- .../latest/test_containerapp_debug_unit.py | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 src/containerapp/azext_containerapp/tests/latest/test_containerapp_debug_unit.py diff --git a/src/containerapp/azext_containerapp/tests/latest/test_containerapp_debug_unit.py b/src/containerapp/azext_containerapp/tests/latest/test_containerapp_debug_unit.py new file mode 100644 index 00000000000..be35ff9d0d3 --- /dev/null +++ b/src/containerapp/azext_containerapp/tests/latest/test_containerapp_debug_unit.py @@ -0,0 +1,185 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import unittest +from unittest import mock + +from azure.cli.core.azclierror import ValidationError +from azext_containerapp.containerapp_debug_command_decorator import ContainerAppDebugCommandDecorator + + +class TestDebugCommandUrlBuilding(unittest.TestCase): + """Unit tests for the debug command URL building with custom image parameters.""" + + def _create_decorator_with_params(self, params): + """Helper to create a decorator instance with mocked params.""" + with mock.patch.object(ContainerAppDebugCommandDecorator, '__init__', lambda self, *a, **kw: None): + decorator = ContainerAppDebugCommandDecorator() + decorator.raw_parameters = params + # Mock get_param to return from our dict + decorator.get_param = lambda key: params.get(key) + return decorator + + def _mock_get_url(self, decorator, cmd_mock, **kwargs): + """Helper to call _get_url with mocked logstream endpoint and subscription.""" + base_endpoint = "https://proxy.example.com/subscriptions/test-sub/resourceGroups/test-rg/containerApps/test-app/revisions/test-rev/replicas/test-replica/logstream" + with mock.patch.object(decorator, '_get_logstream_endpoint', return_value=base_endpoint): + with mock.patch('azext_containerapp.containerapp_debug_command_decorator.get_subscription_id', return_value='test-sub'): + return decorator._get_url( + cmd_mock, + kwargs.get('resource_group_name', 'test-rg'), + kwargs.get('container_app_name', 'test-app'), + kwargs.get('revision_name', 'test-rev'), + kwargs.get('replica_name', 'test-replica'), + kwargs.get('container_name', 'test-container'), + kwargs.get('command', '/bin/bash'), + kwargs.get('custom_debug_image_name'), + kwargs.get('custom_debug_image_entrypoint_command'), + ) + + def test_url_without_custom_image(self): + """URL should not contain custom image params when not specified.""" + decorator = self._create_decorator_with_params({}) + cmd_mock = mock.MagicMock() + url = self._mock_get_url(decorator, cmd_mock) + + self.assertIn("targetContainer=test-container", url) + self.assertNotIn("customDebugImageName", url) + self.assertNotIn("customDebugImageEntrypointCommand", url) + + def test_url_with_custom_image_only(self): + """URL should contain customDebugImageName when --image is specified.""" + decorator = self._create_decorator_with_params({}) + cmd_mock = mock.MagicMock() + url = self._mock_get_url(decorator, cmd_mock, custom_debug_image_name="ubuntu:22.04") + + self.assertIn("customDebugImageName=ubuntu%3A22.04", url) + self.assertNotIn("customDebugImageEntrypointCommand", url) + + def test_url_with_custom_image_and_entrypoint(self): + """URL should contain both params when --image and --entrypoint are specified.""" + decorator = self._create_decorator_with_params({}) + cmd_mock = mock.MagicMock() + url = self._mock_get_url( + decorator, cmd_mock, + custom_debug_image_name="mcr.microsoft.com/dotnet/sdk:8.0", + custom_debug_image_entrypoint_command="/bin/bash", + ) + + self.assertIn("customDebugImageName=mcr.microsoft.com%2Fdotnet%2Fsdk%3A8.0", url) + self.assertIn("customDebugImageEntrypointCommand=%2Fbin%2Fbash", url) + + def test_url_encodes_special_characters(self): + """Custom image params should be URL-encoded.""" + decorator = self._create_decorator_with_params({}) + cmd_mock = mock.MagicMock() + url = self._mock_get_url( + decorator, cmd_mock, + custom_debug_image_name="myregistry.azurecr.io/my-image:v1.0", + custom_debug_image_entrypoint_command="/bin/sh -c 'echo hello'", + ) + + self.assertIn("customDebugImageName=myregistry.azurecr.io%2Fmy-image%3Av1.0", url) + self.assertIn("customDebugImageEntrypointCommand=%2Fbin%2Fsh+-c+%27echo+hello%27", url) + + +class TestDebugCommandValidation(unittest.TestCase): + """Unit tests for client-side validation of custom image parameters.""" + + def _create_decorator_with_params(self, params): + """Helper to create a decorator instance with mocked params.""" + with mock.patch.object(ContainerAppDebugCommandDecorator, '__init__', lambda self, *a, **kw: None): + decorator = ContainerAppDebugCommandDecorator() + decorator.get_param = lambda key: params.get(key) + return decorator + + def test_entrypoint_without_image_raises_validation_error(self): + """--entrypoint without --image should raise ValidationError.""" + decorator = self._create_decorator_with_params({ + 'custom_debug_image_name': None, + 'custom_debug_image_entrypoint_command': '/bin/bash', + 'resource_group_name': 'rg', + 'container_app_name': 'app', + 'revision_name': 'rev', + 'replica_name': 'replica', + 'container_name': 'container', + 'command': '/bin/bash', + }) + + cmd_mock = mock.MagicMock() + with mock.patch.object(decorator, '_get_url'), \ + mock.patch.object(decorator, '_get_auth_token', return_value='token'), \ + mock.patch('azext_containerapp.containerapp_debug_command_decorator.send_raw_request'): + with self.assertRaises(ValidationError) as ctx: + decorator.execute_Command(cmd_mock) + self.assertIn("--entrypoint requires --image", str(ctx.exception)) + + def test_image_without_entrypoint_succeeds(self): + """--image without --entrypoint should not raise.""" + decorator = self._create_decorator_with_params({ + 'custom_debug_image_name': 'ubuntu:22.04', + 'custom_debug_image_entrypoint_command': None, + 'resource_group_name': 'rg', + 'container_app_name': 'app', + 'revision_name': 'rev', + 'replica_name': 'replica', + 'container_name': 'container', + 'command': '/bin/bash', + }) + + cmd_mock = mock.MagicMock() + mock_response = mock.MagicMock() + mock_response.json.return_value = {"status": "ok"} + with mock.patch.object(decorator, '_get_url', return_value='https://example.com/debug'), \ + mock.patch.object(decorator, '_get_auth_token', return_value='token'), \ + mock.patch('azext_containerapp.containerapp_debug_command_decorator.send_raw_request', return_value=mock_response), \ + mock.patch('azext_containerapp.containerapp_debug_command_decorator.transform_debug_command_output', return_value={"status": "ok"}): + # Should not raise + decorator.execute_Command(cmd_mock) + + def test_no_custom_params_succeeds(self): + """No custom image params should not raise.""" + decorator = self._create_decorator_with_params({ + 'custom_debug_image_name': None, + 'custom_debug_image_entrypoint_command': None, + 'resource_group_name': 'rg', + 'container_app_name': 'app', + 'revision_name': 'rev', + 'replica_name': 'replica', + 'container_name': 'container', + 'command': '/bin/bash', + }) + + cmd_mock = mock.MagicMock() + mock_response = mock.MagicMock() + mock_response.json.return_value = {"status": "ok"} + with mock.patch.object(decorator, '_get_url', return_value='https://example.com/debug'), \ + mock.patch.object(decorator, '_get_auth_token', return_value='token'), \ + mock.patch('azext_containerapp.containerapp_debug_command_decorator.send_raw_request', return_value=mock_response), \ + mock.patch('azext_containerapp.containerapp_debug_command_decorator.transform_debug_command_output', return_value={"status": "ok"}): + # Should not raise + decorator.execute_Command(cmd_mock) + + def test_getter_methods(self): + """Getter methods should return correct param values.""" + decorator = self._create_decorator_with_params({ + 'custom_debug_image_name': 'ubuntu:22.04', + 'custom_debug_image_entrypoint_command': '/bin/bash', + }) + + self.assertEqual(decorator.get_argument_custom_debug_image_name(), 'ubuntu:22.04') + self.assertEqual(decorator.get_argument_custom_debug_image_entrypoint_command(), '/bin/bash') + + def test_getter_methods_return_none_when_not_set(self): + """Getter methods should return None when params not provided.""" + decorator = self._create_decorator_with_params({}) + + self.assertIsNone(decorator.get_argument_custom_debug_image_name()) + self.assertIsNone(decorator.get_argument_custom_debug_image_entrypoint_command()) + + +if __name__ == '__main__': + unittest.main() From acf432bcaa266f6b5eb9deb7baca330a50c62cdf Mon Sep 17 00:00:00 2001 From: khkh_microsoft Date: Fri, 15 May 2026 11:55:55 -0500 Subject: [PATCH 3/4] Validate --image/--entrypoint require --command and thread params through containerapp_debug - _validators.validate_debug: reject --image/--entrypoint when --command is missing (interactive mode with custom images is not yet supported server-side) - custom.containerapp_debug: accept and forward custom_debug_image_name and custom_debug_image_entrypoint_command into raw_parameters - tests: add coverage for the new validator branches --- .../azext_containerapp/_validators.py | 4 ++ src/containerapp/azext_containerapp/custom.py | 7 ++- .../latest/test_containerapp_debug_unit.py | 44 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/containerapp/azext_containerapp/_validators.py b/src/containerapp/azext_containerapp/_validators.py index 22b88f40266..81c0f520496 100644 --- a/src/containerapp/azext_containerapp/_validators.py +++ b/src/containerapp/azext_containerapp/_validators.py @@ -224,6 +224,10 @@ def validate_session_timeout_in_seconds(cmd, namespace): def validate_debug(cmd, namespace): logger.warning("Validating...") + has_custom_image = getattr(namespace, 'custom_debug_image_name', None) + has_entrypoint = getattr(namespace, 'custom_debug_image_entrypoint_command', None) + if (has_custom_image or has_entrypoint) and not namespace.debug_command: + raise ValidationError("--image and --entrypoint are only supported with --command. Interactive mode with custom images is not yet supported.") revision_already_set = bool(namespace.revision) replica_already_set = bool(namespace.replica) container_already_set = bool(namespace.container) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 6054e0b8f45..a871d8b5d0f 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -3624,7 +3624,8 @@ def list_maintenance_config(cmd, resource_group_name, env_name): return r -def containerapp_debug(cmd, resource_group_name, name, container=None, revision=None, replica=None, debug_command=None): +def containerapp_debug(cmd, resource_group_name, name, container=None, revision=None, replica=None, debug_command=None, + custom_debug_image_name=None, custom_debug_image_entrypoint_command=None): logger.warning("Connecting...") if debug_command is not None: raw_parameters = { @@ -3633,7 +3634,9 @@ def containerapp_debug(cmd, resource_group_name, name, container=None, revision= 'revision_name': revision, 'replica_name': replica, 'container_name': container, - 'command': debug_command + 'command': debug_command, + 'custom_debug_image_name': custom_debug_image_name, + 'custom_debug_image_entrypoint_command': custom_debug_image_entrypoint_command, } debug_command_decorator = ContainerAppDebugCommandDecorator( cmd=cmd, diff --git a/src/containerapp/azext_containerapp/tests/latest/test_containerapp_debug_unit.py b/src/containerapp/azext_containerapp/tests/latest/test_containerapp_debug_unit.py index be35ff9d0d3..37e34e7c9bc 100644 --- a/src/containerapp/azext_containerapp/tests/latest/test_containerapp_debug_unit.py +++ b/src/containerapp/azext_containerapp/tests/latest/test_containerapp_debug_unit.py @@ -181,5 +181,49 @@ def test_getter_methods_return_none_when_not_set(self): self.assertIsNone(decorator.get_argument_custom_debug_image_entrypoint_command()) +class TestValidateDebugCustomImageRequiresCommand(unittest.TestCase): + """Validate that --image/--entrypoint require --command.""" + + def _make_namespace(self, **kwargs): + ns = mock.MagicMock() + ns.debug_command = kwargs.get('debug_command', None) + ns.custom_debug_image_name = kwargs.get('custom_debug_image_name', None) + ns.custom_debug_image_entrypoint_command = kwargs.get('custom_debug_image_entrypoint_command', None) + ns.revision = kwargs.get('revision', 'rev') + ns.replica = kwargs.get('replica', 'replica') + ns.container = kwargs.get('container', 'container') + ns.name = 'test-app' + ns.resource_group_name = 'test-rg' + return ns + + @mock.patch('azext_containerapp._validators._set_debug_defaults') + def test_image_without_command_raises(self, mock_defaults): + from azext_containerapp._validators import validate_debug + ns = self._make_namespace(custom_debug_image_name='ubuntu:22.04') + with self.assertRaises(ValidationError) as ctx: + validate_debug(mock.MagicMock(), ns) + self.assertIn("--image", str(ctx.exception)) + + @mock.patch('azext_containerapp._validators._set_debug_defaults') + def test_entrypoint_without_command_raises(self, mock_defaults): + from azext_containerapp._validators import validate_debug + ns = self._make_namespace(custom_debug_image_entrypoint_command='/bin/bash') + with self.assertRaises(ValidationError) as ctx: + validate_debug(mock.MagicMock(), ns) + self.assertIn("--image", str(ctx.exception)) + + @mock.patch('azext_containerapp._validators._set_debug_defaults') + @mock.patch('azext_containerapp._validators._validate_revision_exists') + @mock.patch('azext_containerapp._validators._validate_replica_exists') + @mock.patch('azext_containerapp._validators._validate_container_exists') + def test_image_with_command_passes(self, mock_cont, mock_rep, mock_rev, mock_defaults): + from azext_containerapp._validators import validate_debug + ns = self._make_namespace( + debug_command='/bin/bash', + custom_debug_image_name='ubuntu:22.04', + ) + validate_debug(mock.MagicMock(), ns) # should not raise + + if __name__ == '__main__': unittest.main() From b978fd88edca5b2a367d6579330383ec2efca2c9 Mon Sep 17 00:00:00 2001 From: khkh_microsoft Date: Fri, 15 May 2026 12:24:06 -0500 Subject: [PATCH 4/4] Address PR review: move entrypoint validation, bump version, update HISTORY - Move --entrypoint requires --image check from execute_Command into validate_debug so it applies to both interactive and non-interactive paths (Copilot review) - Bump containerapp extension VERSION to 1.3.0b5 - Add HISTORY.rst entry for 1.3.0b5 - Update unit tests: drop stale execute_Command validation test, add validate_debug coverage for entrypoint-without-image --- src/containerapp/HISTORY.rst | 4 +++ .../azext_containerapp/_validators.py | 2 ++ .../containerapp_debug_command_decorator.py | 2 -- .../latest/test_containerapp_debug_unit.py | 33 +++++++------------ src/containerapp/setup.py | 2 +- 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/containerapp/HISTORY.rst b/src/containerapp/HISTORY.rst index d8a16d4737d..d8292f5140c 100644 --- a/src/containerapp/HISTORY.rst +++ b/src/containerapp/HISTORY.rst @@ -5,6 +5,10 @@ Release History upcoming ++++++ +1.3.0b5 +++++++ +* 'az containerapp debug': Add `--image` and `--entrypoint` parameters to customize the ephemeral debug container image and entrypoint command (used together with `--command`). + 1.3.0b4 ++++++ * 'az containerapp env --environment-mode': Add environment mode to create and update commands diff --git a/src/containerapp/azext_containerapp/_validators.py b/src/containerapp/azext_containerapp/_validators.py index 81c0f520496..e410e16deed 100644 --- a/src/containerapp/azext_containerapp/_validators.py +++ b/src/containerapp/azext_containerapp/_validators.py @@ -226,6 +226,8 @@ def validate_debug(cmd, namespace): logger.warning("Validating...") has_custom_image = getattr(namespace, 'custom_debug_image_name', None) has_entrypoint = getattr(namespace, 'custom_debug_image_entrypoint_command', None) + if has_entrypoint and not has_custom_image: + raise ValidationError("--entrypoint requires --image to also be specified.") if (has_custom_image or has_entrypoint) and not namespace.debug_command: raise ValidationError("--image and --entrypoint are only supported with --command. Interactive mode with custom images is not yet supported.") revision_already_set = bool(namespace.revision) diff --git a/src/containerapp/azext_containerapp/containerapp_debug_command_decorator.py b/src/containerapp/azext_containerapp/containerapp_debug_command_decorator.py index abc4e42d149..e44d238d89f 100644 --- a/src/containerapp/azext_containerapp/containerapp_debug_command_decorator.py +++ b/src/containerapp/azext_containerapp/containerapp_debug_command_decorator.py @@ -94,8 +94,6 @@ def execute_Command(self, cmd): command = self.get_argument_command() custom_debug_image_name = self.get_argument_custom_debug_image_name() custom_debug_image_entrypoint_command = self.get_argument_custom_debug_image_entrypoint_command() - if custom_debug_image_entrypoint_command and not custom_debug_image_name: - raise ValidationError("--entrypoint requires --image to also be specified.") url = self._get_url(cmd, resource_group_name, container_app_name, revision_name, replica_name, container_name, command, custom_debug_image_name, custom_debug_image_entrypoint_command) token = self._get_auth_token(cmd, resource_group_name, container_app_name) headers = [f"Authorization=Bearer {token}"] diff --git a/src/containerapp/azext_containerapp/tests/latest/test_containerapp_debug_unit.py b/src/containerapp/azext_containerapp/tests/latest/test_containerapp_debug_unit.py index 37e34e7c9bc..6b81dae6c80 100644 --- a/src/containerapp/azext_containerapp/tests/latest/test_containerapp_debug_unit.py +++ b/src/containerapp/azext_containerapp/tests/latest/test_containerapp_debug_unit.py @@ -96,27 +96,6 @@ def _create_decorator_with_params(self, params): decorator.get_param = lambda key: params.get(key) return decorator - def test_entrypoint_without_image_raises_validation_error(self): - """--entrypoint without --image should raise ValidationError.""" - decorator = self._create_decorator_with_params({ - 'custom_debug_image_name': None, - 'custom_debug_image_entrypoint_command': '/bin/bash', - 'resource_group_name': 'rg', - 'container_app_name': 'app', - 'revision_name': 'rev', - 'replica_name': 'replica', - 'container_name': 'container', - 'command': '/bin/bash', - }) - - cmd_mock = mock.MagicMock() - with mock.patch.object(decorator, '_get_url'), \ - mock.patch.object(decorator, '_get_auth_token', return_value='token'), \ - mock.patch('azext_containerapp.containerapp_debug_command_decorator.send_raw_request'): - with self.assertRaises(ValidationError) as ctx: - decorator.execute_Command(cmd_mock) - self.assertIn("--entrypoint requires --image", str(ctx.exception)) - def test_image_without_entrypoint_succeeds(self): """--image without --entrypoint should not raise.""" decorator = self._create_decorator_with_params({ @@ -224,6 +203,18 @@ def test_image_with_command_passes(self, mock_cont, mock_rep, mock_rev, mock_def ) validate_debug(mock.MagicMock(), ns) # should not raise + @mock.patch('azext_containerapp._validators._set_debug_defaults') + def test_entrypoint_without_image_raises(self, mock_defaults): + """--entrypoint without --image should raise even when --command is set.""" + from azext_containerapp._validators import validate_debug + ns = self._make_namespace( + debug_command='/bin/bash', + custom_debug_image_entrypoint_command='/bin/bash', + ) + with self.assertRaises(ValidationError) as ctx: + validate_debug(mock.MagicMock(), ns) + self.assertIn("--entrypoint requires --image", str(ctx.exception)) + if __name__ == '__main__': unittest.main() diff --git a/src/containerapp/setup.py b/src/containerapp/setup.py index 1986dd98476..f1a5713542b 100644 --- a/src/containerapp/setup.py +++ b/src/containerapp/setup.py @@ -28,7 +28,7 @@ # TODO: Confirm this is the right version number you want and it matches your # HISTORY.rst entry. -VERSION = '1.3.0b4' +VERSION = '1.3.0b5' # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers