Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Playbook and PlaybookInclude into parent chain #73309

Draft
wants to merge 34 commits into
base: devel
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
40510b9
Add Playbook and PlaybookInclude into parent chain. ci_complete
sivel Jan 20, 2021
2d8b7b9
Get inheritance working with Playbook and PlaybookInclude parents
sivel Jan 20, 2021
37046a1
ci_complete
sivel Jan 20, 2021
c97c49d
_parent of Play with adhoc is None
sivel Jan 20, 2021
1bb0a10
Guard against wrong _parent type
sivel Jan 20, 2021
c63c102
give Playbook and PlaybookInclude a _uuid, add isinstance to Block eq…
sivel Jan 20, 2021
f2ef096
PlaybookInclude already has _uuid since it inherits from Base
sivel Jan 20, 2021
5b68f75
Remove debugging code. Oops
sivel Jan 20, 2021
66f3bf0
Prefer role as the parent if there is one
sivel Jan 20, 2021
6d30538
ci_complete
sivel Jan 20, 2021
e361a82
Role parent. ci_complete
sivel Jan 20, 2021
1cfde16
all parents of play are static
sivel Jan 21, 2021
73bc5d6
Block vars win over parent
sivel Jan 21, 2021
2c1e84b
Var precedence fixes. ci_complete
sivel Jan 21, 2021
c5e0de1
Restore some logic. ci_complete
sivel Jan 21, 2021
f93b236
Respect DEFAULT_PRIVATE_ROLE_VARS
sivel Jan 21, 2021
d8df5fb
ci_complete
sivel Jan 21, 2021
eda9168
Prevent bypassing Play in block reparenting. ci_complete
sivel Jan 21, 2021
5177def
TaskInclude.copy accept direct_parent
sivel Jan 21, 2021
94731d9
ci_complete
sivel Jan 21, 2021
53ce2dc
A few more fix ups?
sivel Jan 21, 2021
61c8e98
ci_complete
sivel Jan 21, 2021
3bdda83
Keep it straight...
sivel Jan 22, 2021
6498dc8
ci_complete
sivel Jan 22, 2021
bcd381e
Don't use direct_parent in block reparenting
sivel Jan 22, 2021
e1505cd
ci_complete
sivel Jan 22, 2021
1e5a757
direct_parent again? ci_complete
sivel Jan 22, 2021
9dc5289
play vars need combined, not updated. ci_complete
sivel Jan 22, 2021
dc3b154
Don't overwrite registers
sivel Jan 22, 2021
ffa1bb6
ci_complete
sivel Jan 22, 2021
9d9dfa8
preprocess_vars from vars_files. ci_complete
sivel Jan 22, 2021
0266468
Allow templatable vars_file paths, and first_found behavior. ci_complete
sivel Jan 22, 2021
df163c6
Update tests for changes in APIs and necessary setup
sivel Jan 22, 2021
406d28e
ci_complete
sivel Jan 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions lib/ansible/executor/play_iterator.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,6 @@ def __init__(self, inventory, play, play_context, variable_manager, all_vars, st
if fact_path:
setup_task.args['fact_path'] = fact_path
setup_task.set_loader(self._play._loader)
# short circuit fact gathering if the entire playbook is conditional
if self._play._included_conditional is not None:
setup_task.when = self._play._included_conditional[:]
setup_block.block = [setup_task]

setup_block = setup_block.filter_tagged_tasks(all_vars)
Expand Down
3 changes: 1 addition & 2 deletions lib/ansible/executor/task_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,7 @@ def _run_loop(self, items):
ran_once = True

try:
tmp_task = self._task.copy(exclude_parent=True, exclude_tasks=True)
tmp_task._parent = self._task._parent
tmp_task = self._task.copy(direct_parent=True, exclude_tasks=True)
tmp_play_context = self._play_context.copy()
except AnsibleParserError as e:
results.append(dict(failed=True, msg=to_text(e)))
Expand Down
37 changes: 33 additions & 4 deletions lib/ansible/playbook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from ansible.playbook.playbook_include import PlaybookInclude
from ansible.plugins.loader import add_all_plugin_dirs
from ansible.utils.display import Display
from ansible.utils.vars import get_unique_id

display = Display()

Expand All @@ -37,21 +38,32 @@

class Playbook:

def __init__(self, loader):
def __init__(self, loader, parent=None):
# Entries in the datastructure of a playbook may
# be either a play or an include statement
self._entries = []
self._basedir = to_text(os.getcwd(), errors='surrogate_or_strict')
self._loader = loader
self._file_name = None
self._parent = parent
self._uuid = get_unique_id()

def __repr__(self):
return self.get_name()

def get_name(self):
return self._file_name

@staticmethod
def load(file_name, variable_manager=None, loader=None):
pb = Playbook(loader=loader)
pb._load_playbook_data(file_name=file_name, variable_manager=variable_manager)
return pb

def _load_playbook_data(self, file_name, variable_manager, vars=None):
def _load_playbook_data(self, file_name, variable_manager, parent=None):

if parent is None:
parent = self

if os.path.isabs(file_name):
self._basedir = os.path.dirname(file_name)
Expand Down Expand Up @@ -95,7 +107,7 @@ def _load_playbook_data(self, file_name, variable_manager, vars=None):
if any(action in entry for action in C._ACTION_INCLUDE):
display.deprecated("'include' for playbook includes. You should use 'import_playbook' instead",
version="2.12", collection_name='ansible.builtin')
pb = PlaybookInclude.load(entry, basedir=self._basedir, variable_manager=variable_manager, loader=self._loader)
pb = PlaybookInclude.load(entry, basedir=self._basedir, variable_manager=variable_manager, loader=self._loader, parent=self)
if pb is not None:
self._entries.extend(pb._entries)
else:
Expand All @@ -106,7 +118,7 @@ def _load_playbook_data(self, file_name, variable_manager, vars=None):
break
display.display("skipping playbook '%s' due to conditional test failure" % which, color=C.COLOR_SKIP)
else:
entry_obj = Play.load(entry, variable_manager=variable_manager, loader=self._loader, vars=vars)
entry_obj = Play.load(entry, variable_manager=variable_manager, loader=self._loader, parent=parent)
self._entries.append(entry_obj)

# we're done, so restore the old basedir in the loader
Expand All @@ -115,5 +127,22 @@ def _load_playbook_data(self, file_name, variable_manager, vars=None):
def get_loader(self):
return self._loader

def set_loader(self, loader):
'''
Sets the loader on this object and recursively on parent, child objects.
This is used primarily after the Task has been serialized/deserialized, which
does not preserve the loader.
'''

self._loader = loader

if self._parent:
self._parent.set_loader(loader)

def get_plays(self):
return self._entries[:]

def get_vars(self):
# This does nothing and has no purpose other than making
# Play.get_vars more simple and potentially faster
return {} if not self._parent else self._parent.get_vars()
2 changes: 1 addition & 1 deletion lib/ansible/playbook/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ def squash(self):
self._attributes[name] = getattr(self, name)
self._squashed = True

def copy(self):
def copy(self, **kwargs):
'''
Create a copy of this object and return it.
'''
Expand Down
43 changes: 24 additions & 19 deletions lib/ansible/playbook/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,30 +59,30 @@ def __init__(self, play=None, parent_block=None, role=None, task_include=None, u
elif parent_block:
self._parent = parent_block

if self._parent is None:
self._parent = role if role else play

super(Block, self).__init__()

def __repr__(self):
return "BLOCK(uuid=%s)(id=%s)(parent=%s)" % (self._uuid, id(self), self._parent)

def __eq__(self, other):
'''object comparison based on _uuid'''
return self._uuid == other._uuid
return isinstance(other, Block) and self._uuid == other._uuid

def __ne__(self, other):
'''object comparison based on _uuid'''
return self._uuid != other._uuid
return isinstance(other, Block) and self._uuid != other._uuid

def get_vars(self):
'''
Blocks do not store variables directly, however they may be a member
of a role or task include which does, so return those if present.
'''

all_vars = self.vars.copy()

if self._parent:
all_vars.update(self._parent.get_vars())

all_vars = self._parent.get_vars()
all_vars.update(self.vars.copy())
return all_vars

@staticmethod
Expand Down Expand Up @@ -169,26 +169,30 @@ def _validate_always(self, attr, name, value):
def get_dep_chain(self):
if self._dep_chain is None:
if self._parent:
return self._parent.get_dep_chain()
try:
return self._parent.get_dep_chain()
except AttributeError:
return None
else:
return None
else:
return self._dep_chain[:]

def copy(self, exclude_parent=False, exclude_tasks=False):
def copy(self, exclude_parent=False, exclude_tasks=False, direct_parent=False):
def _dupe_task_list(task_list, new_block):
new_task_list = []
for task in task_list:
new_task = task.copy(exclude_parent=True)
if task._parent:
new_task._parent = task._parent.copy(exclude_tasks=True)
new_task._parent = task._parent.copy(exclude_tasks=True, direct_parent=True)
if task._parent == new_block:
# If task._parent is the same as new_block, just replace it
new_task._parent = new_block
else:
# task may not be a direct child of new_block, search for the correct place to insert new_block
cur_obj = new_task._parent
while cur_obj._parent and cur_obj._parent != new_block:
# Prevent the new block from becoming a parent higher than the Play
while cur_obj._parent.__class__.__name__ != 'Play' and cur_obj._parent != new_block:
cur_obj = cur_obj._parent

cur_obj._parent = new_block
Expand All @@ -205,7 +209,9 @@ def _dupe_task_list(task_list, new_block):
new_me._dep_chain = self._dep_chain[:]

new_me._parent = None
if self._parent and not exclude_parent:
if direct_parent:
new_me._parent = self._parent
elif exclude_parent is False:
new_me._parent = self._parent.copy(exclude_tasks=True)

if not exclude_tasks:
Expand Down Expand Up @@ -376,8 +382,7 @@ def evaluate_and_append_task(target):
return tmp_list

def evaluate_block(block):
new_block = block.copy(exclude_parent=True, exclude_tasks=True)
new_block._parent = block._parent
new_block = block.copy(direct_parent=True, exclude_tasks=True)
new_block.block = evaluate_and_append_task(block.block)
new_block.rescue = evaluate_and_append_task(block.rescue)
new_block.always = evaluate_and_append_task(block.always)
Expand All @@ -389,10 +394,10 @@ def has_tasks(self):
return len(self.block) > 0 or len(self.rescue) > 0 or len(self.always) > 0

def get_include_params(self):
if self._parent:
try:
return self._parent.get_include_params()
else:
return dict()
except AttributeError:
return {}

def all_parents_static(self):
'''
Expand All @@ -402,7 +407,7 @@ def all_parents_static(self):
the chain check the statically_loaded value of the parent.
'''
from ansible.playbook.task_include import TaskInclude
if self._parent:
if self._parent and hasattr(self._parent, 'all_parents_static'):
if isinstance(self._parent, TaskInclude) and not self._parent.statically_loaded:
return False
return self._parent.all_parents_static()
Expand All @@ -411,7 +416,7 @@ def all_parents_static(self):

def get_first_parent_include(self):
from ansible.playbook.task_include import TaskInclude
if self._parent:
if self._parent and hasattr(self._parent, 'get_first_parent_include'):
if isinstance(self._parent, TaskInclude):
return self._parent
return self._parent.get_first_parent_include()
Expand Down
Loading