diff --git a/src/cleveragents/application/services/_resource_registry_ops.py b/src/cleveragents/application/services/_resource_registry_ops.py index 0d54a1b04..37e9f2c53 100644 --- a/src/cleveragents/application/services/_resource_registry_ops.py +++ b/src/cleveragents/application/services/_resource_registry_ops.py @@ -25,6 +25,8 @@ from cleveragents.application.services._resource_registry_data import ( from cleveragents.core.exceptions import NotFoundError, ValidationError from cleveragents.domain.models.core.resource import PhysVirt, Resource from cleveragents.infrastructure.database.models import ( + ResourceEdgeModel, + ResourceLinkModel, ResourceModel, ResourceTypeModel, ) @@ -249,3 +251,78 @@ class ResourceInstanceMixin: return db_resource_to_domain(row) finally: session.close() + + def remove_resource( + self: RegistryHost, + name_or_id: str, + *, + force: bool = False, + ) -> None: + """Remove a resource from the registry. + + Args: + name_or_id: Namespaced name or ULID of the resource to remove. + force: When ``True``, cascade removal of DAG links before + deleting the resource (used by ``--update`` workflows). + + Raises: + ValidationError: If DAG links still reference the resource and + ``force`` is ``False``. + NotFoundError: If the resource does not exist. + """ + + resource = self.show_resource(name_or_id) + + session = self._session() + try: + link_filter = (ResourceLinkModel.parent_id == resource.resource_id) | ( + ResourceLinkModel.child_id == resource.resource_id + ) + link_query = session.query(ResourceLinkModel).filter(link_filter) + link_count = link_query.count() + + edge_filter = (ResourceEdgeModel.parent_id == resource.resource_id) | ( + ResourceEdgeModel.child_id == resource.resource_id + ) + edge_count = session.query(ResourceEdgeModel).filter(edge_filter).count() + + remaining_edges = max(link_count, edge_count) + if remaining_edges > 0 and not force: + display_name = resource.name or resource.resource_id + raise ValidationError( + message=( + f"Cannot remove resource '{display_name}': " + f"{remaining_edges} edge(s) still reference it." + ), + details={ + "resource_id": resource.resource_id, + "link_count": link_count, + "edge_count": edge_count, + }, + ) + + if link_count > 0: + link_query.delete(synchronize_session="fetch") + + if edge_count > 0: + session.query(ResourceEdgeModel).filter(edge_filter).delete( + synchronize_session="fetch" + ) + + row = ( + session.query(ResourceModel) + .filter_by(resource_id=resource.resource_id) + .first() + ) + if row is not None: + session.delete(row) + + session.commit() + except ValidationError: + session.rollback() + raise + except Exception: + session.rollback() + raise + finally: + session.close() diff --git a/src/cleveragents/cli/commands/resource.py b/src/cleveragents/cli/commands/resource.py index 64445f9ac..57ef64296 100644 --- a/src/cleveragents/cli/commands/resource.py +++ b/src/cleveragents/cli/commands/resource.py @@ -714,34 +714,7 @@ def resource_add( # --update: remove existing resource before re-registering if update: try: - existing = service.show_resource(name) - # Remove the existing resource so we can re-register - session = service._session() - try: - from cleveragents.infrastructure.database.models import ( - ResourceEdgeModel, - ResourceLinkModel, - ResourceModel, - ) - - # Remove links and edges first to avoid FK violations - session.query(ResourceLinkModel).filter( - (ResourceLinkModel.parent_id == existing.resource_id) - | (ResourceLinkModel.child_id == existing.resource_id) - ).delete(synchronize_session="fetch") - session.query(ResourceEdgeModel).filter( - (ResourceEdgeModel.parent_id == existing.resource_id) - | (ResourceEdgeModel.child_id == existing.resource_id) - ).delete(synchronize_session="fetch") - session.query(ResourceModel).filter_by( - resource_id=existing.resource_id - ).delete(synchronize_session="fetch") - session.commit() - except Exception: - session.rollback() - raise - finally: - session.close() + service.remove_resource(name, force=True) except NotFoundError: # Resource doesn't exist yet — proceed with normal add pass @@ -1378,45 +1351,11 @@ def resource_remove( console.print("[yellow]Aborted.[/yellow]") raise typer.Abort() - # Delete via session - session = service._session() try: - from cleveragents.infrastructure.database.models import ( - ResourceLinkModel, - ResourceModel, - ) - - # Check for edges - edge_count: int = ( - session.query(ResourceLinkModel) - .filter( - (ResourceLinkModel.parent_id == res.resource_id) - | (ResourceLinkModel.child_id == res.resource_id) - ) - .count() - ) - if edge_count > 0: - console.print( - f"[red]Cannot remove resource '{res.name or res.resource_id}': " - f"{edge_count} edge(s) still reference it.[/red]" - ) - raise typer.Abort() - - row = ( - session.query(ResourceModel) - .filter_by(resource_id=res.resource_id) - .first() - ) - if row is not None: - session.delete(row) - session.commit() - except typer.Abort: - raise - except Exception: - session.rollback() - raise - finally: - session.close() + service.remove_resource(res.resource_id) + except ValidationError as exc: + console.print(f"[red]{exc.message}[/red]") + raise typer.Abort() from exc console.print(f"[green]Removed resource:[/green] {res.name or res.resource_id}")