#!/usr/bin/python3 import os import shutil import subprocess import sys LXC_ROOT = '/var/lib/lxc' CONFIG_TEMPLATE = '''# Image name lxc.uts.name = {image} # Network lxc.net.0.type = veth lxc.net.0.link = lxcbr0 lxc.net.0.flags = up lxc.net.0.ipv4.address = 172.17.0.254/16 lxc.net.0.ipv4.gateway = auto # Volumes lxc.rootfs.path = {rootfs} # Mounts lxc.mount.entry = /etc/hosts etc/hosts none bind 0 0 lxc.mount.entry = /etc/resolv.conf etc/resolv.conf none bind 0 0 {mounts} # Init lxc.init.cmd = {cmd} lxc.init.uid = {uid} lxc.init.gid = {gid} # Halt lxc.signal.halt = SIGTERM # Other lxc.arch = x86_64 lxc.cap.drop = sys_admin lxc.include = /usr/share/lxc/config/alpine.common.conf ''' image = None layers = [] mounts = [] uid = 0 gid = 0 cmd = '/bin/true' script = [] in_script = False if os.path.isfile(sys.argv[1]): lxcfile = os.path.realpath(sys.argv[1]) build_context = os.path.dirname(lxcfile) else: build_context = os.path.realpath(sys.argv[1]) lxcfile = os.path.join(build_context, 'lxcfile') def rebuild_config(): rootfs_layers = [os.path.join(LXC_ROOT, l) for l in layers] for layer in rootfs_layers: os.makedirs(layer, 0o755, True) if len(rootfs_layers) == 1: rootfs_path = rootfs_layers[0] else: rootfs_path = 'overlay:{}'.format(':'.join(rootfs_layers)) mount_entries = '\n'.join(['lxc.mount.entry = {} none bind 0 0'.format(m) for m in mounts]) with open(os.path.join(LXC_ROOT, image, 'config'), 'w') as fd: fd.write(CONFIG_TEMPLATE.format(image=image, rootfs=rootfs_path, mounts=mount_entries, uid=uid, gid=gid, cmd=cmd)) def fix_world(): world_items = [] last_world = [] for layer in layers[:-1]: with open(os.path.join(LXC_ROOT, layer, 'etc/apk/world'), 'r') as fd: last_world = fd.read().splitlines() world_items.extend(last_world) world_items = sorted(set(world_items)) if world_items != sorted(last_world): os.makedirs(os.path.join(LXC_ROOT, layers[-1], 'etc/apk')) with open(os.path.join(LXC_ROOT, layers[-1], 'etc/apk/world'), 'w') as fd: fd.writelines(world_items) def run_script(): script_filename = os.path.join(LXC_ROOT, layers[-1], 'run.sh') with open(script_filename, 'w') as fd: fd.write(' && '.join([s for s in script])) os.chmod(script_filename, 0o700) subprocess.run(['lxc-execute', '-n', image, '--', '/bin/sh', '-lvc', '/run.sh'], check=True) os.unlink(script_filename) def copy_files(src, dst): src = os.path.join(build_context, src) dst = os.path.join(LXC_ROOT, layers[-1], dst) shutil.copytree(src, dst) with open(lxcfile, 'r') as fd: recipe = fd.read().splitlines() for line in recipe: if line == 'RUN': in_script = False run_script() elif in_script and line and not line.startswith('#'): script.append(line) elif line == 'SCRIPT': script = [] in_script = True elif line.startswith('IMAGE'): image = line.split()[1] os.makedirs(os.path.join(LXC_ROOT, image), 0o755, True) elif line.startswith('LAYER'): layers.append(line.split()[1]) rebuild_config() elif line == 'FIXWORLD': fix_world() elif line.startswith('COPY'): copy_files(*line.split()[1:2]) elif line.startswith('MOUNT'): mounts.append(line.split()[1]) rebuild_config() elif line.startswith('USER'): uid = line.split()[1] gid = line.split()[2] rebuild_config() elif line.startswith('CMD'): cmd = line.split()[1] rebuild_config() layers.append('{}/delta0'.format(image))