diff options
author | Sam Thursfield <sam.thursfield@codethink.co.uk> | 2015-05-22 12:13:30 +0100 |
---|---|---|
committer | Sam Thursfield <sam.thursfield@codethink.co.uk> | 2015-05-22 12:13:30 +0100 |
commit | efe56eb94d868870ac67e2a4fe4521f82df86bda (patch) | |
tree | 7e17ef7e424ee1d9716ba7019682f2f72a084137 | |
parent | 6035cf559bea99ea26186ab49c8efaa1e579304c (diff) | |
download | sandboxlib-efe56eb94d868870ac67e2a4fe4521f82df86bda.tar.gz |
Add linux-user-chroot backend, run-sandbox script, 'appc' loader
This library is now enough to run a very simple App Container image,
using either 'chroot' or 'linux-user-chroot'.
-rw-r--r-- | HACKING.mdwn | 25 | ||||
-rwxr-xr-x | run-sandbox | 92 | ||||
-rw-r--r-- | sandboxlib/__init__.py | 44 | ||||
-rw-r--r-- | sandboxlib/chroot.py | 8 | ||||
-rw-r--r-- | sandboxlib/linux_user_chroot.py | 30 | ||||
-rw-r--r-- | sandboxlib/load/__init__.py | 19 | ||||
-rw-r--r-- | sandboxlib/load/appc.py | 48 |
7 files changed, 265 insertions, 1 deletions
diff --git a/HACKING.mdwn b/HACKING.mdwn new file mode 100644 index 0000000..44804d9 --- /dev/null +++ b/HACKING.mdwn @@ -0,0 +1,25 @@ +# Testing that a sandbox conforms to the App Container spec + +The [App Container project] provides an 'ace' package containing a +[validator application for App Container Executors (ACEs)]. + +If you want to test whether a particular sandbox 'exec' module conforms to the +App Container spec, try this. You'll need a [golang] compiler available, and you +might need to set the `GOPATH` environment variable to point to a path where +you're happy for Go to install dependencies. + +First build the `ace-validator-main.aci` and `ace-validator-sidekick.aci` App +Container images. + + git clone git://github.com/appc/spec appc-spec + cd appc-spec + appc-spec/ace/build + +Then, use the run-sandbox program to run the image: + + run-sandbox appc-spec/ace/build/ace-validator-main.aci + + +[App Container project]: https://github.com/appc/spec +[validator application for App Container Executors (ACEs)]: https://github.com/appc/spec#validating-app-container-executors-aces +[golang]: https://golang.org/doc/install diff --git a/run-sandbox b/run-sandbox new file mode 100755 index 0000000..42f6085 --- /dev/null +++ b/run-sandbox @@ -0,0 +1,92 @@ +#!/usr/bin/python3 +# +# Copyright (C) 2015 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. + + +'''Commandline 'run things in a sandbox' tool.''' + + +import argparse +import logging +import sys + +import sandboxlib + + +def info(message, *args): + # FIXME: disable by default, add --verbose flag + print(message % args) + logging.info(message % args) + + +def get_executor(name): + # Convert the name into a valid Python module name. This is a convenience + # for users just because '-' is easier to type than '_'. + name = name.replace('-', '_') + + try: + executor = getattr(sandboxlib, name) + except AttributeError: + raise RuntimeError( + "%s is not a known executor in this version of 'sandboxlib'." % + name) + + return executor + + +def parse_args(): + parser = argparse.ArgumentParser(description="Run something in a sandbox.") + + parser.add_argument( + 'sandbox', metavar='PATH', type=str, + help="path to sandbox (image file, or directory tree)") + parser.add_argument( + 'command', metavar='COMMAND', type=str, nargs='?', + help="command to run in sandbox") + + parser.add_argument( + '--executor', '-e', + choices=['chroot', 'linux_user_chroot', 'linux-user-chroot'], + type=str, default='chroot', + help="which sandboxing backend to use") + + return parser.parse_args() + + +def run(): + args = parse_args() + + executor = get_executor(args.executor) + + if sandboxlib.load.appc.is_app_container_image(args.sandbox): + info("%s is an App Container image." % args.sandbox) + context = sandboxlib.load.appc.unpack_app_container_image( + args.sandbox) + with context as (rootfs_path, manifest): + if args.command is None: + command = manifest['app']['exec'] + else: + command = args.command + executor.run_sandbox(rootfs_path, command) + else: + # We should at minimum handle filesystem trees as well. + raise RuntimeError( + "Only App Container images are supported right now.") + + +try: + run() +except RuntimeError as e: + print("ERROR: %s" % e) diff --git a/sandboxlib/__init__.py b/sandboxlib/__init__.py new file mode 100644 index 0000000..04ae353 --- /dev/null +++ b/sandboxlib/__init__.py @@ -0,0 +1,44 @@ +# Copyright (C) 2015 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. + + +'''sandboxlib module.''' + + +BASE_ENVIRONMENT = { + # Mandated by https://github.com/appc/spec/blob/master/SPEC.md#execution-environment + 'PATH': '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', +} + + +def environment_vars(extra_env=None): + '''Return the complete set of environment variables for a sandbox. + + The base environment is defined above, and callers can add extra variables + to this or override the defaults by passing a dict to 'extra_env'. + + ''' + env = BASE_ENVIRONMENT.copy() + + if extra_env is not None: + env.update(extra_env) + + return env + + +# Executors +import sandboxlib.chroot +import sandboxlib.linux_user_chroot + +import sandboxlib.load diff --git a/sandboxlib/chroot.py b/sandboxlib/chroot.py index a20b6c8..3fe1857 100644 --- a/sandboxlib/chroot.py +++ b/sandboxlib/chroot.py @@ -18,10 +18,16 @@ import subprocess +import sandboxlib -def run_sandbox(rootfs_path, command): + +def run_sandbox(rootfs_path, command, extra_env=None): if type(command) == str: command = [command] + env = sandboxlib.BASE_ENVIRONMENT.copy() + if extra_env is not None: + env.update(extra_env) + # FIXME: you gotta be root for this one. subprocess.call(['chroot', rootfs_path] + command) diff --git a/sandboxlib/linux_user_chroot.py b/sandboxlib/linux_user_chroot.py new file mode 100644 index 0000000..8957191 --- /dev/null +++ b/sandboxlib/linux_user_chroot.py @@ -0,0 +1,30 @@ +# Copyright (C) 2015 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. + + +'''Execute command in a sandbox, using 'linux-user-chroot'.''' + + +import subprocess + +import sandboxlib + + +def run_sandbox(rootfs_path, command, extra_env=None): + if type(command) == str: + command = [command] + + env = sandboxlib.environment_vars(extra_env) + + subprocess.call(['linux-user-chroot', rootfs_path] + command, env=env) diff --git a/sandboxlib/load/__init__.py b/sandboxlib/load/__init__.py new file mode 100644 index 0000000..59b8cf6 --- /dev/null +++ b/sandboxlib/load/__init__.py @@ -0,0 +1,19 @@ +# Copyright (C) 2015 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. + + +'''sandboxlib loaders module.''' + + +import sandboxlib.load.appc diff --git a/sandboxlib/load/appc.py b/sandboxlib/load/appc.py new file mode 100644 index 0000000..486391e --- /dev/null +++ b/sandboxlib/load/appc.py @@ -0,0 +1,48 @@ +# Copyright (C) 2015 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. + + +'''Sandbox loader module for App Container images.''' + + +import contextlib +import json +import logging +import os +import shutil +import tarfile +import tempfile + + +def is_app_container_image(path): + return path.endswith('.aci') + + +@contextlib.contextmanager +def unpack_app_container_image(image_file): + tempdir = tempfile.mkdtemp() + try: + # FIXME: you gotta be root, sorry. + with tarfile.open(image_file, 'r') as tf: + tf.extractall(path=tempdir) + + manifest_path = os.path.join(tempdir, 'manifest') + rootfs_path = os.path.join(tempdir, 'rootfs') + + with open(manifest_path, 'r') as f: + manifest_data = json.load(f) + + yield rootfs_path, manifest_data + finally: + shutil.rmtree(tempdir) |