fix(resource): enforce DAG guard via service layer
CI / helm (pull_request) Successful in 24s
CI / push-validation (pull_request) Successful in 27s
CI / build (pull_request) Successful in 33s
CI / lint (pull_request) Failing after 35s
CI / quality (pull_request) Successful in 47s
CI / typecheck (pull_request) Successful in 55s
CI / security (pull_request) Successful in 55s
CI / coverage (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 3m6s
CI / integration_tests (pull_request) Successful in 4m3s
CI / unit_tests (pull_request) Failing after 4m56s
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been cancelled
CI / benchmark-regression (pull_request) Has been cancelled
CI / helm (pull_request) Successful in 24s
CI / push-validation (pull_request) Successful in 27s
CI / build (pull_request) Successful in 33s
CI / lint (pull_request) Failing after 35s
CI / quality (pull_request) Successful in 47s
CI / typecheck (pull_request) Successful in 55s
CI / security (pull_request) Successful in 55s
CI / coverage (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 3m6s
CI / integration_tests (pull_request) Successful in 4m3s
CI / unit_tests (pull_request) Failing after 4m56s
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been cancelled
CI / benchmark-regression (pull_request) Has been cancelled
ISSUES CLOSED: #6329
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user