M BEMfile => BEMfile +6 -0
@@ 1,2 1,8 @@
[BEM]
minimum_version = 0.0.1
+
+[environment]
+TERM = XTERM
+
+[indirectenvironment]
+BEM_VERSION = {version}
M README.md => README.md +52 -0
@@ 114,3 114,55 @@ that could be used instead.
Not really. It definitely adds to start-up time, especially if the image needs
to be rebuilt. However, starting cold from an image that has already been built
and cached, BEM only adds 1-2 seconds of additional start up time.
+
+## Configuration
+
+### Configuration Keys
+
+The following keys may be specified in the `[BEM]` section of the `BEMfile`.
+Values are strings if not otherwise noted. Values may include other
+configuration keys for substitution using the syntax `{key}`. This makes it
+easy to, for example, specify a file relative to the project root.
+
+* `minimum_version` -- minimum BEM version required to use this `BEMfile`
+ (default: current version)
+* `dockerfile` -- path to Dockerfile BEM should use (default:
+ `{bem_file_dir}/Dockerfile`)
+* `projectdir` -- path to the directory BEM should treat as the project's
+ top-level (default: `{bem_file_dir}`)
+* `nonineractive` -- boolean indicating BEM should run in non-interactive mode
+ (default: `False`) -- **non-interactive mode is WiP and has several problems
+ at the moment**.
+* `logfile` -- when running in non-interactive mode, output should be written
+ here; ignored in interactive mode (default: `/dev/null`)
+* `flush_interval` -- number of seconds between flushes to the logfile
+* `noenvtamper` -- set to `True` to prevent BEM from "tampering" with your
+ environment, such as setting `BEM_PROJECT` and overriding `PS1`. Environment
+ overrides are still honored. (default: `False`)
+* `noenvpassthrough` -- set to `True` to prevent BEM from passing through the
+ host environment into the container. (default: `False`).
+* `x11` -- set to `True` to bind mount in `/tmp/.X11-unix`. (default: `True`)
+* `squashmethod` -- specify how the user account should be squashed. (default:
+ `bind`)
+ * `bind` -- bind-mount in `/etc/passwd`, `/etc/shadow`, and
+ `/etc/group` read-only, and set the UID within the container to the
+ UID running BEM.
+ * `none` -- do nothing and set the UID within the container to 0 (root).
+
+The following config keys should not be changed, but may be used via for
+substitution/indirection.
+
+* `version` -- the running BEM version
+* `bem_file_dir` -- parent directory of the current `BEMfile`
+
+Configuration keys beginning with a `#` are reserved for internal use and
+should not be used for substitutions, nor overridden.
+
+### Environment Overrides
+
+The section `[environment]` in the `BEMfile` may be used to specify environment
+variables that should be defined within the container. Such values will still
+be set even if `noenvtamper` or `noenvpassthrough` are asserted. Values placed
+in `[indirectenvironment]` will be expanded using the substitution syntax used
+for config values. If the same name exists in both `[environment]` and
+`[indirectenvironment]`, the latter takes precedence.
M bem/bem_config.py => bem/bem_config.py +60 -1
@@ 1,6 1,8 @@
import logging
import configparser
import copy
+import pathlib
+import os
from . import bem_constants
from . import bem_util
@@ 54,17 56,21 @@ def load_config(inifile, args, ini_dir):
logging.debug("unresolved config... ")
bem_util.log_pretty(logging.debug, config)
+ ignore = ["#environment"]
iterations = 0
while True:
# check if we're done resolving indirect values
count = 0
for key in config:
+ if key in ignore:
+ continue
+
if '{' in str(config[key]):
count += 1
if count == 0:
- return config
+ break
if iterations > 1000:
raise Exception(
@@ 72,6 78,9 @@ def load_config(inifile, args, ini_dir):
.format([k for k in config if '{' in config[k]]))
for key in config:
+ if key in ignore:
+ continue
+
# perform the next iteration of indirection
if '{' in str(config[key]):
logging.debug("resolving indirect value for key '{}': "
@@ 80,3 89,53 @@ def load_config(inifile, args, ini_dir):
logging.debug("\tresolved to: {}".format(config[key]))
iterations += 1
+
+ # typecast keys as needed
+ keytypes = {
+ "noenvtamper": bem_util.parse_bool,
+ "noenvpassthrough": bem_util.parse_bool,
+ "x11": bem_util.parse_bool,
+ "flush_interval": int,
+ "dockerfile": pathlib.Path,
+ "bem_file_dir": pathlib.Path,
+ "projectdir": pathlib.Path,
+ "noninteractive": pathlib.Path,
+ }
+
+ for key in config:
+ if key in keytypes:
+ config[key] = keytypes[key](config[key])
+
+ # setup environment
+ if not config["noenvpassthrough"]:
+ config["#environment"] = dict(os.environ)
+ else:
+ config["#environment"] = {}
+
+ # environment tampering
+ if not config["noenvtamper"]:
+ config["#environment"]["PS1"] = bem_constants.default_ps1
+ config["#environment"]["BEM_PROJECT"] = pathlib.Path(config["projectdir"]).name
+
+ # load direct environment variables from ini
+ if "environment" in inifile:
+ for key in inifile["environment"]:
+ config["#environment"][key] = inifile["environment"][key]
+ logging.debug("loading key {} from environment: {}".format(
+ key, inifile["environment"][key]))
+
+ # load indirect environment variables from ini
+ if "indirectenvironment" in inifile:
+ for key in inifile["indirectenvironment"]:
+ config["#environment"][key] = inifile["indirectenvironment"][key]
+
+ while '{' in config["#environment"][key]:
+ config["#environment"][key] = config["#environment"][key].format_map(config)
+
+ logging.debug("loading key {} from indirect environment: {}".format(
+ key, inifile["indirectenvironment"][key]))
+
+ logging.debug("resolved config... ")
+ bem_util.log_pretty(logging.debug, config)
+
+ return config
M bem/bem_constants.py => bem/bem_constants.py +6 -0
@@ 11,5 11,11 @@ config_defaults = {
"noninteractive": "False",
"logfile": "/dev/null",
"flush_interval": "5",
+ "noenvtamper": False,
+ "x11": True,
+ "squashmethod": "bind",
+ "noenvpassthrough": False,
}
+default_ps1 = "[BEM:$BEM_PROJECT] $(whoami)@$(hostname) $(pwd) $ "
+
M bem/bem_engine.py => bem/bem_engine.py +29 -18
@@ 105,29 105,40 @@ def execute_command(config, command):
"bind": str(projectdir),
"mode": "rw"
},
- "/etc/shadow": {
- "bind": "/etc/shadow",
- "mode": "ro",
- },
- "/etc/passwd": {
- "bind": "/etc/passwd",
- "mode": "ro",
- },
- "/etc/group": {
- "bind": "/etc/group",
- "mode": "ro",
- },
- "/tmp/.X11-unix": {
+ }
+
+ if bem_util.parse_bool(config["x11"]):
+ volumes["/tmp/.X11-unix"] = {
"bind": "/tmp/.X11-unix",
"mode": "rw",
}
+
+ user = 0
+ if config["squashmethod"] == "bind":
+ volumes["/etc/shadow"] = {
+ "bind": "/etc/shadow",
+ "mode": "ro",
}
- logging.debug("volumes: {}".format(volumes))
+ volumes["/etc/passwd"] = {
+ "bind": "/etc/passwd",
+ "mode": "ro",
+ }
+
+ volumes["/etc/group"] = {
+ "bind": "/etc/group",
+ "mode": "ro",
+ }
+
+ user = os.getuid()
- environment = dict(os.environ)
+ elif config["squashmethod"] == "none":
+ pass
- environment["PS1"] = "[BEM:{}] $(whoami)@$(hostname) $(pwd) $ ".format(pathlib.Path(projectdir).name)
+ else:
+ raise Exception("unknown squashmethod: {}".format(config["squashmethod"]))
+
+ logging.debug("volumes: {}".format(volumes))
# TODO: if getcwd does not exist inside of the container (not a child of
# any volume), set to project dir instead
@@ 156,8 167,8 @@ def execute_command(config, command):
host_config=client.api.create_host_config(
binds=volumes,
),
- user=os.getuid(),
- environment=environment,
+ user=user,
+ environment=config["#environment"],
working_dir=os.getcwd()
)
M bem/bem_util.py => bem/bem_util.py +1 -1
@@ 98,4 98,4 @@ def compare_versions(v1, v2):
return True
def parse_bool(s):
- return s.lower in ["true", "t", "yes", "1", "on", "one", "yep", "yup"]
+ return str(s).lower in ["true", "t", "yes", "1", "on", "one", "yep", "yup"]