def connect_container_to_network(self, network_name: str, container_name_or_id: str, aliases: Optional[List] = None) -> None: LOG.debug( "Connecting container '%s' to network '%s' with aliases '%s'", container_name_or_id, network_name, aliases, ) cmd = self._docker_cmd() cmd += ["network", "connect"] if aliases: cmd += ["--alias", ",".join(aliases)] cmd += [network_name, container_name_or_id] try: run(cmd) except subprocess.CalledProcessError as e: stdout_str = to_str(e.stdout) if re.match(r".*network (.*) not found.*", stdout_str): raise NoSuchNetwork(network_name=network_name) elif "No such container" in stdout_str: raise NoSuchContainer(container_name_or_id, stdout=e.stdout, stderr=e.stderr) else: raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr)
def _run_async_cmd( self, cmd: List[str], stdin: bytes, container_name: str, image_name=None ) -> Tuple[bytes, bytes]: kwargs = { "inherit_env": True, "asynchronous": True, "stderr": subprocess.PIPE, "outfile": self.default_run_outfile or subprocess.PIPE, } if stdin: kwargs["stdin"] = True try: process = run(cmd, **kwargs) stdout, stderr = process.communicate(input=stdin) if process.returncode != 0: raise subprocess.CalledProcessError( process.returncode, cmd, stdout, stderr, ) else: return stdout, stderr except subprocess.CalledProcessError as e: stderr_str = to_str(e.stderr) if "Unable to find image" in stderr_str: raise NoSuchImage(image_name or "", stdout=e.stdout, stderr=e.stderr) if "No such container" in stderr_str: raise NoSuchContainer(container_name, stdout=e.stdout, stderr=e.stderr) raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr )
def inspect_container(self, container_name_or_id: str) -> Dict[str, Union[Dict, str]]: try: return self.client().containers.get(container_name_or_id).attrs except NotFound: raise NoSuchContainer(container_name_or_id) except APIError as e: raise ContainerException() from e
def stream_container_logs(self, container_name_or_id: str) -> CancellableStream: try: container = self.client().containers.get(container_name_or_id) return container.logs(stream=True, follow=True) except NotFound: raise NoSuchContainer(container_name_or_id) except APIError as e: raise ContainerException() from e
def exec_in_container( self, container_name_or_id: str, command: Union[List[str], str], interactive=False, detach=False, env_vars: Optional[Dict[str, Optional[str]]] = None, stdin: Optional[bytes] = None, user: Optional[str] = None, workdir: Optional[str] = None, ) -> Tuple[bytes, bytes]: LOG.debug("Executing command in container %s: %s", container_name_or_id, command) try: container: Container = self.client().containers.get( container_name_or_id) result = container.exec_run( cmd=command, environment=env_vars, user=user, detach=detach, stdin=interactive and bool(stdin), socket=interactive and bool(stdin), stdout=True, stderr=True, demux=True, workdir=workdir, ) tty = False if interactive and stdin: # result is a socket sock = result[1] sock = sock._sock if hasattr(sock, "_sock") else sock with sock: try: sock.sendall(stdin) sock.shutdown(socket.SHUT_WR) stdout, stderr = self._read_from_sock(sock, tty) return stdout, stderr except socket.timeout: pass else: if detach: return b"", b"" return_code = result[0] if isinstance(result[1], bytes): stdout = result[1] stderr = b"" else: stdout, stderr = result[1] if return_code != 0: raise ContainerException( "Exec command returned with exit code %s" % return_code, stdout, stderr) return stdout, stderr except ContainerError: raise NoSuchContainer(container_name_or_id) except APIError: raise ContainerException()
def unpause_container(self, container_name: str) -> None: LOG.debug("Unpausing container: %s", container_name) try: container = self.client().containers.get(container_name) container.unpause() except NotFound: raise NoSuchContainer(container_name) except APIError as e: raise ContainerException() from e
def stop_container(self, container_name: str, timeout: int = None) -> None: if timeout is None: timeout = self.STOP_TIMEOUT LOG.debug("Stopping container: %s", container_name) try: container = self.client().containers.get(container_name) container.stop(timeout=timeout) except NotFound: raise NoSuchContainer(container_name) except APIError as e: raise ContainerException() from e
def get_container_logs(self, container_name_or_id: str, safe=False) -> str: try: container = self.client().containers.get(container_name_or_id) return to_str(container.logs()) except NotFound: if safe: return "" raise NoSuchContainer(container_name_or_id) except APIError as e: if safe: return "" raise ContainerException() from e
def unpause_container(self, container_name: str) -> None: cmd = self._docker_cmd() cmd += ["unpause", container_name] LOG.debug("Unpausing container with cmd %s", cmd) try: run(cmd) except subprocess.CalledProcessError as e: if "No such container" in to_str(e.stdout): raise NoSuchContainer(container_name, stdout=e.stdout, stderr=e.stderr) else: raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr )
def copy_from_container(self, container_name: str, local_path: str, container_path: str) -> None: cmd = self._docker_cmd() cmd += ["cp", f"{container_name}:{container_path}", local_path] LOG.debug("Copying from container with cmd: %s", cmd) try: run(cmd) except subprocess.CalledProcessError as e: if "No such container" in to_str(e.stdout): raise NoSuchContainer(container_name) raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr)
def remove_container(self, container_name: str, force=True, check_existence=False) -> None: LOG.debug("Removing container: %s", container_name) if check_existence and container_name not in self.get_running_container_names(): LOG.debug("Aborting removing due to check_existence check") return try: container = self.client().containers.get(container_name) container.remove(force=force) except NotFound: if not force: raise NoSuchContainer(container_name) except APIError as e: raise ContainerException() from e
def copy_into_container( self, container_name: str, local_path: str, container_path: str ) -> None: # TODO behave like https://docs.docker.com/engine/reference/commandline/cp/ LOG.debug("Copying file %s into %s:%s", local_path, container_name, container_path) try: container = self.client().containers.get(container_name) target_exists, target_isdir = self._container_path_info(container, container_path) target_path = container_path if target_isdir else os.path.dirname(container_path) with Util.tar_path(local_path, container_path, is_dir=target_isdir) as tar: container.put_archive(target_path, tar) except NotFound: raise NoSuchContainer(container_name) except APIError as e: raise ContainerException() from e
def get_container_logs(self, container_name_or_id: str, safe=False) -> str: cmd = self._docker_cmd() cmd += ["logs", container_name_or_id] try: return run(cmd) except subprocess.CalledProcessError as e: if safe: return "" if "No such container" in to_str(e.stdout): raise NoSuchContainer(container_name_or_id, stdout=e.stdout, stderr=e.stderr) else: raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr )
def commit( self, container_name_or_id: str, image_name: str, image_tag: str, ): LOG.debug("Creating image from container %s as %s:%s", container_name_or_id, image_name, image_tag) try: container = self.client().containers.get(container_name_or_id) container.commit(repository=image_name, tag=image_tag) except NotFound: raise NoSuchContainer(container_name_or_id) except APIError: raise ContainerException()
def copy_from_container( self, container_name: str, local_path: str, container_path: str, ) -> None: LOG.debug("Copying file from %s:%s to %s", container_name, container_path, local_path) try: container = self.client().containers.get(container_name) bits, _ = container.get_archive(container_path) Util.untar_to_path(bits, local_path) except NotFound: raise NoSuchContainer(container_name) except APIError as e: raise ContainerException() from e
def disconnect_container_from_network(self, network_name: str, container_name_or_id: str) -> None: LOG.debug("Disconnecting container '%s' from network '%s'", container_name_or_id, network_name) try: try: network = self.client().networks.get(network_name) except NotFound: raise NoSuchNetwork(network_name) try: network.disconnect(container_name_or_id) except NotFound: raise NoSuchContainer(container_name_or_id) except APIError: raise ContainerException()
def stop_container(self, container_name: str, timeout: int = None) -> None: if timeout is None: timeout = self.STOP_TIMEOUT cmd = self._docker_cmd() cmd += ["stop", "--time", str(timeout), container_name] LOG.debug("Stopping container with cmd %s", cmd) try: run(cmd) except subprocess.CalledProcessError as e: if "No such container" in to_str(e.stdout): raise NoSuchContainer(container_name, stdout=e.stdout, stderr=e.stderr) else: raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr )
def remove_container(self, container_name: str, force=True, check_existence=False) -> None: if check_existence and container_name not in self.get_running_container_names(): return cmd = self._docker_cmd() + ["rm"] if force: cmd.append("-f") cmd.append(container_name) LOG.debug("Removing container with cmd %s", cmd) try: run(cmd) except subprocess.CalledProcessError as e: if "No such container" in to_str(e.stdout): raise NoSuchContainer(container_name, stdout=e.stdout, stderr=e.stderr) else: raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr )
def get_container_ip(self, container_name_or_id: str) -> str: cmd = self._docker_cmd() cmd += [ "inspect", "--format", "{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}", container_name_or_id, ] try: result = run(cmd).strip() return result.split(" ")[0] if result else "" except subprocess.CalledProcessError as e: if "No such object" in to_str(e.stdout): raise NoSuchContainer(container_name_or_id, stdout=e.stdout, stderr=e.stderr) else: raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr )
def connect_container_to_network( self, network_name: str, container_name_or_id: str, aliases: Optional[List] = None ) -> None: LOG.debug( "Connecting container '%s' to network '%s' with aliases '%s'", container_name_or_id, network_name, aliases, ) try: network = self.client().networks.get(network_name) except NotFound: raise NoSuchNetwork(network_name) try: network.connect(container=container_name_or_id, aliases=aliases) except NotFound: raise NoSuchContainer(container_name_or_id) except APIError as e: raise ContainerException() from e
def disconnect_container_from_network( self, network_name: str, container_name_or_id: str ) -> None: LOG.debug( "Disconnecting container '%s' from network '%s'", container_name_or_id, network_name ) cmd = self._docker_cmd() + ["network", "disconnect", network_name, container_name_or_id] try: run(cmd) except subprocess.CalledProcessError as e: stdout_str = to_str(e.stdout) if re.match(r".*network (.*) not found.*", stdout_str): raise NoSuchNetwork(network_name=network_name) elif "No such container" in stdout_str: raise NoSuchContainer(container_name_or_id, stdout=e.stdout, stderr=e.stderr) else: raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr )
def commit( self, container_name_or_id: str, image_name: str, image_tag: str, ): cmd = self._docker_cmd() cmd += ["commit", container_name_or_id, f"{image_name}:{image_tag}"] LOG.debug( "Creating image from container %s as %s:%s", container_name_or_id, image_name, image_tag ) try: run(cmd) except subprocess.CalledProcessError as e: if "No such container" in to_str(e.stdout): raise NoSuchContainer(container_name_or_id, stdout=e.stdout, stderr=e.stderr) else: raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr )
def start_container( self, container_name_or_id: str, stdin=None, interactive: bool = False, attach: bool = False, flags: Optional[str] = None, ) -> Tuple[bytes, bytes]: LOG.debug("Starting container %s", container_name_or_id) try: container = self.client().containers.get(container_name_or_id) stdout = to_bytes(container_name_or_id) stderr = b"" if interactive or attach: params = {"stdout": 1, "stderr": 1, "stream": 1} if interactive: params["stdin"] = 1 sock = container.attach_socket(params=params) sock = sock._sock if hasattr(sock, "_sock") else sock result_queue = queue.Queue() thread_started = threading.Event() start_waiting = threading.Event() # Note: We need to be careful about potential race conditions here - .wait() should happen right # after .start(). Hence starting a thread and asynchronously waiting for the container exit code def wait_for_result(*_): _exit_code = -1 try: thread_started.set() start_waiting.wait() _exit_code = container.wait()["StatusCode"] except APIError as e: _exit_code = 1 raise ContainerException(str(e)) finally: result_queue.put(_exit_code) # start listener thread start_worker_thread(wait_for_result) thread_started.wait() # start container container.start() # start awaiting container result start_waiting.set() # handle container input/output # under windows, the socket has no __enter__ / cannot be used as context manager # therefore try/finally instead of with here try: if stdin: sock.sendall(to_bytes(stdin)) sock.shutdown(socket.SHUT_WR) stdout, stderr = self._read_from_sock(sock, False) except socket.timeout: LOG.debug( f"Socket timeout when talking to the I/O streams of Docker container '{container_name_or_id}'" ) finally: sock.close() # get container exit code exit_code = result_queue.get() if exit_code: raise ContainerException( f"Docker container returned with exit code {exit_code}", stdout=stdout, stderr=stderr, ) else: container.start() return stdout, stderr except NotFound: raise NoSuchContainer(container_name_or_id) except APIError as e: raise ContainerException() from e
def inspect_container( self, container_name_or_id: str) -> Dict[str, Union[Dict, str]]: try: return self._inspect_object(container_name_or_id) except NoSuchObject as e: raise NoSuchContainer(container_name_or_id=e.object_id)