Browse Source

connection/ssh_lxc.py: new ssh_lxc interface (1)

Modification of the stock connection plugin ssh.py to use lxc-attach on
the target host.

We replace any `<cmd>` with
`lxc-attach -n <container_name> /bin/sh -c '<cmd>'`
before sending it through the ssh connection.

Based on the original idea of *Pierre Chifflier* availabe on [GitHub].

The container name should be passed as the `ansible_ssh_lxc_name`
variable.
The `ansible_docker_extra_args` variable is still working for backward
compatibiliy.

ToDo: The docstrings need to be updated, they are still mostly the ones
from ssh.py connection plugin.

We figured out the proper method to access inventory variables (see
README.md in [GitHub]), they need to be propery specified inside the
DOCUMENTATION of the Connection [1], and then they can be obtained with the
`Plugin.get_option()` method. That method should not be called in the
`__init__()`, because options are not yet initialized. Calling it in
`_connect()` returned the correct option.

[GitHub]: https://github.com/chifflier/ansible-lxc-ssh
[1]: https://docs.ansible.com/ansible/2.9/dev_guide/developing_plugins.html
python3
Zolfa 5 years ago
parent
commit
33e4e69698
Signed by: zolfa GPG Key ID: E1A43B038C4D6616
1 changed files with 37 additions and 12 deletions
  1. +37
    -12
      connection_plugins/ssh_lxc.py

+ 37
- 12
connection_plugins/ssh_lxc.py View File

@ -2,21 +2,23 @@
# Copyright 2015 Abhijit Menon-Sen <ams@2ndQuadrant.com> # Copyright 2015 Abhijit Menon-Sen <ams@2ndQuadrant.com>
# Copyright 2017 Toshio Kuratomi <tkuratomi@ansible.com> # Copyright 2017 Toshio Kuratomi <tkuratomi@ansible.com>
# Copyright (c) 2017 Ansible Project # Copyright (c) 2017 Ansible Project
# Copyright 2020 Lorenzo Zolfanelli <lorenzo.zolfanelli@gmail.com>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
DOCUMENTATION = ''' DOCUMENTATION = '''
connection: ssh
short_description: connect via ssh client binary
connection: ssh_lxc
short_description: connect via ssh client binary and then to a container with lxc-attach
description: description:
- This connection plugin allows ansible to communicate to the target machines via normal ssh command line. - This connection plugin allows ansible to communicate to the target machines via normal ssh command line.
- Ansible does not expose a channel to allow communication between the user and the ssh process to accept - Ansible does not expose a channel to allow communication between the user and the ssh process to accept
a password manually to decrypt an ssh key when using this connection plugin (which is the default). The a password manually to decrypt an ssh key when using this connection plugin (which is the default). The
use of ``ssh-agent`` is highly recommended. use of ``ssh-agent`` is highly recommended.
author: ansible (@core)
version_added: historical
author: Lorenzo Zolfanelli
version_added: "2.9.6"
options: options:
host: host:
description: Hostname/ip to connect to. description: Hostname/ip to connect to.
@ -24,6 +26,12 @@ DOCUMENTATION = '''
vars: vars:
- name: ansible_host - name: ansible_host
- name: ansible_ssh_host - name: ansible_ssh_host
container_name:
description: name of lxc container to attach to
vars:
- name: ansible_ssh_lxc_name
- name: ansible_docker_extra_args
type: str
host_key_checking: host_key_checking:
description: Determines if ssh should check host keys description: Determines if ssh should check host keys
type: boolean type: boolean
@ -444,7 +452,7 @@ def _ssh_retry(func):
class Connection(ConnectionBase): class Connection(ConnectionBase):
''' ssh based connections ''' ''' ssh based connections '''
transport = 'ssh'
transport = 'ssh_lxc'
has_pipelining = True has_pipelining = True
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -470,6 +478,7 @@ class Connection(ConnectionBase):
# management here. # management here.
def _connect(self): def _connect(self):
self.container_name = self.get_option('container_name')
return self return self
@staticmethod @staticmethod
@ -764,7 +773,7 @@ class Connection(ConnectionBase):
# We don't use _shell.quote as this is run on the controller and independent from the shell plugin chosen # We don't use _shell.quote as this is run on the controller and independent from the shell plugin chosen
display_cmd = u' '.join(shlex_quote(to_text(c)) for c in cmd) display_cmd = u' '.join(shlex_quote(to_text(c)) for c in cmd)
display.vvv(u'SSH: EXEC {0}'.format(display_cmd), host=self.host)
display.vvv(u'SSH_LXC: EXEC {0}'.format(display_cmd), host=self.host)
# Start the given command. If we don't need to pipeline data, we can try # Start the given command. If we don't need to pipeline data, we can try
# to use a pseudo-tty (ssh will have been invoked with -tt). If we are # to use a pseudo-tty (ssh will have been invoked with -tt). If we are
@ -1153,12 +1162,16 @@ class Connection(ConnectionBase):
# #
# Main public methods # Main public methods
# #
def exec_command(self, cmd, in_data=None, sudoable=True):
def exec_command(self, cmd, in_data=None, sudoable=False):
''' run a command on the remote host ''' ''' run a command on the remote host '''
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
display.vvv(u"ESTABLISH SSH CONNECTION FOR USER: {0}".format(self._play_context.remote_user), host=self._play_context.remote_addr)
display.vvv(
"ESTABLISH SSH_LXC CONNECTION TO {1}, AS SSH USER: {0}".format(self._play_context.remote_user,
self.container_name),
host=self._play_context.remote_addr
)
if getattr(self._shell, "_IS_WINDOWS", False): if getattr(self._shell, "_IS_WINDOWS", False):
# Become method 'runas' is done in the wrapper that is executed, # Become method 'runas' is done in the wrapper that is executed,
@ -1179,14 +1192,17 @@ class Connection(ConnectionBase):
ssh_executable = self._play_context.ssh_executable ssh_executable = self._play_context.ssh_executable
lxc_cmd = 'lxc-attach --name %s -- /bin/sh -c %s' % (shlex_quote(self.container_name),
shlex_quote(cmd))
# -tt can cause various issues in some environments so allow the user # -tt can cause various issues in some environments so allow the user
# to disable it as a troubleshooting method. # to disable it as a troubleshooting method.
use_tty = self.get_option('use_tty') use_tty = self.get_option('use_tty')
if not in_data and sudoable and use_tty: if not in_data and sudoable and use_tty:
args = (ssh_executable, '-tt', self.host, cmd)
args = (ssh_executable, '-tt', self.host, lxc_cmd)
else: else:
args = (ssh_executable, self.host, cmd)
args = (ssh_executable, self.host, lxc_cmd)
cmd = self._build_command(*args) cmd = self._build_command(*args)
(returncode, stdout, stderr) = self._run(cmd, in_data, sudoable=sudoable) (returncode, stdout, stderr) = self._run(cmd, in_data, sudoable=sudoable)
@ -1209,7 +1225,10 @@ class Connection(ConnectionBase):
if getattr(self._shell, "_IS_WINDOWS", False): if getattr(self._shell, "_IS_WINDOWS", False):
out_path = self._escape_win_path(out_path) out_path = self._escape_win_path(out_path)
return self._file_transport_command(in_path, out_path, 'put')
with open(in_path, 'rb') as in_f:
in_data = in_f.read()
cmd = 'cat > %s; echo -n done' % shlex_quote(out_path)
return self.exec_command(cmd, in_data, sudoable=False)
def fetch_file(self, in_path, out_path): def fetch_file(self, in_path, out_path):
''' fetch a file from remote to local ''' ''' fetch a file from remote to local '''
@ -1222,7 +1241,13 @@ class Connection(ConnectionBase):
if getattr(self._shell, "_IS_WINDOWS", False): if getattr(self._shell, "_IS_WINDOWS", False):
in_path = self._escape_win_path(in_path) in_path = self._escape_win_path(in_path)
return self._file_transport_command(in_path, out_path, 'get')
cmd = 'cat %s' % shlex_quote(in_path)
(returncode, stdout, stderr) = self.exec_command(cmd, in_data=None, sudoable=False)
if returncode != 0:
raise AnsibleError("failed to transfer file from {0}:\n{1}\n{2}".format(in_path, stdout, stderr))
with open(out_path,'wb') as out_f:
out_f.write(stdout)
def reset(self): def reset(self):
# If we have a persistent ssh connection (ControlPersist), we can ask it to stop listening. # If we have a persistent ssh connection (ControlPersist), we can ask it to stop listening.


Loading…
Cancel
Save