How can I write lines coming from two lists? - data-structures

How can I write lines coming from two lists with Ansible?
Var file: bottle.yml
Bottle:
- wine:
- 1951
- 1960
- country_to_export:
- belgium-1
- belgium-2
main.yml file:
debug:
msg: "I send the bottle {{ item.0 }} to country {{ item.1 }}"
with_items:
- "{{ Bottle.wine }}"
- "{{ Bottle.country_to_export}}"
Result:
"I send the bottle [ u1951, u1960 ] to country [ ubelgium-1,ubelgium-2 ]"
Desired result:
I send the bottle 1951 to country Belgium-1
I send the bottle 1960 to country Belgium-2

You are just using the wrong type of loop, you should use the loop with_together instead of the loop with_items.
Given the playbook:
- hosts: all
gather_facts: yes
tasks:
- debug:
msg: "I send the bottle {{ item.0 }} to country {{ item.1 }}"
with_together:
- "{{ Bottle.wine }}"
- "{{ Bottle.country_to_export}}"
vars:
Bottle:
wine:
- 1951
- 1960
country_to_export:
- belgium-1
- belgium-2
This yields the recap:
PLAY [all] **********************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************************
ok: [localhost] => (item=[1951, 'belgium-1']) => {
"msg": "I send the bottle 1951 to country belgium-1"
}
ok: [localhost] => (item=[1960, 'belgium-2']) => {
"msg": "I send the bottle 1960 to country belgium-2"
}
PLAY RECAP **********************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Also mind that in your given bottle.yml, your dictionary is wrong, compared to what you says it is giving you.
It should rather be:
Bottle:
wine:
- 1951
- 1960
country_to_export:
- belgium-1
- belgium-2
A better syntax, if you want to be compatible with upcoming versions of Ansible is to drop the with_* structure and start to use their loop replacement.
So the above playbook will end up being:
- hosts: all
gather_facts: yes
tasks:
- debug:
msg: "I send the bottle {{ item.0 }} to country {{ item.1 }}"
loop: "{{ Bottle.wine | zip(Bottle.country_to_export) | list }}"
vars:
Bottle:
wine:
- 1951
- 1960
country_to_export:
- belgium-1
- belgium-2
Yielding the same recap.

Related

Ansible: Invalid syntax in Conditional

I'm trying to create a playbook to verifie a space on the datastore before create a VM and I need the creation to fail if datastore usage > 80 %.
My playbook is like the below and I am stuck on the conditional check invalids syntax.
- name: Get VM datastore info
vmware_datastore_facts:
hostname: "{{ vcenter_server }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_pass }}"
datacenter: "{{data_center}}"
validate_certs: False
name: "{{ vm_datastore }}"
register: datastore
delegate_to: localhost
- set_fact:
datastore_capacity: "{{ datastore.datastores[0].capacity }}"
datastore_freeSpace: "{{ datastore.datastores[0].freeSpace}}"
- fail:
msg: "No more space on VMware datastore"
when:
- ' ("{{datastore_freeSpace}}" // "{{datastore_capacity}}") * 100) > 80'
Can you please help?
Q: "Fail if datastore usage > 80%"
A: Try this:
when: 100 - (datastore_freeSpace|int / datastore_capacity|int * 100) > 80
, or this (the same but decimal, instead of the percentage)
when: 1.0 - datastore_freeSpace|int / datastore_capacity|int > 0.8
Example of a complete playbook for testing
shell> cat pb.yml
- hosts: localhost
vars:
datastore:
datastores:
- capacity: 100
freeSpace: 10
datastore_capacity: "{{ datastore.datastores[0].capacity }}"
datastore_freeSpace: "{{ datastore.datastores[0].freeSpace}}"
tasks:
- debug:
msg: |
datastore_capacity: {{ datastore_capacity }}
datastore_freeSpace: {{ datastore_freeSpace }}
datastore_capacity|type_debug: {{ datastore_capacity|type_debug }}
datastore_freeSpace|type_debug: {{ datastore_freeSpace|type_debug }}
used[%]: {{ 100 - (datastore_freeSpace|int / datastore_capacity|int * 100) }}
- debug:
msg: "No more space on VMware datastore"
when: 100 - (datastore_freeSpace|int / datastore_capacity|int * 100) > 80
gives
shell> ansible-playbook pb.yml
PLAY [localhost] *****************************************************************************
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: |-
datastore_capacity: 100
datastore_freeSpace: 10
datastore_capacity|type_debug: str
datastore_freeSpace|type_debug: str
used[%]: 90.0
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: No more space on VMware datastore
PLAY RECAP ***********************************************************************************
localhost: ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

compare value with list of int in ansible

Want to compare values with list for int and display msg values greater than 15
tasks:
- name: Create a List variable and print it
set_fact:
Continents: ["10","20"]
- name: set fatc
set_fact:
int_list: "{{ Continents|map('int')|list }}"
- debug:
msg: "{{greater than 15}}"
when: "{{int_list}}" > 15
Getting error as below:
The offending line appears to be:
msg: "{{ list }}"
when: "{{int_list}}" > 15
^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
Expected Output:
greater than 15
If your goal is to show only those integers in the list that are greater than 15, you can use the select filter:
- hosts: localhost
gather_facts: false
tasks:
- name: Create a list of integers
set_fact:
int_list: [10, 20]
- name: Find integers greater than 15
debug:
msg: "{{ item }}"
loop: "{{ int_list | select('>', 15) }}"
The output of this playbook is:
PLAY [localhost] ***************************************************************
TASK [Create a list of integers] ***********************************************
ok: [localhost]
TASK [Find integers greater than 15] *******************************************
ok: [localhost] => (item=20) => {
"msg": 20
}
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You can achieve the same with two tasks. Change your list to integers. Make the debug task to loop through the Continents variable.
- hosts: localhost
tasks:
- name: Create a variable with a list of integers
set_fact:
Continents: [10, 20]
- debug:
msg: "greater than 15"
loop: "{{ Continents }}"
when: item > 15 ##item becomes each value of the Continents variable. So, 10, 20 and so on.
Gives:
skipping: [localhost] => (item=10)
ok: [localhost] => (item=20) => {
"msg": "greater than 15"
}

How to calculate ansible_uptime_seconds and output this in os.csv

I am trying to create a csv file that can be used to review certain systems details. One of these items is the system uptime, which is reflected in unix seconds. But in the os.csv output file I would like to see it as days, HH:MM:SS.
Below my yaml script:
---
- name: playbook query system and output to file
hosts: OEL7_systems
vars:
output_file: os.csv
tasks:
- block:
# For permisison setup.
- name: get current user
command: whoami
register: whoami
run_once: yes
- name: clean_file
copy:
dest: "{{ output_file }}"
content: 'hostname,distribution,version,release,uptime'
owner: "{{ whoami.stdout }}"
run_once: yes
- name: fill os information
lineinfile:
path: "{{ output_file }}"
line: "{{ ansible_hostname }},\
{{ ansible_distribution }},\
{{ ansible_distribution_version }},\
{{ ansible_distribution_release }},\
{{ ansible_uptime_seconds }}"
# Tries to prevent concurrent writes.
throttle: 1
delegate_to: localhost
Any help is appreciated.
tried several conversions but can't get it to work.
There is actually a (somewhat hard to find) example in the official documentation on complex data manipulations doing exactly what you are looking for (check at the bottom of the page).
Here is a full example playbook to run it on localhost
---
- hosts: localhost
tasks:
- name: Show the uptime in days/hours/minutes/seconds
ansible.builtin.debug:
msg: Uptime {{ now().replace(microsecond=0) - now().fromtimestamp(now(fmt='%s') | int - ansible_uptime_seconds) }}
which gives on my machine:
PLAY [localhost] ************************************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************************************
ok: [localhost]
TASK [Show the uptime in days/hours/minutes/seconds] ************************************************************************************************************
ok: [localhost] => {
"msg": "Uptime 1 day, 3:56:34"
}
PLAY RECAP ******************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

How to delegate facts to localhost from a play targeting remote hosts

ansible version: 2.9.16 running on RHEL 7.9 python ver = 2.7.5 targeting windows 2016 servers. ( should behave the same for linux target servers too)
EDIT: Switched to using host specific variables in inventory to avoid confusion that Iam just trying to print hostnames of a group. Even here its a gross simplification. Pretend that var1 is obtained dynamically for each server instead of being declared in the inventory file.
My playbook has two plays. One targets 3 remote servers ( Note: serial: 0 i.e Concurrently ) and another just the localhost. In play1 I am trying to delegate facts obtained from each of these hosts to the localhost using delegate_facts and delegate_to. The intent is to have these facts delegated to a single host ( localhost ) so I can use it later in a play2 (using hostvars) that targets the localhost. But strangely thats not working. It only has information from the last host from Play1.
Any help will be greatly appreciated.
my inventory file inventory/test.ini looks like this:
[my_servers]
svr1 var1='abc'
svr2 var1='xyz'
svr3 var1='pqr'
My Code:
## Play1
- name: Main play that runs against multiple remote servers and builds a list.
hosts: 'my_servers' # my inventory group that contains 3 servers svr1,svr2,svr3
any_errors_fatal: false
ignore_unreachable: true
gather_facts: true
serial: 0
tasks:
- name: initialize my_server_list as a list and delegate to localhost
set_fact:
my_server_list: []
delegate_facts: yes
delegate_to: localhost
- command: /root/complex_script.sh
register: result
- set_fact:
my_server_list: "{{ my_server_list + hostvars[inventory_hostname]['result.stdout'] }}"
# run_once: true ## Commented as I need to query the hostvars for each host where this executes.
delegate_to: localhost
delegate_facts: true
- name: "Print list - 1"
debug:
msg:
- "{{ hostvars['localhost']['my_server_list'] | default(['NotFound']) | to_nice_yaml }}"
# run_once: true
- name: "Print list - 2"
debug:
msg:
- "{{ my_server_list | default(['NA']) }}"
## Play2
- name: Print my_server_list which was built in Play1
hosts: localhost
gather_facts: true
serial: 0
tasks:
- name: "Print my_server_list without hostvars "
debug:
msg:
- "{{ my_server_list | to_nice_json }}"
# delegate_to: localhost
- name: "Print my_server_list using hostvars"
debug:
msg:
- "{{ hostvars['localhost']['my_server_list'] | to_nice_yaml }}"
# delegate_to: localhost
###Output###
$ ansible-playbook -i inventory/test.ini delegate_facts.yml
PLAY [Main playbook that runs against multiple remote servers and builds a list.] ***********************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************************************************************************
ok: [svr3]
ok: [svr1]
ok: [svr2]
TASK [initialize] ***************************************************************************************************************************************************************************
ok: [svr1]
ok: [svr2]
ok: [svr3]
TASK [Build a list of servers] **************************************************************************************************************************************************************
ok: [svr1]
ok: [svr2]
ok: [svr3]
TASK [Print list - 1] ***********************************************************************************************************************************************************************
ok: [svr1] =>
msg:
- |-
- pqr
ok: [svr2] =>
msg:
- |-
- pqr
ok: [svr3] =>
msg:
- |-
- pqr
TASK [Print list - 2] ***********************************************************************************************************************************************************************
ok: [svr1] =>
msg:
- - NA
ok: [svr2] =>
msg:
- - NA
ok: [svr3] =>
msg:
- - NA
PLAY [Print my_server_list] *****************************************************************************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************************************************************************
ok: [localhost]
TASK [Print my_server_list without hostvars] ************************************************************************************************************************************************
ok: [localhost] =>
msg:
- |-
[
"pqr"
]
TASK [Print my_server_list using hostvars] **************************************************************************************************************************************************
ok: [localhost] =>
msg:
- |-
- pqr
PLAY RECAP **********************************************************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
svr1 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
svr2 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
svr3 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Playbook run took 0 days, 0 hours, 0 minutes, 13 seconds
###Expected Output###
I was expecting the last two debug statements in Play2 to contain the values of var1 for all the servers something like this:
TASK [Print my_server_list using hostvars] **************************************************************************************************************************************************
ok: [localhost] =>
msg:
- |-
- abc
- xyz
- pqr
Use Special Variables, e.g.
- hosts: all
gather_facts: false
tasks:
- set_fact:
my_server_list: "{{ ansible_play_hosts_all }}"
run_once: true
delegate_to: localhost
delegate_facts: true
- hosts: localhost
gather_facts: false
tasks:
- debug:
var: my_server_list
gives
ok: [localhost] =>
my_server_list:
- svr1
- svr2
- svr3
There are many other ways how to create the list, e.g.
- hosts: all
gather_facts: false
tasks:
- debug:
msg: "{{ groups.my_servers }}"
run_once: true
- hosts: all
gather_facts: false
tasks:
- debug:
msg: "{{ hostvars|json_query('*.inventory_hostname') }}"
run_once: true
Q: "Fill the list with outputs gathered by running complex commands."
A: Last example above shows how to create a list from hostvars. Register the result from the complex command, e.g.
shell> ssh admin#srv1 cat /root/complex_script.sh
#!/bin/sh
ifconfig wlan0 | grep inet | cut -w -f3
The playbook
- hosts: all
gather_facts: false
tasks:
- command: /root/complex_script.sh
register: result
- set_fact:
my_server_list: "{{ hostvars|json_query('*.result.stdout') }}"
run_once: true
delegate_to: localhost
delegate_facts: true
- hosts: localhost
gather_facts: false
tasks:
- debug:
var: my_server_list
gives
my_server_list:
- 10.1.0.61
- 10.1.0.62
- 10.1.0.63
Q: "Why the logic of delegating facts to localhost and keep appending them to that list does not work?"
A: The code below (simplified) can't work because the right-hand-side msl value still comes from the hostvars of the inventory_host despite the fact delegate_facts: true. This merely puts the created variable msl into the localhost's hostvars
- hosts: my_servers
tasks:
- set_fact:
msl: "{{ msl|default([]) + [inventory_hostname] }}"
delegate_to: localhost
delegate_facts: true
Quoting from Delegating facts
To assign gathered facts to the delegated host instead of the current host, set delegate_facts to true
As a result of such code, the variable msl will keep the last assigned value only.

How to split a string into a list with Ansible/Jinja2?

I have the following variables:
domain_names:
- app1.example.com
- app2.example.com
customers:
- name: customer1
- name: customer2
And I'm trying to generate the following list of domain names:
- customer1.app1.example.com
- customer2.app1.example.com
- customer1.app2.example.com
- customer2.app2.example.com
By using the following Ansible/Jinja2 code:
- name: check which certificates exist
stat:
path: '/etc/nginx/{{item}}.crt'
register: cert_file
loop: '{% for d in domain_names %}{{ d }} {% for customer in customers %}{{ customer.name }}.{{ d }} {% endfor %}{% endfor %}'
However, I'm getting the following error:
failed | msg: Invalid data passed to 'loop', it requires a list, got this instead: customer1.app1.example.com customer2.app1.example.com customer1.app2.example.com customer2.app2.example.com. Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup.
How can I fix this?
Simply use the right tools :). In this case your friends are:
the map filter to extract the name attribute as a list from your customers variable
the product filter to mix-up your two lists
e.g. test.yml playbook:
---
- name: product and map filters demo
hosts: localhost
gather_facts: false
vars:
domain_names:
- app1.example.com
- app2.example.com
customers:
- name: customer1
- name: customer2
tasks:
- name: Demonstrate product and map filters use
debug:
msg: "{{ item.0 }}.{{ item.1 }}"
loop: "{{ customers | map(attribute='name') | product(domain_names) | list }}"
Which gives:
$ ansible-playbook test.yml
PLAY [product and map filters demo] *******************************************************************************************************************************************************************************
TASK [Demonstrate product and map filters use] ********************************************************************************************************************************************************************
ok: [localhost] => (item=['customer1', 'app1.example.com']) => {
"msg": "customer1.app1.example.com"
}
ok: [localhost] => (item=['customer1', 'app2.example.com']) => {
"msg": "customer1.app2.example.com"
}
ok: [localhost] => (item=['customer2', 'app1.example.com']) => {
"msg": "customer2.app1.example.com"
}
ok: [localhost] => (item=['customer2', 'app2.example.com']) => {
"msg": "customer2.app2.example.com"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Applied to your task, this gives:
- name: Check which certificates exist
stat:
path: '/etc/nginx/{{ item.0 }}.{{ item.1 }}.crt'
register: cert_file
loop: "{{ customers | map(attribute='name') | product(domain_names) | list }}"
If you really want to re-use that list you can build it into an other var. One easy way to understand is to build it in a set_fact task, e.g.
- name: Create my application names list
vars:
current_name: "{{ item.0 }}.{{ item.1 }}"
set_fact:
application_names_list: "{{ application_names_list | default([]) + [current_name] }}"
loop: "{{ customers | map(attribute='name') | product(domain_names) | list }}"
- name: Check which certificates exist
stat:
path: '/etc/nginx/{{ item }}.crt'
register: cert_file
loop: "{{ application_names_list }}"
But you can also declare it "statically" in your vars with a little more complex expression (see the map filter possibilities and the join filter)
---
- name: product and map filters demo
hosts: localhost
gather_facts: false
vars:
domain_names:
- app1.example.com
- app2.example.com
customers:
- name: customer1
- name: customer2
application_names_list: "{{ customers | map(attribute='name') | product(domain_names) | map('join', '.') | list }}"
tasks:
- name: Demonstrate product and map filters use
debug:
var: application_names_list
=>
PLAY [product and map filters demo] ****************************************************************************************************************************************************************************************************
TASK [Demonstrate product and map filters use] *****************************************************************************************************************************************************************************************
ok: [localhost] => {
"all_domains": [
"customer1.app1.example.com",
"customer1.app2.example.com",
"customer2.app1.example.com",
"customer2.app2.example.com"
]
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Resources