Skip to content

Commit

Permalink
[sonic-package-manager] add support for multiple CLI plugin files
Browse files Browse the repository at this point in the history
Signed-off-by: Stepan Blyschak <[email protected]>
  • Loading branch information
stepanblyschak committed Mar 21, 2023
1 parent 05fa751 commit f2fdf62
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 18 deletions.
25 changes: 14 additions & 11 deletions sonic_package_manager/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,17 +160,18 @@ def get_cli_plugin_directory(command: str) -> str:
return plugins_pkg_path


def get_cli_plugin_path(package: Package, command: str) -> str:
def get_cli_plugin_path(package: Package, index: int, command: str) -> str:
""" Returns a path where to put CLI plugin code.
Args:
package: Package to generate this path for.
index: Index of a cli plugin
command: SONiC command: "show"/"config"/"clear".
Returns:
Path generated for this package.
"""

plugin_module_file = package.name + '.py'
plugin_module_file = f'{package.name}_{index}.py'
return os.path.join(get_cli_plugin_directory(command), plugin_module_file)


Expand Down Expand Up @@ -978,19 +979,21 @@ def _uninstall_cli_plugins(self, package: Package):
self._uninstall_cli_plugin(package, command)

def _install_cli_plugin(self, package: Package, command: str):
image_plugin_path = package.manifest['cli'][command]
if not image_plugin_path:
image_plugins = package.manifest['cli'][command]
if not image_plugins:
return
host_plugin_path = get_cli_plugin_path(package, command)
self.docker.extract(package.entry.image_id, image_plugin_path, host_plugin_path)
for index, image_plugin_path in enumerate(image_plugins):
host_plugin_path = get_cli_plugin_path(package, index, command)
self.docker.extract(package.entry.image_id, image_plugin_path, host_plugin_path)

def _uninstall_cli_plugin(self, package: Package, command: str):
image_plugin_path = package.manifest['cli'][command]
if not image_plugin_path:
image_plugins = package.manifest['cli'][command]
if not image_plugins:
return
host_plugin_path = get_cli_plugin_path(package, command)
if os.path.exists(host_plugin_path):
os.remove(host_plugin_path)
for index, _ in enumerate(image_plugins):
host_plugin_path = get_cli_plugin_path(package, index, command)
if os.path.exists(host_plugin_path):
os.remove(host_plugin_path)

@staticmethod
def get_manager() -> 'PackageManager':
Expand Down
26 changes: 23 additions & 3 deletions sonic_package_manager/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,26 @@ def marshal(self, value):
def unmarshal(self, value):
return value

@dataclass
class ListMarshaller(Marshaller):
""" Returns a list. In case input is of other type it returns a list containing that single value. """

type: type

def marshal(self, value):
if isinstance(value, list):
for item in value:
if not isinstance(item, self.type):
raise ManifestError(f'{value} has items not of type {self.type.__name__}')
return value
elif isinstance(value, self.type):
return [value]
else:
raise ManifestError(f'{value} is not of type {self.type.__name__}')

def unmarshal(self, value):
return value

@dataclass
class ManifestNode(Marshaller, ABC):
"""
Expand Down Expand Up @@ -207,9 +227,9 @@ def unmarshal(self, value):
])),
ManifestRoot('cli', [
ManifestField('mandatory', DefaultMarshaller(bool), False),
ManifestField('show', DefaultMarshaller(str), ''),
ManifestField('config', DefaultMarshaller(str), ''),
ManifestField('clear', DefaultMarshaller(str), ''),
ManifestField('show', ListMarshaller(str), []),
ManifestField('config', ListMarshaller(str), []),
ManifestField('clear', ListMarshaller(str), []),
ManifestField('auto-generate-show', DefaultMarshaller(bool), False),
ManifestField('auto-generate-config', DefaultMarshaller(bool), False),
])
Expand Down
25 changes: 21 additions & 4 deletions tests/sonic_package_manager/test_manager.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#!/usr/bin/env python

import re
from unittest.mock import Mock, call
from unittest.mock import Mock, call, patch

import pytest

import sonic_package_manager
from sonic_package_manager.errors import *
from sonic_package_manager.version import Version

Expand Down Expand Up @@ -161,9 +162,25 @@ def test_installation_base_os_constraint_satisfied(package_manager, fake_metadat
def test_installation_cli_plugin(package_manager, fake_metadata_resolver, anything):
manifest = fake_metadata_resolver.metadata_store['Azure/docker-test']['1.6.0']['manifest']
manifest['cli']= {'show': '/cli/plugin.py'}
package_manager._install_cli_plugins = Mock()
package_manager.install('test-package')
package_manager._install_cli_plugins.assert_called_once_with(anything)
with patch('sonic_package_manager.manager.get_cli_plugin_directory') as get_dir_mock:
get_dir_mock.return_value = '/'
package_manager.install('test-package')
package_manager.docker.extract.assert_called_once_with(anything, '/cli/plugin.py', '/test-package_0.py')


def test_installation_multiple_cli_plugin(package_manager, fake_metadata_resolver, anything):
manifest = fake_metadata_resolver.metadata_store['Azure/docker-test']['1.6.0']['manifest']
manifest['cli']= {'show': ['/cli/plugin.py', '/cli/plugin2.py']}
with patch('sonic_package_manager.manager.get_cli_plugin_directory') as get_dir_mock:
get_dir_mock.return_value = '/'
package_manager.install('test-package')
package_manager.docker.extract.assert_has_calls(
[
call(anything, '/cli/plugin.py', '/test-package_0.py'),
call(anything, '/cli/plugin2.py', '/test-package_1.py'),
],
any_order=True,
)


def test_installation_cli_plugin_skipped(package_manager, fake_metadata_resolver, anything):
Expand Down

0 comments on commit f2fdf62

Please sign in to comment.