%PDF- %PDF-
Direktori : /proc/227033/root/lib/python2.7/site-packages/salt/utils/docker/ |
Current File : //proc/227033/root/lib/python2.7/site-packages/salt/utils/docker/__init__.py |
# -*- coding: utf-8 -*- ''' Common logic used by the docker state and execution module This module contains logic to accommodate docker/salt CLI usage, as well as input as formatted by states. ''' # Import Python libs from __future__ import absolute_import, print_function, unicode_literals import copy import logging # Import Salt libs import salt.utils.args import salt.utils.data import salt.utils.docker.translate from salt.utils.docker.translate.helpers import split as _split from salt.exceptions import CommandExecutionError, SaltInvocationError from salt.utils.args import get_function_argspec as _argspec # Import 3rd-party libs from salt.ext import six try: import docker HAS_DOCKER_PY = True except ImportError: HAS_DOCKER_PY = False # These next two imports are only necessary to have access to the needed # functions so that we can get argspecs for the container config, host config, # and networking config (see the get_client_args() function). try: import docker.types except ImportError: pass try: import docker.utils except ImportError: pass NOTSET = object() # Default timeout as of docker-py 1.0.0 CLIENT_TIMEOUT = 60 # Timeout for stopping the container, before a kill is invoked SHUTDOWN_TIMEOUT = 10 log = logging.getLogger(__name__) def get_client_args(limit=None): if not HAS_DOCKER_PY: raise CommandExecutionError('docker Python module not imported') limit = salt.utils.args.split_input(limit or []) ret = {} if not limit or any(x in limit for x in ('create_container', 'host_config', 'connect_container_to_network')): try: ret['create_container'] = \ _argspec(docker.APIClient.create_container).args except AttributeError: try: ret['create_container'] = \ _argspec(docker.Client.create_container).args except AttributeError: raise CommandExecutionError( 'Coult not get create_container argspec' ) try: ret['host_config'] = \ _argspec(docker.types.HostConfig.__init__).args except AttributeError: try: ret['host_config'] = \ _argspec(docker.utils.create_host_config).args except AttributeError: raise CommandExecutionError( 'Could not get create_host_config argspec' ) try: ret['connect_container_to_network'] = \ _argspec(docker.types.EndpointConfig.__init__).args except AttributeError: try: ret['connect_container_to_network'] = \ _argspec(docker.utils.utils.create_endpoint_config).args except AttributeError: try: ret['connect_container_to_network'] = \ _argspec(docker.utils.create_endpoint_config).args except AttributeError: raise CommandExecutionError( 'Could not get connect_container_to_network argspec' ) for key, wrapped_func in ( ('logs', docker.api.container.ContainerApiMixin.logs), ('create_network', docker.api.network.NetworkApiMixin.create_network)): if not limit or key in limit: try: func_ref = wrapped_func if six.PY2: try: # create_network is decorated, so we have to dig into the # closure created by functools.wraps ret[key] = \ _argspec(func_ref.__func__.__closure__[0].cell_contents).args except (AttributeError, IndexError): # functools.wraps changed (unlikely), bail out ret[key] = [] else: try: # functools.wraps makes things a little easier in Python 3 ret[key] = _argspec(func_ref.__wrapped__).args except AttributeError: # functools.wraps changed (unlikely), bail out ret[key] = [] except AttributeError: # Function moved, bail out ret[key] = [] if not limit or 'ipam_config' in limit: try: ret['ipam_config'] = _argspec(docker.types.IPAMPool.__init__).args except AttributeError: try: ret['ipam_config'] = _argspec(docker.utils.create_ipam_pool).args except AttributeError: raise CommandExecutionError('Could not get ipam args') for item in ret: # The API version is passed automagically by the API code that imports # these classes/functions and is not an arg that we will be passing, so # remove it if present. Similarly, don't include "self" if it shows up # in the arglist. for argname in ('version', 'self'): try: ret[item].remove(argname) except ValueError: pass # Remove any args in host or endpoint config from the create_container # arglist. This keeps us from accidentally allowing args that docker-py has # moved from the create_container function to the either the host or # endpoint config. for item in ('host_config', 'connect_container_to_network'): for val in ret.get(item, []): try: ret['create_container'].remove(val) except ValueError: # Arg is not in create_container arglist pass for item in ('create_container', 'host_config', 'connect_container_to_network'): if limit and item not in limit: ret.pop(item, None) try: ret['logs'].remove('container') except (KeyError, ValueError, TypeError): pass return ret def translate_input(translator, skip_translate=None, ignore_collisions=False, validate_ip_addrs=True, **kwargs): ''' Translate CLI/SLS input into the format the API expects. The ``translator`` argument must be a module containing translation functions, within salt.utils.docker.translate. A ``skip_translate`` kwarg can be passed to control which arguments are translated. It can be either a comma-separated list or an iterable containing strings (e.g. a list or tuple), and members of that tuple will have their translation skipped. Optionally, skip_translate can be set to True to skip *all* translation. ''' kwargs = copy.deepcopy(salt.utils.args.clean_kwargs(**kwargs)) invalid = {} collisions = [] if skip_translate is True: # Skip all translation return kwargs else: if not skip_translate: skip_translate = () else: try: skip_translate = _split(skip_translate) except AttributeError: pass if not hasattr(skip_translate, '__iter__'): log.error('skip_translate is not an iterable, ignoring') skip_translate = () try: # Using list(kwargs) here because if there are any invalid arguments we # will be popping them from the kwargs. for key in list(kwargs): real_key = translator.ALIASES.get(key, key) if real_key in skip_translate: continue # ipam_pools is designed to be passed as a list of actual # dictionaries, but if each of the dictionaries passed has a single # element, it will be incorrectly repacked. if key != 'ipam_pools' and salt.utils.data.is_dictlist(kwargs[key]): kwargs[key] = salt.utils.data.repack_dictlist(kwargs[key]) try: kwargs[key] = getattr(translator, real_key)( kwargs[key], validate_ip_addrs=validate_ip_addrs, skip_translate=skip_translate) except AttributeError: log.debug('No translation function for argument \'%s\'', key) continue except SaltInvocationError as exc: kwargs.pop(key) invalid[key] = exc.strerror try: translator._merge_keys(kwargs) except AttributeError: pass # Convert CLI versions of commands to their docker-py counterparts for key in translator.ALIASES: if key in kwargs: new_key = translator.ALIASES[key] value = kwargs.pop(key) if new_key in kwargs: collisions.append(new_key) else: kwargs[new_key] = value try: translator._post_processing(kwargs, skip_translate, invalid) except AttributeError: pass except Exception as exc: error_message = exc.__str__() log.error( 'Error translating input: \'%s\'', error_message, exc_info=True) else: error_message = None error_data = {} if error_message is not None: error_data['error_message'] = error_message if invalid: error_data['invalid'] = invalid if collisions and not ignore_collisions: for item in collisions: error_data.setdefault('collisions', []).append( '\'{0}\' is an alias for \'{1}\', they cannot both be used' .format(translator.ALIASES_REVMAP[item], item) ) if error_data: raise CommandExecutionError( 'Failed to translate input', info=error_data) return kwargs def create_ipam_config(*pools, **kwargs): ''' Builds an IP address management (IPAM) config dictionary ''' kwargs = salt.utils.args.clean_kwargs(**kwargs) try: # docker-py 2.0 and newer pool_args = salt.utils.args.get_function_argspec( docker.types.IPAMPool.__init__).args create_pool = docker.types.IPAMPool create_config = docker.types.IPAMConfig except AttributeError: # docker-py < 2.0 pool_args = salt.utils.args.get_function_argspec( docker.utils.create_ipam_pool).args create_pool = docker.utils.create_ipam_pool create_config = docker.utils.create_ipam_config for primary_key, alias_key in (('driver', 'ipam_driver'), ('options', 'ipam_opts')): if alias_key in kwargs: alias_val = kwargs.pop(alias_key) if primary_key in kwargs: log.warning( 'docker.create_ipam_config: Both \'%s\' and \'%s\' ' 'passed. Ignoring \'%s\'', alias_key, primary_key, alias_key ) else: kwargs[primary_key] = alias_val if salt.utils.data.is_dictlist(kwargs.get('options')): kwargs['options'] = salt.utils.data.repack_dictlist(kwargs['options']) # Get all of the IPAM pool args that were passed as individual kwargs # instead of in the *pools tuple pool_kwargs = {} for key in list(kwargs): if key in pool_args: pool_kwargs[key] = kwargs.pop(key) pool_configs = [] if pool_kwargs: pool_configs.append(create_pool(**pool_kwargs)) pool_configs.extend([create_pool(**pool) for pool in pools]) if pool_configs: # Sanity check the IPAM pools. docker-py's type/function for creating # an IPAM pool will allow you to create a pool with a gateway, IP # range, or map of aux addresses, even when no subnet is passed. # However, attempting to use this IPAM pool when creating the network # will cause the Docker Engine to throw an error. if any('Subnet' not in pool for pool in pool_configs): raise SaltInvocationError('A subnet is required in each IPAM pool') else: kwargs['pool_configs'] = pool_configs ret = create_config(**kwargs) pool_dicts = ret.get('Config') if pool_dicts: # When you inspect a network with custom IPAM configuration, only # arguments which were explictly passed are reflected. By contrast, # docker-py will include keys for arguments which were not passed in # but set the value to None. Thus, for ease of comparison, the below # loop will remove all keys with a value of None from the generated # pool configs. for idx, _ in enumerate(pool_dicts): for key in list(pool_dicts[idx]): if pool_dicts[idx][key] is None: del pool_dicts[idx][key] return ret