mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Become plugins (#50991)
* [WIP] become plugins Move from hardcoded method to plugins for ease of use, expansion and overrides - load into connection as it is going to be the main consumer - play_context will also use to keep backwards compat API - ensure shell is used to construct commands when needed - migrate settings remove from base config in favor of plugin specific configs - cleanup ansible-doc - add become plugin docs - remove deprecated sudo/su code and keywords - adjust become options for cli - set plugin options from context - ensure config defs are avaialbe before instance - refactored getting the shell plugin, fixed tests - changed into regex as they were string matching, which does not work with random string generation - explicitly set flags for play context tests - moved plugin loading up front - now loads for basedir also - allow pyc/o for non m modules - fixes to tests and some plugins - migrate to play objects fro play_context - simiplify gathering - added utf8 headers - moved option setting - add fail msg to dzdo - use tuple for multiple options on fail/missing - fix relative plugin paths - shift from play context to play - all tasks already inherit this from play directly - remove obsolete 'set play' - correct environment handling - add wrap_exe option to pfexec - fix runas to noop - fixed setting play context - added password configs - removed required false - remove from doc building till they are ready future development: - deal with 'enable' and 'runas' which are not 'command wrappers' but 'state flags' and currently hardcoded in diff subsystems * cleanup remove callers to removed func removed --sudo cli doc refs remove runas become_exe ensure keyerorr on plugin also fix backwards compat, missing method is attributeerror, not ansible error get remote_user consistently ignore missing system_tmpdirs on plugin load correct config precedence add deprecation fix networking imports backwards compat for plugins using BECOME_METHODS * Port become_plugins to context.CLIARGS This is a work in progress: * Stop passing options around everywhere as we can use context.CLIARGS instead * Refactor make_become_commands as asked for by alikins * Typo in comment fix * Stop loading values from the cli in more than one place Both play and play_context were saving default values from the cli arguments directly. This changes things so that the default values are loaded into the play and then play_context takes them from there. * Rename BECOME_PLUGIN_PATH to DEFAULT_BECOME_PLUGIN_PATH As alikins said, all other plugin paths are named DEFAULT_plugintype_PLUGIN_PATH. If we're going to rename these, that should be done all at one time rather than piecemeal. * One to throw away This is a set of hacks to get setting FieldAttribute defaults to command line args to work. It's not fully done yet. After talking it over with sivel and jimi-c this should be done by fixing FieldAttributeBase and _get_parent_attribute() calls to do the right thing when there is a non-None default. What we want to be able to do ideally is something like this: class Base(FieldAttributeBase): _check_mode = FieldAttribute([..] default=lambda: context.CLIARGS['check']) class Play(Base): # lambda so that we have a chance to parse the command line args # before we get here. In the future we might be able to restructure # this so that the cli parsing code runs before these classes are # defined. class Task(Base): pass And still have a playbook like this function: --- - hosts: tasks: - command: whoami check_mode: True (The check_mode test that is added as a separate commit in this PR will let you test variations on this case). There's a few separate reasons that the code doesn't let us do this or a non-ugly workaround for this as written right now. The fix that jimi-c, sivel, and I talked about may let us do this or it may still require a workaround (but less ugly) (having one class that has the FieldAttributes with default values and one class that inherits from that but just overrides the FieldAttributes which now have defaults) * Revert "One to throw away" This reverts commit 23aa883cbed11429ef1be2a2d0ed18f83a3b8064. * Set FieldAttr defaults directly from CLIARGS * Remove dead code * Move timeout directly to PlayContext, it's never needed on Play * just for backwards compat, add a static version of BECOME_METHODS to constants * Make the become attr on the connection public, since it's used outside of the connection * Logic fix * Nuke connection testing if it supports specific become methods * Remove unused vars * Address rebase issues * Fix path encoding issue * Remove unused import * Various cleanups * Restore network_cli check in _low_level_execute_command * type improvements for cliargs_deferred_get and swap shallowcopy to default to False * minor cleanups * Allow the su plugin to work, since it doesn't define a prompt the same way * Fix up ksu become plugin * Only set prompt if build_become_command was called * Add helper to assist connection plugins in knowing they need to wait for a prompt * Fix tests and code expectations * Doc updates * Various additional minor cleanups * Make doas functional * Don't change connection signature, load become plugin from TaskExecutor * Remove unused imports * Add comment about setting the become plugin on the playcontext * Fix up tests for recent changes * Support 'Password:' natively for the doas plugin * Make default prompts raw * wording cleanups. ci_complete * Remove unrelated changes * Address spelling mistake * Restore removed test, and udpate to use new functionality * Add changelog fragment * Don't hard fail in set_attributes_from_cli on missing CLI keys * Remove unrelated change to loader * Remove internal deprecated FieldAttributes now * Emit deprecation warnings now
This commit is contained in:
parent
c581fbd0be
commit
445ff39f94
73 changed files with 1849 additions and 721 deletions
8
changelogs/fragments/become-plugins.yaml
Normal file
8
changelogs/fragments/become-plugins.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
major_changes:
|
||||
- become - become functionality has been migrated to a plugin architecture, to
|
||||
allow customization of become functionality and 3rd party become methods
|
||||
(https://github.com/ansible/ansible/pull/50991)
|
||||
- become - The deprecated CLI arguments for ``--sudo``, ``--sudo-user``,
|
||||
``--ask-sudo-pass``, ``-su``, ``--su-user``, and ``--ask-su-pass`` have been
|
||||
removed, in favor of the more generic ``--become``, ``--become-user``,
|
||||
``--become-method``, and ``--ask-become-pass``.
|
|
@ -26,7 +26,7 @@ ifdef PLUGINS
|
|||
PLUGIN_ARGS = -l $(PLUGINS)
|
||||
endif
|
||||
|
||||
DOC_PLUGINS ?= cache callback cliconf connection httpapi inventory lookup shell strategy vars
|
||||
DOC_PLUGINS ?= become cache callback cliconf connection httpapi inventory lookup shell strategy vars
|
||||
|
||||
assertrst:
|
||||
ifndef rst
|
||||
|
|
|
@ -90,7 +90,7 @@ later).
|
|||
|
||||
.. code-block:: shell
|
||||
|
||||
$ ansible myhost --sudo -m raw -a "yum install -y python2"
|
||||
$ ansible myhost --become -m raw -a "yum install -y python2"
|
||||
|
||||
.. _installing_the_control_node:
|
||||
|
||||
|
|
60
docs/docsite/rst/plugins/become.rst
Normal file
60
docs/docsite/rst/plugins/become.rst
Normal file
|
@ -0,0 +1,60 @@
|
|||
.. contents:: Topics
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
Become Plugins
|
||||
--------------
|
||||
|
||||
Become plugins work to ensure that Ansible can use certain privilege escalation systems when running the basic
|
||||
commands to work with the target machine as well as the modules required to execute the tasks specified in
|
||||
the play.
|
||||
|
||||
These utilities (``sudo``, ``su``, ``doas``, etc) generally let you 'become' another user to execute a command
|
||||
with the permissions of that user.
|
||||
|
||||
|
||||
.. _enabling_become:
|
||||
|
||||
Enabling Become Plugins
|
||||
+++++++++++++++++++++++
|
||||
|
||||
The become plugins shipped with Ansible are already enabled. Custom plugins can be added by placing
|
||||
them into a ``become_plugins`` directory adjacent to your play, inside a role, or by placing them in one of
|
||||
the become plugin directory sources configured in :ref:`ansible.cfg <ansible_configuration_settings>`.
|
||||
|
||||
|
||||
.. _using_become:
|
||||
|
||||
Using Become Plugins
|
||||
++++++++++++++++++++
|
||||
|
||||
In addition to the default configuration settings in :ref:`ansible_configuration_settings` or the
|
||||
``--become-method`` command line option, you can use the ``become_method`` keyword in a play or, if you need
|
||||
to be 'host specific', the connection variable ``ansible_become_method`` to select the plugin to use.
|
||||
|
||||
You can further control the settings for each plugin via other configuration options detailed in the plugin
|
||||
themselves (linked below).
|
||||
|
||||
.. toctree:: :maxdepth: 1
|
||||
:glob:
|
||||
|
||||
become/*
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`../user_guide/playbooks`
|
||||
An introduction to playbooks
|
||||
:doc:`inventory`
|
||||
Ansible inventory plugins
|
||||
:doc:`callback`
|
||||
Ansible callback plugins
|
||||
:doc:`../user_guide/playbooks_filters`
|
||||
Jinja2 filter plugins
|
||||
:doc:`../user_guide/playbooks_tests`
|
||||
Jinja2 test plugins
|
||||
:doc:`../user_guide/playbooks_lookups`
|
||||
Jinja2 lookup plugins
|
||||
`User Mailing List <https://groups.google.com/group/ansible-devel>`_
|
||||
Have a question? Stop by the google group!
|
||||
`irc.freenode.net <http://irc.freenode.net>`_
|
||||
#ansible IRC chat channel
|
4
docs/docsite/rst/plugins/index.html
Normal file
4
docs/docsite/rst/plugins/index.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<html>
|
||||
<head><noscript><meta http-equiv="refresh" content="0; url=plugins.html"></noscript></head>
|
||||
<body onload="window.location = 'plugins.html'>Redirecting to <a href='plugins.html'>plugins</a> page.</body>
|
||||
</html>
|
|
@ -14,6 +14,7 @@ This section covers the various types of plugins that are included with Ansible:
|
|||
:maxdepth: 1
|
||||
|
||||
action
|
||||
become
|
||||
cache
|
||||
callback
|
||||
cliconf
|
||||
|
|
|
@ -82,19 +82,13 @@ If you would like to access sudo mode, there are also flags to do that:
|
|||
|
||||
# as bruce
|
||||
$ ansible all -m ping -u bruce
|
||||
# as bruce, sudoing to root
|
||||
$ ansible all -m ping -u bruce --sudo
|
||||
# as bruce, sudoing to root (sudo is default method)
|
||||
$ ansible all -m ping -u bruce --become
|
||||
# as bruce, sudoing to batman
|
||||
$ ansible all -m ping -u bruce --sudo --sudo-user batman
|
||||
$ ansible all -m ping -u bruce --become --become-user batman
|
||||
|
||||
# With latest version of ansible `sudo` is deprecated so use become
|
||||
# as bruce, sudoing to root
|
||||
$ ansible all -m ping -u bruce -b
|
||||
# as bruce, sudoing to batman
|
||||
$ ansible all -m ping -u bruce -b --become-user batman
|
||||
|
||||
(The sudo implementation is changeable in Ansible's configuration file if you happen to want to use a sudo
|
||||
replacement. Flags passed to sudo (like -H) can also be set there.)
|
||||
The sudo implementation (and other methods of changing the current user) can be modified in Ansible's configuration
|
||||
if you happen to want to use a sudo replacement. Flags passed to sudo (like -H) can also be set.
|
||||
|
||||
Now run a live command on all of your nodes:
|
||||
|
||||
|
|
|
@ -198,10 +198,10 @@ You can also use other privilege escalation methods, like su::
|
|||
become: yes
|
||||
become_method: su
|
||||
|
||||
If you need to specify a password to sudo, run ``ansible-playbook`` with ``--ask-become-pass`` or
|
||||
when using the old sudo syntax ``--ask-sudo-pass`` (``-K``). If you run a become playbook and the
|
||||
playbook seems to hang, it's probably stuck at the privilege escalation prompt.
|
||||
Just `Control-C` to kill it and run it again adding the appropriate password.
|
||||
If you need to specify a password for sudo, run ``ansible-playbook`` with ``--ask-become-pass`` or ``-K``.
|
||||
If you run a playbook utilizing ``become`` and the playbook seems to hang, it's probably stuck at the privilege
|
||||
escalation prompt and can be stopped using `Control-C`, allowing you to re-execute the playbook adding the
|
||||
appropriate password.
|
||||
|
||||
.. important::
|
||||
|
||||
|
|
|
@ -190,6 +190,7 @@
|
|||
|
||||
# set plugin path directories here, separate with colons
|
||||
#action_plugins = /usr/share/ansible/plugins/action
|
||||
#become_plugins = /usr/share/ansible/plugins/become
|
||||
#cache_plugins = /usr/share/ansible/plugins/cache
|
||||
#callback_plugins = /usr/share/ansible/plugins/callback
|
||||
#connection_plugins = /usr/share/ansible/plugins/connection
|
||||
|
|
|
@ -29,6 +29,7 @@ from ansible.utils.display import Display
|
|||
from ansible.utils.path import unfrackpath
|
||||
from ansible.vars.manager import VariableManager
|
||||
from ansible.parsing.vault import PromptVaultSecret, get_file_vault_secret
|
||||
from ansible.plugins.loader import add_all_plugin_dirs
|
||||
|
||||
|
||||
display = Display()
|
||||
|
@ -277,36 +278,6 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
|
||||
return (sshpass, becomepass)
|
||||
|
||||
@staticmethod
|
||||
def normalize_become_options(options):
|
||||
''' this keeps backwards compatibility with sudo/su command line options '''
|
||||
if not options.become_ask_pass:
|
||||
options.become_ask_pass = options.ask_sudo_pass or options.ask_su_pass or C.DEFAULT_BECOME_ASK_PASS
|
||||
if not options.become_user:
|
||||
options.become_user = options.sudo_user or options.su_user or C.DEFAULT_BECOME_USER
|
||||
|
||||
def _dep(which):
|
||||
display.deprecated('The %s command line option has been deprecated in favor of the "become" command line arguments' % which, '2.9')
|
||||
|
||||
if options.become:
|
||||
pass
|
||||
elif options.sudo:
|
||||
options.become = True
|
||||
options.become_method = 'sudo'
|
||||
_dep('sudo')
|
||||
elif options.su:
|
||||
options.become = True
|
||||
options.become_method = 'su'
|
||||
_dep('su')
|
||||
|
||||
# other deprecations:
|
||||
if options.ask_sudo_pass or options.sudo_user:
|
||||
_dep('sudo')
|
||||
if options.ask_su_pass or options.su_user:
|
||||
_dep('su')
|
||||
|
||||
return options
|
||||
|
||||
def validate_conflicts(self, op, vault_opts=False, runas_opts=False, fork_opts=False, vault_rekey_opts=False):
|
||||
''' check for conflicting options '''
|
||||
|
||||
|
@ -319,17 +290,6 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
if op.new_vault_id and op.new_vault_password_file:
|
||||
self.parser.error("--new-vault-password-file and --new-vault-id are mutually exclusive")
|
||||
|
||||
if runas_opts:
|
||||
# Check for privilege escalation conflicts
|
||||
if ((op.su or op.su_user) and (op.sudo or op.sudo_user) or
|
||||
(op.su or op.su_user) and (op.become or op.become_user) or
|
||||
(op.sudo or op.sudo_user) and (op.become or op.become_user)):
|
||||
|
||||
self.parser.error("Sudo arguments ('--sudo', '--sudo-user', and '--ask-sudo-pass')"
|
||||
" and su arguments ('--su', '--su-user', and '--ask-su-pass')"
|
||||
" and become arguments ('--become', '--become-user', and"
|
||||
" '--ask-become-pass') are exclusive of each other")
|
||||
|
||||
if fork_opts:
|
||||
if op.forks < 1:
|
||||
self.parser.error("The number of processes (--forks) must be >= 1")
|
||||
|
@ -502,6 +462,7 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
basedir = options.get('basedir', False)
|
||||
if basedir:
|
||||
loader.set_basedir(basedir)
|
||||
add_all_plugin_dirs(basedir)
|
||||
|
||||
vault_ids = list(options['vault_ids'])
|
||||
default_vault_ids = C.DEFAULT_VAULT_IDENTITY_LIST
|
||||
|
@ -516,6 +477,9 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
|
||||
# create the inventory, and filter it based on the subset specified (if any)
|
||||
inventory = InventoryManager(loader=loader, sources=options['inventory'])
|
||||
subset = options.get('subset', False)
|
||||
if subset:
|
||||
inventory.subset(subset)
|
||||
|
||||
# create the variable manager, which will be shared throughout
|
||||
# the code, ensuring a consistent view of global variables
|
||||
|
@ -533,8 +497,6 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
display.warning("provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'")
|
||||
no_hosts = True
|
||||
|
||||
inventory.subset(subset)
|
||||
|
||||
hosts = inventory.list_hosts(pattern)
|
||||
if len(hosts) == 0 and no_hosts is False:
|
||||
raise AnsibleError("Specified hosts and/or --limit does not match any hosts")
|
||||
|
|
|
@ -15,7 +15,6 @@ from ansible.module_utils._text import to_text
|
|||
from ansible.parsing.splitter import parse_kv
|
||||
from ansible.playbook import Playbook
|
||||
from ansible.playbook.play import Play
|
||||
from ansible.plugins.loader import get_all_plugin_loaders
|
||||
from ansible.utils.display import Display
|
||||
|
||||
display = Display()
|
||||
|
@ -66,8 +65,6 @@ class AdHocCLI(CLI):
|
|||
display.verbosity = options.verbosity
|
||||
self.validate_conflicts(options, runas_opts=True, vault_opts=True, fork_opts=True)
|
||||
|
||||
options = self.normalize_become_options(options)
|
||||
|
||||
return options, args
|
||||
|
||||
def _play_ds(self, pattern, async_val, poll):
|
||||
|
@ -100,9 +97,7 @@ class AdHocCLI(CLI):
|
|||
(sshpass, becomepass) = self.ask_passwords()
|
||||
passwords = {'conn_pass': sshpass, 'become_pass': becomepass}
|
||||
|
||||
# dynamically load any plugins
|
||||
get_all_plugin_loaders()
|
||||
|
||||
# get basic objects
|
||||
loader, inventory, variable_manager = self._play_prereqs()
|
||||
|
||||
try:
|
||||
|
|
|
@ -303,22 +303,12 @@ def add_runas_options(parser):
|
|||
"""
|
||||
runas_group = optparse.OptionGroup(parser, "Privilege Escalation Options", "control how and which user you become as on target hosts")
|
||||
|
||||
# priv user defaults to root later on to enable detecting when this option was given here
|
||||
runas_group.add_option("-s", "--sudo", default=C.DEFAULT_SUDO, action="store_true", dest='sudo',
|
||||
help="run operations with sudo (nopasswd) (deprecated, use become)")
|
||||
runas_group.add_option('-U', '--sudo-user', dest='sudo_user', default=None,
|
||||
help='desired sudo user (default=root) (deprecated, use become)')
|
||||
runas_group.add_option('-S', '--su', default=C.DEFAULT_SU, action='store_true',
|
||||
help='run operations with su (deprecated, use become)')
|
||||
runas_group.add_option('-R', '--su-user', default=None,
|
||||
help='run operations with su as this user (default=%s) (deprecated, use become)' % C.DEFAULT_SU_USER)
|
||||
|
||||
# consolidated privilege escalation (become)
|
||||
runas_group.add_option("-b", "--become", default=C.DEFAULT_BECOME, action="store_true", dest='become',
|
||||
help="run operations with become (does not imply password prompting)")
|
||||
runas_group.add_option('--become-method', dest='become_method', default=C.DEFAULT_BECOME_METHOD, type='choice', choices=C.BECOME_METHODS,
|
||||
help="privilege escalation method to use (default=%s), valid choices: [ %s ]" %
|
||||
(C.DEFAULT_BECOME_METHOD, ' | '.join(C.BECOME_METHODS)))
|
||||
runas_group.add_option('--become-method', dest='become_method', default=C.DEFAULT_BECOME_METHOD,
|
||||
help="privilege escalation method to use (default=%default), use "
|
||||
"`ansible-doc -t become -l` to list valid choices.")
|
||||
runas_group.add_option('--become-user', default=None, dest='become_user', type='string',
|
||||
help='run operations as this user (default=%s)' % C.DEFAULT_BECOME_USER)
|
||||
|
||||
|
@ -336,10 +326,6 @@ def add_runas_prompt_options(parser, runas_group=None):
|
|||
runas_group = optparse.OptionGroup(parser, "Privilege Escalation Options",
|
||||
"control how and which user you become as on target hosts")
|
||||
|
||||
runas_group.add_option('--ask-sudo-pass', default=C.DEFAULT_ASK_SUDO_PASS, dest='ask_sudo_pass', action='store_true',
|
||||
help='ask for sudo password (deprecated, use become)')
|
||||
runas_group.add_option('--ask-su-pass', default=C.DEFAULT_ASK_SU_PASS, dest='ask_su_pass', action='store_true',
|
||||
help='ask for su password (deprecated, use become)')
|
||||
runas_group.add_option('-K', '--ask-become-pass', default=False, dest='become_ask_pass', action='store_true',
|
||||
help='ask for privilege escalation password')
|
||||
|
||||
|
|
|
@ -100,7 +100,6 @@ class ConsoleCLI(CLI, cmd.Cmd):
|
|||
def post_process_args(self, options, args):
|
||||
options, args = super(ConsoleCLI, self).post_process_args(options, args)
|
||||
display.verbosity = options.verbosity
|
||||
options = self.normalize_become_options(options)
|
||||
self.validate_conflicts(options, runas_opts=True, vault_opts=True, fork_opts=True)
|
||||
return options, args
|
||||
|
||||
|
|
|
@ -13,9 +13,10 @@ from ansible.cli import CLI
|
|||
from ansible.cli.arguments import optparse_helpers as opt_help
|
||||
from ansible.errors import AnsibleError, AnsibleOptionsError
|
||||
from ansible.executor.playbook_executor import PlaybookExecutor
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.playbook.block import Block
|
||||
from ansible.playbook.play_context import PlayContext
|
||||
from ansible.utils.display import Display
|
||||
from ansible.plugins.loader import add_all_plugin_dirs
|
||||
|
||||
display = Display()
|
||||
|
||||
|
@ -61,8 +62,6 @@ class PlaybookCLI(CLI):
|
|||
display.verbosity = options.verbosity
|
||||
self.validate_conflicts(options, runas_opts=True, vault_opts=True, fork_opts=True)
|
||||
|
||||
options = self.normalize_become_options(options)
|
||||
|
||||
return options, args
|
||||
|
||||
def run(self):
|
||||
|
@ -82,6 +81,14 @@ class PlaybookCLI(CLI):
|
|||
raise AnsibleError("the playbook: %s could not be found" % playbook)
|
||||
if not (os.path.isfile(playbook) or stat.S_ISFIFO(os.stat(playbook).st_mode)):
|
||||
raise AnsibleError("the playbook: %s does not appear to be a file" % playbook)
|
||||
# load plugins from all playbooks in case they add callbacks/inventory/etc
|
||||
add_all_plugin_dirs(
|
||||
os.path.dirname(
|
||||
os.path.abspath(
|
||||
to_bytes(playbook, errors='surrogate_or_strict')
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# don't deal with privilege escalation or passwords when we don't need to
|
||||
if not (context.CLIARGS['listhosts'] or context.CLIARGS['listtasks'] or
|
||||
|
@ -89,16 +96,9 @@ class PlaybookCLI(CLI):
|
|||
(sshpass, becomepass) = self.ask_passwords()
|
||||
passwords = {'conn_pass': sshpass, 'become_pass': becomepass}
|
||||
|
||||
# create base objects
|
||||
loader, inventory, variable_manager = self._play_prereqs()
|
||||
|
||||
# (which is not returned in list_hosts()) is taken into account for
|
||||
# warning if inventory is empty. But it can't be taken into account for
|
||||
# checking if limit doesn't match any hosts. Instead we don't worry about
|
||||
# limit if only implicit localhost was in inventory to start with.
|
||||
#
|
||||
# Fix this when we rewrite inventory by making localhost a real host (and thus show up in list_hosts())
|
||||
hosts = self.get_host_list(inventory, context.CLIARGS['subset'])
|
||||
|
||||
# flush fact cache if requested
|
||||
if context.CLIARGS['flush_cache']:
|
||||
self._flush_cache(inventory, variable_manager)
|
||||
|
@ -161,9 +161,8 @@ class PlaybookCLI(CLI):
|
|||
return taskmsg
|
||||
|
||||
all_vars = variable_manager.get_vars(play=play)
|
||||
play_context = PlayContext(play=play)
|
||||
for block in play.compile():
|
||||
block = block.filter_tagged_tasks(play_context, all_vars)
|
||||
block = block.filter_tagged_tasks(all_vars)
|
||||
if not block.has_tasks():
|
||||
continue
|
||||
taskmsg += _process_block(block)
|
||||
|
|
|
@ -260,7 +260,7 @@ class PullCLI(CLI):
|
|||
|
||||
for ev in context.CLIARGS['extra_vars']:
|
||||
cmd += ' -e %s' % shlex_quote(ev)
|
||||
if context.CLIARGS['ask_sudo_pass'] or context.CLIARGS['ask_su_pass'] or context.CLIARGS['become_ask_pass']:
|
||||
if context.CLIARGS['become_ask_pass']:
|
||||
cmd += ' --ask-become-pass'
|
||||
if context.CLIARGS['skip_tags']:
|
||||
cmd += ' --skip-tags "%s"' % to_native(u','.join(context.CLIARGS['skip_tags']))
|
||||
|
|
|
@ -481,6 +481,14 @@ DEFAULT_BECOME_FLAGS:
|
|||
env: [{name: ANSIBLE_BECOME_FLAGS}]
|
||||
ini:
|
||||
- {key: become_flags, section: privilege_escalation}
|
||||
DEFAULT_BECOME_PLUGIN_PATH:
|
||||
name: Become plugins path
|
||||
default: ~/.ansible/plugins/become:/usr/share/ansible/become
|
||||
description: Colon separated paths in which Ansible will search for Become Plugins.
|
||||
env: [{name: ANSIBLE_BECOME_PLUGINS}]
|
||||
ini:
|
||||
- {key: become_plugins, section: defaults}
|
||||
type: pathspec
|
||||
DEFAULT_BECOME_USER:
|
||||
# FIXME: should really be blank and make -u passing optional depending on it
|
||||
name: Set the user you 'become' via privilege escalation
|
||||
|
@ -632,6 +640,7 @@ DEFAULT_GATHER_SUBSET:
|
|||
- key: gather_subset
|
||||
section: defaults
|
||||
version_added: "2.1"
|
||||
type: list
|
||||
DEFAULT_GATHER_TIMEOUT:
|
||||
name: Gather facts timeout
|
||||
default: 10
|
||||
|
@ -1081,50 +1090,6 @@ DEFAULT_SU:
|
|||
- {key: su, section: defaults}
|
||||
type: boolean
|
||||
yaml: {key: defaults.su}
|
||||
DEFAULT_SUDO:
|
||||
default: False
|
||||
deprecated:
|
||||
why: In favor of Ansible Become, which is a generic framework
|
||||
version: "2.9"
|
||||
alternatives: become
|
||||
description: 'Toggle the use of "sudo" for tasks.'
|
||||
env: [{name: ANSIBLE_SUDO}]
|
||||
ini:
|
||||
- {key: sudo, section: defaults}
|
||||
type: boolean
|
||||
DEFAULT_SUDO_EXE:
|
||||
name: sudo executable
|
||||
default: sudo
|
||||
deprecated:
|
||||
why: In favor of Ansible Become, which is a generic framework. See become_exe.
|
||||
version: "2.9"
|
||||
alternatives: become
|
||||
description: 'specify an "sudo" executable, otherwise it relies on PATH.'
|
||||
env: [{name: ANSIBLE_SUDO_EXE}]
|
||||
ini:
|
||||
- {key: sudo_exe, section: defaults}
|
||||
DEFAULT_SUDO_FLAGS:
|
||||
name: sudo flags
|
||||
default: '-H -S -n'
|
||||
deprecated:
|
||||
why: In favor of Ansible Become, which is a generic framework. See become_flags.
|
||||
version: "2.9"
|
||||
alternatives: become
|
||||
description: 'Flags to pass to "sudo"'
|
||||
env: [{name: ANSIBLE_SUDO_FLAGS}]
|
||||
ini:
|
||||
- {key: sudo_flags, section: defaults}
|
||||
DEFAULT_SUDO_USER:
|
||||
name: sudo user
|
||||
default:
|
||||
deprecated:
|
||||
why: In favor of Ansible Become, which is a generic framework. See become_user.
|
||||
version: "2.9"
|
||||
alternatives: become
|
||||
description: 'User you become when using "sudo", leaving it blank will use the default configured on the target (normally root)'
|
||||
env: [{name: ANSIBLE_SUDO_USER}]
|
||||
ini:
|
||||
- {key: sudo_user, section: defaults}
|
||||
DEFAULT_SU_EXE:
|
||||
name: su executable
|
||||
default: su
|
||||
|
|
|
@ -398,8 +398,8 @@ class ConfigManager(object):
|
|||
origin = 'var: %s' % origin
|
||||
|
||||
# use playbook keywords if you have em
|
||||
if value is None and keys and defs[config].get('keywords'):
|
||||
value, origin = self._loop_entries(keys, defs[config]['keywords'])
|
||||
if value is None and keys and config in keys:
|
||||
value, origin = keys[config], 'keyword'
|
||||
origin = 'keyword: %s' % origin
|
||||
|
||||
# env vars are next precedence
|
||||
|
|
|
@ -12,6 +12,7 @@ from jinja2 import Template
|
|||
from string import ascii_letters, digits
|
||||
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.common.collections import Sequence
|
||||
from ansible.module_utils.parsing.convert_bool import boolean, BOOLEANS_TRUE
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.config.manager import ConfigManager, ensure_type, get_ini_config_value
|
||||
|
@ -68,32 +69,31 @@ def set_constant(name, value, export=vars()):
|
|||
export[name] = value
|
||||
|
||||
|
||||
class _DeprecatedSequenceConstant(Sequence):
|
||||
def __init__(self, value, msg, version):
|
||||
self._value = value
|
||||
self._msg = msg
|
||||
self._version = version
|
||||
|
||||
def __len__(self):
|
||||
_deprecated(self._msg, version=self._version)
|
||||
return len(self._value)
|
||||
|
||||
def __getitem__(self, y):
|
||||
_deprecated(self._msg, version=self._version)
|
||||
return self._value[y]
|
||||
|
||||
|
||||
# Deprecated constants
|
||||
BECOME_METHODS = _DeprecatedSequenceConstant(
|
||||
['sudo', 'su', 'pbrun', 'pfexec', 'doas', 'dzdo', 'ksu', 'runas', 'pmrun', 'enable', 'machinectl'],
|
||||
('ansible.constants.BECOME_METHODS is deprecated, please use '
|
||||
'ansible.plugins.loader.become_loader. This list is statically '
|
||||
'defined and may not include all become methods'),
|
||||
'2.10'
|
||||
)
|
||||
|
||||
# CONSTANTS ### yes, actual ones
|
||||
BECOME_METHODS = ['sudo', 'su', 'pbrun', 'pfexec', 'doas', 'dzdo', 'ksu', 'runas', 'pmrun', 'enable', 'machinectl']
|
||||
BECOME_ERROR_STRINGS = {
|
||||
'sudo': 'Sorry, try again.',
|
||||
'su': 'Authentication failure',
|
||||
'pbrun': '',
|
||||
'pfexec': '',
|
||||
'doas': 'Permission denied',
|
||||
'dzdo': 'Sorry, try again.',
|
||||
'ksu': 'Password incorrect',
|
||||
'pmrun': 'You are not permitted to run this command',
|
||||
'enable': '',
|
||||
'machinectl': '',
|
||||
} # FIXME: deal with i18n
|
||||
BECOME_MISSING_STRINGS = {
|
||||
'sudo': 'sorry, a password is required to run sudo',
|
||||
'su': '',
|
||||
'pbrun': '',
|
||||
'pfexec': '',
|
||||
'doas': 'Authorization required',
|
||||
'dzdo': '',
|
||||
'ksu': 'No password given',
|
||||
'pmrun': '',
|
||||
'enable': '',
|
||||
'machinectl': '',
|
||||
} # FIXME: deal with i18n
|
||||
BLACKLIST_EXTS = ('.pyc', '.pyo', '.swp', '.bak', '~', '.rpm', '.md', '.txt', '.rst')
|
||||
BOOL_TRUE = BOOLEANS_TRUE
|
||||
CONTROLLER_LANG = os.getenv('LANG', 'en_US.UTF-8')
|
||||
|
@ -104,7 +104,7 @@ DEFAULT_REMOTE_PASS = None
|
|||
DEFAULT_SUBSET = None
|
||||
DEFAULT_SU_PASS = None
|
||||
# FIXME: expand to other plugins, but never doc fragments
|
||||
CONFIGURABLE_PLUGINS = ('cache', 'callback', 'connection', 'inventory', 'lookup', 'shell', 'cliconf', 'httpapi')
|
||||
CONFIGURABLE_PLUGINS = ('become', 'cache', 'callback', 'cliconf', 'connection', 'httpapi', 'inventory', 'lookup', 'shell')
|
||||
# NOTE: always update the docs/docsite/Makefile to match
|
||||
DOCUMENTABLE_PLUGINS = CONFIGURABLE_PLUGINS + ('module', 'strategy', 'vars')
|
||||
IGNORE_FILES = ("COPYING", "CONTRIBUTING", "LICENSE", "README", "VERSION", "GUIDELINES") # ignore during module search
|
||||
|
|
|
@ -15,6 +15,8 @@ running the ansible command line tools.
|
|||
These APIs are still in flux so do not use them unless you are willing to update them with every Ansible release
|
||||
"""
|
||||
|
||||
from ansible.module_utils.common._collections_compat import Mapping, Set
|
||||
from ansible.module_utils.common.collections import is_sequence
|
||||
from ansible.utils.context_objects import CLIArgs, GlobalCLIArgs
|
||||
|
||||
|
||||
|
@ -31,3 +33,24 @@ def _init_global_context(cli_args):
|
|||
"""Initialize the global context objects"""
|
||||
global CLIARGS
|
||||
CLIARGS = GlobalCLIArgs.from_options(cli_args)
|
||||
|
||||
|
||||
def cliargs_deferred_get(key, default=None, shallowcopy=False):
|
||||
"""Closure over getting a key from CLIARGS with shallow copy functionality
|
||||
|
||||
Primarily used in ``FieldAttribute`` where we need to defer setting the default
|
||||
until after the CLI arguments have been parsed
|
||||
|
||||
This function is not directly bound to ``CliArgs`` so that it works with
|
||||
``CLIARGS`` being replaced
|
||||
"""
|
||||
def inner():
|
||||
value = CLIARGS.get(key, default=default)
|
||||
if not shallowcopy:
|
||||
return value
|
||||
elif is_sequence(value):
|
||||
return value[:]
|
||||
elif isinstance(value, (Mapping, Set)):
|
||||
return value.copy()
|
||||
return value
|
||||
return inner
|
||||
|
|
|
@ -151,19 +151,9 @@ class PlayIterator:
|
|||
self._variable_manager = variable_manager
|
||||
|
||||
# Default options to gather
|
||||
gather_subset = play_context.gather_subset
|
||||
gather_timeout = play_context.gather_timeout
|
||||
fact_path = play_context.fact_path
|
||||
|
||||
# Retrieve subset to gather
|
||||
if self._play.gather_subset is not None:
|
||||
gather_subset = self._play.gather_subset
|
||||
# Retrieve timeout for gather
|
||||
if self._play.gather_timeout is not None:
|
||||
gather_timeout = self._play.gather_timeout
|
||||
# Retrieve fact_path
|
||||
if self._play.fact_path is not None:
|
||||
fact_path = self._play.fact_path
|
||||
gather_subset = self._play.gather_subset
|
||||
gather_timeout = self._play.gather_timeout
|
||||
fact_path = self._play.fact_path
|
||||
|
||||
setup_block = Block(play=self._play)
|
||||
# Gathering facts with run_once would copy the facts from one host to
|
||||
|
@ -190,11 +180,11 @@ class PlayIterator:
|
|||
setup_task.when = self._play._included_conditional[:]
|
||||
setup_block.block = [setup_task]
|
||||
|
||||
setup_block = setup_block.filter_tagged_tasks(play_context, all_vars)
|
||||
setup_block = setup_block.filter_tagged_tasks(all_vars)
|
||||
self._blocks.append(setup_block)
|
||||
|
||||
for block in self._play.compile():
|
||||
new_block = block.filter_tagged_tasks(play_context, all_vars)
|
||||
new_block = block.filter_tagged_tasks(all_vars)
|
||||
if new_block.has_tasks():
|
||||
self._blocks.append(new_block)
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ from ansible import constants as C
|
|||
from ansible import context
|
||||
from ansible.executor.task_queue_manager import TaskQueueManager
|
||||
from ansible.module_utils._text import to_native, to_text
|
||||
from ansible.plugins.loader import become_loader, connection_loader, shell_loader
|
||||
from ansible.playbook import Playbook
|
||||
from ansible.template import Templar
|
||||
from ansible.plugins.loader import connection_loader, shell_loader
|
||||
|
@ -82,9 +83,10 @@ class PlaybookExecutor:
|
|||
entrylist = []
|
||||
entry = {}
|
||||
try:
|
||||
# preload become/connecition/shell to set config defs cached
|
||||
# preload become/connection/shell to set config defs cached
|
||||
list(connection_loader.all(class_only=True))
|
||||
list(shell_loader.all(class_only=True))
|
||||
list(become_loader.all(class_only=True))
|
||||
|
||||
for playbook_path in self._playbooks:
|
||||
pb = Playbook.load(playbook_path, variable_manager=self._variable_manager, loader=self._loader)
|
||||
|
|
|
@ -23,6 +23,7 @@ from ansible.module_utils._text import to_text, to_native
|
|||
from ansible.module_utils.connection import write_to_file_descriptor
|
||||
from ansible.playbook.conditional import Conditional
|
||||
from ansible.playbook.task import Task
|
||||
from ansible.plugins.loader import become_loader
|
||||
from ansible.template import Templar
|
||||
from ansible.utils.listify import listify_lookup_plugin_terms
|
||||
from ansible.utils.unsafe_proxy import UnsafeProxy, wrap_var
|
||||
|
@ -590,7 +591,6 @@ class TaskExecutor:
|
|||
self._connection._play_context = self._play_context
|
||||
|
||||
self._set_connection_options(variables, templar)
|
||||
self._set_shell_options(variables, templar)
|
||||
|
||||
# get handler
|
||||
self._handler = self._get_action_handler(connection=self._connection, templar=templar)
|
||||
|
@ -849,6 +849,13 @@ class TaskExecutor:
|
|||
else:
|
||||
return async_result
|
||||
|
||||
def _get_become(self, name):
|
||||
become = become_loader.get(name)
|
||||
if not become:
|
||||
raise AnsibleError("Invalid become method specified, could not find matching plugin: '%s'. "
|
||||
"Use `ansible-doc -t become -l` to list available plugins." % name)
|
||||
return become
|
||||
|
||||
def _get_connection(self, variables, templar):
|
||||
'''
|
||||
Reads the connection property for the host, and returns the
|
||||
|
@ -869,8 +876,8 @@ class TaskExecutor:
|
|||
if isinstance(i, string_types) and i.startswith("ansible_") and i.endswith("_interpreter"):
|
||||
variables[i] = delegated_vars[i]
|
||||
|
||||
# load connection
|
||||
conn_type = self._play_context.connection
|
||||
|
||||
connection = self._shared_loader_obj.connection_loader.get(
|
||||
conn_type,
|
||||
self._play_context,
|
||||
|
@ -882,8 +889,30 @@ class TaskExecutor:
|
|||
if not connection:
|
||||
raise AnsibleError("the connection plugin '%s' was not found" % conn_type)
|
||||
|
||||
# load become plugin if needed
|
||||
become_plugin = None
|
||||
if self._play_context.become:
|
||||
become_plugin = self._get_become(self._play_context.become_method)
|
||||
|
||||
if getattr(become_plugin, 'require_tty', False) and not getattr(connection, 'has_tty', False):
|
||||
raise AnsibleError(
|
||||
"The '%s' connection does not provide a tty which is requied for the selected "
|
||||
"become plugin: %s." % (conn_type, become_plugin.name)
|
||||
)
|
||||
|
||||
try:
|
||||
connection.set_become_plugin(become_plugin)
|
||||
except AttributeError:
|
||||
# Connection plugin does not support set_become_plugin
|
||||
pass
|
||||
|
||||
# Backwards compat for connection plugins that don't support become plugins
|
||||
# Just do this unconditionally for now, we could move it inside of the
|
||||
# AttributeError above later
|
||||
self._play_context.set_become_plugin(become_plugin)
|
||||
|
||||
# FIXME: remove once all plugins pull all data from self._options
|
||||
self._play_context.set_options_from_plugin(connection)
|
||||
self._play_context.set_attributes_from_plugin(connection)
|
||||
|
||||
if any(((connection.supports_persistence and C.USE_PERSISTENT_CONNECTIONS), connection.force_persistence)):
|
||||
self._play_context.timeout = connection.get_option('persistent_command_timeout')
|
||||
|
@ -912,13 +941,30 @@ class TaskExecutor:
|
|||
|
||||
return options
|
||||
|
||||
def _set_plugin_options(self, plugin_type, variables, templar, task_keys):
|
||||
try:
|
||||
plugin = getattr(self._connection, '_%s' % plugin_type)
|
||||
except AttributeError:
|
||||
# Some plugins are assigned to private attrs, ``become`` is not
|
||||
plugin = getattr(self._connection, plugin_type)
|
||||
option_vars = C.config.get_plugin_vars(plugin_type, plugin._load_name)
|
||||
options = {}
|
||||
for k in option_vars:
|
||||
if k in variables:
|
||||
options[k] = templar.template(variables[k])
|
||||
# TODO move to task method?
|
||||
plugin.set_options(task_keys=task_keys, var_options=options)
|
||||
|
||||
def _set_connection_options(self, variables, templar):
|
||||
|
||||
# Keep the pre-delegate values for these keys
|
||||
PRESERVE_ORIG = ('inventory_hostname',)
|
||||
|
||||
# create copy with delegation built in
|
||||
final_vars = combine_vars(variables, variables.get('ansible_delegated_vars', dict()).get(self._task.delegate_to, dict()))
|
||||
final_vars = combine_vars(
|
||||
variables,
|
||||
variables.get('ansible_delegated_vars', {}).get(self._task.delegate_to, {})
|
||||
)
|
||||
|
||||
# grab list of usable vars for this plugin
|
||||
option_vars = C.config.get_plugin_vars('connection', self._connection._load_name)
|
||||
|
@ -937,17 +983,25 @@ class TaskExecutor:
|
|||
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)
|
||||
self._set_shell_options(final_vars, templar)
|
||||
task_keys = self._task.dump_attrs()
|
||||
|
||||
def _set_shell_options(self, variables, templar):
|
||||
option_vars = C.config.get_plugin_vars('shell', self._connection._shell._load_name)
|
||||
options = {}
|
||||
for k in option_vars:
|
||||
if k in variables:
|
||||
options[k] = templar.template(variables[k])
|
||||
self._connection._shell.set_options(var_options=options)
|
||||
# set options with 'templated vars' specific to this plugin and dependant ones
|
||||
self._connection.set_options(task_keys=task_keys, var_options=options)
|
||||
self._set_plugin_options('shell', final_vars, templar, task_keys)
|
||||
|
||||
if self._connection.become is not None:
|
||||
# FIXME: find alternate route to provide passwords,
|
||||
# keep out of play objects to avoid accidental disclosure
|
||||
task_keys['become_pass'] = self._play_context.become_pass
|
||||
self._set_plugin_options('become', final_vars, templar, task_keys)
|
||||
|
||||
# FOR BACKWARDS COMPAT:
|
||||
for option in ('become_user', 'become_flags', 'become_exe'):
|
||||
try:
|
||||
setattr(self._play_context, option, self._connection.become.get_option(option))
|
||||
except KeyError:
|
||||
pass # some plugins don't support all base flags
|
||||
self._play_context.prompt = self._connection.become.prompt
|
||||
|
||||
def _get_action_handler(self, connection, templar):
|
||||
'''
|
||||
|
|
|
@ -26,7 +26,6 @@ from ansible.errors import AnsibleParserError
|
|||
from ansible.module_utils._text import to_bytes, to_text, to_native
|
||||
from ansible.playbook.play import Play
|
||||
from ansible.playbook.playbook_include import PlaybookInclude
|
||||
from ansible.plugins.loader import get_all_plugin_loaders
|
||||
from ansible.utils.display import Display
|
||||
|
||||
display = Display()
|
||||
|
@ -64,13 +63,6 @@ class Playbook:
|
|||
|
||||
self._file_name = file_name
|
||||
|
||||
# dynamically load any plugins from the playbook directory
|
||||
for name, obj in get_all_plugin_loaders():
|
||||
if obj.subdir:
|
||||
plugin_path = os.path.join(self._basedir, obj.subdir)
|
||||
if os.path.isdir(to_bytes(plugin_path)):
|
||||
obj.add_directory(plugin_path)
|
||||
|
||||
try:
|
||||
ds = self._loader.load_from_file(os.path.basename(file_name))
|
||||
except UnicodeDecodeError as e:
|
||||
|
|
|
@ -14,7 +14,7 @@ from functools import partial
|
|||
from jinja2.exceptions import UndefinedError
|
||||
|
||||
from ansible import constants as C
|
||||
|
||||
from ansible import context
|
||||
from ansible.module_utils.six import iteritems, string_types, with_metaclass
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible.errors import AnsibleParserError, AnsibleUndefinedVariable, AnsibleAssertionError
|
||||
|
@ -520,10 +520,10 @@ class FieldAttributeBase(with_metaclass(BaseMeta, object)):
|
|||
'''
|
||||
Dumps all attributes to a dictionary
|
||||
'''
|
||||
attrs = dict()
|
||||
attrs = {}
|
||||
for (name, attribute) in iteritems(self._valid_attrs):
|
||||
attr = getattr(self, name)
|
||||
if attribute.isa == 'class' and attr is not None and hasattr(attr, 'serialize'):
|
||||
if attribute.isa == 'class' and hasattr(attr, 'serialize'):
|
||||
attrs[name] = attr.serialize()
|
||||
else:
|
||||
attrs[name] = attr
|
||||
|
@ -592,9 +592,9 @@ class Base(FieldAttributeBase):
|
|||
_name = FieldAttribute(isa='string', default='', always_post_validate=True, inherit=False)
|
||||
|
||||
# connection/transport
|
||||
_connection = FieldAttribute(isa='string')
|
||||
_connection = FieldAttribute(isa='string', default=context.cliargs_deferred_get('connection'))
|
||||
_port = FieldAttribute(isa='int')
|
||||
_remote_user = FieldAttribute(isa='string')
|
||||
_remote_user = FieldAttribute(isa='string', default=context.cliargs_deferred_get('remote_user'))
|
||||
|
||||
# variables
|
||||
_vars = FieldAttribute(isa='dict', priority=100, inherit=False)
|
||||
|
@ -608,8 +608,8 @@ class Base(FieldAttributeBase):
|
|||
_run_once = FieldAttribute(isa='bool')
|
||||
_ignore_errors = FieldAttribute(isa='bool')
|
||||
_ignore_unreachable = FieldAttribute(isa='bool')
|
||||
_check_mode = FieldAttribute(isa='bool')
|
||||
_diff = FieldAttribute(isa='bool')
|
||||
_check_mode = FieldAttribute(isa='bool', default=context.cliargs_deferred_get('check'))
|
||||
_diff = FieldAttribute(isa='bool', default=context.cliargs_deferred_get('diff'))
|
||||
_any_errors_fatal = FieldAttribute(isa='bool', default=C.ANY_ERRORS_FATAL)
|
||||
|
||||
# explicitly invoke a debugger on tasks
|
||||
|
|
|
@ -20,6 +20,7 @@ from __future__ import (absolute_import, division, print_function)
|
|||
__metaclass__ = type
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible import context
|
||||
from ansible.errors import AnsibleParserError
|
||||
from ansible.playbook.attribute import FieldAttribute
|
||||
from ansible.utils.display import Display
|
||||
|
@ -30,9 +31,9 @@ display = Display()
|
|||
class Become:
|
||||
|
||||
# Privilege escalation
|
||||
_become = FieldAttribute(isa='bool')
|
||||
_become_method = FieldAttribute(isa='string')
|
||||
_become_user = FieldAttribute(isa='string')
|
||||
_become = FieldAttribute(isa='bool', default=context.cliargs_deferred_get('become'))
|
||||
_become_method = FieldAttribute(isa='string', default=context.cliargs_deferred_get('become_method'))
|
||||
_become_user = FieldAttribute(isa='string', default=context.cliargs_deferred_get('become_user'))
|
||||
_become_flags = FieldAttribute(isa='string')
|
||||
|
||||
def __init__(self):
|
||||
|
|
|
@ -362,10 +362,9 @@ class Block(Base, Become, Conditional, Taggable):
|
|||
|
||||
return value
|
||||
|
||||
def filter_tagged_tasks(self, play_context, all_vars):
|
||||
def filter_tagged_tasks(self, all_vars):
|
||||
'''
|
||||
Creates a new block, with task lists filtered based on the tags contained
|
||||
within the play_context object.
|
||||
Creates a new block, with task lists filtered based on the tags.
|
||||
'''
|
||||
|
||||
def evaluate_and_append_task(target):
|
||||
|
@ -374,8 +373,8 @@ class Block(Base, Become, Conditional, Taggable):
|
|||
if isinstance(task, Block):
|
||||
tmp_list.append(evaluate_block(task))
|
||||
elif (task.action == 'meta' or
|
||||
(task.action == 'include' and task.evaluate_tags([], play_context.skip_tags, all_vars=all_vars)) or
|
||||
task.evaluate_tags(play_context.only_tags, play_context.skip_tags, all_vars=all_vars)):
|
||||
(task.action == 'include' and task.evaluate_tags([], self._play.skip_tags, all_vars=all_vars)) or
|
||||
task.evaluate_tags(self._play.only_tags, self._play.skip_tags, all_vars=all_vars)):
|
||||
tmp_list.append(task)
|
||||
return tmp_list
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ from __future__ import (absolute_import, division, print_function)
|
|||
__metaclass__ = type
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible import context
|
||||
from ansible.errors import AnsibleParserError, AnsibleAssertionError
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.playbook.attribute import FieldAttribute
|
||||
|
@ -54,10 +55,10 @@ class Play(Base, Taggable, Become):
|
|||
_hosts = FieldAttribute(isa='list', required=True, listof=string_types, always_post_validate=True)
|
||||
|
||||
# Facts
|
||||
_fact_path = FieldAttribute(isa='string', default=None)
|
||||
_gather_facts = FieldAttribute(isa='bool', default=None, always_post_validate=True)
|
||||
_gather_subset = FieldAttribute(isa='list', default=None, listof=string_types, always_post_validate=True)
|
||||
_gather_timeout = FieldAttribute(isa='int', default=None, always_post_validate=True)
|
||||
_gather_timeout = FieldAttribute(isa='int', default=C.DEFAULT_GATHER_TIMEOUT, always_post_validate=True)
|
||||
_fact_path = FieldAttribute(isa='string', default=C.DEFAULT_FACT_PATH)
|
||||
|
||||
# Variable Attributes
|
||||
_vars_files = FieldAttribute(isa='list', default=list, priority=99)
|
||||
|
@ -73,7 +74,7 @@ class Play(Base, Taggable, Become):
|
|||
_tasks = FieldAttribute(isa='list', default=list)
|
||||
|
||||
# Flag/Setting Attributes
|
||||
_force_handlers = FieldAttribute(isa='bool', always_post_validate=True)
|
||||
_force_handlers = FieldAttribute(isa='bool', default=context.cliargs_deferred_get('force_handlers'), always_post_validate=True)
|
||||
_max_fail_percentage = FieldAttribute(isa='percent', always_post_validate=True)
|
||||
_serial = FieldAttribute(isa='list', default=list, always_post_validate=True)
|
||||
_strategy = FieldAttribute(isa='string', default=C.DEFAULT_STRATEGY, always_post_validate=True)
|
||||
|
@ -89,6 +90,9 @@ class Play(Base, Taggable, Become):
|
|||
self._removed_hosts = []
|
||||
self.ROLE_CACHE = {}
|
||||
|
||||
self.only_tags = set(context.CLIARGS.get('tags', [])) or frozenset(('all',))
|
||||
self.skip_tags = set(context.CLIARGS.get('skip_tags', []))
|
||||
|
||||
def __repr__(self):
|
||||
return self.get_name()
|
||||
|
||||
|
|
|
@ -23,22 +23,17 @@ __metaclass__ = type
|
|||
|
||||
import os
|
||||
import pwd
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible import context
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible.playbook.attribute import FieldAttribute
|
||||
from ansible.playbook.base import Base
|
||||
from ansible.plugins import get_plugin_class
|
||||
from ansible.utils.display import Display
|
||||
from ansible.plugins.loader import get_shell_plugin
|
||||
from ansible.utils.ssh_functions import check_for_controlpersist
|
||||
|
||||
|
||||
|
@ -47,41 +42,6 @@ display = Display()
|
|||
|
||||
__all__ = ['PlayContext']
|
||||
|
||||
# TODO: needs to be configurable
|
||||
b_SU_PROMPT_LOCALIZATIONS = [
|
||||
to_bytes('Password'),
|
||||
to_bytes('암호'),
|
||||
to_bytes('パスワード'),
|
||||
to_bytes('Adgangskode'),
|
||||
to_bytes('Contraseña'),
|
||||
to_bytes('Contrasenya'),
|
||||
to_bytes('Hasło'),
|
||||
to_bytes('Heslo'),
|
||||
to_bytes('Jelszó'),
|
||||
to_bytes('Lösenord'),
|
||||
to_bytes('Mật khẩu'),
|
||||
to_bytes('Mot de passe'),
|
||||
to_bytes('Parola'),
|
||||
to_bytes('Parool'),
|
||||
to_bytes('Pasahitza'),
|
||||
to_bytes('Passord'),
|
||||
to_bytes('Passwort'),
|
||||
to_bytes('Salasana'),
|
||||
to_bytes('Sandi'),
|
||||
to_bytes('Senha'),
|
||||
to_bytes('Wachtwoord'),
|
||||
to_bytes('ססמה'),
|
||||
to_bytes('Лозинка'),
|
||||
to_bytes('Парола'),
|
||||
to_bytes('Пароль'),
|
||||
to_bytes('गुप्तशब्द'),
|
||||
to_bytes('शब्दकूट'),
|
||||
to_bytes('సంకేతపదము'),
|
||||
to_bytes('හස්පදය'),
|
||||
to_bytes('密码'),
|
||||
to_bytes('密碼'),
|
||||
to_bytes('口令'),
|
||||
]
|
||||
|
||||
TASK_ATTRIBUTE_OVERRIDES = (
|
||||
'become',
|
||||
|
@ -113,9 +73,6 @@ RESET_VARS = (
|
|||
'ansible_ssh_executable',
|
||||
)
|
||||
|
||||
OPTION_FLAGS = ('connection', 'remote_user', 'private_key_file', 'verbosity', 'force_handlers', 'step', 'start_at_task', 'diff',
|
||||
'ssh_common_args', 'docker_extra_args', 'sftp_extra_args', 'scp_extra_args', 'ssh_extra_args')
|
||||
|
||||
|
||||
class PlayContext(Base):
|
||||
|
||||
|
@ -166,14 +123,6 @@ class PlayContext(Base):
|
|||
_become_flags = FieldAttribute(isa='string', default=C.DEFAULT_BECOME_FLAGS)
|
||||
_prompt = FieldAttribute(isa='string')
|
||||
|
||||
# DEPRECATED: backwards compatibility fields for sudo/su
|
||||
_sudo_exe = FieldAttribute(isa='string', default=C.DEFAULT_SUDO_EXE)
|
||||
_sudo_flags = FieldAttribute(isa='string', default=C.DEFAULT_SUDO_FLAGS)
|
||||
_sudo_pass = FieldAttribute(isa='string')
|
||||
_su_exe = FieldAttribute(isa='string', default=C.DEFAULT_SU_EXE)
|
||||
_su_flags = FieldAttribute(isa='string', default=C.DEFAULT_SU_FLAGS)
|
||||
_su_pass = FieldAttribute(isa='string')
|
||||
|
||||
# general flags
|
||||
_verbosity = FieldAttribute(isa='int', default=0)
|
||||
_only_tags = FieldAttribute(isa='set', default=set)
|
||||
|
@ -182,12 +131,10 @@ class PlayContext(Base):
|
|||
_start_at_task = FieldAttribute(isa='string')
|
||||
_step = FieldAttribute(isa='bool', default=False)
|
||||
|
||||
# Fact gathering settings
|
||||
_gather_subset = FieldAttribute(isa='string', default=C.DEFAULT_GATHER_SUBSET)
|
||||
_gather_timeout = FieldAttribute(isa='string', default=C.DEFAULT_GATHER_TIMEOUT)
|
||||
_fact_path = FieldAttribute(isa='string', default=C.DEFAULT_FACT_PATH)
|
||||
|
||||
def __init__(self, play=None, passwords=None, connection_lockfd=None):
|
||||
# Note: play is really not optional. The only time it could be omitted is when we create
|
||||
# a PlayContext just so we can invoke its deserialize method to load it from a serialized
|
||||
# data source.
|
||||
|
||||
super(PlayContext, self).__init__()
|
||||
|
||||
|
@ -197,6 +144,8 @@ class PlayContext(Base):
|
|||
self.password = passwords.get('conn_pass', '')
|
||||
self.become_pass = passwords.get('become_pass', '')
|
||||
|
||||
self._become_plugin = None
|
||||
|
||||
self.prompt = ''
|
||||
self.success_key = ''
|
||||
|
||||
|
@ -205,37 +154,12 @@ class PlayContext(Base):
|
|||
|
||||
# set options before play to allow play to override them
|
||||
if context.CLIARGS:
|
||||
self.set_options()
|
||||
self.set_attributes_from_cli()
|
||||
|
||||
if play:
|
||||
self.set_play(play)
|
||||
self.set_attributes_from_play(play)
|
||||
|
||||
def set_play(self, play):
|
||||
'''
|
||||
Configures this connection information instance with data from
|
||||
the play class.
|
||||
'''
|
||||
|
||||
if play.connection:
|
||||
self.connection = play.connection
|
||||
|
||||
if play.remote_user:
|
||||
self.remote_user = play.remote_user
|
||||
|
||||
if play.port:
|
||||
self.port = int(play.port)
|
||||
|
||||
if play.become is not None:
|
||||
self.become = play.become
|
||||
if play.become_method:
|
||||
self.become_method = play.become_method
|
||||
if play.become_user:
|
||||
self.become_user = play.become_user
|
||||
|
||||
if play.force_handlers is not None:
|
||||
self.force_handlers = play.force_handlers
|
||||
|
||||
def set_options_from_plugin(self, plugin):
|
||||
def set_attributes_from_plugin(self, plugin):
|
||||
# generic derived from connection plugin, temporary for backwards compat, in the end we should not set play_context properties
|
||||
|
||||
# get options for plugins
|
||||
|
@ -246,46 +170,43 @@ class PlayContext(Base):
|
|||
if flag:
|
||||
setattr(self, flag, self.connection.get_option(flag))
|
||||
|
||||
# TODO: made irrelavent by above
|
||||
# get ssh options
|
||||
# for flag in ('ssh_common_args', 'docker_extra_args', 'sftp_extra_args', 'scp_extra_args', 'ssh_extra_args'):
|
||||
# setattr(self, flag, getattr(options, flag, ''))
|
||||
def set_attributes_from_play(self, play):
|
||||
# From ansible.playbook.Become
|
||||
self.become = play.become
|
||||
self.become_method = play.become_method
|
||||
self.become_user = play.become_user
|
||||
|
||||
def set_options(self):
|
||||
# From ansible.playbook.Base
|
||||
self.check_mode = play.check_mode
|
||||
self.diff = play.diff
|
||||
self.connection = play.connection
|
||||
self.remote_user = play.remote_user
|
||||
|
||||
# from ansible.playbook.Play
|
||||
self.force_handlers = play.force_handlers
|
||||
self.only_tags = play.only_tags
|
||||
self.skip_tags = play.skip_tags
|
||||
|
||||
def set_attributes_from_cli(self):
|
||||
'''
|
||||
Configures this connection information instance with data from
|
||||
options specified by the user on the command line. These have a
|
||||
lower precedence than those set on the play or host.
|
||||
'''
|
||||
|
||||
# privilege escalation
|
||||
self.become = context.CLIARGS['become']
|
||||
self.become_method = context.CLIARGS['become_method']
|
||||
self.become_user = context.CLIARGS['become_user']
|
||||
|
||||
self.check_mode = boolean(context.CLIARGS['check'], strict=False)
|
||||
self.diff = boolean(context.CLIARGS['diff'], strict=False)
|
||||
|
||||
# general flags (should we move out?)
|
||||
# should only be 'non plugin' flags
|
||||
for flag in OPTION_FLAGS:
|
||||
attribute = context.CLIARGS.get(flag, False)
|
||||
if attribute:
|
||||
setattr(self, flag, attribute)
|
||||
|
||||
if context.CLIARGS.get('timeout', False):
|
||||
self.timeout = context.CLIARGS['timeout']
|
||||
self.timeout = int(context.CLIARGS['timeout'])
|
||||
|
||||
# get the tag info from options. We check to see if the options have
|
||||
# the attribute, as it is not always added via the CLI
|
||||
if context.CLIARGS.get('tags', False):
|
||||
self.only_tags.update(context.CLIARGS['tags'])
|
||||
# From the command line. These should probably be used directly by plugins instead
|
||||
# For now, they are likely to be moved to FieldAttribute defaults
|
||||
self.private_key_file = context.CLIARGS.get('private_key_file') # Else default
|
||||
self.verbosity = context.CLIARGS.get('verbosity') # Else default
|
||||
self.ssh_common_args = context.CLIARGS.get('ssh_common_args') # Else default
|
||||
self.ssh_extra_args = context.CLIARGS.get('ssh_extra_args') # Else default
|
||||
self.sftp_extra_args = context.CLIARGS.get('sftp_extra_args') # Else default
|
||||
self.scp_extra_args = context.CLIARGS.get('scp_extra_args') # Else default
|
||||
|
||||
if len(self.only_tags) == 0:
|
||||
self.only_tags = set(['all'])
|
||||
|
||||
if context.CLIARGS.get('skip_tags', False):
|
||||
self.skip_tags.update(context.CLIARGS['skip_tags'])
|
||||
# Not every cli that uses PlayContext has these command line args so have a default
|
||||
self.start_at_task = context.CLIARGS.get('start_at_task', None) # Else default
|
||||
|
||||
def set_task_and_variable_override(self, task, variables, templar):
|
||||
'''
|
||||
|
@ -376,13 +297,6 @@ class PlayContext(Base):
|
|||
attrs_considered.append(attr)
|
||||
# no else, as no other vars should be considered
|
||||
|
||||
# become legacy updates -- from commandline
|
||||
if not new_info.become_pass:
|
||||
if new_info.become_method == 'sudo' and new_info.sudo_pass:
|
||||
new_info.become_pass = new_info.sudo_pass
|
||||
elif new_info.become_method == 'su' and new_info.su_pass:
|
||||
new_info.become_pass = new_info.su_pass
|
||||
|
||||
# become legacy updates -- from inventory file (inventory overrides
|
||||
# commandline)
|
||||
for become_pass_name in C.MAGIC_VARIABLE_MAPPING.get('become_pass'):
|
||||
|
@ -442,135 +356,43 @@ class PlayContext(Base):
|
|||
|
||||
return new_info
|
||||
|
||||
def set_become_plugin(self, plugin):
|
||||
self._become_plugin = plugin
|
||||
|
||||
def make_become_cmd(self, cmd, executable=None):
|
||||
""" helper function to create privilege escalation commands """
|
||||
display.deprecated(
|
||||
"PlayContext.make_become_cmd should not be used, the calling code should be using become plugins instead",
|
||||
version="2.12"
|
||||
)
|
||||
|
||||
prompt = None
|
||||
success_key = None
|
||||
self.prompt = None
|
||||
if not cmd or not self.become:
|
||||
return cmd
|
||||
|
||||
if self.become:
|
||||
become_method = self.become_method
|
||||
|
||||
# load/call become plugins here
|
||||
plugin = self._become_plugin
|
||||
|
||||
if plugin:
|
||||
options = {
|
||||
'become_exe': self.become_exe or become_method,
|
||||
'become_flags': self.become_flags or '',
|
||||
'become_user': self.become_user,
|
||||
'become_pass': self.become_pass
|
||||
}
|
||||
plugin.set_options(direct=options)
|
||||
|
||||
if not executable:
|
||||
executable = self.executable
|
||||
|
||||
becomecmd = None
|
||||
randbits = ''.join(random.choice(string.ascii_lowercase) for x in range(32))
|
||||
success_key = 'BECOME-SUCCESS-%s' % randbits
|
||||
success_cmd = shlex_quote('echo %s; %s' % (success_key, cmd))
|
||||
|
||||
if executable:
|
||||
command = '%s -c %s' % (executable, success_cmd)
|
||||
else:
|
||||
command = success_cmd
|
||||
|
||||
# set executable to use for the privilege escalation method, with various overrides
|
||||
exe = self.become_exe or getattr(self, '%s_exe' % self.become_method, self.become_method)
|
||||
|
||||
# set flags to use for the privilege escalation method, with various overrides
|
||||
flags = self.become_flags or getattr(self, '%s_flags' % self.become_method, '')
|
||||
|
||||
if self.become_method == 'sudo':
|
||||
# If we have a password, we run sudo with a randomly-generated
|
||||
# prompt set using -p. Otherwise we run it with default -n, which makes
|
||||
# it fail if it would have prompted for a password.
|
||||
# Cannot rely on -n as it can be removed from defaults, which should be
|
||||
# done for older versions of sudo that do not support the option.
|
||||
#
|
||||
# Passing a quoted compound command to sudo (or sudo -s)
|
||||
# directly doesn't work, so we shellquote it with shlex_quote()
|
||||
# and pass the quoted string to the user's shell.
|
||||
|
||||
# force quick error if password is required but not supplied, should prevent sudo hangs.
|
||||
if self.become_pass:
|
||||
prompt = '[sudo via ansible, key=%s] password: ' % randbits
|
||||
becomecmd = '%s %s -p "%s" -u %s %s' % (exe, flags.replace('-n', ''), prompt, self.become_user, command)
|
||||
else:
|
||||
becomecmd = '%s %s -u %s %s' % (exe, flags, self.become_user, command)
|
||||
|
||||
elif self.become_method == 'su':
|
||||
|
||||
# passing code ref to examine prompt as simple string comparisson isn't good enough with su
|
||||
def detect_su_prompt(b_data):
|
||||
b_password_string = b"|".join([br'(\w+\'s )?' + x for x in b_SU_PROMPT_LOCALIZATIONS])
|
||||
# Colon or unicode fullwidth colon
|
||||
b_password_string = b_password_string + to_bytes(u' ?(:|:) ?')
|
||||
b_SU_PROMPT_LOCALIZATIONS_RE = re.compile(b_password_string, flags=re.IGNORECASE)
|
||||
return bool(b_SU_PROMPT_LOCALIZATIONS_RE.match(b_data))
|
||||
prompt = detect_su_prompt
|
||||
|
||||
becomecmd = '%s %s %s -c %s' % (exe, flags, self.become_user, shlex_quote(command))
|
||||
|
||||
elif self.become_method == 'pbrun':
|
||||
|
||||
prompt = 'Password:'
|
||||
becomecmd = '%s %s -u %s %s' % (exe, flags, self.become_user, success_cmd)
|
||||
|
||||
elif self.become_method == 'ksu':
|
||||
def detect_ksu_prompt(b_data):
|
||||
return re.match(b"Kerberos password for .*@.*:", b_data)
|
||||
|
||||
prompt = detect_ksu_prompt
|
||||
becomecmd = '%s %s %s -e %s' % (exe, self.become_user, flags, command)
|
||||
|
||||
elif self.become_method == 'pfexec':
|
||||
|
||||
# No user as it uses it's own exec_attr to figure it out
|
||||
becomecmd = '%s %s "%s"' % (exe, flags, success_cmd)
|
||||
|
||||
elif self.become_method == 'runas':
|
||||
# become is handled inside the WinRM connection plugin
|
||||
if not self.become_user:
|
||||
raise AnsibleError(("The 'runas' become method requires a username "
|
||||
"(specify with the '--become-user' CLI arg, the 'become_user' keyword, or the 'ansible_become_user' variable)"))
|
||||
becomecmd = cmd
|
||||
|
||||
elif self.become_method == 'doas':
|
||||
|
||||
prompt = 'doas (%s@' % self.remote_user
|
||||
exe = self.become_exe or 'doas'
|
||||
|
||||
if not self.become_pass:
|
||||
flags += ' -n '
|
||||
|
||||
if self.become_user:
|
||||
flags += ' -u %s ' % self.become_user
|
||||
|
||||
# FIXME: make shell independent
|
||||
becomecmd = '%s %s %s -c %s' % (exe, flags, executable, success_cmd)
|
||||
|
||||
elif self.become_method == 'dzdo':
|
||||
# If we have a password, we run dzdo with a randomly-generated
|
||||
# prompt set using -p. Otherwise we run it with -n, if
|
||||
# requested, which makes it fail if it would have prompted for a
|
||||
# password.
|
||||
|
||||
exe = self.become_exe or 'dzdo'
|
||||
if self.become_pass:
|
||||
prompt = '[dzdo via ansible, key=%s] password: ' % randbits
|
||||
becomecmd = '%s %s -p %s -u %s %s' % (exe, flags.replace('-n', ''), shlex_quote(prompt), self.become_user, command)
|
||||
else:
|
||||
becomecmd = '%s %s -u %s %s' % (exe, flags, self.become_user, command)
|
||||
|
||||
elif self.become_method == 'pmrun':
|
||||
|
||||
exe = self.become_exe or 'pmrun'
|
||||
|
||||
prompt = 'Enter UPM user password:'
|
||||
becomecmd = '%s %s %s' % (exe, flags, shlex_quote(command))
|
||||
|
||||
elif self.become_method == 'machinectl':
|
||||
|
||||
exe = self.become_exe or 'machinectl'
|
||||
becomecmd = '%s shell -q %s %s@ %s' % (exe, flags, self.become_user, command)
|
||||
|
||||
else:
|
||||
raise AnsibleError("Privilege escalation method not found: %s" % self.become_method)
|
||||
|
||||
shell = get_shell_plugin(executable=executable)
|
||||
cmd = plugin.build_become_command(cmd, shell)
|
||||
# for backwards compat:
|
||||
if self.become_pass:
|
||||
self.prompt = prompt
|
||||
self.success_key = success_key
|
||||
return becomecmd
|
||||
self.prompt = plugin.prompt
|
||||
else:
|
||||
raise AnsibleError("Privilege escalation method not found: %s" % become_method)
|
||||
|
||||
return cmd
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ from ansible.playbook.conditional import Conditional
|
|||
from ansible.playbook.helpers import load_list_of_blocks
|
||||
from ansible.playbook.role.metadata import RoleMetadata
|
||||
from ansible.playbook.taggable import Taggable
|
||||
from ansible.plugins.loader import get_all_plugin_loaders
|
||||
from ansible.plugins.loader import add_all_plugin_dirs
|
||||
from ansible.utils.vars import combine_vars
|
||||
|
||||
|
||||
|
@ -194,12 +194,8 @@ class Role(Base, Become, Conditional, Taggable):
|
|||
else:
|
||||
self._attributes[attr_name] = role_include._attributes[attr_name]
|
||||
|
||||
# dynamically load any plugins from the role directory
|
||||
for name, obj in get_all_plugin_loaders():
|
||||
if obj.subdir:
|
||||
plugin_path = os.path.join(self._role_path, obj.subdir)
|
||||
if os.path.isdir(plugin_path):
|
||||
obj.add_directory(plugin_path)
|
||||
# ensure all plugins dirs for this role are added to plugin search path
|
||||
add_all_plugin_dirs(self._role_path)
|
||||
|
||||
# vars and default vars are regular dictionaries
|
||||
self._role_vars = self._load_role_yaml('vars', main=self._from_files.get('vars'), allow_dir=True)
|
||||
|
|
|
@ -24,6 +24,8 @@ __metaclass__ = type
|
|||
from abc import ABCMeta
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.six import with_metaclass, string_types
|
||||
from ansible.utils.display import Display
|
||||
|
||||
|
@ -52,7 +54,10 @@ class AnsiblePlugin(with_metaclass(ABCMeta, object)):
|
|||
|
||||
def get_option(self, option, hostvars=None):
|
||||
if option not in self._options:
|
||||
option_value = C.config.get_config_value(option, plugin_type=get_plugin_class(self), plugin_name=self._load_name, variables=hostvars)
|
||||
try:
|
||||
option_value = C.config.get_config_value(option, plugin_type=get_plugin_class(self), plugin_name=self._load_name, variables=hostvars)
|
||||
except AnsibleError as e:
|
||||
raise KeyError(to_native(e))
|
||||
self.set_option(option, option_value)
|
||||
return self._options.get(option)
|
||||
|
||||
|
|
|
@ -108,6 +108,24 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
|
||||
return result
|
||||
|
||||
def get_plugin_option(self, plugin, option, default=None):
|
||||
"""Helper to get an option from a plugin without having to use
|
||||
the try/except dance everywhere to set a default
|
||||
"""
|
||||
try:
|
||||
return plugin.get_option(option)
|
||||
except (AttributeError, KeyError):
|
||||
return default
|
||||
|
||||
def get_become_option(self, option, default=None):
|
||||
return self.get_plugin_option(self._connection.become, option, default=default)
|
||||
|
||||
def get_connection_option(self, option, default=None):
|
||||
return self.get_plugin_option(self._connection, option, default=default)
|
||||
|
||||
def get_shell_option(self, option, default=None):
|
||||
return self.get_plugin_option(self._connection._shell, option, default=default)
|
||||
|
||||
def _remote_file_exists(self, path):
|
||||
cmd = self._connection._shell.exists(path)
|
||||
result = self._low_level_execute_command(cmd=cmd, sudoable=True)
|
||||
|
@ -241,12 +259,23 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
Returns a list of admin users that are configured for the current shell
|
||||
plugin
|
||||
'''
|
||||
|
||||
return self.get_shell_option('admin_users', ['root'])
|
||||
|
||||
def _get_remote_user(self):
|
||||
''' consistently get the 'remote_user' for the action plugin '''
|
||||
# TODO: use 'current user running ansible' as fallback when moving away from play_context
|
||||
# pwd.getpwuid(os.getuid()).pw_name
|
||||
remote_user = None
|
||||
try:
|
||||
admin_users = self._connection._shell.get_option('admin_users')
|
||||
except AnsibleError:
|
||||
# fallback for old custom plugins w/o get_option
|
||||
admin_users = ['root']
|
||||
return admin_users
|
||||
remote_user = self._connection.get_option('remote_user')
|
||||
except KeyError:
|
||||
# plugin does not have remote_user option, fallback to default and/play_context
|
||||
remote_user = getattr(self._connection, 'default_user', None) or self._play_context.remote_user
|
||||
except AttributeError:
|
||||
# plugin does not use config system, fallback to old play_context
|
||||
remote_user = self._play_context.remote_user
|
||||
return remote_user
|
||||
|
||||
def _is_become_unprivileged(self):
|
||||
'''
|
||||
|
@ -261,11 +290,8 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
# if we use become and the user is not an admin (or same user) then
|
||||
# we need to return become_unprivileged as True
|
||||
admin_users = self._get_admin_users()
|
||||
try:
|
||||
remote_user = self._connection.get_option('remote_user')
|
||||
except AnsibleError:
|
||||
remote_user = self._play_context.remote_user
|
||||
return bool(self._play_context.become_user not in admin_users + [remote_user])
|
||||
remote_user = self._get_remote_user()
|
||||
return bool(self.get_become_option('become_user') not in admin_users + [remote_user])
|
||||
|
||||
def _make_tmp_path(self, remote_user=None):
|
||||
'''
|
||||
|
@ -273,10 +299,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
'''
|
||||
|
||||
become_unprivileged = self._is_become_unprivileged()
|
||||
try:
|
||||
remote_tmp = self._connection._shell.get_option('remote_tmp')
|
||||
except AnsibleError:
|
||||
remote_tmp = '~/.ansible/tmp'
|
||||
remote_tmp = self.get_shell_option('remote_tmp', default='~/.ansible/tmp')
|
||||
|
||||
# deal with tmpdir creation
|
||||
basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48))
|
||||
|
@ -409,7 +432,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
"allow_world_readable_tmpfiles" in the ansible.cfg
|
||||
"""
|
||||
if remote_user is None:
|
||||
remote_user = self._play_context.remote_user
|
||||
remote_user = self._get_remote_user()
|
||||
|
||||
if self._connection._shell.SHELL_FAMILY == 'powershell':
|
||||
# This won't work on Powershell as-is, so we'll just completely skip until
|
||||
|
@ -432,7 +455,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
# start to we'll have to fix this.
|
||||
setfacl_mode = 'r-X'
|
||||
|
||||
res = self._remote_set_user_facl(remote_paths, self._play_context.become_user, setfacl_mode)
|
||||
res = self._remote_set_user_facl(remote_paths, self.get_become_option('become_user'), setfacl_mode)
|
||||
if res['rc'] != 0:
|
||||
# File system acls failed; let's try to use chown next
|
||||
# Set executable bit first as on some systems an
|
||||
|
@ -442,7 +465,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
if res['rc'] != 0:
|
||||
raise AnsibleError('Failed to set file mode on remote temporary files (rc: {0}, err: {1})'.format(res['rc'], to_native(res['stderr'])))
|
||||
|
||||
res = self._remote_chown(remote_paths, self._play_context.become_user)
|
||||
res = self._remote_chown(remote_paths, self.get_become_option('become_user'))
|
||||
if res['rc'] != 0 and remote_user in self._get_admin_users():
|
||||
# chown failed even if remote_user is administrator/root
|
||||
raise AnsibleError('Failed to change ownership of the temporary files Ansible needs to create despite connecting as a privileged user. '
|
||||
|
@ -579,13 +602,14 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
# Network connection plugins (network_cli, netconf, etc.) execute on the controller, rather than the remote host.
|
||||
# As such, we want to avoid using remote_user for paths as remote_user may not line up with the local user
|
||||
# This is a hack and should be solved by more intelligent handling of remote_tmp in 2.7
|
||||
become_user = self.get_become_option('become_user')
|
||||
if getattr(self._connection, '_remote_is_local', False):
|
||||
pass
|
||||
elif sudoable and self._play_context.become and self._play_context.become_user:
|
||||
expand_path = '~%s' % self._play_context.become_user
|
||||
elif sudoable and self._play_context.become and become_user:
|
||||
expand_path = '~%s' % become_user
|
||||
else:
|
||||
# use remote user instead, if none set default to current user
|
||||
expand_path = '~%s' % (self._play_context.remote_user or self._connection.default_user or '')
|
||||
expand_path = '~%s' % (self._get_remote_user() or '')
|
||||
|
||||
# use shell to construct appropriate command and execute
|
||||
cmd = self._connection._shell.expand_user(expand_path)
|
||||
|
@ -673,26 +697,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
module_args['_ansible_tmpdir'] = self._connection._shell.tmpdir
|
||||
|
||||
# make sure the remote_tmp value is sent through in case modules needs to create their own
|
||||
try:
|
||||
module_args['_ansible_remote_tmp'] = self._connection._shell.get_option('remote_tmp')
|
||||
except KeyError:
|
||||
# here for 3rd party shell plugin compatibility in case they do not define the remote_tmp option
|
||||
module_args['_ansible_remote_tmp'] = '~/.ansible/tmp'
|
||||
|
||||
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)
|
||||
module_args['_ansible_remote_tmp'] = self.get_shell_option('remote_tmp', default='~/.ansible/tmp')
|
||||
|
||||
def _execute_module(self, module_name=None, module_args=None, tmp=None, task_vars=None, persist_files=False, delete_remote_tmp=None, wrap_async=False):
|
||||
'''
|
||||
|
@ -748,11 +753,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
# ANSIBLE_ASYNC_DIR is not set on the task, we get the value
|
||||
# from the shell option and temporarily add to the environment
|
||||
# list for async_wrapper to pick up
|
||||
try:
|
||||
async_dir = self._connection._shell.get_option('async_dir')
|
||||
except KeyError:
|
||||
# in case 3rd party plugin has not set this, use the default
|
||||
async_dir = "~/.ansible_async"
|
||||
async_dir = self.get_shell_option('async_dir', default="~/.ansible_async")
|
||||
remove_async_dir = len(self._task.environment)
|
||||
self._task.environment.append({"ANSIBLE_ASYNC_DIR": async_dir})
|
||||
|
||||
|
@ -861,7 +862,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
if remote_files:
|
||||
# remove none/empty
|
||||
remote_files = [x for x in remote_files if x]
|
||||
self._fixup_perms2(remote_files, self._play_context.remote_user)
|
||||
self._fixup_perms2(remote_files, self._get_remote_user())
|
||||
|
||||
# actually execute
|
||||
res = self._low_level_execute_command(cmd, sudoable=sudoable, in_data=in_data)
|
||||
|
@ -934,6 +935,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
data['rc'] = res['rc']
|
||||
return data
|
||||
|
||||
# FIXME: move to connection base
|
||||
def _low_level_execute_command(self, cmd, sudoable=True, in_data=None, executable=None, encoding_errors='surrogate_then_replace', chdir=None):
|
||||
'''
|
||||
This is the function which executes the low level shell command, which
|
||||
|
@ -951,21 +953,20 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
'''
|
||||
|
||||
display.debug("_low_level_execute_command(): starting")
|
||||
# if not cmd:
|
||||
# # this can happen with powershell modules when there is no analog to a Windows command (like chmod)
|
||||
# display.debug("_low_level_execute_command(): no command, exiting")
|
||||
# return dict(stdout='', stderr='', rc=254)
|
||||
# if not cmd:
|
||||
# # this can happen with powershell modules when there is no analog to a Windows command (like chmod)
|
||||
# display.debug("_low_level_execute_command(): no command, exiting")
|
||||
# return dict(stdout='', stderr='', rc=254)
|
||||
|
||||
if chdir:
|
||||
display.debug("_low_level_execute_command(): changing cwd to %s for this command" % chdir)
|
||||
cmd = self._connection._shell.append_command('cd %s' % chdir, cmd)
|
||||
|
||||
allow_same_user = C.BECOME_ALLOW_SAME_USER
|
||||
same_user = self._play_context.become_user == self._play_context.remote_user
|
||||
if sudoable and self._play_context.become and (allow_same_user or not same_user):
|
||||
if (sudoable and self._connection.transport != 'network_cli' and self._connection.become and
|
||||
(C.BECOME_ALLOW_SAME_USER or
|
||||
self.get_become_option('become_user') != self._get_remote_user())):
|
||||
display.debug("_low_level_execute_command(): using become for this command")
|
||||
if self._connection.transport != 'network_cli' and self._play_context.become_method != 'enable':
|
||||
cmd = self._play_context.make_become_cmd(cmd, executable=executable)
|
||||
cmd = self._connection.become.build_become_command(cmd, self._connection._shell)
|
||||
|
||||
if self._connection.allow_executable:
|
||||
if executable is None:
|
||||
|
|
|
@ -37,12 +37,7 @@ class ActionModule(ActionBase):
|
|||
else:
|
||||
# inject the async directory based on the shell option into the
|
||||
# module args
|
||||
try:
|
||||
async_dir = self._connection._shell.get_option('async_dir')
|
||||
except KeyError:
|
||||
# here for 3rd party shell plugin compatibility in case they do
|
||||
# not define the async_dir option
|
||||
async_dir = "~/.ansible_async"
|
||||
async_dir = self.get_shell_option('async_dir', default="~/.ansible_async")
|
||||
|
||||
module_args = dict(jid=jid, mode=mode, _async_dir=async_dir)
|
||||
status = self._execute_module(task_vars=task_vars,
|
||||
|
|
|
@ -307,7 +307,7 @@ class ActionModule(ActionBase):
|
|||
# Get the connect_timeout set on the connection to compare to the original
|
||||
try:
|
||||
connect_timeout = self._connection.get_option('connection_timeout')
|
||||
except AnsibleError:
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if original_connection_timeout != connect_timeout:
|
||||
|
@ -380,7 +380,7 @@ class ActionModule(ActionBase):
|
|||
try:
|
||||
original_connection_timeout = self._connection.get_option('connection_timeout')
|
||||
display.debug("{action}: saving original connect_timeout of {timeout}".format(action=self._task.action, timeout=original_connection_timeout))
|
||||
except AnsibleError:
|
||||
except KeyError:
|
||||
display.debug("{action}: connect_timeout connection option has not been set".format(action=self._task.action))
|
||||
# Initiate reboot
|
||||
reboot_result = self.perform_reboot(task_vars, distribution)
|
||||
|
|
89
lib/ansible/plugins/become/__init__.py
Normal file
89
lib/ansible/plugins/become/__init__.py
Normal file
|
@ -0,0 +1,89 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from abc import abstractmethod
|
||||
from random import choice
|
||||
from string import ascii_lowercase
|
||||
from gettext import dgettext
|
||||
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.plugins import AnsiblePlugin
|
||||
|
||||
|
||||
def _gen_id(length=32):
|
||||
''' return random string used to identify the current privelege escalation '''
|
||||
return ''.join(choice(ascii_lowercase) for x in range(length))
|
||||
|
||||
|
||||
class BecomeBase(AnsiblePlugin):
|
||||
|
||||
name = None
|
||||
|
||||
# messages for detecting prompted password issues
|
||||
fail = tuple()
|
||||
missing = tuple()
|
||||
|
||||
# many connection plugins cannot provide tty, set to True if your become
|
||||
# plugin requires a tty, i.e su
|
||||
require_tty = False
|
||||
|
||||
# prompt to match
|
||||
prompt = ''
|
||||
|
||||
def __init__(self):
|
||||
super(BecomeBase, self).__init__()
|
||||
self._id = ''
|
||||
self.success = ''
|
||||
|
||||
def expect_prompt(self):
|
||||
"""This function assists connection plugins in determining if they need to wait for
|
||||
a prompt. Both a prompt and a password are required.
|
||||
"""
|
||||
return self.prompt and self.get_option('become_pass')
|
||||
|
||||
def _build_success_command(self, cmd, shell, noexe=False):
|
||||
if not all((cmd, shell, self.success)):
|
||||
return cmd
|
||||
|
||||
cmd = shlex_quote('%s %s %s %s' % (shell.ECHO, self.success, shell.COMMAND_SEP, cmd))
|
||||
exe = getattr(shell, 'executable', None)
|
||||
if exe and not noexe:
|
||||
cmd = '%s -c %s' % (exe, cmd)
|
||||
return cmd
|
||||
|
||||
@abstractmethod
|
||||
def build_become_command(self, cmd, shell):
|
||||
self._id = _gen_id()
|
||||
self.success = 'BECOME-SUCCESS-%s' % self._id
|
||||
|
||||
def check_success(self, b_output):
|
||||
b_success = to_bytes(self.success)
|
||||
return any(b_success in l.rstrip() for l in b_output.splitlines(True))
|
||||
|
||||
def check_password_prompt(self, b_output):
|
||||
''' checks if the expected passwod prompt exists in b_output '''
|
||||
if self.prompt:
|
||||
b_prompt = to_bytes(self.prompt).strip()
|
||||
return any(l.strip().startswith(b_prompt) for l in b_output.splitlines())
|
||||
return False
|
||||
|
||||
def _check_password_error(self, b_out, msg):
|
||||
''' returns True/False if domain specific i18n version of msg is found in b_out '''
|
||||
b_fail = to_bytes(dgettext(self.name, msg))
|
||||
return b_fail and b_fail in b_out
|
||||
|
||||
def check_incorrect_password(self, b_output):
|
||||
for errstring in self.fail:
|
||||
if self._check_password_error(b_output, errstring):
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_missing_password(self, b_output):
|
||||
for errstring in self.missing:
|
||||
if self._check_password_error(b_output, errstring):
|
||||
return True
|
||||
return False
|
128
lib/ansible/plugins/become/doas.py
Normal file
128
lib/ansible/plugins/become/doas.py
Normal file
|
@ -0,0 +1,128 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
become: doas
|
||||
short_description: Do As user
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the doas utility.
|
||||
author: ansible (@core)
|
||||
version_added: "2.8"
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: doas_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_doas_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_DOAS_USER
|
||||
become_exe:
|
||||
description: Doas executable
|
||||
default: doas
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: doas_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_doas_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_DOAS_EXE
|
||||
become_flags:
|
||||
description: Options to pass to doas
|
||||
default:
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: doas_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_doas_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_DOAS_FLAGS
|
||||
become_pass:
|
||||
description: password for doas prompt
|
||||
required: False
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_doas_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_DOAS_PASS
|
||||
ini:
|
||||
- section: doas_become_plugin
|
||||
key: password
|
||||
prompt_l10n:
|
||||
description:
|
||||
- List of localized strings to match for prompt detection
|
||||
- If empty we'll use the built in one
|
||||
default: []
|
||||
ini:
|
||||
- section: doas_become_plugin
|
||||
key: localized_prompts
|
||||
vars:
|
||||
- name: ansible_doas_prompt_l10n
|
||||
env:
|
||||
- name: ANSIBLE_DOAS_PROMPT_L10N
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = 'doas'
|
||||
|
||||
# messages for detecting prompted password issues
|
||||
fail = ('Permission denied',)
|
||||
missing = ('Authorization required',)
|
||||
|
||||
def check_password_prompt(self, b_output):
|
||||
''' checks if the expected passwod prompt exists in b_output '''
|
||||
|
||||
# FIXME: more accurate would be: 'doas (%s@' % remote_user
|
||||
# however become plugins don't have that information currently
|
||||
b_prompts = [to_bytes(p) for p in self.get_option('prompt_l10n')] or [br'doas \(', br'Password:']
|
||||
b_prompt = b"|".join(b_prompts)
|
||||
|
||||
return bool(re.match(b_prompt, b_output))
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
super(BecomeModule, self).build_become_command(cmd, shell)
|
||||
|
||||
if not cmd:
|
||||
return cmd
|
||||
|
||||
self.prompt = True
|
||||
|
||||
become_exe = self.get_option('become_exe') or self.name
|
||||
|
||||
flags = self.get_option('become_flags') or ''
|
||||
if not self.get_option('become_pass') and '-n' not in flags:
|
||||
flags += ' -n'
|
||||
|
||||
user = self.get_option('become_user') or ''
|
||||
if user:
|
||||
user = '-u %s' % (user)
|
||||
|
||||
success_cmd = self._build_success_command(cmd, shell, noexe=True)
|
||||
executable = getattr(shell, 'executable', shell.SHELL_FAMILY)
|
||||
|
||||
return '%s %s %s %s -c %s' % (become_exe, flags, user, executable, success_cmd)
|
97
lib/ansible/plugins/become/dzdo.py
Normal file
97
lib/ansible/plugins/become/dzdo.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2018, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
become: dzdo
|
||||
short_description: Centrify's Direct Authorize
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the dzdo utility.
|
||||
author: ansible (@core)
|
||||
version_added: "2.8"
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: dzdo_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_dzdo_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_DZDO_USER
|
||||
become_exe:
|
||||
description: Sudo executable
|
||||
default: dzdo
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: dzdo_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_dzdo_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_DZDO_EXE
|
||||
become_flags:
|
||||
description: Options to pass to dzdo
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: dzdo_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_dzdo_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_DZDO_FLAGS
|
||||
become_pass:
|
||||
description: Options to pass to dzdo
|
||||
required: False
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_dzdo_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_DZDO_PASS
|
||||
ini:
|
||||
- section: dzdo_become_plugin
|
||||
key: password
|
||||
"""
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = 'dzdo'
|
||||
|
||||
# messages for detecting prompted password issues
|
||||
fail = ('Sorry, try again.',)
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
super(BecomeModule, self).build_become_command(cmd, shell)
|
||||
|
||||
if not cmd:
|
||||
return cmd
|
||||
|
||||
becomecmd = self.get_option('become_exe') or self.name
|
||||
|
||||
flags = self.get_option('become_flags') or ''
|
||||
if self.get_option('become_pass'):
|
||||
self._prompt = '[dzdo via ansible, key=%s] password:' % self._id
|
||||
flags = '%s -p "%s"' % (flags.replace('-n', ''), self._prompt)
|
||||
|
||||
user = self.get_option('become_user') or ''
|
||||
if user:
|
||||
user = '-u %s' % (user)
|
||||
|
||||
return ' '.join([becomecmd, flags, user, self._build_success_command(cmd, shell)])
|
120
lib/ansible/plugins/become/ksu.py
Normal file
120
lib/ansible/plugins/become/ksu.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
become: ksu
|
||||
short_description: Kerberos substitute user
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the ksu utility.
|
||||
author: ansible (@core)
|
||||
version_added: "2.8"
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: ksu_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_ksu_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_KSU_USER
|
||||
required: True
|
||||
become_exe:
|
||||
description: Su executable
|
||||
default: ksu
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: ksu_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_ksu_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_KSU_EXE
|
||||
become_flags:
|
||||
description: Options to pass to ksu
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: ksu_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_ksu_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_KSU_FLAGS
|
||||
become_pass:
|
||||
description: ksu password
|
||||
required: False
|
||||
vars:
|
||||
- name: ansible_ksu_pass
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_become_password
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_KSU_PASS
|
||||
ini:
|
||||
- section: ksu_become_plugin
|
||||
key: password
|
||||
prompt_l10n:
|
||||
description:
|
||||
- List of localized strings to match for prompt detection
|
||||
- If empty we'll use the built in one
|
||||
default: []
|
||||
ini:
|
||||
- section: ksu_become_plugin
|
||||
key: localized_prompts
|
||||
vars:
|
||||
- name: ansible_ksu_prompt_l10n
|
||||
env:
|
||||
- name: ANSIBLE_KSU_PROMPT_L10N
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = 'ksu'
|
||||
|
||||
# messages for detecting prompted password issues
|
||||
fail = ('Password incorrect',)
|
||||
missing = ('No password given',)
|
||||
|
||||
def check_password_prompt(self, b_output):
|
||||
''' checks if the expected passwod prompt exists in b_output '''
|
||||
|
||||
prompts = self.get_option('prompt_l10n') or ["Kerberos password for .*@.*:"]
|
||||
b_prompt = b"|".join(to_bytes(p) for p in prompts)
|
||||
|
||||
return bool(re.match(b_prompt, b_output))
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
|
||||
super(BecomeModule, self).build_become_command(cmd, shell)
|
||||
|
||||
# Prompt handling for ``ksu`` is more complicated, this
|
||||
# is used to satisfy the connection plugin
|
||||
self.prompt = True
|
||||
|
||||
if not cmd:
|
||||
return cmd
|
||||
|
||||
exe = self.get_option('become_exe') or self.name
|
||||
flags = self.get_option('become_flags') or ''
|
||||
user = self.get_option('become_user') or ''
|
||||
return '%s %s %s -e %s ' % (exe, user, flags, self._build_success_command(cmd, shell))
|
87
lib/ansible/plugins/become/machinectl.py
Normal file
87
lib/ansible/plugins/become/machinectl.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
become: machinectl
|
||||
short_description: Systemd's machinectl privilege escalation
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the machinectl utility.
|
||||
author: ansible (@core)
|
||||
version_added: "2.8"
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: machinectl_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_machinectl_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_MACHINECTL_USER
|
||||
become_exe:
|
||||
description: Machinectl executable
|
||||
default: machinectl
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: machinectl_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_machinectl_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_MACHINECTL_EXE
|
||||
become_flags:
|
||||
description: Options to pass to machinectl
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: machinectl_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_machinectl_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_MACHINECTL_FLAGS
|
||||
become_pass:
|
||||
description: Password for machinectl
|
||||
required: False
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_machinectl_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_MACHINECTL_PASS
|
||||
ini:
|
||||
- section: machinectl_become_plugin
|
||||
key: password
|
||||
"""
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = 'machinectl'
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
super(BecomeModule, self).build_become_command(cmd, shell)
|
||||
|
||||
if not cmd:
|
||||
return cmd
|
||||
|
||||
become = self._get_option('become_exe') or self.name
|
||||
flags = self.get_option('flags') or ''
|
||||
user = self.get_option('become_user') or ''
|
||||
return '%s shell -q %s %s@ %s' % (become, flags, user, cmd)
|
103
lib/ansible/plugins/become/pbrun.py
Normal file
103
lib/ansible/plugins/become/pbrun.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
become: pbrun
|
||||
short_description: PowerBroker run
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the pbrun utility.
|
||||
author: ansible (@core)
|
||||
version_added: "2.8"
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: pbrun_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_pbrun_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_PBRUN_USER
|
||||
become_exe:
|
||||
description: Sudo executable
|
||||
default: pbrun
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: pbrun_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_pbrun_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_PBRUN_EXE
|
||||
become_flags:
|
||||
description: Options to pass to pbrun
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: pbrun_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_pbrun_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_PBRUN_FLAGS
|
||||
become_pass:
|
||||
description: Password for pbrun
|
||||
required: False
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_pbrun_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_PBRUN_PASS
|
||||
ini:
|
||||
- section: pbrun_become_plugin
|
||||
key: password
|
||||
wrap_exe:
|
||||
description: Toggle to wrap the command pbrun calls in 'shell -c' or not
|
||||
default: False
|
||||
type: bool
|
||||
ini:
|
||||
- section: pbrun_become_plugin
|
||||
key: wrap_execution
|
||||
vars:
|
||||
- name: ansible_pbrun_wrap_execution
|
||||
env:
|
||||
- name: ANSIBLE_PBRUN_WRAP_EXECUTION
|
||||
"""
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = 'pbrun'
|
||||
|
||||
prompt = 'Password:'
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
super(BecomeModule, self).build_become_command(cmd, shell)
|
||||
|
||||
if not cmd:
|
||||
return cmd
|
||||
|
||||
become_exe = self.get_option('become_exe') or self.name
|
||||
flags = self.get_option('become_flags') or ''
|
||||
user = self.get_option('become_user')
|
||||
if user:
|
||||
user = '-u %s' % (user)
|
||||
noexe = not self.get_option('wrap_exe')
|
||||
|
||||
return ' '.join([become_exe, flags, user, self._build_success_command(cmd, shell, noexe=noexe)])
|
103
lib/ansible/plugins/become/pfexec.py
Normal file
103
lib/ansible/plugins/become/pfexec.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
become: pfexec
|
||||
short_description: profile based execution
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the pfexec utility.
|
||||
author: ansible (@core)
|
||||
version_added: "2.8"
|
||||
options:
|
||||
become_user:
|
||||
description:
|
||||
- User you 'become' to execute the task
|
||||
- This plugin ignores this settingas pfexec uses it's own ``exec_attr`` to figure this out,
|
||||
but it is supplied here for Ansible to make decisions needed for the task execution, like file permissions.
|
||||
default: root
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: pfexec_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_pfexec_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_PFEXEC_USER
|
||||
become_exe:
|
||||
description: Sudo executable
|
||||
default: pfexec
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: pfexec_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_pfexec_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_PFEXEC_EXE
|
||||
become_flags:
|
||||
description: Options to pass to pfexec
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: pfexec_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_pfexec_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_PFEXEC_FLAGS
|
||||
become_pass:
|
||||
description: pfexec password
|
||||
required: False
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_pfexec_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_PFEXEC_PASS
|
||||
ini:
|
||||
- section: pfexec_become_plugin
|
||||
key: password
|
||||
wrap_exe:
|
||||
description: Toggle to wrap the command pfexec calls in 'shell -c' or not
|
||||
default: False
|
||||
type: bool
|
||||
ini:
|
||||
- section: pfexec_become_plugin
|
||||
key: wrap_execution
|
||||
vars:
|
||||
- name: ansible_pfexec_wrap_execution
|
||||
env:
|
||||
note:
|
||||
- This plugin ignores ``become_user`` as pfexec uses it's own ``exec_attr`` to figure this out.
|
||||
"""
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = 'pfexec'
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
super(BecomeModule, self).build_become_command(cmd, shell)
|
||||
|
||||
if not cmd:
|
||||
return cmd
|
||||
|
||||
exe = self.get_option('become_exe') or self.name
|
||||
flags = self.get_option('become_flags')
|
||||
noexe = not self.get_option('wrap_exe')
|
||||
return '%s %s "%s"' % (exe, flags, self._build_success_command(cmd, shell, noexe=noexe))
|
75
lib/ansible/plugins/become/pmrun.py
Normal file
75
lib/ansible/plugins/become/pmrun.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
become: pmrun
|
||||
short_description: Privilege Manager run
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the pmrun utility.
|
||||
author: ansible (@core)
|
||||
version_added: "2.8"
|
||||
options:
|
||||
become_exe:
|
||||
description: Sudo executable
|
||||
default: pmrun
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: pmrun_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_pmrun_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_PMRUN_EXE
|
||||
become_flags:
|
||||
description: Options to pass to pmrun
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: pmrun_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_pmrun_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_PMRUN_FLAGS
|
||||
become_pass:
|
||||
description: pmrun password
|
||||
required: False
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_pmrun_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_PMRUN_PASS
|
||||
ini:
|
||||
- section: pmrun_become_plugin
|
||||
key: password
|
||||
notes:
|
||||
- This plugin ignores the become_user supplied and uses pmrun's own configuration to select the user.
|
||||
"""
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = 'pmrun'
|
||||
prompt = 'Enter UPM user password:'
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
super(BecomeModule, self).build_become_command(cmd, shell)
|
||||
|
||||
if not cmd:
|
||||
return cmd
|
||||
|
||||
become = self.get_option('become_exe') or self.name
|
||||
flags = self.get_option('become_flags') or ''
|
||||
return '%s %s %s' % (become, flags, self._build_success_command(cmd, shell))
|
69
lib/ansible/plugins/become/runas.py
Normal file
69
lib/ansible/plugins/become/runas.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
become: runas
|
||||
short_description: Run As user
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the windows runas facility.
|
||||
author: ansible (@core)
|
||||
version_added: "2.8"
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: runas_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_runas_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_RUNAS_USER
|
||||
required: True
|
||||
become_flags:
|
||||
description: Options to pass to runas, a space delimited list of k=v pairs
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: runas_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_runas_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_RUNAS_FLAGS
|
||||
become_pass:
|
||||
description: password
|
||||
ini:
|
||||
- section: runas_become_plugin
|
||||
key: password
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_runas_runas
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_RUNAS_PASS
|
||||
notes:
|
||||
- runas is really implemented in the powershell module handler and as such can only be used with winrm connections.
|
||||
- This plugin ignores the 'become_exe' setting as it uses an API and not an executable.
|
||||
"""
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = 'runas'
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
# runas is implemented inside the winrm connection plugin
|
||||
return cmd
|
90
lib/ansible/plugins/become/sesu.py
Normal file
90
lib/ansible/plugins/become/sesu.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
become: sesu
|
||||
short_description: CA Privilged Access Manager
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the sesu utility.
|
||||
author: ansible (@nekonyuu)
|
||||
version_added: "2.8"
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: sesu_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_sesu_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_SESU_USER
|
||||
become_exe:
|
||||
description: sesu executable
|
||||
default: sesu
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: sesu_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_sesu_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_SESU_EXE
|
||||
become_flags:
|
||||
description: Options to pass to sesu
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: sesu_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_sesu_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_SESU_FLAGS
|
||||
become_pass:
|
||||
description: Password to pass to sesu
|
||||
required: False
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_sesu_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_SESU_PASS
|
||||
ini:
|
||||
- section: sesu_become_plugin
|
||||
key: password
|
||||
"""
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = 'sesu'
|
||||
|
||||
_prompt = 'Please enter your password:'
|
||||
fail = missing = ('Sorry, try again with sesu.',)
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
super(BecomeModule, self).build_become_command(cmd, shell)
|
||||
|
||||
if not cmd:
|
||||
return cmd
|
||||
|
||||
become = self.get_option('become_exe') or self.name
|
||||
flags = self.get_option('become_flags') or ''
|
||||
user = self.get_option('become_user') or ''
|
||||
return '%s %s %s -c %s' % (become, flags, user, self._build_success_command(cmd, shell))
|
158
lib/ansible/plugins/become/su.py
Normal file
158
lib/ansible/plugins/become/su.py
Normal file
|
@ -0,0 +1,158 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
become: su
|
||||
short_description: Substitute User
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the su utility.
|
||||
author: ansible (@core)
|
||||
version_added: "2.8"
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task
|
||||
default: root
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: su_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_su_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_SU_USER
|
||||
become_exe:
|
||||
description: Su executable
|
||||
default: su
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: su_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_su_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_SU_EXE
|
||||
become_flags:
|
||||
description: Options to pass to su
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: su_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_su_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_SU_FLAGS
|
||||
become_pass:
|
||||
description: Password to pass to su
|
||||
required: False
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_su_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_SU_PASS
|
||||
ini:
|
||||
- section: su_become_plugin
|
||||
key: password
|
||||
prompt_l10n:
|
||||
description:
|
||||
- List of localized strings to match for prompt detection
|
||||
- If empty we'll use the built in one
|
||||
default: []
|
||||
ini:
|
||||
- section: su_become_plugin
|
||||
key: localized_prompts
|
||||
vars:
|
||||
- name: ansible_su_prompt_l10n
|
||||
env:
|
||||
- name: ANSIBLE_SU_PROMPT_L10N
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = 'su'
|
||||
|
||||
# messages for detecting prompted password issues
|
||||
fail = ('Authentication failure',)
|
||||
|
||||
SU_PROMPT_LOCALIZATIONS = [
|
||||
'Password',
|
||||
'암호',
|
||||
'パスワード',
|
||||
'Adgangskode',
|
||||
'Contraseña',
|
||||
'Contrasenya',
|
||||
'Hasło',
|
||||
'Heslo',
|
||||
'Jelszó',
|
||||
'Lösenord',
|
||||
'Mật khẩu',
|
||||
'Mot de passe',
|
||||
'Parola',
|
||||
'Parool',
|
||||
'Pasahitza',
|
||||
'Passord',
|
||||
'Passwort',
|
||||
'Salasana',
|
||||
'Sandi',
|
||||
'Senha',
|
||||
'Wachtwoord',
|
||||
'ססמה',
|
||||
'Лозинка',
|
||||
'Парола',
|
||||
'Пароль',
|
||||
'गुप्तशब्द',
|
||||
'शब्दकूट',
|
||||
'సంకేతపదము',
|
||||
'හස්පදය',
|
||||
'密码',
|
||||
'密碼',
|
||||
'口令',
|
||||
]
|
||||
|
||||
def check_password_prompt(self, b_output):
|
||||
''' checks if the expected passwod prompt exists in b_output '''
|
||||
|
||||
prompts = self.get_option('prompt_l10n') or self.SU_PROMPT_LOCALIZATIONS
|
||||
b_password_string = b"|".join((br'(\w+\'s )?' + to_bytes(p)) for p in prompts)
|
||||
# Colon or unicode fullwidth colon
|
||||
b_password_string = b_password_string + to_bytes(u' ?(:|:) ?')
|
||||
b_su_prompt_localizations_re = re.compile(b_password_string, flags=re.IGNORECASE)
|
||||
return bool(b_su_prompt_localizations_re.match(b_output))
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
super(BecomeModule, self).build_become_command(cmd, shell)
|
||||
|
||||
# Prompt handling for ``su`` is more complicated, this
|
||||
# is used to satisfy the connection plugin
|
||||
self.prompt = True
|
||||
|
||||
if not cmd:
|
||||
return cmd
|
||||
|
||||
exe = self.get_option('become_exe') or self.name
|
||||
flags = self.get_option('become_flags') or ''
|
||||
user = self.get_option('become_user') or ''
|
||||
success_cmd = self._build_success_command(cmd, shell)
|
||||
|
||||
return "%s %s %s -c %s" % (exe, flags, user, shlex_quote(success_cmd))
|
104
lib/ansible/plugins/become/sudo.py
Normal file
104
lib/ansible/plugins/become/sudo.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
become: sudo
|
||||
short_description: Substitute User DO
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the sudo utility.
|
||||
author: ansible (@core)
|
||||
version_added: "2.8"
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task
|
||||
default: root
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: sudo_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_sudo_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_SUDO_USER
|
||||
become_exe:
|
||||
description: Sudo executable
|
||||
default: sudo
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: sudo_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_sudo_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_SUDO_EXE
|
||||
become_flags:
|
||||
description: Options to pass to sudo
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: sudo_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_sudo_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_SUDO_FLAGS
|
||||
become_pass:
|
||||
description: Password to pass to sudo
|
||||
required: False
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_sudo_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_SUDO_PASS
|
||||
ini:
|
||||
- section: sudo_become_plugin
|
||||
key: password
|
||||
"""
|
||||
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = 'sudo'
|
||||
|
||||
# messages for detecting prompted password issues
|
||||
fail = ('Sorry, try again.',)
|
||||
missing = ('Sorry, a password is required to run sudo', 'sudo: a password is required')
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
super(BecomeModule, self).build_become_command(cmd, shell)
|
||||
|
||||
if not cmd:
|
||||
return cmd
|
||||
|
||||
becomecmd = self.get_option('become_exe') or self.name
|
||||
|
||||
flags = self.get_option('become_flags') or ''
|
||||
prompt = ''
|
||||
if self.get_option('become_pass'):
|
||||
self.prompt = '[sudo via ansible, key=%s] password:' % self._id
|
||||
if flags: # this could be simplified, but kept as is for now for backwards string matching
|
||||
flags = flags.replace('-n', '')
|
||||
prompt = '-p "%s"' % (self.prompt)
|
||||
|
||||
user = self.get_option('become_user') or ''
|
||||
if user:
|
||||
user = '-u %s' % (user)
|
||||
|
||||
return ' '.join([becomecmd, flags, prompt, user, self._build_success_command(cmd, shell)])
|
|
@ -6,19 +6,16 @@ from __future__ import (absolute_import, division, print_function)
|
|||
__metaclass__ = type
|
||||
|
||||
import fcntl
|
||||
import gettext
|
||||
import os
|
||||
import shlex
|
||||
from abc import abstractmethod, abstractproperty
|
||||
from functools import wraps
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.plugins import AnsiblePlugin
|
||||
from ansible.plugins.loader import shell_loader, connection_loader
|
||||
from ansible.utils.display import Display
|
||||
from ansible.plugins.loader import connection_loader, get_shell_plugin
|
||||
from ansible.utils.path import unfrackpath
|
||||
|
||||
display = Display()
|
||||
|
@ -46,7 +43,7 @@ class ConnectionBase(AnsiblePlugin):
|
|||
has_pipelining = False
|
||||
has_native_async = False # eg, winrm
|
||||
always_pipeline_modules = False # eg, winrm
|
||||
become_methods = C.BECOME_METHODS
|
||||
has_tty = True # for interacting with become plugins
|
||||
# When running over this connection type, prefer modules written in a certain language
|
||||
# as discovered by the specified file extension. An empty string as the
|
||||
# language means any language.
|
||||
|
@ -66,11 +63,12 @@ class ConnectionBase(AnsiblePlugin):
|
|||
|
||||
# All these hasattrs allow subclasses to override these parameters
|
||||
if not hasattr(self, '_play_context'):
|
||||
# Backwards compat: self._play_context isn't really needed, using set_options/get_option
|
||||
self._play_context = play_context
|
||||
if not hasattr(self, '_new_stdin'):
|
||||
self._new_stdin = new_stdin
|
||||
# Backwards compat: self._display isn't really needed, just import the global display and use that.
|
||||
if not hasattr(self, '_display'):
|
||||
# Backwards compat: self._display isn't really needed, just import the global display and use that.
|
||||
self._display = display
|
||||
if not hasattr(self, '_connected'):
|
||||
self._connected = False
|
||||
|
@ -80,30 +78,17 @@ class ConnectionBase(AnsiblePlugin):
|
|||
self._connected = False
|
||||
self._socket_path = None
|
||||
|
||||
if shell is not None:
|
||||
self._shell = shell
|
||||
# helper plugins
|
||||
self._shell = shell
|
||||
|
||||
# load the shell plugin for this action/connection
|
||||
if play_context.shell:
|
||||
shell_type = play_context.shell
|
||||
elif hasattr(self, '_shell_type'):
|
||||
shell_type = getattr(self, '_shell_type')
|
||||
else:
|
||||
shell_type = 'sh'
|
||||
shell_filename = os.path.basename(self._play_context.executable)
|
||||
try:
|
||||
shell = shell_loader.get(shell_filename)
|
||||
except Exception:
|
||||
shell = None
|
||||
if shell is None:
|
||||
for shell in shell_loader.all():
|
||||
if shell_filename in shell.COMPATIBLE_SHELLS:
|
||||
break
|
||||
shell_type = shell.SHELL_FAMILY
|
||||
|
||||
self._shell = shell_loader.get(shell_type)
|
||||
# we always must have shell
|
||||
if not self._shell:
|
||||
raise AnsibleError("Invalid shell type specified (%s), or the plugin for that shell type is missing." % shell_type)
|
||||
self._shell = get_shell_plugin(shell_type=getattr(self, '_shell_type', None), executable=self._play_context.executable)
|
||||
|
||||
self.become = None
|
||||
|
||||
def set_become_plugin(self, plugin):
|
||||
self.become = plugin
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
|
@ -115,14 +100,6 @@ class ConnectionBase(AnsiblePlugin):
|
|||
'''Read-only property holding the connection socket path for this remote host'''
|
||||
return self._socket_path
|
||||
|
||||
def _become_method_supported(self):
|
||||
''' Checks if the current class supports this privilege escalation method '''
|
||||
|
||||
if self._play_context.become_method in self.become_methods:
|
||||
return True
|
||||
|
||||
raise AnsibleError("Internal Error: this connection module does not support running commands via %s" % self._play_context.become_method)
|
||||
|
||||
@staticmethod
|
||||
def _split_ssh_args(argstring):
|
||||
"""
|
||||
|
@ -151,10 +128,6 @@ class ConnectionBase(AnsiblePlugin):
|
|||
def _connect(self):
|
||||
"""Connect to the host we've been initialized with"""
|
||||
|
||||
# Check if PE is supported
|
||||
if self._play_context.become:
|
||||
self._become_method_supported()
|
||||
|
||||
@ensure_connect
|
||||
@abstractmethod
|
||||
def exec_command(self, cmd, in_data=None, sudoable=True):
|
||||
|
@ -240,31 +213,6 @@ class ConnectionBase(AnsiblePlugin):
|
|||
"""Terminate the connection"""
|
||||
pass
|
||||
|
||||
def check_become_success(self, b_output):
|
||||
b_success_key = to_bytes(self._play_context.success_key)
|
||||
for b_line in b_output.splitlines(True):
|
||||
if b_success_key == b_line.rstrip():
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_password_prompt(self, b_output):
|
||||
if self._play_context.prompt is None:
|
||||
return False
|
||||
elif isinstance(self._play_context.prompt, string_types):
|
||||
b_prompt = to_bytes(self._play_context.prompt).strip()
|
||||
b_lines = b_output.splitlines()
|
||||
return any(l.strip().startswith(b_prompt) for l in b_lines)
|
||||
else:
|
||||
return self._play_context.prompt(b_output)
|
||||
|
||||
def check_incorrect_password(self, b_output):
|
||||
b_incorrect_password = to_bytes(gettext.dgettext(self._play_context.become_method, C.BECOME_ERROR_STRINGS[self._play_context.become_method]))
|
||||
return b_incorrect_password and b_incorrect_password in b_output
|
||||
|
||||
def check_missing_password(self, b_output):
|
||||
b_missing_password = to_bytes(gettext.dgettext(self._play_context.become_method, C.BECOME_MISSING_STRINGS[self._play_context.become_method]))
|
||||
return b_missing_password and b_missing_password in b_output
|
||||
|
||||
def connection_lock(self):
|
||||
f = self._play_context.connection_lockfd
|
||||
display.vvvv('CONNECTION: pid %d waiting for lock on %d' % (os.getpid(), f), host=self._play_context.remote_addr)
|
||||
|
@ -279,6 +227,39 @@ class ConnectionBase(AnsiblePlugin):
|
|||
def reset(self):
|
||||
display.warning("Reset is not implemented for this connection")
|
||||
|
||||
# NOTE: these password functions are all become specific, the name is
|
||||
# confusing as it does not handle 'protocol passwords'
|
||||
# DEPRECATED:
|
||||
# These are kept for backwards compatiblity
|
||||
# Use the methods provided by the become plugins instead
|
||||
def check_become_success(self, b_output):
|
||||
display.deprecated(
|
||||
"Connection.check_become_success is deprecated, calling code should be using become plugins instead",
|
||||
version="2.12"
|
||||
)
|
||||
return self.become.check_success(b_output)
|
||||
|
||||
def check_password_prompt(self, b_output):
|
||||
display.deprecated(
|
||||
"Connection.check_password_prompt is deprecated, calling code should be using become plugins instead",
|
||||
version="2.12"
|
||||
)
|
||||
return self.become.check_password_prompt(b_output)
|
||||
|
||||
def check_incorrect_password(self, b_output):
|
||||
display.deprecated(
|
||||
"Connection.check_incorrect_password is deprecated, calling code should be using become plugins instead",
|
||||
version="2.12"
|
||||
)
|
||||
return self.become.check_incorrect_password(b_output)
|
||||
|
||||
def check_missing_password(self, b_output):
|
||||
display.deprecated(
|
||||
"Connection.check_missing_password is deprecated, calling code should be using become plugins instead",
|
||||
version="2.12"
|
||||
)
|
||||
return self.become.check_missing_password(b_output)
|
||||
|
||||
|
||||
class NetworkConnectionBase(ConnectionBase):
|
||||
"""
|
||||
|
|
|
@ -63,7 +63,6 @@ class Connection(ConnectionBase):
|
|||
# String used to identify this Connection class from other classes
|
||||
transport = 'buildah'
|
||||
has_pipelining = True
|
||||
become_methods = frozenset(C.BECOME_METHODS)
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
|
|
@ -59,7 +59,7 @@ class Connection(ConnectionBase):
|
|||
# su currently has an undiagnosed issue with calculating the file
|
||||
# checksums (so copy, for instance, doesn't work right)
|
||||
# Have to look into that before re-enabling this
|
||||
become_methods = frozenset(C.BECOME_METHODS).difference(('su',))
|
||||
has_tty = False
|
||||
|
||||
default_user = 'root'
|
||||
|
||||
|
|
|
@ -62,7 +62,6 @@ class Connection(ConnectionBase):
|
|||
|
||||
transport = 'docker'
|
||||
has_pipelining = True
|
||||
become_methods = frozenset(C.BECOME_METHODS)
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
|
|
@ -56,8 +56,7 @@ class Connection(ConnectionBase):
|
|||
# Pipelining may work. Someone needs to test by setting this to True and
|
||||
# having pipelining=True in their ansible.cfg
|
||||
has_pipelining = True
|
||||
|
||||
become_methods = frozenset(C.BECOME_METHODS)
|
||||
has_tty = False
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
|
|
@ -198,7 +198,6 @@ class Connection(ConnectionBase):
|
|||
connection_options = CONNECTION_OPTIONS
|
||||
documentation = DOCUMENTATION
|
||||
has_pipelining = True
|
||||
become_methods = frozenset(C.BECOME_METHODS)
|
||||
transport_cmd = None
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
|
|
|
@ -49,8 +49,8 @@ class Connection(ConnectionBase):
|
|||
# su currently has an undiagnosed issue with calculating the file
|
||||
# checksums (so copy, for instance, doesn't work right)
|
||||
# Have to look into that before re-enabling this
|
||||
become_methods = frozenset(C.BECOME_METHODS).difference(('su',))
|
||||
default_user = 'root'
|
||||
has_tty = False
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
|
|
@ -83,7 +83,7 @@ class Connection(ConnectionBase):
|
|||
)
|
||||
display.debug("done running command with Popen()")
|
||||
|
||||
if self._play_context.prompt and sudoable:
|
||||
if self.become and self.become.expect_prompt() and sudoable:
|
||||
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK)
|
||||
fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK)
|
||||
selector = selectors.DefaultSelector()
|
||||
|
|
|
@ -54,7 +54,6 @@ class Connection(ConnectionBase):
|
|||
|
||||
transport = 'lxc'
|
||||
has_pipelining = True
|
||||
become_methods = frozenset(C.BECOME_METHODS)
|
||||
default_user = 'root'
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
|
|
|
@ -156,10 +156,8 @@ class Connection(NetworkConnectionBase):
|
|||
|
||||
def _connect(self):
|
||||
if not HAS_NAPALM:
|
||||
raise AnsibleError(
|
||||
'Napalm is required to use the napalm connection type.\n'
|
||||
'Please run pip install napalm'
|
||||
)
|
||||
raise AnsibleError('The "napalm" python library is required to use the napalm connection type.\n')
|
||||
|
||||
super(Connection, self)._connect()
|
||||
|
||||
if not self.connected:
|
||||
|
|
|
@ -274,7 +274,7 @@ class Connection(NetworkConnectionBase):
|
|||
def _connect(self):
|
||||
if not HAS_NCCLIENT:
|
||||
raise AnsibleError(
|
||||
'ncclient is required to use the netconf connection type: %s.\n'
|
||||
'The required "ncclient" python library is required to use the netconf connection type: %s.\n'
|
||||
'Please run pip install ncclient' % to_native(NCCLIENT_IMP_ERR)
|
||||
)
|
||||
|
||||
|
|
|
@ -415,7 +415,7 @@ class Connection(ConnectionBase):
|
|||
|
||||
try:
|
||||
chan.exec_command(cmd)
|
||||
if self._play_context.prompt:
|
||||
if self.become and self.become.expect_prompt():
|
||||
passprompt = False
|
||||
become_sucess = False
|
||||
while not (become_sucess or passprompt):
|
||||
|
|
|
@ -218,7 +218,6 @@ class Connection(ConnectionBase):
|
|||
|
||||
transport = 'psrp'
|
||||
module_implementation_preferences = ('.ps1', '.exe', '')
|
||||
become_methods = ['runas']
|
||||
allow_executable = False
|
||||
has_pipelining = True
|
||||
allow_extras = True
|
||||
|
|
|
@ -61,7 +61,6 @@ class Connection(ConnectionBase):
|
|||
# String used to identify this Connection class from other classes
|
||||
transport = 'qubes'
|
||||
has_pipelining = True
|
||||
become_methods = frozenset(C.BECOME_METHODS)
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
|
|
@ -443,7 +443,6 @@ class Connection(ConnectionBase):
|
|||
|
||||
transport = 'ssh'
|
||||
has_pipelining = True
|
||||
become_methods = frozenset(C.BECOME_METHODS).difference(['runas'])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Connection, self).__init__(*args, **kwargs)
|
||||
|
@ -708,11 +707,11 @@ class Connection(ConnectionBase):
|
|||
suppress_output = False
|
||||
|
||||
# display.debug("Examining line (source=%s, state=%s): '%s'" % (source, state, display_line))
|
||||
if self._play_context.prompt and self.check_password_prompt(b_line):
|
||||
if self.become.expect_prompt() and self.check_password_prompt(b_line):
|
||||
display.debug("become_prompt: (source=%s, state=%s): '%s'" % (source, state, display_line))
|
||||
self._flags['become_prompt'] = True
|
||||
suppress_output = True
|
||||
elif self._play_context.success_key and self.check_become_success(b_line):
|
||||
elif self.become.success and self.check_become_success(b_line):
|
||||
display.debug("become_success: (source=%s, state=%s): '%s'" % (source, state, display_line))
|
||||
self._flags['become_success'] = True
|
||||
suppress_output = True
|
||||
|
@ -811,11 +810,12 @@ class Connection(ConnectionBase):
|
|||
|
||||
state = states.index('ready_to_send')
|
||||
if to_bytes(self.get_option('ssh_executable')) in cmd and sudoable:
|
||||
if self._play_context.prompt:
|
||||
prompt = getattr(self.become, 'prompt', None)
|
||||
if prompt:
|
||||
# We're requesting escalation with a password, so we have to
|
||||
# wait for a password prompt.
|
||||
state = states.index('awaiting_prompt')
|
||||
display.debug(u'Initial state: %s: %s' % (states[state], self._play_context.prompt))
|
||||
display.debug(u'Initial state: %s: %s' % (states[state], prompt))
|
||||
elif self._play_context.become and self._play_context.success_key:
|
||||
# We're requesting escalation without a password, so we have to
|
||||
# detect success/failure before sending any initial data.
|
||||
|
|
|
@ -176,7 +176,6 @@ class Connection(ConnectionBase):
|
|||
|
||||
transport = 'winrm'
|
||||
module_implementation_preferences = ('.ps1', '.exe', '')
|
||||
become_methods = ['runas']
|
||||
allow_executable = False
|
||||
has_pipelining = True
|
||||
allow_extras = True
|
||||
|
@ -207,10 +206,6 @@ class Connection(ConnectionBase):
|
|||
self._winrm_user = self.get_option('remote_user')
|
||||
self._winrm_pass = self._play_context.password
|
||||
|
||||
self._become_method = self._play_context.become_method
|
||||
self._become_user = self._play_context.become_user
|
||||
self._become_pass = self._play_context.become_pass
|
||||
|
||||
self._winrm_port = self.get_option('port')
|
||||
|
||||
self._winrm_scheme = self.get_option('scheme')
|
||||
|
|
|
@ -47,7 +47,7 @@ class Connection(ConnectionBase):
|
|||
|
||||
transport = 'zone'
|
||||
has_pipelining = True
|
||||
become_methods = frozenset(C.BECOME_METHODS).difference(('su',))
|
||||
has_tty = False
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
|
|
@ -18,13 +18,15 @@ from collections import defaultdict
|
|||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils._text import to_bytes, to_native, to_text
|
||||
from ansible.module_utils._text import to_bytes, to_text, to_native
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.parsing.utils.yaml import from_yaml
|
||||
from ansible.parsing.yaml.loader import AnsibleLoader
|
||||
from ansible.plugins import get_plugin_class, MODULE_CACHE, PATH_CACHE, PLUGIN_PATH_CACHE
|
||||
from ansible.utils.display import Display
|
||||
from ansible.utils.plugin_docs import add_fragments
|
||||
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
|
@ -32,6 +34,52 @@ def get_all_plugin_loaders():
|
|||
return [(name, obj) for (name, obj) in globals().items() if isinstance(obj, PluginLoader)]
|
||||
|
||||
|
||||
def add_all_plugin_dirs(path):
|
||||
''' add any existing plugin dirs in the path provided '''
|
||||
b_path = to_bytes(path, errors='surrogate_or_strict')
|
||||
if os.path.isdir(b_path):
|
||||
for name, obj in get_all_plugin_loaders():
|
||||
if obj.subdir:
|
||||
plugin_path = os.path.join(b_path, to_bytes(obj.subdir))
|
||||
if os.path.isdir(plugin_path):
|
||||
obj.add_directory(to_text(plugin_path))
|
||||
else:
|
||||
display.warning("Ignoring invalid path provided to plugin path: %s is not a directory" % to_native(path))
|
||||
|
||||
|
||||
def get_shell_plugin(shell_type=None, executable=None):
|
||||
|
||||
if not shell_type:
|
||||
# default to sh
|
||||
shell_type = 'sh'
|
||||
|
||||
# mostly for backwards compat
|
||||
if executable:
|
||||
if isinstance(executable, string_types):
|
||||
shell_filename = os.path.basename(executable)
|
||||
try:
|
||||
shell = shell_loader.get(shell_filename)
|
||||
except Exception:
|
||||
shell = None
|
||||
|
||||
if shell is None:
|
||||
for shell in shell_loader.all():
|
||||
if shell_filename in shell.COMPATIBLE_SHELLS:
|
||||
shell_type = shell.SHELL_FAMILY
|
||||
break
|
||||
else:
|
||||
raise AnsibleError("Either a shell type or a shell executable must be provided ")
|
||||
|
||||
shell = shell_loader.get(shell_type)
|
||||
if not shell:
|
||||
raise AnsibleError("Could not find the shell plugin required (%s)." % shell_type)
|
||||
|
||||
if executable:
|
||||
setattr(shell, 'executable', executable)
|
||||
|
||||
return shell
|
||||
|
||||
|
||||
class PluginLoader:
|
||||
'''
|
||||
PluginLoader loads plugins from the configured plugin directories.
|
||||
|
@ -394,6 +442,7 @@ class PluginLoader:
|
|||
return None
|
||||
|
||||
self._display_plugin_load(self.class_name, name, self._searched_paths, path, found_in_cache=found_in_cache, class_only=class_only)
|
||||
|
||||
if not class_only:
|
||||
try:
|
||||
obj = obj(*args, **kwargs)
|
||||
|
@ -513,6 +562,7 @@ class PluginLoader:
|
|||
continue
|
||||
|
||||
self._display_plugin_load(self.class_name, basename, self._searched_paths, path, found_in_cache=found_in_cache, class_only=class_only)
|
||||
|
||||
if not class_only:
|
||||
try:
|
||||
obj = obj(*args, **kwargs)
|
||||
|
@ -774,3 +824,10 @@ httpapi_loader = PluginLoader(
|
|||
'httpapi_plugins',
|
||||
required_base_class='HttpApiBase',
|
||||
)
|
||||
|
||||
become_loader = PluginLoader(
|
||||
'BecomeModule',
|
||||
'ansible.plugins.become',
|
||||
C.DEFAULT_BECOME_PLUGIN_PATH,
|
||||
'become_plugins'
|
||||
)
|
||||
|
|
|
@ -46,6 +46,7 @@ class ShellBase(AnsiblePlugin):
|
|||
'LC_MESSAGES': module_locale}
|
||||
|
||||
self.tmpdir = None
|
||||
self.executable = None
|
||||
|
||||
def _normalize_system_tmpdirs(self):
|
||||
# Normalize the tmp directory strings. We don't use expanduser/expandvars because those
|
||||
|
@ -65,15 +66,20 @@ class ShellBase(AnsiblePlugin):
|
|||
|
||||
super(ShellBase, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
||||
|
||||
# set env
|
||||
self.env.update(self.get_option('environment'))
|
||||
# set env if needed, deal with environment's 'dual nature' list of dicts or dict
|
||||
env = self.get_option('environment')
|
||||
if isinstance(env, list):
|
||||
for env_dict in env:
|
||||
self.env.update(env_dict)
|
||||
else:
|
||||
self.env.update(env)
|
||||
|
||||
# We can remove the try: except in the future when we make ShellBase a proper subset of
|
||||
# *all* shells. Right now powershell and third party shells which do not use the
|
||||
# shell_common documentation fragment (and so do not have system_tmpdirs) will fail
|
||||
try:
|
||||
self._normalize_system_tmpdirs()
|
||||
except AnsibleError:
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def env_prefix(self, **kwargs):
|
||||
|
|
|
@ -30,6 +30,10 @@ class ShellModule(ShellBase):
|
|||
# Family of shells this has. Must match the filename without extension
|
||||
SHELL_FAMILY = 'sh'
|
||||
|
||||
# commonly used
|
||||
ECHO = 'echo'
|
||||
COMMAND_SEP = ';'
|
||||
|
||||
# How to end lines in a python script one-liner
|
||||
_SHELL_EMBEDDED_PY_EOL = '\n'
|
||||
_SHELL_REDIRECT_ALLNULL = '> /dev/null 2>&1'
|
||||
|
|
|
@ -880,7 +880,7 @@ class StrategyBase:
|
|||
|
||||
host_results = []
|
||||
for host in notified_hosts:
|
||||
if not iterator.is_failed(host) or play_context.force_handlers:
|
||||
if not iterator.is_failed(host) or iterator._play.force_handlers:
|
||||
task_vars = self._variable_manager.get_vars(play=iterator._play, host=host, task=handler)
|
||||
self.add_tqm_variables(task_vars, play=iterator._play)
|
||||
self._queue_task(host, handler, task_vars, play_context)
|
||||
|
@ -1061,7 +1061,7 @@ class StrategyBase:
|
|||
del self._active_connections[target_host]
|
||||
else:
|
||||
connection = connection_loader.get(play_context.connection, play_context, os.devnull)
|
||||
play_context.set_options_from_plugin(connection)
|
||||
play_context.set_attributes_from_plugin(connection)
|
||||
|
||||
if connection:
|
||||
try:
|
||||
|
|
|
@ -235,7 +235,7 @@ class StrategyModule(StrategyBase):
|
|||
|
||||
for new_block in new_blocks:
|
||||
task_vars = self._variable_manager.get_vars(play=iterator._play, task=new_block._parent)
|
||||
final_block = new_block.filter_tagged_tasks(play_context, task_vars)
|
||||
final_block = new_block.filter_tagged_tasks(task_vars)
|
||||
for host in hosts_left:
|
||||
if host in included_file._hosts:
|
||||
all_blocks[host].append(final_block)
|
||||
|
|
|
@ -365,7 +365,7 @@ class StrategyModule(StrategyBase):
|
|||
task=new_block._parent
|
||||
)
|
||||
display.debug("filtering new block on tags")
|
||||
final_block = new_block.filter_tagged_tasks(play_context, task_vars)
|
||||
final_block = new_block.filter_tagged_tasks(task_vars)
|
||||
display.debug("done filtering new block on tags")
|
||||
|
||||
noop_block = self._prepare_and_create_noop_block_from(final_block, task._parent, iterator)
|
||||
|
|
|
@ -20,7 +20,6 @@ class Connection(ConnectionBase):
|
|||
|
||||
transport = 'dummy'
|
||||
has_pipelining = True
|
||||
become_methods = frozenset(C.BECOME_METHODS)
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
|
|
@ -7,20 +7,17 @@
|
|||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
|
||||
import re
|
||||
import pytest
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible import context
|
||||
from ansible.cli.arguments import optparse_helpers as opt_help
|
||||
from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.playbook.play_context import PlayContext
|
||||
from ansible.playbook.play import Play
|
||||
from ansible.plugins.loader import become_loader
|
||||
from ansible.utils import context_objects as co
|
||||
from units.compat import unittest
|
||||
|
||||
from units.mock.loader import DictDataLoader
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -51,8 +48,13 @@ def test_play_context(mocker, parser, reset_cli_args):
|
|||
(options, args) = parser.parse_args(['-vv', '--check'])
|
||||
options.args = args
|
||||
context._init_global_context(options)
|
||||
play_context = PlayContext()
|
||||
play = Play.load({})
|
||||
play_context = PlayContext(play=play)
|
||||
|
||||
# Note: **Must** test the value from _attributes here because play_context.connection will end
|
||||
# up calling PlayContext._get_attr_connection() which changes the 'smart' connection type into
|
||||
# the best guessed type (and since C.DEFAULT_TRANSPORT starts off as smart, we would then never
|
||||
# match)
|
||||
assert play_context._attributes['connection'] == C.DEFAULT_TRANSPORT
|
||||
assert play_context.remote_addr is None
|
||||
assert play_context.remote_user is None
|
||||
|
@ -65,28 +67,11 @@ def test_play_context(mocker, parser, reset_cli_args):
|
|||
assert play_context.check_mode is True
|
||||
assert play_context.no_log is None
|
||||
|
||||
mock_play = mocker.MagicMock()
|
||||
mock_play.connection = 'mock'
|
||||
mock_play.remote_user = 'mock'
|
||||
mock_play.port = 1234
|
||||
mock_play.become = True
|
||||
mock_play.become_method = 'mock'
|
||||
mock_play.become_user = 'mockroot'
|
||||
mock_play.no_log = True
|
||||
|
||||
play_context = PlayContext(play=mock_play)
|
||||
assert play_context.connection == 'mock'
|
||||
assert play_context.remote_user == 'mock'
|
||||
assert play_context.password == ''
|
||||
assert play_context.port == 1234
|
||||
assert play_context.become is True
|
||||
assert play_context.become_method == "mock"
|
||||
assert play_context.become_user == "mockroot"
|
||||
|
||||
mock_task = mocker.MagicMock()
|
||||
mock_task.connection = 'mocktask'
|
||||
mock_task.remote_user = 'mocktask'
|
||||
mock_task.no_log = mock_play.no_log
|
||||
mock_task.port = 1234
|
||||
mock_task.no_log = True
|
||||
mock_task.become = True
|
||||
mock_task.become_method = 'mocktask'
|
||||
mock_task.become_user = 'mocktaskroot'
|
||||
|
@ -101,7 +86,7 @@ def test_play_context(mocker, parser, reset_cli_args):
|
|||
|
||||
mock_templar = mocker.MagicMock()
|
||||
|
||||
play_context = PlayContext(play=mock_play)
|
||||
play_context = PlayContext()
|
||||
play_context = play_context.set_task_and_variable_override(task=mock_task, variables=all_vars, templar=mock_templar)
|
||||
|
||||
assert play_context.connection == 'mock_inventory'
|
||||
|
@ -126,16 +111,16 @@ def test_play_context_make_become_cmd(mocker, parser, reset_cli_args):
|
|||
|
||||
default_cmd = "/bin/foo"
|
||||
default_exe = "/bin/bash"
|
||||
sudo_exe = C.DEFAULT_SUDO_EXE or 'sudo'
|
||||
sudo_flags = C.DEFAULT_SUDO_FLAGS
|
||||
su_exe = C.DEFAULT_SU_EXE or 'su'
|
||||
su_flags = C.DEFAULT_SU_FLAGS or ''
|
||||
sudo_exe = 'sudo'
|
||||
sudo_flags = '-H -s -n'
|
||||
su_exe = 'su'
|
||||
su_flags = ''
|
||||
pbrun_exe = 'pbrun'
|
||||
pbrun_flags = ''
|
||||
pfexec_exe = 'pfexec'
|
||||
pfexec_flags = ''
|
||||
doas_exe = 'doas'
|
||||
doas_flags = ' -n -u foo '
|
||||
doas_flags = '-n'
|
||||
ksu_exe = 'ksu'
|
||||
ksu_flags = ''
|
||||
dzdo_exe = 'dzdo'
|
||||
|
@ -144,54 +129,64 @@ def test_play_context_make_become_cmd(mocker, parser, reset_cli_args):
|
|||
cmd = play_context.make_become_cmd(cmd=default_cmd, executable=default_exe)
|
||||
assert cmd == default_cmd
|
||||
|
||||
success = 'BECOME-SUCCESS-.+?'
|
||||
|
||||
play_context.become = True
|
||||
play_context.become_user = 'foo'
|
||||
|
||||
play_context.become_method = 'sudo'
|
||||
play_context.set_become_plugin(become_loader.get('sudo'))
|
||||
play_context.become_flags = sudo_flags
|
||||
cmd = play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash")
|
||||
assert (cmd == """%s %s -u %s %s -c 'echo %s; %s'""" % (sudo_exe, sudo_flags, play_context.become_user,
|
||||
default_exe, play_context.success_key, default_cmd))
|
||||
|
||||
assert (re.match("""%s %s -u %s %s -c 'echo %s; %s'""" % (sudo_exe, sudo_flags, play_context.become_user,
|
||||
default_exe, success, default_cmd), cmd) is not None)
|
||||
|
||||
play_context.become_pass = 'testpass'
|
||||
cmd = play_context.make_become_cmd(cmd=default_cmd, executable=default_exe)
|
||||
assert (cmd == """%s %s -p "%s" -u %s %s -c 'echo %s; %s'""" % (sudo_exe, sudo_flags.replace('-n', ''),
|
||||
play_context.prompt, play_context.become_user, default_exe,
|
||||
play_context.success_key, default_cmd))
|
||||
assert (re.match("""%s %s -p "%s" -u %s %s -c 'echo %s; %s'""" % (sudo_exe, sudo_flags.replace('-n', ''),
|
||||
r"\[sudo via ansible, key=.+?\] password:", play_context.become_user,
|
||||
default_exe, success, default_cmd), cmd) is not None)
|
||||
|
||||
play_context.become_pass = None
|
||||
play_context.become_method = 'su'
|
||||
play_context.set_become_plugin(become_loader.get('su'))
|
||||
play_context.become_flags = su_flags
|
||||
cmd = play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash")
|
||||
assert (cmd == """%s %s -c '%s -c '"'"'echo %s; %s'"'"''""" % (su_exe, play_context.become_user, default_exe,
|
||||
play_context.success_key, default_cmd))
|
||||
assert (re.match("""%s %s -c '%s -c '"'"'echo %s; %s'"'"''""" % (su_exe, play_context.become_user, default_exe,
|
||||
success, default_cmd), cmd) is not None)
|
||||
|
||||
play_context.become_method = 'pbrun'
|
||||
play_context.set_become_plugin(become_loader.get('pbrun'))
|
||||
play_context.become_flags = pbrun_flags
|
||||
cmd = play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash")
|
||||
assert cmd == """%s %s -u %s 'echo %s; %s'""" % (pbrun_exe, pbrun_flags, play_context.become_user, play_context.success_key, default_cmd)
|
||||
assert re.match("""%s %s -u %s 'echo %s; %s'""" % (pbrun_exe, pbrun_flags, play_context.become_user,
|
||||
success, default_cmd), cmd) is not None
|
||||
|
||||
play_context.become_method = 'pfexec'
|
||||
play_context.set_become_plugin(become_loader.get('pfexec'))
|
||||
play_context.become_flags = pfexec_flags
|
||||
cmd = play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash")
|
||||
assert cmd == '''%s %s "'echo %s; %s'"''' % (pfexec_exe, pfexec_flags, play_context.success_key, default_cmd)
|
||||
assert re.match('''%s %s "'echo %s; %s'"''' % (pfexec_exe, pfexec_flags, success, default_cmd), cmd) is not None
|
||||
|
||||
play_context.become_method = 'doas'
|
||||
play_context.set_become_plugin(become_loader.get('doas'))
|
||||
play_context.become_flags = doas_flags
|
||||
cmd = play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash")
|
||||
assert (cmd == """%s %s %s -c 'echo %s; %s'""" % (doas_exe, doas_flags, default_exe, play_context.success_key, default_cmd))
|
||||
assert (re.match("""%s %s -u %s %s -c 'echo %s; %s'""" % (doas_exe, doas_flags, play_context.become_user, default_exe, success,
|
||||
default_cmd), cmd) is not None)
|
||||
|
||||
play_context.become_method = 'ksu'
|
||||
play_context.set_become_plugin(become_loader.get('ksu'))
|
||||
play_context.become_flags = ksu_flags
|
||||
cmd = play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash")
|
||||
assert (cmd == """%s %s %s -e %s -c 'echo %s; %s'""" % (ksu_exe, play_context.become_user, ksu_flags,
|
||||
default_exe, play_context.success_key, default_cmd))
|
||||
assert (re.match("""%s %s %s -e %s -c 'echo %s; %s'""" % (ksu_exe, play_context.become_user, ksu_flags,
|
||||
default_exe, success, default_cmd), cmd) is not None)
|
||||
|
||||
play_context.become_method = 'bad'
|
||||
play_context.set_become_plugin(become_loader.get('bad'))
|
||||
with pytest.raises(AnsibleError):
|
||||
play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash")
|
||||
|
||||
play_context.become_method = 'dzdo'
|
||||
play_context.set_become_plugin(become_loader.get('dzdo'))
|
||||
play_context.become_flags = dzdo_flags
|
||||
cmd = play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash")
|
||||
assert cmd == """%s %s -u %s %s -c 'echo %s; %s'""" % (dzdo_exe, dzdo_flags, play_context.become_user, default_exe, play_context.success_key, default_cmd)
|
||||
|
||||
assert re.match("""%s %s -u %s %s -c 'echo %s; %s'""" % (dzdo_exe, dzdo_flags, play_context.become_user, default_exe,
|
||||
success, default_cmd), cmd) is not None
|
||||
play_context.become_pass = 'testpass'
|
||||
play_context.become_method = 'dzdo'
|
||||
play_context.set_become_plugin(become_loader.get('dzdo'))
|
||||
cmd = play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash")
|
||||
assert (cmd == """%s %s -p %s -u %s %s -c 'echo %s; %s'""" % (dzdo_exe, dzdo_flags, shlex_quote(play_context.prompt),
|
||||
play_context.become_user, default_exe,
|
||||
play_context.success_key, default_cmd))
|
||||
assert re.match("""%s %s -p %s -u %s %s -c 'echo %s; %s'""" % (dzdo_exe, dzdo_flags, r'\"\[dzdo via ansible, key=.+?\] password:\"',
|
||||
play_context.become_user, default_exe, success, default_cmd), cmd) is not None
|
||||
|
|
|
@ -500,31 +500,31 @@ class TestActionBase(unittest.TestCase):
|
|||
fake_loader = MagicMock()
|
||||
fake_loader.get_basedir.return_value = os.getcwd()
|
||||
play_context = PlayContext()
|
||||
action_base = DerivedActionBase(None, None, play_context, fake_loader, None, None)
|
||||
action_base._connection = MagicMock(exec_command=MagicMock(return_value=(0, '', '')))
|
||||
action_base._connection._shell = MagicMock(append_command=MagicMock(return_value=('JOINED CMD')))
|
||||
|
||||
play_context.become = True
|
||||
play_context.become_user = play_context.remote_user = 'root'
|
||||
play_context.make_become_cmd = MagicMock(return_value='CMD')
|
||||
action_base = DerivedActionBase(None, None, play_context, fake_loader, None, None)
|
||||
action_base.get_become_option = MagicMock(return_value='root')
|
||||
action_base._get_remote_user = MagicMock(return_value='root')
|
||||
|
||||
action_base._connection = MagicMock(exec_command=MagicMock(return_value=(0, '', '')))
|
||||
|
||||
action_base._connection._shell = shell = MagicMock(append_command=MagicMock(return_value=('JOINED CMD')))
|
||||
|
||||
action_base._connection.become = become = MagicMock()
|
||||
become.build_become_command.return_value = 'foo'
|
||||
|
||||
action_base._low_level_execute_command('ECHO', sudoable=True)
|
||||
play_context.make_become_cmd.assert_not_called()
|
||||
become.build_become_command.assert_not_called()
|
||||
|
||||
play_context.remote_user = 'apo'
|
||||
action_base._get_remote_user.return_value = 'apo'
|
||||
action_base._low_level_execute_command('ECHO', sudoable=True, executable='/bin/csh')
|
||||
play_context.make_become_cmd.assert_called_once_with("ECHO", executable='/bin/csh')
|
||||
become.build_become_command.assert_called_once_with("ECHO", shell)
|
||||
|
||||
play_context.make_become_cmd.reset_mock()
|
||||
become.build_become_command.reset_mock()
|
||||
|
||||
become_allow_same_user = C.BECOME_ALLOW_SAME_USER
|
||||
C.BECOME_ALLOW_SAME_USER = True
|
||||
try:
|
||||
play_context.remote_user = 'root'
|
||||
with patch.object(C, 'BECOME_ALLOW_SAME_USER', new=True):
|
||||
action_base._get_remote_user.return_value = 'root'
|
||||
action_base._low_level_execute_command('ECHO SAME', sudoable=True)
|
||||
play_context.make_become_cmd.assert_called_once_with("ECHO SAME", executable=None)
|
||||
finally:
|
||||
C.BECOME_ALLOW_SAME_USER = become_allow_same_user
|
||||
become.build_become_command.assert_called_once_with("ECHO SAME", shell)
|
||||
|
||||
|
||||
class TestActionBaseCleanReturnedData(unittest.TestCase):
|
||||
|
|
|
@ -30,6 +30,7 @@ from units.compat.mock import patch
|
|||
from ansible.errors import AnsibleError
|
||||
from ansible.playbook.play_context import PlayContext
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.plugins.loader import become_loader
|
||||
# from ansible.plugins.connection.accelerate import Connection as AccelerateConnection
|
||||
# from ansible.plugins.connection.chroot import Connection as ChrootConnection
|
||||
# from ansible.plugins.connection.funcd import Connection as FuncdConnection
|
||||
|
@ -250,6 +251,8 @@ debug1: Sending command: /bin/sh -c 'sudo -H -S -p "[sudo via ansible, key=ouzm
|
|||
pass
|
||||
|
||||
c = ConnectionFoo(self.play_context, self.in_stream)
|
||||
c.set_become_plugin(become_loader.get('sudo'))
|
||||
c.become.prompt = '[sudo via ansible, key=ouzmdnewuhucvuaabtjmweasarviygqq] password: '
|
||||
|
||||
self.assertTrue(c.check_password_prompt(local))
|
||||
self.assertTrue(c.check_password_prompt(ssh_pipelining_vvvv))
|
||||
|
|
|
@ -34,7 +34,7 @@ from ansible.module_utils.six.moves import shlex_quote
|
|||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.playbook.play_context import PlayContext
|
||||
from ansible.plugins.connection import ssh
|
||||
from ansible.plugins.loader import connection_loader
|
||||
from ansible.plugins.loader import connection_loader, become_loader
|
||||
|
||||
|
||||
class TestConnectionBaseClass(unittest.TestCase):
|
||||
|
@ -93,6 +93,7 @@ class TestConnectionBaseClass(unittest.TestCase):
|
|||
new_stdin = StringIO()
|
||||
|
||||
conn = connection_loader.get('ssh', pc, new_stdin)
|
||||
conn.set_become_plugin(become_loader.get('sudo'))
|
||||
|
||||
conn.check_password_prompt = MagicMock()
|
||||
conn.check_become_success = MagicMock()
|
||||
|
@ -133,6 +134,14 @@ class TestConnectionBaseClass(unittest.TestCase):
|
|||
)
|
||||
|
||||
pc.prompt = True
|
||||
conn.become.prompt = True
|
||||
|
||||
def get_option(option):
|
||||
if option == 'become_pass':
|
||||
return 'password'
|
||||
return None
|
||||
|
||||
conn.become.get_option = get_option
|
||||
output, unprocessed = conn._examine_output(u'source', u'state', b'line 1\nline 2\nfoo\nline 3\nthis should be the remainder', False)
|
||||
self.assertEqual(output, b'line 1\nline 2\nline 3\n')
|
||||
self.assertEqual(unprocessed, b'this should be the remainder')
|
||||
|
@ -150,7 +159,9 @@ class TestConnectionBaseClass(unittest.TestCase):
|
|||
)
|
||||
|
||||
pc.prompt = False
|
||||
conn.become.prompt = False
|
||||
pc.success_key = u'BECOME-SUCCESS-abcdefghijklmnopqrstuvxyz'
|
||||
conn.become.success = u'BECOME-SUCCESS-abcdefghijklmnopqrstuvxyz'
|
||||
output, unprocessed = conn._examine_output(u'source', u'state', b'line 1\nline 2\nBECOME-SUCCESS-abcdefghijklmnopqrstuvxyz\nline 3\n', False)
|
||||
self.assertEqual(output, b'line 1\nline 2\nline 3\n')
|
||||
self.assertEqual(unprocessed, b'')
|
||||
|
@ -168,6 +179,7 @@ class TestConnectionBaseClass(unittest.TestCase):
|
|||
)
|
||||
|
||||
pc.prompt = False
|
||||
conn.become.prompt = False
|
||||
pc.success_key = None
|
||||
output, unprocessed = conn._examine_output(u'source', u'state', b'line 1\nline 2\nincorrect password\n', True)
|
||||
self.assertEqual(output, b'line 1\nline 2\nincorrect password\n')
|
||||
|
@ -186,6 +198,7 @@ class TestConnectionBaseClass(unittest.TestCase):
|
|||
)
|
||||
|
||||
pc.prompt = False
|
||||
conn.become.prompt = False
|
||||
pc.success_key = None
|
||||
output, unprocessed = conn._examine_output(u'source', u'state', b'line 1\nbad password\n', True)
|
||||
self.assertEqual(output, b'line 1\nbad password\n')
|
||||
|
@ -332,6 +345,7 @@ def mock_run_env(request, mocker):
|
|||
new_stdin = StringIO()
|
||||
|
||||
conn = connection_loader.get('ssh', pc, new_stdin)
|
||||
conn.set_become_plugin(become_loader.get('sudo'))
|
||||
conn._send_initial_data = MagicMock()
|
||||
conn._examine_output = MagicMock()
|
||||
conn._terminate_process = MagicMock()
|
||||
|
@ -425,7 +439,7 @@ class TestSSHConnectionRun(object):
|
|||
def test_password_with_prompt(self):
|
||||
# test with password prompting enabled
|
||||
self.pc.password = None
|
||||
self.pc.prompt = b'Password:'
|
||||
self.conn.become.prompt = b'Password:'
|
||||
self.conn._examine_output.side_effect = self._password_with_prompt_examine_output
|
||||
self.mock_popen_res.stdout.read.side_effect = [b"Password:", b"Success", b""]
|
||||
self.mock_popen_res.stderr.read.side_effect = [b""]
|
||||
|
@ -450,8 +464,10 @@ class TestSSHConnectionRun(object):
|
|||
def test_password_with_become(self):
|
||||
# test with some become settings
|
||||
self.pc.prompt = b'Password:'
|
||||
self.conn.become.prompt = b'Password:'
|
||||
self.pc.become = True
|
||||
self.pc.success_key = 'BECOME-SUCCESS-abcdefg'
|
||||
self.conn.become._id = 'abcdefg'
|
||||
self.conn._examine_output.side_effect = self._password_with_prompt_examine_output
|
||||
self.mock_popen_res.stdout.read.side_effect = [b"Password:", b"BECOME-SUCCESS-abcdefg", b"abc"]
|
||||
self.mock_popen_res.stderr.read.side_effect = [b"123"]
|
||||
|
|
Loading…
Reference in a new issue