allow config for callbaks and some fixes

* only complain about ini deprecation if value is set
 * set plugin config for stdout and other types
 * updated plugin docs, moved several plugins to new config
 * finished ssh docs
 * fixed some issues seen in plugins while modifying docs
 * placeholder for 'required'
 * callbacks must use _plugin_options as _options already in use

(cherry picked from commit 869a318492)
pull/29947/head
Brian Coca 7 years ago committed by Toshio Kuratomi
parent 61315cd3c5
commit 34db3cd9be

@ -215,6 +215,15 @@ class ConfigManager(object):
''' Load YAML Config Files in order, check merge flags, keep origin of settings''' ''' Load YAML Config Files in order, check merge flags, keep origin of settings'''
pass pass
def get_plugin_options(self, plugin_type, name, 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)
return options
def get_configuration_definitions(self, plugin_type=None, name=None): def get_configuration_definitions(self, plugin_type=None, name=None):
''' just list the possible settings, either base or for specific plugins or plugin ''' ''' just list the possible settings, either base or for specific plugins or plugin '''
@ -224,7 +233,7 @@ class ConfigManager(object):
elif name is None: elif name is None:
ret = self._plugins.get(plugin_type, {}) ret = self._plugins.get(plugin_type, {})
else: else:
ret = {name: self._plugins.get(plugin_type, {}).get(name, {})} ret = self._plugins.get(plugin_type, {}).get(name, {})
return ret return ret
@ -287,7 +296,7 @@ class ConfigManager(object):
for ini_entry in defs[config]['ini']: for ini_entry in defs[config]['ini']:
value = get_ini_config_value(self._parser, ini_entry) value = get_ini_config_value(self._parser, ini_entry)
origin = cfile origin = cfile
if 'deprecated' in ini_entry: if value is not None and 'deprecated' in ini_entry:
self.DEPRECATED.append(('[%s]%s' % (ini_entry['section'], ini_entry['key']), ini_entry['deprecated'])) self.DEPRECATED.append(('[%s]%s' % (ini_entry['section'], ini_entry['key']), ini_entry['deprecated']))
except Exception as e: except Exception as e:
sys.stderr.write("Error while loading ini config %s: %s" % (cfile, to_native(e))) sys.stderr.write("Error while loading ini config %s: %s" % (cfile, to_native(e)))

@ -176,6 +176,7 @@ class TaskQueueManager:
raise AnsibleError("Invalid callback for stdout specified: %s" % self._stdout_callback) raise AnsibleError("Invalid callback for stdout specified: %s" % self._stdout_callback)
else: else:
self._stdout_callback = callback_loader.get(self._stdout_callback) self._stdout_callback = callback_loader.get(self._stdout_callback)
self._stdout_callback.set_options(C.config.get_plugin_options('callback', self._stdout_callback._load_name))
stdout_callback_loaded = True stdout_callback_loaded = True
else: else:
raise AnsibleError("callback must be an instance of CallbackBase or the name of a callback plugin") raise AnsibleError("callback must be an instance of CallbackBase or the name of a callback plugin")
@ -198,7 +199,9 @@ class TaskQueueManager:
C.DEFAULT_CALLBACK_WHITELIST is None or callback_name not in C.DEFAULT_CALLBACK_WHITELIST)): C.DEFAULT_CALLBACK_WHITELIST is None or callback_name not in C.DEFAULT_CALLBACK_WHITELIST)):
continue continue
self._callback_plugins.append(callback_plugin()) callback_obj = callback_plugin()
callback_obj .set_options(C.config.get_plugin_options('callback', callback_plugin._load_name))
self._callback_plugins.append(callback_obj)
self._callbacks_loaded = True self._callbacks_loaded = True
@ -366,4 +369,4 @@ class TaskQueueManager:
display.warning(u"Failure using method (%s) in callback plugin (%s): %s" % (to_text(method_name), to_text(callback_plugin), to_text(e))) display.warning(u"Failure using method (%s) in callback plugin (%s): %s" % (to_text(method_name), to_text(callback_plugin), to_text(e)))
from traceback import format_tb from traceback import format_tb
from sys import exc_info from sys import exc_info
display.debug('Callback Exception: \n' + ' '.join(format_tb(exc_info()[2]))) display.vvv('Callback Exception: \n' + ' '.join(format_tb(exc_info()[2])))

@ -24,7 +24,7 @@ __metaclass__ = type
from abc import ABCMeta from abc import ABCMeta
from ansible import constants as C from ansible import constants as C
from ansible.module_utils.six import with_metaclass from ansible.module_utils.six import with_metaclass, string_types
try: try:
from __main__ import display from __main__ import display
@ -39,22 +39,29 @@ PLUGIN_PATH_CACHE = {}
def get_plugin_class(obj): def get_plugin_class(obj):
if isinstance(obj, string_types):
return obj.lower().replace('module', '')
else:
return obj.__class__.__name__.lower().replace('module', '') return obj.__class__.__name__.lower().replace('module', '')
class AnsiblePlugin(with_metaclass(ABCMeta, object)): class AnsiblePlugin(with_metaclass(ABCMeta, object)):
def __init__(self): def __init__(self):
self.options = {} self._options = {}
def get_option(self, option, hostvars=None): def get_option(self, option, hostvars=None):
if option not in self.options: if option not in self._options:
option_value = C.config.get_config_value(option, plugin_type=get_plugin_class(self), plugin_name=self.name, variables=hostvars) option_value = C.config.get_config_value(option, plugin_type=get_plugin_class(self), plugin_name=self.name, variables=hostvars)
self.set_option(option, option_value) self.set_option(option, option_value)
return self.options.get(option) return self._options.get(option)
def set_option(self, option, value): def set_option(self, option, value):
self.options[option] = value self._options[option] = value
def set_options(self, options): def set_options(self, options):
self.options = options self._options = options
def _check_required(self):
# FIXME: standarize required check based on config
pass

@ -26,6 +26,7 @@ import warnings
from copy import deepcopy from copy import deepcopy
from ansible import constants as C from ansible import constants as C
from ansible.plugins import AnsiblePlugin
from ansible.module_utils._text import to_text from ansible.module_utils._text import to_text
from ansible.utils.color import stringc from ansible.utils.color import stringc
from ansible.vars.manager import strip_internal_keys from ansible.vars.manager import strip_internal_keys
@ -45,7 +46,7 @@ except ImportError:
__all__ = ["CallbackBase"] __all__ = ["CallbackBase"]
class CallbackBase: class CallbackBase(AnsiblePlugin):
''' '''
This is a base ansible callback class that does nothing. New callbacks should This is a base ansible callback class that does nothing. New callbacks should
@ -53,7 +54,8 @@ class CallbackBase:
custom actions. custom actions.
''' '''
def __init__(self, display=None): def __init__(self, display=None, options=None):
if display: if display:
self._display = display self._display = display
else: else:
@ -70,9 +72,18 @@ class CallbackBase:
version = getattr(self, 'CALLBACK_VERSION', '1.0') version = getattr(self, 'CALLBACK_VERSION', '1.0')
self._display.vvvv('Loading callback plugin %s of type %s, v%s from %s' % (name, ctype, version, __file__)) self._display.vvvv('Loading callback plugin %s of type %s, v%s from %s' % (name, ctype, version, __file__))
self.disabled = False
self._plugin_options = {}
if options is not None:
self.set_options(options)
''' helper for callbacks, so they don't all have to include deepcopy ''' ''' helper for callbacks, so they don't all have to include deepcopy '''
_copy_result = deepcopy _copy_result = deepcopy
def set_options(self, options):
self._plugin_options = options
def _dump_results(self, result, indent=None, sort_keys=True, keep_invocation=False): def _dump_results(self, result, indent=None, sort_keys=True, keep_invocation=False):
if result.get('_ansible_no_log', False): if result.get('_ansible_no_log', False):
return json.dumps(dict(censored="the output has been hidden due to the fact that 'no_log: true' was specified for this result")) return json.dumps(dict(censored="the output has been hidden due to the fact that 'no_log: true' was specified for this result"))

@ -14,17 +14,17 @@ DOCUMENTATION:
show_skipped_hosts: show_skipped_hosts:
name: Show skipped hosts name: Show skipped hosts
description: "Toggle to control displaying skipped task/host results in a task" description: "Toggle to control displaying skipped task/host results in a task"
default: True
env: env:
- name: DISPLAY_SKIPPED_HOSTS - name: DISPLAY_SKIPPED_HOSTS
ini: ini:
- key: display_skipped_hosts - key: display_skipped_hosts
section: defaults section: defaults
type: boolean type: boolean
default: True
show_custom_stats: show_custom_stats:
name: Show custom stats name: Show custom stats
default: False
description: 'This adds the custom stats set via the set_stats plugin to the play recap' description: 'This adds the custom stats set via the set_stats plugin to the play recap'
default: False
env: env:
- name: ANSIBLE_SHOW_CUSTOM_STATS - name: ANSIBLE_SHOW_CUSTOM_STATS
ini: ini:
@ -119,7 +119,7 @@ class CallbackModule(CallbackBase):
self._display.display(msg, color=color) self._display.display(msg, color=color)
def v2_runner_on_skipped(self, result): def v2_runner_on_skipped(self, result):
if C.DISPLAY_SKIPPED_HOSTS: if self._plugin_options['show_skipped_hosts']:
delegated_vars = result._result.get('_ansible_delegated_vars', None) delegated_vars = result._result.get('_ansible_delegated_vars', None)
self._clean_results(result._result, result._task.action) self._clean_results(result._result, result._task.action)
@ -248,7 +248,7 @@ class CallbackModule(CallbackBase):
self._display.display(msg + " (item=%s) => %s" % (self._get_item(result._result), self._dump_results(result._result)), color=C.COLOR_ERROR) self._display.display(msg + " (item=%s) => %s" % (self._get_item(result._result), self._dump_results(result._result)), color=C.COLOR_ERROR)
def v2_runner_item_on_skipped(self, result): def v2_runner_item_on_skipped(self, result):
if C.DISPLAY_SKIPPED_HOSTS: if self._plugin_options['show_skipped_hosts']:
self._clean_results(result._result, result._task.action) self._clean_results(result._result, result._task.action)
msg = "skipping: [%s] => (item=%s) " % (result._host.get_name(), self._get_item(result._result)) msg = "skipping: [%s] => (item=%s) " % (result._host.get_name(), self._get_item(result._result))
if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result: if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
@ -287,7 +287,7 @@ class CallbackModule(CallbackBase):
self._display.display("", screen_only=True) self._display.display("", screen_only=True)
# print custom stats # print custom stats
if C.SHOW_CUSTOM_STATS and stats.custom: if self._plugin_options['show_custom_stats'] and stats.custom:
self._display.banner("CUSTOM STATS: ") self._display.banner("CUSTOM STATS: ")
# per host # per host
# TODO: come up with 'pretty format' # TODO: come up with 'pretty format'
@ -308,11 +308,11 @@ class CallbackModule(CallbackBase):
self._display.banner("PLAYBOOK: %s" % basename(playbook._file_name)) self._display.banner("PLAYBOOK: %s" % basename(playbook._file_name))
if self._display.verbosity > 3: if self._display.verbosity > 3:
if self._options is not None: if self._plugin_options is not None:
for option in dir(self._options): for option in dir(self._plugin_options):
if option.startswith('_') or option in ['read_file', 'ensure_value', 'read_module']: if option.startswith('_') or option in ['read_file', 'ensure_value', 'read_module']:
continue continue
val = getattr(self._options, option) val = getattr(self._plugin_options, option)
if val: if val:
self._display.vvvv('%s: %s' % (option, val)) self._display.vvvv('%s: %s' % (option, val))

@ -8,13 +8,14 @@ DOCUMENTATION:
type: notification type: notification
short_description: Sends events to Logentries short_description: Sends events to Logentries
description: description:
- This callback plugin will generate JSON objects and send them to Logentries for auditing/debugging purposes. - This callback plugin will generate JSON objects and send them to Logentries via TCP for auditing/debugging purposes.
- If you want to use an ini configuration, the file must be placed in the same directory as this plugin and named logentries.ini - Before 2.4, if you wanted to use an ini configuration, the file must be placed in the same directory as this plugin and named logentries.ini
- In 2.4 and above you can just put it in the main Ansible configuration file.
version_added: "2.0" version_added: "2.0"
requirements: requirements:
- whitelisting in configuration - whitelisting in configuration
- certifi (python library) - certifi (python library)
- flatdict (pytnon library) - flatdict (pytnon library), if you want to use the 'flatten' option
options: options:
api: api:
description: URI to the Logentries API description: URI to the Logentries API
@ -22,7 +23,7 @@ DOCUMENTATION:
- name: LOGENTRIES_API - name: LOGENTRIES_API
default: data.logentries.com default: data.logentries.com
ini: ini:
- section: defaults - section: callback_logentries
key: api key: api
port: port:
description: Http port to use when connecting to the API description: Http port to use when connecting to the API
@ -30,7 +31,7 @@ DOCUMENTATION:
- name: LOGENTRIES_PORT - name: LOGENTRIES_PORT
default: 80 default: 80
ini: ini:
- section: defaults - section: callback_logentries
key: port key: port
tls_port: tls_port:
description: Port to use when connecting to the API when TLS is enabled description: Port to use when connecting to the API when TLS is enabled
@ -38,15 +39,15 @@ DOCUMENTATION:
- name: LOGENTRIES_TLS_PORT - name: LOGENTRIES_TLS_PORT
default: 443 default: 443
ini: ini:
- section: defaults - section: callback_logentries
key: tls_port key: tls_port
token: token:
description: the authentication token description: The logentries "TCP token"
env: env:
- name: LOGENTRIES_ANSIBLE_TOKEN - name: LOGENTRIES_ANSIBLE_TOKEN
required: True required: True
ini: ini:
- section: defaults - section: callback_logentries
key: token key: token
use_tls: use_tls:
description: description:
@ -56,7 +57,7 @@ DOCUMENTATION:
default: False default: False
type: boolean type: boolean
ini: ini:
- section: defaults - section: callback_logentries
key: use_tls key: use_tls
flatten: flatten:
description: flatten complex data structures into a single dictionary with complex keys description: flatten complex data structures into a single dictionary with complex keys
@ -65,7 +66,7 @@ DOCUMENTATION:
env: env:
- name: LOGENTRIES_FLATTEN - name: LOGENTRIES_FLATTEN
ini: ini:
- section: defaults - section: callback_logentries
key: flatten key: flatten
EXAMPLES: > EXAMPLES: >
To enable, add this to your ansible.cfg file in the defaults block To enable, add this to your ansible.cfg file in the defaults block
@ -78,8 +79,8 @@ EXAMPLES: >
export LOGENTRIES_PORT=10000 export LOGENTRIES_PORT=10000
export LOGENTRIES_ANSIBLE_TOKEN=dd21fc88-f00a-43ff-b977-e3a4233c53af export LOGENTRIES_ANSIBLE_TOKEN=dd21fc88-f00a-43ff-b977-e3a4233c53af
Or create a logentries.ini config file that sites next to the plugin with the following contents Or in the main Ansible config file
[logentries] [callback_logentries]
api = data.logentries.com api = data.logentries.com
port = 10000 port = 10000
tls_port = 20000 tls_port = 20000
@ -108,8 +109,8 @@ try:
except ImportError: except ImportError:
HAS_FLATDICT = False HAS_FLATDICT = False
from ansible.module_utils.six.moves import configparser from ansible.errors import AnsibleError
from ansible.module_utils._text import to_bytes, to_text from ansible.module_utils._text import to_bytes, to_text, to_native
from ansible.plugins.callback import CallbackBase from ansible.plugins.callback import CallbackBase
""" """
Todo: Todo:
@ -118,11 +119,7 @@ Todo:
class PlainTextSocketAppender(object): class PlainTextSocketAppender(object):
def __init__(self, def __init__(self, display, LE_API='data.logentries.com', LE_PORT=80, LE_TLS_PORT=443):
verbose=True,
LE_API='data.logentries.com',
LE_PORT=80,
LE_TLS_PORT=443):
self.LE_API = LE_API self.LE_API = LE_API
self.LE_PORT = LE_PORT self.LE_PORT = LE_PORT
@ -134,7 +131,7 @@ class PlainTextSocketAppender(object):
# Unicode Line separator character \u2028 # Unicode Line separator character \u2028
self.LINE_SEP = u'\u2028' self.LINE_SEP = u'\u2028'
self.verbose = verbose self._display = display
self._conn = None self._conn = None
def open_connection(self): def open_connection(self):
@ -149,9 +146,8 @@ class PlainTextSocketAppender(object):
try: try:
self.open_connection() self.open_connection()
return return
except Exception: except Exception as e:
if self.verbose: self._display.vvvv("Unable to connect to Logentries: %s" % str(e))
self._display.warning("Unable to connect to Logentries")
root_delay *= 2 root_delay *= 2
if (root_delay > self.MAX_DELAY): if (root_delay > self.MAX_DELAY):
@ -160,6 +156,7 @@ class PlainTextSocketAppender(object):
wait_for = root_delay + random.uniform(0, root_delay) wait_for = root_delay + random.uniform(0, root_delay)
try: try:
self._display.vvvv("sleeping %s before retry" % wait_for)
time.sleep(wait_for) time.sleep(wait_for)
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
@ -221,90 +218,75 @@ class CallbackModule(CallbackBase):
CALLBACK_NEEDS_WHITELIST = True CALLBACK_NEEDS_WHITELIST = True
def __init__(self): def __init__(self):
# TODO: allow for alternate posting methods (REST/UDP/agent/etc)
super(CallbackModule, self).__init__() super(CallbackModule, self).__init__()
# verify dependencies
if not HAS_SSL: if not HAS_SSL:
self._display.warning("Unable to import ssl module. Will send over port 80.") self._display.warning("Unable to import ssl module. Will send over port 80.")
warn = ''
if not HAS_CERTIFI: if not HAS_CERTIFI:
self.disabled = True self.disabled = True
warn += 'The `certifi` python module is not installed.' self._display.warning('The `certifi` python module is not installed.\nDisabling the Logentries callback plugin.')
if not HAS_FLATDICT: self.le_jobid = str(uuid.uuid4())
self.disabled = True
warn += 'The `flatdict` python module is not installed.'
if warn: # FIXME: remove when done testing
self._display.warning('%s\nDisabling the Logentries callback plugin.' % warn) # initialize configurable
self.api_url = 'data.logentries.com'
self.api_port = 80
self.api_tls_port = 443
self.use_tls = False
self.flatten = False
self.token = None
config_path = os.path.abspath(os.path.dirname(__file__)) # FIXME: make configurable, move to options
config = configparser.ConfigParser() self.timeout = 10
try:
config.readfp(open(os.path.join(config_path, 'logentries.ini')))
if config.has_option('logentries', 'api'):
self.api_uri = config.get('logentries', 'api')
if config.has_option('logentries', 'port'):
self.api_port = config.getint('logentries', 'port')
if config.has_option('logentries', 'tls_port'):
self.api_tls_port = config.getint('logentries', 'tls_port')
if config.has_option('logentries', 'use_tls'):
self.use_tls = config.getboolean('logentries', 'use_tls')
if config.has_option('logentries', 'token'):
self.token = config.get('logentries', 'token')
if config.has_option('logentries', 'flatten'):
self.flatten = config.getboolean('logentries', 'flatten')
except:
self.api_uri = os.getenv('LOGENTRIES_API')
if self.api_uri is None:
self.api_uri = 'data.logentries.com'
try: # FIXME: remove testing
self.api_port = int(os.getenv('LOGENTRIES_PORT')) # self.set_options({'api': 'data.logentries.com', 'port': 80,
if self.api_port is None: # 'tls_port': 10000, 'use_tls': True, 'flatten': False, 'token': 'ae693734-4c5b-4a44-8814-1d2feb5c8241'})
self.api_port = 80
except TypeError: def set_option(self, name, value):
self.api_port = 80 raise AnsibleError("The Logentries callabck plugin does not suport setting individual options.")
def set_options(self, options):
super(CallbackModule, self).set_options(options)
# get options
try: try:
self.api_tls_port = int(os.getenv('LOGENTRIES_TLS_PORT')) self.api_url = self._plugin_options['api']
if self.api_tls_port is None: self.api_port = self._plugin_options['port']
self.api_tls_port = 443 self.api_tls_port = self._plugin_options['tls_port']
except TypeError: self.use_tls = self._plugin_options['use_tls']
self.api_tls_port = 443 self.flatten = self._plugin_options['flatten']
except KeyError as e:
self._display.warning("Missing option for Logentries callback plugin: %s" % to_native(e))
self.disabled = True
# this just needs to be set to use TLS try:
self.use_tls = os.getenv('LOGENTRIES_USE_TLS') self.token = self._plugin_options['token']
if self.use_tls is None: except KeyError as e:
self.use_tls = False self._display.warning('Logentries token was not provided, this is required for this callback to operate, disabling')
elif self.use_tls.lower() in ['yes', 'true']: self.disabled = True
self.use_tls = True
self.token = os.getenv('LOGENTRIES_ANSIBLE_TOKEN') if self.flatten and not HAS_FLATDICT:
if self.token is None:
self.disabled = True self.disabled = True
self._display.warning('Logentries token could not be loaded. The logentries token can be provided using the `LOGENTRIES_TOKEN` environment ' self._display.warning('You have chosen to flatten and the `flatdict` python module is not installed.\nDisabling the Logentries callback plugin.')
'variable')
self.flatten = os.getenv('LOGENTRIES_FLATTEN') self._initialize_connections()
if self.flatten is None:
self.flatten = False
elif self.flatten.lower() in ['yes', 'true']:
self.flatten = True
self.verbose = False def _initialize_connections(self):
self.timeout = 10
self.le_jobid = str(uuid.uuid4())
if not self.disabled:
if self.use_tls: if self.use_tls:
self._appender = TLSSocketAppender(verbose=self.verbose, self._display.vvvv("Connecting to %s:%s with TLS" % (self.api_url, self.api_tls_port))
LE_API=self.api_uri, self._appender = TLSSocketAppender(display=self._display, LE_API=self.api_url, LE_TLS_PORT=self.api_tls_port)
LE_TLS_PORT=self.api_tls_port)
else: else:
self._appender = PlainTextSocketAppender(verbose=self.verbose, self._display.vvvv("Connecting to %s:%s" % (self.api_url, self.api_port))
LE_API=self.api_uri, self._appender = PlainTextSocketAppender(display=self._display, LE_API=self.api_url, LE_PORT=self.api_port)
LE_PORT=self.api_port)
self._appender.reopen_connection() self._appender.reopen_connection()
def emit_formatted(self, record): def emit_formatted(self, record):
@ -318,43 +300,34 @@ class CallbackModule(CallbackBase):
msg = record.rstrip('\n') msg = record.rstrip('\n')
msg = "{} {}".format(self.token, msg) msg = "{} {}".format(self.token, msg)
self._appender.put(msg) self._appender.put(msg)
self._display.vvvv("Sent event to logentries")
def _set_info(self, host, res):
return {'le_jobid': self.le_jobid, 'hostname': host, 'results': res}
def runner_on_ok(self, host, res): def runner_on_ok(self, host, res):
results = {} results = self._set_info(host, res)
results['le_jobid'] = self.le_jobid
results['hostname'] = host
results['results'] = res
results['status'] = 'OK' results['status'] = 'OK'
self.emit_formatted(results) self.emit_formatted(results)
def runner_on_failed(self, host, res, ignore_errors=False): def runner_on_failed(self, host, res, ignore_errors=False):
results = {} results = self._set_info(host, res)
results['le_jobid'] = self.le_jobid
results['hostname'] = host
results['results'] = res
results['status'] = 'FAILED' results['status'] = 'FAILED'
self.emit_formatted(results) self.emit_formatted(results)
def runner_on_skipped(self, host, item=None): def runner_on_skipped(self, host, item=None):
results = {} results = self._set_info(host, item)
results['le_jobid'] = self.le_jobid del results['results']
results['hostname'] = host
results['status'] = 'SKIPPED' results['status'] = 'SKIPPED'
self.emit_formatted(results) self.emit_formatted(results)
def runner_on_unreachable(self, host, res): def runner_on_unreachable(self, host, res):
results = {} results = self._set_info(host, res)
results['le_jobid'] = self.le_jobid
results['hostname'] = host
results['results'] = res
results['status'] = 'UNREACHABLE' results['status'] = 'UNREACHABLE'
self.emit_formatted(results) self.emit_formatted(results)
def runner_on_async_failed(self, host, res, jid): def runner_on_async_failed(self, host, res, jid):
results = {} results = self._set_info(host, res)
results['le_jobid'] = self.le_jobid
results['hostname'] = host
results['results'] = res
results['jid'] = jid results['jid'] = jid
results['status'] = 'ASYNC_FAILED' results['status'] = 'ASYNC_FAILED'
self.emit_formatted(results) self.emit_formatted(results)

@ -1,23 +1,20 @@
# (C) 2017, Tennis Smith, http://github.com/gamename # (c) 2017, Tennis Smith, http://github.com/gamename
# # (c) 2017 Ansible Project
# This file is free software: you can redistribute it and/or modify # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or '''
# (at your option) any later version. DOCUMENTATION:
# callback: profile_roles
# File is distributed in the hope that it will be useful, type: aggregate
# but WITHOUT ANY WARRANTY; without even the implied warranty of short_description: adds timing information to roles
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the version_added: "2.4"
# GNU General Public License for more details. description:
# - This callback module provides profiling for ansible roles.
# See <http://www.gnu.org/licenses/> for a copy of the requirements:
# GNU General Public License - whitelisting in configuration
'''
#
# This will track the use of each role during the life of a playbook's # Make coding more python3-ish
# execution. The total time spent in each role will be printed at the
# end.
#
# Make coding more python3-ish # Make coding more python3-ish
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
@ -117,11 +114,9 @@ class CallbackModule(CallbackBase):
# Print the timings starting with the largest one # Print the timings starting with the largest one
for result in self.totals.most_common(): for result in self.totals.most_common():
msg = u"{0:-<70}{1:->9}".format(result[0] + u' ', msg = u"{0:-<70}{1:->9}".format(result[0] + u' ', u' {0:.02f}s'.format(result[1]))
u' {0:.02f}s'.format(result[1]))
self._display.display(msg) self._display.display(msg)
msg_total = u"{0:-<70}{1:->9}".format(u'total ', msg_total = u"{0:-<70}{1:->9}".format(u'total ', u' {0:.02f}s'.format(total_time))
u' {0:.02f}s'.format(total_time))
self._display.display(filled("", fchar="~")) self._display.display(filled("", fchar="~"))
self._display.display(msg_total) self._display.display(msg_total)

@ -2,29 +2,60 @@
# (C) 2015, Tom Paine, <github@aioue.net> # (C) 2015, Tom Paine, <github@aioue.net>
# (C) 2014, Jharrod LaFon, @JharrodLaFon # (C) 2014, Jharrod LaFon, @JharrodLaFon
# (C) 2012-2013, Michael DeHaan, <michael.dehaan@gmail.com> # (C) 2012-2013, Michael DeHaan, <michael.dehaan@gmail.com>
# (C) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
'''
DOCUMENTATION:
callback: profile_tasks
type: aggregate
short_description: adds time information to tasks
version_added: "2.0"
description:
- Ansible callback plugin for timing individual tasks and overall execution time.
- "Mashup of 2 excellent original works: https://github.com/jlafon/ansible-profile,
https://github.com/junaid18183/ansible_home/blob/master/ansible_plugins/callback_plugins/timestamp.py.old"
- "Format: ``<task start timestamp> (<length of previous task>) <current elapsed playbook execution time>``"
- It also lists the top/bottom time consuming tasks in the summary (configurable)
- Before 2.4 only the environment variables were available for configuration.
requirements:
- whitelisting in configuration
options:
output_limit:
description: Number of tasks to display in the summary
default: 20
env:
- name: PROFILE_TASKS_TASK_OUTPUT_LIMIT
ini:
- section: callback_profile_tasks
key: task_output_limit
sort_order:
description: Adjust the sorting output of summary tasks
choices: ['descending', 'ascending', 'none']
default: 'descending'
env:
- name: PROFILE_TASKS_SORT_ORDER
ini:
- section: callback_profile_tasks
key: sort_order
#EXAMPLES: > '
# #
# This file is free software: you can redistribute it and/or modify # TASK: [ensure messaging security group exists] ********************************
# it under the terms of the GNU General Public License as published by # Thursday 11 June 2017 22:50:53 +0100 (0:00:00.721) 0:00:05.322 *********
# the Free Software Foundation, either version 3 of the License, or # ok: [localhost]
# (at your option) any later version.
# #
# File is distributed in the hope that it will be useful, # TASK: [ensure db security group exists] ***************************************
# but WITHOUT ANY WARRANTY; without even the implied warranty of # Thursday 11 June 2017 22:50:54 +0100 (0:00:00.558) 0:00:05.880 *********
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # changed: [localhost]
# GNU General Public License for more details. # '
# '''
# See <http://www.gnu.org/licenses/> for a copy of the
# GNU General Public License
# Provides per-task timing, ongoing playbook elapsed time and
# ordered list of top 20 longest running tasks at end
# Make coding more python3-ish
# Make coding more python3-ish # Make coding more python3-ish
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import collections import collections
import os
import time import time
from ansible.module_utils.six.moves import reduce from ansible.module_utils.six.moves import reduce
@ -82,19 +113,32 @@ class CallbackModule(CallbackBase):
def __init__(self): def __init__(self):
self.stats = collections.OrderedDict() self.stats = collections.OrderedDict()
self.current = None self.current = None
self.sort_order = os.getenv('PROFILE_TASKS_SORT_ORDER', True)
self.task_output_limit = os.getenv('PROFILE_TASKS_TASK_OUTPUT_LIMIT', 20)
self.sort_order = None
self.task_output_limit = None
super(CallbackModule, self).__init__()
def set_options(self, options):
super(CallbackModule, self).set_options(options)
self.sort_order = self._plugin_options['sort_order']
if self.sort_order is not None:
if self.sort_order == 'ascending': if self.sort_order == 'ascending':
self.sort_order = False self.sort_order = False
elif self.sort_order == 'descending':
self.sort_order = True
elif self.sort_order == 'none':
self.sort_order = None
self.task_output_limit = self._plugin_options['output_limit']
if self.task_output_limit is not None:
if self.task_output_limit == 'all': if self.task_output_limit == 'all':
self.task_output_limit = None self.task_output_limit = None
else: else:
self.task_output_limit = int(self.task_output_limit) self.task_output_limit = int(self.task_output_limit)
super(CallbackModule, self).__init__()
def _record_task(self, task): def _record_task(self, task):
""" """
Logs the start of each task Logs the start of each task
@ -126,7 +170,7 @@ class CallbackModule(CallbackBase):
results = self.stats.items() results = self.stats.items()
# Sort the tasks by the specified sort # Sort the tasks by the specified sort
if self.sort_order != 'none': if self.sort_order is not None:
results = sorted( results = sorted(
self.stats.items(), self.stats.items(),
key=lambda x: x[1]['time'], key=lambda x: x[1]['time'],

@ -1,75 +0,0 @@
profile\_tasks.py
=================
Ansible plugin for timing individual tasks and overall execution time.
Mashup of 2 excellent original works:
- https://github.com/jlafon/ansible-profile
- https://github.com/junaid18183/ansible_home/blob/master/ansible_plugins/callback_plugins/timestamp.py.old
Usage
-----
Add ``profile_tasks`` to the ``callback_whitelist`` in ``ansible.cfg``.
Run playbooks as normal.
Certain options are configurable using environment variables. You can specify ``ascending`` or ``none`` for
the environment variable ``PROFILE_TASKS_SORT_ORDER`` to adjust sorting output. If you want to see more than
20 tasks in the output you can set ``PROFILE_TASKS_TASK_OUTPUT_LIMIT`` to any number, or the special value
``all`` to get a list of all tasks.
Features
--------
Tasks
~~~~~
Ongoing timing of each task as it happens.
| Format:
| ``<task start timestamp> (<length of previous task>) <current elapsed playbook execution time>``
Task output example:
.. code:: shell
TASK: [ensure messaging security group exists] ********************************
Thursday 11 June 2017 22:50:53 +0100 (0:00:00.721) 0:00:05.322 *********
ok: [localhost]
TASK: [ensure db security group exists] ***************************************
Thursday 11 June 2017 22:50:54 +0100 (0:00:00.558) 0:00:05.880 *********
changed: [localhost]
Play Recap
~~~~~~~~~~
Recap includes ending timestamp, total playbook execution time and a
sorted list of the top longest running tasks.
No more wondering how old the results in a terminal window are.
.. code:: shell
ansible <args here>
<normal output here>
PLAY RECAP ********************************************************************
Thursday 11 June 2016 22:51:00 +0100 (0:00:01.011) 0:00:43.247 *********
===============================================================================
old_and_slow : install tons of packages -------------------------------- 20.03s
/home/bob/ansible/roles/old_and_slow/tasks/main.yml:4 -------------------------
db : second task to run ------------------------------------------------- 2.03s
/home/bob/ansible/roles/db/tasks/main.yml:4 -----------------------------------
setup ------------------------------------------------------------------- 0.42s
None --------------------------------------------------------------------------
www : first task to run ------------------------------------------------- 0.03s
/home/bob/ansible/roles/www/tasks/main.yml:1 ----------------------------------
fast_task : first task to run ------------------------------------------- 0.01s
/home/bob/ansible/roles/fast_task.yml:1 ---------------------------------------
Compatibility
-------------
Ansible 2.0+

@ -1,61 +1,59 @@
# (c) Fastly, inc 2016 # (c) Fastly, inc 2016
# # (c) 2017 Ansible Project
# This file is part of Ansible # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
""" """
selective.py callback plugin. DOCUMENTATION:
callback: selective
This callback only prints tasks that have been tagged with `print_action` or that have failed. callback_type: stdout
Tasks that are not printed are placed with a '.'. requirements:
- set as main display callback
For example: short_description: only print certain tasks
version_added: "2.4"
description:
- This callback only prints tasks that have been tagged with `print_action` or that have failed.
This allows operators to focus on the tasks that provide value only.
- Tasks that are not printed are placed with a '.'.
- If you increase verbosity all tasks are printed.
options:
nocolor:
default: False
description: This setting allows suppressing colorizing output
env:
- name: ANSIBLE_NOCOLOR
- name: ANSIBLE_SELECTIVE_DONT_COLORIZE
ini:
- section: defaults
- key: nocolor
type: boolean
EXAMPLES:
- debug: msg="This will not be printed" - debug: msg="This will not be printed"
- debug: msg="But this will" - debug: msg="But this will"
tags: [print_action]" tags: [print_action]
This allows operators to focus on the tasks that provide value only.
If you increase verbosity all tasks are printed.
""" """
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
import difflib import difflib
import os
from ansible import constants as C
from ansible.plugins.callback import CallbackBase from ansible.plugins.callback import CallbackBase
from ansible.module_utils._text import to_text from ansible.module_utils._text import to_text
__metaclass__ = type __metaclass__ = type
DONT_COLORIZE = False
COLORS = { COLORS = {
'normal': '\033[0m', 'normal': '\033[0m',
'ok': '\033[92m', 'ok': C.COLOR_OK,
'bold': '\033[1m', 'bold': '\033[1m',
'not_so_bold': '\033[1m\033[34m', 'not_so_bold': '\033[1m\033[34m',
'changed': '\033[93m', 'changed': C.COLOR_CHANGED,
'failed': '\033[91m', 'failed': C.COLOR_ERROR,
'endc': '\033[0m', 'endc': '\033[0m',
'skipped': '\033[96m', 'skipped': C.COLOR_SKIP,
} }
DONT_COLORIZE = os.getenv('ANSIBLE_SELECTIVE_DONT_COLORIZE', default=False)
def dict_diff(prv, nxt): def dict_diff(prv, nxt):
"""Return a dict of keys that differ with another config object.""" """Return a dict of keys that differ with another config object."""
@ -89,6 +87,13 @@ class CallbackModule(CallbackBase):
self.last_task_name = None self.last_task_name = None
self.printed_last_task = False self.printed_last_task = False
def set_options(self, options):
super(CallbackModule, self).set_options(options)
global DONT_COLORIZE
DONT_COLORIZE = self._plugin_options['nocolor']
def _print_task(self, task_name=None): def _print_task(self, task_name=None):
if task_name is None: if task_name is None:
task_name = self.last_task_name task_name = self.last_task_name

@ -6,7 +6,8 @@
DOCUMENTATION: DOCUMENTATION:
callback: skippy callback: skippy
callback_type: stdout callback_type: stdout
requires: set as display requirements:
- set as main display callback
short_description: Ansible screen output that ignores skipped status short_description: Ansible screen output that ignores skipped status
version_added: "2.0" version_added: "2.0"
description: description:

@ -1,20 +1,45 @@
# (C) 2014-2015, Matt Martz <matt@sivel.net> # (C) 2014-2015, Matt Martz <matt@sivel.net>
# (C) 2017 Ansible Project
# This file is part of Ansible # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# Ansible is free software: you can redistribute it and/or modify '''
# it under the terms of the GNU General Public License as published by DOCUMENTATION:
# the Free Software Foundation, either version 3 of the License, or callback: slack
# (at your option) any later version. callback_type: notification
# requirements:
# Ansible is distributed in the hope that it will be useful, - whitelist in configuration
# but WITHOUT ANY WARRANTY; without even the implied warranty of - prettytable (python library)
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the short_description: Sends play events to a Slack channel
# GNU General Public License for more details. version_added: "2.1"
# description:
# You should have received a copy of the GNU General Public License - This is an ansible callback plugin that sends status updates to a Slack channel during playbook execution.
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. - Before 2.4 only environment variables were available for configuring this plugin
options:
webhook_url:
required: True
description: Slack Webhook URL
env:
- name: SLACK_WEBHOOK_URL
ini:
- section: callback_slack
key: webhook_url
channel:
default: "#ansible"
description: Slack room to post in.
env:
- name: SLACK_CHANNEL
ini:
- section: callback_slack
key: channel
username:
description: Username to post as.
env:
- name: SLACK_USERNAME
default: ansible
ini:
- section: callback_slack
key: username
'''
# Make coding more python3-ish # Make coding more python3-ish
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
@ -42,17 +67,6 @@ except ImportError:
class CallbackModule(CallbackBase): class CallbackModule(CallbackBase):
"""This is an ansible callback plugin that sends status """This is an ansible callback plugin that sends status
updates to a Slack channel during playbook execution. updates to a Slack channel during playbook execution.
This plugin makes use of the following environment variables:
SLACK_WEBHOOK_URL (required): Slack Webhook URL
SLACK_CHANNEL (optional): Slack room to post in. Default: #ansible
SLACK_USERNAME (optional): Username to post as. Default: ansible
SLACK_INVOCATION (optional): Show command line invocation
details. Default: False
Requires:
prettytable
""" """
CALLBACK_VERSION = 2.0 CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification' CALLBACK_TYPE = 'notification'
@ -64,9 +78,9 @@ class CallbackModule(CallbackBase):
self.disabled = False self.disabled = False
if cli: if cli:
self._options = cli.options self._plugin_options = cli.options
else: else:
self._options = None self._plugin_options = None
super(CallbackModule, self).__init__(display=display) super(CallbackModule, self).__init__(display=display)
@ -76,13 +90,10 @@ class CallbackModule(CallbackBase):
'installed. Disabling the Slack callback ' 'installed. Disabling the Slack callback '
'plugin.') 'plugin.')
self.webhook_url = os.getenv('SLACK_WEBHOOK_URL') self.webhook_url = self._plugin_options['webook_url']
self.channel = os.getenv('SLACK_CHANNEL', '#ansible') self.channel = self._plugin_options['channel']
self.username = os.getenv('SLACK_USERNAME', 'ansible') self.username = self._plugin_options['username']
self.show_invocation = boolean( self.show_invocation = (self._display.verbosity > 1)
os.getenv('SLACK_INVOCATION', self._display.verbosity > 1),
strict=False
)
if self.webhook_url is None: if self.webhook_url is None:
self.disabled = True self.disabled = True
@ -91,6 +102,7 @@ class CallbackModule(CallbackBase):
'the `SLACK_WEBHOOK_URL` environment ' 'the `SLACK_WEBHOOK_URL` environment '
'variable.') 'variable.')
else:
self.playbook_name = None self.playbook_name = None
# This is a 6 character identifier provided with each message # This is a 6 character identifier provided with each message
@ -125,13 +137,13 @@ class CallbackModule(CallbackBase):
'*Playbook initiated* (_%s_)' % self.guid '*Playbook initiated* (_%s_)' % self.guid
] ]
invocation_items = [] invocation_items = []
if self._options and self.show_invocation: if self._plugin_options and self.show_invocation:
tags = self._options.tags tags = self._plugin_options.tags
skip_tags = self._options.skip_tags skip_tags = self._plugin_options.skip_tags
extra_vars = self._options.extra_vars extra_vars = self._plugin_options.extra_vars
subset = self._options.subset subset = self._plugin_options.subset
inventory = os.path.basename( inventory = os.path.basename(
os.path.realpath(self._options.inventory) os.path.realpath(self._plugin_options.inventory)
) )
invocation_items.append('Inventory: %s' % inventory) invocation_items.append('Inventory: %s' % inventory)
@ -145,7 +157,7 @@ class CallbackModule(CallbackBase):
invocation_items.append('Extra Vars: %s' % invocation_items.append('Extra Vars: %s' %
' '.join(extra_vars)) ' '.join(extra_vars))
title.append('by *%s*' % self._options.remote_user) title.append('by *%s*' % self._plugin_options.remote_user)
title.append('\n\n*%s*' % self.playbook_name) title.append('\n\n*%s*' % self.playbook_name)
msg_items = [' '.join(title)] msg_items = [' '.join(title)]

@ -1,20 +1,19 @@
# (c) 2017, Frederic Van Espen <github@freh.be> # (c) 2017, Frederic Van Espen <github@freh.be>
# # (c) 2017 Ansible Project
# This file is part of Ansible # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
'''
DOCUMENTATION:
callback: stderr
callback_type: stdout
requirements:
- set as main display callback
short_description: Splits output, sending failed tasks to stderr
version_added: "2.4"
description:
- This is the stderr callback plugin, it behaves like the default callback plugin but sends error output to stderr.
- Also it does not output skipped host/task/item status
'''
# Make coding more python3-ish # Make coding more python3-ish
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type

@ -1,3 +1,44 @@
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
'''
DOCUMENTATION:
callback: syslog_json
callback_type: notification
requirements:
- whietlist in configuration
short_description: sends JSON events to syslog
version_added: "1.9"
description:
- This plugin logs ansible-playbook and ansible runs to a syslog server in JSON format
- Before 2.4 only environment variables were available for configuration
options:
server:
description: syslog server that will recieve the event
env:
- name: SYSLOG_SERVER
default: localhost
ini:
- section: callback_syslog_json
key: syslog_server
port:
description: prot on which the syslog server is listening
env:
- name: SYSLOG_PORT
default: 514
ini:
- section: callback_syslog_json
key: syslog_port
facility:
description: syslog facitliy to log as
env:
- name: SYSLOG_FACILITY
default: user
ini:
- section: callback_syslog_json
key: syslog_facility
'''
# Make coding more python3-ish # Make coding more python3-ish
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
@ -16,15 +57,8 @@ from ansible.plugins.callback import CallbackBase
class CallbackModule(CallbackBase): class CallbackModule(CallbackBase):
""" """
logs ansible-playbook and ansible runs to a syslog server in json format logs ansible-playbook and ansible runs to a syslog server in json format
make sure you have in ansible.cfg:
callback_plugins = <path_to_callback_plugins_folder>
and put the plugin in <path_to_callback_plugins_folder>
This plugin makes use of the following environment variables:
SYSLOG_SERVER (optional): defaults to localhost
SYSLOG_PORT (optional): defaults to 514
SYSLOG_FACILITY (optional): defaults to user
""" """
CALLBACK_VERSION = 2.0 CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'aggregate' CALLBACK_TYPE = 'aggregate'
CALLBACK_NAME = 'syslog_json' CALLBACK_NAME = 'syslog_json'

@ -1,3 +1,16 @@
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
'''
DOCUMENTATION:
callback: timer
callback_type: aggregate
requirements:
- whitelist in configuration
short_description: Adds time to play stats
version_added: "2.0"
description:
- This callback just adds total play duration to the play stats.
'''
# Make coding more python3-ish # Make coding more python3-ish
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type

@ -1,20 +1,18 @@
# (c) 2012-2014, Ansible, Inc # (c) 2012-2014, Ansible, Inc
# # (c) 2017 Ansible Project
# This file is part of Ansible # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# '''
# Ansible is free software: you can redistribute it and/or modify DOCUMENTATION:
# it under the terms of the GNU General Public License as published by callback: tree
# the Free Software Foundation, either version 3 of the License, or callback_type: notification
# (at your option) any later version. requirements:
# - invoked in the command line
# Ansible is distributed in the hope that it will be useful, short_description: Save host events to files
# but WITHOUT ANY WARRANTY; without even the implied warranty of version_added: "2.0"
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the description:
# GNU General Public License for more details. - "This callback is used by the Ansible (adhoc) command line option `-t|--tree`"
# - This produces a JSON dump of events in a directory, a file for each host, the directory used MUST be passed as a commadn line option.
# You should have received a copy of the GNU General Public License '''
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type

@ -28,6 +28,11 @@ DOCUMENTATION:
private_key_file: private_key_file:
description: description:
- Key or certificate file used for authentication - Key or certificate file used for authentication
ini:
- section: defaults
key: private_key_file
env:
- name: ANSIBLE_PRIVATE_KEY_FILE
vars: vars:
- name: ansible_private_key_file - name: ansible_private_key_file
timeout: timeout:

@ -69,10 +69,11 @@ DOCUMENTATION:
description: Extra exclusive to the 'ssh' CLI description: Extra exclusive to the 'ssh' CLI
vars: vars:
- name: ansible_ssh_extra_args - name: ansible_ssh_extra_args
ssh_retries: retries:
# constant: ANSIBLE_SSH_RETRIES # constant: ANSIBLE_SSH_RETRIES
description: Number of attempts to connect. description: Number of attempts to connect.
default: 3 default: 3
type: integer
env: env:
- name: ANSIBLE_SSH_RETRIES - name: ANSIBLE_SSH_RETRIES
ini: ini:
@ -118,14 +119,54 @@ DOCUMENTATION:
- {key: pipelining, section: ssh_connection} - {key: pipelining, section: ssh_connection}
type: boolean type: boolean
vars: [{name: ansible_ssh_pipelining}] vars: [{name: ansible_ssh_pipelining}]
# TODO: private_key_file:
# ANSIBLE_SSH_RETRIES description:
- Path to private key file to use for authentication
# self._play_context.private_key_file ini:
# ANSIBLE_SSH_CONTROL_PATH - section: defaults
# ANSIBLE_SSH_CONTROL_PATH_DIR key: private_key_file
# DEFAULT_SFTP_BATCH_MODE env:
# DEFAULT_SCP_IF_SSH - name: ANSIBLE_PRIVATE_KEY_FILE
vars:
- name: ansible_private_key_file
- name: ansible_ssh_private_key_file
control_path:
default: null
description:
- This is the location to save ssh's ControlPath sockets, it uses ssh's variable substitution.
- Since 2.3, if null, ansible will generate a unique hash. Use `%(directory)s` to indicate where to use the control dir path setting.
env:
- name: ANSIBLE_SSH_CONTROL_PATH
ini:
- key: control_path
section: ssh_connection
control_path_dir:
default: ~/.ansible/cp
description:
- This sets the directory to use for ssh control path if the control path setting is null.
- Also, provides the `%(directory)s` variable for the control path setting.
env:
- name: ANSIBLE_SSH_CONTROL_PATH_DIR
ini:
- section: ssh_connection
key: control_path_dir
sftp_batch_mode:
default: True
description: 'TODO: write it'
env: [{name: ANSIBLE_SFTP_BATCH_MODE}]
ini:
- {key: sftp_batch_mode, section: ssh_connection}
type: boolean
scp_if_ssh:
default: smart
description:
- "Prefered method to use when transfering files over ssh"
- When set to smart, Ansible will try them until one succeeds or they all fail
- If set to True, it will force 'scp', if False it will use 'sftp'
env: [{name: ANSIBLE_SCP_IF_SSH}]
ini:
- {key: scp_if_ssh, section: ssh_connection}
''' '''
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)

@ -205,10 +205,13 @@ class PluginLoader:
''' Reads plugin docs to find configuration setting definitions, to push to config manager for later use ''' ''' Reads plugin docs to find configuration setting definitions, to push to config manager for later use '''
# plugins w/o class name don't support config # plugins w/o class name don't support config
if self.class_name and self.class_name in ('Connection'): if self.class_name:
# FIXME: expand from just connection type_name = get_plugin_class(self.class_name)
type_name = get_plugin_class(self)
# FIXME: expand from just connection and callback
if type_name in ('connection', 'callback'):
dstring = read_docstring(path, verbose=False, ignore_errors=False) dstring = read_docstring(path, verbose=False, ignore_errors=False)
if dstring.get('doc', False): if dstring.get('doc', False):
if 'options' in dstring['doc'] and isinstance(dstring['doc']['options'], dict): if 'options' in dstring['doc'] and isinstance(dstring['doc']['options'], dict):
C.config.initialize_plugin_configuration_definitions(type_name, name, dstring['doc']['options']) C.config.initialize_plugin_configuration_definitions(type_name, name, dstring['doc']['options'])

Loading…
Cancel
Save