Skip to content

Commit

Permalink
[azure] [feat] Implement update_tag, delete_tag of a resource (#1806)
Browse files Browse the repository at this point in the history
  • Loading branch information
1101-1 authored Oct 17, 2023
1 parent 6b6ee30 commit c464a3f
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 3 deletions.
50 changes: 50 additions & 0 deletions plugins/azure/resoto_plugin_azure/azure_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from azure.mgmt.core.exceptions import ARMErrorFormat
from azure.mgmt.resource import ResourceManagementClient
from azure.mgmt.resource.resources._serialization import Serializer
from azure.mgmt.resource.resources.models import GenericResource

from resoto_plugin_azure.config import AzureCredentials
from resotolib.types import Json
Expand Down Expand Up @@ -45,6 +46,14 @@ def for_location(self, location: str) -> AzureClient:
def delete(self, resource_id: str) -> bool:
pass

@abstractmethod
def update_resource_tag(self, tag_name: str, tag_value: str, resource_id: str) -> bool:
pass

@abstractmethod
def delete_resource_tag(self, tag_name: str, resource_id: str) -> bool:
pass

@staticmethod
def __create_management_client(
credential: AzureCredentials, subscription_id: str, resource_group: Optional[str] = None
Expand Down Expand Up @@ -81,6 +90,47 @@ def delete(self, resource_id: str) -> bool:

return True

def update_resource_tag(self, tag_name: str, tag_value: str, resource_id: str) -> bool:
return self._update_or_delete_tag(
tag_name=tag_name, tag_value=tag_value, resource_id=resource_id, is_update=True
)

def delete_resource_tag(self, tag_name: str, resource_id: str) -> bool:
return self._update_or_delete_tag(tag_name=tag_name, tag_value="", resource_id=resource_id, is_update=False)

def _update_or_delete_tag(self, tag_name: str, tag_value: str, resource_id: str, is_update: bool) -> bool:
try:
# Get the resource by its ID
resource = self.client.resources.get_by_id(resource_id=resource_id, api_version="2021-04-01")

# Check if need to update or delete tag
if is_update:
# Create the tag or update its value if it exists
resource.tags[tag_name] = tag_value
else:
# Check if the tag exists in the resource's tags
existing_tag_value = resource.tags.get(tag_name)

# If the tag exists, delete it
if existing_tag_value is not None:
resource.tags.pop(tag_name)
else:
return True

# Create or update the resource to reflect the removal of the tag
updated_resource = GenericResource(location=resource.location, tags=resource.tags)
self.client.resources.begin_create_or_update_by_id(resource_id, "2021-04-01", updated_resource)

except HttpResponseError as e:
if e.error and e.error.code == "ResourceNotFoundError":
return False # Resource not found
elif e.error and e.error.code == "ResourceExistsError":
return False # Tag for update/delete does not exist
else:
raise e

return True

# noinspection PyProtectedMember
def _call(self, spec: AzureApiSpec, **kwargs: Any) -> List[Json]:
_SERIALIZER = Serializer()
Expand Down
33 changes: 30 additions & 3 deletions plugins/azure/resoto_plugin_azure/resource/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,45 @@ class AzureResource(BaseResource):
# Which API to call and what to expect in the result.
api_spec: ClassVar[Optional[AzureApiSpec]] = None

def resource_subscription_id(self) -> str:
"""
Extracts {subscriptionId} value from a resource ID.
e.g. /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/...
Returns:
str: The extracted subscription ID.
"""
return self.id.split("/")[2]

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]
subscription_id = self.resource_subscription_id()
return get_client(subscription_id).delete(self.id)

def delete_tag(self, key: str) -> bool:
"""Deletes a tag value.
This method removes a specific value from a tag associated with a subscription, while keeping the tag itself intact.
The tag remains on the account, but the specified value will be deleted.
"""
subscription_id = self.resource_subscription_id()
return get_client(subscription_id).delete_resource_tag(tag_name=key, resource_id=self.id)

def update_tag(self, key: str, value: str) -> bool:
"""Creates a tag value. The name of the tag must already exist.
This method allows for the creation or update of a tag value associated with the specified tag name.
The tag name must already exist for the operation to be successful.
"""
subscription_id = self.resource_subscription_id()
return get_client(subscription_id).update_resource_tag(tag_name=key, tag_value=value, resource_id=self.id)

def pre_process(self, graph_builder: GraphBuilder, source: Json) -> None:
"""
Hook method to pre process the resource before it is added to the graph.
Expand Down
6 changes: 6 additions & 0 deletions plugins/azure/test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ def for_location(self, location: str) -> AzureClient:
def delete(self, resource_id: str) -> bool:
return False

def delete_resource_tag(self, tag_name: str, resource_id: str) -> bool:
return False

def update_resource_tag(self, tag_name: str, tag_value: str, resource_id: str) -> bool:
return False


@fixture
def config() -> AzureConfig:
Expand Down

0 comments on commit c464a3f

Please sign in to comment.