def export(self, chunk_size: int = api.DEFAULT_CHUNK_SIZE) -> Iterator[bytes]: """Download container's filesystem contents as a tar archive. Args: chunk_size: <= number of bytes to return for each iteration of the generator. Yields: tarball in size/chunk_size chunks Raises: NotFound when container has been removed from service APIError when service reports an error """ response = self.client.get(f"/containers/{self.id}/export", stream=True) if response.status_code != 200: body = response.json() if response.status_code == 404: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"]) for out in response.iter_content(chunk_size=chunk_size): yield out
def remove(self, name: Union[Volume, str], force: Optional[bool] = None) -> None: """Delete a volume. Args: name: Identifier for Volume to be deleted. force: When true, force deletion of in-use volume Raises: APIError: when service reports an error Notes: Podman only. """ if isinstance(name, Volume): name = name.name response = self.client.delete(f"/volumes/{name}", params={"force": force}) if response.status_code == requests.codes.no_content: return data = response.json() if response.status_code == requests.codes.not_found: raise NotFound(data["cause"], response=response, explanation=data["message"]) raise APIError(data["cause"], response=response, explanation=data["message"])
def top(self, **kwargs) -> Union[Iterator[Dict[str, Any]], Dict[str, Any]]: """Report on running processes in the container. Keyword Args: ps_args (str): When given, arguments will be passed to ps stream (bool): When True, repeatedly return results. Default: False Raises: NotFound: when the container no longer exists APIError: when the service reports an error """ params = { "ps_args": kwargs.get("ps_args"), "stream": kwargs.get("stream", False), } response = self.client.get(f"/containers/{self.id}/top", params=params) if response.status_code != requests.codes.okay: body = response.json() if response.status_code == requests.codes.not_found: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"]) if params["stream"]: self._top_helper(response) return response.json()
def wait(self, **kwargs) -> Dict[str, Any]: """Block until the container enters given state. Keyword Args: condition (str): Container state on which to release, values: not-running (default), next-exit or removed. timeout (int): Number of seconds to wait for the container to stop. Returns: Keys: - StatusCode (int): Container's exit code - Error["Message"] (str): Error message from container Raises: NotFound: When Container not found ReadTimeoutError: When timeout is exceeded APIError: When service returns an error """ condition = kwargs.get("condition") if isinstance(condition, str): condition = [condition] response = self.client.post(f"/containers/{self.id}/wait", params={"condition": condition}) body = response.json() if response.status_code == requests.codes.okay: return body if response.status_code == requests.codes.not_found: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"])
def get(self, network_id: str, *_, **kwargs) -> Network: # pylint: disable=arguments-differ """Return information for network network_id. Args: network_id: Network name or id. Keyword Args: compatible (bool): Should compatible API be used. Default: True Raises: NotFound: Network does not exist. APIError: Error returned by service. Note: The compatible API is used, this allows the server to provide dynamic fields. id is the most important example. """ compatible = kwargs.get("compatible", True) path = f"/networks/{network_id}" + ("" if compatible else "/json") response = self.client.get(path, compatible=compatible) body = response.json() if response.status_code != 200: if response.status_code == 404: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"]) if not compatible: body = body[0] return self.prepare_model(body)
def stats(self, **kwargs) -> Union[Sequence[Dict[str, bytes]], bytes]: """Return statistics for container. Keyword Args: decode (bool): If True and stream is True, stream will be decoded into dict's. Default: False. stream (bool): Stream statistics until cancelled. Default: True. Raises: APIError when service reports an error """ # FIXME Errors in stream are not handled, need content and json to read Errors. stream = kwargs.get("stream", True) decode = kwargs.get("decode", False) params = { "containers": self.id, "stream": stream, } response = self.client.get("/containers/stats", params=params) if response.status_code != requests.codes.okay: body = response.json() if response.status_code == requests.codes.not_found: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"]) if stream: return self._stats_helper(decode, response.iter_lines()) with io.StringIO() as buffer: for entry in response.text: buffer.writer(json.dumps(entry) + "\n") return buffer.getvalue()
def get_archive( self, path: str, chunk_size: int = api.DEFAULT_CHUNK_SIZE ) -> Tuple[Iterable, Dict[str, Any]]: """Download a file or folder from the container's filesystem. Args: path: Path to file or folder. chunk_size: <= number of bytes to return for each iteration of the generator. Returns: First item is a raw tar data stream. Second item is a dict containing stat information on the specified path. """ response = self.client.get(f"/containers/{self.id}/archive", params={"path": [path]}) if response.status_code != 200: body = response.json() if response.status_code == 404: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"]) stat = response.headers.get('x-docker-container-path-stat', None) stat = api.decode_header(stat) return response.iter_content(chunk_size=chunk_size), stat
def stats(self, **kwargs) -> Dict[str, Any]: """Resource usage statistics for the containers in pods. Keyword Args: all (bool): Provide statistics for all running pods. name (Union[str, List[str]]): Pods to include in report. Raises: NotFound when pod not found. APIError when service reports an error. """ if "all" in kwargs and "name" in kwargs: raise ValueError( "Keywords 'all' and 'name' are mutually exclusive.") params = { "all": kwargs.get("all"), "namesOrIDs": kwargs.get("name"), } response = self.client.get("/pods/stats", params=params) body = response.json() if response.status_code == requests.codes.okay: return body if response.status_code == requests.codes.not_found: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"])
def pause(self) -> None: """Pause processes within the container.""" response = self.client.post(f"/containers/{self.id}/pause") if response.status_code == requests.codes.no_content: return body = response.json() if response.status_code == requests.codes.not_found: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"])
def diff(self) -> List[Dict[str, int]]: """Report changes of a container's filesystem. Raises: APIError: when service reports error """ response = self.client.get(f"/containers/{self.id}/changes") body = response.json() if response.status_code == requests.codes.okay: return body if response.status_code == requests.codes.not_found: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"])
def commit(self, repository: str = None, tag: str = None, **kwargs) -> Image: """Save container to given repository using given parameters. Args: repository: Where to save Image tag: Tag to push with Image Keyword Args: author (str): Name of commit author changes (List[str]): Instructions to apply during commit comment (List[str]): Instructions to apply while committing in Dockerfile format conf (Dict[str, Any]): Ignored format (str): Format of the image manifest and metadata message (str): Commit message to include with Image pause (bool): Pause the container before committing it See https://docs.podman.io/en/latest/_static/api.html#operation/libpodCommitContainer """ params = { "author": kwargs.get("author", None), "changes": kwargs.get("changes", None), "comment": kwargs.get("comment", None), "container": self.id, "format": kwargs.get("format", None), "pause": kwargs.get("pause", None), "repo": repository, "tag": tag, } response = self.client.post("/commit", params=params) body = response.json() if response.status_code != 201: if response.status_code == 404: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"]) return ImagesManager(client=self.client).get(body["ID"])
def resize(self, height: int = None, width: int = None) -> None: """Resize the tty session. Args: height: New height of tty session. width: New width of tty session. """ params = { "h": height, "w": width, } response = self.client.post(f"/containers/{self.id}/resize", params=params) if response.status_code == requests.codes.okay: return body = response.json() if response.status_code == requests.codes.not_found: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"])
def get(self, container_id: str) -> Container: # pylint: disable=arguments-differ """Get container by name or id. Args: container_id: Container name or id. Raises: NotFound: Container does not exist. APIError: Error return by service. """ container_id = urllib.parse.quote_plus(container_id) response = self.client.get(f"/containers/{container_id}/json") body = response.json() if response.status_code == 200: return self.prepare_model(body) if response.status_code == 404: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"])
def remove(self, force: bool = None): """Delete this volume. Args: force: When true, force deletion of volume Raises: APIError when service reports an error """ params = {"force": force} response = self.client.delete(f"/volumes/{self.name}", params=params) if response.status_code == 204: return data = response.json() if response.status_code == 404: raise NotFound(data["cause"], response=response, explanation=data["message"]) raise APIError(data["cause"], response=response, explanation=data["message"])
def get(self, volume_id: str) -> Volume: # pylint: disable=arguments-differ """Returns and volume by name or id. Args: volume_id: Volume id or name for which to search Raises: NotFound if volume could not be found APIError when service reports an error """ response = self.client.get(f"/volumes/{volume_id}") if response.status_code == 404: raise NotFound( response.text, response=response, explanation=f"Failed to find volume '{volume_id}'" ) data = response.json() if response.status_code == 200: return self.prepare_model(data) raise APIError(data["cause"], response=response, explanation=data["message"])
def logs(self, **kwargs) -> Union[bytes, Iterator[bytes]]: """Get logs from the container. Keyword Args: stdout (bool): Include stdout. Default: True stderr (bool): Include stderr. Default: True stream (bool): Return generator of strings as the response. Default: False timestamps (bool): Show timestamps in output. Default: False tail (Union[str, int]): Output specified number of lines at the end of logs. Integer representing the number of lines to display, or the string all. Default: all since (Union[datetime, int]): Show logs since a given datetime or integer epoch (in seconds) follow (bool): Follow log output. Default: False until (Union[datetime, int]): Show logs that occurred before the given datetime or integer epoch (in seconds) """ params = { "follow": kwargs.get("follow", kwargs.get("stream", None)), "since": api.prepare_timestamp(kwargs.get("since")), "stderr": kwargs.get("stderr", None), "stdout": kwargs.get("stdout", True), "tail": kwargs.get("tail"), "timestamps": kwargs.get("timestamps"), "until": api.prepare_timestamp(kwargs.get("until")), } response = self.client.get(f"/containers/{self.id}/logs", params=params) if response.status_code != requests.codes.okay: body = response.json() if response.status_code == requests.codes.not_found: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"]) if bool(kwargs.get("stream", False)): return api.stream_frames(response) return api.frames(response)
def remove(self, force: Optional[bool] = None) -> None: """Delete this volume. Args: force: When true, force deletion of in-use volume Raises: APIError when service reports an error """ response = self.client.delete(f"/volumes/{self.name}", params={"force": force}) if response.status_code == requests.codes.no_content: return data = response.json() if response.status_code == requests.codes.not_found: raise NotFound(data["cause"], response=response, explanation=data["message"]) raise APIError(data["cause"], response=response, explanation=data["message"])
def get(self, pod_id: str) -> Pod: # pylint: disable=arguments-differ """Return information for Pod by name or id. Args: pod_id: Pod name or id. Raises: NotFound: When network does not exist. APIError: When error returned by service. """ response = self.client.get(f"/pods/{pod_id}/json") body = response.json() if response.status_code == requests.codes.okay: return self.prepare_model(attrs=body) if response.status_code == requests.codes.not_found: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"])
def top(self, **kwargs) -> Dict[str, Any]: """Report on running processes in container. Keyword Args: ps_args (str): Optional arguments passed to ps """ params = { "ps_args": kwargs.get("ps_args", None), "stream": kwargs.get("stream", None), } response = self.client.get(f"/containers/{self.id}/top", params=params) body = response.json() if response.status_code != 200: if response.status_code == 404: raise NotFound(body["cause"], response=response, explanation=body["message"]) raise APIError(body["cause"], response=response, explanation=body["message"]) return body