summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJim MacArthur <jim.macarthur@codethink.co.uk>2018-03-13 15:17:22 +0000
committerTristan Van Berkom <tristan.van.berkom@gmail.com>2018-03-23 14:37:38 +0000
commitf4d3892e4b7276f25599ce6e9fbae840ab10c833 (patch)
tree5f300f0851a2b28bd4e569cc2b6d2fa4a4964d7c
parent7d92ef0f39f64eb7dfddb969d88487287733e5b2 (diff)
downloadbuildstream-f4d3892e4b7276f25599ce6e9fbae840ab10c833.tar.gz
Add 'sandbox' configuration key and build-uid/build-gid elements
This only affects SandboxBWrap at the moment. buildstream/_loader.py: Add Symbol.SANDBOX and allow it in validation buildstream/_metaelement.py: Add 'sandbox' variable and store it in the object buildstream/_project.py: Add 'sandbox' configuration key and load it from project.conf. buildstream/data/projectconfig.yaml: Default build-uid/build-gid values of 0 for 'sandbox'. buildstream/element.py: Add __extract_sandbox_config to find the final sandbox configuration. Pass this to the sandbox constructor. buildstream/sandbox/_sandboxbwrap.py: If sandbox configuration was supplied, use it for uid and gid instead of the default 0. buildstream/sandbox/_sandboxchroot.py: Throw exception if non-0 uid/gid were supplied. buildstream/sandbox/__init__.py: Import SandboxConfig. buildstream/sandbox/_private.py: New file, containing SandboxConfig. Made private to avoid documentation for this class.
-rw-r--r--buildstream/_loader.py6
-rw-r--r--buildstream/_metaelement.py6
-rw-r--r--buildstream/_project.py7
-rw-r--r--buildstream/data/projectconfig.yaml7
-rw-r--r--buildstream/element.py37
-rw-r--r--buildstream/sandbox/_config.py30
-rw-r--r--buildstream/sandbox/_sandboxbwrap.py4
-rw-r--r--buildstream/sandbox/_sandboxchroot.py7
-rw-r--r--buildstream/sandbox/sandbox.py16
9 files changed, 105 insertions, 15 deletions
diff --git a/buildstream/_loader.py b/buildstream/_loader.py
index dc092dd0e..24dea7c97 100644
--- a/buildstream/_loader.py
+++ b/buildstream/_loader.py
@@ -57,6 +57,7 @@ class Symbol():
ALL = "all"
DIRECTORY = "directory"
JUNCTION = "junction"
+ SANDBOX = "sandbox"
# A simple dependency object
@@ -91,7 +92,7 @@ class LoadElement():
# Ensure the root node is valid
_yaml.node_validate(self.data, [
- 'kind', 'depends', 'sources',
+ 'kind', 'depends', 'sources', 'sandbox',
'variables', 'environment', 'environment-nocache',
'config', 'public', 'description',
])
@@ -582,7 +583,8 @@ class Loader():
_yaml.node_get(data, Mapping, Symbol.VARIABLES, default_value={}),
_yaml.node_get(data, Mapping, Symbol.ENVIRONMENT, default_value={}),
_yaml.node_get(data, list, Symbol.ENV_NOCACHE, default_value=[]),
- _yaml.node_get(data, Mapping, Symbol.PUBLIC, default_value={}))
+ _yaml.node_get(data, Mapping, Symbol.PUBLIC, default_value={}),
+ _yaml.node_get(data, Mapping, Symbol.SANDBOX, default_value={}))
# Cache it now, make sure it's already there before recursing
self.meta_elements[element_name] = meta_element
diff --git a/buildstream/_metaelement.py b/buildstream/_metaelement.py
index f9d95a3ce..7ba6ed0ed 100644
--- a/buildstream/_metaelement.py
+++ b/buildstream/_metaelement.py
@@ -36,8 +36,10 @@ class MetaElement():
# environment: The environment variables declared or overridden on this element
# env_nocache: List of environment vars which should not be considered in cache keys
# public: Public domain data dictionary
+ # sandbox: Configuration specific to the sandbox environment
#
- def __init__(self, project, name, kind, provenance, sources, config, variables, environment, env_nocache, public):
+ def __init__(self, project, name, kind, provenance, sources, config,
+ variables, environment, env_nocache, public, sandbox):
self.project = project
self.name = name
self.kind = kind
@@ -48,6 +50,6 @@ class MetaElement():
self.environment = environment
self.env_nocache = env_nocache
self.public = public
-
+ self.sandbox = sandbox
self.build_dependencies = []
self.dependencies = []
diff --git a/buildstream/_project.py b/buildstream/_project.py
index 85319b414..aeacf6bdf 100644
--- a/buildstream/_project.py
+++ b/buildstream/_project.py
@@ -173,7 +173,7 @@ class Project():
'aliases', 'name',
'artifacts', 'options',
'fail-on-overlap', 'shell',
- 'ref-storage'
+ 'ref-storage', 'sandbox'
])
# The project name, element path and option declarations
@@ -292,10 +292,13 @@ class Project():
if option.variable:
self._variables[option.variable] = option.get_value()
- # Load sandbox configuration
+ # Load sandbox environment variables
self._environment = _yaml.node_get(config, Mapping, 'environment')
self._env_nocache = _yaml.node_get(config, list, 'environment-nocache')
+ # Load sandbox configuration
+ self._sandbox = _yaml.node_get(config, Mapping, 'sandbox')
+
# Load project split rules
self._splits = _yaml.node_get(config, Mapping, 'split-rules')
diff --git a/buildstream/data/projectconfig.yaml b/buildstream/data/projectconfig.yaml
index d8fefc5e1..408304544 100644
--- a/buildstream/data/projectconfig.yaml
+++ b/buildstream/data/projectconfig.yaml
@@ -110,6 +110,13 @@ environment:
#
environment-nocache: []
+# Configuration for the sandbox other than environment variables
+# should go in 'sandbox'. This just contains the UID and GID that
+# the user in the sandbox will have. Not all sandboxes will support
+# changing the values.
+sandbox:
+ build-uid: 0
+ build-gid: 0
# Defaults for the 'split-rules' public data found on elements
# in the 'bst' domain.
diff --git a/buildstream/element.py b/buildstream/element.py
index b62945f4b..57d3732e7 100644
--- a/buildstream/element.py
+++ b/buildstream/element.py
@@ -37,12 +37,13 @@ from . import _yaml
from ._variables import Variables
from ._exceptions import BstError, LoadError, LoadErrorReason, ImplError, ErrorDomain
from . import Plugin, Consistency
-from . import SandboxFlags
+from . import Sandbox, SandboxFlags
from . import utils
from . import _cachekey
from . import _signals
from . import _site
from ._platform import Platform
+from .sandbox._config import SandboxConfig
# The base BuildStream artifact version
@@ -191,6 +192,9 @@ class Element(Plugin):
self.__config = self.__extract_config(meta)
self.configure(self.__config)
+ # Extract Sandbox config
+ self.__sandbox_config = self.__extract_sandbox_config(meta)
+
self.__tainted = None
self.__workspaced_artifact = None
self.__workspaced_dependencies_artifact = None
@@ -1072,7 +1076,7 @@ class Element(Plugin):
utils._force_rmtree(rootdir)
with _signals.terminator(cleanup_rootdir), \
- self.__sandbox(rootdir, output_file, output_file) as sandbox: # nopep8
+ self.__sandbox(rootdir, output_file, output_file, self.__sandbox_config) as sandbox: # nopep8
sandbox_root = sandbox.get_directory()
@@ -1376,7 +1380,7 @@ class Element(Plugin):
@contextmanager
def _prepare_sandbox(self, scope, directory, integrate=True):
- with self.__sandbox(directory) as sandbox:
+ with self.__sandbox(directory, config=self.__sandbox_config) as sandbox:
# Configure always comes first, and we need it.
self.configure_sandbox(sandbox)
@@ -1694,7 +1698,7 @@ class Element(Plugin):
# Private Local Methods #
#############################################################
@contextmanager
- def __sandbox(self, directory, stdout=None, stderr=None):
+ def __sandbox(self, directory, stdout=None, stderr=None, config=None):
context = self._get_context()
project = self._get_project()
platform = Platform.get_platform()
@@ -1703,7 +1707,8 @@ class Element(Plugin):
sandbox = platform.create_sandbox(context, project,
directory,
stdout=stdout,
- stderr=stderr)
+ stderr=stderr,
+ config=config)
yield sandbox
else:
@@ -1711,7 +1716,7 @@ class Element(Plugin):
rootdir = tempfile.mkdtemp(prefix="{}-".format(self.normal_name), dir=context.builddir)
# Recursive contextmanager...
- with self.__sandbox(rootdir, stdout=stdout, stderr=stderr) as sandbox:
+ with self.__sandbox(rootdir, stdout=stdout, stderr=stderr, config=config) as sandbox:
yield sandbox
# Cleanup the build dir
@@ -1821,6 +1826,26 @@ class Element(Plugin):
return config
+ # Sandbox-specific configuration data, to be passed to the sandbox's constructor.
+ #
+ def __extract_sandbox_config(self, meta):
+ project = self._get_project()
+
+ # The default config is already composited with the project overrides
+ sandbox_defaults = _yaml.node_get(self.__defaults, Mapping, 'sandbox', default_value={})
+ sandbox_defaults = _yaml.node_chain_copy(sandbox_defaults)
+
+ sandbox_config = _yaml.node_chain_copy(project._sandbox)
+ _yaml.composite(sandbox_config, sandbox_defaults)
+ _yaml.composite(sandbox_config, meta.sandbox)
+ _yaml.node_final_assertions(sandbox_config)
+
+ # Sandbox config, unlike others, has fixed members so we should validate them
+ _yaml.node_validate(sandbox_config, ['build-uid', 'build-gid'])
+
+ return SandboxConfig(self.node_get_member(sandbox_config, int, 'build-uid'),
+ self.node_get_member(sandbox_config, int, 'build-gid'))
+
# This makes a special exception for the split rules, which
# elements may extend but whos defaults are defined in the project.
#
diff --git a/buildstream/sandbox/_config.py b/buildstream/sandbox/_config.py
new file mode 100644
index 000000000..56282ca1f
--- /dev/null
+++ b/buildstream/sandbox/_config.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+# Jim MacArthur <jim.macarthur@codethink.co.uk>
+
+
+"""A container for sandbox configuration data. We want the internals
+of this to be opaque, hence putting it in its own private file.
+"""
+
+
+class SandboxConfig():
+ def __init__(self, build_uid, build_gid):
+ self.build_uid = build_uid
+ self.build_gid = build_gid
diff --git a/buildstream/sandbox/_sandboxbwrap.py b/buildstream/sandbox/_sandboxbwrap.py
index f955a5e44..d18cb9ec0 100644
--- a/buildstream/sandbox/_sandboxbwrap.py
+++ b/buildstream/sandbox/_sandboxbwrap.py
@@ -149,7 +149,9 @@ class SandboxBwrap(Sandbox):
if self.user_ns_available:
bwrap_command += ['--unshare-user']
if not flags & SandboxFlags.INHERIT_UID:
- bwrap_command += ['--uid', '0', '--gid', '0']
+ uid = self._get_config().build_uid
+ gid = self._get_config().build_gid
+ bwrap_command += ['--uid', str(uid), '--gid', str(gid)]
# Add the command
bwrap_command += command
diff --git a/buildstream/sandbox/_sandboxchroot.py b/buildstream/sandbox/_sandboxchroot.py
index c5e14e485..7f27f50d0 100644
--- a/buildstream/sandbox/_sandboxchroot.py
+++ b/buildstream/sandbox/_sandboxchroot.py
@@ -38,6 +38,13 @@ from . import Sandbox, SandboxFlags
class SandboxChroot(Sandbox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+
+ uid = self._get_config().build_uid
+ gid = self._get_config().build_gid
+ if uid != 0 or gid != 0:
+ raise SandboxError("Chroot sandboxes cannot specify a non-root uid/gid "
+ "({},{} were supplied via config)".format(uid, gid))
+
self.mount_map = None
def run(self, command, flags, *, cwd=None, env=None):
diff --git a/buildstream/sandbox/sandbox.py b/buildstream/sandbox/sandbox.py
index f5caaa5e1..3ab75f1a8 100644
--- a/buildstream/sandbox/sandbox.py
+++ b/buildstream/sandbox/sandbox.py
@@ -88,12 +88,14 @@ class Sandbox():
def __init__(self, context, project, directory, **kwargs):
self.__context = context
self.__project = project
- self.__stdout = kwargs['stdout']
- self.__stderr = kwargs['stderr']
self.__directories = []
self.__cwd = None
self.__env = None
self.__mount_sources = {}
+ # Configuration from kwargs common to all subclasses
+ self.__config = kwargs['config']
+ self.__stdout = kwargs['stdout']
+ self.__stderr = kwargs['stderr']
# Setup the directories
self.__directory = directory
@@ -269,3 +271,13 @@ class Sandbox():
# (file): The stderr, or None to inherit
def _get_output(self):
return (self.__stdout, self.__stderr)
+
+ # _get_config()
+ #
+ # Fetches the sandbox configuration object.
+ #
+ # Returns:
+ # (SandboxConfig): An object containing the configuration
+ # data passed in during construction.
+ def _get_config(self):
+ return self.__config