From c2cd5b12a017c248b48d9f83ce6322c9064e6d59 Mon Sep 17 00:00:00 2001 From: Disassembler Date: Sun, 2 Jan 2022 10:15:27 +0100 Subject: [PATCH] Add support for installation from definition file --- src/spoc/__init__.py | 12 ++++++------ src/spoc/app.py | 23 +++++++++++++++-------- src/spoc_cli.py | 18 ++++++++++++------ tests/test_app.py | 38 +++++++++++++++++++++++++++++--------- tests/test_cli.py | 14 +++++++------- tests/test_spoc.py | 15 ++++++++++++--- 6 files changed, 81 insertions(+), 39 deletions(-) diff --git a/src/spoc/__init__.py b/src/spoc/__init__.py index a1231f2..43adc87 100644 --- a/src/spoc/__init__.py +++ b/src/spoc/__init__.py @@ -40,20 +40,20 @@ def list_updates(): return dict(sorted(apps.items())) @locked() -def install(app_name): +def install(app_name, from_file=None): if app_name in podman.get_apps(): raise AppAlreadyInstalledError(app_name) - if app_name not in repo.get_apps(): + if not from_file and app_name not in repo.get_apps(): raise AppNotInRepoError(app_name) - app.install(app_name) + app.install(app_name, from_file=from_file) @locked() -def update(app_name): +def update(app_name, from_file=None): if app_name not in podman.get_apps(): raise AppNotInstalledError(app_name) - if app_name not in list_updates(): + if not from_file and app_name not in list_updates(): raise AppNotUpdateableError(app_name) - app.update(app_name) + app.update(app_name, from_file=from_file) @locked() def uninstall(app_name): diff --git a/src/spoc/app.py b/src/spoc/app.py index b287a8a..12c72af 100644 --- a/src/spoc/app.py +++ b/src/spoc/app.py @@ -1,4 +1,5 @@ import os +import json from . import autostart from . import config @@ -11,8 +12,14 @@ class App: self.app_name = app_name self.env_file = os.path.join(config.DATA_DIR, f'{app_name}.env') - def install(self, is_update=False): - definition = repo.get_apps()[self.app_name] + def get_definition(self, from_file=None): + if from_file: + with open(from_file, encoding='utf-8') as f: + return json.load(f) + return repo.get_apps()[self.app_name] + + def install(self, is_update=False, from_file=None): + definition = self.get_definition(from_file) version = definition['version'] containers = definition['containers'] @@ -44,8 +51,8 @@ class App: self.create_pod(version) self.create_containers(containers) - def update(self): - self.install(is_update=True) + def update(self, from_file=None): + self.install(is_update=True, from_file=from_file) def uninstall(self): autostart.set_app(self.app_name, False) @@ -124,11 +131,11 @@ class App: podman.create_container(self.app_name, name, image, env_file=self.env_file, volumes=volumes, requires=requires, hosts=hosts) -def install(app_name): - App(app_name).install() +def install(app_name, from_file=None): + App(app_name).install(from_file=from_file) -def update(app_name): - App(app_name).update() +def update(app_name, from_file=None): + App(app_name).update(from_file=from_file) def uninstall(app_name): App(app_name).uninstall() diff --git a/src/spoc_cli.py b/src/spoc_cli.py index cd0173a..75e27b3 100644 --- a/src/spoc_cli.py +++ b/src/spoc_cli.py @@ -42,11 +42,11 @@ def listing(list_type): for app_name, app_version in apps.items(): print(app_name, app_version) -def install(app_name): - spoc.install(app_name) +def install(app_name, from_file): + spoc.install(app_name, from_file) -def update(app_name): - spoc.update(app_name) +def update(app_name, from_file): + spoc.update(app_name, from_file) def uninstall(app_name): spoc.uninstall(app_name) @@ -93,10 +93,16 @@ def parse_args(args=None): parser_install = subparsers.add_parser('install') parser_install.set_defaults(action=install) parser_install.add_argument('app', help='Name of the application to install') + parser_install.add_argument('--from-file', + help='Filename containing the application definition ' \ + 'to be used instead of online repository') parser_update = subparsers.add_parser('update') parser_update.set_defaults(action=update) parser_update.add_argument('app', help='Name of the application to update') + parser_update.add_argument('--from-file', + help='Filename containing the application definition ' \ + 'to be used instead of online repository') parser_uninstall = subparsers.add_parser('uninstall') parser_uninstall.set_defaults(action=uninstall) @@ -143,9 +149,9 @@ def main(): # pylint: disable=too-many-branches if args.action is listing: listing(args.type) elif args.action is install: - install(args.app) + install(args.app, args.from_file) elif args.action is update: - update(args.app) + update(args.app, args.from_file) elif args.action is uninstall: uninstall(args.app) elif args.action is start: diff --git a/tests/test_app.py b/tests/test_app.py index 0689b38..26097a7 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -28,6 +28,26 @@ def test_init(): assert instance.env_file == os.path.join(config.DATA_DIR, 'someapp.env') @patch('spoc.repo.get_apps', return_value=MOCK_REPODATA) +def test_get_definition(repo_get_apps): + instance = app.App('someapp') + definition = instance.get_definition() + + assert definition == MOCK_REPODATA['someapp'] + + repo_get_apps.assert_called_once() + +@patch('spoc.repo.get_apps') +@patch('builtins.open', new_callable=mock_open, read_data=json.dumps(MOCK_REPODATA['someapp'])) +def test_get_definition_from_file(file_open, repo_get_apps): + instance = app.App('someapp') + definition = instance.get_definition('somefile') + + assert definition == MOCK_REPODATA['someapp'] + + file_open.assert_called_once_with('somefile', encoding='utf-8') + repo_get_apps.assert_not_called() + +@patch('spoc.app.App.get_definition', return_value=MOCK_REPODATA['someapp']) @patch('spoc.app.App.get_existing_volumes', return_value=set('somevol')) @patch('spoc.app.App.remove_volumes') @patch('spoc.app.App.create_volumes') @@ -37,11 +57,11 @@ def test_init(): @patch('spoc.app.App.create_containers') def test_install(create_containers, create_pod, write_env_vars, #pylint: disable=too-many-arguments read_env_vars, create_volumes, remove_volumes, - get_existing_volumes, repo_get_apps): + get_existing_volumes, get_definition): instance = app.App('someapp') instance.install() - repo_get_apps.assert_called_once() + get_definition.assert_called_once() get_existing_volumes.assert_called_once() remove_volumes.assert_called_once_with(set('somevol')) create_volumes.assert_called_once_with(set(('migrate', 'storage', 'uploads', 'postgres-data'))) @@ -50,7 +70,7 @@ def test_install(create_containers, create_pod, write_env_vars, #pylint: disable create_pod.assert_called_once_with('0.23.5-210416') create_containers.assert_called_once_with(MOCK_REPODATA['someapp']['containers']) -@patch('spoc.repo.get_apps', return_value=MOCK_REPODATA) +@patch('spoc.app.App.get_definition', return_value=MOCK_REPODATA['someapp']) @patch('spoc.app.App.get_existing_volumes', return_value=set(('somevol', 'migrate', 'storage'))) @patch('spoc.app.App.remove_volumes') @patch('spoc.app.App.create_volumes') @@ -60,11 +80,11 @@ def test_install(create_containers, create_pod, write_env_vars, #pylint: disable @patch('spoc.app.App.create_containers') def test_update(create_containers, create_pod, write_env_vars, #pylint: disable=too-many-arguments read_env_vars, create_volumes, remove_volumes, - get_existing_volumes, repo_get_apps): + get_existing_volumes, get_definition): instance = app.App('someapp') - instance.update() + instance.update(from_file='somefile') - repo_get_apps.assert_called_once() + get_definition.assert_called_once() get_existing_volumes.assert_called_once() remove_volumes.assert_called_once_with(set(('somevol',))) create_volumes.assert_called_once_with(set(('uploads', 'postgres-data'))) @@ -229,14 +249,14 @@ def test_module_install(instance): app.install('someapp') instance.assert_called_once_with('someapp') - instance.return_value.install.assert_called_once() + instance.return_value.install.assert_called_once_with(from_file=None) @patch('spoc.app.App') def test_module_update(instance): - app.update('someapp') + app.update('someapp', from_file='somefile') instance.assert_called_once_with('someapp') - instance.return_value.update.assert_called_once_with() + instance.return_value.update.assert_called_once_with(from_file='somefile') @patch('spoc.app.App') def test_module_uninstall(instance): diff --git a/tests/test_cli.py b/tests/test_cli.py index ace7af8..9f7bd41 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -97,15 +97,15 @@ def test_listing_invalid(list_updates, list_online, list_installed, capsys): @patch('spoc.install') def test_install(install): - spoc_cli.install('someapp') + spoc_cli.install('someapp', 'somefile') - install.assert_called_once_with('someapp') + install.assert_called_once_with('someapp', 'somefile') @patch('spoc.update') def test_update(update): - spoc_cli.update('someapp') + spoc_cli.update('someapp', None) - update.assert_called_once_with('someapp') + update.assert_called_once_with('someapp', None) @patch('spoc.uninstall') def test_uninstall(uninstall): @@ -212,14 +212,14 @@ def test_main_listing_online(listing): def test_main_install(install): spoc_cli.main() - install.assert_called_once_with('someapp') + install.assert_called_once_with('someapp', None) -@patch('sys.argv', ['foo', 'update', 'someapp']) +@patch('sys.argv', ['foo', 'update', '--from-file', 'somefile', 'someapp']) @patch('spoc_cli.update') def test_main_update(update): spoc_cli.main() - update.assert_called_once_with('someapp') + update.assert_called_once_with('someapp', 'somefile') @patch('sys.argv', ['foo', 'uninstall', 'someapp']) @patch('spoc_cli.uninstall') diff --git a/tests/test_spoc.py b/tests/test_spoc.py index b136cb2..1424647 100644 --- a/tests/test_spoc.py +++ b/tests/test_spoc.py @@ -66,11 +66,20 @@ def test_list_updates(podman_get_apps, repo_get_apps): @patch('spoc.app.install') def test_install(app_install, podman_get_apps, repo_get_apps): spoc.install.__wrapped__('someapp') - #spoc.install('someapp') podman_get_apps.assert_called_once() repo_get_apps.assert_called_once() - app_install.assert_called_once_with('someapp') + app_install.assert_called_once_with('someapp', from_file=None) + +@patch('spoc.repo.get_apps') +@patch('spoc.podman.get_apps', return_value={}) +@patch('spoc.app.install') +def test_install_from_file(app_install, podman_get_apps, repo_get_apps): + spoc.install.__wrapped__('someapp', 'somefile') + + podman_get_apps.assert_called_once() + repo_get_apps.assert_not_called() + app_install.assert_called_once_with('someapp', from_file='somefile') @patch('spoc.repo.get_apps', return_value={'someapp': {'version': '0.1'}}) @patch('spoc.podman.get_apps', return_value={'someapp': '0.1'}) @@ -102,7 +111,7 @@ def test_update(app_update, podman_get_apps, list_updates): podman_get_apps.assert_called_once() list_updates.assert_called_once() - app_update.assert_called_once_with('someapp') + app_update.assert_called_once_with('someapp', from_file=None) @patch('spoc.list_updates', return_value={}) @patch('spoc.podman.get_apps', return_value={})