[lxc-devel] [pylxd/master] Enable decoding control over stdout/stderr on execute
ajkavanagh on Github
lxc-bot at linuxcontainers.org
Tue Mar 20 17:14:43 UTC 2018
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 788 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180320/9f8019ec/attachment.bin>
-------------- next part --------------
From 201f0e2d61fa3d77eb8485c0594542c74c31bee3 Mon Sep 17 00:00:00 2001
From: Alex Kavanagh <alex at ajkavanagh.co.uk>
Date: Tue, 20 Mar 2018 17:11:50 +0000
Subject: [PATCH] Enable decoding control over stdout/stderr on execute
This patch adds support to control the decoding of the stdout
and stderr when executing commands in a container. It is possible to
force a particular decode, or just get the raw buffers. The existing
behaviour of trying to decode to utf-8 is retained if no decode or
encoding paramters are passed to the execute method.
Tests are in the integration/test_containers.py file as it's tricky to
test properly in a unit test.
Signed-off-by: Alex Kavanagh <alex at ajkavanagh.co.uk>
---
integration/test_containers.py | 24 +++++++++++++++++++++
pylxd/models/container.py | 47 +++++++++++++++++++++++++++++++++++-------
2 files changed, 63 insertions(+), 8 deletions(-)
diff --git a/integration/test_containers.py b/integration/test_containers.py
index 75c6df8..ae68109 100644
--- a/integration/test_containers.py
+++ b/integration/test_containers.py
@@ -155,6 +155,30 @@ def test_execute(self):
self.assertEqual('test\n', result.stdout)
self.assertEqual('', result.stderr)
+ def test_execute_no_decode(self):
+ """A command is executed on the container that isn't utf-8 decodable
+ """
+ self.container.start(wait=True)
+ self.addCleanup(self.container.stop, wait=True)
+
+ result = self.container.execute(['printf', '\\xff'], decode=None)
+
+ self.assertEqual(0, result.exit_code)
+ self.assertEqual(b'\xff', result.stdout)
+ self.assertEqual(b'', result.stderr)
+
+ def test_execute_force_decode(self):
+ """A command is executed and force output to ascii"""
+ self.container.start(wait=True)
+ self.addCleanup(self.container.stop, wait=True)
+
+ result = self.container.execute(['printf', 'qu\\xe9'], decode=True,
+ encoding='latin1')
+
+ self.assertEqual(0, result.exit_code)
+ self.assertEqual('qué', result.stdout)
+ self.assertEqual('', result.stderr)
+
def test_publish(self):
"""A container is published."""
image = self.container.publish(wait=True)
diff --git a/pylxd/models/container.py b/pylxd/models/container.py
index ca03c2a..4034cb5 100644
--- a/pylxd/models/container.py
+++ b/pylxd/models/container.py
@@ -255,11 +255,26 @@ def unfreeze(self, timeout=30, force=True, wait=False):
force=force,
wait=wait)
- def execute(self, commands, environment={}):
+ def execute(self, commands, environment={}, encoding=None, decode=True):
"""Execute a command on the container.
In pylxd 2.2, this method will be renamed `execute` and the existing
`execute` method removed.
+
+ :param commands: The command and arguments as a list of strings
+ :type commands: [str]
+ :param environment: The environment variables to pass with the command
+ :type environment: {str: str}
+ :param encoding: The encoding to use for stdout/stderr if the param
+ decode is True. If encoding is None, then no override is
+ performed and whatever the existing encoding from LXD is used.
+ :type encoding: str
+ :param decode: Whether to decode the stdout/stderr or just return the
+ raw buffers.
+ :type decode: bool
+ :raises ValueError: if the ws4py library is not installed.
+ :returns: The return value, stdout and stdin
+ :rtype: _ContainerExecuteResult() namedtuple
"""
if not _ws4py_installed:
raise ValueError(
@@ -283,11 +298,13 @@ def execute(self, commands, environment={}):
stdin.resource = '{}?secret={}'.format(parsed.path, fds['0'])
stdin.connect()
stdout = _CommandWebsocketClient(
- manager, self.client.websocket_url)
+ manager, self.client.websocket_url,
+ encoding=encoding, decode=decode)
stdout.resource = '{}?secret={}'.format(parsed.path, fds['1'])
stdout.connect()
stderr = _CommandWebsocketClient(
- manager, self.client.websocket_url)
+ manager, self.client.websocket_url,
+ encoding=encoding, decode=decode)
stderr.resource = '{}?secret={}'.format(parsed.path, fds['2'])
stderr.connect()
@@ -383,8 +400,15 @@ def publish(self, public=False, wait=False):
class _CommandWebsocketClient(WebSocketBaseClient): # pragma: no cover
+ """Handle a websocket for container.execute(...) and manage decoding of the
+ returned values depending on 'decode' and 'encoding' parameters.
+ """
+
def __init__(self, manager, *args, **kwargs):
self.manager = manager
+ self.decode = kwargs.pop('decode', True)
+ self.encoding = kwargs.pop('encoding', None)
+ self.message_encoding = None
super(_CommandWebsocketClient, self).__init__(*args, **kwargs)
def handshake_ok(self):
@@ -392,14 +416,21 @@ def handshake_ok(self):
self.buffer = []
def received_message(self, message):
- if message.encoding:
- self.buffer.append(message.data.decode(message.encoding))
- else:
- self.buffer.append(message.data.decode('utf-8'))
+ if message.encoding and self.message_encoding is None:
+ self.message_encoding = message.encoding
+ self.buffer.append(message.data)
@property
def data(self):
- return ''.join(self.buffer)
+ buffer = b''.join(self.buffer)
+ if self.decode:
+ if self.encoding:
+ return buffer.decode(self.encoding)
+ if self.message_encoding:
+ return buffer.decode(self.message_encoding)
+ # This is the backwards compatible "always decode to utf-8"
+ return buffer.decode('utf-8')
+ return buffer
class _StdinWebsocket(WebSocketBaseClient): # pragma: no cover
More information about the lxc-devel
mailing list