From 9b3ed5ec68a6edde5b061b18b9ebc603c3b87cc8 Mon Sep 17 00:00:00 2001 From: Martin Krizek Date: Tue, 3 Oct 2023 15:07:26 -0400 Subject: [PATCH] Properly template tags in parent blocks (#81624) When templating tags (which happens outside of standard `post_validate`) we need to template each object in the inheritance chain and set the templated values on those objects individually. That way when `task.tags` is called the `extend` functionality properly picks up the templated values of all parents into one flatten list. Fixes #81053 --- .../81053-templated-tags-inheritance.yml | 2 ++ lib/ansible/playbook/taggable.py | 33 ++++++++++--------- test/integration/targets/tags/runme.sh | 9 +++++ .../tags/test_template_parent_tags.yml | 10 ++++++ test/units/playbook/test_taggable.py | 1 + 5 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 changelogs/fragments/81053-templated-tags-inheritance.yml create mode 100644 test/integration/targets/tags/test_template_parent_tags.yml diff --git a/changelogs/fragments/81053-templated-tags-inheritance.yml b/changelogs/fragments/81053-templated-tags-inheritance.yml new file mode 100644 index 00000000000..64cacdd6b8a --- /dev/null +++ b/changelogs/fragments/81053-templated-tags-inheritance.yml @@ -0,0 +1,2 @@ +bugfixes: + - Properly template tags in parent blocks (https://github.com/ansible/ansible/issues/81053) diff --git a/lib/ansible/playbook/taggable.py b/lib/ansible/playbook/taggable.py index 4038d7f562e..828c7b2e5a1 100644 --- a/lib/ansible/playbook/taggable.py +++ b/lib/ansible/playbook/taggable.py @@ -23,6 +23,17 @@ from ansible.errors import AnsibleError from ansible.module_utils.six import string_types from ansible.playbook.attribute import FieldAttribute from ansible.template import Templar +from ansible.utils.sentinel import Sentinel + + +def _flatten_tags(tags: list) -> list: + rv = set() + for tag in tags: + if isinstance(tag, list): + rv.update(tag) + else: + rv.add(tag) + return list(rv) class Taggable: @@ -34,11 +45,7 @@ class Taggable: if isinstance(ds, list): return ds elif isinstance(ds, string_types): - value = ds.split(',') - if isinstance(value, list): - return [x.strip() for x in value] - else: - return [ds] + return [x.strip() for x in ds.split(',')] else: raise AnsibleError('tags must be specified as a list', obj=ds) @@ -47,16 +54,12 @@ class Taggable: if self.tags: templar = Templar(loader=self._loader, variables=all_vars) - tags = templar.template(self.tags) - - _temp_tags = set() - for tag in tags: - if isinstance(tag, list): - _temp_tags.update(tag) - else: - _temp_tags.add(tag) - tags = _temp_tags - self.tags = list(tags) + obj = self + while obj is not None: + if (_tags := getattr(obj, "_tags", Sentinel)) is not Sentinel: + obj._tags = _flatten_tags(templar.template(_tags)) + obj = obj._parent + tags = set(self.tags) else: # this makes isdisjoint work for untagged tags = self.untagged diff --git a/test/integration/targets/tags/runme.sh b/test/integration/targets/tags/runme.sh index 9da0b301619..7dcb998560b 100755 --- a/test/integration/targets/tags/runme.sh +++ b/test/integration/targets/tags/runme.sh @@ -73,3 +73,12 @@ ansible-playbook -i ../../inventory ansible_run_tags.yml -e expect=list --tags t ansible-playbook -i ../../inventory ansible_run_tags.yml -e expect=untagged --tags untagged "$@" ansible-playbook -i ../../inventory ansible_run_tags.yml -e expect=untagged_list --tags untagged,tag3 "$@" ansible-playbook -i ../../inventory ansible_run_tags.yml -e expect=tagged --tags tagged "$@" + +ansible-playbook test_template_parent_tags.yml "$@" 2>&1 | tee out.txt +[ "$(grep out.txt -ce 'Tagged_task')" = "1" ]; rm out.txt + +ansible-playbook test_template_parent_tags.yml --tags tag1 "$@" 2>&1 | tee out.txt +[ "$(grep out.txt -ce 'Tagged_task')" = "1" ]; rm out.txt + +ansible-playbook test_template_parent_tags.yml --skip-tags tag1 "$@" 2>&1 | tee out.txt +[ "$(grep out.txt -ce 'Tagged_task')" = "0" ]; rm out.txt diff --git a/test/integration/targets/tags/test_template_parent_tags.yml b/test/integration/targets/tags/test_template_parent_tags.yml new file mode 100644 index 00000000000..ea1c828973c --- /dev/null +++ b/test/integration/targets/tags/test_template_parent_tags.yml @@ -0,0 +1,10 @@ +- hosts: localhost + gather_facts: false + vars: + tags_in_var: + - tag1 + tasks: + - block: + - name: Tagged_task + debug: + tags: "{{ tags_in_var }}" diff --git a/test/units/playbook/test_taggable.py b/test/units/playbook/test_taggable.py index 3881e17dda8..c6ce35d3f0f 100644 --- a/test/units/playbook/test_taggable.py +++ b/test/units/playbook/test_taggable.py @@ -29,6 +29,7 @@ class TaggableTestObj(Taggable): def __init__(self): self._loader = DictDataLoader({}) self.tags = [] + self._parent = None class TestTaggable(unittest.TestCase):