1
0
Fork 0
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:
Matt Martz 2019-02-11 11:27:44 -06:00 committed by GitHub
parent c581fbd0be
commit 445ff39f94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
73 changed files with 1849 additions and 721 deletions

View 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``.

View file

@ -26,7 +26,7 @@ ifdef PLUGINS
PLUGIN_ARGS = -l $(PLUGINS) PLUGIN_ARGS = -l $(PLUGINS)
endif 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: assertrst:
ifndef rst ifndef rst

View file

@ -90,7 +90,7 @@ later).
.. code-block:: shell .. 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: .. _installing_the_control_node:

View 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

View 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>

View file

@ -14,6 +14,7 @@ This section covers the various types of plugins that are included with Ansible:
:maxdepth: 1 :maxdepth: 1
action action
become
cache cache
callback callback
cliconf cliconf

View file

@ -82,19 +82,13 @@ If you would like to access sudo mode, there are also flags to do that:
# as bruce # as bruce
$ ansible all -m ping -u bruce $ ansible all -m ping -u bruce
# as bruce, sudoing to root # as bruce, sudoing to root (sudo is default method)
$ ansible all -m ping -u bruce --sudo $ ansible all -m ping -u bruce --become
# as bruce, sudoing to batman # 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 The sudo implementation (and other methods of changing the current user) can be modified in Ansible's configuration
# as bruce, sudoing to root if you happen to want to use a sudo replacement. Flags passed to sudo (like -H) can also be set.
$ 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.)
Now run a live command on all of your nodes: Now run a live command on all of your nodes:

View file

@ -198,10 +198,10 @@ You can also use other privilege escalation methods, like su::
become: yes become: yes
become_method: su become_method: su
If you need to specify a password to sudo, run ``ansible-playbook`` with ``--ask-become-pass`` or If you need to specify a password for sudo, run ``ansible-playbook`` with ``--ask-become-pass`` or ``-K``.
when using the old sudo syntax ``--ask-sudo-pass`` (``-K``). If you run a become playbook and the If you run a playbook utilizing ``become`` and the playbook seems to hang, it's probably stuck at the privilege
playbook seems to hang, it's probably stuck at the privilege escalation prompt. escalation prompt and can be stopped using `Control-C`, allowing you to re-execute the playbook adding the
Just `Control-C` to kill it and run it again adding the appropriate password. appropriate password.
.. important:: .. important::

View file

@ -190,6 +190,7 @@
# set plugin path directories here, separate with colons # set plugin path directories here, separate with colons
#action_plugins = /usr/share/ansible/plugins/action #action_plugins = /usr/share/ansible/plugins/action
#become_plugins = /usr/share/ansible/plugins/become
#cache_plugins = /usr/share/ansible/plugins/cache #cache_plugins = /usr/share/ansible/plugins/cache
#callback_plugins = /usr/share/ansible/plugins/callback #callback_plugins = /usr/share/ansible/plugins/callback
#connection_plugins = /usr/share/ansible/plugins/connection #connection_plugins = /usr/share/ansible/plugins/connection

View file

@ -29,6 +29,7 @@ from ansible.utils.display import Display
from ansible.utils.path import unfrackpath from ansible.utils.path import unfrackpath
from ansible.vars.manager import VariableManager from ansible.vars.manager import VariableManager
from ansible.parsing.vault import PromptVaultSecret, get_file_vault_secret from ansible.parsing.vault import PromptVaultSecret, get_file_vault_secret
from ansible.plugins.loader import add_all_plugin_dirs
display = Display() display = Display()
@ -277,36 +278,6 @@ class CLI(with_metaclass(ABCMeta, object)):
return (sshpass, becomepass) 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): def validate_conflicts(self, op, vault_opts=False, runas_opts=False, fork_opts=False, vault_rekey_opts=False):
''' check for conflicting options ''' ''' 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: 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") 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 fork_opts:
if op.forks < 1: if op.forks < 1:
self.parser.error("The number of processes (--forks) must be >= 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) basedir = options.get('basedir', False)
if basedir: if basedir:
loader.set_basedir(basedir) loader.set_basedir(basedir)
add_all_plugin_dirs(basedir)
vault_ids = list(options['vault_ids']) vault_ids = list(options['vault_ids'])
default_vault_ids = C.DEFAULT_VAULT_IDENTITY_LIST 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) # create the inventory, and filter it based on the subset specified (if any)
inventory = InventoryManager(loader=loader, sources=options['inventory']) 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 # create the variable manager, which will be shared throughout
# the code, ensuring a consistent view of global variables # 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'") display.warning("provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'")
no_hosts = True no_hosts = True
inventory.subset(subset)
hosts = inventory.list_hosts(pattern) hosts = inventory.list_hosts(pattern)
if len(hosts) == 0 and no_hosts is False: if len(hosts) == 0 and no_hosts is False:
raise AnsibleError("Specified hosts and/or --limit does not match any hosts") raise AnsibleError("Specified hosts and/or --limit does not match any hosts")

View file

@ -15,7 +15,6 @@ from ansible.module_utils._text import to_text
from ansible.parsing.splitter import parse_kv from ansible.parsing.splitter import parse_kv
from ansible.playbook import Playbook from ansible.playbook import Playbook
from ansible.playbook.play import Play from ansible.playbook.play import Play
from ansible.plugins.loader import get_all_plugin_loaders
from ansible.utils.display import Display from ansible.utils.display import Display
display = Display() display = Display()
@ -66,8 +65,6 @@ class AdHocCLI(CLI):
display.verbosity = options.verbosity display.verbosity = options.verbosity
self.validate_conflicts(options, runas_opts=True, vault_opts=True, fork_opts=True) self.validate_conflicts(options, runas_opts=True, vault_opts=True, fork_opts=True)
options = self.normalize_become_options(options)
return options, args return options, args
def _play_ds(self, pattern, async_val, poll): def _play_ds(self, pattern, async_val, poll):
@ -100,9 +97,7 @@ class AdHocCLI(CLI):
(sshpass, becomepass) = self.ask_passwords() (sshpass, becomepass) = self.ask_passwords()
passwords = {'conn_pass': sshpass, 'become_pass': becomepass} passwords = {'conn_pass': sshpass, 'become_pass': becomepass}
# dynamically load any plugins # get basic objects
get_all_plugin_loaders()
loader, inventory, variable_manager = self._play_prereqs() loader, inventory, variable_manager = self._play_prereqs()
try: try:

View file

@ -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") 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) # consolidated privilege escalation (become)
runas_group.add_option("-b", "--become", default=C.DEFAULT_BECOME, action="store_true", dest='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)") 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, runas_group.add_option('--become-method', dest='become_method', default=C.DEFAULT_BECOME_METHOD,
help="privilege escalation method to use (default=%s), valid choices: [ %s ]" % help="privilege escalation method to use (default=%default), use "
(C.DEFAULT_BECOME_METHOD, ' | '.join(C.BECOME_METHODS))) "`ansible-doc -t become -l` to list valid choices.")
runas_group.add_option('--become-user', default=None, dest='become_user', type='string', 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) 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", runas_group = optparse.OptionGroup(parser, "Privilege Escalation Options",
"control how and which user you become as on target hosts") "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', runas_group.add_option('-K', '--ask-become-pass', default=False, dest='become_ask_pass', action='store_true',
help='ask for privilege escalation password') help='ask for privilege escalation password')

View file

@ -100,7 +100,6 @@ class ConsoleCLI(CLI, cmd.Cmd):
def post_process_args(self, options, args): def post_process_args(self, options, args):
options, args = super(ConsoleCLI, self).post_process_args(options, args) options, args = super(ConsoleCLI, self).post_process_args(options, args)
display.verbosity = options.verbosity display.verbosity = options.verbosity
options = self.normalize_become_options(options)
self.validate_conflicts(options, runas_opts=True, vault_opts=True, fork_opts=True) self.validate_conflicts(options, runas_opts=True, vault_opts=True, fork_opts=True)
return options, args return options, args

View file

@ -13,9 +13,10 @@ from ansible.cli import CLI
from ansible.cli.arguments import optparse_helpers as opt_help from ansible.cli.arguments import optparse_helpers as opt_help
from ansible.errors import AnsibleError, AnsibleOptionsError from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.executor.playbook_executor import PlaybookExecutor from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.module_utils._text import to_bytes
from ansible.playbook.block import Block from ansible.playbook.block import Block
from ansible.playbook.play_context import PlayContext
from ansible.utils.display import Display from ansible.utils.display import Display
from ansible.plugins.loader import add_all_plugin_dirs
display = Display() display = Display()
@ -61,8 +62,6 @@ class PlaybookCLI(CLI):
display.verbosity = options.verbosity display.verbosity = options.verbosity
self.validate_conflicts(options, runas_opts=True, vault_opts=True, fork_opts=True) self.validate_conflicts(options, runas_opts=True, vault_opts=True, fork_opts=True)
options = self.normalize_become_options(options)
return options, args return options, args
def run(self): def run(self):
@ -82,6 +81,14 @@ class PlaybookCLI(CLI):
raise AnsibleError("the playbook: %s could not be found" % playbook) 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)): 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) 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 # don't deal with privilege escalation or passwords when we don't need to
if not (context.CLIARGS['listhosts'] or context.CLIARGS['listtasks'] or if not (context.CLIARGS['listhosts'] or context.CLIARGS['listtasks'] or
@ -89,16 +96,9 @@ class PlaybookCLI(CLI):
(sshpass, becomepass) = self.ask_passwords() (sshpass, becomepass) = self.ask_passwords()
passwords = {'conn_pass': sshpass, 'become_pass': becomepass} passwords = {'conn_pass': sshpass, 'become_pass': becomepass}
# create base objects
loader, inventory, variable_manager = self._play_prereqs() 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 # flush fact cache if requested
if context.CLIARGS['flush_cache']: if context.CLIARGS['flush_cache']:
self._flush_cache(inventory, variable_manager) self._flush_cache(inventory, variable_manager)
@ -161,9 +161,8 @@ class PlaybookCLI(CLI):
return taskmsg return taskmsg
all_vars = variable_manager.get_vars(play=play) all_vars = variable_manager.get_vars(play=play)
play_context = PlayContext(play=play)
for block in play.compile(): 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(): if not block.has_tasks():
continue continue
taskmsg += _process_block(block) taskmsg += _process_block(block)

View file

@ -260,7 +260,7 @@ class PullCLI(CLI):
for ev in context.CLIARGS['extra_vars']: for ev in context.CLIARGS['extra_vars']:
cmd += ' -e %s' % shlex_quote(ev) 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' cmd += ' --ask-become-pass'
if context.CLIARGS['skip_tags']: if context.CLIARGS['skip_tags']:
cmd += ' --skip-tags "%s"' % to_native(u','.join(context.CLIARGS['skip_tags'])) cmd += ' --skip-tags "%s"' % to_native(u','.join(context.CLIARGS['skip_tags']))

View file

@ -481,6 +481,14 @@ DEFAULT_BECOME_FLAGS:
env: [{name: ANSIBLE_BECOME_FLAGS}] env: [{name: ANSIBLE_BECOME_FLAGS}]
ini: ini:
- {key: become_flags, section: privilege_escalation} - {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: DEFAULT_BECOME_USER:
# FIXME: should really be blank and make -u passing optional depending on it # FIXME: should really be blank and make -u passing optional depending on it
name: Set the user you 'become' via privilege escalation name: Set the user you 'become' via privilege escalation
@ -632,6 +640,7 @@ DEFAULT_GATHER_SUBSET:
- key: gather_subset - key: gather_subset
section: defaults section: defaults
version_added: "2.1" version_added: "2.1"
type: list
DEFAULT_GATHER_TIMEOUT: DEFAULT_GATHER_TIMEOUT:
name: Gather facts timeout name: Gather facts timeout
default: 10 default: 10
@ -1081,50 +1090,6 @@ DEFAULT_SU:
- {key: su, section: defaults} - {key: su, section: defaults}
type: boolean type: boolean
yaml: {key: defaults.su} 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: DEFAULT_SU_EXE:
name: su executable name: su executable
default: su default: su

View file

@ -398,8 +398,8 @@ class ConfigManager(object):
origin = 'var: %s' % origin origin = 'var: %s' % origin
# use playbook keywords if you have em # use playbook keywords if you have em
if value is None and keys and defs[config].get('keywords'): if value is None and keys and config in keys:
value, origin = self._loop_entries(keys, defs[config]['keywords']) value, origin = keys[config], 'keyword'
origin = 'keyword: %s' % origin origin = 'keyword: %s' % origin
# env vars are next precedence # env vars are next precedence

View file

@ -12,6 +12,7 @@ from jinja2 import Template
from string import ascii_letters, digits from string import ascii_letters, digits
from ansible.module_utils._text import to_text 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.parsing.convert_bool import boolean, BOOLEANS_TRUE
from ansible.module_utils.six import string_types from ansible.module_utils.six import string_types
from ansible.config.manager import ConfigManager, ensure_type, get_ini_config_value 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 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 # 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') BLACKLIST_EXTS = ('.pyc', '.pyo', '.swp', '.bak', '~', '.rpm', '.md', '.txt', '.rst')
BOOL_TRUE = BOOLEANS_TRUE BOOL_TRUE = BOOLEANS_TRUE
CONTROLLER_LANG = os.getenv('LANG', 'en_US.UTF-8') CONTROLLER_LANG = os.getenv('LANG', 'en_US.UTF-8')
@ -104,7 +104,7 @@ DEFAULT_REMOTE_PASS = None
DEFAULT_SUBSET = None DEFAULT_SUBSET = None
DEFAULT_SU_PASS = None DEFAULT_SU_PASS = None
# FIXME: expand to other plugins, but never doc fragments # 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 # NOTE: always update the docs/docsite/Makefile to match
DOCUMENTABLE_PLUGINS = CONFIGURABLE_PLUGINS + ('module', 'strategy', 'vars') DOCUMENTABLE_PLUGINS = CONFIGURABLE_PLUGINS + ('module', 'strategy', 'vars')
IGNORE_FILES = ("COPYING", "CONTRIBUTING", "LICENSE", "README", "VERSION", "GUIDELINES") # ignore during module search IGNORE_FILES = ("COPYING", "CONTRIBUTING", "LICENSE", "README", "VERSION", "GUIDELINES") # ignore during module search

View file

@ -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 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 from ansible.utils.context_objects import CLIArgs, GlobalCLIArgs
@ -31,3 +33,24 @@ def _init_global_context(cli_args):
"""Initialize the global context objects""" """Initialize the global context objects"""
global CLIARGS global CLIARGS
CLIARGS = GlobalCLIArgs.from_options(cli_args) 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

View file

@ -151,19 +151,9 @@ class PlayIterator:
self._variable_manager = variable_manager self._variable_manager = variable_manager
# Default options to gather # Default options to gather
gather_subset = play_context.gather_subset gather_subset = self._play.gather_subset
gather_timeout = play_context.gather_timeout gather_timeout = self._play.gather_timeout
fact_path = play_context.fact_path fact_path = self._play.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
setup_block = Block(play=self._play) setup_block = Block(play=self._play)
# Gathering facts with run_once would copy the facts from one host to # 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_task.when = self._play._included_conditional[:]
setup_block.block = [setup_task] 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) self._blocks.append(setup_block)
for block in self._play.compile(): 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(): if new_block.has_tasks():
self._blocks.append(new_block) self._blocks.append(new_block)

View file

@ -25,6 +25,7 @@ from ansible import constants as C
from ansible import context from ansible import context
from ansible.executor.task_queue_manager import TaskQueueManager from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.module_utils._text import to_native, to_text 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.playbook import Playbook
from ansible.template import Templar from ansible.template import Templar
from ansible.plugins.loader import connection_loader, shell_loader from ansible.plugins.loader import connection_loader, shell_loader
@ -82,9 +83,10 @@ class PlaybookExecutor:
entrylist = [] entrylist = []
entry = {} entry = {}
try: 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(connection_loader.all(class_only=True))
list(shell_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: for playbook_path in self._playbooks:
pb = Playbook.load(playbook_path, variable_manager=self._variable_manager, loader=self._loader) pb = Playbook.load(playbook_path, variable_manager=self._variable_manager, loader=self._loader)

View file

@ -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.module_utils.connection import write_to_file_descriptor
from ansible.playbook.conditional import Conditional from ansible.playbook.conditional import Conditional
from ansible.playbook.task import Task from ansible.playbook.task import Task
from ansible.plugins.loader import become_loader
from ansible.template import Templar from ansible.template import Templar
from ansible.utils.listify import listify_lookup_plugin_terms from ansible.utils.listify import listify_lookup_plugin_terms
from ansible.utils.unsafe_proxy import UnsafeProxy, wrap_var from ansible.utils.unsafe_proxy import UnsafeProxy, wrap_var
@ -590,7 +591,6 @@ class TaskExecutor:
self._connection._play_context = self._play_context self._connection._play_context = self._play_context
self._set_connection_options(variables, templar) self._set_connection_options(variables, templar)
self._set_shell_options(variables, templar)
# get handler # get handler
self._handler = self._get_action_handler(connection=self._connection, templar=templar) self._handler = self._get_action_handler(connection=self._connection, templar=templar)
@ -849,6 +849,13 @@ class TaskExecutor:
else: else:
return async_result 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): def _get_connection(self, variables, templar):
''' '''
Reads the connection property for the host, and returns the 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"): if isinstance(i, string_types) and i.startswith("ansible_") and i.endswith("_interpreter"):
variables[i] = delegated_vars[i] variables[i] = delegated_vars[i]
# load connection
conn_type = self._play_context.connection conn_type = self._play_context.connection
connection = self._shared_loader_obj.connection_loader.get( connection = self._shared_loader_obj.connection_loader.get(
conn_type, conn_type,
self._play_context, self._play_context,
@ -882,8 +889,30 @@ class TaskExecutor:
if not connection: if not connection:
raise AnsibleError("the connection plugin '%s' was not found" % conn_type) 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 # 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)): if any(((connection.supports_persistence and C.USE_PERSISTENT_CONNECTIONS), connection.force_persistence)):
self._play_context.timeout = connection.get_option('persistent_command_timeout') self._play_context.timeout = connection.get_option('persistent_command_timeout')
@ -912,13 +941,30 @@ class TaskExecutor:
return options 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): def _set_connection_options(self, variables, templar):
# Keep the pre-delegate values for these keys # Keep the pre-delegate values for these keys
PRESERVE_ORIG = ('inventory_hostname',) PRESERVE_ORIG = ('inventory_hostname',)
# create copy with delegation built in # 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 # grab list of usable vars for this plugin
option_vars = C.config.get_plugin_vars('connection', self._connection._load_name) 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: if k.startswith('ansible_%s_' % self._connection._load_name) and k not in options:
options['_extras'][k] = templar.template(final_vars[k]) options['_extras'][k] = templar.template(final_vars[k])
# set options with 'templated vars' specific to this plugin task_keys = self._task.dump_attrs()
self._connection.set_options(var_options=options)
self._set_shell_options(final_vars, templar)
def _set_shell_options(self, variables, templar): # set options with 'templated vars' specific to this plugin and dependant ones
option_vars = C.config.get_plugin_vars('shell', self._connection._shell._load_name) self._connection.set_options(task_keys=task_keys, var_options=options)
options = {} self._set_plugin_options('shell', final_vars, templar, task_keys)
for k in option_vars:
if k in variables: if self._connection.become is not None:
options[k] = templar.template(variables[k]) # FIXME: find alternate route to provide passwords,
self._connection._shell.set_options(var_options=options) # 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): def _get_action_handler(self, connection, templar):
''' '''

View file

@ -26,7 +26,6 @@ from ansible.errors import AnsibleParserError
from ansible.module_utils._text import to_bytes, to_text, to_native from ansible.module_utils._text import to_bytes, to_text, to_native
from ansible.playbook.play import Play from ansible.playbook.play import Play
from ansible.playbook.playbook_include import PlaybookInclude from ansible.playbook.playbook_include import PlaybookInclude
from ansible.plugins.loader import get_all_plugin_loaders
from ansible.utils.display import Display from ansible.utils.display import Display
display = Display() display = Display()
@ -64,13 +63,6 @@ class Playbook:
self._file_name = file_name 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: try:
ds = self._loader.load_from_file(os.path.basename(file_name)) ds = self._loader.load_from_file(os.path.basename(file_name))
except UnicodeDecodeError as e: except UnicodeDecodeError as e:

View file

@ -14,7 +14,7 @@ from functools import partial
from jinja2.exceptions import UndefinedError from jinja2.exceptions import UndefinedError
from ansible import constants as C 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.six import iteritems, string_types, with_metaclass
from ansible.module_utils.parsing.convert_bool import boolean from ansible.module_utils.parsing.convert_bool import boolean
from ansible.errors import AnsibleParserError, AnsibleUndefinedVariable, AnsibleAssertionError from ansible.errors import AnsibleParserError, AnsibleUndefinedVariable, AnsibleAssertionError
@ -520,10 +520,10 @@ class FieldAttributeBase(with_metaclass(BaseMeta, object)):
''' '''
Dumps all attributes to a dictionary Dumps all attributes to a dictionary
''' '''
attrs = dict() attrs = {}
for (name, attribute) in iteritems(self._valid_attrs): for (name, attribute) in iteritems(self._valid_attrs):
attr = getattr(self, name) 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() attrs[name] = attr.serialize()
else: else:
attrs[name] = attr attrs[name] = attr
@ -592,9 +592,9 @@ class Base(FieldAttributeBase):
_name = FieldAttribute(isa='string', default='', always_post_validate=True, inherit=False) _name = FieldAttribute(isa='string', default='', always_post_validate=True, inherit=False)
# connection/transport # connection/transport
_connection = FieldAttribute(isa='string') _connection = FieldAttribute(isa='string', default=context.cliargs_deferred_get('connection'))
_port = FieldAttribute(isa='int') _port = FieldAttribute(isa='int')
_remote_user = FieldAttribute(isa='string') _remote_user = FieldAttribute(isa='string', default=context.cliargs_deferred_get('remote_user'))
# variables # variables
_vars = FieldAttribute(isa='dict', priority=100, inherit=False) _vars = FieldAttribute(isa='dict', priority=100, inherit=False)
@ -608,8 +608,8 @@ class Base(FieldAttributeBase):
_run_once = FieldAttribute(isa='bool') _run_once = FieldAttribute(isa='bool')
_ignore_errors = FieldAttribute(isa='bool') _ignore_errors = FieldAttribute(isa='bool')
_ignore_unreachable = FieldAttribute(isa='bool') _ignore_unreachable = FieldAttribute(isa='bool')
_check_mode = FieldAttribute(isa='bool') _check_mode = FieldAttribute(isa='bool', default=context.cliargs_deferred_get('check'))
_diff = FieldAttribute(isa='bool') _diff = FieldAttribute(isa='bool', default=context.cliargs_deferred_get('diff'))
_any_errors_fatal = FieldAttribute(isa='bool', default=C.ANY_ERRORS_FATAL) _any_errors_fatal = FieldAttribute(isa='bool', default=C.ANY_ERRORS_FATAL)
# explicitly invoke a debugger on tasks # explicitly invoke a debugger on tasks

View file

@ -20,6 +20,7 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
from ansible import constants as C from ansible import constants as C
from ansible import context
from ansible.errors import AnsibleParserError from ansible.errors import AnsibleParserError
from ansible.playbook.attribute import FieldAttribute from ansible.playbook.attribute import FieldAttribute
from ansible.utils.display import Display from ansible.utils.display import Display
@ -30,9 +31,9 @@ display = Display()
class Become: class Become:
# Privilege escalation # Privilege escalation
_become = FieldAttribute(isa='bool') _become = FieldAttribute(isa='bool', default=context.cliargs_deferred_get('become'))
_become_method = FieldAttribute(isa='string') _become_method = FieldAttribute(isa='string', default=context.cliargs_deferred_get('become_method'))
_become_user = FieldAttribute(isa='string') _become_user = FieldAttribute(isa='string', default=context.cliargs_deferred_get('become_user'))
_become_flags = FieldAttribute(isa='string') _become_flags = FieldAttribute(isa='string')
def __init__(self): def __init__(self):

View file

@ -362,10 +362,9 @@ class Block(Base, Become, Conditional, Taggable):
return value 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 Creates a new block, with task lists filtered based on the tags.
within the play_context object.
''' '''
def evaluate_and_append_task(target): def evaluate_and_append_task(target):
@ -374,8 +373,8 @@ class Block(Base, Become, Conditional, Taggable):
if isinstance(task, Block): if isinstance(task, Block):
tmp_list.append(evaluate_block(task)) tmp_list.append(evaluate_block(task))
elif (task.action == 'meta' or elif (task.action == 'meta' or
(task.action == 'include' and task.evaluate_tags([], play_context.skip_tags, all_vars=all_vars)) or (task.action == 'include' and task.evaluate_tags([], self._play.skip_tags, all_vars=all_vars)) or
task.evaluate_tags(play_context.only_tags, play_context.skip_tags, all_vars=all_vars)): task.evaluate_tags(self._play.only_tags, self._play.skip_tags, all_vars=all_vars)):
tmp_list.append(task) tmp_list.append(task)
return tmp_list return tmp_list

View file

@ -20,6 +20,7 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
from ansible import constants as C from ansible import constants as C
from ansible import context
from ansible.errors import AnsibleParserError, AnsibleAssertionError from ansible.errors import AnsibleParserError, AnsibleAssertionError
from ansible.module_utils.six import string_types from ansible.module_utils.six import string_types
from ansible.playbook.attribute import FieldAttribute 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) _hosts = FieldAttribute(isa='list', required=True, listof=string_types, always_post_validate=True)
# Facts # Facts
_fact_path = FieldAttribute(isa='string', default=None)
_gather_facts = FieldAttribute(isa='bool', default=None, always_post_validate=True) _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_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 # Variable Attributes
_vars_files = FieldAttribute(isa='list', default=list, priority=99) _vars_files = FieldAttribute(isa='list', default=list, priority=99)
@ -73,7 +74,7 @@ class Play(Base, Taggable, Become):
_tasks = FieldAttribute(isa='list', default=list) _tasks = FieldAttribute(isa='list', default=list)
# Flag/Setting Attributes # 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) _max_fail_percentage = FieldAttribute(isa='percent', always_post_validate=True)
_serial = FieldAttribute(isa='list', default=list, 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) _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._removed_hosts = []
self.ROLE_CACHE = {} 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): def __repr__(self):
return self.get_name() return self.get_name()

View file

@ -23,22 +23,17 @@ __metaclass__ = type
import os import os
import pwd import pwd
import random
import re
import string
import sys import sys
from ansible import constants as C from ansible import constants as C
from ansible import context from ansible import context
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.module_utils.six import iteritems 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.attribute import FieldAttribute
from ansible.playbook.base import Base from ansible.playbook.base import Base
from ansible.plugins import get_plugin_class from ansible.plugins import get_plugin_class
from ansible.utils.display import Display from ansible.utils.display import Display
from ansible.plugins.loader import get_shell_plugin
from ansible.utils.ssh_functions import check_for_controlpersist from ansible.utils.ssh_functions import check_for_controlpersist
@ -47,41 +42,6 @@ display = Display()
__all__ = ['PlayContext'] __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 = ( TASK_ATTRIBUTE_OVERRIDES = (
'become', 'become',
@ -113,9 +73,6 @@ RESET_VARS = (
'ansible_ssh_executable', '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): class PlayContext(Base):
@ -166,14 +123,6 @@ class PlayContext(Base):
_become_flags = FieldAttribute(isa='string', default=C.DEFAULT_BECOME_FLAGS) _become_flags = FieldAttribute(isa='string', default=C.DEFAULT_BECOME_FLAGS)
_prompt = FieldAttribute(isa='string') _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 # general flags
_verbosity = FieldAttribute(isa='int', default=0) _verbosity = FieldAttribute(isa='int', default=0)
_only_tags = FieldAttribute(isa='set', default=set) _only_tags = FieldAttribute(isa='set', default=set)
@ -182,12 +131,10 @@ class PlayContext(Base):
_start_at_task = FieldAttribute(isa='string') _start_at_task = FieldAttribute(isa='string')
_step = FieldAttribute(isa='bool', default=False) _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): 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__() super(PlayContext, self).__init__()
@ -197,6 +144,8 @@ class PlayContext(Base):
self.password = passwords.get('conn_pass', '') self.password = passwords.get('conn_pass', '')
self.become_pass = passwords.get('become_pass', '') self.become_pass = passwords.get('become_pass', '')
self._become_plugin = None
self.prompt = '' self.prompt = ''
self.success_key = '' self.success_key = ''
@ -205,37 +154,12 @@ class PlayContext(Base):
# set options before play to allow play to override them # set options before play to allow play to override them
if context.CLIARGS: if context.CLIARGS:
self.set_options() self.set_attributes_from_cli()
if play: if play:
self.set_play(play) self.set_attributes_from_play(play)
def set_play(self, play): def set_attributes_from_plugin(self, plugin):
'''
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):
# generic derived from connection plugin, temporary for backwards compat, in the end we should not set play_context properties # generic derived from connection plugin, temporary for backwards compat, in the end we should not set play_context properties
# get options for plugins # get options for plugins
@ -246,46 +170,43 @@ class PlayContext(Base):
if flag: if flag:
setattr(self, flag, self.connection.get_option(flag)) setattr(self, flag, self.connection.get_option(flag))
# TODO: made irrelavent by above def set_attributes_from_play(self, play):
# get ssh options # From ansible.playbook.Become
# for flag in ('ssh_common_args', 'docker_extra_args', 'sftp_extra_args', 'scp_extra_args', 'ssh_extra_args'): self.become = play.become
# setattr(self, flag, getattr(options, flag, '')) 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 Configures this connection information instance with data from
options specified by the user on the command line. These have a options specified by the user on the command line. These have a
lower precedence than those set on the play or host. 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): 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 # From the command line. These should probably be used directly by plugins instead
# the attribute, as it is not always added via the CLI # For now, they are likely to be moved to FieldAttribute defaults
if context.CLIARGS.get('tags', False): self.private_key_file = context.CLIARGS.get('private_key_file') # Else default
self.only_tags.update(context.CLIARGS['tags']) 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: # Not every cli that uses PlayContext has these command line args so have a default
self.only_tags = set(['all']) self.start_at_task = context.CLIARGS.get('start_at_task', None) # Else default
if context.CLIARGS.get('skip_tags', False):
self.skip_tags.update(context.CLIARGS['skip_tags'])
def set_task_and_variable_override(self, task, variables, templar): def set_task_and_variable_override(self, task, variables, templar):
''' '''
@ -376,13 +297,6 @@ class PlayContext(Base):
attrs_considered.append(attr) attrs_considered.append(attr)
# no else, as no other vars should be considered # 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 # become legacy updates -- from inventory file (inventory overrides
# commandline) # commandline)
for become_pass_name in C.MAGIC_VARIABLE_MAPPING.get('become_pass'): for become_pass_name in C.MAGIC_VARIABLE_MAPPING.get('become_pass'):
@ -442,135 +356,43 @@ class PlayContext(Base):
return new_info return new_info
def set_become_plugin(self, plugin):
self._become_plugin = plugin
def make_become_cmd(self, cmd, executable=None): def make_become_cmd(self, cmd, executable=None):
""" helper function to create privilege escalation commands """ """ 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 if not cmd or not self.become:
success_key = None return cmd
self.prompt = None
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: if not executable:
executable = self.executable executable = self.executable
becomecmd = None shell = get_shell_plugin(executable=executable)
randbits = ''.join(random.choice(string.ascii_lowercase) for x in range(32)) cmd = plugin.build_become_command(cmd, shell)
success_key = 'BECOME-SUCCESS-%s' % randbits # for backwards compat:
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)
if self.become_pass: if self.become_pass:
self.prompt = prompt self.prompt = plugin.prompt
self.success_key = success_key else:
return becomecmd raise AnsibleError("Privilege escalation method not found: %s" % become_method)
return cmd return cmd

View file

@ -31,7 +31,7 @@ from ansible.playbook.conditional import Conditional
from ansible.playbook.helpers import load_list_of_blocks from ansible.playbook.helpers import load_list_of_blocks
from ansible.playbook.role.metadata import RoleMetadata from ansible.playbook.role.metadata import RoleMetadata
from ansible.playbook.taggable import Taggable 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 from ansible.utils.vars import combine_vars
@ -194,12 +194,8 @@ class Role(Base, Become, Conditional, Taggable):
else: else:
self._attributes[attr_name] = role_include._attributes[attr_name] self._attributes[attr_name] = role_include._attributes[attr_name]
# dynamically load any plugins from the role directory # ensure all plugins dirs for this role are added to plugin search path
for name, obj in get_all_plugin_loaders(): add_all_plugin_dirs(self._role_path)
if obj.subdir:
plugin_path = os.path.join(self._role_path, obj.subdir)
if os.path.isdir(plugin_path):
obj.add_directory(plugin_path)
# vars and default vars are regular dictionaries # vars and default vars are regular dictionaries
self._role_vars = self._load_role_yaml('vars', main=self._from_files.get('vars'), allow_dir=True) self._role_vars = self._load_role_yaml('vars', main=self._from_files.get('vars'), allow_dir=True)

View file

@ -24,6 +24,8 @@ __metaclass__ = type
from abc import ABCMeta from abc import ABCMeta
from ansible import constants as C 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.module_utils.six import with_metaclass, string_types
from ansible.utils.display import Display from ansible.utils.display import Display
@ -52,7 +54,10 @@ class AnsiblePlugin(with_metaclass(ABCMeta, object)):
def get_option(self, option, hostvars=None): def get_option(self, option, hostvars=None):
if option not in self._options: if option not in self._options:
option_value = C.config.get_config_value(option, plugin_type=get_plugin_class(self), plugin_name=self._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) self.set_option(option, option_value)
return self._options.get(option) return self._options.get(option)

View file

@ -108,6 +108,24 @@ class ActionBase(with_metaclass(ABCMeta, object)):
return result 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): def _remote_file_exists(self, path):
cmd = self._connection._shell.exists(path) cmd = self._connection._shell.exists(path)
result = self._low_level_execute_command(cmd=cmd, sudoable=True) 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 Returns a list of admin users that are configured for the current shell
plugin 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: try:
admin_users = self._connection._shell.get_option('admin_users') remote_user = self._connection.get_option('remote_user')
except AnsibleError: except KeyError:
# fallback for old custom plugins w/o get_option # plugin does not have remote_user option, fallback to default and/play_context
admin_users = ['root'] remote_user = getattr(self._connection, 'default_user', None) or self._play_context.remote_user
return admin_users 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): 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 # if we use become and the user is not an admin (or same user) then
# we need to return become_unprivileged as True # we need to return become_unprivileged as True
admin_users = self._get_admin_users() admin_users = self._get_admin_users()
try: remote_user = self._get_remote_user()
remote_user = self._connection.get_option('remote_user') return bool(self.get_become_option('become_user') not in admin_users + [remote_user])
except AnsibleError:
remote_user = self._play_context.remote_user
return bool(self._play_context.become_user not in admin_users + [remote_user])
def _make_tmp_path(self, remote_user=None): def _make_tmp_path(self, remote_user=None):
''' '''
@ -273,10 +299,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
''' '''
become_unprivileged = self._is_become_unprivileged() become_unprivileged = self._is_become_unprivileged()
try: remote_tmp = self.get_shell_option('remote_tmp', default='~/.ansible/tmp')
remote_tmp = self._connection._shell.get_option('remote_tmp')
except AnsibleError:
remote_tmp = '~/.ansible/tmp'
# deal with tmpdir creation # deal with tmpdir creation
basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) 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 "allow_world_readable_tmpfiles" in the ansible.cfg
""" """
if remote_user is None: 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': if self._connection._shell.SHELL_FAMILY == 'powershell':
# This won't work on Powershell as-is, so we'll just completely skip until # 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. # start to we'll have to fix this.
setfacl_mode = 'r-X' 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: if res['rc'] != 0:
# File system acls failed; let's try to use chown next # File system acls failed; let's try to use chown next
# Set executable bit first as on some systems an # Set executable bit first as on some systems an
@ -442,7 +465,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
if res['rc'] != 0: 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']))) 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(): if res['rc'] != 0 and remote_user in self._get_admin_users():
# chown failed even if remote_user is administrator/root # 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. ' 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. # 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 # 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 # 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): if getattr(self._connection, '_remote_is_local', False):
pass pass
elif sudoable and self._play_context.become and self._play_context.become_user: elif sudoable and self._play_context.become and become_user:
expand_path = '~%s' % self._play_context.become_user expand_path = '~%s' % become_user
else: else:
# use remote user instead, if none set default to current user # 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 # use shell to construct appropriate command and execute
cmd = self._connection._shell.expand_user(expand_path) 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 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 # make sure the remote_tmp value is sent through in case modules needs to create their own
try: module_args['_ansible_remote_tmp'] = self.get_shell_option('remote_tmp', default='~/.ansible/tmp')
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)
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): 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 # ANSIBLE_ASYNC_DIR is not set on the task, we get the value
# from the shell option and temporarily add to the environment # from the shell option and temporarily add to the environment
# list for async_wrapper to pick up # list for async_wrapper to pick up
try: async_dir = self.get_shell_option('async_dir', default="~/.ansible_async")
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"
remove_async_dir = len(self._task.environment) remove_async_dir = len(self._task.environment)
self._task.environment.append({"ANSIBLE_ASYNC_DIR": async_dir}) self._task.environment.append({"ANSIBLE_ASYNC_DIR": async_dir})
@ -861,7 +862,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
if remote_files: if remote_files:
# remove none/empty # remove none/empty
remote_files = [x for x in remote_files if x] 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 # actually execute
res = self._low_level_execute_command(cmd, sudoable=sudoable, in_data=in_data) 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'] data['rc'] = res['rc']
return data 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): 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 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") display.debug("_low_level_execute_command(): starting")
# if not cmd: # if not cmd:
# # this can happen with powershell modules when there is no analog to a Windows command (like chmod) # # 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") # display.debug("_low_level_execute_command(): no command, exiting")
# return dict(stdout='', stderr='', rc=254) # return dict(stdout='', stderr='', rc=254)
if chdir: if chdir:
display.debug("_low_level_execute_command(): changing cwd to %s for this command" % 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) cmd = self._connection._shell.append_command('cd %s' % chdir, cmd)
allow_same_user = C.BECOME_ALLOW_SAME_USER if (sudoable and self._connection.transport != 'network_cli' and self._connection.become and
same_user = self._play_context.become_user == self._play_context.remote_user (C.BECOME_ALLOW_SAME_USER or
if sudoable and self._play_context.become and (allow_same_user or not same_user): self.get_become_option('become_user') != self._get_remote_user())):
display.debug("_low_level_execute_command(): using become for this command") 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._connection.become.build_become_command(cmd, self._connection._shell)
cmd = self._play_context.make_become_cmd(cmd, executable=executable)
if self._connection.allow_executable: if self._connection.allow_executable:
if executable is None: if executable is None:

View file

@ -37,12 +37,7 @@ class ActionModule(ActionBase):
else: else:
# inject the async directory based on the shell option into the # inject the async directory based on the shell option into the
# module args # module args
try: async_dir = self.get_shell_option('async_dir', default="~/.ansible_async")
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"
module_args = dict(jid=jid, mode=mode, _async_dir=async_dir) module_args = dict(jid=jid, mode=mode, _async_dir=async_dir)
status = self._execute_module(task_vars=task_vars, status = self._execute_module(task_vars=task_vars,

View file

@ -307,7 +307,7 @@ class ActionModule(ActionBase):
# Get the connect_timeout set on the connection to compare to the original # Get the connect_timeout set on the connection to compare to the original
try: try:
connect_timeout = self._connection.get_option('connection_timeout') connect_timeout = self._connection.get_option('connection_timeout')
except AnsibleError: except KeyError:
pass pass
else: else:
if original_connection_timeout != connect_timeout: if original_connection_timeout != connect_timeout:
@ -380,7 +380,7 @@ class ActionModule(ActionBase):
try: try:
original_connection_timeout = self._connection.get_option('connection_timeout') 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)) 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)) display.debug("{action}: connect_timeout connection option has not been set".format(action=self._task.action))
# Initiate reboot # Initiate reboot
reboot_result = self.perform_reboot(task_vars, distribution) reboot_result = self.perform_reboot(task_vars, distribution)

View 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

View 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)

View 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)])

View 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))

View 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)

View 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)])

View 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))

View 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))

View 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

View 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))

View 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))

View 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)])

View file

@ -6,19 +6,16 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import fcntl import fcntl
import gettext
import os import os
import shlex import shlex
from abc import abstractmethod, abstractproperty from abc import abstractmethod, abstractproperty
from functools import wraps from functools import wraps
from ansible import constants as C 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.module_utils._text import to_bytes, to_text
from ansible.plugins import AnsiblePlugin from ansible.plugins import AnsiblePlugin
from ansible.plugins.loader import shell_loader, connection_loader
from ansible.utils.display import Display from ansible.utils.display import Display
from ansible.plugins.loader import connection_loader, get_shell_plugin
from ansible.utils.path import unfrackpath from ansible.utils.path import unfrackpath
display = Display() display = Display()
@ -46,7 +43,7 @@ class ConnectionBase(AnsiblePlugin):
has_pipelining = False has_pipelining = False
has_native_async = False # eg, winrm has_native_async = False # eg, winrm
always_pipeline_modules = 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 # 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 # as discovered by the specified file extension. An empty string as the
# language means any language. # language means any language.
@ -66,11 +63,12 @@ class ConnectionBase(AnsiblePlugin):
# All these hasattrs allow subclasses to override these parameters # All these hasattrs allow subclasses to override these parameters
if not hasattr(self, '_play_context'): 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 self._play_context = play_context
if not hasattr(self, '_new_stdin'): if not hasattr(self, '_new_stdin'):
self._new_stdin = 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'): if not hasattr(self, '_display'):
# Backwards compat: self._display isn't really needed, just import the global display and use that.
self._display = display self._display = display
if not hasattr(self, '_connected'): if not hasattr(self, '_connected'):
self._connected = False self._connected = False
@ -80,30 +78,17 @@ class ConnectionBase(AnsiblePlugin):
self._connected = False self._connected = False
self._socket_path = None self._socket_path = None
if shell is not None: # helper plugins
self._shell = shell self._shell = shell
# load the shell plugin for this action/connection # we always must have shell
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)
if not self._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 @property
def connected(self): def connected(self):
@ -115,14 +100,6 @@ class ConnectionBase(AnsiblePlugin):
'''Read-only property holding the connection socket path for this remote host''' '''Read-only property holding the connection socket path for this remote host'''
return self._socket_path 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 @staticmethod
def _split_ssh_args(argstring): def _split_ssh_args(argstring):
""" """
@ -151,10 +128,6 @@ class ConnectionBase(AnsiblePlugin):
def _connect(self): def _connect(self):
"""Connect to the host we've been initialized with""" """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 @ensure_connect
@abstractmethod @abstractmethod
def exec_command(self, cmd, in_data=None, sudoable=True): def exec_command(self, cmd, in_data=None, sudoable=True):
@ -240,31 +213,6 @@ class ConnectionBase(AnsiblePlugin):
"""Terminate the connection""" """Terminate the connection"""
pass 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): def connection_lock(self):
f = self._play_context.connection_lockfd 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) 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): def reset(self):
display.warning("Reset is not implemented for this connection") 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): class NetworkConnectionBase(ConnectionBase):
""" """

View file

@ -63,7 +63,6 @@ class Connection(ConnectionBase):
# String used to identify this Connection class from other classes # String used to identify this Connection class from other classes
transport = 'buildah' transport = 'buildah'
has_pipelining = True has_pipelining = True
become_methods = frozenset(C.BECOME_METHODS)
def __init__(self, play_context, new_stdin, *args, **kwargs): def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)

View file

@ -59,7 +59,7 @@ class Connection(ConnectionBase):
# su currently has an undiagnosed issue with calculating the file # su currently has an undiagnosed issue with calculating the file
# checksums (so copy, for instance, doesn't work right) # checksums (so copy, for instance, doesn't work right)
# Have to look into that before re-enabling this # Have to look into that before re-enabling this
become_methods = frozenset(C.BECOME_METHODS).difference(('su',)) has_tty = False
default_user = 'root' default_user = 'root'

View file

@ -62,7 +62,6 @@ class Connection(ConnectionBase):
transport = 'docker' transport = 'docker'
has_pipelining = True has_pipelining = True
become_methods = frozenset(C.BECOME_METHODS)
def __init__(self, play_context, new_stdin, *args, **kwargs): def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)

View file

@ -56,8 +56,7 @@ class Connection(ConnectionBase):
# Pipelining may work. Someone needs to test by setting this to True and # Pipelining may work. Someone needs to test by setting this to True and
# having pipelining=True in their ansible.cfg # having pipelining=True in their ansible.cfg
has_pipelining = True has_pipelining = True
has_tty = False
become_methods = frozenset(C.BECOME_METHODS)
def __init__(self, play_context, new_stdin, *args, **kwargs): def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)

View file

@ -198,7 +198,6 @@ class Connection(ConnectionBase):
connection_options = CONNECTION_OPTIONS connection_options = CONNECTION_OPTIONS
documentation = DOCUMENTATION documentation = DOCUMENTATION
has_pipelining = True has_pipelining = True
become_methods = frozenset(C.BECOME_METHODS)
transport_cmd = None transport_cmd = None
def __init__(self, play_context, new_stdin, *args, **kwargs): def __init__(self, play_context, new_stdin, *args, **kwargs):

View file

@ -49,8 +49,8 @@ class Connection(ConnectionBase):
# su currently has an undiagnosed issue with calculating the file # su currently has an undiagnosed issue with calculating the file
# checksums (so copy, for instance, doesn't work right) # checksums (so copy, for instance, doesn't work right)
# Have to look into that before re-enabling this # Have to look into that before re-enabling this
become_methods = frozenset(C.BECOME_METHODS).difference(('su',))
default_user = 'root' default_user = 'root'
has_tty = False
def __init__(self, play_context, new_stdin, *args, **kwargs): def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)

View file

@ -83,7 +83,7 @@ class Connection(ConnectionBase):
) )
display.debug("done running command with Popen()") 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.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) fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK)
selector = selectors.DefaultSelector() selector = selectors.DefaultSelector()

View file

@ -54,7 +54,6 @@ class Connection(ConnectionBase):
transport = 'lxc' transport = 'lxc'
has_pipelining = True has_pipelining = True
become_methods = frozenset(C.BECOME_METHODS)
default_user = 'root' default_user = 'root'
def __init__(self, play_context, new_stdin, *args, **kwargs): def __init__(self, play_context, new_stdin, *args, **kwargs):

View file

@ -156,10 +156,8 @@ class Connection(NetworkConnectionBase):
def _connect(self): def _connect(self):
if not HAS_NAPALM: if not HAS_NAPALM:
raise AnsibleError( raise AnsibleError('The "napalm" python library is required to use the napalm connection type.\n')
'Napalm is required to use the napalm connection type.\n'
'Please run pip install napalm'
)
super(Connection, self)._connect() super(Connection, self)._connect()
if not self.connected: if not self.connected:

View file

@ -274,7 +274,7 @@ class Connection(NetworkConnectionBase):
def _connect(self): def _connect(self):
if not HAS_NCCLIENT: if not HAS_NCCLIENT:
raise AnsibleError( 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) 'Please run pip install ncclient' % to_native(NCCLIENT_IMP_ERR)
) )

View file

@ -415,7 +415,7 @@ class Connection(ConnectionBase):
try: try:
chan.exec_command(cmd) chan.exec_command(cmd)
if self._play_context.prompt: if self.become and self.become.expect_prompt():
passprompt = False passprompt = False
become_sucess = False become_sucess = False
while not (become_sucess or passprompt): while not (become_sucess or passprompt):

View file

@ -218,7 +218,6 @@ class Connection(ConnectionBase):
transport = 'psrp' transport = 'psrp'
module_implementation_preferences = ('.ps1', '.exe', '') module_implementation_preferences = ('.ps1', '.exe', '')
become_methods = ['runas']
allow_executable = False allow_executable = False
has_pipelining = True has_pipelining = True
allow_extras = True allow_extras = True

View file

@ -61,7 +61,6 @@ class Connection(ConnectionBase):
# String used to identify this Connection class from other classes # String used to identify this Connection class from other classes
transport = 'qubes' transport = 'qubes'
has_pipelining = True has_pipelining = True
become_methods = frozenset(C.BECOME_METHODS)
def __init__(self, play_context, new_stdin, *args, **kwargs): def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)

View file

@ -443,7 +443,6 @@ class Connection(ConnectionBase):
transport = 'ssh' transport = 'ssh'
has_pipelining = True has_pipelining = True
become_methods = frozenset(C.BECOME_METHODS).difference(['runas'])
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Connection, self).__init__(*args, **kwargs) super(Connection, self).__init__(*args, **kwargs)
@ -708,11 +707,11 @@ class Connection(ConnectionBase):
suppress_output = False suppress_output = False
# display.debug("Examining line (source=%s, state=%s): '%s'" % (source, state, display_line)) # 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)) display.debug("become_prompt: (source=%s, state=%s): '%s'" % (source, state, display_line))
self._flags['become_prompt'] = True self._flags['become_prompt'] = True
suppress_output = 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)) display.debug("become_success: (source=%s, state=%s): '%s'" % (source, state, display_line))
self._flags['become_success'] = True self._flags['become_success'] = True
suppress_output = True suppress_output = True
@ -811,11 +810,12 @@ class Connection(ConnectionBase):
state = states.index('ready_to_send') state = states.index('ready_to_send')
if to_bytes(self.get_option('ssh_executable')) in cmd and sudoable: 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 # We're requesting escalation with a password, so we have to
# wait for a password prompt. # wait for a password prompt.
state = states.index('awaiting_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: elif self._play_context.become and self._play_context.success_key:
# We're requesting escalation without a password, so we have to # We're requesting escalation without a password, so we have to
# detect success/failure before sending any initial data. # detect success/failure before sending any initial data.

View file

@ -176,7 +176,6 @@ class Connection(ConnectionBase):
transport = 'winrm' transport = 'winrm'
module_implementation_preferences = ('.ps1', '.exe', '') module_implementation_preferences = ('.ps1', '.exe', '')
become_methods = ['runas']
allow_executable = False allow_executable = False
has_pipelining = True has_pipelining = True
allow_extras = True allow_extras = True
@ -207,10 +206,6 @@ class Connection(ConnectionBase):
self._winrm_user = self.get_option('remote_user') self._winrm_user = self.get_option('remote_user')
self._winrm_pass = self._play_context.password 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_port = self.get_option('port')
self._winrm_scheme = self.get_option('scheme') self._winrm_scheme = self.get_option('scheme')

View file

@ -47,7 +47,7 @@ class Connection(ConnectionBase):
transport = 'zone' transport = 'zone'
has_pipelining = True has_pipelining = True
become_methods = frozenset(C.BECOME_METHODS).difference(('su',)) has_tty = False
def __init__(self, play_context, new_stdin, *args, **kwargs): def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)

View file

@ -18,13 +18,15 @@ from collections import defaultdict
from ansible import constants as C from ansible import constants as C
from ansible.errors import AnsibleError 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.utils.yaml import from_yaml
from ansible.parsing.yaml.loader import AnsibleLoader from ansible.parsing.yaml.loader import AnsibleLoader
from ansible.plugins import get_plugin_class, MODULE_CACHE, PATH_CACHE, PLUGIN_PATH_CACHE from ansible.plugins import get_plugin_class, MODULE_CACHE, PATH_CACHE, PLUGIN_PATH_CACHE
from ansible.utils.display import Display from ansible.utils.display import Display
from ansible.utils.plugin_docs import add_fragments from ansible.utils.plugin_docs import add_fragments
display = Display() display = Display()
@ -32,6 +34,52 @@ def get_all_plugin_loaders():
return [(name, obj) for (name, obj) in globals().items() if isinstance(obj, PluginLoader)] 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: class PluginLoader:
''' '''
PluginLoader loads plugins from the configured plugin directories. PluginLoader loads plugins from the configured plugin directories.
@ -394,6 +442,7 @@ class PluginLoader:
return None return None
self._display_plugin_load(self.class_name, name, self._searched_paths, path, found_in_cache=found_in_cache, class_only=class_only) 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: if not class_only:
try: try:
obj = obj(*args, **kwargs) obj = obj(*args, **kwargs)
@ -513,6 +562,7 @@ class PluginLoader:
continue continue
self._display_plugin_load(self.class_name, basename, self._searched_paths, path, found_in_cache=found_in_cache, class_only=class_only) 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: if not class_only:
try: try:
obj = obj(*args, **kwargs) obj = obj(*args, **kwargs)
@ -774,3 +824,10 @@ httpapi_loader = PluginLoader(
'httpapi_plugins', 'httpapi_plugins',
required_base_class='HttpApiBase', required_base_class='HttpApiBase',
) )
become_loader = PluginLoader(
'BecomeModule',
'ansible.plugins.become',
C.DEFAULT_BECOME_PLUGIN_PATH,
'become_plugins'
)

View file

@ -46,6 +46,7 @@ class ShellBase(AnsiblePlugin):
'LC_MESSAGES': module_locale} 'LC_MESSAGES': module_locale}
self.tmpdir = None self.tmpdir = None
self.executable = None
def _normalize_system_tmpdirs(self): def _normalize_system_tmpdirs(self):
# Normalize the tmp directory strings. We don't use expanduser/expandvars because those # 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) super(ShellBase, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
# set env # set env if needed, deal with environment's 'dual nature' list of dicts or dict
self.env.update(self.get_option('environment')) 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 # 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 # *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 # shell_common documentation fragment (and so do not have system_tmpdirs) will fail
try: try:
self._normalize_system_tmpdirs() self._normalize_system_tmpdirs()
except AnsibleError: except KeyError:
pass pass
def env_prefix(self, **kwargs): def env_prefix(self, **kwargs):

View file

@ -30,6 +30,10 @@ class ShellModule(ShellBase):
# Family of shells this has. Must match the filename without extension # Family of shells this has. Must match the filename without extension
SHELL_FAMILY = 'sh' SHELL_FAMILY = 'sh'
# commonly used
ECHO = 'echo'
COMMAND_SEP = ';'
# How to end lines in a python script one-liner # How to end lines in a python script one-liner
_SHELL_EMBEDDED_PY_EOL = '\n' _SHELL_EMBEDDED_PY_EOL = '\n'
_SHELL_REDIRECT_ALLNULL = '> /dev/null 2>&1' _SHELL_REDIRECT_ALLNULL = '> /dev/null 2>&1'

View file

@ -880,7 +880,7 @@ class StrategyBase:
host_results = [] host_results = []
for host in notified_hosts: 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) task_vars = self._variable_manager.get_vars(play=iterator._play, host=host, task=handler)
self.add_tqm_variables(task_vars, play=iterator._play) self.add_tqm_variables(task_vars, play=iterator._play)
self._queue_task(host, handler, task_vars, play_context) self._queue_task(host, handler, task_vars, play_context)
@ -1061,7 +1061,7 @@ class StrategyBase:
del self._active_connections[target_host] del self._active_connections[target_host]
else: else:
connection = connection_loader.get(play_context.connection, play_context, os.devnull) 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: if connection:
try: try:

View file

@ -235,7 +235,7 @@ class StrategyModule(StrategyBase):
for new_block in new_blocks: for new_block in new_blocks:
task_vars = self._variable_manager.get_vars(play=iterator._play, task=new_block._parent) 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: for host in hosts_left:
if host in included_file._hosts: if host in included_file._hosts:
all_blocks[host].append(final_block) all_blocks[host].append(final_block)

View file

@ -365,7 +365,7 @@ class StrategyModule(StrategyBase):
task=new_block._parent task=new_block._parent
) )
display.debug("filtering new block on tags") 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") display.debug("done filtering new block on tags")
noop_block = self._prepare_and_create_noop_block_from(final_block, task._parent, iterator) noop_block = self._prepare_and_create_noop_block_from(final_block, task._parent, iterator)

View file

@ -20,7 +20,6 @@ class Connection(ConnectionBase):
transport = 'dummy' transport = 'dummy'
has_pipelining = True has_pipelining = True
become_methods = frozenset(C.BECOME_METHODS)
def __init__(self, play_context, new_stdin, *args, **kwargs): def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)

View file

@ -7,20 +7,17 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import os import re
import pytest import pytest
from ansible import constants as C from ansible import constants as C
from ansible import context from ansible import context
from ansible.cli.arguments import optparse_helpers as opt_help from ansible.cli.arguments import optparse_helpers as opt_help
from ansible.errors import AnsibleError, AnsibleParserError from ansible.errors import AnsibleError
from ansible.module_utils.six.moves import shlex_quote
from ansible.playbook.play_context import PlayContext 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 ansible.utils import context_objects as co
from units.compat import unittest
from units.mock.loader import DictDataLoader
@pytest.fixture @pytest.fixture
@ -51,8 +48,13 @@ def test_play_context(mocker, parser, reset_cli_args):
(options, args) = parser.parse_args(['-vv', '--check']) (options, args) = parser.parse_args(['-vv', '--check'])
options.args = args options.args = args
context._init_global_context(options) 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._attributes['connection'] == C.DEFAULT_TRANSPORT
assert play_context.remote_addr is None assert play_context.remote_addr is None
assert play_context.remote_user 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.check_mode is True
assert play_context.no_log is None 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 = mocker.MagicMock()
mock_task.connection = 'mocktask' mock_task.connection = 'mocktask'
mock_task.remote_user = '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 = True
mock_task.become_method = 'mocktask' mock_task.become_method = 'mocktask'
mock_task.become_user = 'mocktaskroot' mock_task.become_user = 'mocktaskroot'
@ -101,7 +86,7 @@ def test_play_context(mocker, parser, reset_cli_args):
mock_templar = mocker.MagicMock() 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) play_context = play_context.set_task_and_variable_override(task=mock_task, variables=all_vars, templar=mock_templar)
assert play_context.connection == 'mock_inventory' 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_cmd = "/bin/foo"
default_exe = "/bin/bash" default_exe = "/bin/bash"
sudo_exe = C.DEFAULT_SUDO_EXE or 'sudo' sudo_exe = 'sudo'
sudo_flags = C.DEFAULT_SUDO_FLAGS sudo_flags = '-H -s -n'
su_exe = C.DEFAULT_SU_EXE or 'su' su_exe = 'su'
su_flags = C.DEFAULT_SU_FLAGS or '' su_flags = ''
pbrun_exe = 'pbrun' pbrun_exe = 'pbrun'
pbrun_flags = '' pbrun_flags = ''
pfexec_exe = 'pfexec' pfexec_exe = 'pfexec'
pfexec_flags = '' pfexec_flags = ''
doas_exe = 'doas' doas_exe = 'doas'
doas_flags = ' -n -u foo ' doas_flags = '-n'
ksu_exe = 'ksu' ksu_exe = 'ksu'
ksu_flags = '' ksu_flags = ''
dzdo_exe = 'dzdo' 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) cmd = play_context.make_become_cmd(cmd=default_cmd, executable=default_exe)
assert cmd == default_cmd assert cmd == default_cmd
success = 'BECOME-SUCCESS-.+?'
play_context.become = True play_context.become = True
play_context.become_user = 'foo' play_context.become_user = 'foo'
play_context.set_become_plugin(become_loader.get('sudo'))
play_context.become_method = 'sudo' play_context.become_flags = sudo_flags
cmd = play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash") 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' play_context.become_pass = 'testpass'
cmd = play_context.make_become_cmd(cmd=default_cmd, executable=default_exe) 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', ''), assert (re.match("""%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, r"\[sudo via ansible, key=.+?\] password:", play_context.become_user,
play_context.success_key, default_cmd)) default_exe, success, default_cmd), cmd) is not None)
play_context.become_pass = 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") 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, assert (re.match("""%s %s -c '%s -c '"'"'echo %s; %s'"'"''""" % (su_exe, play_context.become_user, default_exe,
play_context.success_key, default_cmd)) 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") 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") 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") 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") 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, assert (re.match("""%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)) 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): with pytest.raises(AnsibleError):
play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash") 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") 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_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") 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), 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, play_context.become_user, default_exe, success, default_cmd), cmd) is not None
play_context.success_key, default_cmd))

View file

@ -500,31 +500,31 @@ class TestActionBase(unittest.TestCase):
fake_loader = MagicMock() fake_loader = MagicMock()
fake_loader.get_basedir.return_value = os.getcwd() fake_loader.get_basedir.return_value = os.getcwd()
play_context = PlayContext() 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 action_base = DerivedActionBase(None, None, play_context, fake_loader, None, None)
play_context.become_user = play_context.remote_user = 'root' action_base.get_become_option = MagicMock(return_value='root')
play_context.make_become_cmd = MagicMock(return_value='CMD') 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) 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') 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 with patch.object(C, 'BECOME_ALLOW_SAME_USER', new=True):
C.BECOME_ALLOW_SAME_USER = True action_base._get_remote_user.return_value = 'root'
try:
play_context.remote_user = 'root'
action_base._low_level_execute_command('ECHO SAME', sudoable=True) action_base._low_level_execute_command('ECHO SAME', sudoable=True)
play_context.make_become_cmd.assert_called_once_with("ECHO SAME", executable=None) become.build_become_command.assert_called_once_with("ECHO SAME", shell)
finally:
C.BECOME_ALLOW_SAME_USER = become_allow_same_user
class TestActionBaseCleanReturnedData(unittest.TestCase): class TestActionBaseCleanReturnedData(unittest.TestCase):

View file

@ -30,6 +30,7 @@ from units.compat.mock import patch
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.playbook.play_context import PlayContext from ansible.playbook.play_context import PlayContext
from ansible.plugins.connection import ConnectionBase 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.accelerate import Connection as AccelerateConnection
# from ansible.plugins.connection.chroot import Connection as ChrootConnection # from ansible.plugins.connection.chroot import Connection as ChrootConnection
# from ansible.plugins.connection.funcd import Connection as FuncdConnection # 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 pass
c = ConnectionFoo(self.play_context, self.in_stream) 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(local))
self.assertTrue(c.check_password_prompt(ssh_pipelining_vvvv)) self.assertTrue(c.check_password_prompt(ssh_pipelining_vvvv))

View file

@ -34,7 +34,7 @@ from ansible.module_utils.six.moves import shlex_quote
from ansible.module_utils._text import to_bytes from ansible.module_utils._text import to_bytes
from ansible.playbook.play_context import PlayContext from ansible.playbook.play_context import PlayContext
from ansible.plugins.connection import ssh 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): class TestConnectionBaseClass(unittest.TestCase):
@ -93,6 +93,7 @@ class TestConnectionBaseClass(unittest.TestCase):
new_stdin = StringIO() new_stdin = StringIO()
conn = connection_loader.get('ssh', pc, new_stdin) conn = connection_loader.get('ssh', pc, new_stdin)
conn.set_become_plugin(become_loader.get('sudo'))
conn.check_password_prompt = MagicMock() conn.check_password_prompt = MagicMock()
conn.check_become_success = MagicMock() conn.check_become_success = MagicMock()
@ -133,6 +134,14 @@ class TestConnectionBaseClass(unittest.TestCase):
) )
pc.prompt = True 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) 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(output, b'line 1\nline 2\nline 3\n')
self.assertEqual(unprocessed, b'this should be the remainder') self.assertEqual(unprocessed, b'this should be the remainder')
@ -150,7 +159,9 @@ class TestConnectionBaseClass(unittest.TestCase):
) )
pc.prompt = False pc.prompt = False
conn.become.prompt = False
pc.success_key = u'BECOME-SUCCESS-abcdefghijklmnopqrstuvxyz' 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) 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(output, b'line 1\nline 2\nline 3\n')
self.assertEqual(unprocessed, b'') self.assertEqual(unprocessed, b'')
@ -168,6 +179,7 @@ class TestConnectionBaseClass(unittest.TestCase):
) )
pc.prompt = False pc.prompt = False
conn.become.prompt = False
pc.success_key = None pc.success_key = None
output, unprocessed = conn._examine_output(u'source', u'state', b'line 1\nline 2\nincorrect password\n', True) 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') self.assertEqual(output, b'line 1\nline 2\nincorrect password\n')
@ -186,6 +198,7 @@ class TestConnectionBaseClass(unittest.TestCase):
) )
pc.prompt = False pc.prompt = False
conn.become.prompt = False
pc.success_key = None pc.success_key = None
output, unprocessed = conn._examine_output(u'source', u'state', b'line 1\nbad password\n', True) 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') self.assertEqual(output, b'line 1\nbad password\n')
@ -332,6 +345,7 @@ def mock_run_env(request, mocker):
new_stdin = StringIO() new_stdin = StringIO()
conn = connection_loader.get('ssh', pc, new_stdin) conn = connection_loader.get('ssh', pc, new_stdin)
conn.set_become_plugin(become_loader.get('sudo'))
conn._send_initial_data = MagicMock() conn._send_initial_data = MagicMock()
conn._examine_output = MagicMock() conn._examine_output = MagicMock()
conn._terminate_process = MagicMock() conn._terminate_process = MagicMock()
@ -425,7 +439,7 @@ class TestSSHConnectionRun(object):
def test_password_with_prompt(self): def test_password_with_prompt(self):
# test with password prompting enabled # test with password prompting enabled
self.pc.password = None 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.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.stdout.read.side_effect = [b"Password:", b"Success", b""]
self.mock_popen_res.stderr.read.side_effect = [b""] self.mock_popen_res.stderr.read.side_effect = [b""]
@ -450,8 +464,10 @@ class TestSSHConnectionRun(object):
def test_password_with_become(self): def test_password_with_become(self):
# test with some become settings # test with some become settings
self.pc.prompt = b'Password:' self.pc.prompt = b'Password:'
self.conn.become.prompt = b'Password:'
self.pc.become = True self.pc.become = True
self.pc.success_key = 'BECOME-SUCCESS-abcdefg' 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.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.stdout.read.side_effect = [b"Password:", b"BECOME-SUCCESS-abcdefg", b"abc"]
self.mock_popen_res.stderr.read.side_effect = [b"123"] self.mock_popen_res.stderr.read.side_effect = [b"123"]