%PDF- %PDF-
Direktori : /lib/python2.7/site-packages/salt/cli/ |
Current File : //lib/python2.7/site-packages/salt/cli/cp.py |
# -*- coding: utf-8 -*- ''' The cp module is used to execute the logic used by the salt-cp command line application, salt-cp is NOT intended to broadcast large files, it is intended to handle text files. Salt-cp can be used to distribute configuration files ''' # Import python libs from __future__ import absolute_import, print_function, unicode_literals import base64 import errno import logging import os import re import sys # Import salt libs import salt.client import salt.output import salt.utils.files import salt.utils.gzip_util import salt.utils.itertools import salt.utils.minions import salt.utils.parsers import salt.utils.platform import salt.utils.stringutils import salt.utils.verify # Import 3rd party libs from salt.ext import six log = logging.getLogger(__name__) class SaltCPCli(salt.utils.parsers.SaltCPOptionParser): ''' Run the salt-cp command line client ''' def run(self): ''' Execute salt-cp ''' self.parse_args() # Setup file logging! self.setup_logfile_logger() salt.utils.verify.verify_log(self.config) cp_ = SaltCP(self.config) cp_.run() class SaltCP(object): ''' Create a salt cp object, used to distribute simple files with salt ''' def __init__(self, opts): self.opts = opts self.is_windows = salt.utils.platform.is_windows() def _mode(self, path): if self.is_windows: return None try: return int(oct(os.stat(path).st_mode)[-4:], 8) except (TypeError, IndexError, ValueError): return None def _recurse(self, path): ''' Get a list of all specified files ''' files = {} empty_dirs = [] try: sub_paths = os.listdir(path) except OSError as exc: if exc.errno == errno.ENOENT: # Path does not exist sys.stderr.write('{0} does not exist\n'.format(path)) sys.exit(42) elif exc.errno in (errno.EINVAL, errno.ENOTDIR): # Path is a file (EINVAL on Windows, ENOTDIR otherwise) files[path] = self._mode(path) else: if not sub_paths: empty_dirs.append(path) for fn_ in sub_paths: files_, empty_dirs_ = self._recurse(os.path.join(path, fn_)) files.update(files_) empty_dirs.extend(empty_dirs_) return files, empty_dirs def _list_files(self): files = {} empty_dirs = set() for fn_ in self.opts['src']: files_, empty_dirs_ = self._recurse(fn_) files.update(files_) empty_dirs.update(empty_dirs_) return files, sorted(empty_dirs) def _file_dict(self, fn_): ''' Take a path and return the contents of the file as a string ''' if not os.path.isfile(fn_): err = 'The referenced file, {0} is not available.'.format(fn_) sys.stderr.write(err + '\n') sys.exit(42) with salt.utils.files.fopen(fn_, 'r') as fp_: data = fp_.read() return {fn_: data} def _load_files(self): ''' Parse the files indicated in opts['src'] and load them into a python object for transport ''' files = {} for fn_ in self.opts['src']: if os.path.isfile(fn_): files.update(self._file_dict(fn_)) elif os.path.isdir(fn_): salt.utils.stringutils.print_cli( fn_ + ' is a directory, only files are supported ' 'in non-chunked mode. Use "--chunked" command ' 'line argument.') sys.exit(1) return files def run(self): ''' Make the salt client call ''' if self.opts['chunked']: ret = self.run_chunked() else: ret = self.run_oldstyle() salt.output.display_output( ret, self.opts.get('output', 'nested'), self.opts) def run_oldstyle(self): ''' Make the salt client call in old-style all-in-one call method ''' arg = [self._load_files(), self.opts['dest']] local = salt.client.get_local_client(self.opts['conf_file']) args = [self.opts['tgt'], 'cp.recv', arg, self.opts['timeout'], ] selected_target_option = self.opts.get('selected_target_option', None) if selected_target_option is not None: args.append(selected_target_option) return local.cmd(*args) def run_chunked(self): ''' Make the salt client call in the new fasion chunked multi-call way ''' files, empty_dirs = self._list_files() dest = self.opts['dest'] gzip = self.opts['gzip'] tgt = self.opts['tgt'] timeout = self.opts['timeout'] selected_target_option = self.opts.get('selected_target_option') dest_is_dir = bool(empty_dirs) \ or len(files) > 1 \ or bool(re.search(r'[\\/]$', dest)) reader = salt.utils.gzip_util.compress_file \ if gzip \ else salt.utils.itertools.read_file _res = salt.utils.minions.CkMinions(self.opts).check_minions( tgt, tgt_type=selected_target_option or 'glob') minions = _res['minions'] local = salt.client.get_local_client(self.opts['conf_file']) def _get_remote_path(fn_): if fn_ in self.opts['src']: # This was a filename explicitly passed on the CLI return os.path.join(dest, os.path.basename(fn_)) \ if dest_is_dir \ else dest else: for path in self.opts['src']: relpath = os.path.relpath(fn_, path + os.sep) if relpath.startswith(parent): # File is not within this dir continue return os.path.join(dest, os.path.basename(path), relpath) else: # pylint: disable=useless-else-on-loop # Should not happen log.error('Failed to find remote path for %s', fn_) return None ret = {} parent = '..' + os.sep for fn_, mode in six.iteritems(files): remote_path = _get_remote_path(fn_) index = 1 failed = {} for chunk in reader(fn_, chunk_size=self.opts['salt_cp_chunk_size']): chunk = base64.b64encode(salt.utils.stringutils.to_bytes(chunk)) append = index > 1 log.debug( 'Copying %s to %starget \'%s\' as %s%s', fn_, '{0} '.format(selected_target_option) if selected_target_option else '', tgt, remote_path, ' (chunk #{0})'.format(index) if append else '' ) args = [ tgt, 'cp.recv_chunked', [remote_path, chunk, append, gzip, mode], timeout, ] if selected_target_option is not None: args.append(selected_target_option) result = local.cmd(*args) if not result: # Publish failed msg = ( 'Publish failed.{0} It may be necessary to ' 'decrease salt_cp_chunk_size (current value: ' '{1})'.format( ' File partially transferred.' if index > 1 else '', self.opts['salt_cp_chunk_size'], ) ) for minion in minions: ret.setdefault(minion, {})[remote_path] = msg break for minion_id, minion_ret in six.iteritems(result): ret.setdefault(minion_id, {})[remote_path] = minion_ret # Catch first error message for a given minion, we will # rewrite the results after we're done iterating through # the chunks. if minion_ret is not True and minion_id not in failed: failed[minion_id] = minion_ret index += 1 for minion_id, msg in six.iteritems(failed): ret[minion_id][remote_path] = msg for dirname in empty_dirs: remote_path = _get_remote_path(dirname) log.debug( 'Creating empty dir %s on %starget \'%s\'', dirname, '{0} '.format(selected_target_option) # pylint: disable=str-format-in-logging if selected_target_option else '', tgt, ) args = [tgt, 'cp.recv_chunked', [remote_path, None], timeout] if selected_target_option is not None: args.append(selected_target_option) for minion_id, minion_ret in six.iteritems(local.cmd(*args)): ret.setdefault(minion_id, {})[remote_path] = minion_ret return ret