Commit 8564dbaf by Toshio Kuratomi

Fix problem with chroot connection plugin and symlinks from within the chroot.

Manually apply changes from 952166f4
because git won't cherry-pick successfully
parent 0845151c
# Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> # Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
# (c) 2013, Maykel Moya <mmoya@speedyrails.com> # (c) 2013, Maykel Moya <mmoya@speedyrails.com>
# (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com>
# #
# This file is part of Ansible # This file is part of Ansible
# #
...@@ -15,17 +16,20 @@ ...@@ -15,17 +16,20 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import distutils.spawn import distutils.spawn
import traceback import traceback
import os import os
import shutil
import subprocess import subprocess
from ansible import errors from ansible import errors
from ansible import utils from ansible import utils
from ansible.callbacks import vvv from ansible.callbacks import vvv
import ansible.constants as C import ansible.constants as C
BUFSIZE = 65536
class Connection(object): class Connection(object):
''' Local chroot based connections ''' ''' Local chroot based connections '''
...@@ -62,8 +66,21 @@ class Connection(object): ...@@ -62,8 +66,21 @@ class Connection(object):
return self return self
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None): def _generate_cmd(self, executable, cmd):
''' run a command on the chroot ''' if executable:
local_cmd = [self.chroot_cmd, self.chroot, executable, '-c', cmd]
else:
local_cmd = '%s "%s" %s' % (self.chroot_cmd, self.chroot, cmd)
return local_cmd
def _buffered_exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None, stdin=subprocess.PIPE):
''' run a command on the chroot. This is only needed for implementing
put_file() get_file() so that we don't have to read the whole file
into memory.
compared to exec_command() it looses some niceties like being able to
return the process's exit code immediately.
'''
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported: if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method) raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
...@@ -72,60 +89,65 @@ class Connection(object): ...@@ -72,60 +89,65 @@ class Connection(object):
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining") raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
# We enter chroot as root so we ignore privlege escalation? # We enter chroot as root so we ignore privlege escalation?
local_cmd = self._generate_cmd(executable, cmd)
if executable:
local_cmd = [self.chroot_cmd, self.chroot, executable, '-c', cmd]
else:
local_cmd = '%s "%s" %s' % (self.chroot_cmd, self.chroot, cmd)
vvv("EXEC %s" % (local_cmd), host=self.chroot) vvv("EXEC %s" % (local_cmd), host=self.chroot)
p = subprocess.Popen(local_cmd, shell=isinstance(local_cmd, basestring), p = subprocess.Popen(local_cmd, shell=isinstance(local_cmd, basestring),
cwd=self.runner.basedir, cwd=self.runner.basedir,
stdin=subprocess.PIPE, stdin=stdin,
stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return p
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the chroot '''
p = self._buffered_exec_command(cmd, tmp_path, become_user, sudoable, executable, in_data)
stdout, stderr = p.communicate() stdout, stderr = p.communicate()
return (p.returncode, '', stdout, stderr) return (p.returncode, '', stdout, stderr)
def put_file(self, in_path, out_path): def put_file(self, in_path, out_path):
''' transfer a file from local to chroot ''' ''' transfer a file from local to chroot '''
if not out_path.startswith(os.path.sep):
out_path = os.path.join(os.path.sep, out_path)
normpath = os.path.normpath(out_path)
out_path = os.path.join(self.chroot, normpath[1:])
vvv("PUT %s TO %s" % (in_path, out_path), host=self.chroot) vvv("PUT %s TO %s" % (in_path, out_path), host=self.chroot)
if not os.path.exists(in_path):
raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path)
try: try:
shutil.copyfile(in_path, out_path) with open(in_path, 'rb') as in_file:
except shutil.Error: try:
traceback.print_exc() p = self._buffered_exec_command('dd of=%s' % out_path, None, stdin=in_file)
raise errors.AnsibleError("failed to copy: %s and %s are the same" % (in_path, out_path)) except OSError:
raise errors.AnsibleError("chroot connection requires dd command in the chroot")
try:
stdout, stderr = p.communicate()
except:
traceback.print_exc()
raise errors.AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
if p.returncode != 0:
raise errors.AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
except IOError: except IOError:
traceback.print_exc() raise errors.AnsibleError("file or module does not exist at: %s" % in_path)
raise errors.AnsibleError("failed to transfer file to %s" % out_path)
def fetch_file(self, in_path, out_path): def fetch_file(self, in_path, out_path):
''' fetch a file from chroot to local ''' ''' fetch a file from chroot to local '''
if not in_path.startswith(os.path.sep):
in_path = os.path.join(os.path.sep, in_path)
normpath = os.path.normpath(in_path)
in_path = os.path.join(self.chroot, normpath[1:])
vvv("FETCH %s TO %s" % (in_path, out_path), host=self.chroot) vvv("FETCH %s TO %s" % (in_path, out_path), host=self.chroot)
if not os.path.exists(in_path):
raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path)
try: try:
shutil.copyfile(in_path, out_path) p = self._buffered_exec_command('dd if=%s bs=%s' % (in_path, BUFSIZE), None)
except shutil.Error: except OSError:
traceback.print_exc() raise errors.AnsibleError("chroot connection requires dd command in the jail")
raise errors.AnsibleError("failed to copy: %s and %s are the same" % (in_path, out_path))
except IOError: with open(out_path, 'wb+') as out_file:
traceback.print_exc() try:
raise errors.AnsibleError("failed to transfer file to %s" % out_path) for chunk in p.stdout.read(BUFSIZE):
out_file.write(chunk)
except:
traceback.print_exc()
raise errors.AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
stdout, stderr = p.communicate()
if p.returncode != 0:
raise errors.AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
def close(self): def close(self):
''' terminate the connection; nothing to do here ''' ''' terminate the connection; nothing to do here '''
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment