diff --git a/plugins/azure/resoto_plugin_azure/azure_client.py b/plugins/azure/resoto_plugin_azure/azure_client.py index 0c50f8f14b..2be9b300e5 100644 --- a/plugins/azure/resoto_plugin_azure/azure_client.py +++ b/plugins/azure/resoto_plugin_azure/azure_client.py @@ -41,6 +41,10 @@ def list(self, spec: AzureApiSpec, **kwargs: Any) -> List[Json]: def for_location(self, location: str) -> AzureClient: pass + @abstractmethod + def delete(self, resource_id: str) -> bool: + pass + @staticmethod def __create_management_client( credential: AzureCredentials, subscription_id: str, resource_group: Optional[str] = None @@ -66,8 +70,16 @@ def list(self, spec: AzureApiSpec, **kwargs: Any) -> List[Json]: else: raise e - def delete(self, resource_id: str) -> None: - self.client.resources.delete_by_id(resource_id) + def delete(self, resource_id: str) -> bool: + try: + self.client.resources.begin_delete_by_id(resource_id=resource_id, api_version="2021-04-01") + except HttpResponseError as e: + if e.error and e.error.code == "ResourceNotFoundError": + return False # Resource not found to delete + else: + raise e + + return True # noinspection PyProtectedMember def _call(self, spec: AzureApiSpec, **kwargs: Any) -> List[Json]: diff --git a/plugins/azure/resoto_plugin_azure/resource/base.py b/plugins/azure/resoto_plugin_azure/resource/base.py index 8eb21a3bc1..68ceb1154a 100644 --- a/plugins/azure/resoto_plugin_azure/resource/base.py +++ b/plugins/azure/resoto_plugin_azure/resource/base.py @@ -3,21 +3,36 @@ import logging from concurrent.futures import Future from threading import Lock -from typing import Any, ClassVar, Dict, Optional, TypeVar, List, Type, Callable +from typing import Any, ClassVar, Dict, Optional, TypeVar, List, Type, Callable, cast from attr import define, field from azure.core.utils import CaseInsensitiveDict +from azure.identity import DefaultAzureCredential from resoto_plugin_azure.azure_client import AzureApiSpec, AzureClient -from resoto_plugin_azure.config import AzureCredentials +from resoto_plugin_azure.config import AzureConfig, AzureCredentials from resotolib.baseresources import BaseResource, Cloud, EdgeType, BaseAccount, BaseRegion, ModelReference from resotolib.core.actions import CoreFeedback from resotolib.graph import Graph, EdgeKey from resotolib.json_bender import Bender, bend, S, ForallBend, Bend from resotolib.threading import ExecutorQueue from resotolib.types import Json +from resotolib.config import current_config log = logging.getLogger("resoto.plugins.azure") + + +def get_client(subscription_id: str) -> AzureClient: + config = current_config() + azure_config = cast(AzureConfig, config.azure) + # Taking credentials from the config if access through the environment cannot be provided + if azure_config.accounts and (account := azure_config.accounts.get(subscription_id)): + credential = account.credentials() + else: + credential = DefaultAzureCredential() + return AzureClient.create(credential=credential, subscription_id=subscription_id) + + T = TypeVar("T") @@ -28,10 +43,17 @@ class AzureResource(BaseResource): # Which API to call and what to expect in the result. api_spec: ClassVar[Optional[AzureApiSpec]] = None - def delete(self, graph: Any) -> bool: - # TODO: implement me. - # get_client().delete(self.id) - return False + def delete(self, graph: Graph) -> bool: + """ + Deletes a resource by ID. + + Returns: + bool: True if the resource was successfully deleted; False otherwise. + """ + # Extracts {subscriptionId} value from a resource_id + # e.g /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/... + subscription_id = self.id.split("/")[2] + return get_client(subscription_id).delete(self.id) def pre_process(self, graph_builder: GraphBuilder, source: Json) -> None: """ diff --git a/plugins/azure/test/conftest.py b/plugins/azure/test/conftest.py index bc97daa8ad..4841ad2545 100644 --- a/plugins/azure/test/conftest.py +++ b/plugins/azure/test/conftest.py @@ -36,6 +36,9 @@ def create(*args: Any, **kwargs: Any) -> StaticFileAzureClient: def for_location(self, location: str) -> AzureClient: return self + def delete(self, resource_id: str) -> bool: + return False + @fixture def config() -> AzureConfig: