Unable to get the list value returned by the json query from the ansible facts - ansible

I am trying to get the size_available value for the /home filesystem from the ansible facts.
I am using the following code after setting gather_facts: True
{{ansible_facts['mounts']|json_query('[?mount==`/home`].size_available')}}
This way I get something like this [34545646] with msg: from the debug module.
I need to compare this value to a static one and continue or not the playbook but when I try:
{{ansible_facts['mounts']|json_query('[?mount==`/home`].size_available')[0]}}
I get:
"msg": "template error while templating string: expected token 'end of print statement', got '['. String: > {{ansible_facts['mounts']|json_query('[?mount==`/home`].size_available')[0]}}
Even if the type_debug shows me the result should be indeed a list that should be accessible by the [0] extension.

You need to parenthesize the initial expression before attempting to index the result, like this:
{{
(
ansible_facts['mounts'] |
json_query('[?mount==`/home`].size_available')
)[0]
}}
E.g., if I run this playbook on my system:
- hosts: localhost
gather_facts: true
tasks:
- debug:
msg: >-
{{
(
ansible_facts['mounts'] |
json_query('[?mount==`/home`].size_available')
)[0]
}}
I get this output for the debug task:
TASK [debug] ********************************************************************************************
ok: [localhost] => {
"msg": "402658955264"
}

Q: "Get the size_available value for a mount point from the ansible facts."
A: Declare the dictionary below, for example in group_vars
shell> cat group_vars/all/mount_vars.yml
mount_size_available: "{{ ansible_mounts|
items2dict(key_name='mount',
value_name='size_available') }}"
gives, for example
mount_size_available:
/: 8480206848
/boot/efi: 30278656
/export: 12902629376
Then, you can easily reference an available size at a mount point, for example
mount_size_available['/export']: '12902629376'
Example of a project for testing
shell> tree .
.
├── ansible.cfg
├── group_vars
│   └── all
│   └── mount_vars.yml
├── hosts
└── pb.yml
2 directories, 4 files
shell> cat ansible.cfg
[defaults]
gathering = explicit
inventory = $PWD/hosts
remote_tmp = ~/.ansible/tmp
retry_files_enabled = false
stdout_callback = yaml
shell> cat group_vars/all/mount_vars.yml
mount_size_available: "{{ ansible_mounts|
items2dict(key_name='mount',
value_name='size_available') }}"
size_1G: "{{ 1 * 1024 * 1024 * 1024 }}"
size_10G: "{{ 10 * 1024 * 1024 * 1024 }}"
size_100G: "{{ 100 * 1024 * 1024 * 1024 }}"
shell> cat hosts
localhost
shell> cat pb.yml
- hosts: localhost
tasks:
- setup:
gather_subset: mounts
- debug:
var: mount_size_available
- debug:
var: mount_size_available['/export']
- debug:
msg: "Free space at /export is greater than 10G."
when: mount_size_available['/export'] > size_10G|int
- debug:
msg: "Free space at /export is less than 100G."
when: mount_size_available['/export'] < size_100G|int
gives
shell> ansible-playbook pb.yml
PLAY [localhost] *****************************************************************************
TASK [setup] *********************************************************************************
ok: [localhost]
TASK [debug] *********************************************************************************
ok: [localhost] =>
mount_size_available:
/: 8479465472
/boot/efi: 30278656
/export: 12901998592
TASK [debug] *********************************************************************************
ok: [localhost] =>
mount_size_available['/export']: '12901998592'
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: Free space at /export is greater than 10G.
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: Free space at /export is less than 100G.
PLAY RECAP ***********************************************************************************
localhost: ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Related

Can I use ansible-playbook "--extra-vars" to execute roles conditionally

I have an Ansible playbook I am working on and I am trying to make the execution a little more dynamic. I have two roles that don't always need to be run in conjunction, sometimes only one of them needs to be run. I have been digging into the Ansible docs, and I am wondering if I can pass --extra-vars parameters to only run a specific role.
Currently, my playbook looks like this:
---
- hosts: default
become: true
roles:
- role: upgrades
when: {{ upgrades_role }}
- role: custom-packages
when: {{ custom_packages_role }}
So, the goal is to be able to run:
ansible-playbook playbook.yml -e "upgrades_role=upgrades"
And this would only run the upgrades role and skip the custom_packages role.
Similarly, if I want to run both roles on the same hosts/system:
ansible-playbook playbook.yml -e "upgrades_role=upgrades custom_packages_role=custom-packages"
This would run both roles.
Based on my understating of Ansible syntax and the --extra-vars, -e parameter, this seems like it should work. I just want to be sure I am doing this the proper way and avoiding anti-patterns.
Ansible Version: 2.14
Let me provide you with a framework to automate the use case that you described:
Pass --extra-vars parameters to only run a specific role
Create the project
shell> ls -1
ansible.cfg
hosts
playbook.yml.j2
roles
setup.yml
shell> cat ansible.cfg
[defaults]
gathering = explicit
collections_path = $HOME/.local/lib/python3.9/site-packages/
inventory = $PWD/hosts
roles_path = $PWD/roles
retry_files_enabled = false
stdout_callback = yaml
shell> cat hosts
localhost
The playbook setup.yml
Gets the list of the roles. Fit the variable my_roles_dir to your needs.
Creates file my_roles_order.yml with the list my_roles_order. The purpose of this file is to create the order of the roles.
Creates file my_roles_enable.yml with the dictionary my_roles_enable. The purpose of this file is to create defaults.
Creates file playbook.yml from the template. Fit the template to your needs.
shell> cat setup.yml
- hosts: localhost
vars:
my_roles_dir: "{{ lookup('config', 'DEFAULT_ROLES_PATH') }}"
tasks:
- set_fact:
my_roles: "{{ my_roles|default([]) +
lookup('pipe', 'ls -1 ' ~ item).splitlines() }}"
loop: "{{ my_roles_dir }}"
- copy:
dest: "{{ playbook_dir }}/my_roles_order.yml"
content: |
my_roles_order:
{{ my_roles|to_nice_yaml|indent(2, true) }}
force: "{{ my_roles_order_force|default(false) }}"
- include_vars: my_roles_order.yml
- copy:
dest: "{{ playbook_dir }}/my_roles_enable.yml"
content: |
my_roles_enable:
{% for role in my_roles %}
{{ role }}: false
{% endfor %}
force: "{{ my_roles_enable_force|default(false) }}"
- include_vars: my_roles_enable.yml
- template:
src: playbook.yml.j2
dest: "{{ playbook_dir }}/playbook.yml"
shell> cat playbook.yml.j2
- hosts: localhost
become: true
vars_files:
- my_roles_enable.yml
roles:
{% for role in my_roles_order %}
- role: {{ role }}
when: {{ role }}_role|default(my_roles_enable.{{ role }})|bool
{% endfor %}
Test the trivial roles
shell> tree roles/
roles/
├── current_packages
│   └── tasks
│   └── main.yml
├── custom_packages
│   └── tasks
│   └── main.yml
├── stable_packages
│   └── tasks
│   └── main.yml
└── upgrades
└── tasks
└── main.yml
8 directories, 4 files
shell> cat roles/*/tasks/main.yml
- debug:
msg: Role current_packages running ...
- debug:
msg: Role custom_packages running ...
- debug:
msg: Role stable_packages running ...
- debug:
msg: Role upgrades running ...
Run the playbook setup.yml
shell> ansible-playbook setup.yml
PLAY [localhost] ****************************************************************************************
TASK [set_fact] *****************************************************************************************
ok: [localhost] => (item=/scratch/tmp7/test-204/roles)
TASK [copy] *********************************************************************************************
changed: [localhost]
TASK [include_vars] *************************************************************************************
ok: [localhost]
TASK [copy] *********************************************************************************************
changed: [localhost]
TASK [include_vars] *************************************************************************************
ok: [localhost]
TASK [template] *****************************************************************************************
changed: [localhost]
PLAY RECAP **********************************************************************************************
localhost: ok=6 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
This creates playbook.yml
shell> cat playbook.yml
- hosts: localhost
become: true
vars_files:
- my_roles_enable.yml
roles:
- role: current_packages
when: current_packages_role|default(my_roles_enable.current_packages)|bool
- role: custom_packages
when: custom_packages_role|default(my_roles_enable.custom_packages)|bool
- role: stable_packages
when: stable_packages_role|default(my_roles_enable.stable_packages)|bool
- role: upgrades
when: upgrades_role|default(my_roles_enable.upgrades)|bool
and the files
shell> cat my_roles_order.yml
my_roles_order:
- current_packages
- custom_packages
- stable_packages
- upgrades
shell> cat my_roles_enable.yml
my_roles_enable:
current_packages: false
custom_packages: false
stable_packages: false
upgrades: false
By default, the playbook runs nothing. Here you can "pass --extra-vars parameters to only run a specific role". For example, enable the role upgrades
shell> ansible-playbook playbook.yml -e upgrades_role=true
PLAY [localhost] *****************************************************************************
TASK [current_packages : debug] **************************************************************
skipping: [localhost]
TASK [custom_packages : debug] ***************************************************************
skipping: [localhost]
TASK [stable_packages : debug] ***************************************************************
skipping: [localhost]
TASK [upgrades : debug] **********************************************************************
ok: [localhost] =>
msg: Role upgrades running ...
PLAY RECAP ***********************************************************************************
localhost: ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
If you want to change the order and/or the enablement defaults of the roles edit the files my_roles_order.yml and my_roles_enable.yml. For example, put the role upgrades in the first place and enable it by default
shell> cat my_roles_order.yml
my_roles_order:
- upgrades
- current_packages
- custom_packages
- stable_packages
shell> cat my_roles_enable.yml
my_roles_enable:
current_packages: false
custom_packages: false
stable_packages: false
upgrades: true
Update the playbook
shell> ansible-playbook setup.yml
Test it
shell> ansible-playbook playbook.yml
PLAY [localhost] *****************************************************************************
TASK [upgrades : debug] **********************************************************************
ok: [localhost] =>
msg: Role upgrades running ...
TASK [current_packages : debug] **************************************************************
skipping: [localhost]
TASK [custom_packages : debug] ***************************************************************
skipping: [localhost]
TASK [stable_packages : debug] ***************************************************************
skipping: [localhost]
PLAY RECAP ***********************************************************************************
localhost: ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
It's practical to create a file if you want to enable/disable multiple roles on the command line. For example,
shell> cat myroles.yml
upgrades_role: false
stable_packages_role: true
custom_packages_role: true
Use it in the command line
shell> ansible-playbook playbook.yml -e #myroles.yml
PLAY [localhost] *****************************************************************************
TASK [upgrades : debug] **********************************************************************
skipping: [localhost]
TASK [current_packages : debug] **************************************************************
skipping: [localhost]
TASK [custom_packages : debug] ***************************************************************
ok: [localhost] =>
msg: Role custom_packages running ...
TASK [stable_packages : debug] ***************************************************************
ok: [localhost] =>
msg: Role stable_packages running ...
PLAY RECAP ***********************************************************************************
localhost: ok=2 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
Don't display skipped hosts
shell> ANSIBLE_DISPLAY_SKIPPED_HOSTS=false ansible-playbook playbook.yml -e #myroles.yml
PLAY [localhost] *****************************************************************************
TASK [custom_packages : debug] ***************************************************************
ok: [localhost] =>
msg: Role custom_packages running ...
TASK [stable_packages : debug] ***************************************************************
ok: [localhost] =>
msg: Role stable_packages running ...
PLAY RECAP ***********************************************************************************
localhost: ok=2 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
The most common way will be to use tags; updating the example in the question:
---
- hosts: default
become: true
roles:
- role: upgrades
tags: upgrades
- role: custom-packages
tags: packages
...
So, the goal is to be able to run:
# Execute all the roles
ansible-playbook playbook.yml
# Execute only the upgrade
ansible-playbook playbook.yml -t upgrades
# Execute only the setup of custom packages
ansible-playbook playbook.yml -t packages
Here is the documentation for tags.
Using variables and when is possible, but you'll need to ensure to cast it to bool.

Display Ansible playbook with lookups interpolated

I have an Ansible playbook that looks, in part, like this:
...
environment:
F2B_DB_PURGE_AGE: "{{ lookup('env','F2B_DB_PURGE_AGE') }}"
F2B_LOG_LEVEL: "{{ lookup('env','F2B_LOG_LEVEL') }}"
SSMTP_HOST: "{{ lookup('env','SSMTP_HOST') }}"
SSMTP_PORT: "{{ lookup('env','SSMTP_PORT') }}"
SSMTP_TLS: "{{ lookup('env','SSMTP_TLS') }}"
...
Is there any way to run ansible-playbook so that it will show the results of the YAML file after replacing the lookups with their values? That is, I would like to be able to run something like ansible-playbook file.yaml --dry-run and see on standard output (assuming the environment variables were set appropriately):
...
environment:
F2B_DB_PURGE_AGE: "20"
F2B_LOG_LEVEL: "debug"
SSMTP_HOST: "smtp.example.com"
SSMTP_PORT: "487"
SSMTP_TLS: "true"
...
Set the environment for testing
shell> cat env.sh
#!/usr/bin/bash
export F2B_DB_PURGE_AGE="20"
export F2B_LOG_LEVEL="debug"
export SSMTP_HOST="smtp.example.com"
export SSMTP_PORT="487"
export SSMTP_TLS="true"
shell> source env.sh
Given the inventory
shell> cat hosts
localhost ansible_connection=local
Q: "Run something like ansible-playbook file.yaml --dry-run and see environment"
A: The below playbook does the job
shell> cat file.yml
- hosts: all
vars:
my_environment:
F2B_DB_PURGE_AGE: "{{ lookup('env','F2B_DB_PURGE_AGE') }}"
F2B_LOG_LEVEL: "{{ lookup('env','F2B_LOG_LEVEL') }}"
SSMTP_HOST: "{{ lookup('env','SSMTP_HOST') }}"
SSMTP_PORT: "{{ lookup('env','SSMTP_PORT') }}"
SSMTP_TLS: "{{ lookup('env','SSMTP_TLS') }}"
tasks:
- block:
- debug:
msg: |
my_environment:
{{ my_environment|to_nice_yaml|indent(2) }}
- meta: end_play
when: dry_run|d(false)|bool
- debug:
msg: Continue ...
Set dry_run=true
shell> ansible-playbook file.yml -e dry_run=true
PLAY [all] ***********************************************************************************
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: |-
my_environment:
F2B_DB_PURGE_AGE: '20'
F2B_LOG_LEVEL: debug
SSMTP_HOST: smtp.example.com
SSMTP_PORT: '487'
SSMTP_TLS: 'true'
TASK [meta] **********************************************************************************
PLAY RECAP ***********************************************************************************
localhost: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
By default, the playbook will execute tasks
shell> ansible-playbook file.yml
PLAY [all] ***********************************************************************************
TASK [debug] *********************************************************************************
skipping: [localhost]
TASK [meta] **********************************************************************************
skipping: [localhost]
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: Continue ...
PLAY RECAP ***********************************************************************************
localhost: ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
Optionally, let the playbook gather facts and use the dictionary ansible_env. Use the filer ansible.utils.keep_keys to select your variables
- hosts: all
gather_facts: true
vars:
my_environment_vars:
- F2B_DB_PURGE_AGE
- F2B_LOG_LEVEL
- SSMTP_HOST
- SSMTP_PORT
- SSMTP_TLS
my_environment: "{{ ansible_env|
ansible.utils.keep_keys(target=my_environment_vars) }}"
tasks:
- block:
- debug:
msg: |
my_environment:
{{ my_environment|to_nice_yaml|indent(2) }}
- meta: end_play
when: dry_run|d(false)|bool
- debug:
msg: Continue ...

Ansible playbook doesnt get values from inventory group_vars

I have got a simple playbook:
- hosts: all
serial: 1
order: "{{ run_order }}"
gather_facts: false
tasks:
- ping:
register: resultt
My inventory tree:
.
├── group_vars
│   └── g1
│   └── values.yml
├── host_vars
└── inv
inv file:
[g1:children]
m1
m2
[g2:children]
m3
m4
[m1]
192.168.0.60
[m2]
192.168.0.61
[m3]
192.168.0.62
[m4]
192.168.0.63
group_vars/g1/values.yml:
---
run_order: 'reverse_sorted'
When I try to run playbook:
ansible-playbook -i inv-test/inv --limit='g1' my-playbook.yml
I get the error:
ERROR! The field 'order' has an invalid value, which includes an undefined variable. The error was: 'run_order' is undefined
The error appears to be in '/home/mk/throttle': line 1, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- hosts: all
^ here
Although the output from ansible-inventory -i inv --graph --vars is correct
Why ansible doesn't parse vars from inventory groups_vars?
Ansible version 2.10.15
The problem is that you want to use a host variable in the scope of a play. See Scoping variables. It's possible if you create such a host variable first. Then you can use hostvars to reference the host variable. For example, given the simplified inventory
shell> cat hosts
[g1]
host_01
host_02
and the group_vars
shell> cat group_vars/g1.yml
run_order: 'reverse_sorted'
The playbook
shell> cat pb.yml
- hosts: all
gather_facts: false
tasks:
- debug:
var: run_order
gives (abridged)
shell> ansible-playbook -i hosts pb.yml
ok: [host_01] =>
run_order: reverse_sorted
ok: [host_02] =>
run_order: reverse_sorted
But, the playbook below
shell> cat pb.yml
- hosts: all
gather_facts: false
order: "{{ run_order }}"
tasks:
- debug:
var: run_order
fails because the variable run_order is not defined in the scope of the play
ERROR! The field 'order' has an invalid value, which includes an undefined variable. The error was: 'run_order' is undefined
The (awkward) solution is to create the hostvars in the first play and use it in the second play. For example, the second play in the playbook below is executed in the reverse order
shell> cat pb.yml
- hosts: all
gather_facts: false
tasks:
- debug:
var: run_order
- hosts: all
gather_facts: false
serial: 1
order: "{{ hostvars.host_01.run_order }}"
tasks:
- debug:
var: run_order
gives
shell> ansible-playbook -i hosts pb.yml
PLAY [all] ***********************************************************************************
TASK [debug] *********************************************************************************
ok: [host_01] =>
run_order: reverse_sorted
ok: [host_02] =>
run_order: reverse_sorted
PLAY [all] ***********************************************************************************
TASK [debug] *********************************************************************************
ok: [host_02] =>
run_order: reverse_sorted
PLAY [all] ***********************************************************************************
TASK [debug] *********************************************************************************
ok: [host_01] =>
run_order: reverse_sorted

Use dynamic variable name

I'm trying to get the value of ip_address from the following yaml that I'm including as variables on ansible:
common:
ntp:
- time.google.com
node1:
default_route: 10.128.0.1
dns:
- 10.128.0.2
hostname: ip-10-128-5-17
device_interface: ens5
cluster_interface: ens5
interfaces:
ens5:
ip_address: 10.128.5.17
nat_ip_address: 18.221.63.178
netmask: 255.255.240.0
version: 2
However the network interface (ens5 here) may be named something else, such as eth0. My ansible code is this:
- hosts: all
tasks:
- name: Read configuration from the yaml file
include_vars: "{{ config_yaml }}"
- name: Dump Interface Settings
vars:
msg: node1.interfaces.{{ cvp_device_interface }}.ip_address
debug:
msg: "{{ msg }}"
tags: debug_info
Running the code like this I can get the key's name:
TASK [Dump Interface Settings] *************************************************
│ ok: [18.221.63.178] => {
│ "msg": "node1.interfaces.ens5.ip_address"
│ }
But what I actually need is the value (i.e: something like {{ vars[msg] }}, which should expand into {{ node1.interfaces.ens5.ip_address }}). How can I accomplish this?
Use sqare brackets.
Example: a minimal playbook, which defines a variable called "device". This variable is used to return the active status of the device.
- hosts: localhost
connection: local
vars:
device: enx0050b60c19af
tasks:
- debug: var=device
- debug: var=hostvars.localhost.ansible_facts[device].active
Output:
$ ansible-playbook example.yaml
[WARNING]: provided hosts list is empty, only localhost is available. Note that
the implicit localhost does not match 'all'
PLAY [localhost] *******************************************************************
TASK [Gathering Facts] *************************************************************
ok: [localhost]
TASK [debug] ***********************************************************************
ok: [localhost] => {
"device": "enx0050b60c19af"
}
TASK [debug] ***********************************************************************
ok: [localhost] => {
"hostvars.localhost.ansible_facts[device].active": true
}
PLAY RECAP *************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
see comment
- hosts: all
tasks:
- name: Read configuration from the yaml file
include_vars: "{{ config_yaml }}"
- name: Dump Interface Settings
debug:
msg: "{{ node1['interfaces'][cvp_device_interface]['ip_address'] }}"
debug:
msg: "{{ msg }}"
tags: debug_info

Ansible playbook file selection task

It's probably against best practice but using an Ansible playbook, is it possible to get a list of files from one task and then offer a user prompt to select one of the files to pass into a variable?
For example:
Choose file to select:
1. file1.txt
2. file2.txt
3. file3.txt
> 1
The playbook would theoretically pause for the user input and then pass the resulting file selection into a variable to use in a future task.
Many thanks in advance.
Use pause. For example, given the files
shell> tree files
files
├── file1.txt
├── file2.txt
└── file3.txt
0 directories, 3 files
the playbook below
shell> cat playbook.yml
- hosts: localhost
tasks:
- find:
path: files
register: result
- set_fact:
my_files: "{{ result.files|map(attribute='path')|list|sort }}"
- pause:
prompt: |
Choose file to select:
{% for file in my_files %}
{{ loop.index }} {{ file }}
{% endfor %}
register: result
- debug:
msg: "selected file: {{ my_files[result.user_input|int - 1] }}"
gives (when selected 2nd file and typed '2<ENTER')
shell> ansible-playbook playbook.yml
PLAY [localhost] ****
TASK [find] ****
ok: [localhost]
TASK [set_fact] ****
ok: [localhost]
TASK [pause] ****
[pause]
Choose file to select:
1 files/file1.txt
2 files/file2.txt
3 files/file3.txt
:
ok: [localhost]
TASK [debug] ****
ok: [localhost] => {
"msg": "selected file: files/file2.txt"
}
PLAY RECAP ****
localhost: ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Resources