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
This commit is contained in:
@ -0,0 +1,2 @@
|
||||
bugfixes:
|
||||
- Properly template tags in parent blocks (https://github.com/ansible/ansible/issues/81053)
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
10
test/integration/targets/tags/test_template_parent_tags.yml
Normal file
10
test/integration/targets/tags/test_template_parent_tags.yml
Normal file
@ -0,0 +1,10 @@
|
||||
- hosts: localhost
|
||||
gather_facts: false
|
||||
vars:
|
||||
tags_in_var:
|
||||
- tag1
|
||||
tasks:
|
||||
- block:
|
||||
- name: Tagged_task
|
||||
debug:
|
||||
tags: "{{ tags_in_var }}"
|
||||
@ -29,6 +29,7 @@ class TaggableTestObj(Taggable):
|
||||
def __init__(self):
|
||||
self._loader = DictDataLoader({})
|
||||
self.tags = []
|
||||
self._parent = None
|
||||
|
||||
|
||||
class TestTaggable(unittest.TestCase):
|
||||
|
||||
Reference in New Issue
Block a user