diff --git a/README.rst b/README.rst index 64c9246..94a2c3b 100644 --- a/README.rst +++ b/README.rst @@ -51,6 +51,13 @@ The ``[docker:container-name]`` section may contain the following directives: This value is passed directly to Docker, and may be of any of the forms that Docker accepts in eg ``docker run``. +``privileged`` + A boolean (``true || false``) to set `runtime privilege + `__. + This value is coerced to a boolean based of the same rules as + Python's default `ConfigParser + `__. + ``environment`` A multi-line list of ``KEY=value`` settings which is used to set environment variables for the container. The variables are only available diff --git a/tox.ini b/tox.ini index b8bacf5..b00d3c8 100644 --- a/tox.ini +++ b/tox.ini @@ -52,6 +52,7 @@ ports = [docker:networking-two] # along with networking-one, proves we can run multiple copies of the same image image = ksdn117/tcp-udp-test +privileged = off ports = 2345:1234/tcp links = networking-one:linked_host @@ -65,6 +66,7 @@ healthcheck_start_period = 1 [docker:healthcheck-custom] image = docker.io/toxdocker/healthcheck:latest +privileged = true healthcheck_cmd = touch /healthcheck/web/healthy healthcheck_interval = 1 healthcheck_timeout = 1 diff --git a/tox_docker/config.py b/tox_docker/config.py index 1721a1d..26df3a2 100644 --- a/tox_docker/config.py +++ b/tox_docker/config.py @@ -109,6 +109,7 @@ def __init__( name: str, image: Image, stop: bool, + privileged: bool = False, environment: Optional[Mapping[str, str]] = None, healthcheck_cmd: Optional[str] = None, healthcheck_interval: Optional[float] = None, @@ -123,6 +124,7 @@ def __init__( self.runas_name = runas_name(name) self.image = image self.stop = stop + self.privileged = privileged self.environment: Mapping[str, str] = environment or {} self.ports: Collection[Port] = ports or {} self.links: Collection[Link] = links or {} diff --git a/tox_docker/plugin.py b/tox_docker/plugin.py index eec57fd..6348613 100644 --- a/tox_docker/plugin.py +++ b/tox_docker/plugin.py @@ -108,6 +108,7 @@ def docker_run( ports=ports, publish_all_ports=len(ports) == 0, mounts=container_config.mounts, + privileged=container_config.privileged, ) container.reload() # TODO: why do we need this? return container diff --git a/tox_docker/tests/test_privileged.py b/tox_docker/tests/test_privileged.py new file mode 100644 index 0000000..29634ed --- /dev/null +++ b/tox_docker/tests/test_privileged.py @@ -0,0 +1,19 @@ +from tox_docker.tests.util import find_container + + +def test_can_grant_privileged() -> None: + container = find_container("healthcheck-custom") + + assert container.attrs["HostConfig"]["Privileged"] is True + + +def test_can_deny_privileged() -> None: + container = find_container("networking-two") + + assert container.attrs["HostConfig"]["Privileged"] is False + + +def test_not_privileged_by_default() -> None: + container = find_container("healthcheck-builtin") + + assert container.attrs["HostConfig"]["Privileged"] is False diff --git a/tox_docker/tox3/config.py b/tox_docker/tox3/config.py index eaa4850..47a8e2f 100644 --- a/tox_docker/tox3/config.py +++ b/tox_docker/tox3/config.py @@ -53,6 +53,22 @@ def getint(reader: SectionReader, key: str) -> Optional[int]: return val +def getboolean(reader: SectionReader, key: str) -> Optional[bool]: + val = reader.getstring(key) + if val is None: + return None + + # Same implementation as: + # https://docs.python.org/3/library/configparser.html#configparser.ConfigParser.getboolean + lower_val = val.lower().strip() + if lower_val in ("yes", "on", "true", "1"): + return True + if lower_val in ("no", "off", "false", "0"): + return False + msg = f"{val!r} is not a boolean (for {key} in [{reader.section_name}])" + raise ValueError(msg) + + def getenvdict(reader: SectionReader, key: str) -> Mapping[str, str]: environment = {} for value in reader.getlist(key): @@ -105,6 +121,10 @@ def parse_container_config( "stop": container_name not in config.option.docker_dont_stop, } + privileged = False + if reader.getstring("privileged"): + privileged = bool(getboolean(reader, "privileged")) + environment = None if reader.getstring("environment"): environment = getenvdict(reader, "environment") @@ -137,6 +157,7 @@ def parse_container_config( name=container_name, image=Image(reader.getstring("image")), stop=container_name not in config.option.docker_dont_stop, + privileged=privileged, environment=environment, healthcheck_cmd=hc_cmd, healthcheck_interval=hc_interval, diff --git a/tox_docker/tox4/config.py b/tox_docker/tox4/config.py index 8beb390..3299608 100644 --- a/tox_docker/tox4/config.py +++ b/tox_docker/tox4/config.py @@ -28,6 +28,12 @@ def register_config(self) -> None: desc="docker image to run", post_process=image_required, ) + self.add_config( + keys=["privileged"], + of_type=bool, + default=False, + desc="give extended privileges to this container", + ) self.add_config( keys=["environment"], of_type=Dict[str, str], @@ -93,6 +99,7 @@ def parse_container_config(docker_config: DockerConfigSet) -> ContainerConfig: name=docker_config.name, image=docker_config["image"], stop=docker_config.name not in docker_config._conf.options.docker_dont_stop, + privileged=docker_config["privileged"], environment=docker_config["environment"], healthcheck_cmd=docker_config["healthcheck_cmd"], healthcheck_interval=docker_config["healthcheck_interval"],