diff --git a/sonic_package_manager/database.py b/sonic_package_manager/database.py index 6c1cec5c07..11f7b305d6 100644 --- a/sonic_package_manager/database.py +++ b/sonic_package_manager/database.py @@ -31,6 +31,7 @@ class PackageEntry: built_in: Boolean flag whether the package is built in. image_id: Image ID for this package or None if package is not installed. + tag: Tag for this package or None if package is not installed. """ name: str @@ -41,6 +42,7 @@ class PackageEntry: installed: bool = False built_in: bool = False image_id: Optional[str] = None + tag: Optional[str] = None def package_from_dict(name: str, package_info: Dict) -> PackageEntry: @@ -55,10 +57,10 @@ def package_from_dict(name: str, package_info: Dict) -> PackageEntry: installed = package_info.get('installed', False) built_in = package_info.get('built-in', False) image_id = package_info.get('image-id') - + tag = package_info.get('tag') return PackageEntry(name, repository, description, default_reference, version, installed, - built_in, image_id) + built_in, image_id, tag) def package_to_dict(package: PackageEntry) -> Dict: @@ -72,6 +74,7 @@ def package_to_dict(package: PackageEntry) -> Dict: 'installed': package.installed, 'built-in': package.built_in, 'image-id': package.image_id, + 'tag': package.tag, } diff --git a/sonic_package_manager/service_creator/creator.py b/sonic_package_manager/service_creator/creator.py index cb7f55a2dc..815055c224 100644 --- a/sonic_package_manager/service_creator/creator.py +++ b/sonic_package_manager/service_creator/creator.py @@ -283,6 +283,7 @@ def generate_container_mgmt(self, package: Package): 'docker_container_name': name, 'docker_image_id': image_id, 'docker_image_name': package.entry.repository, + 'docker_image_tag': package.entry.tag, 'docker_image_run_opt': run_opt, 'sonic_asic_platform': sonic_asic_platform } diff --git a/sonic_package_manager/source.py b/sonic_package_manager/source.py index 2a0f07b0f1..37c6cb3928 100644 --- a/sonic_package_manager/source.py +++ b/sonic_package_manager/source.py @@ -51,6 +51,11 @@ def install(self, package: Package): image = self.install_image(package) package.entry.image_id = image.id + if image.tags: + package.entry.tag = image.tags[0] + else: + package.entry.tag = image.id + # if no repository is defined for this package # get repository from image if not package.repository: diff --git a/tests/sonic_package_manager/conftest.py b/tests/sonic_package_manager/conftest.py index 3d6beae9ff..e8c616d3f0 100644 --- a/tests/sonic_package_manager/conftest.py +++ b/tests/sonic_package_manager/conftest.py @@ -26,16 +26,17 @@ def mock_docker_api(): @dataclass class Image: id: str + tags: list[str] @property def attrs(self): return {'RepoTags': [self.id]} def pull(repo, ref): - return Image(f'{repo}:{ref}') + return Image(f'{repo}:{ref}', [ref]) def load(filename): - return Image(filename) + return Image(filename, ["latest"]) docker.pull = MagicMock(side_effect=pull) docker.load = MagicMock(side_effect=load) diff --git a/tests/sonic_package_manager/test_database.py b/tests/sonic_package_manager/test_database.py index 3ae8ec5ad0..2b8380835d 100644 --- a/tests/sonic_package_manager/test_database.py +++ b/tests/sonic_package_manager/test_database.py @@ -2,7 +2,7 @@ import pytest -from sonic_package_manager.database import PackageEntry +from sonic_package_manager.database import PackageEntry, package_from_dict from sonic_package_manager.errors import ( PackageNotFoundError, PackageAlreadyExistsError, @@ -87,3 +87,48 @@ def test_database_remove_package_built_in(fake_db): match='Package swss is built-in, ' 'cannot remove it'): fake_db.remove_package('swss') + + +def test_package_from_dict(): + """Test converting dictionary to PackageEntry object.""" + package_info = { + 'repository': 'test-repo', + 'description': 'Test package description', + 'default-reference': '1.0.0', + 'installed-version': '1.0.0', + 'installed': True, + 'built-in': False, + 'image-id': 'abc123', + 'tag': 'latest' + } + + package = package_from_dict('test-package', package_info) + + assert package.name == 'test-package' + assert package.repository == 'test-repo' + assert package.description == 'Test package description' + assert package.default_reference == '1.0.0' + assert package.version == Version.parse('1.0.0') + assert package.installed is True + assert package.built_in is False + assert package.image_id == 'abc123' + assert package.tag == 'latest' + + +def test_package_from_dict_minimal(): + """Test converting minimal dictionary to PackageEntry object.""" + package_info = { + 'repository': 'test-repo' + } + + package = package_from_dict('test-package', package_info) + + assert package.name == 'test-package' + assert package.repository == 'test-repo' + assert package.description is None + assert package.default_reference is None + assert package.version is None + assert package.installed is False + assert package.built_in is False + assert package.image_id is None + assert package.tag is None diff --git a/tests/sonic_package_manager/test_manager.py b/tests/sonic_package_manager/test_manager.py index 26e838ce6d..5c236dc869 100644 --- a/tests/sonic_package_manager/test_manager.py +++ b/tests/sonic_package_manager/test_manager.py @@ -2,7 +2,7 @@ import re import unittest -from unittest.mock import Mock, call, patch, mock_open +from unittest.mock import Mock, call, patch, mock_open, MagicMock import pytest import sonic_package_manager @@ -600,3 +600,30 @@ def test_download_file_sftp(package_manager): username="admin", password="test_password" ) + + +def test_installation_from_file_no_tags(package_manager, mock_docker_api, sonic_fs): + # Override the load function to return an image without tags + def load_no_tags(filename): + class Image: + def __init__(self, id): + self.id = id + self.tags = [] + + @property + def attrs(self): + return {'RepoTags': []} + + return Image(filename) + + mock_docker_api.load = MagicMock(side_effect=load_no_tags) + + sonic_fs.create_file('Azure/docker-test:1.6.0') + package_manager.install(tarball='Azure/docker-test:1.6.0') + + # Verify the image was loaded + mock_docker_api.load.assert_called_once_with('Azure/docker-test:1.6.0') + + # Get the package from the database and verify the tag was set to the image ID + package = package_manager.database.get_package('test-package') + assert package.tag == 'Azure/docker-test:1.6.0'