From 6427a6ec09b67898a76afbf0978fde13e9594dfc Mon Sep 17 00:00:00 2001 From: Disassembler Date: Thu, 6 Sep 2018 09:32:20 +0200 Subject: [PATCH] Make lxc-build a bit more presentable --- activemq/lxcfile | 6 +- lxc-build | 195 +++++++++++++++++++++++++++-------------------- 2 files changed, 113 insertions(+), 88 deletions(-) diff --git a/activemq/lxcfile b/activemq/lxcfile index d4dbd0d..8f6d29a 100644 --- a/activemq/lxcfile +++ b/activemq/lxcfile @@ -22,8 +22,6 @@ RUN COPY lxc -CMD s6-svscan /etc/services.d - -LAYER activemq/delta0 - MOUNT /srv/activemq/data srv/activemq/data + +CMD s6-svscan /etc/services.d diff --git a/lxc-build b/lxc-build index 7d98676..1652f73 100755 --- a/lxc-build +++ b/lxc-build @@ -7,7 +7,7 @@ import sys LXC_ROOT = '/var/lib/lxc' CONFIG_TEMPLATE = '''# Image name -lxc.uts.name = {image} +lxc.uts.name = {name} # Network lxc.net.0.type = veth @@ -23,6 +23,7 @@ lxc.rootfs.path = {rootfs} 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} @@ -37,54 +38,102 @@ 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 +class LXCImage: + def __init__(self, build_path): + self.name = None + self.layers = [] + self.mounts = [] + self.uid = 0 + self.gid = 0 + self.cmd = '/bin/true' -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') + if os.path.isfile(build_path): + self.lxcfile = os.path.realpath(build_path) + self.build_context = os.path.dirname(lxcfile) + else: + self.build_context = os.path.realpath(build_path) + self.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: + def build(self): + with open(self.lxcfile, 'r') as fd: + lxcfile = [l.strip() for l in fd.readlines()] + + in_script = False + script = [] + + for line in lxcfile: + if line == 'RUN': + in_script = False + self.run_script(script) + elif in_script: + script.append(line) + elif line == 'SCRIPT': + script = [] + in_script = True + elif line.startswith('IMAGE'): + self.set_name(line.split()[1]) + elif line.startswith('LAYER'): + self.add_layer(line.split()[1]) + elif line.startswith('COPY'): + srcdst = line.split() + self.copy_files(srcdst[1], srcdst[2] if len(srcdst) == 3 else '') + elif line.startswith('MOUNT'): + srcdst = line.split() + self.add_mount(srcdst[1], srcdst[2]) + elif line.startswith('USER'): + uidgid = line.split() + self.set_user(uidgid[1], uidgid[2]) + elif line.startswith('CMD'): + self.set_cmd(line.split()[1]) + # Add the final layer which can be treated as nonpersistent + self.add_layer('{}/delta0'.format(self.name)) + + def rebuild_config(self): + if len(layers) == 1: + rootfs_path = layers[0] + else: + # Multiple lower overlayfs layers are ordered from right to left (lower2:lower1:rootfs:upper) + rootfs_path = 'overlay:{}:{}'.format(':'.join(layers[:-1][::-1], layers[-1])) + mount_entries = '\n'.join(['lxc.mount.entry = {} none bind 0 0'.format(m) for m in mounts]) + with open(os.path.join(LXC_ROOT, self.name, 'config'), 'w') as fd: + fd.write(CONFIG_TEMPLATE.format(name=self.name, rootfs=rootfs_path, mounts=mount_entries, uid=self.uid, gid=self.gid, cmd=self.cmd)) + + def run_script(self, script): + sh = os.path.join(self.layers[-1], 'run.sh') + with open(sh, 'w') as fd: + fd.write('#!/bin/sh\nset -ev\n\n{}\n'.format('\n'.join(script))) + os.chmod(sh, 0o700) + subprocess.run(['lxc-execute', '-n', self.name, '--', '/bin/sh', '-lc', '/run.sh'], check=True) + os.unlink(sh) + + def set_name(self, name): + self.name = name + os.makedirs(os.path.join(LXC_ROOT, self.name), 0o755, True) + self.rebuild_config() + + def add_layer(self, layer): + layer = os.path.join(LXC_ROOT, layer) + self.layers.append(layer) 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)) + self.rebuild_config() -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 copy_files(self, src, dst): + src = os.path.join(build_context, src) + dst = os.path.join(layers[-1], dst) + copy_tree(src, dst) -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 add_mount(self, src, dst): + mounts.append('{} {}'.format(src, dst)) + self.rebuild_config() + + def set_user(self, uid, gid): + self.uid = uid + self.gid = gid + self.rebuild_config() + + def set_cmd(self, cmd): + self.cmd = cmd + self.rebuild_config() def copy_tree(src, dst): if not os.path.isdir(src): @@ -95,44 +144,22 @@ def copy_tree(src, dst): 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) - copy_tree(src, dst) +# def fix_world(): +# world_items = [] +# last_world = [] +# for layer in layers[:-1]: +# with open(os.path.join(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(layers[-1], 'etc/apk', 0o755, True)) +# with open(os.path.join(layers[-1], 'etc/apk/world'), 'w') as fd: +# fd.writelines(world_items) -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)) -rebuild_config() +if __name__ == '__main__': + if len(sys.argv) != 2: + print('Usage: lxc-build \n where the buildpath can be either specific lxcfile or a directory containing one') + else: + i = LXCImage(sys.argv[1]) + i.build()