diff --git a/usr/bin/spoc-app b/usr/bin/spoc-app index d5df60b..a554908 100644 --- a/usr/bin/spoc-app +++ b/usr/bin/spoc-app @@ -9,7 +9,8 @@ from spoc import repo_local from spoc import repo_online from spoc import repo_publish from spoc.app import App -from spoc.cli import readable_size +from spoc.cli import ActionQueue, readable_size +from spoc.image import Image def listing(list_type): if list_type == 'installed': @@ -24,23 +25,33 @@ def listing(list_type): for app in apps: print(app) -def install(): - raise NotImplementedException() +def install(app_name): + queue = ActionQueue() + required_images = [] + for container in repo_online.get_app(app_name)['containers'].values(): + required_images.extend(repo_online.get_image(container['image'])['layers']) + local_images = repo_local.get_images() + for layer in set(required_images): + if layer not in local_images: + queue.download_image(Image(layer, False)) + queue.download_app(App(app_name, False)) + queue.process() -def upgrade(): - raise NotImplementedException() +def update(app_name): + App(app_name).update() -def uninstall(): - raise NotImplementedException() +def uninstall(app_name): + App(app_name).uninstall() -def start(): - raise NotImplementedException() +def start(app_name): + App(app_name).start() -def stop(): - raise NotImplementedException() +def stop(app_name): + App(app_name).stop() -def status(): - raise NotImplementedException() +def status(app_name): + for container,status in App(app_name).status(): + print(f'{container}: {status}') def publish(filename, force): app_name = os.path.basename(os.path.dirname(os.path.abspath(filename))) @@ -69,9 +80,9 @@ parser_install = subparsers.add_parser('install') parser_install.set_defaults(action=install) parser_install.add_argument('app') -parser_upgrade = subparsers.add_parser('upgrade') -parser_upgrade.set_defaults(action=upgrade) -parser_upgrade.add_argument('app') +parser_update = subparsers.add_parser('update') +parser_update.set_defaults(action=update) +parser_update.add_argument('app') parser_uninstall = subparsers.add_parser('uninstall') parser_uninstall.set_defaults(action=uninstall) @@ -104,8 +115,8 @@ if args.action is listing: listing(args.type) elif args.action is install: install(args.app) -elif args.action is upgrade: - upgrade(args.app) +elif args.action is update: + update(args.app) elif args.action is uninstall: uninstall(args.app) elif args.action is start: diff --git a/usr/lib/python3.8/spoc/app.py b/usr/lib/python3.8/spoc/app.py index 403463b..3ed1181 100644 --- a/usr/lib/python3.8/spoc/app.py +++ b/usr/lib/python3.8/spoc/app.py @@ -2,12 +2,16 @@ import json import os +import subprocess import tarfile +import urllib.parse from . import repo_local +from . import repo_online from . import repo_publish -from .config import PUB_APPS_DIR +from .config import APPS_DIR, ONLINE_APPS_URL, PUB_APPS_DIR, TMP_APPS_DIR, LAYERS_DIR, VOLUMES_DIR from .container import Container +from .image import Image class App: def __init__(self, name, load_from_repo=True): @@ -30,6 +34,74 @@ class App: 'containers': [container.name for container in self.containers] } + def download(self, observer): + os.makedirs(TMP_APPS_DIR, 0o700, True) + archive_url = urllib.parse.urljoin(ONLINE_APPS_URL, f'{self.name}.tar.xz') + archive_path = os.path.join(TMP_APPS_DIR, f'{self.name}.tar.xz') + definition = repo_online.get_app(self.name) + observer.units_total = definition['dlsize'] + repo_online.download_archive(archive_url, archive_path, definition['hash'], observer) + + def unpack_downloaded(self, observer): + archive_path = os.path.join(TMP_APPS_DIR, f'{self.name}.tar.xz') + definition = repo_online.get_app(self.name) + observer.units_total = definition['size'] + repo_online.unpack_archive(archive_path, APPS_DIR, definition['hash'], observer) + + def run_script(self, action): + # Runs script for an app, if the script is present + app_dir = os.path.join(APPS_DIR, self.name) + script_dir = os.path.join(app_dir, action) + script_path = os.path.join(app_dir, f'{script_dir}.sh') + if os.path.exists(script_path): + # Run the script in its working directory, if there is one, so it doesn't have to figure out paths to packaged files + env = os.environ.copy() + env['LAYERS_DIR'] = LAYERS_DIR + env['VOLUMES_DIR'] = VOLUMES_DIR + cwd = script_dir if os.path.exists(script_dir) else app_dir + subprocess.run(script_path, cwd=cwd, env=env, check=True) + + def create_container(self, name, definition): + container = Container(name, False) + container.set_definition(Image(definition['image']).get_definition()) + if 'depends' in definition: + container.depends = definition['depends'] + if 'env' in definition: + container.env.update(definition['env']) + if 'mounts' in definition: + container.mounts.update(definition['mounts']) + container.create() + self.containers.append(container) + + def install(self): + definition = repo_online.get_app(self.name) + self.version = definition['version'] + self.meta = definition['meta'] + self.run_script('uninstall') + # Build containers + for container,container_defintion in definition['containers'].items(): + self.create_container(container, container_defintion) + # Run install script and register the app + self.run_script('install') + repo_local.register_app(self.name, self.get_definition()) + + def update(self): + raise NotImplementedError() + + def uninstall(self): + raise NotImplementedError() + + def start(self): + for container in self.containers: + container.start() + + def stop(self): + for container in self.containers: + container.stop() + + def status(self): + return {container.name:container.get_state() for container in sorted(self.containers)} + def publish(self, filename): builddir = os.path.dirname(filename) os.makedirs(PUB_APPS_DIR, 0o700, True) diff --git a/usr/lib/python3.8/spoc/cli.py b/usr/lib/python3.8/spoc/cli.py index e097233..5531442 100644 --- a/usr/lib/python3.8/spoc/cli.py +++ b/usr/lib/python3.8/spoc/cli.py @@ -17,6 +17,11 @@ class ActionQueue: def delete_image(self, image): self.queue.append(ActionItem(f'Deleting image {image.name}', image.delete, False)) + def download_app(self, app): + self.queue.append(ActionItem(f'Downloading application {app.name}', app.download)) + self.queue.append(ActionItem(f'Unpacking application {app.name}', app.unpack_downloaded)) + self.queue.append(ActionItem(f'Installing application {app.name}', app.install, False)) + def process(self): index = 0 queue_length = len(self.queue) diff --git a/usr/lib/python3.8/spoc/image.py b/usr/lib/python3.8/spoc/image.py index c5c72fb..5344389 100644 --- a/usr/lib/python3.8/spoc/image.py +++ b/usr/lib/python3.8/spoc/image.py @@ -9,7 +9,7 @@ import urllib.parse from . import repo_local from . import repo_online from . import repo_publish -from .config import LAYERS_DIR, PUB_LAYERS_DIR, ONLINE_LAYERS_URL, TMP_LAYERS_DIR +from .config import LAYERS_DIR, ONLINE_LAYERS_URL, PUB_LAYERS_DIR, TMP_LAYERS_DIR DEFINITION_MEMBERS = {'layers', 'env', 'uid', 'gid', 'cmd', 'cwd', 'ready', 'halt'} @@ -56,6 +56,22 @@ class Image: except FileNotFoundError: pass + def download(self, observer): + os.makedirs(TMP_LAYERS_DIR, 0o700, True) + archive_url = urllib.parse.urljoin(ONLINE_LAYERS_URL, f'{self.name}.tar.xz') + archive_path = os.path.join(TMP_LAYERS_DIR, f'{self.name}.tar.xz') + definition = repo_online.get_image(self.name) + observer.units_total = definition['dlsize'] + repo_online.download_archive(archive_url, archive_path, definition['hash'], observer) + + def unpack_downloaded(self, observer): + archive_path = os.path.join(TMP_LAYERS_DIR, f'{self.name}.tar.xz') + definition = repo_online.get_image(self.name) + observer.units_total = definition['size'] + repo_online.unpack_archive(archive_path, LAYERS_DIR, definition['hash'], observer) + self.set_definition(definition) + repo_local.register_image(self.name, definition) + def publish(self): os.makedirs(PUB_LAYERS_DIR, 0o700, True) files = repo_publish.TarSizeCounter() @@ -76,19 +92,3 @@ class Image: os.unlink(archive_path) except FileNotFoundError: pass - - def download(self, observer): - os.makedirs(TMP_LAYERS_DIR, 0o700, True) - definition = repo_online.get_image(self.name) - observer.units_total = definition['dlsize'] - archive_url = urllib.parse.urljoin(ONLINE_LAYERS_URL, f'{self.name}.tar.xz') - archive_path = os.path.join(TMP_LAYERS_DIR, f'{self.name}.tar.xz') - repo_online.download_archive(archive_url, archive_path, definition['hash'], observer) - - def unpack_downloaded(self, observer): - definition = repo_online.get_image(self.name) - observer.units_total = definition['size'] - archive_path = os.path.join(TMP_LAYERS_DIR, f'{self.name}.tar.xz') - repo_online.unpack_archive(archive_path, LAYERS_DIR, definition['hash'], observer) - self.set_definition(definition) - repo_local.register_image(self.name, definition)