from .. import errors from .. import utils from ..types import CancellableStream class ExecApiMixin: @utils.check_resource('container') def exec_create(self, container, cmd, stdout=True, stderr=True, stdin=False, tty=False, privileged=False, user='', environment=None, workdir=None, detach_keys=None): """ Sets up an exec instance in a running container. Args: container (str): Target container where exec instance will be created cmd (str or list): Command to be executed stdout (bool): Attach to stdout. Default: ``True`` stderr (bool): Attach to stderr. Default: ``True`` stdin (bool): Attach to stdin. Default: ``False`` tty (bool): Allocate a pseudo-TTY. Default: False privileged (bool): Run as privileged. user (str): User to execute command as. Default: root environment (dict or list): A dictionary or a list of strings in the following format ``["PASSWORD=xxx"]`` or ``{"PASSWORD": "xxx"}``. workdir (str): Path to working directory for this exec session detach_keys (str): Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-` where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. ~/.docker/config.json is used by default. Returns: (dict): A dictionary with an exec ``Id`` key. Raises: :py:class:`docker.errors.APIError` If the server returns an error. """ if environment is not None and utils.version_lt(self._version, '1.25'): raise errors.InvalidVersion( 'Setting environment for exec is not supported in API < 1.25' ) if isinstance(cmd, str): cmd = utils.split_command(cmd) if isinstance(environment, dict): environment = utils.utils.format_environment(environment) data = { 'Container': container, 'User': user, 'Privileged': privileged, 'Tty': tty, 'AttachStdin': stdin, 'AttachStdout': stdout, 'AttachStderr': stderr, 'Cmd': cmd, 'Env': environment, } if workdir is not None: if utils.version_lt(self._version, '1.35'): raise errors.InvalidVersion( 'workdir is not supported for API version < 1.35' ) data['WorkingDir'] = workdir if detach_keys: data['detachKeys'] = detach_keys elif 'detachKeys' in self._general_configs: data['detachKeys'] = self._general_configs['detachKeys'] url = self._url('/containers/{0}/exec', container) res = self._post_json(url, data=data) return self._result(res, True) def exec_inspect(self, exec_id): """ Return low-level information about an exec command. Args: exec_id (str): ID of the exec instance Returns: (dict): Dictionary of values returned by the endpoint. Raises: :py:class:`docker.errors.APIError` If the server returns an error. """ if isinstance(exec_id, dict): exec_id = exec_id.get('Id') res = self._get(self._url("/exec/{0}/json", exec_id)) return self._result(res, True) def exec_resize(self, exec_id, height=None, width=None): """ Resize the tty session used by the specified exec command. Args: exec_id (str): ID of the exec instance height (int): Height of tty session width (int): Width of tty session """ if isinstance(exec_id, dict): exec_id = exec_id.get('Id') params = {'h': height, 'w': width} url = self._url("/exec/{0}/resize", exec_id) res = self._post(url, params=params) self._raise_for_status(res) @utils.check_resource('exec_id') def exec_start(self, exec_id, detach=False, tty=False, stream=False, socket=False, demux=False): """ Start a previously set up exec instance. Args: exec_id (str): ID of the exec instance detach (bool): If true, detach from the exec command. Default: False tty (bool): Allocate a pseudo-TTY. Default: False stream (bool): Return response data progressively as an iterator of strings, rather than a single string. socket (bool): Return the connection socket to allow custom read/write operations. Must be closed by the caller when done. demux (bool): Return stdout and stderr separately Returns: (generator or str or tuple): If ``stream=True``, a generator yielding response chunks. If ``socket=True``, a socket object for the connection. A string containing response data otherwise. If ``demux=True``, a tuple with two elements of type byte: stdout and stderr. Raises: :py:class:`docker.errors.APIError` If the server returns an error. """ # we want opened socket if socket == True data = { 'Tty': tty, 'Detach': detach } headers = {} if detach else { 'Connection': 'Upgrade', 'Upgrade': 'tcp' } res = self._post_json( self._url('/exec/{0}/start', exec_id), headers=headers, data=data, stream=True ) if detach: try: return self._result(res) finally: res.close() if socket: return self._get_raw_response_socket(res) output = self._read_from_socket(res, stream, tty=tty, demux=demux) if stream: return CancellableStream(output, res) else: return output