Started implementing become in v2

This commit is contained in:
James Cammarata 2015-03-13 11:57:27 -05:00
parent f451974efe
commit 070c7c319f
16 changed files with 236 additions and 93 deletions

View file

@ -24,7 +24,6 @@ import pwd
import sys import sys
from . compat import configparser from . compat import configparser
from string import ascii_letters, digits from string import ascii_letters, digits
# copied from utils, avoid circular reference fun :) # copied from utils, avoid circular reference fun :)
@ -143,6 +142,19 @@ DEFAULT_ASK_SU_PASS = get_config(p, DEFAULTS, 'ask_su_pass', 'ANSIBLE_ASK_
DEFAULT_GATHERING = get_config(p, DEFAULTS, 'gathering', 'ANSIBLE_GATHERING', 'implicit').lower() DEFAULT_GATHERING = get_config(p, DEFAULTS, 'gathering', 'ANSIBLE_GATHERING', 'implicit').lower()
DEFAULT_LOG_PATH = shell_expand_path(get_config(p, DEFAULTS, 'log_path', 'ANSIBLE_LOG_PATH', '')) DEFAULT_LOG_PATH = shell_expand_path(get_config(p, DEFAULTS, 'log_path', 'ANSIBLE_LOG_PATH', ''))
#TODO: get rid of ternary chain mess
BECOME_METHODS = ['sudo','su','pbrun','pfexec','runas']
BECOME_ERROR_STRINGS = {'sudo': 'Sorry, try again.', 'su': 'Authentication failure', 'pbrun': '', 'pfexec': '', 'runas': ''}
DEFAULT_BECOME = get_config(p, 'privilege_escalation', 'become', 'ANSIBLE_BECOME',True if DEFAULT_SUDO or DEFAULT_SU else False, boolean=True)
DEFAULT_BECOME_METHOD = get_config(p, 'privilege_escalation', 'become_method', 'ANSIBLE_BECOME_METHOD','sudo' if DEFAULT_SUDO else 'su' if DEFAULT_SU else 'sudo' ).lower()
DEFAULT_BECOME_USER = get_config(p, 'privilege_escalation', 'become_user', 'ANSIBLE_BECOME_USER',DEFAULT_SUDO_USER if DEFAULT_SUDO else DEFAULT_SU_USER if DEFAULT_SU else 'root')
DEFAULT_BECOME_ASK_PASS = get_config(p, 'privilege_escalation', 'become_ask_pass', 'ANSIBLE_BECOME_ASK_PASS',True if DEFAULT_ASK_SUDO_PASS else False, boolean=True)
# need to rethink impementing these 2
DEFAULT_BECOME_EXE = None
#DEFAULT_BECOME_EXE = get_config(p, DEFAULTS, 'become_exe', 'ANSIBLE_BECOME_EXE','sudo' if DEFAULT_SUDO else 'su' if DEFAULT_SU else 'sudo')
#DEFAULT_BECOME_FLAGS = get_config(p, DEFAULTS, 'become_flags', 'ANSIBLE_BECOME_FLAGS',DEFAULT_SUDO_FLAGS if DEFAULT_SUDO else DEFAULT_SU_FLAGS if DEFAULT_SU else '-H')
DEFAULT_ACTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'action_plugins', 'ANSIBLE_ACTION_PLUGINS', '~/.ansible/plugins/action_plugins:/usr/share/ansible_plugins/action_plugins') DEFAULT_ACTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'action_plugins', 'ANSIBLE_ACTION_PLUGINS', '~/.ansible/plugins/action_plugins:/usr/share/ansible_plugins/action_plugins')
DEFAULT_CACHE_PLUGIN_PATH = get_config(p, DEFAULTS, 'cache_plugins', 'ANSIBLE_CACHE_PLUGINS', '~/.ansible/plugins/cache_plugins:/usr/share/ansible_plugins/cache_plugins') DEFAULT_CACHE_PLUGIN_PATH = get_config(p, DEFAULTS, 'cache_plugins', 'ANSIBLE_CACHE_PLUGINS', '~/.ansible/plugins/cache_plugins:/usr/share/ansible_plugins/cache_plugins')
DEFAULT_CALLBACK_PLUGIN_PATH = get_config(p, DEFAULTS, 'callback_plugins', 'ANSIBLE_CALLBACK_PLUGINS', '~/.ansible/plugins/callback_plugins:/usr/share/ansible_plugins/callback_plugins') DEFAULT_CALLBACK_PLUGIN_PATH = get_config(p, DEFAULTS, 'callback_plugins', 'ANSIBLE_CALLBACK_PLUGINS', '~/.ansible/plugins/callback_plugins:/usr/share/ansible_plugins/callback_plugins')
@ -168,12 +180,15 @@ DEFAULT_CALLABLE_WHITELIST = get_config(p, DEFAULTS, 'callable_whitelist', '
COMMAND_WARNINGS = get_config(p, DEFAULTS, 'command_warnings', 'ANSIBLE_COMMAND_WARNINGS', False, boolean=True) COMMAND_WARNINGS = get_config(p, DEFAULTS, 'command_warnings', 'ANSIBLE_COMMAND_WARNINGS', False, boolean=True)
DEFAULT_LOAD_CALLBACK_PLUGINS = get_config(p, DEFAULTS, 'bin_ansible_callbacks', 'ANSIBLE_LOAD_CALLBACK_PLUGINS', False, boolean=True) DEFAULT_LOAD_CALLBACK_PLUGINS = get_config(p, DEFAULTS, 'bin_ansible_callbacks', 'ANSIBLE_LOAD_CALLBACK_PLUGINS', False, boolean=True)
RETRY_FILES_ENABLED = get_config(p, DEFAULTS, 'retry_files_enabled', 'ANSIBLE_RETRY_FILES_ENABLED', True, boolean=True)
RETRY_FILES_SAVE_PATH = get_config(p, DEFAULTS, 'retry_files_save_path', 'ANSIBLE_RETRY_FILES_SAVE_PATH', '~/')
# CONNECTION RELATED # CONNECTION RELATED
ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None) ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None)
ANSIBLE_SSH_CONTROL_PATH = get_config(p, 'ssh_connection', 'control_path', 'ANSIBLE_SSH_CONTROL_PATH', "%(directory)s/ansible-ssh-%%h-%%p-%%r") ANSIBLE_SSH_CONTROL_PATH = get_config(p, 'ssh_connection', 'control_path', 'ANSIBLE_SSH_CONTROL_PATH', "%(directory)s/ansible-ssh-%%h-%%p-%%r")
ANSIBLE_SSH_PIPELINING = get_config(p, 'ssh_connection', 'pipelining', 'ANSIBLE_SSH_PIPELINING', False, boolean=True) ANSIBLE_SSH_PIPELINING = get_config(p, 'ssh_connection', 'pipelining', 'ANSIBLE_SSH_PIPELINING', False, boolean=True)
PARAMIKO_RECORD_HOST_KEYS = get_config(p, 'paramiko_connection', 'record_host_keys', 'ANSIBLE_PARAMIKO_RECORD_HOST_KEYS', True, boolean=True) PARAMIKO_RECORD_HOST_KEYS = get_config(p, 'paramiko_connection', 'record_host_keys', 'ANSIBLE_PARAMIKO_RECORD_HOST_KEYS', True, boolean=True)
# obsolete -- will be formally removed in 1.6 # obsolete -- will be formally removed
ZEROMQ_PORT = get_config(p, 'fireball_connection', 'zeromq_port', 'ANSIBLE_ZEROMQ_PORT', 5099, integer=True) ZEROMQ_PORT = get_config(p, 'fireball_connection', 'zeromq_port', 'ANSIBLE_ZEROMQ_PORT', 5099, integer=True)
ACCELERATE_PORT = get_config(p, 'accelerate', 'accelerate_port', 'ACCELERATE_PORT', 5099, integer=True) ACCELERATE_PORT = get_config(p, 'accelerate', 'accelerate_port', 'ACCELERATE_PORT', 5099, integer=True)
ACCELERATE_TIMEOUT = get_config(p, 'accelerate', 'accelerate_timeout', 'ACCELERATE_TIMEOUT', 30, integer=True) ACCELERATE_TIMEOUT = get_config(p, 'accelerate', 'accelerate_timeout', 'ACCELERATE_TIMEOUT', 30, integer=True)
@ -189,6 +204,7 @@ PARAMIKO_PTY = get_config(p, 'paramiko_connection', 'pty', 'AN
DEFAULT_PASSWORD_CHARS = ascii_letters + digits + ".,:-_" DEFAULT_PASSWORD_CHARS = ascii_letters + digits + ".,:-_"
# non-configurable things # non-configurable things
DEFAULT_BECOME_PASS = None
DEFAULT_SUDO_PASS = None DEFAULT_SUDO_PASS = None
DEFAULT_REMOTE_PASS = None DEFAULT_REMOTE_PASS = None
DEFAULT_SUBSET = None DEFAULT_SUBSET = None

View file

@ -48,16 +48,16 @@ class ConnectionInformation:
self.password = '' self.password = ''
self.port = 22 self.port = 22
self.private_key_file = None self.private_key_file = None
self.su = False
self.su_user = ''
self.su_pass = ''
self.sudo = False
self.sudo_user = ''
self.sudo_pass = ''
self.verbosity = 0 self.verbosity = 0
self.only_tags = set() self.only_tags = set()
self.skip_tags = set() self.skip_tags = set()
# privilege escalation
self.become = False
self.become_method = C.DEFAULT_BECOME_METHOD
self.become_user = ''
self.become_pass = ''
self.no_log = False self.no_log = False
self.check_mode = False self.check_mode = False
@ -87,12 +87,10 @@ class ConnectionInformation:
self.remote_user = play.remote_user self.remote_user = play.remote_user
self.password = '' self.password = ''
self.port = int(play.port) if play.port else 22 self.port = int(play.port) if play.port else 22
self.su = play.su self.become = play.become
self.su_user = play.su_user self.become_method = play.become_method
self.su_pass = play.su_pass self.become_user = play.become_user
self.sudo = play.sudo self.become_pass = play.become_pass
self.sudo_user = play.sudo_user
self.sudo_pass = play.sudo_pass
# non connection related # non connection related
self.no_log = play.no_log self.no_log = play.no_log
@ -158,7 +156,7 @@ class ConnectionInformation:
new_info = ConnectionInformation() new_info = ConnectionInformation()
new_info.copy(self) new_info.copy(self)
for attr in ('connection', 'remote_user', 'su', 'su_user', 'su_pass', 'sudo', 'sudo_user', 'sudo_pass', 'environment', 'no_log'): for attr in ('connection', 'remote_user', 'become', 'become_user', 'become_pass', 'become_method', 'environment', 'no_log'):
if hasattr(task, attr): if hasattr(task, attr):
attr_val = getattr(task, attr) attr_val = getattr(task, attr)
if attr_val: if attr_val:
@ -166,31 +164,58 @@ class ConnectionInformation:
return new_info return new_info
def make_sudo_cmd(self, sudo_exe, executable, cmd): def make_become_cmd(self, cmd, shell, become_settings=None):
"""
Helper function for wrapping commands with sudo.
Rather than detect if sudo wants a password this time, -k makes
sudo always ask for a password if one is required. Passing a quoted
compound command to sudo (or sudo -s) directly doesn't work, so we
shellquote it with pipes.quote() and pass the quoted string to the
user's shell. We loop reading output until we see the randomly-
generated sudo prompt set with the -p option.
""" """
helper function to create privilege escalation commands
"""
# FIXME: become settings should probably be stored in the connection info itself
if become_settings is None:
become_settings = {}
randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32)) randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32))
success_key = 'BECOME-SUCCESS-%s' % randbits
prompt = None
becomecmd = None
shell = shell or '$SHELL'
if self.become_method == 'sudo':
# Rather than detect if sudo wants a password this time, -k makes sudo always ask for
# a password if one is required. Passing a quoted compound command to sudo (or sudo -s)
# directly doesn't work, so we shellquote it with pipes.quote() and pass the quoted
# string to the user's shell. We loop reading output until we see the randomly-generated
# sudo prompt set with the -p option.
prompt = '[sudo via ansible, key=%s] password: ' % randbits prompt = '[sudo via ansible, key=%s] password: ' % randbits
success_key = 'SUDO-SUCCESS-%s' % randbits exe = become_settings.get('sudo_exe', C.DEFAULT_SUDO_EXE)
flags = become_settings.get('sudo_flags', C.DEFAULT_SUDO_FLAGS)
becomecmd = '%s -k && %s %s -S -p "%s" -u %s %s -c "%s"' % \
(exe, exe, flags or C.DEFAULT_SUDO_FLAGS, prompt, self.become_user, shell, 'echo %s; %s' % (success_key, cmd))
sudocmd = '%s -k && %s %s -S -p "%s" -u %s %s -c %s' % ( elif self.become_method == 'su':
sudo_exe, sudo_exe, C.DEFAULT_SUDO_FLAGS, prompt, exe = become_settings.get('su_exe', C.DEFAULT_SU_EXE)
self.sudo_user, executable or '$SHELL', flags = become_settings.get('su_flags', C.DEFAULT_SU_FLAGS)
pipes.quote('echo %s; %s' % (success_key, cmd)) becomecmd = '%s %s %s -c "%s -c %s"' % (exe, flags, self.become_user, shell, pipes.quote('echo %s; %s' % (success_key, cmd)))
)
# FIXME: old code, can probably be removed as it's been commented out for a while elif self.become_method == 'pbrun':
#return ('/bin/sh -c ' + pipes.quote(sudocmd), prompt, success_key) exe = become_settings.get('pbrun_exe', 'pbrun')
return (sudocmd, prompt, success_key) flags = become_settings.get('pbrun_flags', '')
becomecmd = '%s -b -l %s -u %s "%s"' % (exe, flags, self.become_user, 'echo %s; %s' % (success_key,cmd))
elif self.become_method == 'pfexec':
exe = become_settings.get('pfexec_exe', 'pbrun')
flags = become_settings.get('pfexec_flags', '')
# No user as it uses it's own exec_attr to figure it out
becomecmd = '%s %s "%s"' % (exe, flags, 'echo %s; %s' % (success_key,cmd))
elif self.become:
raise errors.AnsibleError("Privilege escalation method not found: %s" % method)
return (('%s -c ' % shell) + pipes.quote(becomecmd), prompt, success_key)
def check_become_success(self, output, become_settings):
#TODO: implement
pass
def _get_fields(self): def _get_fields(self):
return [i for i in self.__dict__.keys() if i[:1] != '_'] return [i for i in self.__dict__.keys() if i[:1] != '_']
@ -204,4 +229,3 @@ class ConnectionInformation:
for field in self._get_fields(): for field in self._get_fields():
value = templar.template(getattr(self, field)) value = templar.template(getattr(self, field))
setattr(self, field, value) setattr(self, field, value)

View file

@ -197,7 +197,7 @@ class PlayIterator:
self._host_states[host.name] = s self._host_states[host.name] = s
def get_failed_hosts(self): def get_failed_hosts(self):
return dict((host, True) for (host, state) in self._host_states.iteritems() if state.run_state == self.ITERATING_COMPLETE and state.failed_state != self.FAILED_NONE) return dict((host, True) for (host, state) in self._host_states.iteritems() if state.run_state == self.ITERATING_COMPLETE and state.fail_state != self.FAILED_NONE)
def get_original_task(self, host, task): def get_original_task(self, host, task):
''' '''

View file

@ -33,6 +33,7 @@ __all__ = ['TaskExecutor']
import json import json
import time import time
import pipes
class TaskExecutor: class TaskExecutor:

View file

@ -72,6 +72,11 @@ class Base:
def munge(self, ds): def munge(self, ds):
''' infrequently used method to do some pre-processing of legacy terms ''' ''' infrequently used method to do some pre-processing of legacy terms '''
for base_class in self.__class__.__bases__:
method = getattr(self, ("_munge_%s" % base_class.__name__).lower(), None)
if method:
ds = method(ds)
return ds return ds
def load_data(self, ds, variable_manager=None, loader=None): def load_data(self, ds, variable_manager=None, loader=None):

View file

@ -0,0 +1,88 @@
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.playbook.attribute import Attribute, FieldAttribute
#from ansible.utils.display import deprecated
class Become:
# Privlege escalation
_become = FieldAttribute(isa='bool', default=False)
_become_method = FieldAttribute(isa='string')
_become_user = FieldAttribute(isa='string')
_become_pass = FieldAttribute(isa='string')
def __init__(self):
return super(Become, self).__init__()
def _detect_privilege_escalation_conflict(self, ds):
# Fail out if user specifies conflicting privelege escalations
has_become = 'become' in ds or 'become_user'in ds
has_sudo = 'sudo' in ds or 'sudo_user' in ds
has_su = 'su' in ds or 'su_user' in ds
if has_become:
msg = 'The become params ("become", "become_user") and'
if has_sudo:
raise errors.AnsibleParserError('%s sudo params ("sudo", "sudo_user") cannot be used together' % msg)
elif has_su:
raise errors.AnsibleParserError('%s su params ("su", "su_user") cannot be used together' % msg)
elif has_sudo and has_su:
raise errors.AnsibleParserError('sudo params ("sudo", "sudo_user") and su params ("su", "su_user") cannot be used together')
def _munge_become(self, ds):
self._detect_privilege_escalation_conflict(ds)
# Setting user implies setting become/sudo/su to true
if 'become_user' in ds and not ds.get('become', False):
ds['become'] = True
# Privilege escalation, backwards compatibility for sudo/su
if 'sudo' in ds or 'sudo_user' in ds:
ds['become_method'] = 'sudo'
if 'sudo' in ds:
ds['become'] = ds['sudo']
del ds['sudo']
else:
ds['become'] = True
if 'sudo_user' in ds:
ds['become_user'] = ds['sudo_user']
del ds['sudo_user']
#deprecated("Instead of sudo/sudo_user, use become/become_user and set become_method to 'sudo' (default)")
elif 'su' in ds or 'su_user' in ds:
ds['become_method'] = 'su'
if 'su' in ds:
ds['become'] = ds['su']
del ds['su']
else:
ds['become'] = True
if 'su_user' in ds:
ds['become_user'] = ds['su_user']
del ds['su_user']
#deprecated("Instead of su/su_user, use become/become_user and set become_method to 'su' (default is sudo)")
return ds

View file

@ -21,6 +21,7 @@ __metaclass__ = type
from ansible.playbook.attribute import Attribute, FieldAttribute from ansible.playbook.attribute import Attribute, FieldAttribute
from ansible.playbook.base import Base from ansible.playbook.base import Base
#from ansible.playbook.become import Become
from ansible.playbook.conditional import Conditional from ansible.playbook.conditional import Conditional
from ansible.playbook.helpers import load_list_of_tasks from ansible.playbook.helpers import load_list_of_tasks
from ansible.playbook.role import Role from ansible.playbook.role import Role
@ -80,7 +81,8 @@ class Block(Base, Conditional, Taggable):
return dict(block=ds) return dict(block=ds)
else: else:
return dict(block=[ds]) return dict(block=[ds])
return ds
return super(Block, self).munge(ds)
def _load_block(self, attr, ds): def _load_block(self, attr, ds):
return load_list_of_tasks( return load_list_of_tasks(

View file

@ -23,6 +23,7 @@ from ansible.errors import AnsibleError, AnsibleParserError
from ansible.playbook.attribute import Attribute, FieldAttribute from ansible.playbook.attribute import Attribute, FieldAttribute
from ansible.playbook.base import Base from ansible.playbook.base import Base
from ansible.playbook.become import Become
from ansible.playbook.helpers import load_list_of_blocks, load_list_of_roles, compile_block_list from ansible.playbook.helpers import load_list_of_blocks, load_list_of_roles, compile_block_list
from ansible.playbook.role import Role from ansible.playbook.role import Role
from ansible.playbook.taggable import Taggable from ansible.playbook.taggable import Taggable
@ -33,7 +34,7 @@ from ansible.utils.vars import combine_vars
__all__ = ['Play'] __all__ = ['Play']
class Play(Base, Taggable): class Play(Base, Taggable, Become):
""" """
A play is a language feature that represents a list of roles and/or A play is a language feature that represents a list of roles and/or
@ -47,21 +48,19 @@ class Play(Base, Taggable):
# ================================================================================= # =================================================================================
# Connection-Related Attributes # Connection-Related Attributes
# TODO: generalize connection
_accelerate = FieldAttribute(isa='bool', default=False) _accelerate = FieldAttribute(isa='bool', default=False)
_accelerate_ipv6 = FieldAttribute(isa='bool', default=False) _accelerate_ipv6 = FieldAttribute(isa='bool', default=False)
_accelerate_port = FieldAttribute(isa='int', default=5099) _accelerate_port = FieldAttribute(isa='int', default=5099) # should be alias of port
# Connection
_connection = FieldAttribute(isa='string', default='smart') _connection = FieldAttribute(isa='string', default='smart')
_gather_facts = FieldAttribute(isa='string', default='smart') _gather_facts = FieldAttribute(isa='string', default='smart')
_hosts = FieldAttribute(isa='list', default=[], required=True) _hosts = FieldAttribute(isa='list', default=[], required=True)
_name = FieldAttribute(isa='string', default='<no name specified>') _name = FieldAttribute(isa='string', default='<no name specified>')
_port = FieldAttribute(isa='int', default=22) _port = FieldAttribute(isa='int', default=22)
_remote_user = FieldAttribute(isa='string', default='root') _remote_user = FieldAttribute(isa='string', default='root')
_su = FieldAttribute(isa='bool', default=False)
_su_user = FieldAttribute(isa='string', default='root')
_su_pass = FieldAttribute(isa='string')
_sudo = FieldAttribute(isa='bool', default=False)
_sudo_user = FieldAttribute(isa='string', default='root')
_sudo_pass = FieldAttribute(isa='string')
# Variable Attributes # Variable Attributes
_vars = FieldAttribute(isa='dict', default=dict()) _vars = FieldAttribute(isa='dict', default=dict())
@ -101,6 +100,7 @@ class Play(Base, Taggable):
@staticmethod @staticmethod
def load(data, variable_manager=None, loader=None): def load(data, variable_manager=None, loader=None):
p = Play() p = Play()
print("in play load, become is: %s" % getattr(p, 'become'))
return p.load_data(data, variable_manager=variable_manager, loader=loader) return p.load_data(data, variable_manager=variable_manager, loader=loader)
def munge(self, ds): def munge(self, ds):
@ -122,7 +122,7 @@ class Play(Base, Taggable):
ds['remote_user'] = ds['user'] ds['remote_user'] = ds['user']
del ds['user'] del ds['user']
return ds return super(Play, self).munge(ds)
def _load_vars(self, attr, ds): def _load_vars(self, attr, ds):
''' '''
@ -187,7 +187,7 @@ class Play(Base, Taggable):
roles.append(Role.load(ri)) roles.append(Role.load(ri))
return roles return roles
# FIXME: post_validation needs to ensure that su/sudo are not both set # FIXME: post_validation needs to ensure that become/su/sudo have only 1 set
def _compile_roles(self): def _compile_roles(self):
''' '''

View file

@ -98,7 +98,7 @@ class PlaybookInclude(Base):
raise AnsibleParserError("vars for include statements must be specified as a dictionary", obj=ds) raise AnsibleParserError("vars for include statements must be specified as a dictionary", obj=ds)
new_ds[k] = v new_ds[k] = v
return new_ds return super(PlaybookInclude, self).munge(new_ds)
def _munge_include(self, ds, new_ds, k, v): def _munge_include(self, ds, new_ds, k, v):
''' '''

View file

@ -88,7 +88,7 @@ class RoleDefinition(Base, Conditional, Taggable):
self._ds = ds self._ds = ds
# and return the cleaned-up data structure # and return the cleaned-up data structure
return new_ds return super(RoleDefinition, self).munge(new_ds)
def _load_role_name(self, ds): def _load_role_name(self, ds):
''' '''

View file

@ -29,6 +29,7 @@ from ansible.plugins import module_loader, lookup_loader
from ansible.playbook.attribute import Attribute, FieldAttribute from ansible.playbook.attribute import Attribute, FieldAttribute
from ansible.playbook.base import Base from ansible.playbook.base import Base
from ansible.playbook.become import Become
from ansible.playbook.block import Block from ansible.playbook.block import Block
from ansible.playbook.conditional import Conditional from ansible.playbook.conditional import Conditional
from ansible.playbook.role import Role from ansible.playbook.role import Role
@ -36,7 +37,7 @@ from ansible.playbook.taggable import Taggable
__all__ = ['Task'] __all__ = ['Task']
class Task(Base, Conditional, Taggable): class Task(Base, Conditional, Taggable, Become):
""" """
A task is a language feature that represents a call to a module, with given arguments and other parameters. A task is a language feature that represents a call to a module, with given arguments and other parameters.
@ -86,12 +87,6 @@ class Task(Base, Conditional, Taggable):
_remote_user = FieldAttribute(isa='string') _remote_user = FieldAttribute(isa='string')
_retries = FieldAttribute(isa='int', default=1) _retries = FieldAttribute(isa='int', default=1)
_run_once = FieldAttribute(isa='bool') _run_once = FieldAttribute(isa='bool')
_su = FieldAttribute(isa='bool')
_su_pass = FieldAttribute(isa='string')
_su_user = FieldAttribute(isa='string')
_sudo = FieldAttribute(isa='bool')
_sudo_user = FieldAttribute(isa='string')
_sudo_pass = FieldAttribute(isa='string')
_transport = FieldAttribute(isa='string') _transport = FieldAttribute(isa='string')
_until = FieldAttribute(isa='list') # ? _until = FieldAttribute(isa='list') # ?
_vars = FieldAttribute(isa='dict', default=dict()) _vars = FieldAttribute(isa='dict', default=dict())
@ -172,6 +167,7 @@ class Task(Base, Conditional, Taggable):
args_parser = ModuleArgsParser(task_ds=ds) args_parser = ModuleArgsParser(task_ds=ds)
(action, args, delegate_to) = args_parser.parse() (action, args, delegate_to) = args_parser.parse()
new_ds['action'] = action new_ds['action'] = action
new_ds['args'] = args new_ds['args'] = args
new_ds['delegate_to'] = delegate_to new_ds['delegate_to'] = delegate_to
@ -186,7 +182,7 @@ class Task(Base, Conditional, Taggable):
else: else:
new_ds[k] = v new_ds[k] = v
return new_ds return super(Task, self).munge(new_ds)
def post_validate(self, all_vars=dict(), fail_on_undefined=True): def post_validate(self, all_vars=dict(), fail_on_undefined=True):
''' '''

View file

@ -130,7 +130,7 @@ class ActionBase:
if tmp and "tmp" in tmp: if tmp and "tmp" in tmp:
# tmp has already been created # tmp has already been created
return False return False
if not self._connection._has_pipelining or not C.ANSIBLE_SSH_PIPELINING or C.DEFAULT_KEEP_REMOTE_FILES or self._connection_info.su: if not self._connection._has_pipelining or not C.ANSIBLE_SSH_PIPELINING or C.DEFAULT_KEEP_REMOTE_FILES or self._connection_info.become:
# tmp is necessary to store module source code # tmp is necessary to store module source code
return True return True
if not self._connection._has_pipelining: if not self._connection._has_pipelining:
@ -152,12 +152,11 @@ class ActionBase:
basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48))
use_system_tmp = False use_system_tmp = False
if (self._connection_info.sudo and self._connection_info.sudo_user != 'root') or (self._connection_info.su and self._connection_info.su_user != 'root'): if self._connection_info.become and self._connection_info.become_user != 'root':
use_system_tmp = True use_system_tmp = True
tmp_mode = None tmp_mode = None
if self._connection_info.remote_user != 'root' or \ if self._connection_info.remote_user != 'root' or self._connection_info.become and self._connection_info.become_user != 'root':
((self._connection_info.sudo and self._connection_info.sudo_user != 'root') or (self._connection_info.su and self._connection_info.su_user != 'root')):
tmp_mode = 'a+rx' tmp_mode = 'a+rx'
cmd = self._shell.mkdtemp(basefile, use_system_tmp, tmp_mode) cmd = self._shell.mkdtemp(basefile, use_system_tmp, tmp_mode)
@ -291,10 +290,8 @@ class ActionBase:
split_path = path.split(os.path.sep, 1) split_path = path.split(os.path.sep, 1)
expand_path = split_path[0] expand_path = split_path[0]
if expand_path == '~': if expand_path == '~':
if self._connection_info.sudo and self._connection_info.sudo_user: if self._connection_info.become and self._connection_info.become_user:
expand_path = '~%s' % self._connection_info.sudo_user expand_path = '~%s' % self._connection_info.become_user
elif self._connection_info.su and self._connection_info.su_user:
expand_path = '~%s' % self._connection_info.su_user
cmd = self._shell.expand_user(expand_path) cmd = self._shell.expand_user(expand_path)
debug("calling _low_level_execute_command to expand the remote user path") debug("calling _low_level_execute_command to expand the remote user path")
@ -373,7 +370,7 @@ class ActionBase:
environment_string = self._compute_environment_string() environment_string = self._compute_environment_string()
if tmp and "tmp" in tmp and ((self._connection_info.sudo and self._connection_info.sudo_user != 'root') or (self._connection_info.su and self._connection_info.su_user != 'root')): if tmp and "tmp" in tmp and self._connection_info.become and self._connection_info.become_user != 'root':
# deal with possible umask issues once sudo'ed to other user # deal with possible umask issues once sudo'ed to other user
self._remote_chmod(tmp, 'a+r', remote_module_path) self._remote_chmod(tmp, 'a+r', remote_module_path)
@ -391,7 +388,7 @@ class ActionBase:
rm_tmp = None rm_tmp = None
if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp: if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp:
if not self._connection_info.sudo or self._connection_info.su or self._connection_info.sudo_user == 'root' or self._connection_info.su_user == 'root': if not self._connection_info.become or self._connection_info.become_user == 'root':
# not sudoing or sudoing to root, so can cleanup files in the same step # not sudoing or sudoing to root, so can cleanup files in the same step
rm_tmp = tmp rm_tmp = tmp
@ -409,7 +406,7 @@ class ActionBase:
debug("_low_level_execute_command returned ok") debug("_low_level_execute_command returned ok")
if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp: if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp:
if (self._connection_info.sudo and self._connection_info.sudo_user != 'root') or (self._connection_info.su and self._connection_info.su_user != 'root'): if self._connection_info.become and self._connection_info.become_user != 'root':
# not sudoing to root, so maybe can't delete files as that other user # not sudoing to root, so maybe can't delete files as that other user
# have to clean up temp files as original user in a second step # have to clean up temp files as original user in a second step
cmd2 = self._shell.remove(tmp, recurse=True) cmd2 = self._shell.remove(tmp, recurse=True)
@ -457,11 +454,7 @@ class ActionBase:
success_key = None success_key = None
if sudoable: if sudoable:
if self._connection_info.su and self._connection_info.su_user: cmd, prompt, success_key = self._connection_info.make_become_cmd(executable, cmd)
cmd, prompt, success_key = self._connection_info.make_su_cmd(executable, cmd)
elif self._connection_info.sudo and self._connection_info.sudo_user:
# FIXME: hard-coded sudo_exe here
cmd, prompt, success_key = self._connection_info.make_sudo_cmd('/usr/bin/sudo', executable, cmd)
debug("executing the command %s through the connection" % cmd) debug("executing the command %s through the connection" % cmd)
rc, stdin, stdout, stderr = self._connection.exec_command(cmd, tmp, executable=executable, in_data=in_data) rc, stdin, stdout, stderr = self._connection.exec_command(cmd, tmp, executable=executable, in_data=in_data)

View file

@ -44,8 +44,8 @@ class Connection(ConnectionBase):
debug("in local.exec_command()") debug("in local.exec_command()")
# su requires to be run from a terminal, and therefore isn't supported here (yet?) # su requires to be run from a terminal, and therefore isn't supported here (yet?)
if self._connection_info.su: #if self._connection_info.su:
raise AnsibleError("Internal Error: this module does not support running commands via su") # raise AnsibleError("Internal Error: this module does not support running commands via su")
if in_data: if in_data:
raise AnsibleError("Internal Error: this module does not support optimized module pipelining") raise AnsibleError("Internal Error: this module does not support optimized module pipelining")
@ -57,7 +57,7 @@ class Connection(ConnectionBase):
# else: # else:
# local_cmd = cmd # local_cmd = cmd
#else: #else:
# local_cmd, prompt, success_key = utils.make_sudo_cmd(self.runner.sudo_exe, sudo_user, executable, cmd) # local_cmd, prompt, success_key = utils.make_become_cmd(self.runner.sudo_exe, sudo_user, executable, cmd)
if executable: if executable:
local_cmd = executable.split() + ['-c', cmd] local_cmd = executable.split() + ['-c', cmd]
else: else:

View file

@ -281,19 +281,19 @@ class Connection(ConnectionBase):
# ssh_cmd += ['-6'] # ssh_cmd += ['-6']
ssh_cmd += [self._connection_info.remote_addr] ssh_cmd += [self._connection_info.remote_addr]
if not (self._connection_info.sudo or self._connection_info.su): #if not (self._connection_info.sudo or self._connection_info.su):
prompt = None # prompt = None
if executable: # if executable:
ssh_cmd.append(executable + ' -c ' + pipes.quote(cmd)) # ssh_cmd.append(executable + ' -c ' + pipes.quote(cmd))
else: # else:
ssh_cmd.append(cmd) # ssh_cmd.append(cmd)
elif self._connection_info.su and self._connection_info.su_user: #elif self._connection_info.su and self._connection_info.su_user:
su_cmd, prompt, success_key = self._connection_info.make_su_cmd(executable, cmd) # su_cmd, prompt, success_key = self._connection_info.make_su_cmd(executable, cmd)
ssh_cmd.append(su_cmd) # ssh_cmd.append(su_cmd)
else: #else:
# FIXME: hard-coded sudo_exe here # # FIXME: hard-coded sudo_exe here
sudo_cmd, prompt, success_key = self._connection_info.make_sudo_cmd('/usr/bin/sudo', executable, cmd) # sudo_cmd, prompt, success_key = self._connection_info.make_become_cmd('/usr/bin/sudo', executable, cmd)
ssh_cmd.append(sudo_cmd) # ssh_cmd.append(sudo_cmd)
self._display.vvv("EXEC %s" % ' '.join(ssh_cmd), host=self._connection_info.remote_addr) self._display.vvv("EXEC %s" % ' '.join(ssh_cmd), host=self._connection_info.remote_addr)
@ -369,6 +369,8 @@ class Connection(ConnectionBase):
# no_prompt_err += sudo_errput # no_prompt_err += sudo_errput
#(returncode, stdout, stderr) = self._communicate(p, stdin, in_data, su=su, sudoable=sudoable, prompt=prompt) #(returncode, stdout, stderr) = self._communicate(p, stdin, in_data, su=su, sudoable=sudoable, prompt=prompt)
# FIXME: the prompt won't be here anymore
prompt=""
(returncode, stdout, stderr) = self._communicate(p, stdin, in_data, prompt=prompt) (returncode, stdout, stderr) = self._communicate(p, stdin, in_data, prompt=prompt)
#if C.HOST_KEY_CHECKING and not_in_host_file: #if C.HOST_KEY_CHECKING and not_in_host_file:

View file

@ -59,6 +59,8 @@ def base_parser(usage="", output_opts=False, runas_opts=False,
help='ask for sudo password') help='ask for sudo password')
parser.add_option('--ask-su-pass', default=False, dest='ask_su_pass', action='store_true', parser.add_option('--ask-su-pass', default=False, dest='ask_su_pass', action='store_true',
help='ask for su password') help='ask for su password')
parser.add_option('--ask-become-pass', default=False, dest='ask_become_pass', action='store_true',
help='ask for privlege escalation password')
parser.add_option('--ask-vault-pass', default=False, dest='ask_vault_pass', action='store_true', parser.add_option('--ask-vault-pass', default=False, dest='ask_vault_pass', action='store_true',
help='ask for vault password') help='ask for vault password')
parser.add_option('--vault-password-file', default=C.DEFAULT_VAULT_PASSWORD_FILE, parser.add_option('--vault-password-file', default=C.DEFAULT_VAULT_PASSWORD_FILE,
@ -84,6 +86,10 @@ def base_parser(usage="", output_opts=False, runas_opts=False,
help='log output to this directory') help='log output to this directory')
if runas_opts: if runas_opts:
parser.add_option("-b", "--become", default=C.DEFAULT_BECOME, action="store_true",
dest='become', help="run operations with become (nopasswd implied)")
parser.add_option('-B', '--become-user', help='run operations with as this '
'user (default=%s)' % C.DEFAULT_BECOME_USER)
parser.add_option("-s", "--sudo", default=C.DEFAULT_SUDO, action="store_true", parser.add_option("-s", "--sudo", default=C.DEFAULT_SUDO, action="store_true",
dest='sudo', help="run operations with sudo (nopasswd)") dest='sudo', help="run operations with sudo (nopasswd)")
parser.add_option('-U', '--sudo-user', dest='sudo_user', default=None, parser.add_option('-U', '--sudo-user', dest='sudo_user', default=None,
@ -100,6 +106,9 @@ def base_parser(usage="", output_opts=False, runas_opts=False,
parser.add_option('-c', '--connection', dest='connection', parser.add_option('-c', '--connection', dest='connection',
default=C.DEFAULT_TRANSPORT, default=C.DEFAULT_TRANSPORT,
help="connection type to use (default=%s)" % C.DEFAULT_TRANSPORT) help="connection type to use (default=%s)" % C.DEFAULT_TRANSPORT)
parser.add_option('--become-method', dest='become_method',
default=C.DEFAULT_BECOME_METHOD,
help="privlege escalation method to use (default=%s)" % C.DEFAULT_BECOME_METHOD)
if async_opts: if async_opts:
parser.add_option('-P', '--poll', default=C.DEFAULT_POLL_INTERVAL, type='int', parser.add_option('-P', '--poll', default=C.DEFAULT_POLL_INTERVAL, type='int',

View file

@ -0,0 +1,7 @@
- hosts: all
gather_facts: no
tasks:
- command: whoami
become: yes
become_user: jamesc
become_method: su