From ac596a6dc1b5287f3aaa7dafe840e566aea33cd8 Mon Sep 17 00:00:00 2001 From: Disassembler Date: Fri, 7 Feb 2020 15:12:27 +0100 Subject: [PATCH] Store full image definitons --- usr/bin/spoc-container | 3 ++- usr/bin/spoc-image | 11 ++++---- usr/lib/python3.8/spoc/image.py | 20 +++------------ usr/lib/python3.8/spoc/imagebuilder.py | 35 ++++++++++++++++++-------- 4 files changed, 36 insertions(+), 33 deletions(-) diff --git a/usr/bin/spoc-container b/usr/bin/spoc-container index 4a8bff2..16805fc 100644 --- a/usr/bin/spoc-container +++ b/usr/bin/spoc-container @@ -66,7 +66,8 @@ def modify_container(container, depends, mounts, envs, uid, gid, cmd, cwd, ready setattr(container, member, value) def create(container_name, image_name, depends, mounts, env, uid, gid, cmd, cwd, ready, halt, autostart): - container = Image(image_name).get_container(container_name) + container = Container(container_name, False) + container.set_definition(Image(image_name).get_definition()) modify_container(container, depends, mounts, env, uid, gid, cmd, cwd, ready, halt, autostart) container.create() diff --git a/usr/bin/spoc-image b/usr/bin/spoc-image index 142186e..697040c 100644 --- a/usr/bin/spoc-image +++ b/usr/bin/spoc-image @@ -6,6 +6,7 @@ import argparse from spoc import repo_local from spoc import repo_publish from spoc.image import Image +from spoc.imagebuilder import ImageBuilder from spoc.utils import readable_size ACTION_LIST = 1 @@ -44,11 +45,11 @@ def delete(image_name): def build(filename, force, do_publish): # Check if a build is needed and attempt to build the image from image file image_name = get_image_name(filename) - image = Image(image_name, False) - if force or image.name not in repo_local.get_images(): + if force or image_name not in repo_local.get_images(): + image = Image(image_name, False) image.delete() print(f'Building image {image_name} from file {filename}') - image.build(filename) + ImageBuilder().build(image, filename) print(f'Image {image_name} built successfully') # If publishing was requested, force publish after successful build force = True @@ -59,8 +60,8 @@ def build(filename, force, do_publish): def publish(image_name, force): # Check if publishing is needed and attempt to publish the image - image = Image(image_name) - if force or image.name not in repo_publish.get_images(): + if force or image_name not in repo_publish.get_images(): + image = Image(image_name, False) image.unpublish() print(f'Publishing image {image_name}') image.publish() diff --git a/usr/lib/python3.8/spoc/image.py b/usr/lib/python3.8/spoc/image.py index 7157052..28190f3 100644 --- a/usr/lib/python3.8/spoc/image.py +++ b/usr/lib/python3.8/spoc/image.py @@ -8,18 +8,16 @@ from . import crypto from . import repo_local from . import repo_publish from . import utils -from .container import Container -from .imagebuilder import ImageBuilder from .paths import LAYERS_DIR, PUB_LAYERS_DIR -DEFINITION_MEMBERS = {'parent', 'env', 'uid', 'gid', 'cmd', 'cwd', 'ready', 'halt', 'size', 'dlsize', 'hash'} +DEFINITION_MEMBERS = {'layers', 'env', 'uid', 'gid', 'cmd', 'cwd', 'ready', 'halt', 'size', 'dlsize', 'hash'} class Image: def __init__(self, name, load_from_repo=True): self.name = name self.layer_path = os.path.join(LAYERS_DIR, name) self.archive_path = os.path.join(PUB_LAYERS_DIR, f'{name}.tar.xz') - self.parent = None + self.layers = [name] self.env = {} self.uid = None self.gid = None @@ -45,22 +43,12 @@ class Image: definition[key] = value return definition - def get_container(self, container_name): - container = Image(self.parent).get_container(container_name) if self.parent else Container(container_name, False) - container.layers.append(self.name) - container.env.update(self.env) - for member in ('uid', 'gid', 'cmd', 'cwd', 'ready', 'halt'): - value = getattr(self, member) - if value: - setattr(container, member, value) - return container - - def build(self, filename): + def create(self, imagebuilder): # 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) + imagebuilder.process_file() repo_local.register_image(self.name, self.get_definition()) def delete(self): diff --git a/usr/lib/python3.8/spoc/imagebuilder.py b/usr/lib/python3.8/spoc/imagebuilder.py index 43e855e..411e9d0 100644 --- a/usr/lib/python3.8/spoc/imagebuilder.py +++ b/usr/lib/python3.8/spoc/imagebuilder.py @@ -7,6 +7,7 @@ import subprocess import tempfile from .container import Container +from .image import Image from .paths import VOLUME_DIR class ImageBuilder: @@ -16,7 +17,10 @@ class ImageBuilder: self.filename = filename self.script_eof = None self.script_lines = [] - with open(filename, 'r') as f: + self.image.create(self) + + def process_file(self): + with open(self.filename, 'r') as f: for line in f: self.process_line(line.strip()) @@ -37,17 +41,18 @@ class ImageBuilder: self.script_lines = [] self.script_eof = args elif 'FROM' == directive: - # Set the name of image from which this one inherits - self.image.parent = args + # Set the values of image from which this one inherits + self.image.set_definition(Image(args).get_definition()) + self.image.layers.append(self.image.name) elif 'COPY' == directive: srcdst = args.split() self.copy_files(srcdst[0], srcdst[1] if len(srcdst) > 1 else '') elif 'ENV' == directive: - # Sets environment records - self.image.env.append(args.split(None, 1)) + # Sets/unsets environment variable + self.set_env(*args.split(None, 1)) elif 'USER' == directive: # Sets init UID / GID - self.image.uid,self.image.gid = args.split() #TODO: Get UID/GID by name + get GIT automatically as primary UID group + self.image.uid,self.image.gid = args.split() #TODO: Get UID/GID by name + get GID automatically as primary UID group if only UID is given elif 'CMD' == directive: # Sets init command self.image.cmd = args @@ -65,23 +70,31 @@ class ImageBuilder: # Creates a temporary container, runs a script in its namespace, and stores the files modified by it as part of the layer # TODO: Run the script as the correct user if UID/GID has been already set - doesn't this the LXC init do automatically? os.makedirs(VOLUME_DIR, 0o755, True) - script_fd, script_path = tempfile.mkstemp(suffix='.sh', dir=VOLUME_DIR, text=True) + script_fd, script_path = tempfile.mkstemp(suffix='.sh', dir=self.image.layer_path, text=True) script_name = os.path.basename(script_path) script_lines = '\n'.join(script_lines) with os.fdopen(script_fd, 'w') as script: script.write(f'#!/bin/sh\nset -ev\n\n{script_lines}\n') os.chmod(script_path, 0o700) os.chown(script_path, 100000, 100000) - # Create a temporary container from the current image definition - container = self.image.get_container(self.image.name) + # Create a temporary container from the current image definition and execute the script within the container + container = Container(self.image.name, False) + container.set_definition(self.image.get_definition()) container.build = True - # Add the script file as a mount and execute it withing the container - container.mounts[script_name] = (os.path.basename(script_path), False) container.create() container.execute(['/bin/sh', '-lc', os.path.join('/', script_name)], True) container.destroy() os.unlink(script_path) + def set_env(self, key, value=None): + if value: + self.image.env[key] = value + else: + try: + del self.image.env[key] + except KeyError: + pass + def copy_files(self, src, dst): # Copy files from the host or download them from a http(s) URL dst = os.path.join(self.image.layer_path, dst.lstrip('/'))