%PDF- %PDF-
Direktori : /proc/self/root/proc/self/root/lib/python2.7/site-packages/salt/modules/ |
Current File : //proc/self/root/proc/self/root/lib/python2.7/site-packages/salt/modules/container_resource.py |
# -*- coding: utf-8 -*- ''' Common resources for LXC and systemd-nspawn containers .. versionadded:: 2015.8.0 These functions are not designed to be called directly, but instead from the :mod:`lxc <salt.modules.lxc>`, :mod:`nspawn <salt.modules.nspawn>`, and :mod:`docker <salt.modules.docker>` execution modules. They provide for common logic to be re-used for common actions. ''' # Import python libs from __future__ import absolute_import, print_function, unicode_literals import functools import copy import logging import os import pipes import time import traceback # Import salt libs import salt.utils.args import salt.utils.path import salt.utils.vt from salt.exceptions import CommandExecutionError, SaltInvocationError log = logging.getLogger(__name__) PATH = 'PATH=/bin:/usr/bin:/sbin:/usr/sbin:/opt/bin:' \ '/usr/local/bin:/usr/local/sbin' def _validate(wrapped): ''' Decorator for common function argument validation ''' @functools.wraps(wrapped) def wrapper(*args, **kwargs): container_type = kwargs.get('container_type') exec_driver = kwargs.get('exec_driver') valid_driver = { 'docker': ('lxc-attach', 'nsenter', 'docker-exec'), 'lxc': ('lxc-attach',), 'nspawn': ('nsenter',), } if container_type not in valid_driver: raise SaltInvocationError( 'Invalid container type \'{0}\'. Valid types are: {1}' .format(container_type, ', '.join(sorted(valid_driver))) ) if exec_driver not in valid_driver[container_type]: raise SaltInvocationError( 'Invalid command execution driver. Valid drivers are: {0}' .format(', '.join(valid_driver[container_type])) ) if exec_driver == 'lxc-attach' and not salt.utils.path.which('lxc-attach'): raise SaltInvocationError( 'The \'lxc-attach\' execution driver has been chosen, but ' 'lxc-attach is not available. LXC may not be installed.' ) return wrapped(*args, **salt.utils.args.clean_kwargs(**kwargs)) return wrapper def _nsenter(pid): ''' Return the nsenter command to attach to the named container ''' return ( 'nsenter --target {0} --mount --uts --ipc --net --pid' .format(pid) ) def _get_md5(name, path, run_func): ''' Get the MD5 checksum of a file from a container ''' output = run_func(name, 'md5sum {0}'.format(pipes.quote(path)), ignore_retcode=True)['stdout'] try: return output.split()[0] except IndexError: # Destination file does not exist or could not be accessed return None def cache_file(source): ''' Wrapper for cp.cache_file which raises an error if the file was unable to be cached. CLI Example: .. code-block:: bash salt myminion container_resource.cache_file salt://foo/bar/baz.txt ''' try: # Don't just use cp.cache_file for this. Docker has its own code to # pull down images from the web. if source.startswith('salt://'): cached_source = __salt__['cp.cache_file'](source) if not cached_source: raise CommandExecutionError( 'Unable to cache {0}'.format(source) ) return cached_source except AttributeError: raise SaltInvocationError('Invalid source file {0}'.format(source)) return source @_validate def run(name, cmd, container_type=None, exec_driver=None, output=None, no_start=False, stdin=None, python_shell=True, output_loglevel='debug', ignore_retcode=False, path=None, use_vt=False, keep_env=None): ''' Common logic for running shell commands in containers path path to the container parent (for LXC only) default: /var/lib/lxc (system default) CLI Example: .. code-block:: bash salt myminion container_resource.run mycontainer 'ps aux' container_type=docker exec_driver=nsenter output=stdout ''' valid_output = ('stdout', 'stderr', 'retcode', 'all') if output is None: cmd_func = 'cmd.run' elif output not in valid_output: raise SaltInvocationError( '\'output\' param must be one of the following: {0}' .format(', '.join(valid_output)) ) else: cmd_func = 'cmd.run_all' if keep_env is None or isinstance(keep_env, bool): to_keep = [] elif not isinstance(keep_env, (list, tuple)): try: to_keep = keep_env.split(',') except AttributeError: log.warning('Invalid keep_env value, ignoring') to_keep = [] else: to_keep = keep_env if exec_driver == 'lxc-attach': full_cmd = 'lxc-attach ' if path: full_cmd += '-P {0} '.format(pipes.quote(path)) if keep_env is not True: full_cmd += '--clear-env ' if 'PATH' not in to_keep: full_cmd += '--set-var {0} '.format(PATH) # --clear-env results in a very restrictive PATH # (/bin:/usr/bin), use a good fallback. full_cmd += ' '.join( ['--set-var {0}={1}'.format(x, pipes.quote(os.environ[x])) for x in to_keep if x in os.environ] ) full_cmd += ' -n {0} -- {1}'.format(pipes.quote(name), cmd) elif exec_driver == 'nsenter': pid = __salt__['{0}.pid'.format(container_type)](name) full_cmd = ( 'nsenter --target {0} --mount --uts --ipc --net --pid -- ' .format(pid) ) if keep_env is not True: full_cmd += 'env -i ' if 'PATH' not in to_keep: full_cmd += '{0} '.format(PATH) full_cmd += ' '.join( ['{0}={1}'.format(x, pipes.quote(os.environ[x])) for x in to_keep if x in os.environ] ) full_cmd += ' {0}'.format(cmd) elif exec_driver == 'docker-exec': # We're using docker exec on the CLI as opposed to via docker-py, since # the Docker API doesn't return stdout and stderr separately. full_cmd = 'docker exec ' if stdin: full_cmd += '-i ' full_cmd += '{0} '.format(name) if keep_env is not True: full_cmd += 'env -i ' if 'PATH' not in to_keep: full_cmd += '{0} '.format(PATH) full_cmd += ' '.join( ['{0}={1}'.format(x, pipes.quote(os.environ[x])) for x in to_keep if x in os.environ] ) full_cmd += ' {0}'.format(cmd) if not use_vt: ret = __salt__[cmd_func](full_cmd, stdin=stdin, python_shell=python_shell, output_loglevel=output_loglevel, ignore_retcode=ignore_retcode) else: stdout, stderr = '', '' proc = salt.utils.vt.Terminal( full_cmd, shell=python_shell, log_stdin_level='quiet' if output_loglevel == 'quiet' else 'info', log_stdout_level=output_loglevel, log_stderr_level=output_loglevel, log_stdout=True, log_stderr=True, stream_stdout=False, stream_stderr=False ) # Consume output try: while proc.has_unread_data: try: cstdout, cstderr = proc.recv() if cstdout: stdout += cstdout if cstderr: if output is None: stdout += cstderr else: stderr += cstderr time.sleep(0.5) except KeyboardInterrupt: break ret = stdout if output is None \ else {'retcode': proc.exitstatus, 'pid': 2, 'stdout': stdout, 'stderr': stderr} except salt.utils.vt.TerminalException: trace = traceback.format_exc() log.error(trace) ret = stdout if output is None \ else {'retcode': 127, 'pid': 2, 'stdout': stdout, 'stderr': stderr} finally: proc.terminate() return ret @_validate def copy_to(name, source, dest, container_type=None, path=None, exec_driver=None, overwrite=False, makedirs=False): ''' Common logic for copying files to containers path path to the container parent (for LXC only) default: /var/lib/lxc (system default) CLI Example: .. code-block:: bash salt myminion container_resource.copy_to mycontainer /local/file/path /container/file/path container_type=docker exec_driver=nsenter ''' # Get the appropriate functions state = __salt__['{0}.state'.format(container_type)] def run_all(*args, **akwargs): akwargs = copy.deepcopy(akwargs) if container_type in ['lxc'] and 'path' not in akwargs: akwargs['path'] = path return __salt__['{0}.run_all'.format(container_type)]( *args, **akwargs) state_kwargs = {} cmd_kwargs = {'ignore_retcode': True} if container_type in ['lxc']: cmd_kwargs['path'] = path state_kwargs['path'] = path def _state(name): if state_kwargs: return state(name, **state_kwargs) else: return state(name) c_state = _state(name) if c_state != 'running': raise CommandExecutionError( 'Container \'{0}\' is not running'.format(name) ) local_file = cache_file(source) source_dir, source_name = os.path.split(local_file) # Source file sanity checks if not os.path.isabs(local_file): raise SaltInvocationError('Source path must be absolute') elif not os.path.exists(local_file): raise SaltInvocationError( 'Source file {0} does not exist'.format(local_file) ) elif not os.path.isfile(local_file): raise SaltInvocationError('Source must be a regular file') # Destination file sanity checks if not os.path.isabs(dest): raise SaltInvocationError('Destination path must be absolute') if run_all(name, 'test -d {0}'.format(pipes.quote(dest)), **cmd_kwargs)['retcode'] == 0: # Destination is a directory, full path to dest file will include the # basename of the source file. dest = os.path.join(dest, source_name) else: # Destination was not a directory. We will check to see if the parent # dir is a directory, and then (if makedirs=True) attempt to create the # parent directory. dest_dir, dest_name = os.path.split(dest) if run_all(name, 'test -d {0}'.format(pipes.quote(dest_dir)), **cmd_kwargs)['retcode'] != 0: if makedirs: result = run_all(name, 'mkdir -p {0}'.format(pipes.quote(dest_dir)), **cmd_kwargs) if result['retcode'] != 0: error = ('Unable to create destination directory {0} in ' 'container \'{1}\''.format(dest_dir, name)) if result['stderr']: error += ': {0}'.format(result['stderr']) raise CommandExecutionError(error) else: raise SaltInvocationError( 'Directory {0} does not exist on {1} container \'{2}\'' .format(dest_dir, container_type, name) ) if not overwrite and run_all(name, 'test -e {0}'.format(pipes.quote(dest)), **cmd_kwargs)['retcode'] == 0: raise CommandExecutionError( 'Destination path {0} already exists. Use overwrite=True to ' 'overwrite it'.format(dest) ) # Before we try to replace the file, compare checksums. source_md5 = __salt__['file.get_sum'](local_file, 'md5') if source_md5 == _get_md5(name, dest, run_all): log.debug('{0} and {1}:{2} are the same file, skipping copy' .format(source, name, dest)) return True log.debug('Copying {0} to {1} container \'{2}\' as {3}' .format(source, container_type, name, dest)) # Using cat here instead of opening the file, reading it into memory, # and passing it as stdin to run(). This will keep down memory # usage for the minion and make the operation run quicker. if exec_driver == 'lxc-attach': lxcattach = 'lxc-attach' if path: lxcattach += ' -P {0}'.format(pipes.quote(path)) copy_cmd = ( 'cat "{0}" | {4} --clear-env --set-var {1} -n {2} -- ' 'tee "{3}"'.format(local_file, PATH, name, dest, lxcattach) ) elif exec_driver == 'nsenter': pid = __salt__['{0}.pid'.format(container_type)](name) copy_cmd = ( 'cat "{0}" | {1} env -i {2} tee "{3}"' .format(local_file, _nsenter(pid), PATH, dest) ) elif exec_driver == 'docker-exec': copy_cmd = ( 'cat "{0}" | docker exec -i {1} env -i {2} tee "{3}"' .format(local_file, name, PATH, dest) ) __salt__['cmd.run'](copy_cmd, python_shell=True, output_loglevel='quiet') return source_md5 == _get_md5(name, dest, run_all)