Kaynağa Gözat

web: use firmware server module for feature defaults and exposing standard artifacts

Shiv Tyagi 1 ay önce
ebeveyn
işleme
ba431bafea

+ 33 - 0
web/api/v1/vehicles.py

@@ -5,6 +5,7 @@ from web.schemas import (
     VehicleBase,
     VersionOut,
     BoardOut,
+    StandardArtifactOut,
     FeatureOut,
 )
 from web.services.vehicles import get_vehicles_service, VehiclesService
@@ -183,6 +184,38 @@ async def get_board(
     return board
 
 
+@router.get(
+    "/{vehicle_id}/versions/{version_id}/boards/{board_id}/standard_artifacts",
+    response_model=List[StandardArtifactOut],
+    responses={
+        404: {"description": "Standard artifacts not found"},
+    },
+)
+async def list_board_standard_artifacts(
+    vehicle_id: str = Path(..., description="Vehicle identifier"),
+    version_id: str = Path(..., description="Version identifier"),
+    board_id: str = Path(..., description="Board identifier"),
+    service: VehiclesService = Depends(get_vehicles_service),
+):
+    """
+    Get standard build artifact files from firmware.ardupilot.org for a board.
+
+    Only official manifest-backed versions include per-board artifact URLs.
+    """
+    artifacts = service.get_board_standard_artifacts(
+        vehicle_id, version_id, board_id
+    )
+    if artifacts is None:
+        raise HTTPException(
+            status_code=404,
+            detail=(
+                f"Standard artifacts not found for board '{board_id}' "
+                f"in vehicle '{vehicle_id}' version '{version_id}'"
+            ),
+        )
+    return artifacts
+
+
 # --- Feature Endpoints ---
 @router.get(
     "/{vehicle_id}/versions/{version_id}/boards/{board_id}/features",

+ 8 - 0
web/main.py

@@ -26,6 +26,7 @@ import ap_git
 import build_manager
 from metadata_manager import (
     APSourceMetadataFetcher,
+    FeaturesTxtClient,
     ManifestJSON,
     VehiclesManager,
     VersionsManager,
@@ -64,6 +65,12 @@ async def lifespan(app: FastAPI):
         redis_port=settings.redis_port,
     )
 
+    features_txt_client = FeaturesTxtClient(
+        redis_host=settings.redis_host,
+        redis_port=settings.redis_port,
+        caching_enabled=True,
+    )
+
     versions_manager = VersionsManager(
         ap_repo=repo,
         remotes_json_path=settings.remotes_json_path,
@@ -101,6 +108,7 @@ async def lifespan(app: FastAPI):
     app.state.repo = repo
     app.state.ap_src_metadata_fetcher = ap_src_metadata_fetcher
     app.state.manifest_json = manifest_json
+    app.state.features_txt_client = features_txt_client
     app.state.versions_manager = versions_manager
     app.state.vehicles_manager = vehicles_manager
     app.state.build_manager = build_mgr

+ 2 - 0
web/schemas/__init__.py

@@ -27,6 +27,7 @@ from web.schemas.vehicles import (
     VersionOut,
     BoardBase,
     BoardOut,
+    StandardArtifactOut,
     CategoryBase,
     FeatureDefault,
     FeatureBase,
@@ -49,6 +50,7 @@ __all__ = [
     "VersionOut",
     "BoardBase",
     "BoardOut",
+    "StandardArtifactOut",
     "CategoryBase",
     "FeatureDefault",
     "FeatureBase",

+ 7 - 0
web/schemas/vehicles.py

@@ -49,6 +49,13 @@ class BoardOut(BoardBase):
     version_id: str = Field(..., description="Associated version identifier")
 
 
+class StandardArtifactOut(BaseModel):
+    name: str = Field(..., description="Artifact filename")
+    url: str = Field(..., description="Download URL on firmware.ardupilot.org")
+    format: str = Field(..., description="Artifact format (e.g. apj)")
+    size: Optional[int] = Field(None, description="File size in bytes")
+
+
 # --- Features ---
 class CategoryBase(BaseModel):
     id: str = Field(..., description="Unique category identifier")

+ 74 - 9
web/services/vehicles.py

@@ -5,11 +5,14 @@ import logging
 from typing import List, Optional
 from fastapi import Request
 
+from metadata_manager.firmware_server.index import latest_features_txt_url
+from metadata_manager.versions_manager.providers import OFFICIAL_REMOTE_NAME
 from web.schemas import (
     VehicleBase,
     RemoteInfo,
     VersionOut,
     BoardOut,
+    StandardArtifactOut,
     FeatureOut,
     CategoryBase,
     FeatureDefault,
@@ -25,10 +28,14 @@ class VehiclesService:
     def __init__(self, vehicle_manager=None,
                  versions_manager=None,
                  ap_src_metadata_fetcher=None,
+                 manifest_json=None,
+                 features_txt_client=None,
                  repo=None):
         self.vehicles_manager = vehicle_manager
         self.versions_manager = versions_manager
         self.ap_src_metadata_fetcher = ap_src_metadata_fetcher
+        self.manifest_json = manifest_json
+        self.features_txt_client = features_txt_client
         self.repo = repo
 
     def get_all_vehicles(self) -> List[VehicleBase]:
@@ -150,6 +157,58 @@ class VehiclesService:
                 return board
         return None
 
+    def get_board_standard_artifacts(
+        self,
+        vehicle_id: str,
+        version_id: str,
+        board_id: str,
+    ) -> Optional[List[StandardArtifactOut]]:
+        """Get standard build artifacts for an official manifest-backed version."""
+        version_info = self.versions_manager.get_version_info(
+            vehicle_id=vehicle_id,
+            version_id=version_id,
+        )
+        if (
+            version_info is None
+            or version_info.remote_info.name != OFFICIAL_REMOTE_NAME
+        ):
+            return None
+
+        with self.repo.get_checkout_lock():
+            boards = self.ap_src_metadata_fetcher.get_boards(
+                remote=version_info.remote_info.name,
+                commit_ref=version_info.commit_ref,
+                vehicle_id=vehicle_id,
+            )
+        if board_id not in boards:
+            return None
+
+        logger.info(
+            "Standard artifacts requested for %s version %s board %s",
+            vehicle_id,
+            version_id,
+            board_id,
+        )
+
+        artifacts = self.manifest_json.get_board_artifacts(
+            vehicle_id=vehicle_id,
+            release_type=version_info.release_type,
+            version_number=version_info.version_number,
+            board_id=board_id,
+        )
+        if not artifacts:
+            return None
+
+        return [
+            StandardArtifactOut(
+                name=artifact.name,
+                url=artifact.url,
+                format=artifact.format,
+                size=artifact.size,
+            )
+            for artifact in artifacts
+        ]
+
     def get_features(
         self,
         vehicle_id: str,
@@ -182,16 +241,20 @@ class VehiclesService:
             )
 
         # Try to fetch board-specific defaults from firmware-server
-        board_defaults = None
-        artifacts_dir = version_info.ap_build_artifacts_url
-        if artifacts_dir is not None:
-            board_defaults = (
-                self.ap_src_metadata_fetcher.get_board_defaults_from_fw_server(
-                    artifacts_url=artifacts_dir,
-                    board_id=board_id,
-                    vehicle_id=vehicle_id,
-                )
+        if version_info.remote_info.name == OFFICIAL_REMOTE_NAME:
+            features_url = self.manifest_json.get_features_txt_url(
+                vehicle_id=vehicle_id,
+                release_type=version_info.release_type,
+                version_number=version_info.version_number,
+                board_id=board_id,
             )
+        else:
+            features_url = latest_features_txt_url(vehicle_id, board_id)
+        board_defaults = (
+            self.features_txt_client.get_defaults(features_url)
+            if features_url
+            else None
+        )
 
         # Build feature list
         features = []
@@ -269,5 +332,7 @@ def get_vehicles_service(request: Request) -> VehiclesService:
         vehicle_manager=request.app.state.vehicles_manager,
         versions_manager=request.app.state.versions_manager,
         ap_src_metadata_fetcher=request.app.state.ap_src_metadata_fetcher,
+        manifest_json=request.app.state.manifest_json,
+        features_txt_client=request.app.state.features_txt_client,
         repo=request.app.state.repo,
     )