diff --git a/binderhub/app.py b/binderhub/app.py index 47198e60c..614bda4b9 100644 --- a/binderhub/app.py +++ b/binderhub/app.py @@ -30,6 +30,7 @@ Bytes, Dict, Integer, + List, TraitError, Type, Unicode, @@ -410,16 +411,19 @@ def _valid_badge_base_url(self, proposal): Currently, only paths are supported, and they are expected to be available on all the hosts. + + Set to empty to disable the docker socket mount. """, ) @validate("build_docker_host") def docker_build_host_validate(self, proposal): - parts = urlparse(proposal.value) - if parts.scheme != "unix" or parts.netloc != "": - raise TraitError( - "Only unix domain sockets on same node are supported for build_docker_host" - ) + if proposal.value: + parts = urlparse(proposal.value) + if parts.scheme != "unix" or parts.netloc != "": + raise TraitError( + "Only unix domain sockets on same node are supported for build_docker_host" + ) return proposal.value build_docker_config = Dict( @@ -499,6 +503,17 @@ def _default_build_namespace(self): config=True, ) + build_capabilities = List( + [], + help=""" + Additional Kubernetes capabilities to add to the build container + + If the special string "privileged" is found the container is run in + privileged mode and other capabilities are ignored. + """, + config=True, + ) + build_node_selector = Dict( {}, config=True, @@ -802,6 +817,7 @@ def initialize(self, *args, **kwargs): "ban_networks_min_prefix_len": self.ban_networks_min_prefix_len, "build_namespace": self.build_namespace, "build_image": self.build_image, + "build_capabilities": self.build_capabilities, "build_node_selector": self.build_node_selector, "build_pool": self.build_pool, "build_token_check_origin": self.build_token_check_origin, diff --git a/binderhub/build.py b/binderhub/build.py index 6baa09d25..52c4e717a 100644 --- a/binderhub/build.py +++ b/binderhub/build.py @@ -83,6 +83,7 @@ def __init__( build_image, docker_host, image_name, + build_capabilities=None, git_credentials=None, push_secret=None, memory_limit=0, @@ -118,9 +119,14 @@ def __init__( The docker socket to use for building the image. Must be a unix domain socket on a filesystem path accessible on the node in which the build pod is running. + If empty no socket is mounted. image_name : str Full name of the image to build. Includes the tag. Passed through to repo2docker. + build_capabilities: [str] + Capabilities to add to the build pod. + If the special string "privileged" is found the container is run in + privileged mode and other capabilities are ignored. git_credentials : str Git credentials to use to clone private repositories. Passed through to repo2docker via the GIT_CREDENTIAL_ENV environment @@ -162,6 +168,7 @@ def __init__( self.image_name = image_name self.push_secret = push_secret self.build_image = build_image + self.build_capabilities = build_capabilities or [] self.main_loop = IOLoop.current() self.memory_limit = memory_limit self.memory_request = memory_request @@ -350,20 +357,24 @@ def submit(self): Progress of the build can be monitored by listening for items in the Queue passed to the constructor as `q`. """ - volume_mounts = [ - client.V1VolumeMount( - mount_path="/var/run/docker.sock", name="docker-socket" + volume_mounts = [] + volumes = [] + + if self.docker_host: + volume_mounts.append( + client.V1VolumeMount( + mount_path="/var/run/docker.sock", name="docker-socket" + ) ) - ] - docker_socket_path = urlparse(self.docker_host).path - volumes = [ - client.V1Volume( - name="docker-socket", - host_path=client.V1HostPathVolumeSource( - path=docker_socket_path, type="Socket" - ), + docker_socket_path = urlparse(self.docker_host).path + volumes.append( + client.V1Volume( + name="docker-socket", + host_path=client.V1HostPathVolumeSource( + path=docker_socket_path, type="Socket" + ), + ) ) - ] if self.push_secret: volume_mounts.append( @@ -382,6 +393,13 @@ def submit(self): client.V1EnvVar(name="GIT_CREDENTIAL_ENV", value=self.git_credentials) ) + if "privileged" in self.build_capabilities: + security_context = client.V1SecurityContext(privileged=True) + else: + security_context = client.V1SecurityContext( + capabilities=client.V1Capabilities(add=self.build_capabilities) + ) + self.pod = client.V1Pod( metadata=client.V1ObjectMeta( name=self.name, @@ -405,6 +423,7 @@ def submit(self): requests={"memory": self.memory_request}, ), env=env, + security_context=security_context, ) ], tolerations=[ diff --git a/binderhub/build_local.py b/binderhub/build_local.py index b356e4138..8cb0d6a77 100644 --- a/binderhub/build_local.py +++ b/binderhub/build_local.py @@ -124,6 +124,7 @@ def __init__( build_image, docker_host, image_name, + build_capabilities=None, git_credentials=None, push_secret=None, memory_limit=0, @@ -151,6 +152,7 @@ def __init__( Ref of repository to build Passed through to repo2docker. build_image : ignored + build_capabilities: ignored docker_host : ignored image_name : str Full name of the image to build. Includes the tag. diff --git a/binderhub/builder.py b/binderhub/builder.py index fdbc391c9..0e53cded2 100644 --- a/binderhub/builder.py +++ b/binderhub/builder.py @@ -436,6 +436,7 @@ async def get(self, provider_prefix, _unescaped_spec): image_name=image_name, push_secret=push_secret, build_image=self.settings["build_image"], + build_capabilities=self.settings["build_capabilities"], memory_limit=self.settings["build_memory_limit"], memory_request=self.settings["build_memory_request"], docker_host=self.settings["build_docker_host"],