diff --git a/azext_edge/edge/common.py b/azext_edge/edge/common.py index de9bbb438..fa57020c5 100644 --- a/azext_edge/edge/common.py +++ b/azext_edge/edge/common.py @@ -58,16 +58,17 @@ class ResourceState(Enum): K8s resource state. """ - starting = "Starting" - running = "Running" - recovering = "Recovering" - succeeded = "Succeeded" - failed = "Failed" - waiting = "Waiting" - ok = "OK" + starting = "starting" + running = "running" + recovering = "recovering" + succeeded = "succeeded" + failed = "failed" + waiting = "waiting" + warning = "warning" + ok = "ok" warn = "warn" - error = "Error" - n_a = "N/A" + error = "error" + n_a = "n/a" @classmethod def map_to_color(cls, value) -> str: @@ -75,6 +76,7 @@ def map_to_color(cls, value) -> str: @classmethod def map_to_status(cls, value) -> CheckTaskStatus: + value = value.lower() status_map = { cls.starting.value: CheckTaskStatus.warning, cls.recovering.value: CheckTaskStatus.warning, @@ -83,6 +85,7 @@ def map_to_status(cls, value) -> CheckTaskStatus: cls.failed.value: CheckTaskStatus.error, cls.error.value: CheckTaskStatus.error, cls.waiting.value: CheckTaskStatus.warning, + cls.warning.value: CheckTaskStatus.warning, } return status_map.get(value, CheckTaskStatus.success) @@ -92,14 +95,15 @@ class PodState(Enum): K8s pod state. """ - pending = "Pending" - running = "Running" - succeeded = "Succeeded" - failed = "Failed" - unknown = "Unknown" + pending = "pending" + running = "running" + succeeded = "succeeded" + failed = "failed" + unknown = "unknown" @classmethod def map_to_status(cls, value) -> CheckTaskStatus: + value = value.lower() status_map = { cls.pending.value: CheckTaskStatus.warning, cls.unknown.value: CheckTaskStatus.warning, @@ -222,6 +226,7 @@ class FileType(ListableEnum): """ Supported file types/extensions for bulk asset operations. """ + csv = "csv" json = "json" portal_csv = "portal-csv" diff --git a/azext_edge/edge/providers/check/base/resource.py b/azext_edge/edge/providers/check/base/resource.py index b223145c3..6f85181fb 100644 --- a/azext_edge/edge/providers/check/base/resource.py +++ b/azext_edge/edge/providers/check/base/resource.py @@ -21,7 +21,7 @@ from ...edge_api import EdgeResourceApi from ....common import CheckTaskStatus, ResourceState -# TODO: unit test + refactor +# TODO: refactor logger = get_logger(__name__) @@ -160,7 +160,9 @@ def process_dict_resource( display_text = f"{key}:" check_manager.add_display( - target_name=target_name, namespace=namespace, display=Padding(display_text, (0, 0, 0, padding)) + target_name=target_name, + namespace=namespace, + display=Padding(display_text, (0, 0, 0, padding)), ) process_list_resource( @@ -175,7 +177,9 @@ def process_dict_resource( value_padding = padding if isinstance(value, str) and len(value) > 50: check_manager.add_display( - target_name=target_name, namespace=namespace, display=Padding(display_text, (0, 0, 0, padding)) + target_name=target_name, + namespace=namespace, + display=Padding(display_text, (0, 0, 0, padding)), ) value_padding += PADDING_SIZE display_text = "" @@ -183,7 +187,9 @@ def process_dict_resource( check_manager=check_manager, target_name=target_name, key=key, value=value ) check_manager.add_display( - target_name=target_name, namespace=namespace, display=Padding(display_text, (0, 0, 0, value_padding)) + target_name=target_name, + namespace=namespace, + display=Padding(display_text, (0, 0, 0, value_padding)), ) @@ -260,7 +266,12 @@ def process_resource_properties( def process_resource_property_by_type( - check_manager: CheckManager, target_name: str, properties: Any, display_name: str, namespace: str, padding: tuple + check_manager: CheckManager, + target_name: str, + properties: Any, + display_name: str, + namespace: str, + padding: tuple, ) -> None: padding_left = padding[3] if isinstance(properties, list): @@ -268,7 +279,9 @@ def process_resource_property_by_type( return display_text = f"{display_name}:" - check_manager.add_display(target_name=target_name, namespace=namespace, display=Padding(display_text, padding)) + check_manager.add_display( + target_name=target_name, namespace=namespace, display=Padding(display_text, padding) + ) for property in properties: display_text = f"- {display_name} {properties.index(property) + 1}" @@ -295,10 +308,14 @@ def process_resource_property_by_type( display_text = f"[cyan]{properties}[/cyan]" padding = (0, 0, 0, padding_left + 4) - check_manager.add_display(target_name=target_name, namespace=namespace, display=Padding(display_text, padding)) + check_manager.add_display( + target_name=target_name, namespace=namespace, display=Padding(display_text, padding) + ) elif isinstance(properties, dict): display_text = f"{display_name}:" - check_manager.add_display(target_name=target_name, namespace=namespace, display=Padding(display_text, padding)) + check_manager.add_display( + target_name=target_name, namespace=namespace, display=Padding(display_text, padding) + ) for prop, value in properties.items(): display_text = f"{prop}: [cyan]{value}[/cyan]" check_manager.add_display( @@ -346,9 +363,15 @@ def validate_one_of_conditions( eval_status = CheckTaskStatus.error.value one_of_condition = f"oneOf({conditions_names})" - check_manager.add_target_conditions(target_name=target_name, namespace=namespace, conditions=[one_of_condition]) + check_manager.add_target_conditions( + target_name=target_name, namespace=namespace, conditions=[one_of_condition] + ) check_manager.add_target_eval( - target_name=target_name, namespace=namespace, status=eval_status, value=eval_value, resource_name=resource_name + target_name=target_name, + namespace=namespace, + status=eval_status, + value=eval_value, + resource_name=resource_name, ) @@ -444,7 +467,9 @@ def process_custom_resource_status( if prop_value: status_text = f"{prop_name} {{{decorate_resource_status(prop_value.get('status'))}}}." if detail_level == ResourceOutputDetailLevel.verbose.value: - status_description = prop_value.get("statusDescription") or prop_value.get("output", {}).get("message") + status_description = prop_value.get("statusDescription") or prop_value.get( + "output", {} + ).get("message") if status_description: status_text = status_text.replace(".", f", [cyan]{status_description}[/cyan].") diff --git a/azext_edge/tests/edge/checks/base/test_resource_unit.py b/azext_edge/tests/edge/checks/base/test_resource_unit.py index 0e73acb0f..7f5dccc84 100644 --- a/azext_edge/tests/edge/checks/base/test_resource_unit.py +++ b/azext_edge/tests/edge/checks/base/test_resource_unit.py @@ -24,6 +24,7 @@ from azext_edge.edge.providers.check.base.resource import ( calculate_status, combine_statuses, + decorate_resource_status, process_resource_property_by_type, ) from azext_edge.edge.providers.check.common import ALL_NAMESPACES_TARGET, ResourceOutputDetailLevel @@ -35,7 +36,10 @@ OPCUA_API_V1, OpcuaResourceKinds, ) -from azext_edge.edge.providers.edge_api.deviceregistry import DEVICEREGISTRY_API_V1, DeviceRegistryResourceKinds +from azext_edge.edge.providers.edge_api.deviceregistry import ( + DEVICEREGISTRY_API_V1, + DeviceRegistryResourceKinds, +) from azext_edge.tests.edge.checks.conftest import generate_api_resource_list, generate_pod_stub @@ -62,7 +66,16 @@ "namespaced": True, "short_names": ["akrii"], "singular_name": "instance", - "verbs": ["delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"], + "verbs": [ + "delete", + "deletecollection", + "get", + "list", + "patch", + "create", + "update", + "watch", + ], }, { "categories": None, @@ -72,7 +85,16 @@ "namespaced": True, "short_names": ["akric"], "singular_name": "configuration", - "verbs": ["delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"], + "verbs": [ + "delete", + "deletecollection", + "get", + "list", + "patch", + "create", + "update", + "watch", + ], }, ], ), @@ -101,7 +123,16 @@ "short_names": None, "singular_name": "assettype", "storage_version_hash": "FCPRUJA7s2I=", - "verbs": ["delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"], + "verbs": [ + "delete", + "deletecollection", + "get", + "list", + "patch", + "create", + "update", + "watch", + ], "version": None, }, ], @@ -207,7 +238,11 @@ def test_filter_resources_by_name( @pytest.mark.parametrize( "api_info, resource_kind, expected_name", [ - (DEVICEREGISTRY_API_V1, DeviceRegistryResourceKinds.ASSET.value, "assets.deviceregistry.microsoft.com"), + ( + DEVICEREGISTRY_API_V1, + DeviceRegistryResourceKinds.ASSET.value, + "assets.deviceregistry.microsoft.com", + ), (DEVICEREGISTRY_API_V1, "mocktype", "mocktypes.deviceregistry.microsoft.com"), ], ) @@ -223,7 +258,11 @@ def test_generate_target_resource_name(api_info, resource_kind, expected_name): "asset", "test*", "namespace", - [{"metadata": {"name": "test1"}}, {"metadata": {"name": "test2"}}, {"metadata": {"name": "nontest"}}], + [ + {"metadata": {"name": "test1"}}, + {"metadata": {"name": "test2"}}, + {"metadata": {"name": "nontest"}}, + ], [{"metadata": {"name": "test1"}}, {"metadata": {"name": "test2"}}], ), ( @@ -236,7 +275,13 @@ def test_generate_target_resource_name(api_info, resource_kind, expected_name): ], [{"metadata": {"name": "asset1", "namespace": "default"}}], ), - ("asset", "nonexistent", None, [{"metadata": {"name": "test1"}}, {"metadata": {"name": "test2"}}], []), + ( + "asset", + "nonexistent", + None, + [{"metadata": {"name": "test1"}}, {"metadata": {"name": "test2"}}], + [], + ), ], ) def test_get_resources_by_name( @@ -463,6 +508,33 @@ def test_process_list_resource(mocker, mocked_check_manager, resource, expected_ assert not mock_process_dict_resource.called +@pytest.mark.parametrize( + "status, expected_status_text", + [ + ("Starting", "[yellow]{}[/yellow]"), + ("Running", "[green]{}[/green]"), + ("Recovering", "[yellow]{}[/yellow]"), + ("Succeeded", "[green]{}[/green]"), + ("Failed", "[red]{}[/red]"), + ("Waiting", "[yellow]{}[/yellow]"), + ("Warning", "[yellow]{}[/yellow]"), + ("Ok", "[green]{}[/green]"), + ("Warn", "[yellow]{}[/yellow]"), + ("Error", "[red]{}[/red]"), + ("N/A", "[yellow]{}[/yellow]"), + ("Unknown", "[green]{}[/green]"), + ], +) +def test_decorate_resource_status(status, expected_status_text): + # Call the function with status has upper case + result = decorate_resource_status(status) + assert result == expected_status_text.format(status) + + # Call the function with status has lower case + result = decorate_resource_status(status.lower()) + assert result == expected_status_text.format(status.lower()) + + @pytest.mark.parametrize( "detail_level, prop_value, properties, expected_calls", [ @@ -670,7 +742,9 @@ def test_process_resource_properties( ), ], ) -def test_process_resource_property_by_type(mocked_check_manager, properties, display_name, padding, expected_calls): +def test_process_resource_property_by_type( + mocked_check_manager, properties, display_name, padding, expected_calls +): # Call the function being tested process_resource_property_by_type( check_manager=mocked_check_manager,