Access list sub-elements with wildcard in Ansible? - loops

I want to loop through the sub-elements of a list in Ansible.
I have a var file like this:
config:
machine1:
services:
sub-element11:
- value
ports:
sub-element21:
- value
- value
sub-element22:
- value
- value
machine2:
.
.
.
And I want to loop through the sub-elements while not giving the name of the element that comes before it:
- debug
var: item
with_items: "{{ config.machine1.* | list }}"
where item would have the value: sub-element11, sub-element21, sub-element22
I can use 2 loops as I know I'll always have "services" and "ports" as a key-word. But I don't want to clone everything that comes after.
Maybe I can use nested loops with set facts but is seems tedious.
Thank you for your help :)

Related

Ansible: How to assign an element to an array/variable which has dot in key-name?

I was adding few elements to an array, which is fetched from a file
The file contents are like
#shipping.yml
shipping.enabled: true
shipping.method: "postal"
country.states.availability: ["ST1", "ST2", "ST3"]
I'm trying to extract country.states.available from the file and add entries from another list to have more states and update the file back with new list
my playbook is something like
- include_vars:
file: shipping.yml
name: shipping_var
- set_fact:
shipping_var['country.states.availability']: "shipping_var['country.states.availability'] + ['{{ item }}'] "
loop: "{{ lookup ('file', 'new_state_list_per_line').splitlines()}}"
# And then I've to update the filename back to have the list with new states.availability
But getting error:
The variable name 'shipping_var['country.states.availability']' is not valid.
Variables must start with letter or underscore and contain only letters, numbers, underscore
Any chance on how to update a variable with dot in the key-name?
You cannot use set_fact to update directly a nested element. You have to use the top level element and work your way around. Note that your loop is not needed since you get a list you can directly concatenate to the existing one.
In your current situation you can fix the issue with the following. I broke the vars in different pieces in the task to have a readable final expression. I also added the unique and sort filter so that you produce the same result each time, even when running the task on an already updated file.
- name: update var with new countries
vars:
new_states: "{{ lookup ('file', 'new_state_list_per_line').splitlines()}}"
all_states: "{{ shipping_var['country.states.availability'] + new_states | unique | sort }}"
set_fact:
shipping_var: "{{ shipping_var | combine({'country.states.availability': all_states}) }}"
To push the content back into the original file, you just have to write out the content of the var back to the file in yaml format with the copy module for example:
- name: Update file with new content if needed
copy:
dest: path/to/shipping.yml
content: "{{ shipping_var | to_yaml }}"
owner: someuser
group: somegroup
mode: 0640
delegate_to: localhost
run_once: true

Run a command with different args everytime in Ansible playbook

I am trying to run a command in Ansible so as to find the neighbors in my network:
- name: Get neighbors
junos_rpc:
rpc: "get-lldp-interface-neighbors"
output: 'xml'
args:
interface_device: A
register: net_topology
So my problem comes when in this task I need to loop over a list and give another arg for the interface_device and register the result also in another variable 'net_topology' every time.
- name: Get neighbors
junos_rpc:
rpc: "get-lldp-interface-neighbors"
output: 'xml'
args:
interface_device: "{{ item }}"
loop:
- A
- B
- C
register: net_topology
Once you modify your task like this, it will play three times: once for each element in my example loop. The variable item will get the value of the current element in the list.
You do not need to change your register variable: it will automatically be modified as explained in the ansible documentation:
When you use register with a loop, the data structure placed in the variable will contain a results attribute that is a list of all responses from the module. This differs from the data structure returned when using register without a loop
So you can inspect all your results in a subsequent task by looping over net_topology.results which contains the list of individual results.
Actually i did something similar with the above, but i just passed my list with a different way:
- name: building network topology
junos_rpc:
rpc: "get-lldp-interface-neighbors"
output: 'xml'
args:
interface_device: "{{item}}"
loop:
"{{my_list}}"
register: net_topology
And this is actually the same as doing this also:
- name: building network topology
junos_rpc:
rpc: "get-lldp-interface-neighbors"
output: 'xml'
args:
interface_device: "{{item}}"
with_items:
"{{my_list}}"
register: net_topology
I must say that my initial mistake was the identation of the loop, because it was placed inside the junos_rpc and by doing this I could not get any result !!!

Ansible loops and conditionals

Ansible docs states that:
Combining when with with_items (see Loops), be aware that the when statement is processed separately for each item.
However when I try to skip one item in task, it doesn't work that way:
value_var: [1, 5]
- name: register variable
command: echo "4"
register: var
- name: conditional check
command: nevermind
when: var.stdout > item
By my understanding, that I would get changed on first item within conditional check task, and skipped on second item. But I get:
changed: [guest] => (item=5)
changed: [guest] => (item=1)
What am I doing wrong?
It has nothing to do with loops. You are comparing a string (the result of echo command) with an integer.
You should first cast the value:
when: var.stdout|int > item

Ansible custom nested looping

I have to process output from CloudFormation Outputs that is:
Ansible code that produces this output:
- debug:
var: stack.stack_outputs
Output:
ok: [localhost] => {
"stack.stack_outputs": {
"Roles": "webserver balancer dbserver",
"dbserver": "54.0.1.1 54.0.1.2",
"balancer": "54.0.2.3",
"webserver": "54.0.2.5 54.0.2.7 54.0.3.1"
}}
With that, I want to create 3 (dynamic number!) groups named accordingly filled with appropriate IPs.
Ansible code that I want to HELP WITH:
- name: fill roles with proper hosts
local_action: add_host hostname={{item}} groupname={{role}}
with_whatever: ?...?
In pseudo ansible python it would look like this:
for role in stack.stack_outputs.Roles.split(): # Python
for ip in stack.stack_outputs[role].split(): # Python
local_action: add_host hostname={{ip}} groupname={{role}} # Ansible
Note:
The way to do it for these three roles statically is obviously:
- name: fill role WEBSERVER
local_action: add_host hostname={{item}} groupname=webserver
with_items: stack.stack_outputs.webserver.split()
- name: fill role DBSERVER
local_action: add_host hostname={{item}} groupname=dbserver
with_items: stack.stack_outputs.dbserver.split()
- name: fill role BALANCER
local_action: add_host hostname={{item}} groupname=balancer
with_items: stack.stack_outputs.balancer.split()
I want to do it dynamically, is it even possible in Ansible?
Yes, I can use shell module to hack it putting everything in temporary file and then looping over that; but is there a better solution?
Thanks for any suggestions.
I understand you want the answer to fit a very specific framework. Within that, a custom lookup_plugin is your best bet. Otherwise it'll be an ugly sequence of set_fact and add_host. Sophisticated control structures are the antithesis of Ansible.
You don't explicitly rule out the following, so even if it's too out of the box for you, consider it because I've been reconciling cfn and ansible for a good long while:
Don't use stack outputs to fill your inventory. Use a dynamic inventory script for that (e.g. one that goes over stack outputs or tags set in the templates).
I'm aware of the implications like, you can't have this in a single playbook, but if that's paramount, use group_by.
Hope this helps.
For anyone that comes asking without reading the documentation first. Ansible has been updated to support nested indexing:
http://docs.ansible.com/ansible/playbooks_loops.html#nested-loops
Cheers!

Ansible - Power of 2 loop on a variable

I need:
vars_prompt:
- name: loopvar
prompt: Enter the loop variable
private: False
default: 16
- hosts: epcs
serial:1
gather_facts: no
tasks:
- name: Do some mathematics divide multiply
#insert logic here
register: my_content # save logic in register
with_sequence: count={{loopvar}} #I need a loop sequence here
when: inventory_hostname == "vm1"
ignore_errors: yes
A simple loop in c++ would be sth like:
int x=0;
for (int i=1; i<loopvar; i+=pow(2,x)) //pow is a math function with pow(2,x)= 2^x
{
cout<<hi;
x++;
}
One more thing, how can I store the results of each iteration in a register, so that I have it available, when the playbook runs serially the 2nd, 3rd or 4th time etc.
Update:
Jinja2 allows the following:
Raise the left operand to the power of the right operand.
{{ 2**3 }} would return 8.
Now keeping this new information in mind, can we do a power of 2 loop?
The type of loop you want is not going to be possible in Ansible. The tool is limited to looping over a list, dictionary, or a sequence but the counter cannot be modified. See the ansible docs on loops.
For something like this you may want to look into writing a custom module.
One more thing, how can I store the results of each iteration in a register, so that I have it available, when the playbook runs serially the 2nd, 3rd or 4th time etc.
See the section in the Ansible docs on how to use register in a loop.
If you register a var named my_content you can access the individual results in my_content.results.

Resources