Make v2 playbook class attributes inheritable

Also fixing some other become-related things
This commit is contained in:
James Cammarata 2015-03-20 14:13:51 -05:00
parent 8d8c4c0615
commit 393246fdd3
14 changed files with 152 additions and 51 deletions

View file

@ -157,13 +157,10 @@ class ConnectionInformation:
new_info.copy(self)
for attr in ('connection', 'remote_user', 'become', 'become_user', 'become_pass', 'become_method', 'environment', 'no_log'):
attr_val = None
if hasattr(task, attr):
attr_val = getattr(task, attr)
if task._block and hasattr(task._block, attr) and not attr_val:
attr_val = getattr(task._block, attr)
if attr_val:
setattr(new_info, attr, attr_val)
if attr_val:
setattr(new_info, attr, attr_val)
return new_info
@ -184,6 +181,7 @@ class ConnectionInformation:
executable = executable or '$SHELL'
success_cmd = pipes.quote('echo %s; %s' % (success_key, cmd))
if self.become:
if self.become_method == 'sudo':
# Rather than detect if sudo wants a password this time, -k makes sudo always ask for
@ -195,23 +193,23 @@ class ConnectionInformation:
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, executable, pipes.quote('echo %s; %s' % (success_key, cmd)))
(exe, exe, flags or C.DEFAULT_SUDO_FLAGS, prompt, self.become_user, executable, success_cmd)
elif self.become_method == 'su':
exe = become_settings.get('su_exe', C.DEFAULT_SU_EXE)
flags = become_settings.get('su_flags', C.DEFAULT_SU_FLAGS)
becomecmd = '%s %s %s -c "%s -c %s"' % (exe, flags, self.become_user, executable, pipes.quote('echo %s; %s' % (success_key, cmd)))
becomecmd = '%s %s %s -c "%s -c %s"' % (exe, flags, self.become_user, executable, success_cmd)
elif self.become_method == 'pbrun':
exe = become_settings.get('pbrun_exe', 'pbrun')
flags = become_settings.get('pbrun_flags', '')
becomecmd = '%s -b -l %s -u %s "%s"' % (exe, flags, user, pipes.quote('echo %s; %s' % (success_key, cmd)))
becomecmd = '%s -b -l %s -u %s "%s"' % (exe, flags, user, success_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, pipes.quote('echo %s; %s' % (success_key, cmd)))
becomecmd = '%s %s "%s"' % (exe, flags, success_cmd)
else:
raise errors.AnsibleError("Privilege escalation method not found: %s" % method)

View file

@ -72,11 +72,20 @@ class Base:
def munge(self, ds):
''' 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)
def _get_base_classes_munge(target_class):
base_classes = list(target_class.__bases__[:])
for base_class in target_class.__bases__:
base_classes.extend( _get_base_classes_munge(base_class))
return base_classes
base_classes = list(self.__class__.__bases__[:])
for base_class in self.__class__.__bases__:
base_classes.extend(_get_base_classes_munge(base_class))
for base_class in base_classes:
method = getattr(self, "_munge_%s" % base_class.__name__.lower(), None)
if method:
return method(ds)
return ds
def load_data(self, ds, variable_manager=None, loader=None):
@ -271,15 +280,21 @@ class Base:
# optionally allowing masking by accessors
if not needle.startswith("_"):
method = "get_%s" % needle
if method in self.__dict__:
return method(self)
method = "_get_attr_%s" % needle
if method in dir(self):
return getattr(self, method)()
if needle in self._attributes:
return self._attributes[needle]
raise AttributeError("attribute not found in %s: %s" % (self.__class__.__name__, needle))
def __setattr__(self, needle, value):
if hasattr(self, '_attributes') and needle in self._attributes:
self._attributes[needle] = value
else:
super(Base, self).__setattr__(needle, value)
def __getstate__(self):
return self.serialize()

View file

@ -95,3 +95,41 @@ class Become:
ds['become_user'] = C.DEFAULT_BECOME_USER
return ds
def _get_attr_become(self):
'''
Override for the 'become' getattr fetcher, used from Base.
'''
if hasattr(self, '_get_parent_attribute'):
return self._get_parent_attribute('become')
else:
return self._attributes['become']
def _get_attr_become_method(self):
'''
Override for the 'become_method' getattr fetcher, used from Base.
'''
if hasattr(self, '_get_parent_attribute'):
return self._get_parent_attribute('become_method')
else:
return self._attributes['become_method']
def _get_attr_become_user(self):
'''
Override for the 'become_user' getattr fetcher, used from Base.
'''
if hasattr(self, '_get_parent_attribute'):
return self._get_parent_attribute('become_user')
else:
return self._attributes['become_user']
def _get_attr_become_password(self):
'''
Override for the 'become_password' getattr fetcher, used from Base.
'''
if hasattr(self, '_get_parent_attribute'):
return self._get_parent_attribute('become_password')
else:
return self._attributes['become_password']

View file

@ -131,23 +131,24 @@ class Block(Base, Become, Conditional, Taggable):
# use_handlers=self._use_handlers,
# )
def compile(self):
'''
Returns the task list for this object
'''
task_list = []
for task in self.block:
# FIXME: evaulate task tags/conditionals here
task_list.extend(task.compile())
return task_list
def copy(self):
def _dupe_task_list(task_list, new_block):
new_task_list = []
for task in task_list:
new_task = task.copy(exclude_block=True)
new_task._block = new_block
new_task_list.append(new_task)
return new_task_list
new_me = super(Block, self).copy()
new_me._use_handlers = self._use_handlers
new_me._dep_chain = self._dep_chain[:]
new_me.block = _dupe_task_list(self.block or [], new_me)
new_me.rescue = _dupe_task_list(self.rescue or [], new_me)
new_me.always = _dupe_task_list(self.always or [], new_me)
print("new block tasks are: %s" % new_me.block)
new_me._parent_block = None
if self._parent_block:
new_me._parent_block = self._parent_block.copy()
@ -252,3 +253,24 @@ class Block(Base, Become, Conditional, Taggable):
for dep in self._dep_chain:
dep.set_loader(loader)
def _get_parent_attribute(self, attr):
'''
Generic logic to get the attribute or parent attribute for a block value.
'''
value = self._attributes[attr]
if not value:
if self._parent_block:
value = getattr(self._block, attr)
elif self._role:
value = getattr(self._role, attr)
if not value and len(self._dep_chain):
reverse_dep_chain = self._dep_chain[:]
reverse_dep_chain.reverse()
for dep in reverse_dep_chain:
value = getattr(dep, attr)
if value:
break
return value

View file

@ -37,6 +37,7 @@ def load_list_of_blocks(ds, parent_block=None, role=None, task_include=None, use
assert type(ds) in (list, NoneType)
block_list = []
print("in load list of blocks, ds is: %s" % ds)
if ds:
for block in ds:
b = Block.load(
@ -50,6 +51,7 @@ def load_list_of_blocks(ds, parent_block=None, role=None, task_include=None, use
)
block_list.append(b)
print("-> returning block list: %s" % block_list)
return block_list

View file

@ -219,6 +219,7 @@ class Play(Base, Taggable, Become):
block_list.extend(self.tasks)
block_list.extend(self.post_tasks)
print("block list is: %s" % block_list)
return block_list
def get_vars(self):

View file

@ -30,6 +30,7 @@ from ansible.errors import AnsibleError, AnsibleParserError
from ansible.parsing import DataLoader
from ansible.playbook.attribute import FieldAttribute
from ansible.playbook.base import Base
from ansible.playbook.become import Become
from ansible.playbook.conditional import Conditional
from ansible.playbook.helpers import load_list_of_blocks, compile_block_list
from ansible.playbook.role.include import RoleInclude
@ -69,7 +70,7 @@ def hash_params(params):
ROLE_CACHE = dict()
class Role(Base, Conditional, Taggable):
class Role(Base, Become, Conditional, Taggable):
def __init__(self):
self._role_name = None
@ -136,6 +137,12 @@ class Role(Base, Conditional, Taggable):
if parent_role:
self.add_parent(parent_role)
# copy over all field attributes, except for when and tags, which
# are special cases and need to preserve pre-existing values
for (attr_name, _) in iteritems(self._get_base_attributes()):
if attr_name not in ('when', 'tags'):
setattr(self, attr_name, getattr(role_include, attr_name))
current_when = getattr(self, 'when')[:]
current_when.extend(role_include.when)
setattr(self, 'when', current_when)
@ -144,10 +151,6 @@ class Role(Base, Conditional, Taggable):
current_tags.extend(role_include.tags)
setattr(self, 'tags', current_tags)
# save the current base directory for the loader and set it to the current role path
#cur_basedir = self._loader.get_basedir()
#self._loader.set_basedir(self._role_path)
# load the role's files, if they exist
library = os.path.join(self._role_path, 'library')
if os.path.isdir(library):
@ -179,9 +182,6 @@ class Role(Base, Conditional, Taggable):
elif self._default_vars is None:
self._default_vars = dict()
# and finally restore the previous base directory
#self._loader.set_basedir(cur_basedir)
def _load_role_yaml(self, subdir):
file_path = os.path.join(self._role_path, subdir)
if self._loader.path_exists(file_path) and self._loader.is_directory(file_path):
@ -313,9 +313,6 @@ class Role(Base, Conditional, Taggable):
for dep in deps:
dep_blocks = dep.compile(dep_chain=new_dep_chain)
for dep_block in dep_blocks:
# since we're modifying the task, and need it to be unique,
# we make a copy of it here and assign the dependency chain
# to the copy, then append the copy to the task list.
new_dep_block = dep_block.copy()
new_dep_block._dep_chain = new_dep_chain
block_list.append(new_dep_block)

View file

@ -28,6 +28,7 @@ from ansible.errors import AnsibleError
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping
from ansible.playbook.attribute import Attribute, FieldAttribute
from ansible.playbook.base import Base
from ansible.playbook.become import Become
from ansible.playbook.conditional import Conditional
from ansible.playbook.taggable import Taggable
from ansible.utils.path import unfrackpath
@ -36,7 +37,7 @@ from ansible.utils.path import unfrackpath
__all__ = ['RoleDefinition']
class RoleDefinition(Base, Conditional, Taggable):
class RoleDefinition(Base, Become, Conditional, Taggable):
_role = FieldAttribute(isa='string')
@ -57,6 +58,9 @@ class RoleDefinition(Base, Conditional, Taggable):
assert isinstance(ds, dict) or isinstance(ds, string_types)
if isinstance(ds, dict):
ds = super(RoleDefinition, self).munge(ds)
# we create a new data structure here, using the same
# object used internally by the YAML parsing code so we
# can preserve file:line:column information if it exists
@ -88,7 +92,7 @@ class RoleDefinition(Base, Conditional, Taggable):
self._ds = ds
# and return the cleaned-up data structure
return super(RoleDefinition, self).munge(new_ds)
return new_ds
def _load_role_name(self, ds):
'''

View file

@ -210,20 +210,21 @@ class Task(Base, Conditional, Taggable, Become):
del all_vars['when']
return all_vars
def compile(self):
'''
For tasks, this is just a dummy method returning an array
with 'self' in it, so we don't have to care about task types
further up the chain.
'''
# no longer used, as blocks are the lowest level of compilation now
#def compile(self):
# '''
# For tasks, this is just a dummy method returning an array
# with 'self' in it, so we don't have to care about task types
# further up the chain.
# '''
#
# return [self]
return [self]
def copy(self):
def copy(self, exclude_block=False):
new_me = super(Task, self).copy()
new_me._block = None
if self._block:
if self._block and not exclude_block:
new_me._block = self._block.copy()
new_me._role = None
@ -309,3 +310,12 @@ class Task(Base, Conditional, Taggable, Become):
if self._task_include:
self._task_include.set_loader(loader)
def _get_parent_attribute(self, attr):
'''
Generic logic to get the attribute or parent attribute for a task value.
'''
value = self._attributes[attr]
if not value and self._block:
value = getattr(self._block, attr)
return value

View file

@ -0,0 +1 @@
allow_duplicates: yes

View file

@ -0,0 +1,2 @@
- debug: msg="this is test_become_r1"
- command: whoami

View file

@ -0,0 +1,3 @@
allow_duplicates: yes
dependencies:
- test_become_r1

View file

@ -0,0 +1,2 @@
- debug: msg="this is test_become_r2"
- command: whoami

View file

@ -1,8 +1,14 @@
- hosts: all
gather_facts: no
roles:
- { role: test_become_r2 }
- { role: test_become_r2, sudo_user: testing }
tasks:
- command: whoami
- command: whoami
become_user: testing
- block:
- command: whoami
- block:
- command: whoami
become_user: testing