From 23b1dbacaf8a20f7147de0f67f8d6c67de2142ef Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Thu, 16 Nov 2017 13:49:57 -0500 Subject: [PATCH] Config continued (#31024) * included inventory and callback in new config allow inventory to be configurable updated connection options settings also updated winrm to work with new configs removed now obsolete set_host_overrides added notes for future bcoca, current one is just punting, it's future's problem updated docs per feedback added remove group/host methods to inv data moved fact cache from data to constructed cleaner/better options fix when vars are added extended ignore list to config dicts updated paramiko connection docs removed options from base that paramiko already handles left the look option as it is used by other plugin types resolve delegation updated cache doc options fixed test_script better fragment merge for options fixed proxy command restore ini for proxy normalized options moved pipelining to class updates for host_key_checking restructured mixins * fix typo --- bin/ansible-connection | 5 +- lib/ansible/cli/doc.py | 11 +- lib/ansible/config/base.yml | 25 +- lib/ansible/config/manager.py | 35 ++- lib/ansible/executor/task_executor.py | 36 ++- lib/ansible/executor/task_queue_manager.py | 4 +- lib/ansible/inventory/data.py | 20 ++ lib/ansible/inventory/manager.py | 4 +- lib/ansible/plugins/__init__.py | 27 +- lib/ansible/plugins/action/__init__.py | 15 + lib/ansible/plugins/cache/__init__.py | 3 + lib/ansible/plugins/callback/__init__.py | 20 +- lib/ansible/plugins/callback/logentries.py | 3 - lib/ansible/plugins/connection/__init__.py | 11 - lib/ansible/plugins/connection/funcd.py | 3 +- lib/ansible/plugins/connection/network_cli.py | 19 +- .../plugins/connection/paramiko_ssh.py | 110 ++++++-- lib/ansible/plugins/connection/persistent.py | 2 - lib/ansible/plugins/connection/ssh.py | 11 +- lib/ansible/plugins/connection/winrm.py | 106 +++++-- lib/ansible/plugins/inventory/__init__.py | 265 ++++++++++-------- lib/ansible/plugins/inventory/constructed.py | 31 +- lib/ansible/plugins/inventory/ini.py | 2 +- lib/ansible/plugins/inventory/openstack.py | 11 +- lib/ansible/plugins/inventory/script.py | 26 +- lib/ansible/plugins/inventory/virtualbox.py | 52 ++-- lib/ansible/plugins/inventory/yaml.py | 22 +- lib/ansible/plugins/loader.py | 2 +- .../module_docs_fragments/inventory_cache.py | 46 +++ lib/ansible/utils/plugin_docs.py | 43 ++- .../plugins/connection/test_network_cli.py | 10 +- test/units/plugins/inventory/test_script.py | 21 +- 32 files changed, 651 insertions(+), 350 deletions(-) create mode 100644 lib/ansible/utils/module_docs_fragments/inventory_cache.py diff --git a/bin/ansible-connection b/bin/ansible-connection index ced24ee47bd..e97408ea522 100755 --- a/bin/ansible-connection +++ b/bin/ansible-connection @@ -65,6 +65,7 @@ class ConnectionProcess(object): self.play_context.private_key_file = os.path.join(self.original_path, self.play_context.private_key_file) self.connection = connection_loader.get(self.play_context.connection, self.play_context, '/dev/null') + self.connection.set_options() self.connection._connect() self.srv.register(self.connection) messages.append('connection to remote device started successfully') @@ -143,7 +144,7 @@ class ConnectionProcess(object): if self.connection: self.connection.close() - except: + except Exception: pass finally: @@ -271,7 +272,7 @@ def main(): wfd = os.fdopen(w, 'w') process = ConnectionProcess(wfd, play_context, socket_path, original_path) process.start() - except Exception as exc: + except Exception: messages.append(traceback.format_exc()) rc = 1 diff --git a/lib/ansible/cli/doc.py b/lib/ansible/cli/doc.py index 7e84f6bd515..3067c3d40a1 100644 --- a/lib/ansible/cli/doc.py +++ b/lib/ansible/cli/doc.py @@ -44,6 +44,9 @@ class DocCLI(CLI): provides a printout of their DOCUMENTATION strings, and it can create a short "snippet" which can be pasted into a playbook. ''' + # default ignore list for detailed views + IGNORE = ('module', 'docuri', 'version_added', 'short_description', 'now_date', 'plainexamples', 'returndocs') + def __init__(self, args): super(DocCLI, self).__init__(args) @@ -394,6 +397,10 @@ class DocCLI(CLI): for config in ('env', 'ini', 'yaml', 'vars'): if config in opt and opt[config]: conf[config] = opt.pop(config) + for ignore in self.IGNORE: + for item in conf[config]: + if ignore in item: + del item[ignore] if conf: text.append(self._dump_yaml({'set_via': conf}, opt_indent)) @@ -441,7 +448,7 @@ class DocCLI(CLI): def get_man_text(self, doc): - IGNORE = frozenset(['module', 'docuri', 'version_added', 'short_description', 'now_date', 'plainexamples', 'returndocs', self.options.type]) + self.IGNORE = self.IGNORE + (self.options.type,) opt_indent = " " text = [] pad = display.columns * 0.20 @@ -492,7 +499,7 @@ class DocCLI(CLI): # Generic handler for k in sorted(doc): - if k in IGNORE or not doc[k]: + if k in self.IGNORE or not doc[k]: continue if isinstance(doc[k], string_types): text.append('%s: %s' % (k.upper(), textwrap.fill(CLI.tty_ify(doc[k]), limit - (len(k) + 2), subsequent_indent=opt_indent))) diff --git a/lib/ansible/config/base.yml b/lib/ansible/config/base.yml index e417c816b95..70560d9ef03 100644 --- a/lib/ansible/config/base.yml +++ b/lib/ansible/config/base.yml @@ -1319,36 +1319,13 @@ PARAMIKO_HOST_KEY_AUTO_ADD: - {key: host_key_auto_add, section: paramiko_connection} type: boolean PARAMIKO_LOOK_FOR_KEYS: - # TODO: move to plugin + name: look for keys default: True description: 'TODO: write it' env: [{name: ANSIBLE_PARAMIKO_LOOK_FOR_KEYS}] ini: - {key: look_for_keys, section: paramiko_connection} type: boolean -PARAMIKO_PROXY_COMMAND: - # TODO: move to plugin - default: - description: 'TODO: write it' - env: [{name: ANSIBLE_PARAMIKO_PROXY_COMMAND}] - ini: - - {key: proxy_command, section: paramiko_connection} -PARAMIKO_PTY: - # TODO: move to plugin - default: True - description: 'TODO: write it' - env: [{name: ANSIBLE_PARAMIKO_PTY}] - ini: - - {key: pty, section: paramiko_connection} - type: boolean -PARAMIKO_RECORD_HOST_KEYS: - # TODO: move to plugin - default: True - description: 'TODO: write it' - env: [{name: ANSIBLE_PARAMIKO_RECORD_HOST_KEYS}] - ini: - - {key: record_host_keys, section: paramiko_connection} - type: boolean PERSISTENT_CONTROL_PATH_DIR: name: Persistence socket path default: ~/.ansible/pc diff --git a/lib/ansible/config/manager.py b/lib/ansible/config/manager.py index 2cb74538f1f..6255c47faba 100644 --- a/lib/ansible/config/manager.py +++ b/lib/ansible/config/manager.py @@ -128,7 +128,7 @@ def get_ini_config_value(p, entry): if p is not None: try: value = p.get(entry.get('section', 'defaults'), entry.get('key', ''), raw=True) - except: # FIXME: actually report issues here + except Exception: # FIXME: actually report issues here pass return value @@ -224,15 +224,24 @@ class ConfigManager(object): ''' Load YAML Config Files in order, check merge flags, keep origin of settings''' pass - def get_plugin_options(self, plugin_type, name, variables=None): + def get_plugin_options(self, plugin_type, name, keys=None, variables=None): options = {} defs = self.get_configuration_definitions(plugin_type, name) for option in defs: - options[option] = self.get_config_value(option, plugin_type=plugin_type, plugin_name=name, variables=variables) + options[option] = self.get_config_value(option, plugin_type=plugin_type, plugin_name=name, keys=keys, variables=variables) return options + def get_plugin_vars(self, plugin_type, name): + + pvars = [] + for pdef in self.get_configuration_definitions(plugin_type, name).values(): + if 'vars' in pdef and pdef['vars']: + for var_entry in pdef['vars']: + pvars.append(var_entry['name']) + return pvars + def get_configuration_definitions(self, plugin_type=None, name=None): ''' just list the possible settings, either base or for specific plugins or plugin ''' @@ -264,12 +273,12 @@ class ConfigManager(object): return value, origin - def get_config_value(self, config, cfile=None, plugin_type=None, plugin_name=None, variables=None): + def get_config_value(self, config, cfile=None, plugin_type=None, plugin_name=None, keys=None, variables=None): ''' wrapper ''' - value, _drop = self.get_config_value_and_origin(config, cfile=cfile, plugin_type=plugin_type, plugin_name=plugin_name, variables=variables) + value, _drop = self.get_config_value_and_origin(config, cfile=cfile, plugin_type=plugin_type, plugin_name=plugin_name, keys=keys, variables=variables) return value - def get_config_value_and_origin(self, config, cfile=None, plugin_type=None, plugin_name=None, variables=None): + def get_config_value_and_origin(self, config, cfile=None, plugin_type=None, plugin_name=None, keys=None, variables=None): ''' Given a config key figure out the actual value and report on the origin of the settings ''' if cfile is None: @@ -290,10 +299,15 @@ class ConfigManager(object): if config in defs: # Use 'variable overrides' if present, highest precedence, but only present when querying running play - if variables: + if variables and defs[config].get('vars'): value, origin = self._loop_entries(variables, defs[config]['vars']) origin = 'var: %s' % origin + # use playbook keywords if you have em + if value is None and keys: + value, origin = self._loop_entries(keys, defs[config]['keywords']) + origin = 'keyword: %s' % origin + # env vars are next precedence if value is None and defs[config].get('env'): value, origin = self._loop_entries(os.environ, defs[config]['env']) @@ -319,13 +333,6 @@ class ConfigManager(object): # FIXME: implement, also , break down key from defs (. notation???) origin = cfile - ''' - # for plugins, try using existing constants, this is for backwards compatiblity - if plugin_name and defs[config].get('constants'): - value, origin = self._loop_entries(self.data, defs[config]['constants']) - origin = 'constant: %s' % origin - ''' - # set default if we got here w/o a value if value is None: value = defs[config].get('default') diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py index efc1f828b78..c4a92136574 100644 --- a/lib/ansible/executor/task_executor.py +++ b/lib/ansible/executor/task_executor.py @@ -19,11 +19,11 @@ from ansible.module_utils.six.moves import cPickle from ansible.module_utils._text import to_text from ansible.playbook.conditional import Conditional from ansible.playbook.task import Task -from ansible.plugins.connection import ConnectionBase from ansible.template import Templar from ansible.utils.listify import listify_lookup_plugin_terms from ansible.utils.unsafe_proxy import UnsafeProxy, wrap_var from ansible.vars.clean import namespace_facts, clean_facts +from ansible.utils.vars import combine_vars try: from __main__ import display @@ -480,18 +480,14 @@ class TaskExecutor: not getattr(self._connection, 'connected', False) or self._play_context.remote_addr != self._connection._play_context.remote_addr): self._connection = self._get_connection(variables=variables, templar=templar) - if getattr(self._connection, '_socket_path'): - variables['ansible_socket'] = self._connection._socket_path - # only template the vars if the connection actually implements set_host_overrides - # NB: this is expensive, and should be removed once connection-specific vars are being handled by play_context - sho_impl = getattr(type(self._connection), 'set_host_overrides', None) - if sho_impl and sho_impl != ConnectionBase.set_host_overrides: - self._connection.set_host_overrides(self._host, variables, templar) else: # if connection is reused, its _play_context is no longer valid and needs # to be replaced with the one templated above, in case other data changed self._connection._play_context = self._play_context + self._set_connection_options(variables, templar) + + # get handler self._handler = self._get_action_handler(connection=self._connection, templar=templar) # And filter out any fields which were set to default(omit), and got the omit token value @@ -734,6 +730,7 @@ class TaskExecutor: if not connection: raise AnsibleError("the connection plugin '%s' was not found" % conn_type) + # FIXME: remove once all plugins pull all data from self._options self._play_context.set_options_from_plugin(connection) if any(((connection.supports_persistence and C.USE_PERSISTENT_CONNECTIONS), connection.force_persistence)): @@ -745,6 +742,29 @@ class TaskExecutor: return connection + def _set_connection_options(self, variables, templar): + + # create copy with delegation built in + final_vars = combine_vars(variables, variables.get('ansible_delegated_vars', dict()).get(self._task.delegate_to, dict())) + + # grab list of usable vars for this plugin + option_vars = C.config.get_plugin_vars('connection', self._connection._load_name) + + # create dict of 'templated vars' + options = {'_extras': {}} + for k in option_vars: + if k in final_vars: + options[k] = templar.template(final_vars[k]) + + # add extras if plugin supports them + if getattr(self._connection, 'allow_extras', False): + for k in final_vars: + if k.startswith('ansible_%s_' % self._connection._load_name) and k not in options: + options['_extras'][k] = templar.template(final_vars[k]) + + # set options with 'templated vars' specific to this plugin + self._connection.set_options(var_options=options) + def _get_action_handler(self, connection, templar): ''' Returns the correct action plugin to handle the requestion task action diff --git a/lib/ansible/executor/task_queue_manager.py b/lib/ansible/executor/task_queue_manager.py index 9270d16aab2..6d25125a201 100644 --- a/lib/ansible/executor/task_queue_manager.py +++ b/lib/ansible/executor/task_queue_manager.py @@ -178,7 +178,7 @@ class TaskQueueManager: else: self._stdout_callback = callback_loader.get(self._stdout_callback) try: - self._stdout_callback.set_options(C.config.get_plugin_options('callback', self._stdout_callback._load_name)) + self._stdout_callback.set_options() except AttributeError: display.deprecated("%s stdout callback, does not support setting 'options', it will work for now, " " but this will be required in the future and should be updated," @@ -207,7 +207,7 @@ class TaskQueueManager: callback_obj = callback_plugin() try: - callback_obj .set_options(C.config.get_plugin_options('callback', callback_plugin._load_name)) + callback_obj.set_options() except AttributeError: display.deprecated("%s callback, does not support setting 'options', it will work for now, " " but this will be required in the future and should be updated, " diff --git a/lib/ansible/inventory/data.py b/lib/ansible/inventory/data.py index 7eb5715e6eb..27cbecc3bcf 100644 --- a/lib/ansible/inventory/data.py +++ b/lib/ansible/inventory/data.py @@ -173,6 +173,17 @@ class InventoryData(object): else: display.debug("group %s already in inventory" % group) + def remove_group(self, group): + + if group in self.groups: + del self.groups[group] + display.debug("Removed group %s from inventory" % group) + self._groups_dict_cache = {} + + for host in self.hosts: + h = self.hosts[host] + h.remove_group(group) + def add_host(self, host, group=None, port=None): ''' adds a host to inventory and possibly a group if not there already ''' @@ -209,6 +220,15 @@ class InventoryData(object): self._groups_dict_cache = {} display.debug("Added host %s to group %s" % (host, group)) + def remove_host(self, host): + + if host in self.hosts: + del self.hosts[host] + + for group in self.groups: + g = self.groups[group] + g.remove_host(host) + def set_variable(self, entity, varname, value): ''' sets a varible for an inventory object ''' diff --git a/lib/ansible/inventory/manager.py b/lib/ansible/inventory/manager.py index 167e7305e6b..aab983c27dc 100644 --- a/lib/ansible/inventory/manager.py +++ b/lib/ansible/inventory/manager.py @@ -183,6 +183,7 @@ class InventoryManager(object): for name in C.INVENTORY_ENABLED: plugin = inventory_loader.get(name) if plugin: + plugin.set_options() self._inventory_plugins.append(plugin) else: display.warning('Failed to load inventory plugin, skipping %s' % name) @@ -282,7 +283,8 @@ class InventoryManager(object): else: for fail in failures: display.warning(u'\n* Failed to parse %s with %s plugin: %s' % (to_text(fail['src']), fail['plugin'], to_text(fail['exc']))) - display.vvv(to_text(fail['exc'].tb)) + if hasattr(fail['exc'], 'tb'): + display.vvv(to_text(fail['exc'].tb)) if not parsed: display.warning("Unable to parse %s as an inventory source" % to_text(source)) diff --git a/lib/ansible/plugins/__init__.py b/lib/ansible/plugins/__init__.py index cee24dad581..20c3e822af0 100644 --- a/lib/ansible/plugins/__init__.py +++ b/lib/ansible/plugins/__init__.py @@ -47,6 +47,9 @@ def get_plugin_class(obj): class AnsiblePlugin(with_metaclass(ABCMeta, object)): + # allow extra passthrough parameters + allow_extras = False + def __init__(self): self._options = {} @@ -59,8 +62,28 @@ class AnsiblePlugin(with_metaclass(ABCMeta, object)): def set_option(self, option, value): self._options[option] = value - def set_options(self, options): - self._options = options + def set_options(self, task_keys=None, var_options=None, direct=None): + ''' + Sets the _options attribute with the configuration/keyword information for this plugin + + :arg task_keys: Dict with playbook keywords that affect this option + :arg var_options: Dict with either 'conneciton variables' + :arg direct: Dict with 'direct assignment' + ''' + + if not self._options: + # load config options if we have not done so already, if vars provided we should be mostly done + self._options = C.config.get_plugin_options(get_plugin_class(self), self._load_name, keys=task_keys, variables=var_options) + + # they can be direct options overriding config + if direct: + for k in self._options: + if k in direct: + self.set_option(k, direct[k]) + + # allow extras/wildcards from vars that are not directly consumed in configuration + if self.allow_extras and var_options and '_extras' in var_options: + self.set_option('_extras', var_options['_extras']) def _check_required(self): # FIXME: standarize required check based on config diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py index 6b5ff9dcbc6..e7f9c95fed4 100644 --- a/lib/ansible/plugins/action/__init__.py +++ b/lib/ansible/plugins/action/__init__.py @@ -612,6 +612,21 @@ class ActionBase(with_metaclass(ABCMeta, object)): # make sure all commands use the designated shell executable module_args['_ansible_shell_executable'] = self._play_context.executable + def _update_connection_options(self, options, variables=None): + ''' ensures connections have the appropriate information ''' + update = {} + + if getattr(self.connection, 'glob_option_vars', False): + # if the connection allows for it, pass any variables matching it. + if variables is not None: + for varname in variables: + if varname.match('ansible_%s_' % self.connection._load_name): + update[varname] = variables[varname] + + # always override existing with options + update.update(options) + self.connection.set_options(update) + def _execute_module(self, module_name=None, module_args=None, tmp=None, task_vars=None, persist_files=False, delete_remote_tmp=True, wrap_async=False): ''' Transfer and run a module along with its arguments. diff --git a/lib/ansible/plugins/cache/__init__.py b/lib/ansible/plugins/cache/__init__.py index 7bc11226aa7..0008e982d20 100644 --- a/lib/ansible/plugins/cache/__init__.py +++ b/lib/ansible/plugins/cache/__init__.py @@ -246,6 +246,9 @@ class FactCache(MutableMapping): # Backwards compat: self._display isn't really needed, just import the global display and use that. self._display = display + # in memory cache so plugins don't expire keys mid run + self._cache = {} + def __getitem__(self, key): if not self._plugin.contains(key): raise KeyError diff --git a/lib/ansible/plugins/callback/__init__.py b/lib/ansible/plugins/callback/__init__.py index 29034168aaf..2bd6b689b97 100644 --- a/lib/ansible/plugins/callback/__init__.py +++ b/lib/ansible/plugins/callback/__init__.py @@ -26,7 +26,7 @@ import warnings from copy import deepcopy from ansible import constants as C -from ansible.plugins import AnsiblePlugin +from ansible.plugins import AnsiblePlugin, get_plugin_class from ansible.module_utils._text import to_text from ansible.utils.color import stringc from ansible.vars.clean import strip_internal_keys @@ -81,8 +81,22 @@ class CallbackBase(AnsiblePlugin): ''' helper for callbacks, so they don't all have to include deepcopy ''' _copy_result = deepcopy - def set_options(self, options): - self._plugin_options = options + def set_option(self, k, v): + self._plugin_options[k] = v + + def set_options(self, task_keys=None, var_options=None, direct=None): + ''' This is different than the normal plugin method as callbacks get called early and really don't accept keywords. + Also _options was already taken for CLI args and callbacks use _plugin_options instead. + ''' + + # load from config + self._plugin_options = C.config.get_plugin_options(get_plugin_class(self), self._load_name, keys=task_keys, variables=var_options) + + # or parse specific options + if direct: + for k in direct: + if k in self._plugin_options: + self.set_option(k, direct[k]) def _dump_results(self, result, indent=None, sort_keys=True, keep_invocation=False): diff --git a/lib/ansible/plugins/callback/logentries.py b/lib/ansible/plugins/callback/logentries.py index 980f2a7aed5..b9f67496574 100644 --- a/lib/ansible/plugins/callback/logentries.py +++ b/lib/ansible/plugins/callback/logentries.py @@ -249,9 +249,6 @@ class CallbackModule(CallbackBase): # self.set_options({'api': 'data.logentries.com', 'port': 80, # 'tls_port': 10000, 'use_tls': True, 'flatten': False, 'token': 'ae693734-4c5b-4a44-8814-1d2feb5c8241'}) - def set_option(self, name, value): - raise AnsibleError("The Logentries callabck plugin does not suport setting individual options.") - def set_options(self, options): super(CallbackModule, self).set_options(options) diff --git a/lib/ansible/plugins/connection/__init__.py b/lib/ansible/plugins/connection/__init__.py index aac8324c40e..f34e3d51fe2 100644 --- a/lib/ansible/plugins/connection/__init__.py +++ b/lib/ansible/plugins/connection/__init__.py @@ -116,17 +116,6 @@ class ConnectionBase(AnsiblePlugin): raise AnsibleError("Internal Error: this connection module does not support running commands via %s" % self._play_context.become_method) - def set_host_overrides(self, host, hostvars=None): - ''' - An optional method, which can be used to set connection plugin parameters - from variables set on the host (or groups to which the host belongs) - - Any connection plugin using this should first initialize its attributes in - an overridden `def __init__(self):`, and then use `host.get_vars()` to find - variables which may be used to set those attributes in this method. - ''' - pass - @staticmethod def _split_ssh_args(argstring): """ diff --git a/lib/ansible/plugins/connection/funcd.py b/lib/ansible/plugins/connection/funcd.py index 0562e3afd94..5401725b690 100644 --- a/lib/ansible/plugins/connection/funcd.py +++ b/lib/ansible/plugins/connection/funcd.py @@ -49,10 +49,11 @@ except ImportError: class Connection(object): ''' Func-based connections ''' + has_pipelining = False + def __init__(self, runner, host, port, *args, **kwargs): self.runner = runner self.host = host - self.has_pipelining = False # port is unused, this go on func self.port = port diff --git a/lib/ansible/plugins/connection/network_cli.py b/lib/ansible/plugins/connection/network_cli.py index f567feecab0..bce7d0b9d09 100644 --- a/lib/ansible/plugins/connection/network_cli.py +++ b/lib/ansible/plugins/connection/network_cli.py @@ -48,23 +48,18 @@ import json import logging import re import os -import signal import socket import traceback -from collections import Sequence - from ansible import constants as C from ansible.errors import AnsibleConnectionFailure -from ansible.module_utils.six import PY3, BytesIO, binary_type +from ansible.module_utils.six import BytesIO, PY3 from ansible.module_utils.six.moves import cPickle from ansible.module_utils._text import to_bytes, to_text from ansible.playbook.play_context import PlayContext from ansible.plugins.loader import cliconf_loader, terminal_loader, connection_loader from ansible.plugins.connection import ConnectionBase -from ansible.plugins.connection.local import Connection as LocalConnection -from ansible.plugins.connection.paramiko_ssh import Connection as ParamikoSshConnection -from ansible.utils.path import unfrackpath, makedirs_safe +from ansible.utils.path import unfrackpath try: from __main__ import display @@ -91,7 +86,8 @@ class Connection(ConnectionBase): self._last_response = None self._history = list() - self._local = LocalConnection(play_context, new_stdin, *args, **kwargs) + self._local = connection_loader.get('local', play_context, '/dev/null') + self._local.set_options() self._terminal = None self._cliconf = None @@ -166,10 +162,9 @@ class Connection(ConnectionBase): if self.connected: return - if self._play_context.password and not self._play_context.private_key_file: - C.PARAMIKO_LOOK_FOR_KEYS = False - - ssh = ParamikoSshConnection(self._play_context, '/dev/null')._connect() + p = connection_loader.get('paramiko', self._play_context, '/dev/null') + p.set_options(direct={'look_for_keys': bool(self._play_context.password and not self._play_context.private_key_file)}) + ssh = p._connect() self.ssh = ssh.ssh display.vvvv('ssh connection done, setting terminal', host=self._play_context.remote_addr) diff --git a/lib/ansible/plugins/connection/paramiko_ssh.py b/lib/ansible/plugins/connection/paramiko_ssh.py index 1ae551c7777..93698836573 100644 --- a/lib/ansible/plugins/connection/paramiko_ssh.py +++ b/lib/ansible/plugins/connection/paramiko_ssh.py @@ -15,6 +15,7 @@ DOCUMENTATION = """ - This is needed on the Ansible control machine to be reasonably efficient with connections. Thus paramiko is faster for most users on these platforms. Users with ControlPersist capability can consider using -c ssh or configuring the transport in the configuration file. + - This plugin also borrows a lot of settings from the ssh plugin as they both cover the same protocol. version_added: "0.1" options: remote_addr: @@ -28,26 +29,94 @@ DOCUMENTATION = """ remote_user: description: - User to login/authenticate as + - Can be set from the CLI via the ``--user`` or ``-u`` options. vars: - name: ansible_user - name: ansible_ssh_user - name: ansible_paramiko_user + env: + - name: ANSIBLE_REMOTE_USER + - name: ANSIBLE_PARAMIKO_REMOTE_USER + version_added: '2.5' + ini: + - section: defaults + key: remote_user + - section: paramiko_connection + key: remote_user + version_added: '2.5' + password: + description: + - Secret used to either login the ssh server or as a passphrase for ssh keys that require it + - Can be set from the CLI via the ``--ask-pass`` option. + vars: + - name: ansible_password + - name: ansible_ssh_pass + - name: ansible_paramiko_pass + version_added: '2.5' + host_key_auto_add: + description: 'TODO: write it' + env: [{name: ANSIBLE_PARAMIKO_HOST_KEY_AUTO_ADD}] + ini: + - {key: host_key_auto_add, section: paramiko_connection} + type: boolean + look_for_keys: + default: True + description: 'TODO: write it' + env: [{name: ANSIBLE_PARAMIKO_LOOK_FOR_KEYS}] + ini: + - {key: look_for_keys, section: paramiko_connection} + type: boolean + proxy_command: + default: '' + description: + - Proxy information for running the connection via a jumphost + - Also this plugin will scan 'ssh_args', 'ssh_extra_args' and 'ssh_common_args' from the 'ssh' plugin settings for proxy information if set. + env: [{name: ANSIBLE_PARAMIKO_PROXY_COMMAND}] + ini: + - {key: proxy_command, section: paramiko_connection} + pty: + default: True + description: 'TODO: write it' + env: + - name: ANSIBLE_PARAMIKO_PTY + ini: + - section: paramiko_connection + key: pty + type: boolean + record_host_keys: + default: True + description: 'TODO: write it' + env: [{name: ANSIBLE_PARAMIKO_RECORD_HOST_KEYS}] + ini: + - section: paramiko_connection + key: record_host_keys + type: boolean + host_key_checking: + description: 'Set this to "False" if you want to avoid host key checking by the underlying tools Ansible uses to connect to the host' + type: boolean + default: True + env: + - name: ANSIBLE_HOST_KEY_CHECKING + - name: ANSIBLE_SSH_HOST_KEY_CHECKING + version_added: '2.5' + - name: ANSIBLE_PARAMIKO_HOST_KEY_CHECKING + version_added: '2.5' + ini: + - section: defaults + key: host_key_checking + - section: paramiko_connection + key: host_key_checking + version_added: '2.5' + vars: + - name: ansible_host_key_checking + version_added: '2.5' + - name: ansible_ssh_host_key_checking + version_added: '2.5' + - name: ansible_paramiko_host_key_checking + version_added: '2.5' # TODO: -#getattr(self._play_context, 'ssh_extra_args', '') or '', -#getattr(self._play_context, 'ssh_common_args', '') or '', -#getattr(self._play_context, 'ssh_args', '') or '', -#C.HOST_KEY_CHECKING -#C.PARAMIKO_HOST_KEY_AUTO_ADD #C.USE_PERSISTENT_CONNECTIONS: -# ssh.connect( -# look_for_keys=C.PARAMIKO_LOOK_FOR_KEYS, -# key_filename, -# password=self._play_context.password, -# timeout=self._play_context.timeout, -# port=port, -#proxy_command = proxy_command or C.PARAMIKO_PROXY_COMMAND -#C.PARAMIKO_PTY -#C.PARAMIKO_RECORD_HOST_KEYS +#timeout=self._play_context.timeout, """ import warnings @@ -110,10 +179,11 @@ class MyAddPolicy(object): def __init__(self, new_stdin, connection): self._new_stdin = new_stdin self.connection = connection + self._options = connection._options def missing_host_key(self, client, hostname, key): - if all((C.HOST_KEY_CHECKING, not C.PARAMIKO_HOST_KEY_AUTO_ADD)): + if all((self._options['host_key_checking'], not self._options['host_key_auto_add'])): fingerprint = hexlify(key.get_fingerprint()) ktype = key.get_name() @@ -194,7 +264,7 @@ class Connection(ConnectionBase): if proxy_command: break - proxy_command = proxy_command or C.PARAMIKO_PROXY_COMMAND + proxy_command = proxy_command or self._options['proxy_command'] sock_kwarg = {} if proxy_command: @@ -229,7 +299,7 @@ class Connection(ConnectionBase): self.keyfile = os.path.expanduser("~/.ssh/known_hosts") - if C.HOST_KEY_CHECKING: + if self._options['host_key_checking']: for ssh_known_hosts in ("/etc/ssh/ssh_known_hosts", "/etc/openssh/ssh_known_hosts"): try: # TODO: check if we need to look at several possible locations, possible for loop @@ -257,7 +327,7 @@ class Connection(ConnectionBase): self._play_context.remote_addr, username=self._play_context.remote_user, allow_agent=allow_agent, - look_for_keys=C.PARAMIKO_LOOK_FOR_KEYS, + look_for_keys=self._options['look_for_keys'], key_filename=key_filename, password=self._play_context.password, timeout=self._play_context.timeout, @@ -301,7 +371,7 @@ class Connection(ConnectionBase): # sudo usually requires a PTY (cf. requiretty option), therefore # we give it one by default (pty=True in ansble.cfg), and we try # to initialise from the calling environment when sudoable is enabled - if C.PARAMIKO_PTY and sudoable: + if self._options['pty'] and sudoable: chan.get_pty(term=os.getenv('TERM', 'vt100'), width=int(os.getenv('COLUMNS', 0)), height=int(os.getenv('LINES', 0))) display.vvv("EXEC %s" % cmd, host=self._play_context.remote_addr) @@ -454,7 +524,7 @@ class Connection(ConnectionBase): if self.sftp is not None: self.sftp.close() - if C.HOST_KEY_CHECKING and C.PARAMIKO_RECORD_HOST_KEYS and self._any_keys_added(): + if self._options['host_key_checking'] and self._options['record_host_keys'] and self._any_keys_added(): # add any new SSH host keys -- warning -- this could be slow # (This doesn't acquire the connection lock because it needs diff --git a/lib/ansible/plugins/connection/persistent.py b/lib/ansible/plugins/connection/persistent.py index 7ade4f4bc78..53415f7c6f7 100644 --- a/lib/ansible/plugins/connection/persistent.py +++ b/lib/ansible/plugins/connection/persistent.py @@ -14,13 +14,11 @@ DOCUMENTATION = """ version_added: "2.3" """ import os -import sys import pty import json import subprocess from ansible import constants as C -from ansible.plugins.loader import connection_loader from ansible.plugins.connection import ConnectionBase from ansible.module_utils._text import to_text from ansible.module_utils.six.moves import cPickle diff --git a/lib/ansible/plugins/connection/ssh.py b/lib/ansible/plugins/connection/ssh.py index 2890795e45d..6207e31a053 100644 --- a/lib/ansible/plugins/connection/ssh.py +++ b/lib/ansible/plugins/connection/ssh.py @@ -22,14 +22,23 @@ DOCUMENTATION = ''' - name: ansible_host - name: ansible_ssh_host host_key_checking: - #constant: HOST_KEY_CHECKING description: Determines if ssh should check host keys type: boolean ini: - section: defaults key: 'host_key_checking' + - section: ssh_connection + key: 'host_key_checking' + version_added: '2.5' env: - name: ANSIBLE_HOST_KEY_CHECKING + - name: ANSIBLE_SSH_HOST_KEY_CHECKING + version_added: '2.5' + vars: + - name: ansible_host_key_checking + version_added: '2.5' + - name: ansible_ssh_host_key_checking + version_added: '2.5' password: description: Authentication password for the C(remote_user). Can be supplied as CLI option. vars: diff --git a/lib/ansible/plugins/connection/winrm.py b/lib/ansible/plugins/connection/winrm.py index 500162d6a03..81df18e4c95 100644 --- a/lib/ansible/plugins/connection/winrm.py +++ b/lib/ansible/plugins/connection/winrm.py @@ -11,8 +11,13 @@ DOCUMENTATION = """ short_description: Run tasks over Microsoft's WinRM description: - Run commands or put/fetch on a target via WinRM + - This plugin allows extra arguments to be passed that are supported by the protocol but not explicitly defined here. + They should take the form of variables declared with the following pattern `ansible_winrm_