summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Thursfield <sam.thursfield@codethink.co.uk>2015-05-22 12:13:30 +0100
committerSam Thursfield <sam.thursfield@codethink.co.uk>2015-05-22 12:13:30 +0100
commitefe56eb94d868870ac67e2a4fe4521f82df86bda (patch)
tree7e17ef7e424ee1d9716ba7019682f2f72a084137
parent6035cf559bea99ea26186ab49c8efaa1e579304c (diff)
downloadsandboxlib-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.mdwn25
-rwxr-xr-xrun-sandbox92
-rw-r--r--sandboxlib/__init__.py44
-rw-r--r--sandboxlib/chroot.py8
-rw-r--r--sandboxlib/linux_user_chroot.py30
-rw-r--r--sandboxlib/load/__init__.py19
-rw-r--r--sandboxlib/load/appc.py48
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)