I'm using the following fact:
- name: Set server ips fact
ansible.builtin.set_fact:
k3s_server_ips: "{{ k3s_server_hosts | map('extract', hostvars, ['ansible_default_ipv4', 'address']) }}"
when: ansible_host in k3s_server_hosts
This produces:
ok: [apollo] => changed=false
ansible_facts:
k3s_server_ips:
- 192.168.4.2
- 192.168.4.3
- 192.168.4.4
When I use the defined fact inside a Jinja2 Template:
kubeProxy:
endpoints: '{{ k3s_server_ips }}'
The following error is displayed:
Traceback (most recent call last):
File "/usr/local/Cellar/ansible/9.4.0/libexec/lib/python3.12/site-packages/ansible/executor/task_executor.py", line 526, in _execute
self._task.post_validate(templar=templar)
File "/usr/local/Cellar/ansible/9.4.0/libexec/lib/python3.12/site-packages/ansible/playbook/task.py", line 290, in post_validate
super(Task, self).post_validate(templar)
File "/usr/local/Cellar/ansible/9.4.0/libexec/lib/python3.12/site-packages/ansible/playbook/base.py", line 543, in post_validate
value = method(attribute, getattr(self, name), templar)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/Cellar/ansible/9.4.0/libexec/lib/python3.12/site-packages/ansible/playbook/task.py", line 298, in _post_validate_args
args = templar.template(value)
^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/Cellar/ansible/9.4.0/libexec/lib/python3.12/site-packages/ansible/template/__init__.py", line 791, in template
d[k] = self.template(
^^^^^^^^^^^^^^
File "/usr/local/Cellar/ansible/9.4.0/libexec/lib/python3.12/site-packages/ansible/template/__init__.py", line 764, in template
result = self.do_template(
^^^^^^^^^^^^^^^^^
File "/usr/local/Cellar/ansible/9.4.0/libexec/lib/python3.12/site-packages/ansible/template/__init__.py", line 1010, in do_template
res = myenv.concat(rf)
^^^^^^^^^^^^^^^^
File "/usr/local/Cellar/ansible/9.4.0/libexec/lib/python3.12/site-packages/ansible/template/native_helpers.py", line 43, in ansible_eval_concat
head = list(islice(nodes, 2))
^^^^^^^^^^^^^^^^^^^^^^
File "<template>", line 18, in root
File "/usr/local/Cellar/ansible/9.4.0/libexec/lib/python3.12/site-packages/ansible/template/__init__.py", line 295, in wrapper
ret = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/Cellar/ansible/9.4.0/libexec/lib/python3.12/site-packages/ansible/plugins/filter/core.py", line 221, in from_yaml
return yaml_load(text_type(to_text(data, errors='surrogate_or_strict')))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/Cellar/ansible/9.4.0/libexec/lib/python3.12/site-packages/yaml/__init__.py", line 81, in load
return loader.get_single_data()
^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/Cellar/ansible/9.4.0/libexec/lib/python3.12/site-packages/yaml/constructor.py", line 49, in get_single_data
node = self.get_single_node()
^^^^^^^^^^^^^^^^^^^^^^
File "yaml/_yaml.pyx", line 673, in yaml._yaml.CParser.get_single_node
File "yaml/_yaml.pyx", line 687, in yaml._yaml.CParser._compose_document
File "yaml/_yaml.pyx", line 731, in yaml._yaml.CParser._compose_node
File "yaml/_yaml.pyx", line 845, in yaml._yaml.CParser._compose_mapping_node
File "yaml/_yaml.pyx", line 731, in yaml._yaml.CParser._compose_node
File "yaml/_yaml.pyx", line 847, in yaml._yaml.CParser._compose_mapping_node
File "yaml/_yaml.pyx", line 860, in yaml._yaml.CParser._parse_next_event
yaml.scanner.ScannerError: mapping values are not allowed in this context
in "<unicode string>", line 26, column 68
fatal: [apollo]: FAILED! => changed=false
This happens only when I use the fact inside the template, if I use k3s_server_ips
inside a task, there are no issues.
As workaround, I tried:
kubeProxy:
endpoints:
{% for ip in k3s_server_ips %}
- {{ ip }}
{% endfor %}
Which fixes the issue. I'm wondering if there is a better implementation.
The error message
yaml.scanner.ScannerError: mapping values are not allowed in this contextvin "<unicode string>"
indicates syntax and or indention errors within your created YAML structure.
For a Jinja2 Template called templates/my_list.j2
as input and with content of
kubeProxy:
endpoints:
{{ k3s_server_ips | to_nice_yaml(indent=4) | trim | indent(4) }}
as_well:
{% for ip in k3s_server_ips %}
- {{ ip }}
{% endfor %}
processing with a minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
vars:
k3s_server_ips:
- 192.168.4.2
- 192.168.4.3
- 192.168.4.4
tasks:
- template:
src: templates/my_list.j2
dest: kubeProxy.endpoints.yml
- name: "Set from template"
set_fact:
my_list: "{{ lookup('ansible.builtin.template', 'templates/my_list.j2') }}"
- debug:
msg: "{{ my_list | from_yaml }}"
will result into an output of
TASK [debug] ******
ok: [localhost] =>
msg:
kubeProxy:
as_well:
- 192.168.4.2
- 192.168.4.3
- 192.168.4.4
endpoints:
- 192.168.4.2
- 192.168.4.3
- 192.168.4.4
as well a file written, called kubeProxy.endpoints.yml
with content of
kubeProxy:
endpoints:
- 192.168.4.2
- 192.168.4.3
- 192.168.4.4
as_well:
- 192.168.4.2
- 192.168.4.3
- 192.168.4.4
Thanks To / Similar Q&A
"I'm wondering if there is a better implementation."
Probably yes, by not using set_fact
, respective avoiding intermediate facts. Just take the shortcut and not the detour via the playbook by directly generating the k3s_server_ips
list within the Jinja2 Template.