diff --git a/lib/ansible/playbook/role_include.py b/lib/ansible/playbook/role_include.py index 51f3990b0da..7d5b2351d0e 100644 --- a/lib/ansible/playbook/role_include.py +++ b/lib/ansible/playbook/role_include.py @@ -76,10 +76,16 @@ class IncludeRole(Task): actual_role = Role.load(ri, myplay, parent_role=self._parent_role, from_files=self._from_files) actual_role._metadata.allow_duplicates = self.allow_duplicates - # compile role - blocks = actual_role.compile(play=myplay) + # compile role with parent roles as dependencies to ensure they inherit + # variables + if not self._parent_role: + dep_chain = [] + else: + dep_chain = list(self._parent_role._parents) + dep_chain.extend(self._parent_role.get_all_dependencies()) + dep_chain.append(self._parent_role) - # set parent to ensure proper inheritance + blocks = actual_role.compile(play=myplay, dep_chain=dep_chain) for b in blocks: b._parent = self diff --git a/test/units/playbook/role/test_include_role.py b/test/units/playbook/role/test_include_role.py new file mode 100644 index 00000000000..7098346b9c1 --- /dev/null +++ b/test/units/playbook/role/test_include_role.py @@ -0,0 +1,229 @@ +# (c) 2016, Daniel Miranda +# +# 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 . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.compat.tests import unittest +from ansible.compat.tests.mock import patch + +from ansible.playbook import Play +from ansible.playbook.task import Task +from ansible.vars import VariableManager + +from units.mock.loader import DictDataLoader +from units.mock.path import mock_unfrackpath_noop + + +def flatten_tasks(tasks): + for task in tasks: + if isinstance(task, Task): + yield task + else: + for t in flatten_tasks(task.block): + yield t + + +class TestIncludeRole(unittest.TestCase): + + def setUp(self): + self.var_manager = VariableManager() + + self.loader = DictDataLoader({ + '/etc/ansible/roles/l1/tasks/main.yml': """ + - shell: echo 'hello world from l1' + - include_role: name=l2 + """, + '/etc/ansible/roles/l1/tasks/alt.yml': """ + - shell: echo 'hello world from l1 alt' + - include_role: name=l2 tasks_from=alt defaults_from=alt + """, + '/etc/ansible/roles/l1/defaults/main.yml': """ + test_variable: l1-main + l1_variable: l1-main + """, + '/etc/ansible/roles/l1/defaults/alt.yml': """ + test_variable: l1-alt + l1_variable: l1-alt + """, + '/etc/ansible/roles/l2/tasks/main.yml': """ + - shell: echo 'hello world from l2' + - include_role: name=l3 + """, + '/etc/ansible/roles/l2/tasks/alt.yml': """ + - shell: echo 'hello world from l2 alt' + - include_role: name=l3 tasks_from=alt defaults_from=alt + """, + '/etc/ansible/roles/l2/defaults/main.yml': """ + test_variable: l2-main + l2_variable: l2-main + """, + '/etc/ansible/roles/l2/defaults/alt.yml': """ + test_variable: l2-alt + l2_variable: l2-alt + """, + '/etc/ansible/roles/l3/tasks/main.yml': """ + - shell: echo 'hello world from l3' + """, + '/etc/ansible/roles/l3/tasks/alt.yml': """ + - shell: echo 'hello world from l3 alt' + """, + '/etc/ansible/roles/l3/defaults/main.yml': """ + test_variable: l3-main + l3_variable: l3-main + """, + '/etc/ansible/roles/l3/defaults/alt.yml': """ + test_variable: l3-alt + l3_variable: l3-alt + """ + }) + + def tearDown(self): + pass + + def get_tasks_vars(self, play, tasks): + for task in flatten_tasks(tasks): + role = task._role + if not role: + continue + + yield (role.get_name(), + self.var_manager.get_vars(self.loader, play=play, + task=task)) + + @patch('ansible.playbook.role.definition.unfrackpath', + mock_unfrackpath_noop) + def test_simple(self): + + """Test one-level include with default tasks and variables""" + + play = Play.load(dict( + name="test play", + hosts=['foo'], + gather_facts=False, + tasks=[ + {'include_role': 'name=l3'} + ] + ), loader=self.loader, variable_manager=self.var_manager) + + tasks = play.compile() + for role, task_vars in self.get_tasks_vars(play, tasks): + self.assertEqual(task_vars.get('l3_variable'), 'l3-main') + self.assertEqual(task_vars.get('test_variable'), 'l3-main') + + @patch('ansible.playbook.role.definition.unfrackpath', + mock_unfrackpath_noop) + def test_simple_alt_files(self): + + """Test one-level include with alternative tasks and variables""" + + play = Play.load(dict( + name="test play", + hosts=['foo'], + gather_facts=False, + tasks=[ + {'include_role': 'name=l3 tasks_from=alt defaults_from=alt'} + ] + ), loader=self.loader, variable_manager=self.var_manager) + + tasks = play.compile() + for role, task_vars in self.get_tasks_vars(play, tasks): + self.assertEqual(task_vars.get('l3_variable'), 'l3-alt') + self.assertEqual(task_vars.get('test_variable'), 'l3-alt') + + @patch('ansible.playbook.role.definition.unfrackpath', + mock_unfrackpath_noop) + def test_nested(self): + + """ + Test nested includes with default tasks and variables. + + Variables from outer roles should be inherited, but overriden in inner + roles. + """ + + play = Play.load(dict( + name="test play", + hosts=['foo'], + gather_facts=False, + tasks=[ + {'include_role': 'name=l1'} + ] + ), loader=self.loader, variable_manager=self.var_manager) + + tasks = play.compile() + for role, task_vars in self.get_tasks_vars(play, tasks): + # Outer-most role must not have variables from inner roles yet + if role == 'l1': + self.assertEqual(task_vars.get('l1_variable'), 'l1-main') + self.assertEqual(task_vars.get('l2_variable'), None) + self.assertEqual(task_vars.get('l3_variable'), None) + self.assertEqual(task_vars.get('test_variable'), 'l1-main') + # Middle role must have variables from outer role, but not inner + elif role == 'l2': + self.assertEqual(task_vars.get('l1_variable'), 'l1-main') + self.assertEqual(task_vars.get('l2_variable'), 'l2-main') + self.assertEqual(task_vars.get('l3_variable'), None) + self.assertEqual(task_vars.get('test_variable'), 'l2-main') + # Inner role must have variables from both outer roles + elif role == 'l3': + self.assertEqual(task_vars.get('l1_variable'), 'l1-main') + self.assertEqual(task_vars.get('l2_variable'), 'l2-main') + self.assertEqual(task_vars.get('l3_variable'), 'l3-main') + self.assertEqual(task_vars.get('test_variable'), 'l3-main') + + @patch('ansible.playbook.role.definition.unfrackpath', + mock_unfrackpath_noop) + def test_nested_alt_files(self): + + """ + Test nested includes with alternative tasks and variables. + + Variables from outer roles should be inherited, but overriden in inner + roles. + """ + + play = Play.load(dict( + name="test play", + hosts=['foo'], + gather_facts=False, + tasks=[ + {'include_role': 'name=l1 tasks_from=alt defaults_from=alt'} + ] + ), loader=self.loader, variable_manager=self.var_manager) + + tasks = play.compile() + for role, task_vars in self.get_tasks_vars(play, tasks): + # Outer-most role must not have variables from inner roles yet + if role == 'l1': + self.assertEqual(task_vars.get('l1_variable'), 'l1-alt') + self.assertEqual(task_vars.get('l2_variable'), None) + self.assertEqual(task_vars.get('l3_variable'), None) + self.assertEqual(task_vars.get('test_variable'), 'l1-alt') + # Middle role must have variables from outer role, but not inner + elif role == 'l2': + self.assertEqual(task_vars.get('l1_variable'), 'l1-alt') + self.assertEqual(task_vars.get('l2_variable'), 'l2-alt') + self.assertEqual(task_vars.get('l3_variable'), None) + self.assertEqual(task_vars.get('test_variable'), 'l2-alt') + # Inner role must have variables from both outer roles + elif role == 'l3': + self.assertEqual(task_vars.get('l1_variable'), 'l1-alt') + self.assertEqual(task_vars.get('l2_variable'), 'l2-alt') + self.assertEqual(task_vars.get('l3_variable'), 'l3-alt') + self.assertEqual(task_vars.get('test_variable'), 'l3-alt')