Store full image definitons
This commit is contained in:
parent
a131948826
commit
ac596a6dc1
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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('/'))
|
||||
|
Loading…
Reference in New Issue
Block a user