Ansible: Invalid syntax in Conditional - ansible

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

Related

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

Ansible: How to check multiple servers for a text file value, to decide which servers to run the script on?

I am trying to ask Ansible to check if a server is passive or active based on the value of a specific file in each server, then Ansible will decide which server it runs the next script on.
For example with 2 servers:
Server1
cat /tmp/currentstate
PASSIVE
Server2
cat /tmp/currentstate
ACTIVE
In Ansible
Trigger next set of jobs on server where the output was ACTIVE.
Once the jobs complete, trigger next set of jobs on server where output was PASSIVE
What I have done so far to grab the state, and output the value to Ansible is
- hosts: "{{ hostname1 | mandatory }}"
gather_facts: no
tasks:
- name: Grab state of first server
shell: |
cat {{ ans_script_path }}currentstate.log
register: state_server1
- debug:
msg: "{{ state_server1.stdout }}"
- hosts: "{{ hostname2 | mandatory }}"
gather_facts: no
tasks:
- name: Grab state of second server
shell: |
cat {{ ans_script_path }}currentstate.log
register: state_server2
- debug:
msg: "{{ state_server2.stdout }}"
What I have done so far to trigger the script
- hosts: "{{ active_hostname | mandatory }}"
tasks:
- name: Run the shutdown on active server first
shell: sh {{ ans_script_path }}stopstart_terracotta_main.sh shutdown
register: run_result
- debug:
msg: "{{ run_result.stdout }}"
- hosts: "{{ passive_hostname | mandatory }}"
tasks:
- name: Run the shutdown on passive server first
shell: sh {{ ans_script_path }}stopstart_terracotta_main.sh shutdown
register: run_result
- debug:
msg: "{{ run_result.stdout }}"
but I don't know how to set the value of active_hostname & passive_hostname based on the value from the script above.
How can I set the Ansible variable of active_hostname & passive_hostname based on the output of the first section?
A better solution came to my mind is to include hosts in new groups according to their state.
This would be more optimal in case there are more than two hosts.
- hosts: all
gather_facts: no
vars:
ans_script_path: /tmp/
tasks:
- name: Grab state of server
shell: |
cat {{ ans_script_path }}currentstate.log
register: server_state
- add_host:
hostname: "{{ item }}"
# every host will be added to a new group according to its state
groups: "{{ 'active' if hostvars[item].server_state.stdout == 'ACTIVE' else 'passive' }}"
# Shorter, but the new groups will be in capital letters
# groups: "{{ hostvars[item].server_state.stdout }}"
loop: "{{ ansible_play_hosts }}"
changed_when: false
- name: show the groups the host(s) are in
debug:
msg: "{{ group_names }}"
- hosts: active
gather_facts: no
tasks:
- name: Run the shutdown on active server first
shell: hostname -f # changed that for debugging
register: run_result
- debug:
msg: "{{ run_result.stdout }}"
- hosts: passive
gather_facts: no
tasks:
- name: Run the shutdown on passive server first
shell: hostname -f
register: run_result
- debug:
msg: "{{ run_result.stdout }}"
test-001 is PASSIVE
test-002 is ACTIVE
PLAY [all] ***************************************************************
TASK [Grab state of server] **********************************************
ok: [test-002]
ok: [test-001]
TASK [add_host] **********************************************************
ok: [test-001] => (item=test-001)
ok: [test-001] => (item=test-002)
TASK [show the groups the host(s) are in] ********************************
ok: [test-001] => {
"msg": [
"passive"
]
}
ok: [test-002] => {
"msg": [
"active"
]
}
PLAY [active] *************************************************************
TASK [Run the shutdown on active server first] ****************************
changed: [test-002]
TASK [debug] **************************************************************
ok: [test-002] => {
"msg": "test-002"
}
PLAY [passive] ************************************************************
TASK [Run the shutdown on passive server first] ****************************
changed: [test-001]
TASK [debug] **************************************************************
ok: [test-001] => {
"msg": "test-001"
}
PLAY RECAP ****************************************************************
test-001 : ok=5 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test-002 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
For example, given two remote hosts
shell> ssh admin#test_11 cat /tmp/currentstate.log
ACTIVE
shell> ssh admin#test_13 cat /tmp/currentstate.log
PASSIVE
The playbook below reads the files and runs the commands on active and passive servers
shell> cat pb.yml
- hosts: "{{ host1 }},{{ host2 }}"
gather_facts: false
vars:
server_states: "{{ dict(ansible_play_hosts|
zip(ansible_play_hosts|
map('extract', hostvars, ['server_state', 'stdout'])|
list)) }}"
server_active: "{{ server_states|dict2items|
selectattr('value', 'eq', 'ACTIVE')|
map(attribute='key')|list }}"
server_pasive: "{{ server_states|dict2items|
selectattr('value', 'eq', 'PASSIVE')|
map(attribute='key')|list }}"
tasks:
- command: cat /tmp/currentstate.log
register: server_state
- debug:
var: server_state.stdout
- block:
- debug:
var: server_states
- debug:
var: server_active
- debug:
var: server_pasive
run_once: true
- command: echo 'Shutdown active server'
register: out_active
delegate_to: "{{ server_active.0 }}"
- command: echo 'Shutdown passive server'
register: out_pasive
delegate_to: "{{ server_pasive.0 }}"
- debug:
msg: |
{{ server_active.0 }}: [{{ out_active.stdout }}] {{ out_active.start }}
{{ server_pasive.0 }}: [{{ out_pasive.stdout }}] {{ out_pasive.start }}
run_once: true
shell> ansible-playbook pb.yml -e host1=test_11 -e host2=test_13
PLAY [test_11,test_13] ***********************************************************************
TASK [command] *******************************************************************************
changed: [test_13]
changed: [test_11]
TASK [debug] *********************************************************************************
ok: [test_11] =>
server_state.stdout: ACTIVE
ok: [test_13] =>
server_state.stdout: PASSIVE
TASK [debug] *********************************************************************************
ok: [test_11] =>
server_states:
test_11: ACTIVE
test_13: PASSIVE
TASK [debug] *********************************************************************************
ok: [test_11] =>
server_active:
- test_11
TASK [debug] *********************************************************************************
ok: [test_11] =>
server_pasive:
- test_13
TASK [command] *******************************************************************************
changed: [test_11]
changed: [test_13 -> test_11]
TASK [command] *******************************************************************************
changed: [test_11 -> test_13]
changed: [test_13]
TASK [debug] *********************************************************************************
ok: [test_11] =>
msg: |-
test_11: [Shutdown active server] 2022-10-27 11:16:00.766309
test_13: [Shutdown passive server] 2022-10-27 11:16:02.501907
PLAY RECAP ***********************************************************************************
test_11: ok=8 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test_13: ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
From the description of your use case I understand that you like to perform tasks on certain servers which a have service role installed (annot.: Terracotta Server) and based on a certain service state.
Therefore, I like to recommend an approach with Custom facts.
Depending on if you have control about where the currentstate.log is placed or how it is structured, you could use in example something like
cat /tmp/ansible/service/terracotta.fact
[currentstate]
ACTIVE = true
PASSIVE = false
or add dynamic facts by adding executable scripts to facts.d ...
Means, alternatively, you can add the current service state to your host facts by creating and running a script in facts.d, which would just read the content of /tmp/currentstate.log.
Then, a sample playbook like
---
- hosts: localhost
become: false
gather_facts: true
fact_path: /tmp/ansible/service
gather_subset:
- "!all"
- "!min"
- "local"
tasks:
- name: Show Gathered Facts
debug:
msg: "{{ ansible_facts }}"
when: ansible_local.terracotta.currentstate.active | bool
will result into an output of
TASK [Show Gathered Facts] ******
ok: [localhost] =>
msg:
ansible_local:
terracotta:
currentstate:
active: 'true'
passive: 'false'
gather_subset:
- '!all'
- '!min'
- local
module_setup: true
An other approach is to address How the inventory is build and Group the hosts
[terracotta:children]
terracotta_active
terracotta_passive
[terracotta_active]
terracotta1.example.com
[terracotta_passive]
terracotta2.example.com
You can then just easily and simple define where a playbook or task should run, just by Targeting hosts and groups
ansible-inventory -i hosts--graph
#all:
|--#terracotta:
| |--#terracotta_active:
| | |--terracotta1.example.com
| |--#terracotta_passive:
| | |--terracotta2.example.com
|--#ungrouped:
ansible-inventory -i hosts terracotta_active --graph
#terracotta_active:
|--terracotta1.example.com
or Conditionals based on ansible_facts, in example
when: 'terracotta_active' in group_names
... from my understanding, both would be minimal and simple solutions without re-implementing functionality which seems to be already there.

How can I write lines coming from two lists?

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.

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

How to change play_hosts from playbook

I have a file with two playbooks. Inventory is generated dynamically and there is no possibility to change it before starting playbook.
Run with command:
ansible-playbook -b adapter.yml --limit=host_group
adapter.yml
- name: Prepare stage
hosts: all
# The problem is that the inventory contains hosts in the format "x.x.x.x" ie physical address.
# I need to run a third-party role.
# But, it needs hosts in the format "instance-alias", that is, the name of the instance.
tasks:
# for this I create a new host group
- name: Add host in new format
add_host:
name: "{{ item.alias }}"
host: "{{ item.ansible_host }}"
groups: new_format_hosts
with_items: "{{ groups.all }}"
# I create a new play host group that matches the previous one in a new format
- name: Compose new play_hosts group
add_host:
name: "{{ item.alias }}"
groups: new_play_hosts
when: item.ansible_host in play_hosts
with_items: "{{ groups.all }}"
- name: Management stage
hosts: new_format_hosts
# in this playbook I want to change the composition
# of the target hosts and launch an external role
vars:
hostvars: "{{ hostvars }}"
play_hosts: "{{ groups.new_play_hosts }}" # THIS DONT WORK
- name: Run external role
import_role:
name: role_name
tasks_from: file_name
But I can’t change play_hosts so that the launched role uses only new hosts.
How to fix it?
This works for me:
- name: Prepare stage
hosts: localhost
tasks:
- name: Show hostavers
debug:
msg: "{{ hostvars[item]['ansible_host'] }}"
with_items: "{{ groups.all }}"
# for this I create a new host group
- name: Add host in new format
add_host:
name: "{{ hostvars[item].alias }}"
ansible_host: "{{ hostvars[item].ansible_host }}"
groups: new_format_hosts
with_items: "{{ groups.all }}"
- name: Management stage
hosts: new_format_hosts
tasks:
- name: Ping New Format Hosts
ping:
- name: Show ansible_host for each host
debug:
var: ansible_host
- name: Show playhosts
debug:
var: play_hosts
delegate_to: localhost
run_once: yes
This assumes, of course, that alias and ansible_host are set for all the hosts.
The hosts were:
AnsibleTower ansible_host=192.168.124.8 alias=fred
192.168.124.111 ansible_host=192.168.124.111 alias=barney
jaxsatB ansible_host=192.168.124.111 alias=wilma
The relevant output of the playbook was:
PLAY [Management stage] *****************************************************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************************************************************
Friday 29 May 2020 18:09:26 -0400 (0:00:00.057) 0:00:01.332 ************
ok: [fred]
ok: [barney]
ok: [wilma]
TASK [New Format Hosts] *****************************************************************************************************************************************************************
Friday 29 May 2020 18:09:30 -0400 (0:00:04.308) 0:00:05.641 ************
ok: [barney]
ok: [wilma]
ok: [fred]
TASK [Show ansible_host] ****************************************************************************************************************************************************************
Friday 29 May 2020 18:09:30 -0400 (0:00:00.450) 0:00:06.091 ************
ok: [fred] => {
"ansible_host": "192.168.124.8"
}
ok: [barney] => {
"ansible_host": "192.168.124.111"
}
ok: [wilma] => {
"ansible_host": "192.168.124.111"
}
TASK [Show playhosts] *******************************************************************************************************************************************************************
Friday 29 May 2020 18:09:30 -0400 (0:00:00.109) 0:00:06.200 ************
ok: [fred -> localhost] => {
"play_hosts": [
"fred",
"barney",
"wilma"
]
}
PLAY RECAP ******************************************************************************************************************************************************************************
barney : ok=3 changed=0 unreachable=0 failed=0
fred : ok=4 changed=0 unreachable=0 failed=0
localhost : ok=3 changed=1 unreachable=0 failed=0
wilma : ok=3 changed=0 unreachable=0 failed=0
Friday 29 May 2020 18:09:30 -0400 (0:00:00.030) 0:00:06.231 ************
===============================================================================
You do not need to set the play_hosts variable. That is set by the line hosts: new_format_hosts in the second play.

Resources