Ansible variable value as variable name - ansible

I want to make a variable named as value of _id.
And it seems to be working fine (tested on _id value i know), except I have no clue how to deal with it via _id field.
- name: Set fact
set_fact:
"{{_id}}": "{{ _name }}"

You can do this with the set_fact module, you just have to change your syntax up a bit to reference the metadata in a dictionary for the variable.
- set_fact: {"{{ item.key }}":"{{ item.val }}"}
loop: "{{ item }}"
loop_control:
loop_var: item
Let me know if you have any questions or need any additional help. Happy to assist if I can. Ansible is fun. :)

Related

Ansible - multiple items in path, but cannot use loop

I'm not sure how to describe the title or my question properly, feel free to edit.
I'll jump right in. I have this working piece of Ansible code:
- file:
path: "{{ item.item.value.my_folder }}/{{ item.item.value.filename }}"
state: absent
loop: "{{ my_stat.results }}"
when: item.stat is defined and item.stat.exists and item.stat.islnk
If Ansible is run, the task is executed properly, and the file is removed from the system.
Now, the issue. What I want Ansible to do is loop over multiple items described in "path". This way I won't have to create a seperate task for each filename I want to be deleted.
Example:
- file:
path:
- "{{ item.item.value.my_folder }}/{{ item.item.value.filename }}"
- "{{ item.item.value.my_folder }}/{{ item.item.value.other_filename }}"
state: absent
loop: "{{ my_stat.results }}"
when: item.stat is defined and item.stat.exists and item.stat.islnk
But Ansible doesn't proces the items in the list described in 'path', so the filesnames will not be deleted.
I see I cannot use 'loop', since it is already in use for another value.
Question: How would I configure Ansible so that I can have multiple items in the path and let Ansible delete the filenames, and keeping the current loop intact.
-- EDIT --
Output of the tasks:
I've removed the pastebin url since I believe it has no added value for the question, and the answer has been given.
As described in the documentation, path is of type path, so Ansible will only accept a valid path in there, not a list.
What you can do, though, is to slightly modify your loop and make a product between your existing list and a list of the filenames properties you want to remove, then use those as the key to access item.item.value (or item.0.item.value now, since we have the product filter applied).
For example:
- file:
path: "{{ item.0.item.value.my_folder }}/{{ item.0.item.value[item.1] }}"
state: absent
loop: "{{ my_stat.results | product(['filename', 'other_filename']) }}"
when:
- item.0.stat is defined
- item.0.stat.exists
- item.0.stat.islnk
PS: a list in a when is the same as adding and statements in the said when

How can I nest a with_filetree in Ansible?

I'm using Ansible to push config files for various apps (based on group_names) & need to loop thru the config .j2 templates from a list variable. If I use a known list of config templates I can use a standard with_nested like this...
template:
src: '{{ playbook_dir }}/templates/{{ item[1] }}/configs/{{ item[0] }}.j2'
dest: /path/to/{{ item[1] }}/configs/{{ item[0] }}
with_nested:
- ['file.1', 'file.2', 'file.3', 'file.4']
- '{{ group_names }}'
However, since each app will have its own configs I can't use a common list for a with_nested. Every attempt to somehow use with_filetree nested fails. Is there any way to nest a with_filetree? Am I missing something painfully obvious?
The most straightforward way to deal with this is probably to imbricate loops through an include. I take for granted that your app directory only contains .j2 files. Adapt if this is not the case.
In e.g. push_templates.yml
---
- name: Copy templates for group {{ current_group }}
template:
src: "{{ item.src }}"
dest: /path/to/{{ current_group }}/configs/{{ (item.src | splitext).0 | basename }}
with_filetree: "{{ playbook_dir }}/templates/{{ current_group }}"
# Or using the latest loop syntax
# loop: "{{ query('filetree', playbook_dir + '/templates/' + current_group) }}"
when: item.src is defined
Note: on the dest line, I am removing the last found extension of the file and getting its name only without the leading directory path. Check the ansible doc on filters for splitext and basename for more info
Then in your e.g. main.yml
- name: Copy templates for all groups
include_tasks: push_templates.yml
loop: "{{ group_names }}"
loop_control:
loop_var: current_group
Note the loop_var in the control section to disambiguate the possible item overlap in the included file. The var name is of course aligned with the one I used in the above included file. See the ansible loops documentation for more info.
An alternative approach to the above would be to construct your own data structure looping over your groups with set_fact and calling the filetree lookup on each iteration (see example above with the newer loop syntax), then loop over your custom data structure to do the job.

How to init var of hostvars to null

I would like to reset a varaible to an empty list on all nodes of my inventory, whether it's already existing or not.
The best I come with is:
- name: clear union_files list
set_fact:
hostvars: "{{ hostvars.item | combine({'union_files': []}) }}"
loop: "{{ groups['all'] }}"
run_once: true
But with this code, I've got this error that I don't understand:
MSG:
|combine expects dictionaries, got Undefined
Why it is saying undefined while I'm setting the dict {'union_files': []}
Have you got any clue? What would by the most ansible way to do so?
There are a couple of things going on with this; first, hostvars.item is not the same as hostvars[item]; the former syntax is designed for a quick-and-dirty way of dereferencing keys in a dict, such as ansible_version.full which is not a variable named full, it's ansible_version["full"]. The latter is looking up a variable key name in the dict
The second thing going on is that you are attempting to assign hostvars as if it were a fact, but it's not a fact, it's a namespace in which facts live. So even if you were to do hostvars: {{ hostvars[item] }} it would create a fact named hostvars in the hostvars namespace
If you just want to clear the union_files fact, then use set_fact as you normally would:
- name: clear union_files list
set_fact:
union_files: []
and, assuming the playbook in which that task lives is hosts: all, then it will cheerfully set, or reset, union_files to [] on every host
Thanks Matthew,
I finally come up with this solution:
- name: clear list
set_fact:
union_files: []
delegate_to: item
loop: "{{ ansible_play_hosts }}"
run_once: true
which do the job.

Overwrite ansible variable from file fails

I'm trying to overwrite a previously defined variable my_var (when it's set to LATEST) by loading a value from a file (containing, say, NEWVALUE).
- name: Load from file
vars:
my_var: "{{ lookup('file', '~/file.txt') }}"
my_var2: "{{ lookup('file', '~/file.txt') }}"
debug: msg="my_var is {{ my_var }} my_var2 is {{ my_var2 }}"
when: "{{ my_var=='LATEST' }}"
This prints
ok: [host] ==> {
"msg": "my_var is LATEST my_var2 is NEWVALUE"
}
So I feel that I've verified that I'm loading the value correctly.. but for some reason I can't set the result of lookup in a previously set variable. Disabling the when clause doesn't seem to make any difference.
Should I be able to do this? As an alternative I'm going to use a third variable and just set it to either the preexisting value or the value from the file - but this seems like an unnecessary step to me.
Ansible version 2.1.0.0 b.t.w.
The vars you defined in your example only are available for the single debug task.
As you mentioned in the comment you figured this out and used set_fact instead. And yes, this won't work if you have passed the same variable as extra-var, as it has the highest precedence. There is no way to override a variable you passed as extra-var.

How to use Ansible's with_item with a variable?

I'm trying to transform some fields of the items of a list in an Ansible Playbook. Here is the simplest reproduction path, skipping the transformation. The result should be identical to the users variable.
---
# Run with:
# ansible-playbook -i "localhost," loop3.yml
- hosts: localhost
connection: local
gather_facts: false
vars:
users:
- name: paul
uid: 1
- name: pete
uid: 2
tasks:
- set_fact:
args:
useritem:
name: '{{ item.name }}'
uid: '{{ item.uid }}'
with_items:
- users
register: sf_result
- debug: var=sf_result
- set_fact:
userslist: "{{ sf_result.results | map(attribute='ansible_facts.useritem') | list }}"
- debug: var=userslist
I get this error:
TASK [set_fact useritem={u'name': u'{{ item.name }}', u'uid': u'{{ item.uid }}'}] ***
fatal: [localhost]: FAILED! => {"failed": true, "msg": "ERROR! 'unicode object' has no attribute 'name'"}
There are several examples very close to what I needbut I could find no working example using set_fact along with with_items and items as a map.
I've tried Ansible 1.9.2, 1.9.4, and 2.0.0-0.6.rc1 with different error messages but no more success. Ansible 2 should allow skipping the second call to set_fact but the error happens before getting there.
I thought I did read somewhere that with_items accepts a bare variable name, but it's not the case.
The program runs as expected using:
with_items: "{{ users }}"
Referencing simple variables
After you define a variable, use Jinja2 syntax to reference it. Jinja2 variables use double curly braces. For example, the expression users goes to {{ users }} demonstrates the most basic form of variable substitution. You can use Jinja2 syntax in playbooks. For example:
with_items: "{{ users }}"
and also
loop: "{{ users }}"
Now, the following parameters can be used to loop through an array/dictionary/list.
loop (preferred)
with_items
with_list
NOTE: When possible, Ansible recommends using the loop parameter, as the loop parameter is meant to supersede the with_items option.
with_items:
Ansible with_items is a lookup type plugin that is used to return list items passed into it. When we pass a list of items to a task, then the task will be performed for all items in that list. If a high-level item has also another list, then that list will be flattened and Ansible will not perform recursion for it. This feature is not available in it. Because that is done by another plugin named list lookup. You can use it to achieve recursion.
Also, you can pass multiple entries in a single item to pass data to parameters when you are running a task that needs more than one parameter like while adding a user, you might need to pass userid, name, groups, etc. This flexibility makes it more suitable in real-world scenarios.
- name: with_items
ansible.builtin.debug:
msg: "{{ item }}"
with_items: "{{ items }}"
- name: with_items -> loop
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ items|flatten(levels=1) }}"
Comparing loop and with_*
The with_ keywords rely on Lookup plugins - even items is a lookup.
The loop keyword is equivalent to with_list and is the best choice for simple loops.
The loop keyword will not accept a string as input, see Ensuring list input for loop: using query rather than lookup.
Generally speaking, any use of with_* covered in Migrating from with_X to loop can be updated to use loop.
Be careful when changing with_items to loop, as with_items performed implicit single-level flattening. You may need to use flatten(1) with loop to match the exact outcome. For example, to get the same output as:
with_items:
- 1
- [2,3]
- 4
you would need
loop: "{{ [1, [2, 3], 4] | flatten(1) }}"
Any with_* statement that requires using lookup within a loop should not be converted to use the loop keyword. For example, instead of doing:
loop: "{{ lookup('fileglob', '*.txt', wantlist=True) }}"

Resources