diff --git a/changelogs/fragments/75269-import_role-support-from-files-templates.yml b/changelogs/fragments/75269-import_role-support-from-files-templates.yml new file mode 100644 index 00000000000..1f89a48dd19 --- /dev/null +++ b/changelogs/fragments/75269-import_role-support-from-files-templates.yml @@ -0,0 +1,2 @@ +minor_changes: + - import_role - Template tasks_from, vars_from, defaults_from, and handlers_from with --extra-vars (https://github.com/ansible/ansible/issues/69097). diff --git a/docs/docsite/rst/user_guide/playbooks_reuse.rst b/docs/docsite/rst/user_guide/playbooks_reuse.rst index bbd7118598f..a2095ef3643 100644 --- a/docs/docsite/rst/user_guide/playbooks_reuse.rst +++ b/docs/docsite/rst/user_guide/playbooks_reuse.rst @@ -33,6 +33,17 @@ You can incorporate multiple playbooks into a main playbook. However, you can on Importing incorporates playbooks in other playbooks statically. Ansible runs the plays and tasks in each imported playbook in the order they are listed, just as if they had been defined directly in the main playbook. +You can select which playbook you want to import at runtime by defining your imported playbook filename with a variable, then passing the variable with either ``--extra-vars`` or the ``vars`` keyword. For example: + +.. code-block:: yaml + + - import_playbook: "/path/to/{{ import_from_extra_var }}" + - import_playbook: "{{ import_from_vars }}" + vars: + import_from_vars: /path/to/one_playbook.yml + +If you run this playbook with ``ansible-playbook my_playbook -e import_from_extra_var=other_playbook.yml``, Ansible imports both one_playbook.yml and other_playbook.yml. + Re-using files and roles ======================== @@ -60,6 +71,8 @@ Including roles, tasks, or variables adds them to a playbook dynamically. Ansibl The primary advantage of using ``include_*`` statements is looping. When a loop is used with an include, the included tasks or role will be executed once for each item in the loop. +The filenames for included roles, tasks, and vars are templated before inclusion. + You can pass variables into includes. See :ref:`ansible_variable_precedence` for more details on variable inheritance and precedence. Imports: static re-use @@ -67,6 +80,8 @@ Imports: static re-use Importing roles, tasks, or playbooks adds them to a playbook statically. Ansible pre-processes imported files and roles before it runs any tasks in a playbook, so imported content is never affected by other tasks within the top-level playbook. +The filenames for imported roles and tasks support templating, but the variables must be available when Ansible is pre-processing the imports. This can be done with the ``vars`` keyword or by using ``--extra-vars``. + You can pass variables to imports. You must pass variables if you want to run an imported file more than once in a playbook. For example: .. code-block:: yaml diff --git a/lib/ansible/playbook/role_include.py b/lib/ansible/playbook/role_include.py index 59803f27c15..c6227031ca7 100644 --- a/lib/ansible/playbook/role_include.py +++ b/lib/ansible/playbook/role_include.py @@ -29,6 +29,7 @@ from ansible.playbook.role import Role from ansible.playbook.role.include import RoleInclude from ansible.utils.display import Display from ansible.module_utils.six import string_types +from ansible.template import Templar __all__ = ['IncludeRole'] @@ -79,8 +80,15 @@ class IncludeRole(TaskInclude): ri = RoleInclude.load(self._role_name, play=myplay, variable_manager=variable_manager, loader=loader, collection_list=self.collections) ri.vars.update(self.vars) + if variable_manager is not None: + available_variables = variable_manager.get_vars(play=myplay, task=self) + else: + available_variables = {} + templar = Templar(loader=loader, variables=available_variables) + from_files = templar.template(self._from_files) + # build role - actual_role = Role.load(ri, myplay, parent_role=self._parent_role, from_files=self._from_files, + actual_role = Role.load(ri, myplay, parent_role=self._parent_role, from_files=from_files, from_include=True, validate=self.rolespec_validate) actual_role._metadata.allow_duplicates = self.allow_duplicates diff --git a/test/integration/targets/include_import/playbook/test_templated_filenames.yml b/test/integration/targets/include_import/playbook/test_templated_filenames.yml new file mode 100644 index 00000000000..2f78ab098b8 --- /dev/null +++ b/test/integration/targets/include_import/playbook/test_templated_filenames.yml @@ -0,0 +1,47 @@ +- name: test templating import_playbook with extra vars + import_playbook: "{{ pb }}" + +- name: test templating import_playbook with vars + import_playbook: "{{ test_var }}" + vars: + test_var: validate_templated_playbook.yml + +- name: test templating import_tasks + hosts: localhost + gather_facts: no + vars: + play_var: validate_templated_tasks.yml + tasks: + - name: test templating import_tasks with play vars + import_tasks: "{{ play_var }}" + + - name: test templating import_tasks with task vars + import_tasks: "{{ task_var }}" + vars: + task_var: validate_templated_tasks.yml + + - name: test templating import_tasks with extra vars + import_tasks: "{{ tasks }}" + +- name: test templating import_role from_files + hosts: localhost + gather_facts: no + vars: + play_var: templated.yml + tasks: + - name: test templating import_role tasks_from with play vars + import_role: + name: role1 + tasks_from: "{{ play_var }}" + + - name: test templating import_role tasks_from with task vars + import_role: + name: role1 + tasks_from: "{{ task_var }}" + vars: + task_var: templated.yml + + - name: test templating import_role tasks_from with extra vars + import_role: + name: role1 + tasks_from: "{{ tasks_from }}" diff --git a/test/integration/targets/include_import/playbook/validate_templated_playbook.yml b/test/integration/targets/include_import/playbook/validate_templated_playbook.yml new file mode 100644 index 00000000000..631ee9b47c1 --- /dev/null +++ b/test/integration/targets/include_import/playbook/validate_templated_playbook.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + gather_facts: no + tasks: + - debug: msg="In imported playbook" diff --git a/test/integration/targets/include_import/playbook/validate_templated_tasks.yml b/test/integration/targets/include_import/playbook/validate_templated_tasks.yml new file mode 100644 index 00000000000..16d682d1fba --- /dev/null +++ b/test/integration/targets/include_import/playbook/validate_templated_tasks.yml @@ -0,0 +1 @@ +- debug: msg="In imported tasks" diff --git a/test/integration/targets/include_import/roles/role1/tasks/templated.yml b/test/integration/targets/include_import/roles/role1/tasks/templated.yml new file mode 100644 index 00000000000..eb9a997676b --- /dev/null +++ b/test/integration/targets/include_import/roles/role1/tasks/templated.yml @@ -0,0 +1 @@ +- debug: msg="In imported role" diff --git a/test/integration/targets/include_import/runme.sh b/test/integration/targets/include_import/runme.sh index 8ddbfa61fab..7029ab6d627 100755 --- a/test/integration/targets/include_import/runme.sh +++ b/test/integration/targets/include_import/runme.sh @@ -128,3 +128,10 @@ ansible-playbook test_include_loop.yml "$@" ansible-playbook test_include_loop_fqcn.yml "$@" ansible-playbook include_role_omit/playbook.yml "$@" + +# Test templating import_playbook, import_tasks, and import_role files +ansible-playbook playbook/test_templated_filenames.yml -e "pb=validate_templated_playbook.yml tasks=validate_templated_tasks.yml tasks_from=templated.yml" "$@" | tee out.txt +cat out.txt +test "$(grep out.txt -ce 'In imported playbook')" = 2 +test "$(grep out.txt -ce 'In imported tasks')" = 3 +test "$(grep out.txt -ce 'In imported role')" = 3