100 lines
3.9 KiB
Python
100 lines
3.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import copy
|
|
import os
|
|
import shutil
|
|
import tarfile
|
|
import urllib.parse
|
|
|
|
from . import config, repo_local, repo_online, repo_publish
|
|
|
|
DEFINITION_MEMBERS = {'layers', 'env', 'uid', 'gid', 'cmd', 'cwd', 'ready', 'halt'}
|
|
|
|
class Image:
|
|
def __init__(self, name, load_from_repo=True):
|
|
self.name = name
|
|
self.layer_path = os.path.join(config.LAYERS_DIR, name)
|
|
self.layers = [name]
|
|
self.env = {}
|
|
self.uid = None
|
|
self.gid = None
|
|
self.cmd = None
|
|
self.cwd = None
|
|
self.ready = None
|
|
self.halt = None
|
|
if load_from_repo:
|
|
self.set_definition(repo_local.get_image(name))
|
|
|
|
def set_definition(self, definition):
|
|
# Set attributes given by definition
|
|
for key in DEFINITION_MEMBERS.intersection(definition):
|
|
setattr(self, key, definition[key])
|
|
|
|
def get_definition(self):
|
|
# Return shallow copy of image definition as dictionary
|
|
definition = {}
|
|
for key in DEFINITION_MEMBERS:
|
|
value = getattr(self, key)
|
|
if value:
|
|
definition[key] = copy.copy(value)
|
|
return definition
|
|
|
|
def create(self, imagebuilder, filename):
|
|
# Build the container from image file and save to local repository
|
|
# Chown is possible only when the process is running as root, for user namespaces, see https://linuxcontainers.org/lxc/manpages/man1/lxc-usernsexec.1.html
|
|
os.makedirs(self.layer_path, 0o755, True)
|
|
os.chown(self.layer_path, 100000, 100000)
|
|
imagebuilder.build(self, filename)
|
|
repo_local.register_image(self.name, self.get_definition())
|
|
|
|
def delete(self, observer=None):
|
|
# Remove the layer from local repository and filesystem
|
|
repo_local.unregister_image(self.name)
|
|
try:
|
|
shutil.rmtree(self.layer_path)
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
def download(self, observer=None):
|
|
# Download the archive with layer data
|
|
os.makedirs(config.TMP_LAYERS_DIR, 0o700, True)
|
|
archive_url = urllib.parse.urljoin(config.ONLINE_LAYERS_URL, f'{self.name}.tar.xz')
|
|
archive_path = os.path.join(config.TMP_LAYERS_DIR, f'{self.name}.tar.xz')
|
|
definition = repo_online.get_image(self.name)
|
|
if observer:
|
|
observer.units_total = definition['dlsize']
|
|
repo_online.download_archive(archive_url, archive_path, definition['hash'], observer)
|
|
|
|
def unpack_downloaded(self, observer=None):
|
|
# Unpack downloaded archive with layer data
|
|
archive_path = os.path.join(config.TMP_LAYERS_DIR, f'{self.name}.tar.xz')
|
|
definition = repo_online.get_image(self.name)
|
|
if observer:
|
|
observer.units_total = definition['size']
|
|
repo_online.unpack_archive(archive_path, config.LAYERS_DIR, definition['hash'], observer)
|
|
self.set_definition(definition)
|
|
repo_local.register_image(self.name, definition)
|
|
|
|
def publish(self):
|
|
# Create layer archive and register to publish repository
|
|
os.makedirs(config.PUB_LAYERS_DIR, 0o755, True)
|
|
files = repo_publish.TarSizeCounter()
|
|
archive_path = os.path.join(config.PUB_LAYERS_DIR, f'{self.name}.tar.xz')
|
|
with tarfile.open(archive_path, 'w:xz') as tar:
|
|
tar.add(self.layer_path, self.name, filter=files.add_file)
|
|
definition = self.get_definition()
|
|
definition['size'] = files.size
|
|
definition['dlsize'] = os.path.getsize(archive_path)
|
|
definition['hash'] = repo_publish.sign_file(archive_path).hex()
|
|
repo_publish.register_image(self.name, definition)
|
|
return (definition['size'], definition['dlsize'])
|
|
|
|
def unpublish(self):
|
|
# Remove the layer from publish repository
|
|
repo_publish.unregister_image(self.name)
|
|
archive_path = os.path.join(config.PUB_LAYERS_DIR, f'{self.name}.tar.xz')
|
|
try:
|
|
os.unlink(archive_path)
|
|
except FileNotFoundError:
|
|
pass
|