Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactive Execute on Container #348

Merged
merged 8 commits into from
Jan 18, 2019
Merged
2 changes: 2 additions & 0 deletions doc/source/containers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ Container methods
a list, in the form of `subprocess.Popen` with each item of the command
as a separate item in the list. Returns a tuple of `(exit_code, stdout, stderr)`.
This method will block while the command is executed.
- `interactive_execute` - Execute a command on the container. It will return
an interactive websocket and the execution only starts after a client connected to the websocket.
felix-engelmann marked this conversation as resolved.
Show resolved Hide resolved
- `migrate` - Migrate the container. The first argument is a client
connection to the destination server. This call is asynchronous, so
`wait=True` is optional. The container on the new client is returned.
Expand Down
35 changes: 35 additions & 0 deletions pylxd/models/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,41 @@ def execute(
return _ContainerExecuteResult(
operation.metadata['return'], stdout.data, stderr.data)

def interactive_execute(
self, commands, environment=None
):
felix-engelmann marked this conversation as resolved.
Show resolved Hide resolved
"""Execute a command on the container interactively and returns websockets.

felix-engelmann marked this conversation as resolved.
Show resolved Hide resolved
:param commands: The command and arguments as a list of strings
(most likely a shell)
:type commands: [str]
:param environment: The environment variables to pass with the command
:type environment: {str: str}
:returns: Two urls to an interactive websocket and a control socket
:rtype: {'ws':str,'control':str}
"""
if isinstance(commands, six.string_types):
raise TypeError("First argument must be a list.")

if environment is None:
environment = {}

response = self.api['exec'].post(json={
'command': commands,
'environment': environment,
'wait-for-websocket': True,
'interactive': True,
})

fds = response.json()['metadata']['metadata']['fds']
operation_id = response.json()['operation']\
.split('/')[-1].split('?')[0]
parsed = parse.urlparse(
self.client.api.operations[operation_id].websocket._api_endpoint)

return {'ws': '{}?secret={}'.format(parsed.path, fds['0']),
'control': '{}?secret={}'.format(parsed.path, fds['control'])}

def migrate(self, new_client, wait=False):
"""Migrate a container.

Expand Down
29 changes: 29 additions & 0 deletions pylxd/tests/models/test_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,35 @@ def test_execute_string(self):

self.assertRaises(TypeError, an_container.execute, 'apt-get update')

def test_interactive_execute(self):
an_container = models.Container(self.client, name='an-container')

result = an_container.interactive_execute(['/bin/bash'])

self.assertEqual(result['ws'],
'/1.0/operations/operation-abc/websocket?secret=abc')
self.assertEqual(result['control'],
'/1.0/operations/operation-abc/websocket?secret=jkl')

def test_interactive_execute_env(self):
an_container = models.Container(self.client, name='an-container')

result = an_container.interactive_execute(['/bin/bash'], {"PATH": "/"})

self.assertEqual(result['ws'],
'/1.0/operations/operation-abc/websocket?secret=abc')
self.assertEqual(result['control'],
'/1.0/operations/operation-abc/websocket?secret=jkl')

def test_interactive_execute_string(self):
"""A command passed as string raises a TypeError."""
an_container = models.Container(
self.client, name='an-container')

self.assertRaises(TypeError,
an_container.interactive_execute,
'apt-get update')

def test_migrate(self):
"""A container is migrated."""
from pylxd.client import Client
Expand Down