#!/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', 0o755, True)) 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('#!/bin/sh\nset -ev\n\n{}\n'.format('\n'.join(script))) os.chmod(script_filename, 0o700) subprocess.run(['lxc-execute', '-n', image, '--', '/bin/sh', '-lc', '/run.sh'], check=True) os.unlink(script_filename) def copy_tree(src, dst): if not os.path.isdir(src): shutil.copy2(src, dst) else: os.makedirs(dst, exist_ok=True) for name in os.listdir(src): copy_tree(os.path.join(src, name), os.path.join(dst, name)) shutil.copystat(src, dst) def copy_files(src, dst): src = os.path.join(build_context, src) dst = os.path.join(LXC_ROOT, layers[-1], dst) print('SRC: {}'.format(src)) print('DST: {}'.format(dst)) copy_tree(src, dst) with open(lxcfile, 'r') as fd: recipe = fd.readlines() for line in recipe: line = line.strip() if line == 'RUN': in_script = False run_script() elif in_script: 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'): srcdst = line.split() copy_files(srcdst[1], srcdst[2] if len(srcdst) == 3 else '') 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))