소스 검색

metadata_manager: add VersionFetcher class and supporting components

Shiv Tyagi 1 년 전
부모
커밋
815cde7f81
2개의 변경된 파일235개의 추가작업 그리고 1개의 파일을 삭제
  1. 2 1
      metadata_manager/__init__.py
  2. 233 0
      metadata_manager/core.py

+ 2 - 1
metadata_manager/__init__.py

@@ -1,4 +1,4 @@
-from .core import APSourceMetadataFetcher
+from .core import APSourceMetadataFetcher, VersionsFetcher
 from .exceptions import (
     MetadataManagerException,
     TooManyInstancesError
@@ -6,6 +6,7 @@ from .exceptions import (
 
 __all__ = [
     "APSourceMetadataFetcher",
+    "VersionsFetcher",
     "MetadataManagerException",
     "TooManyInstancesError"
 ]

+ 233 - 0
metadata_manager/core.py

@@ -3,7 +3,10 @@ import time
 import os
 import fnmatch
 import ap_git
+import json
+import jsonschema
 from . import exceptions as ex
+from threading import Lock
 
 logger = logging.getLogger(__name__)
 
@@ -134,3 +137,233 @@ class APSourceMetadataFetcher:
     @staticmethod
     def get_singleton():
         return APSourceMetadataFetcher.__singleton
+
+
+class VersionInfo:
+    """
+    Class to wrap version info properties inside a single object
+    """
+    def __init__(self,
+                 remote: str,
+                 commit_ref: str,
+                 release_type: str,
+                 version_number: str,
+                 ap_build_artifacts_url) -> None:
+        self.remote = remote
+        self.commit_ref = commit_ref
+        self.release_type = release_type
+        self.version_number = version_number
+        self.ap_build_artifacts_url = ap_build_artifacts_url
+
+
+class RemoteInfo:
+    """
+    Class to wrap remote info properties inside a single object
+    """
+    def __init__(self,
+                 name: str,
+                 url: str) -> None:
+        self.name = name
+        self.url = url
+
+
+class VersionsFetcher:
+    """
+    Class to fetch the version-to-build metadata from remotes.json
+    and provide methods to view the same
+    """
+
+    __singleton = None
+
+    def __init__(self, remotes_json_path: str):
+        """
+        Initializes the VersionsFetcher instance
+        with a given remotes.json path.
+
+        Parameters:
+            remotes_json_path (str): Path to the remotes.json file.
+
+        Raises:
+            TooManyInstancesError: If an instance of this class already exists,
+                                   enforcing a singleton pattern.
+        """
+        # Enforce singleton pattern by raising an error if
+        # an instance already exists.
+        if VersionsFetcher.__singleton:
+            raise ex.TooManyInstancesError()
+
+        self.__remotes_json_path = remotes_json_path
+        self.__access_lock_versions_metadata = Lock()
+        self.__versions_metadata = []
+        VersionsFetcher.__singleton = self
+
+    def get_all_remotes_info(self) -> list[RemoteInfo]:
+        """
+        Return the list of RemoteInfo objects constructed from the
+        information in the remotes.json file
+
+        Returns:
+            list: RemoteInfo objects for all remotes mentioned in remotes.json
+        """
+        return [
+            RemoteInfo(
+                name=remote.get('name', None),
+                url=remote.get('url', None)
+            )
+            for remote in self.__get_versions_metadata()
+        ]
+
+    def get_versions_for_vehicle(self, vehicle_name: str) -> list[VersionInfo]:
+        """
+        Return the list of dictionaries containing the info about the
+        versions listed to be built for a particular vehicle.
+
+        Parameters:
+            vehicle_name (str): the vehicle to fetch versions list for
+
+        Returns:
+            list: VersionInfo objects for all versions allowed to be
+                  built for the said vehicle.
+
+        """
+        if vehicle_name is None:
+            raise ValueError("Vehicle is a required parameter.")
+
+        versions_list = []
+        for remote in self.__get_versions_metadata():
+            for vehicle in remote['vehicles']:
+                if vehicle['name'] != vehicle_name:
+                    continue
+
+                for release in vehicle['releases']:
+                    versions_list.append(VersionInfo(
+                        remote=remote.get('name', None),
+                        commit_ref=release.get('commit_reference', None),
+                        release_type=release.get('release_type', None),
+                        version_number=release.get('version_number', None),
+                        ap_build_artifacts_url=release.get(
+                            'ap_build_artifacts_url',
+                            None
+                        )
+                    ))
+        return versions_list
+
+    def get_all_vehicles_sorted_uniq(self) -> list[str]:
+        """
+        Return a sorted list of all vehicles listed in remotes.json structure
+
+        Returns:
+            list: Vehicles listed in remotes.json
+
+        """
+        vehicles_set = set()
+        for remote in self.__get_versions_metadata():
+            for vehicle in remote['vehicles']:
+                vehicles_set.add(vehicle['name'])
+        return sorted(list(vehicles_set))
+
+    def is_version_listed(self, vehicle: str, remote: str,
+                          commit_ref: str) -> bool:
+        """
+        Check if a version with given properties mentioned in remotes.json
+
+        Parameters:
+            vehicle (str): vehicle for which version is listed
+            remote (str): remote under which the version is listed
+            commit_ref(str): commit reference for the version
+
+        Returns:
+            bool: True if the said version is mentioned in remotes.json,
+                  False otherwise
+
+        """
+        if vehicle is None:
+            raise ValueError("Vehicle is a required parameter.")
+
+        if remote is None:
+            raise ValueError("Remote is a required parameter.")
+
+        if commit_ref is None:
+            raise ValueError("Commit reference is a required parameter.")
+
+        return (remote, commit_ref) in [
+            (version_info.remote, version_info.commit_ref)
+            for version_info in
+            self.get_versions_for_vehicle(vehicle_name=vehicle)
+        ]
+
+    def get_version_info(self, vehicle: str, remote: str,
+                         commit_ref: str) -> VersionInfo:
+        """
+        Find first version matching the given properties in remotes.json
+
+        Parameters:
+            vehicle (str): vehicle for which version is listed
+            remote (str): remote under which the version is listed
+            commit_ref(str): commit reference for the version
+
+        Returns:
+            VersionInfo: Object for the version matching the properties,
+                         None if not found
+
+        """
+        return next(
+            (
+                version
+                for version in self.get_versions_for_vehicle(
+                    vehicle_name=vehicle
+                )
+                if version.remote == remote and
+                version.commit_ref == commit_ref
+            ),
+            None
+        )
+
+    def reload_remotes_json(self) -> None:
+        """
+        Read remotes.json, validate its structure against the schema
+        and cache it in memory
+        """
+        # load file containing vehicles listed to be built for each
+        # remote along with the branches/tags/commits on which the
+        # firmware can be built
+        remotes_json_schema_path = os.path.join(
+            os.path.dirname(__file__),
+            'remotes.schema.json'
+        )
+        with open(self.__remotes_json_path, 'r') as f, \
+             open(remotes_json_schema_path, 'r') as s:
+            versions_metadata = json.loads(f.read())
+            schema = json.loads(s.read())
+            # validate schema
+            jsonschema.validate(instance=versions_metadata, schema=schema)
+            self.__set_versions_metadata(versions_metadata=versions_metadata)
+
+    def __set_versions_metadata(self, versions_metadata: list) -> None:
+        """
+        Set versions metadata property with the one passed as parameter
+        This requires to acquire the access lock to avoid overwriting the
+        object while it is being read
+        """
+        if versions_metadata is None:
+            raise ValueError("versions_metadata is a required parameter. "
+                             "Cannot be None.")
+
+        with self.__access_lock_versions_metadata:
+            self.__versions_metadata = versions_metadata
+
+    def __get_versions_metadata(self) -> list:
+        """
+        Read versions metadata property
+        This requires to acquire the access lock to avoid reading the list
+        while it is being modified
+
+        Returns:
+            list: the versions metadata list
+        """
+        with self.__access_lock_versions_metadata:
+            return self.__versions_metadata
+
+    @staticmethod
+    def get_singleton():
+        return VersionsFetcher.__singleton