diff --git a/hub_adapter/autostart.py b/hub_adapter/autostart.py index 214224e..ec3740c 100644 --- a/hub_adapter/autostart.py +++ b/hub_adapter/autostart.py @@ -21,18 +21,13 @@ get_core_client, get_flame_hub_auth_flow, get_node_id, - get_node_metadata_for_url, get_node_type_cache, - get_registry_metadata_for_url, get_settings, get_ssl_context, ) from hub_adapter.errors import KongConflictError, KongConnectError from hub_adapter.middleware import log_event -from hub_adapter.routers.hub import ( - _parse_query_params, - list_analysis_nodes, -) +from hub_adapter.routers.hub import _parse_query_params, list_analysis_nodes from hub_adapter.routers.kong import ( create_and_connect_analysis_to_project, delete_analysis, @@ -117,7 +112,7 @@ async def auto_start_analyses(self) -> set | None: ) return None - formatted_query_params = _parse_query_params(sort="-updated_at") + formatted_query_params = _parse_query_params(sort="-updated_at", include="analysis") try: analyses = await list_analysis_nodes( @@ -377,16 +372,12 @@ async def send_start_request(self, analysis_props: dict, kong_token: str) -> tup service=ServiceTag.AUTOSTART, ) - node_metadata = get_node_metadata_for_url(analysis_props["node_id"], core_client=self.core_client) - analysis_info = get_registry_metadata_for_url(node_metadata, core_client=self.core_client) - analysis_id = analysis_props["analysis_id"] project_id = analysis_props["project_id"] props = compile_analysis_pod_data( analysis_id=analysis_id, project_id=project_id, - compiled_info=analysis_info, kong_token=kong_token, ) diff --git a/hub_adapter/constants.py b/hub_adapter/constants.py index a6fc7e5..a9b1ec6 100644 --- a/hub_adapter/constants.py +++ b/hub_adapter/constants.py @@ -15,12 +15,6 @@ HOST = "host" NODE = "node" NODE_ID = "NODE_ID" -REGISTRY = "registry" -ACCOUNT_NAME = "account_name" -EXTERNAL_NAME = "external_name" -ACCOUNT_SECRET = "account_secret" -REGISTRY_PROJECT = "registry_project" -REGISTRY_PROJECT_ID = "registry_project_id" class ServiceTag(StrEnum): diff --git a/hub_adapter/dependencies.py b/hub_adapter/dependencies.py index bb9389d..374d9ff 100644 --- a/hub_adapter/dependencies.py +++ b/hub_adapter/dependencies.py @@ -316,128 +316,15 @@ async def get_node_type_cache( return _node_type_cache -def get_node_metadata_for_url( - node_id: Annotated[uuid.UUID | str, Body(description="Node UUID")], - core_client: Annotated[flame_hub.CoreClient, Depends(get_core_client)], -) -> Node | None: - """Get analysis metadata for a given UUID to be used in creating analysis image URL.""" - node_metadata: Node | None = core_client.get_node(node_id=node_id) - - if not node_metadata.registry_project_id: - err_msg = f"No registry project associated with node {node_id}" - logger.error(err_msg) - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail={ - "message": err_msg, - "service": "Hub", - "status_code": status.HTTP_400_BAD_REQUEST, - }, - headers={"WWW-Authenticate": "Bearer"}, - ) - - return node_metadata - - -def get_registry_metadata_for_url( - node_metadata: Annotated[Node, Depends(get_node_metadata_for_url)], - core_client: Annotated[flame_hub.CoreClient, Depends(get_core_client)], -): - """Get registry metadata for a given UUID to be used in creating analysis image URL.""" - registry_metadata = dict() - - try: - registry_metadata = core_client.get_registry_project( - node_metadata.registry_project_id, - fields=("account_id", "account_name", "account_secret"), - ) - - except HubAPIError as err: - if err.error_response.status_code == status.HTTP_404_NOT_FOUND: - err_msg = f"Registry Project {node_metadata.registry_project_id} not found" - logger.error(err_msg) - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail={ - "message": err_msg, - "service": "Hub", - "status_code": status.HTTP_404_NOT_FOUND, - }, - headers={"WWW-Authenticate": "Bearer"}, - ) from err - - else: - logger.error(err.error_response.message) - raise HTTPException( - status_code=err.error_response.status_code, - detail={ - "message": err.error_response.message, - "service": "Hub", - "status_code": err.error_response.status_code, - }, - headers={"WWW-Authenticate": "Bearer"}, - ) from err - - if not registry_metadata.external_name: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail={ - "message": "No external name for node found", - "service": "Hub", - "status_code": status.HTTP_400_BAD_REQUEST, - }, - headers={"WWW-Authenticate": "Bearer"}, - ) - - if not registry_metadata.account_name or not registry_metadata.account_secret: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail={ - "message": "Unable to retrieve node client ID or secret from the registry", - "service": "Hub", - "status_code": status.HTTP_404_NOT_FOUND, - }, - headers={"WWW-Authenticate": "Bearer"}, - ) - - registry_project_external_name = registry_metadata.external_name - registry_id = registry_metadata.registry_id - - if not registry_id: - err = f"No registry is associated with node {registry_project_external_name}" - logger.error(err) - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail={ - "message": err, - "service": "Hub", - "status_code": status.HTTP_400_BAD_REQUEST, - }, - headers={"WWW-Authenticate": "Bearer"}, - ) - - host = registry_metadata.registry.host - user = registry_metadata.account_name - pwd = registry_metadata.account_secret - - return host, registry_project_external_name, user, pwd - - def compile_analysis_pod_data( analysis_id: Annotated[uuid.UUID | str, Body(description="Analysis UUID")], project_id: Annotated[uuid.UUID | str, Body(description="Project UUID")], - compiled_info: Annotated[tuple, Depends(get_registry_metadata_for_url)], kong_token: Annotated[str, Body(description="Analysis keyauth kong token")] = None, ): """Put all the data together for passing on to the PO.""" - host, registry_project_external_name, registry_user, registry_sec = compiled_info compiled_response = { - "image_url": f"{host}/{registry_project_external_name}/{analysis_id}", "analysis_id": str(analysis_id), "project_id": str(project_id), "kong_token": kong_token, - "registry_url": host, - "registry_user": registry_user, - "registry_password": registry_sec, } return compiled_response diff --git a/hub_adapter/routers/hub.py b/hub_adapter/routers/hub.py index 0c57432..92cc8a5 100644 --- a/hub_adapter/routers/hub.py +++ b/hub_adapter/routers/hub.py @@ -32,10 +32,14 @@ def _parse_query_params( page: Annotated[str | None, Query(description='Pagination as JSON, e.g. {"limit": 50, "offset": 0}')] = None, sort: Annotated[str | None, Query(description="Sort field; prefix with - for descending, + for ascending")] = None, fields: Annotated[str | None, Query(description="Comma-separated list of fields to return")] = None, + include: Annotated[str | None, Query(description="Comma-separated list of nested fields to include")] = None, ) -> dict: """Format query params for the hub client.""" formatted: dict = {} + if include: + formatted["include"] = include.split(",") + if fields: formatted["fields"] = fields.split(",") diff --git a/hub_adapter/routers/meta.py b/hub_adapter/routers/meta.py index 7a734cb..3c17ddd 100644 --- a/hub_adapter/routers/meta.py +++ b/hub_adapter/routers/meta.py @@ -62,7 +62,7 @@ async def initialize_analysis( # The Hub client is synchronous and this handler has to stay async, so the call is offloaded analysis = await run_in_threadpool( - core_client.find_analysis_nodes, filter={"analysis_id": analysis_params.analysis_id} + core_client.find_analysis_nodes, filter={"analysis_id": analysis_params.analysis_id}, include="analysis" ) if not analysis: raise HTTPException( diff --git a/hub_adapter/routers/podorc.py b/hub_adapter/routers/podorc.py index e05dc47..f34e7c2 100644 --- a/hub_adapter/routers/podorc.py +++ b/hub_adapter/routers/podorc.py @@ -9,12 +9,7 @@ from starlette.requests import Request from starlette.responses import Response -from hub_adapter.auth import ( - _add_internal_token_if_missing, - jwtbearer, - require_researcher_role, - verify_idp_token, -) +from hub_adapter.auth import _add_internal_token_if_missing, jwtbearer, require_researcher_role, verify_idp_token from hub_adapter.constants import ServiceTag from hub_adapter.core import route from hub_adapter.dependencies import compile_analysis_pod_data, get_settings @@ -52,10 +47,6 @@ body_params=[ "analysis_id", "project_id", - "registry_url", - "registry_user", - "registry_password", - "image_url", "kong_token", ], name="podorc.pods.create", diff --git a/hub_adapter/schemas/hub.py b/hub_adapter/schemas/hub.py index f06a4c1..3e21f06 100644 --- a/hub_adapter/schemas/hub.py +++ b/hub_adapter/schemas/hub.py @@ -1,25 +1,8 @@ """Models for Hub endpoints.""" -from flame_hub.models import Analysis, MasterImage, Project, Registry, NodeType +from flame_hub.models import NodeType from pydantic import BaseModel -class DetailedAnalysis(Analysis): - """Model representing a single detailed analysis.""" - - registry: Registry | None = None - project: Project | None = None - master_image: MasterImage | None = None - - -class AnalysisImageUrl(BaseModel): - image_url: str - project_id: str | None = None - kong_token: str | None = None - analysis_id: str - registry_url: str - registry_user: str | None = None - - class NodeTypeResponse(BaseModel): type: NodeType diff --git a/hub_adapter/schemas/podorc.py b/hub_adapter/schemas/podorc.py index b9da16a..dac26c6 100644 --- a/hub_adapter/schemas/podorc.py +++ b/hub_adapter/schemas/podorc.py @@ -23,11 +23,7 @@ class CreateAnalysis(BaseModel): analysis_id: str project_id: str - registry_url: str - registry_user: str - registry_password: str kong_token: str - image_url: str class LogReport(RootModel[dict[uuid.UUID, list[str | None]]]): diff --git a/pyproject.toml b/pyproject.toml index cb20db5..7ad25dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ requires-python = ">=3.12" dependencies = [ "uvicorn>=0.38.0", "fastapi>=0.138.0", - "flame-hub-client>=1.1.0", + "flame-hub-client>=2.0.0", "click>=8.1.7", "python-multipart>=0.0.31", "httpx2>=2.9.1", diff --git a/tests/constants.py b/tests/constants.py index e951feb..e3262fc 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -51,7 +51,6 @@ realm_id=None, registry_id=None, type="default", - robot_id=None, # deprecated ) MOCK_ANALYSIS = { diff --git a/tests/test_autostart.py b/tests/test_autostart.py index 29c49cc..608d107 100644 --- a/tests/test_autostart.py +++ b/tests/test_autostart.py @@ -267,16 +267,10 @@ async def test_fetch_token_header(self, mock_fetch_token, mock_log_event): @patch("hub_adapter.autostart.make_request") @patch("hub_adapter.autostart.GoGoAnalysis.fetch_token_header") @patch("hub_adapter.autostart.compile_analysis_pod_data") - @patch("hub_adapter.autostart.get_node_metadata_for_url") - @patch("hub_adapter.autostart.get_registry_metadata_for_url") @pytest.mark.asyncio - async def test_send_start_request( - self, mock_registry_metadata, mock_node_metadata, mock_pod_data, mock_token_header, mock_request, mock_log_event - ): + async def test_send_start_request(self, mock_pod_data, mock_token_header, mock_request, mock_log_event): """Test starting an analysis pod.""" # These first methods are for feeding into one another - mock_registry_metadata.return_value = {} # Not needed - mock_node_metadata.return_value = {} # Not needed mock_pod_data.return_value = {} # Not needed mock_token_header.return_value = {"foo"} # Just need something @@ -429,21 +423,15 @@ async def test_describe_node_success(self, mock_node_type_cache, mock_get_node_i @patch("hub_adapter.autostart.make_request") @patch("hub_adapter.autostart.GoGoAnalysis.fetch_token_header") @patch("hub_adapter.autostart.compile_analysis_pod_data") - @patch("hub_adapter.autostart.get_node_metadata_for_url") - @patch("hub_adapter.autostart.get_registry_metadata_for_url") @pytest.mark.asyncio async def test_send_start_request_http_status_error( self, - mock_registry_metadata, - mock_node_metadata, mock_pod_data, mock_token_header, mock_request, mock_log_event, ): """Test send_start_request with HTTPStatusError.""" - mock_registry_metadata.return_value = {} - mock_node_metadata.return_value = {} mock_pod_data.return_value = {} mock_token_header.return_value = {"Authorization": "Bearer token"} @@ -469,21 +457,15 @@ async def test_send_start_request_http_status_error( @patch("hub_adapter.autostart.make_request") @patch("hub_adapter.autostart.GoGoAnalysis.fetch_token_header") @patch("hub_adapter.autostart.compile_analysis_pod_data") - @patch("hub_adapter.autostart.get_node_metadata_for_url") - @patch("hub_adapter.autostart.get_registry_metadata_for_url") @pytest.mark.asyncio async def test_send_start_request_connect_error( self, - mock_registry_metadata, - mock_node_metadata, mock_pod_data, mock_token_header, mock_request, mock_log_event, ): """Test send_start_request with ConnectError.""" - mock_registry_metadata.return_value = {} - mock_node_metadata.return_value = {} mock_pod_data.return_value = {} mock_token_header.return_value = {"Authorization": "Bearer token"} @@ -503,12 +485,8 @@ async def test_send_start_request_connect_error( @patch("hub_adapter.autostart.GoGoAnalysis.fetch_token_header") @patch("hub_adapter.autostart.compile_analysis_pod_data") - @patch("hub_adapter.autostart.get_node_metadata_for_url") - @patch("hub_adapter.autostart.get_registry_metadata_for_url") @pytest.mark.asyncio - async def test_send_start_request_no_token( - self, mock_registry_metadata, mock_node_metadata, mock_pod_data, mock_token_header - ): + async def test_send_start_request_no_token(self, mock_pod_data, mock_token_header): """Test send_start_request when token header is None.""" mock_token_header.return_value = None @@ -896,21 +874,15 @@ def teardown_method(self): @patch("hub_adapter.autostart.make_request") @patch("hub_adapter.autostart.GoGoAnalysis.fetch_token_header") @patch("hub_adapter.autostart.compile_analysis_pod_data") - @patch("hub_adapter.autostart.get_node_metadata_for_url") - @patch("hub_adapter.autostart.get_registry_metadata_for_url") @pytest.mark.asyncio async def test_send_start_request_remote_protocol_error( self, - mock_registry_metadata, - mock_node_metadata, mock_pod_data, mock_token_header, mock_request, mock_log_event, ): """Test send_start_request with RemoteProtocolError.""" - mock_registry_metadata.return_value = {} - mock_node_metadata.return_value = {} mock_pod_data.return_value = {} mock_token_header.return_value = {"Authorization": "Bearer token"} diff --git a/tests/test_dependencies.py b/tests/test_dependencies.py index 6b6f6a9..0a3adf1 100644 --- a/tests/test_dependencies.py +++ b/tests/test_dependencies.py @@ -18,9 +18,7 @@ get_core_client, get_flame_hub_auth_flow, get_node_id, - get_node_metadata_for_url, get_node_type_cache, - get_registry_metadata_for_url, get_ssl_context, ) from hub_adapter.errors import HubConnectError @@ -138,113 +136,18 @@ async def test_get_node_type_cache(self, mock_node_id): good_cache = await get_node_type_cache(self.mock_settings, self.cc) assert good_cache["type"] == TEST_MOCK_NODE.type - @patch("flame_hub._core_client.CoreClient.get_node") - def test_get_node_metadata_for_url(self, mock_node): - """Test the get_node_metadata_for_url method.""" - mock_node.return_value = TEST_MOCK_NODE - correct_metadata = get_node_metadata_for_url(TEST_MOCK_NODE.id, self.cc) - assert correct_metadata == TEST_MOCK_NODE - - # Raise error if needed info is missing - mock_node.return_value.registry_project_id = None - with pytest.raises(HTTPException) as err: - get_node_metadata_for_url(TEST_MOCK_NODE.id, self.cc) - - assert err.value.status_code == status.HTTP_400_BAD_REQUEST - - @patch("flame_hub._core_client.CoreClient.get_registry_project") - def test_get_registry_metadata_for_url(self, mock_registry_project): - """Test the get_registry_metadata_for_url method.""" - - account_name = "fakeName" - account_secret = "fakeSecret" - host = "fakeHost" - - class FakeRegistry(BaseModel): - host: str - - class FakeRegistryProject(BaseModel): - account_id: uuid.UUID | None = None - account_name: str | None = None - account_secret: str | None = None - external_name: str | None = None - registry_id: uuid.UUID | None = None - registry: FakeRegistry | None = None - - # Registry project not found - TODO fix - # mock_registry_project.side_effect = HubAPIError(message="No external name for node found", request=None) - # with pytest.raises(HubAPIError) as err: - # get_registry_metadata_for_url(FAKE_NODE, self.cc) - # - # assert err.value.error_response.status_code == status.HTTP_404_NOT_FOUND - # - # mock_registry_project.side_effect = None - - mock_registry_project.return_value = FakeRegistryProject( - account_id=TEST_MOCK_NODE.id, - account_name=account_name, - account_secret=account_secret, - registry=FakeRegistry(host=host), - ) - - # No external name for node found - with pytest.raises(HTTPException) as err: - get_registry_metadata_for_url(TEST_MOCK_NODE, self.cc) - - assert err.value.status_code == status.HTTP_400_BAD_REQUEST - assert err.value.detail["message"] == "No external name for node found" - - mock_registry_project.return_value.external_name = account_name - - # No account_name for node found - mock_registry_project.return_value.account_name = None - with pytest.raises(HTTPException) as err: - get_registry_metadata_for_url(TEST_MOCK_NODE, self.cc) - - assert err.value.status_code == status.HTTP_404_NOT_FOUND - assert err.value.detail["message"] == "Unable to retrieve node client ID or secret from the registry" - - # Missing registry_id - mock_registry_project.return_value.account_name = account_name - with pytest.raises(HTTPException) as err: - get_registry_metadata_for_url(TEST_MOCK_NODE, self.cc) - - assert err.value.status_code == status.HTTP_400_BAD_REQUEST - assert "No registry is associated with node " in err.value.detail["message"] - - mock_registry_project.return_value.registry_id = TEST_MOCK_NODE.id - - # Working response - assert get_registry_metadata_for_url(TEST_MOCK_NODE, self.cc) == ( - host, - account_name, - account_name, - account_secret, - ) - - @patch("flame_hub._core_client.CoreClient.get_registry_project") - def test_compile_analysis_pod_data(self, mock_registry_project): + def test_compile_analysis_pod_data(self): """Test the compile_analysis_pod_data method.""" - host = "fakeHost" - ext_name = "fakeExternalName" - username = "fakeUsername" - pwd = "fakePwd" kong_token = "fakeKongToken" - registry_info = (host, ext_name, username, pwd) - compiled_info = compile_analysis_pod_data( - TEST_MOCK_ANALYSIS_ID, TEST_MOCK_PROJECT_ID, registry_info, kong_token + TEST_MOCK_ANALYSIS_ID, TEST_MOCK_PROJECT_ID, kong_token ) expected_result = { - "image_url": f"{host}/{ext_name}/{TEST_MOCK_ANALYSIS_ID}", "analysis_id": str(TEST_MOCK_ANALYSIS_ID), "project_id": str(TEST_MOCK_PROJECT_ID), "kong_token": kong_token, - "registry_url": host, - "registry_user": username, - "registry_password": pwd, } assert compiled_info == expected_result diff --git a/uv.lock b/uv.lock index f345f7d..374560f 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 5 +revision = 3 requires-python = ">=3.12" [[package]] @@ -334,16 +334,16 @@ wheels = [ [[package]] name = "flame-hub-client" -version = "1.1.0" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx2" }, { name = "pydantic", extra = ["email"] }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/27/3dc4c1197ee0a54d509f93ea734c01fd402be0ed8a98658dd5b487f87281/flame_hub_client-1.1.0.tar.gz", hash = "sha256:4eff02b8ff8df01d9a313ef464f30ceb6d62f341e39c5b368385564a914bddeb", size = 28058, upload-time = "2026-10-16T14:26:26.918Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/20/9b41b8dc36bbc85970bcd32f0e94899cb35d7b58a912ffcef448209befe6/flame_hub_client-2.0.0.tar.gz", hash = "sha256:4f792b12ce0547b871db4c5205d5edfa5742797e22b1c57a8fe41ca04747defb", size = 27882, upload-time = "2026-10-29T11:02:38.836Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/b0/1cb499cb502d5bfb7ae2aba0b618c169aa61762665487dd6e1471930b135/flame_hub_client-1.1.0-py3-none-any.whl", hash = "sha256:fa1ce9f764bb832eb512273deb287b8b8846b62f8e79d3a87d67784e334e8d2d", size = 30570, upload-time = "2026-10-16T14:26:25.576Z" }, + { url = "https://files.pythonhosted.org/packages/bc/3d/0479d5bf5208cf334c0296cb40d7b2a975fed9dfee379980db7319b68bf5/flame_hub_client-2.0.0-py3-none-any.whl", hash = "sha256:a6014398d4ed89bcd5bec6d6936aaea401ab6ed57354fcbc5a8ae429b15e7789", size = 30342, upload-time = "2026-10-29T11:02:37.447Z" }, ] [[package]] @@ -458,7 +458,7 @@ requires-dist = [ { name = "click", specifier = ">=8.1.7" }, { name = "cryptography", specifier = ">=50.0.0" }, { name = "fastapi", specifier = ">=0.138.0" }, - { name = "flame-hub-client", specifier = ">=1.1.0" }, + { name = "flame-hub-client", specifier = ">=2.0.0" }, { name = "httpx2", specifier = ">=2.9.1" }, { name = "kongadminclient", git = "https://github.com/PrivateAIM/kong-admin-python-client.git" }, { name = "peewee", specifier = ">=4.0.2" },