From 5b17947f2f963d10515f2cf9b2074c097bfdb39b Mon Sep 17 00:00:00 2001 From: Daryl Lim <5508348+daryllimyt@users.noreply.github.com> Date: Wed, 6 Nov 2024 20:04:13 +0000 Subject: [PATCH] feat: Allow deletion of remote repository (#494) --- frontend/src/client/services.gen.ts | 6 +-- frontend/src/client/types.gen.ts | 4 +- .../registry/registry-repos-table.tsx | 8 +++- tracecat/registry/repositories/router.py | 38 +++++++++---------- tracecat/registry/repositories/service.py | 16 +++++--- 5 files changed, 42 insertions(+), 30 deletions(-) diff --git a/frontend/src/client/services.gen.ts b/frontend/src/client/services.gen.ts index b31fb331c..d0a8cf9c5 100644 --- a/frontend/src/client/services.gen.ts +++ b/frontend/src/client/services.gen.ts @@ -1205,15 +1205,15 @@ export const registryRepositoriesUpdateRegistryRepository = (data: RegistryRepos * Delete Registry Repository * Delete a registry repository. * @param data The data for the request. - * @param data.origin + * @param data.id * @returns void Successful Response * @throws ApiError */ export const registryRepositoriesDeleteRegistryRepository = (data: RegistryRepositoriesDeleteRegistryRepositoryData): CancelablePromise => { return __request(OpenAPI, { method: 'DELETE', - url: '/registry/repos/{origin}', + url: '/registry/repos/{id}', path: { - origin: data.origin + id: data.id }, errors: { 422: 'Validation Error' diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts index bbb09008c..33b44a8c5 100644 --- a/frontend/src/client/types.gen.ts +++ b/frontend/src/client/types.gen.ts @@ -1602,7 +1602,7 @@ export type RegistryRepositoriesUpdateRegistryRepositoryData = { export type RegistryRepositoriesUpdateRegistryRepositoryResponse = RegistryRepositoryRead; export type RegistryRepositoriesDeleteRegistryRepositoryData = { - origin: string; + id: string; }; export type RegistryRepositoriesDeleteRegistryRepositoryResponse = void; @@ -2481,6 +2481,8 @@ export type $OpenApiTs = { 422: HTTPValidationError; }; }; + }; + '/registry/repos/{id}': { delete: { req: RegistryRepositoriesDeleteRegistryRepositoryData; res: { diff --git a/frontend/src/components/registry/registry-repos-table.tsx b/frontend/src/components/registry/registry-repos-table.tsx index cf385e6af..3e81c1193 100644 --- a/frontend/src/components/registry/registry-repos-table.tsx +++ b/frontend/src/components/registry/registry-repos-table.tsx @@ -116,9 +116,13 @@ export function RegistryRepositoriesTable() { console.error("No repository selected") return } - console.log("Deleting repository", selectedRepo.origin) + console.log( + "Deleting repository", + selectedRepo.origin, + selectedRepo.id + ) try { - await deleteRepo({ origin: selectedRepo.origin }) + await deleteRepo({ id: selectedRepo.id }) } catch (error) { console.error("Error deleting repository", error) } finally { diff --git a/tracecat/registry/repositories/router.py b/tracecat/registry/repositories/router.py index 2359ab361..4d80058c6 100644 --- a/tracecat/registry/repositories/router.py +++ b/tracecat/registry/repositories/router.py @@ -1,4 +1,5 @@ from fastapi import APIRouter, HTTPException, Query, status +from pydantic import UUID4 from tracecat.auth.credentials import RoleACL from tracecat.auth.dependencies import OrgUserOrServiceRole, OrgUserRole @@ -46,7 +47,7 @@ async def sync_registry_repositories( # Check if the base registry repository already exists await ensure_base_repository(session=session, role=role) if origins is None: - repos = await repos_service.list_repositories() + repos = list(await repos_service.list_repositories()) else: # If origins are provided, only sync those repositories repos: list[RegistryRepository] = [] @@ -67,7 +68,7 @@ async def sync_registry_repositories( try: await actions_service.sync_actions(repos) except RegistryError as e: - logger.warning("Cannot sync repository", origin=origin, exc=e) + logger.warning("Cannot sync repository", exc=e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) from e @@ -138,10 +139,11 @@ async def create_registry_repository( logger.info("Creating registry", params=params) created_repository = await service.create_repository(params) return RegistryRepositoryRead( + origin=created_repository.origin, actions=[ RegistryActionRead.model_validate(action, from_attributes=True) for action in created_repository.actions - ] + ], ) except Exception as e: raise HTTPException( @@ -170,32 +172,30 @@ async def update_registry_repository( updated_repository = await service.update_repository(repository) return RegistryRepositoryRead( origin=updated_repository.origin, - actions=[action.to_read_model() for action in updated_repository.actions], + actions=[ + RegistryActionRead.model_validate(action, from_attributes=True) + for action in updated_repository.actions + ], ) -@router.delete("/{origin:path}", status_code=status.HTTP_204_NO_CONTENT) +@router.delete("/{id:uuid}", status_code=status.HTTP_204_NO_CONTENT) async def delete_registry_repository( - role: OrgUserOrServiceRole, session: AsyncDBSession, origin: str + role: OrgUserOrServiceRole, session: AsyncDBSession, id: UUID4 ): """Delete a registry repository.""" - logger.info("Deleting registry repository", origin=origin) - if origin == DEFAULT_REGISTRY_ORIGIN: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="You cannot delete the base Tracecat repository.", - ) - elif origin == CUSTOM_REPOSITORY_ORIGIN: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="You cannot delete the custom repository.", - ) service = RegistryReposService(session, role) - repository = await service.get_repository(origin) + repository = await service.get_repository_by_id(id) if repository is None: - logger.error("Registry repository not found", origin=origin) + logger.error("Registry repository not found", id=id) raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Registry repository not found", ) + logger.info("Deleting registry repository", id=id) + if repository.origin in (DEFAULT_REGISTRY_ORIGIN, CUSTOM_REPOSITORY_ORIGIN): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=f"The {repository.origin!r} repository cannot be deleted.", + ) await service.delete_repository(repository) diff --git a/tracecat/registry/repositories/service.py b/tracecat/registry/repositories/service.py index fff0bb1c5..0345e174b 100644 --- a/tracecat/registry/repositories/service.py +++ b/tracecat/registry/repositories/service.py @@ -1,8 +1,9 @@ from __future__ import annotations -from collections.abc import AsyncGenerator +from collections.abc import AsyncGenerator, Sequence from contextlib import asynccontextmanager +from pydantic import UUID4 from sqlmodel import select from sqlmodel.ext.asyncio.session import AsyncSession @@ -31,7 +32,7 @@ async def with_session( async with get_async_session_context_manager() as session: yield RegistryReposService(session, role=role) - async def list_repositories(self) -> list[RegistryRepository]: + async def list_repositories(self) -> Sequence[RegistryRepository]: """Get all registry repositories.""" statement = select(RegistryRepository) result = await self.session.exec(statement) @@ -45,6 +46,12 @@ async def get_repository(self, origin: str) -> RegistryRepository | None: result = await self.session.exec(statement) return result.one_or_none() + async def get_repository_by_id(self, id: UUID4) -> RegistryRepository | None: + """Get a registry by ID.""" + statement = select(RegistryRepository).where(RegistryRepository.id == id) + result = await self.session.exec(statement) + return result.one_or_none() + async def create_repository( self, params: RegistryRepositoryCreate ) -> RegistryRepository: @@ -63,11 +70,10 @@ async def update_repository( """Update a registry repository.""" self.session.add(repository) await self.session.commit() + await self.session.refresh(repository) return repository - async def delete_repository( - self, repository: RegistryRepository - ) -> RegistryRepository: + async def delete_repository(self, repository: RegistryRepository) -> None: """Delete a registry repository.""" await self.session.delete(repository) await self.session.commit()