130 lines
5.0 KiB
Python
130 lines
5.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
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 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):
|
|
self.name = name
|
|
self.version = None
|
|
self.meta = {}
|
|
self.containers = []
|
|
if load_from_repo:
|
|
self.set_definition(repo_local.get_app(name))
|
|
|
|
def set_definition(self, definition):
|
|
self.version = definition['version']
|
|
self.meta = definition['meta']
|
|
self.containers = [Container(container) for container in definition['containers']]
|
|
|
|
def get_definition(self):
|
|
return {
|
|
'version': self.version,
|
|
'meta': self.meta.copy(),
|
|
'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)
|
|
files = repo_publish.TarSizeCounter()
|
|
archive_path = os.path.join(PUB_APPS_DIR, f'{self.name}.tar.xz')
|
|
with tarfile.open(archive_path, 'w:xz') as tar:
|
|
for content in ('install', 'install.sh', 'update', 'update.sh', 'uninstall', 'uninstall.sh'):
|
|
content_path = os.path.join(builddir, content)
|
|
if os.path.exists(content_path):
|
|
tar.add(content_path, os.path.join(self.name, content), filter=files.add_file)
|
|
with open(filename) as f:
|
|
definition = json.load(f)
|
|
definition['size'] = files.size
|
|
definition['dlsize'] = os.path.getsize(archive_path)
|
|
definition['hash'] = repo_publish.sign_file(archive_path).hex()
|
|
repo_publish.register_app(self.name, definition)
|
|
return (definition['size'], definition['dlsize'])
|
|
|
|
def unpublish(self):
|
|
repo_publish.unregister_app(self.name)
|
|
archive_path = os.path.join(PUB_APPS_DIR, f'{self.name}.tar.xz')
|
|
try:
|
|
os.unlink(archive_path)
|
|
except FileNotFoundError:
|
|
pass
|