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 inspect_network(self, network_name: str) -> Dict[str, Union[Dict, str]]: try: return self.client().networks.get(network_name).attrs except NotFound: raise NoSuchNetwork(network_name) except APIError as e: raise ContainerException() from e
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 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 list_containers(self, filter: Union[List[str], str, None] = None, all=True) -> List[dict]: filter = [filter] if isinstance(filter, str) else filter cmd = self._docker_cmd() cmd.append("ps") if all: cmd.append("-a") options = [] if filter: options += [y for filter_item in filter for y in ["--filter", filter_item]] cmd += options cmd.append("--format") cmd.append("{{json . }}") try: cmd_result = run(cmd).strip() except subprocess.CalledProcessError as e: raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr ) container_list = [] if cmd_result: container_list = [json.loads(line) for line in cmd_result.splitlines()] result = [] for container in container_list: result.append( { "id": container["ID"], "image": container["Image"], "name": container["Names"], "status": container["State"], "labels": container["Labels"], } ) return result
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 list_containers(self, filter: Union[List[str], str, None] = None, all=True) -> List[dict]: if filter: filter = [filter] if isinstance(filter, str) else filter filter = dict([f.split("=", 1) for f in filter]) LOG.debug("Listing containers with filters: %s", filter) try: container_list = self.client().containers.list(filters=filter, all=all) result = [] for container in container_list: try: result.append({ "id": container.id, "image": container.image, "name": container.name, "status": container.status, "labels": container.labels, }) except Exception as e: LOG.error(f"Error checking container {container}: {e}") return result except APIError: raise ContainerException()
def push_image(self, docker_image: str) -> None: LOG.debug("Pushing Docker image: %s", docker_image) try: result = self.client().images.push(docker_image) # some SDK clients (e.g., 5.0.0) seem to return an error string, instead of raising if isinstance(result, (str, bytes)) and '"errorDetail"' in to_str(result): if "image does not exist locally" in to_str(result): raise NoSuchImage(docker_image) if "is denied" in to_str(result): raise AccessDenied(docker_image) if "connection refused" in to_str(result): raise RegistryConnectionError(result) raise ContainerException(result) except ImageNotFound: raise NoSuchImage(docker_image) 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 tag_image(self, source_ref: str, target_name: str) -> None: try: LOG.debug("Tagging Docker image '%s' as '%s'", source_ref, target_name) image = self.client().images.get(source_ref) image.tag(target_name) except APIError as e: if e.status_code == 404: raise NoSuchImage(source_ref) raise ContainerException("Unable to tag Docker image") from e
def pull_image(self, docker_image: str) -> None: LOG.debug("Pulling Docker image: %s", docker_image) # some path in the docker image string indicates a custom repository try: self.client().images.pull(docker_image) except ImageNotFound: raise NoSuchImage(docker_image) except APIError as e: raise ContainerException() from e
def remove_image(self, image: str, force: bool = True): LOG.debug("Removing image %s %s", image, "(forced)" if force else "") try: self.client().images.remove(image=image, force=force) except ImageNotFound: if not force: raise NoSuchImage(image) except APIError as e: raise ContainerException() from e
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 inspect_image(self, image_name: str, pull: bool = True) -> Dict[str, Union[Dict, str]]: try: return self.client().images.get(image_name).attrs except NotFound: if pull: self.pull_image(image_name) return self.inspect_image(image_name, pull=False) raise NoSuchImage(image_name) except APIError as e: raise ContainerException() from e
def get_docker_image_names(self, strip_latest=True, include_tags=True): try: images = self.client().images.list() image_names = [tag for image in images for tag in image.tags if image.tags] if not include_tags: image_names = list(map(lambda image_name: image_name.split(":")[0], image_names)) if strip_latest: Util.append_without_latest(image_names) return image_names 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 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)
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 pull_image(self, docker_image: str) -> None: cmd = self._docker_cmd() cmd += ["pull", docker_image] LOG.debug("Pulling image with cmd: %s", cmd) try: run(cmd) except subprocess.CalledProcessError as e: if "pull access denied" in to_str(e.stdout): raise NoSuchImage(docker_image) raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr)
def tag_image(self, source_ref: str, target_name: str) -> None: cmd = self._docker_cmd() cmd += ["tag", source_ref, target_name] LOG.debug("Tagging Docker image %s as %s", source_ref, target_name) try: run(cmd) except subprocess.CalledProcessError as e: if "No such image" in to_str(e.stdout): raise NoSuchImage(source_ref) raise ContainerException( f"Docker process returned with error code {e.returncode}", e.stdout, e.stderr) from e
def build_image(self, dockerfile_path: str, image_name: str, context_path: str = None): cmd = self._docker_cmd() dockerfile_path = Util.resolve_dockerfile_path(dockerfile_path) context_path = context_path or os.path.dirname(dockerfile_path) cmd += ["build", "-t", image_name, "-f", dockerfile_path, context_path] LOG.debug("Building Docker image: %s", cmd) try: run(cmd) except subprocess.CalledProcessError as e: raise ContainerException( f"Docker build process returned with error code {e.returncode}", e.stdout, e.stderr ) from e
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 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 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 build_image(self, dockerfile_path: str, image_name: str, context_path: str = None): try: dockerfile_path = Util.resolve_dockerfile_path(dockerfile_path) context_path = context_path or os.path.dirname(dockerfile_path) LOG.debug("Building Docker image %s from %s", image_name, dockerfile_path) self.client().images.build( path=context_path, dockerfile=dockerfile_path, tag=image_name, rm=True, ) except APIError as e: raise ContainerException("Unable to build Docker image") 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 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 _inspect_object(self, object_name_or_id: str) -> Dict[str, Union[Dict, str]]: cmd = self._docker_cmd() cmd += ["inspect", "--format", "{{json .}}", object_name_or_id] try: cmd_result = run(cmd) except subprocess.CalledProcessError as e: if "No such object" in to_str(e.stdout): raise NoSuchObject(object_name_or_id, stdout=e.stdout, stderr=e.stderr) else: raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr ) image_data = json.loads(cmd_result.strip()) return image_data
def get_container_status(self, container_name: str) -> DockerContainerStatus: # LOG.debug("Getting container status for container: %s", container_name) # too verbose try: container = self.client().containers.get(container_name) if container.status == "running": return DockerContainerStatus.UP elif container.status == "paused": return DockerContainerStatus.PAUSED else: return DockerContainerStatus.DOWN except NotFound: return DockerContainerStatus.NON_EXISTENT except APIError as e: raise ContainerException() from e
def remove_image(self, image: str, force: bool = True) -> None: cmd = self._docker_cmd() cmd += ["rmi", image] if force: cmd += ["--force"] LOG.debug("Removing image %s %s", image, "(forced)" if force else "") try: run(cmd) except subprocess.CalledProcessError as e: if "No such image" in to_str(e.stdout): raise NoSuchImage(image, stdout=e.stdout, stderr=e.stderr) else: raise ContainerException( "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr)