Hello everyone, would someone be able to explain the behavior I am encountering (or tell me where I mess up )?
I have a dictionary which contains variables with (i.a.) filenames. The purpose of this is to copy files (plural!) from the local host to the remote host. As this is a recurring task in my repository I have created an utility script to verify & copy the files and whenever I need this kind of action in a playbook, I use ‘include_tasks’ to call the utility script. The first step in the utility script is to check whether the filename is ‘yml’ or ‘j2’ as I later on need to use ‘ansible.builtin.copy’ or ‘…template’.
When I call this script I get an error that the task includes an option with an undefined variable. However, when I run the code from the utility script in the main script, it works. Upon troubleshooting I noticed that within the main script the loop is ran as a separate dictionary while in the utility script the loop is ran as one dictionary.
I have the following two stand-alone scripts to reproduce the issue.
main.yml
- name: 'A script to show my problem.'
hosts: 'localhost'
check_mode: true
gather_facts: true
tasks:
- name: 'Deduct the file format (within this scipt).'
ansible.builtin.debug:
msg: "{{ deduct_filename.value.from | split('.') | last }}"
loop: "{{ lookup('dict', files) }}"
loop_control:
loop_var: 'deduct_filename'
- name: 'Deduct the file format (with an helper script).'
ansible.builtin.include_tasks: 'copy_file.yml'
loop:
- "{{ lookup('dict', files) }}"
loop_control:
loop_var: 'deduct_filename'
vars:
files:
test_file_1:
from: '/path/to/location.yml'
to: 'not/important.yml'
test_file_2:
from: '/path/to/location.j2'
to: 'not/important.j2'
copy_file.yml
- name: 'Deduct the file format (within this scipt).'
ansible.builtin.debug:
msg: "{{ deduct_filename.value.from | split('.') | last }}"
# in the actual script
# task: when needed, validate file.
# task: verify directory exists on remote host. If not, stop.
# tasks: copy/template file with correct owner, group, mode.
Running main.yml results in the following output:
PLAY [A script to show all the dictonary (filter) options.] *****
TASK [Gathering Facts] *****
ok: [localhost]
TASK [Deduct the file format (within this script).] *****
ok: [localhost] => (item={'key': 'test_file_1', 'value': {'from': '/path/to/location.yml', 'to': 'not/important.yml'}}) => {
"msg": "yml"
}
ok: [localhost] => (item={'key': 'test_file_2', 'value': {'from': '/path/to/location.j2', 'to': 'not/important.j2'}}) => {
"msg": "j2"
}
TASK [Deduct the file format (with an helper script).] *****
included: copy_file.yml for localhost => (item=[{'key': 'test_file_1', 'value': {'from': '/path/to/location.yml', 'to': 'not/important.yml'}}, {'key': 'test_file_2', 'value': {'from': '/path/to/location.j2', 'to': 'not/important.j2'}}])
TASK [Show passed along variable.] *****
ok: [localhost] => {
"msg": [
{
"key": "test_file_1",
"value": {
"from": "/path/to/location.yml",
"to": "not/important.yml"
}
},
{
"key": "test_file_2",
"value": {
"from": "/path/to/location.j2",
"to": "not/important.j2"
}
}
]
}
TASK [Reduct the file format (within this scipt).] *****
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: Unable to look up a name or access an attribute in template string ({{ deduct_filename.value.from | split('.') | last }}).\nMake sure your variable name does not contain invalid characters like '-': descriptor 'split' for 'str' objects doesn't apply to a 'AnsibleUndefined' object. descriptor 'split' for 'str' objects doesn't apply to a 'AnsibleUndefined' object. Unable to look up a name or access an attribute in template string ({{ deduct_filename.value.from | split('.') | last }}).\nMake sure your variable name does not contain invalid characters like '-': descriptor 'split' for 'str' objects doesn't apply to a 'AnsibleUndefined' object. descriptor 'split' for 'str' objects doesn't apply to a 'AnsibleUndefined' object\n\nThe error appears to be in 'files_copy.yml': line 24, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: 'Reduct the file format (within this scipt).'\n ^ here\n"}
PLAY RECAP *****
localhost : ok=4 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
As you can see the task ‘Show passed along variable.’ returns the dictionary as one item where the task ‘Deduct the file format (within this script).’ above it returns it as two dictionaries.
Details about my Ansible version:
ansible [core 2.16.3]
config file = [redacted]
configured module search path = ['[redacted].ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
ansible collection location = [redacted].ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible
python version = 3.12.3 (main, Feb 4 2025, 14:48:35) [GCC 13.3.0] (/usr/bin/python3)
jinja version = 3.1.2
libyaml = True