From 7f78602cb50721581c21638ed26f9ad99efc4969 Mon Sep 17 00:00:00 2001 From: janosmurai Date: Mon, 4 Mar 2024 17:44:00 +0100 Subject: [PATCH] It was possible to install Dev Envs with unavailable images. Now the DEM shows an error and aborts the installation. --- dem/cli/command/install_cmd.py | 2 +- dem/core/dev_env.py | 14 ---- dem/core/platform.py | 21 ++++-- tests/cli/test_install_cmd.py | 2 +- tests/core/test_dev_env.py | 45 ------------- tests/core/test_platform.py | 113 +++++++++++++++++++++++++-------- 6 files changed, 105 insertions(+), 92 deletions(-) diff --git a/dem/cli/command/install_cmd.py b/dem/cli/command/install_cmd.py index c4bc011..5f9e0b5 100644 --- a/dem/cli/command/install_cmd.py +++ b/dem/cli/command/install_cmd.py @@ -23,6 +23,6 @@ def execute(platform: Platform, dev_env_name: str) -> None: try: platform.install_dev_env(dev_env_to_install) except PlatformError as e: - stderr.print(f"[red]Error: {e}[/]") + stderr.print(f"[red]{e}[/]") else: stdout.print(f"[green]Successfully installed the {dev_env_name}![/]") \ No newline at end of file diff --git a/dem/core/dev_env.py b/dem/core/dev_env.py index dbb08b5..e07d49b 100755 --- a/dem/core/dev_env.py +++ b/dem/core/dev_env.py @@ -104,20 +104,6 @@ def check_image_availability(self, all_tool_images: ToolImages, return image_statuses - def get_registry_only_tool_images(self, all_tool_images: ToolImages, - update_tool_image_store: bool) -> set: - """ Get the list of registry only tool images. - - Returns with the list of registry only tool images. - """ - registry_only_tool_images: set = set() - self.check_image_availability(all_tool_images, update_tool_image_store) - for tool in self.tools: - if tool["image_status"] == ToolImages.REGISTRY_ONLY: - registry_only_tool_images.add(tool["image_name"] + ':' + tool["image_version"]) - - return registry_only_tool_images - def get_deserialized(self, omit_is_installed: bool = False) -> dict[str, str]: """ Create the deserialized json. diff --git a/dem/core/platform.py b/dem/core/platform.py index ddcca0e..0deb4ed 100644 --- a/dem/core/platform.py +++ b/dem/core/platform.py @@ -144,12 +144,21 @@ def install_dev_env(self, dev_env_to_install: DevEnv) -> None: Args: dev_env_to_install -- the Development Environment to install """ - for tool_image in dev_env_to_install.get_registry_only_tool_images(self.tool_images, False): - self.user_output.msg(f"\nPulling image {tool_image}", is_title=True) - try: - self.container_engine.pull(tool_image) - except ContainerEngineError: - raise PlatformError("Dev Env install failed.") + dev_env_to_install.check_image_availability(self.tool_images, False) + + # First check if the missing images are available in the registries. + for tool in dev_env_to_install.tools: + if tool["image_status"] == ToolImages.NOT_AVAILABLE: + raise PlatformError(f"The {tool['image_name']}:{tool['image_version']} image is not available.") + + for tool in dev_env_to_install.tools: + if tool["image_status"] == ToolImages.REGISTRY_ONLY: + self.user_output.msg(f"\nPulling image {tool['image_name']}:{tool['image_version']}", + is_title=True) + try: + self.container_engine.pull(f"{tool['image_name']}:{tool['image_version']}") + except ContainerEngineError as e: + raise PlatformError(f"Dev Env install failed. Reason: {str(e)}") dev_env_to_install.is_installed = True self.flush_descriptors() diff --git a/tests/cli/test_install_cmd.py b/tests/cli/test_install_cmd.py index bed25db..d955c7a 100644 --- a/tests/cli/test_install_cmd.py +++ b/tests/cli/test_install_cmd.py @@ -89,5 +89,5 @@ def test_install_dev_env_valid_name_failed(mock_stderr_print): assert 0 == runner_result.exit_code mock_platform.get_dev_env_by_name.assert_called_once_with(fake_dev_env_to_install.name ) - mock_stderr_print.assert_called_once_with(f"[red]Error: Platform error: {test_exception_text}[/]") + mock_stderr_print.assert_called_once_with(f"[red]Platform error: {test_exception_text}[/]") diff --git a/tests/core/test_dev_env.py b/tests/core/test_dev_env.py index 1acefdb..2d4fe74 100644 --- a/tests/core/test_dev_env.py +++ b/tests/core/test_dev_env.py @@ -196,53 +196,8 @@ def test_DevEnv_check_image_availability_local_only(): for idx, tool in enumerate(test_dev_env.tools): assert expected_statuses[idx] == tool["image_status"] - mock_tool_images.local.update.assert_called_once() -def test_DevEnv_get_registry_only_tool_images() -> None: - # Test setup - test_descriptor = { - "name": "test_name", - "installed": "False", - "tools": [ - { - "image_name": "test_image_name1", - "image_version": "test_image_tag1" - }, - { - "image_name": "test_image_name2", - "image_version": "test_image_tag2" - }, - { - "image_name": "test_image_name3", - "image_version": "test_image_tag3" - }, - { - "image_name": "test_image_name4", - "image_version": "test_image_tag4" - }, - ] - } - mock_tool_images = MagicMock() - mock_tool_images.local.elements = [ - "test_image_name1:test_image_tag1", - "test_image_name2:test_image_tag2" - ] - mock_tool_images.registry.elements = [ - "test_image_name1:test_image_tag1", - "test_image_name3:test_image_tag3" - ] - test_dev_env = dev_env.DevEnv(test_descriptor) - - # Run unit under test - actual_registry_onnly_tool_images = test_dev_env.get_registry_only_tool_images(mock_tool_images, True) - - # Check expectations - expected_registry_only_tool_images = { - "test_image_name3:test_image_tag3", - } - assert expected_registry_only_tool_images == actual_registry_onnly_tool_images - def test_DevEnv_get_deserialized_is_installed_true() -> None: # Test setup test_descriptor: dict[str, Any] = { diff --git a/tests/core/test_platform.py b/tests/core/test_platform.py index 2a5bf8d..5f878ac 100644 --- a/tests/core/test_platform.py +++ b/tests/core/test_platform.py @@ -272,33 +272,54 @@ def test_Platform_get_dev_env_by_name_no_match(mock___init__: MagicMock) -> None @patch.object(platform.Platform, "user_output") @patch.object(platform.Platform, "__init__") def test_Platform_install_dev_env_succes(mock___init__: MagicMock, mock_user_input: MagicMock, - mock_container_engine: MagicMock, mock_tool_images, - mock_flush_descriptors: MagicMock) -> None: + mock_container_engine: MagicMock, mock_tool_images, + mock_flush_descriptors: MagicMock) -> None: # Test setup mock___init__.return_value = None - test_dev_env = MagicMock() - test_registry_only_tool_images: list[str] = ["test_image_name1:test_image_version1", - "test_image_name2:test_image_version2"] - test_dev_env.get_registry_only_tool_images.return_value = test_registry_only_tool_images + mock_dev_env = MagicMock() + mock_dev_env.tools = [ + { + "image_name": "test_image_name0", + "image_version": "test_image_version0", + "image_status": platform.ToolImages.LOCAL_AND_REGISTRY + }, + { + "image_name": "test_image_name1", + "image_version": "test_image_version1", + "image_status": platform.ToolImages.REGISTRY_ONLY + }, + { + "image_name": "test_image_name2", + "image_version": "test_image_version2", + "image_status": platform.ToolImages.REGISTRY_ONLY + }, + { + "image_name": "test_image_name3", + "image_version": "test_image_version3", + "image_status": platform.ToolImages.LOCAL_ONLY + } + ] test_platform = platform.Platform() # Run unit under test - test_platform.install_dev_env(test_dev_env) + test_platform.install_dev_env(mock_dev_env) # Check expectations mock___init__.assert_called_once() - test_dev_env.get_registry_only_tool_images.assert_called_once_with(mock_tool_images, False) + mock_dev_env.check_image_availability.assert_called_once_with(mock_tool_images, False) + expected_registry_only_tool_images: list[str] = ["test_image_name1:test_image_version1", + "test_image_name2:test_image_version2"] mock_user_input.msg.assert_has_calls([ - call(f"\nPulling image {test_registry_only_tool_images[0]}", is_title=True), - call(f"\nPulling image {test_registry_only_tool_images[1]}", is_title=True) + call(f"\nPulling image {expected_registry_only_tool_images[0]}", is_title=True), + call(f"\nPulling image {expected_registry_only_tool_images[1]}", is_title=True) ]) mock_container_engine.pull.assert_has_calls([ - call(test_registry_only_tool_images[0]), - call(test_registry_only_tool_images[1]) + call(expected_registry_only_tool_images[0]), + call(expected_registry_only_tool_images[1]) ]) mock_flush_descriptors.assert_called_once() @@ -306,29 +327,71 @@ def test_Platform_install_dev_env_succes(mock___init__: MagicMock, mock_user_inp @patch.object(platform.Platform, "container_engine") @patch.object(platform.Platform, "user_output") @patch.object(platform.Platform, "__init__") -def test_Platform_install_dev_env_failure(mock___init__: MagicMock, mock_user_output: MagicMock, - mock_container_engine: MagicMock,mock_tool_images) -> None: +def test_Platform_install_dev_env_pull_failure(mock___init__: MagicMock, mock_user_output: MagicMock, + mock_container_engine: MagicMock,mock_tool_images) -> None: # Test setup mock___init__.return_value = None - - test_dev_env = MagicMock() - test_registry_only_tool_images: list[str] = ["test_image_name1:test_image_version1", - "test_image_name2:test_image_version2"] - test_dev_env.get_registry_only_tool_images.return_value = test_registry_only_tool_images + mock_dev_env = MagicMock() + mock_dev_env.tools = [ + { + "image_name": "test_image_name1", + "image_version": "test_image_version1", + "image_status": platform.ToolImages.REGISTRY_ONLY + } + ] + + test_exception_text = "test_exception_text" + mock_container_engine.pull.side_effect = platform.ContainerEngineError(test_exception_text) test_platform = platform.Platform() - - mock_container_engine.pull.side_effect = platform.ContainerEngineError("") # Run unit under test with pytest.raises(platform.PlatformError) as exported_exception_info: - test_platform.install_dev_env(test_dev_env) + test_platform.install_dev_env(mock_dev_env) - # Check expectations - mock___init__.assert_called_once() + # Check expectations + assert str(exported_exception_info.value) == f"Platform error: Dev Env install failed. Reason:" + \ + f" Container engine error: {test_exception_text}" + + mock___init__.assert_called_once() + + mock_dev_env.check_image_availability.assert_called_once_with(mock_tool_images, False) + expected_registry_only_tool_image = "test_image_name1:test_image_version1" + mock_user_output.msg.assert_called_once_with(f"\nPulling image {expected_registry_only_tool_image}", + is_title=True) + mock_container_engine.pull.assert_called_once_with(expected_registry_only_tool_image) + +@patch.object(platform.Platform, "tool_images") +@patch.object(platform.Platform, "container_engine") +@patch.object(platform.Platform, "user_output") +@patch.object(platform.Platform, "__init__") +def test_Platform_install_dev_env_not_avilable(mock___init__: MagicMock, mock_user_output: MagicMock, + mock_container_engine: MagicMock,mock_tool_images) -> None: + # Test setup + mock___init__.return_value = None + + mock_dev_env = MagicMock() + mock_dev_env.tools = [ + { + "image_name": "test_image_name1", + "image_version": "test_image_version1", + "image_status": platform.ToolImages.NOT_AVAILABLE + } + ] + + test_platform = platform.Platform() + + # Run unit under test + with pytest.raises(platform.PlatformError) as exported_exception_info: + test_platform.install_dev_env(mock_dev_env) + + # Check expectations + assert str(exported_exception_info.value) == f"Platform error: The test_image_name1:test_image_version1 image is not available." + + mock___init__.assert_called_once() - assert str(exported_exception_info) == "Platform error: Dev Env install failed." + mock_dev_env.check_image_availability.assert_called_once_with(mock_tool_images, False) @patch.object(platform.Platform, "flush_descriptors") @patch.object(platform.Platform, "container_engine")