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

@ -24,6 +24,8 @@ __metaclass__ = type
from abc import ABCMeta
from ansible import constants as C
from ansible.errors import AnsibleError
from ansible.module_utils._text import to_native
from ansible.module_utils.six import with_metaclass, string_types
from ansible.utils.display import Display
@ -52,7 +54,10 @@ class AnsiblePlugin(with_metaclass(ABCMeta, object)):
def get_option(self, option, hostvars=None):
if option not in self._options:
option_value = C.config.get_config_value(option, plugin_type=get_plugin_class(self), plugin_name=self._load_name, variables=hostvars)
try:
option_value = C.config.get_config_value(option, plugin_type=get_plugin_class(self), plugin_name=self._load_name, variables=hostvars)
except AnsibleError as e:
raise KeyError(to_native(e))
self.set_option(option, option_value)
return self._options.get(option)

View file

@ -108,6 +108,24 @@ class ActionBase(with_metaclass(ABCMeta, object)):
return result
def get_plugin_option(self, plugin, option, default=None):
"""Helper to get an option from a plugin without having to use
the try/except dance everywhere to set a default
"""
try:
return plugin.get_option(option)
except (AttributeError, KeyError):
return default
def get_become_option(self, option, default=None):
return self.get_plugin_option(self._connection.become, option, default=default)
def get_connection_option(self, option, default=None):
return self.get_plugin_option(self._connection, option, default=default)
def get_shell_option(self, option, default=None):
return self.get_plugin_option(self._connection._shell, option, default=default)
def _remote_file_exists(self, path):
cmd = self._connection._shell.exists(path)
result = self._low_level_execute_command(cmd=cmd, sudoable=True)
@ -241,12 +259,23 @@ class ActionBase(with_metaclass(ABCMeta, object)):
Returns a list of admin users that are configured for the current shell
plugin
'''
return self.get_shell_option('admin_users', ['root'])
def _get_remote_user(self):
''' consistently get the 'remote_user' for the action plugin '''
# TODO: use 'current user running ansible' as fallback when moving away from play_context
# pwd.getpwuid(os.getuid()).pw_name
remote_user = None
try:
admin_users = self._connection._shell.get_option('admin_users')
except AnsibleError:
# fallback for old custom plugins w/o get_option
admin_users = ['root']
return admin_users
remote_user = self._connection.get_option('remote_user')
except KeyError:
# plugin does not have remote_user option, fallback to default and/play_context
remote_user = getattr(self._connection, 'default_user', None) or self._play_context.remote_user
except AttributeError:
# plugin does not use config system, fallback to old play_context
remote_user = self._play_context.remote_user
return remote_user
def _is_become_unprivileged(self):
'''
@ -261,11 +290,8 @@ class ActionBase(with_metaclass(ABCMeta, object)):
# if we use become and the user is not an admin (or same user) then
# we need to return become_unprivileged as True
admin_users = self._get_admin_users()
try:
remote_user = self._connection.get_option('remote_user')
except AnsibleError:
remote_user = self._play_context.remote_user
return bool(self._play_context.become_user not in admin_users + [remote_user])
remote_user = self._get_remote_user()
return bool(self.get_become_option('become_user') not in admin_users + [remote_user])
def _make_tmp_path(self, remote_user=None):
'''
@ -273,10 +299,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
'''
become_unprivileged = self._is_become_unprivileged()
try:
remote_tmp = self._connection._shell.get_option('remote_tmp')
except AnsibleError:
remote_tmp = '~/.ansible/tmp'
remote_tmp = self.get_shell_option('remote_tmp', default='~/.ansible/tmp')
# deal with tmpdir creation
basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48))
@ -409,7 +432,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
"allow_world_readable_tmpfiles" in the ansible.cfg
"""
if remote_user is None:
remote_user = self._play_context.remote_user
remote_user = self._get_remote_user()
if self._connection._shell.SHELL_FAMILY == 'powershell':
# This won't work on Powershell as-is, so we'll just completely skip until
@ -432,7 +455,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
# start to we'll have to fix this.
setfacl_mode = 'r-X'
res = self._remote_set_user_facl(remote_paths, self._play_context.become_user, setfacl_mode)
res = self._remote_set_user_facl(remote_paths, self.get_become_option('become_user'), setfacl_mode)
if res['rc'] != 0:
# File system acls failed; let's try to use chown next
# Set executable bit first as on some systems an
@ -442,7 +465,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
if res['rc'] != 0:
raise AnsibleError('Failed to set file mode on remote temporary files (rc: {0}, err: {1})'.format(res['rc'], to_native(res['stderr'])))
res = self._remote_chown(remote_paths, self._play_context.become_user)
res = self._remote_chown(remote_paths, self.get_become_option('become_user'))
if res['rc'] != 0 and remote_user in self._get_admin_users():
# chown failed even if remote_user is administrator/root
raise AnsibleError('Failed to change ownership of the temporary files Ansible needs to create despite connecting as a privileged user. '
@ -579,13 +602,14 @@ class ActionBase(with_metaclass(ABCMeta, object)):
# Network connection plugins (network_cli, netconf, etc.) execute on the controller, rather than the remote host.
# As such, we want to avoid using remote_user for paths as remote_user may not line up with the local user
# This is a hack and should be solved by more intelligent handling of remote_tmp in 2.7
become_user = self.get_become_option('become_user')
if getattr(self._connection, '_remote_is_local', False):
pass
elif sudoable and self._play_context.become and self._play_context.become_user:
expand_path = '~%s' % self._play_context.become_user
elif sudoable and self._play_context.become and become_user:
expand_path = '~%s' % become_user
else:
# use remote user instead, if none set default to current user
expand_path = '~%s' % (self._play_context.remote_user or self._connection.default_user or '')
expand_path = '~%s' % (self._get_remote_user() or '')
# use shell to construct appropriate command and execute
cmd = self._connection._shell.expand_user(expand_path)
@ -673,26 +697,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
module_args['_ansible_tmpdir'] = self._connection._shell.tmpdir
# make sure the remote_tmp value is sent through in case modules needs to create their own
try:
module_args['_ansible_remote_tmp'] = self._connection._shell.get_option('remote_tmp')
except KeyError:
# here for 3rd party shell plugin compatibility in case they do not define the remote_tmp option
module_args['_ansible_remote_tmp'] = '~/.ansible/tmp'
def _update_connection_options(self, options, variables=None):
''' ensures connections have the appropriate information '''
update = {}
if getattr(self.connection, 'glob_option_vars', False):
# if the connection allows for it, pass any variables matching it.
if variables is not None:
for varname in variables:
if varname.match('ansible_%s_' % self.connection._load_name):
update[varname] = variables[varname]
# always override existing with options
update.update(options)
self.connection.set_options(update)
module_args['_ansible_remote_tmp'] = self.get_shell_option('remote_tmp', default='~/.ansible/tmp')
def _execute_module(self, module_name=None, module_args=None, tmp=None, task_vars=None, persist_files=False, delete_remote_tmp=None, wrap_async=False):
'''
@ -748,11 +753,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
# ANSIBLE_ASYNC_DIR is not set on the task, we get the value
# from the shell option and temporarily add to the environment
# list for async_wrapper to pick up
try:
async_dir = self._connection._shell.get_option('async_dir')
except KeyError:
# in case 3rd party plugin has not set this, use the default
async_dir = "~/.ansible_async"
async_dir = self.get_shell_option('async_dir', default="~/.ansible_async")
remove_async_dir = len(self._task.environment)
self._task.environment.append({"ANSIBLE_ASYNC_DIR": async_dir})
@ -861,7 +862,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
if remote_files:
# remove none/empty
remote_files = [x for x in remote_files if x]
self._fixup_perms2(remote_files, self._play_context.remote_user)
self._fixup_perms2(remote_files, self._get_remote_user())
# actually execute
res = self._low_level_execute_command(cmd, sudoable=sudoable, in_data=in_data)
@ -934,6 +935,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
data['rc'] = res['rc']
return data
# FIXME: move to connection base
def _low_level_execute_command(self, cmd, sudoable=True, in_data=None, executable=None, encoding_errors='surrogate_then_replace', chdir=None):
'''
This is the function which executes the low level shell command, which
@ -951,21 +953,20 @@ class ActionBase(with_metaclass(ABCMeta, object)):
'''
display.debug("_low_level_execute_command(): starting")
# if not cmd:
# # this can happen with powershell modules when there is no analog to a Windows command (like chmod)
# display.debug("_low_level_execute_command(): no command, exiting")
# return dict(stdout='', stderr='', rc=254)
# if not cmd:
# # this can happen with powershell modules when there is no analog to a Windows command (like chmod)
# display.debug("_low_level_execute_command(): no command, exiting")
# return dict(stdout='', stderr='', rc=254)
if chdir:
display.debug("_low_level_execute_command(): changing cwd to %s for this command" % chdir)
cmd = self._connection._shell.append_command('cd %s' % chdir, cmd)
allow_same_user = C.BECOME_ALLOW_SAME_USER
same_user = self._play_context.become_user == self._play_context.remote_user
if sudoable and self._play_context.become and (allow_same_user or not same_user):
if (sudoable and self._connection.transport != 'network_cli' and self._connection.become and
(C.BECOME_ALLOW_SAME_USER or
self.get_become_option('become_user') != self._get_remote_user())):
display.debug("_low_level_execute_command(): using become for this command")
if self._connection.transport != 'network_cli' and self._play_context.become_method != 'enable':
cmd = self._play_context.make_become_cmd(cmd, executable=executable)
cmd = self._connection.become.build_become_command(cmd, self._connection._shell)
if self._connection.allow_executable:
if executable is None:

View file

@ -37,12 +37,7 @@ class ActionModule(ActionBase):
else:
# inject the async directory based on the shell option into the
# module args
try:
async_dir = self._connection._shell.get_option('async_dir')
except KeyError:
# here for 3rd party shell plugin compatibility in case they do
# not define the async_dir option
async_dir = "~/.ansible_async"
async_dir = self.get_shell_option('async_dir', default="~/.ansible_async")
module_args = dict(jid=jid, mode=mode, _async_dir=async_dir)
status = self._execute_module(task_vars=task_vars,

View file

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

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
import fcntl
import gettext
import os
import shlex
from abc import abstractmethod, abstractproperty
from functools import wraps
from ansible import constants as C
from ansible.errors import AnsibleError
from ansible.module_utils.six import string_types
from ansible.module_utils._text import to_bytes, to_text
from ansible.plugins import AnsiblePlugin
from ansible.plugins.loader import shell_loader, connection_loader
from ansible.utils.display import Display
from ansible.plugins.loader import connection_loader, get_shell_plugin
from ansible.utils.path import unfrackpath
display = Display()
@ -46,7 +43,7 @@ class ConnectionBase(AnsiblePlugin):
has_pipelining = False
has_native_async = False # eg, winrm
always_pipeline_modules = False # eg, winrm
become_methods = C.BECOME_METHODS
has_tty = True # for interacting with become plugins
# When running over this connection type, prefer modules written in a certain language
# as discovered by the specified file extension. An empty string as the
# language means any language.
@ -66,11 +63,12 @@ class ConnectionBase(AnsiblePlugin):
# All these hasattrs allow subclasses to override these parameters
if not hasattr(self, '_play_context'):
# Backwards compat: self._play_context isn't really needed, using set_options/get_option
self._play_context = play_context
if not hasattr(self, '_new_stdin'):
self._new_stdin = new_stdin
# Backwards compat: self._display isn't really needed, just import the global display and use that.
if not hasattr(self, '_display'):
# Backwards compat: self._display isn't really needed, just import the global display and use that.
self._display = display
if not hasattr(self, '_connected'):
self._connected = False
@ -80,30 +78,17 @@ class ConnectionBase(AnsiblePlugin):
self._connected = False
self._socket_path = None
if shell is not None:
self._shell = shell
# helper plugins
self._shell = shell
# load the shell plugin for this action/connection
if play_context.shell:
shell_type = play_context.shell
elif hasattr(self, '_shell_type'):
shell_type = getattr(self, '_shell_type')
else:
shell_type = 'sh'
shell_filename = os.path.basename(self._play_context.executable)
try:
shell = shell_loader.get(shell_filename)
except Exception:
shell = None
if shell is None:
for shell in shell_loader.all():
if shell_filename in shell.COMPATIBLE_SHELLS:
break
shell_type = shell.SHELL_FAMILY
self._shell = shell_loader.get(shell_type)
# we always must have shell
if not self._shell:
raise AnsibleError("Invalid shell type specified (%s), or the plugin for that shell type is missing." % shell_type)
self._shell = get_shell_plugin(shell_type=getattr(self, '_shell_type', None), executable=self._play_context.executable)
self.become = None
def set_become_plugin(self, plugin):
self.become = plugin
@property
def connected(self):
@ -115,14 +100,6 @@ class ConnectionBase(AnsiblePlugin):
'''Read-only property holding the connection socket path for this remote host'''
return self._socket_path
def _become_method_supported(self):
''' Checks if the current class supports this privilege escalation method '''
if self._play_context.become_method in self.become_methods:
return True
raise AnsibleError("Internal Error: this connection module does not support running commands via %s" % self._play_context.become_method)
@staticmethod
def _split_ssh_args(argstring):
"""
@ -151,10 +128,6 @@ class ConnectionBase(AnsiblePlugin):
def _connect(self):
"""Connect to the host we've been initialized with"""
# Check if PE is supported
if self._play_context.become:
self._become_method_supported()
@ensure_connect
@abstractmethod
def exec_command(self, cmd, in_data=None, sudoable=True):
@ -240,31 +213,6 @@ class ConnectionBase(AnsiblePlugin):
"""Terminate the connection"""
pass
def check_become_success(self, b_output):
b_success_key = to_bytes(self._play_context.success_key)
for b_line in b_output.splitlines(True):
if b_success_key == b_line.rstrip():
return True
return False
def check_password_prompt(self, b_output):
if self._play_context.prompt is None:
return False
elif isinstance(self._play_context.prompt, string_types):
b_prompt = to_bytes(self._play_context.prompt).strip()
b_lines = b_output.splitlines()
return any(l.strip().startswith(b_prompt) for l in b_lines)
else:
return self._play_context.prompt(b_output)
def check_incorrect_password(self, b_output):
b_incorrect_password = to_bytes(gettext.dgettext(self._play_context.become_method, C.BECOME_ERROR_STRINGS[self._play_context.become_method]))
return b_incorrect_password and b_incorrect_password in b_output
def check_missing_password(self, b_output):
b_missing_password = to_bytes(gettext.dgettext(self._play_context.become_method, C.BECOME_MISSING_STRINGS[self._play_context.become_method]))
return b_missing_password and b_missing_password in b_output
def connection_lock(self):
f = self._play_context.connection_lockfd
display.vvvv('CONNECTION: pid %d waiting for lock on %d' % (os.getpid(), f), host=self._play_context.remote_addr)
@ -279,6 +227,39 @@ class ConnectionBase(AnsiblePlugin):
def reset(self):
display.warning("Reset is not implemented for this connection")
# NOTE: these password functions are all become specific, the name is
# confusing as it does not handle 'protocol passwords'
# DEPRECATED:
# These are kept for backwards compatiblity
# Use the methods provided by the become plugins instead
def check_become_success(self, b_output):
display.deprecated(
"Connection.check_become_success is deprecated, calling code should be using become plugins instead",
version="2.12"
)
return self.become.check_success(b_output)
def check_password_prompt(self, b_output):
display.deprecated(
"Connection.check_password_prompt is deprecated, calling code should be using become plugins instead",
version="2.12"
)
return self.become.check_password_prompt(b_output)
def check_incorrect_password(self, b_output):
display.deprecated(
"Connection.check_incorrect_password is deprecated, calling code should be using become plugins instead",
version="2.12"
)
return self.become.check_incorrect_password(b_output)
def check_missing_password(self, b_output):
display.deprecated(
"Connection.check_missing_password is deprecated, calling code should be using become plugins instead",
version="2.12"
)
return self.become.check_missing_password(b_output)
class NetworkConnectionBase(ConnectionBase):
"""

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -83,7 +83,7 @@ class Connection(ConnectionBase):
)
display.debug("done running command with Popen()")
if self._play_context.prompt and sudoable:
if self.become and self.become.expect_prompt() and sudoable:
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK)
fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK)
selector = selectors.DefaultSelector()

View file

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

View file

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

View file

@ -274,7 +274,7 @@ class Connection(NetworkConnectionBase):
def _connect(self):
if not HAS_NCCLIENT:
raise AnsibleError(
'ncclient is required to use the netconf connection type: %s.\n'
'The required "ncclient" python library is required to use the netconf connection type: %s.\n'
'Please run pip install ncclient' % to_native(NCCLIENT_IMP_ERR)
)

View file

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

View file

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

View file

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

View file

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

View file

@ -176,7 +176,6 @@ class Connection(ConnectionBase):
transport = 'winrm'
module_implementation_preferences = ('.ps1', '.exe', '')
become_methods = ['runas']
allow_executable = False
has_pipelining = True
allow_extras = True
@ -207,10 +206,6 @@ class Connection(ConnectionBase):
self._winrm_user = self.get_option('remote_user')
self._winrm_pass = self._play_context.password
self._become_method = self._play_context.become_method
self._become_user = self._play_context.become_user
self._become_pass = self._play_context.become_pass
self._winrm_port = self.get_option('port')
self._winrm_scheme = self.get_option('scheme')

View file

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

View file

@ -18,13 +18,15 @@ from collections import defaultdict
from ansible import constants as C
from ansible.errors import AnsibleError
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils._text import to_bytes, to_text, to_native
from ansible.module_utils.six import string_types
from ansible.parsing.utils.yaml import from_yaml
from ansible.parsing.yaml.loader import AnsibleLoader
from ansible.plugins import get_plugin_class, MODULE_CACHE, PATH_CACHE, PLUGIN_PATH_CACHE
from ansible.utils.display import Display
from ansible.utils.plugin_docs import add_fragments
display = Display()
@ -32,6 +34,52 @@ def get_all_plugin_loaders():
return [(name, obj) for (name, obj) in globals().items() if isinstance(obj, PluginLoader)]
def add_all_plugin_dirs(path):
''' add any existing plugin dirs in the path provided '''
b_path = to_bytes(path, errors='surrogate_or_strict')
if os.path.isdir(b_path):
for name, obj in get_all_plugin_loaders():
if obj.subdir:
plugin_path = os.path.join(b_path, to_bytes(obj.subdir))
if os.path.isdir(plugin_path):
obj.add_directory(to_text(plugin_path))
else:
display.warning("Ignoring invalid path provided to plugin path: %s is not a directory" % to_native(path))
def get_shell_plugin(shell_type=None, executable=None):
if not shell_type:
# default to sh
shell_type = 'sh'
# mostly for backwards compat
if executable:
if isinstance(executable, string_types):
shell_filename = os.path.basename(executable)
try:
shell = shell_loader.get(shell_filename)
except Exception:
shell = None
if shell is None:
for shell in shell_loader.all():
if shell_filename in shell.COMPATIBLE_SHELLS:
shell_type = shell.SHELL_FAMILY
break
else:
raise AnsibleError("Either a shell type or a shell executable must be provided ")
shell = shell_loader.get(shell_type)
if not shell:
raise AnsibleError("Could not find the shell plugin required (%s)." % shell_type)
if executable:
setattr(shell, 'executable', executable)
return shell
class PluginLoader:
'''
PluginLoader loads plugins from the configured plugin directories.
@ -394,6 +442,7 @@ class PluginLoader:
return None
self._display_plugin_load(self.class_name, name, self._searched_paths, path, found_in_cache=found_in_cache, class_only=class_only)
if not class_only:
try:
obj = obj(*args, **kwargs)
@ -513,6 +562,7 @@ class PluginLoader:
continue
self._display_plugin_load(self.class_name, basename, self._searched_paths, path, found_in_cache=found_in_cache, class_only=class_only)
if not class_only:
try:
obj = obj(*args, **kwargs)
@ -774,3 +824,10 @@ httpapi_loader = PluginLoader(
'httpapi_plugins',
required_base_class='HttpApiBase',
)
become_loader = PluginLoader(
'BecomeModule',
'ansible.plugins.become',
C.DEFAULT_BECOME_PLUGIN_PATH,
'become_plugins'
)

View file

@ -46,6 +46,7 @@ class ShellBase(AnsiblePlugin):
'LC_MESSAGES': module_locale}
self.tmpdir = None
self.executable = None
def _normalize_system_tmpdirs(self):
# Normalize the tmp directory strings. We don't use expanduser/expandvars because those
@ -65,15 +66,20 @@ class ShellBase(AnsiblePlugin):
super(ShellBase, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
# set env
self.env.update(self.get_option('environment'))
# set env if needed, deal with environment's 'dual nature' list of dicts or dict
env = self.get_option('environment')
if isinstance(env, list):
for env_dict in env:
self.env.update(env_dict)
else:
self.env.update(env)
# We can remove the try: except in the future when we make ShellBase a proper subset of
# *all* shells. Right now powershell and third party shells which do not use the
# shell_common documentation fragment (and so do not have system_tmpdirs) will fail
try:
self._normalize_system_tmpdirs()
except AnsibleError:
except KeyError:
pass
def env_prefix(self, **kwargs):

View file

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

View file

@ -880,7 +880,7 @@ class StrategyBase:
host_results = []
for host in notified_hosts:
if not iterator.is_failed(host) or play_context.force_handlers:
if not iterator.is_failed(host) or iterator._play.force_handlers:
task_vars = self._variable_manager.get_vars(play=iterator._play, host=host, task=handler)
self.add_tqm_variables(task_vars, play=iterator._play)
self._queue_task(host, handler, task_vars, play_context)
@ -1061,7 +1061,7 @@ class StrategyBase:
del self._active_connections[target_host]
else:
connection = connection_loader.get(play_context.connection, play_context, os.devnull)
play_context.set_options_from_plugin(connection)
play_context.set_attributes_from_plugin(connection)
if connection:
try:

View file

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

View file

@ -365,7 +365,7 @@ class StrategyModule(StrategyBase):
task=new_block._parent
)
display.debug("filtering new block on tags")
final_block = new_block.filter_tagged_tasks(play_context, task_vars)
final_block = new_block.filter_tagged_tasks(task_vars)
display.debug("done filtering new block on tags")
noop_block = self._prepare_and_create_noop_block_from(final_block, task._parent, iterator)