diff --git a/focker.py b/focker.py index d9972e7..c627c69 100644 --- a/focker.py +++ b/focker.py @@ -21,7 +21,7 @@ from .jail import command_jail_create, \ command_jail_stop, \ command_jail_remove, \ command_jail_exec, \ - command_jail_run, \ + command_jail_oneshot, \ command_jail_list, \ command_jail_tag, \ command_jail_untag, \ @@ -89,11 +89,12 @@ def create_parser(): parser.add_argument('reference', type=str) parser.add_argument('command', type=str, nargs=argparse.REMAINDER, default=['/bin/sh']) - parser = subparsers.add_parser('run') - parser.set_defaults(func=command_jail_run) + parser = subparsers.add_parser('oneshot') + parser.set_defaults(func=command_jail_oneshot) parser.add_argument('image', type=str) - parser.add_argument('--command', '-c', type=str, default='/bin/sh') + parser.add_argument('--env', '-e', type=str, nargs='+', default=[]) parser.add_argument('--mounts', '-m', type=str, nargs='+', default=[]) + parser.add_argument('command', type=str, nargs=argparse.REMAINDER, default=['/bin/sh']) parser = subparsers.add_parser('list') parser.set_defaults(func=command_jail_list) diff --git a/jail.py b/jail.py index 63af6d6..2e02292 100644 --- a/jail.py +++ b/jail.py @@ -6,17 +6,39 @@ import json from tabulate import tabulate import os import jailconf -import shlex from .mount import getmntinfo +def jail_fs_create(image): + image, _ = zfs_find(image, focker_type='image', zfs_type='snapshot') + sha256 = bytes([ random.randint(0, 255) for _ in range(32) ]).hex() + lst = zfs_list(fields=['focker:sha256'], focker_type='image') + lst = list(filter(lambda a: a[0] == sha256, lst)) + if lst: + raise ValueError('Whew, a collision...') + poolname = zfs_poolname() + for pre in range(7, 32): + name = poolname + '/focker/jails/' + sha256[:pre] + if not zfs_exists(name): + break + zfs_run(['zfs', 'clone', '-o', 'focker:sha256=' + sha256, image, name]) + return name + + def gen_env_command(command, env): - env = [ 'export ' + k + '=' + shlex.quote(v) \ + env = [ 'export ' + k + '=' + quote(v) \ for (k, v) in env.items() ] command = ' && '.join(env + [ command ]) return command +def quote(s): + s = s.replace('\\', '\\\\') + s = s.replace('\'', '\\\'') + s = '\'' + s + '\'' + return s + + def jail_create(path, command, env, mounts, hostname=None): name = os.path.split(path)[-1] if os.path.exists('/etc/jail.conf'): @@ -27,24 +49,26 @@ def jail_create(path, command, env, mounts, hostname=None): blk['path'] = path if command: command = gen_env_command(command, env) + command = quote(command) + print('command:', command) blk['exec.start'] = command prestart = [ 'cp /etc/resolv.conf ' + - shlex.quote(os.path.join(path, 'etc/resolv.conf')) ] + quote(os.path.join(path, 'etc/resolv.conf')) ] poststop = [] if mounts: for (from_, on) in mounts: if not from_.startswith('/'): from_, _ = zfs_find(from_, focker_type='volume') from_ = zfs_mountpoint(from_) - prestart.append('mount -t nullfs ' + shlex.quote(from_) + - ' ' + shlex.quote(os.path.join(path, on.strip('/')))) + prestart.append('mount -t nullfs ' + quote(from_) + + ' ' + quote(os.path.join(path, on.strip('/')))) poststop += [ 'umount -f ' + os.path.join(path, on.strip('/')) \ for (_, on) in reversed(mounts) ] if prestart: - blk['exec.prestart'] = shlex.quote(' && '.join(prestart)) + blk['exec.prestart'] = quote(' && '.join(prestart)) if poststop: - blk['exec.poststop'] = shlex.quote(' && '.join(poststop)) + blk['exec.poststop'] = quote(' && '.join(poststop)) blk['persist'] = True blk['interface'] = 'lo1' blk['ip4.addr'] = '127.0.1.0' @@ -52,6 +76,7 @@ def jail_create(path, command, env, mounts, hostname=None): blk['exec.clean'] = True blk['host.hostname'] = hostname or name conf.write('/etc/jail.conf') + return name def jail_run_v2(path, command, env, mounts): @@ -143,18 +168,7 @@ def jail_remove(path): def command_jail_create(args): - image, _ = zfs_find(args.image, focker_type='image', zfs_type='snapshot') - sha256 = bytes([ random.randint(0, 255) for _ in range(32) ]).hex() - lst = zfs_list(fields=['focker:sha256'], focker_type='image') - lst = list(filter(lambda a: a[0] == sha256, lst)) - if lst: - raise ValueError('Whew, a collision...') - poolname = zfs_poolname() - for pre in range(7, 32): - name = poolname + '/focker/jails/' + sha256[:pre] - if not zfs_exists(name): - break - zfs_run(['zfs', 'clone', '-o', 'focker:sha256=' + sha256, image, name]) + name = jail_fs_create(args.image) if args.tags: zfs_tag(name, args.tags) path = zfs_mountpoint(name) @@ -194,7 +208,19 @@ def command_jail_exec(args): subprocess.run(['jexec', str(jid)] + args.command) -def command_jail_run(args): +def command_jail_oneshot(args): + name = jail_fs_create(args.image) + path = zfs_mountpoint(name) + env = { a.split(':')[0]: ':'.join(a.split(':')[1:]) \ + for a in args.env } + mounts = [ [ a.split(':')[0], a.split(':')[1] ] \ + for a in args.mounts] + jailname = jail_create(path, ' '.join(map(quote, args.command)), env, mounts) + subprocess.run(['jail', '-c', jailname]) + jail_remove(path) + +# Deprecated +def command_jail_oneshot_old(): base, _ = zfs_snapshot_by_tag_or_sha256(args.image) # root = '/'.join(base.split('/')[:-1]) for _ in range(10**6):