Make lxc-build a bit more presentable
This commit is contained in:
parent
933a75bf57
commit
6427a6ec09
@ -22,8 +22,6 @@ RUN
|
|||||||
|
|
||||||
COPY lxc
|
COPY lxc
|
||||||
|
|
||||||
CMD s6-svscan /etc/services.d
|
|
||||||
|
|
||||||
LAYER activemq/delta0
|
|
||||||
|
|
||||||
MOUNT /srv/activemq/data srv/activemq/data
|
MOUNT /srv/activemq/data srv/activemq/data
|
||||||
|
|
||||||
|
CMD s6-svscan /etc/services.d
|
||||||
|
195
lxc-build
195
lxc-build
@ -7,7 +7,7 @@ import sys
|
|||||||
|
|
||||||
LXC_ROOT = '/var/lib/lxc'
|
LXC_ROOT = '/var/lib/lxc'
|
||||||
CONFIG_TEMPLATE = '''# Image name
|
CONFIG_TEMPLATE = '''# Image name
|
||||||
lxc.uts.name = {image}
|
lxc.uts.name = {name}
|
||||||
|
|
||||||
# Network
|
# Network
|
||||||
lxc.net.0.type = veth
|
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/hosts etc/hosts none bind 0 0
|
||||||
lxc.mount.entry = /etc/resolv.conf etc/resolv.conf none bind 0 0
|
lxc.mount.entry = /etc/resolv.conf etc/resolv.conf none bind 0 0
|
||||||
{mounts}
|
{mounts}
|
||||||
|
|
||||||
# Init
|
# Init
|
||||||
lxc.init.cmd = {cmd}
|
lxc.init.cmd = {cmd}
|
||||||
lxc.init.uid = {uid}
|
lxc.init.uid = {uid}
|
||||||
@ -37,54 +38,102 @@ lxc.cap.drop = sys_admin
|
|||||||
lxc.include = /usr/share/lxc/config/alpine.common.conf
|
lxc.include = /usr/share/lxc/config/alpine.common.conf
|
||||||
'''
|
'''
|
||||||
|
|
||||||
image = None
|
class LXCImage:
|
||||||
layers = []
|
def __init__(self, build_path):
|
||||||
mounts = []
|
self.name = None
|
||||||
uid = 0
|
self.layers = []
|
||||||
gid = 0
|
self.mounts = []
|
||||||
cmd = '/bin/true'
|
self.uid = 0
|
||||||
script = []
|
self.gid = 0
|
||||||
in_script = False
|
self.cmd = '/bin/true'
|
||||||
|
|
||||||
if os.path.isfile(sys.argv[1]):
|
if os.path.isfile(build_path):
|
||||||
lxcfile = os.path.realpath(sys.argv[1])
|
self.lxcfile = os.path.realpath(build_path)
|
||||||
build_context = os.path.dirname(lxcfile)
|
self.build_context = os.path.dirname(lxcfile)
|
||||||
else:
|
else:
|
||||||
build_context = os.path.realpath(sys.argv[1])
|
self.build_context = os.path.realpath(build_path)
|
||||||
lxcfile = os.path.join(build_context, 'lxcfile')
|
self.lxcfile = os.path.join(build_context, 'lxcfile')
|
||||||
|
|
||||||
def rebuild_config():
|
def build(self):
|
||||||
rootfs_layers = [os.path.join(LXC_ROOT, l) for l in layers]
|
with open(self.lxcfile, 'r') as fd:
|
||||||
for layer in rootfs_layers:
|
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)
|
os.makedirs(layer, 0o755, True)
|
||||||
if len(rootfs_layers) == 1:
|
self.rebuild_config()
|
||||||
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():
|
def copy_files(self, src, dst):
|
||||||
world_items = []
|
src = os.path.join(build_context, src)
|
||||||
last_world = []
|
dst = os.path.join(layers[-1], dst)
|
||||||
for layer in layers[:-1]:
|
copy_tree(src, dst)
|
||||||
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():
|
def add_mount(self, src, dst):
|
||||||
script_filename = os.path.join(LXC_ROOT, layers[-1], 'run.sh')
|
mounts.append('{} {}'.format(src, dst))
|
||||||
with open(script_filename, 'w') as fd:
|
self.rebuild_config()
|
||||||
fd.write('#!/bin/sh\nset -ev\n\n{}\n'.format('\n'.join(script)))
|
|
||||||
os.chmod(script_filename, 0o700)
|
def set_user(self, uid, gid):
|
||||||
subprocess.run(['lxc-execute', '-n', image, '--', '/bin/sh', '-lc', '/run.sh'], check=True)
|
self.uid = uid
|
||||||
os.unlink(script_filename)
|
self.gid = gid
|
||||||
|
self.rebuild_config()
|
||||||
|
|
||||||
|
def set_cmd(self, cmd):
|
||||||
|
self.cmd = cmd
|
||||||
|
self.rebuild_config()
|
||||||
|
|
||||||
def copy_tree(src, dst):
|
def copy_tree(src, dst):
|
||||||
if not os.path.isdir(src):
|
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))
|
copy_tree(os.path.join(src, name), os.path.join(dst, name))
|
||||||
shutil.copystat(src, dst)
|
shutil.copystat(src, dst)
|
||||||
|
|
||||||
def copy_files(src, dst):
|
# def fix_world():
|
||||||
src = os.path.join(build_context, src)
|
# world_items = []
|
||||||
dst = os.path.join(LXC_ROOT, layers[-1], dst)
|
# last_world = []
|
||||||
copy_tree(src, dst)
|
# 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:
|
if __name__ == '__main__':
|
||||||
recipe = fd.readlines()
|
if len(sys.argv) != 2:
|
||||||
|
print('Usage: lxc-build <buildpath>\n where the buildpath can be either specific lxcfile or a directory containing one')
|
||||||
for line in recipe:
|
else:
|
||||||
line = line.strip()
|
i = LXCImage(sys.argv[1])
|
||||||
if line == 'RUN':
|
i.build()
|
||||||
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()
|
|
||||||
|
Loading…
Reference in New Issue
Block a user