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
Related
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
I created a variable in group_vars
group_vars/all
---
hostname: name1
I want to set name2 to the real host(10.20.30.40), so I created a file and set the hostname again there
host_vars/10.20.30.40
---
hostname: name2
When I run the playbook, it returned name1 but not name2:
roles/os/tasks/main.yml
- name: Print hostname
ansible.builtin.debug:
msg: "{{ hostname }}"
Result:
TASK [server : Print hostname] *************************************
ok: [web_server] => {
"msg": "name1"
}
I want to set the variable in each host that I want to update, isn't it the right usage?
And, if I name the host file this way under the host_vars folder, does it work?
web_server
The inventory:
[web_server]
10.20.30.40
playbook:
---
- hosts: web_server
become: true
vars_files:
- group_vars/all
roles:
- os
Q: "When I run the playbook, it returned name1 but not name2."
shell> cat hosts
[web_server]
10.20.30.40
shell> cat group_vars/all
---
hostname: name1
shell> cat host_vars/10.20.30.40
---
hostname: name2
shell> cat playbook.yml
---
- hosts: web_server
vars_files:
- group_vars/all
tasks:
- debug:
var: hostname
A: Don't include group_vars/all in a playbook.
The playbook's group_vars/all are included by the playbook automatically at precedence 5. See Understanding variable precedence. If you put group_vars/all into the vars_files (precedence 14) you override the host_vars (precedence 10).
Example. Given the inventory
shell> cat hosts
[web_server]
10.20.30.40
10.20.30.41
the playbook
shell> cat playbook.yml
---
- hosts: web_server
gather_facts: false
tasks:
- debug:
var: hostname
gives as expected
shell> ansible-playbook -i hosts playbook.yml
PLAY [web_server] *********************************************************
TASK [debug] **************************************************************
ok: [10.20.30.40] =>
hostname: name2
ok: [10.20.30.41] =>
hostname: name1
The variable in host_vars/10.20.30.40 override the variable in group_vars/all
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
I'm having a very difficult time understanding how to organize large playbooks with many roles, using inventory with multiple "environments" and using sub-plays to try and organize things. All the while having common variables at the parent playbook, sharing those with sub-plays. I use ansible but in a very limited way so I'm trying to expand my knowledge of it by doing this exercise.
Directory structure (simplified for testing)
├── inventory
│ ├── dev
│ │ ├── group_vars
│ │ │ └── all.yml
│ │ └── hosts
│ └── prod
│ ├── group_vars
│ │ └── all.yml
│ └── hosts
├── playbooks
│ └── infra
│ └── site.yml
├── site.yml
└── vars
└── secrets.yml
Various secrets are in the secrets.yml file, including the ansible_ssh_user and ansible_become_pass.
Contents of all.yml
---
ansible_ssh_user: "{{ vault_ansible_ssh_user }}"
ansible_become_pass: "{{ vault_ansible_become_pass }}"
Contents of site.yml
---
- name: test plays
hosts: all
vars_files:
- vars/secrets.yml
become: true
gather_facts: true
pre_tasks:
- include_vars: secrets.yml
tasks:
- debug:
var: ansible_ssh_user
- import_playbook: playbooks/infra/site.yml
Content of playbooks/infra/site.yml
---
- name: test sub-play
hosts: all
become: true
gather_facts: true
tasks:
- debug:
var: ansible_ssh_user
The main parent playbook is being called with ansible-playbook -i inventory/dev site.yml. The problem is I can't access vault_ansible_ssh_user or vault_ansible_become_pass (or any secrets in vault) from within the sub-play if I don't include both var_files AND pre_tasks: - include_vars
If I remove var_files, I can't access the secrets in the parent playbook. If I remove pre_tasks: - include_vars, I can't access any secrets in the imported sub-play. Any idea why I need both of these variable include statements for this to work? Also, is this just a terrible design and I'm doing things completely wrong? I'm having a hard time wrapping my head around the best way to organize huge playbooks with a lot of required variables so I ended up with a directory structure like this to try and compartmentalize the variables to avoid very large variables files and the need to duplicate variable files all over the place. This probably just boils down to me wanting to fit a round peg in a square hole but I can't find a great best practices example for something like this.
This issue might also have to do with me trying to put ansible vault variables in an inventory var file maybe. If so, is that something I should or shouldn't be doing? As I was writing this, I may have had a "light bulb" moment and finally understand how I should handle this but I need to test some things to understand it fully but regardless, I'm still very interested in what the stackoverflow community has to say about how I'm currently doing this.
EDIT: turns out my "light bulb" idea is just the same as I have here just moved around in a different way, with the same issues
Q: "If I remove ... include_vars, I can't access any secrets in the imported sub-play."
A: To share variables among the plays use include_vars or set_fact. Quoting from Variable scope: how long is a value available?
Variable values associated directly with a host or group, including variables defined in inventory, by vars plugins, or using modules like set_fact and include_vars, are available to all plays. These ‘host scope’ variables are also available via the hostvars[] dictionary.
Given the files below
shell> cat inventory/prod/hosts
test_01
test_02
shell> cat inventory/prod/group_vars/all.yml
ansible_ssh_user: "{{ vault_ansible_ssh_user }}"
ansible_become_pass: "{{ vault_ansible_become_pass }}"
shell> cat vars/secrets.yml
vault_ansible_ssh_user: ansible-ssh-user
vault_ansible_become_pass: ansible-become-pass
shell> cat site.yml
- name: test plays
hosts: all
gather_facts: false
vars_files: vars/secrets.yml
tasks:
- debug:
var: ansible_ssh_user
- debug:
var: ansible_become_pass
- import_playbook: playbooks/infra/site.yml
shell> cat playbooks/infra/site.yml
- name: test sub-plays
hosts: all
gather_facts: false
tasks:
- debug:
var: ansible_ssh_user
The variables declared by vars_files are not shared among the plays and the second play will fail. The abridged result is
shell> ANSIBLE_INVENTORY=$PWD/inventory/prod/hosts ansible-playbook site.yml
PLAY [test plays] ****
TASK [debug] ****
ok: [test_01] => {
"ansible_ssh_user": "ansible-ssh-user"
}
ok: [test_02] => {
"ansible_ssh_user": "ansible-ssh-user"
}
TASK [debug] ****
ok: [test_01] => {
"ansible_become_pass": "ansible-become-pass"
}
ok: [test_02] => {
"ansible_become_pass": "ansible-become-pass"
}
PLAY [test sub-plays] ****
TASK [debug] ****
fatal: [test_01]: FAILED! => {"msg": "The field 'become_pass' has an invalid value, which includes an undefined variable. The error was: 'vault_ansible_become_pass' is undefined"}
fatal: [test_02]: FAILED! => {"msg": "The field 'become_pass' has an invalid value, which includes an undefined variable. The error was: 'vault_ansible_become_pass' is undefined"}
The problem will disappear if you use include_vars or set_fact, i.e. "instantiate" the variables. Commenting set_fact and uncommenting include_vars, or uncommenting both, will give the same result
- name: test plays
hosts: all
gather_facts: false
vars_files: vars/secrets.yml
tasks:
- debug:
var: ansible_ssh_user
- debug:
var: ansible_become_pass
# - include_vars: secrets.yml
- set_fact:
ansible_ssh_user: "{{ ansible_ssh_user }}"
ansible_become_pass: "{{ ansible_become_pass }}"
- import_playbook: playbooks/infra/site.yml
Then the abridged result is
shell> ANSIBLE_INVENTORY=$PWD/inventory/prod/hosts ansible-playbook site.yml
PLAY [test plays] ****
TASK [debug] ****
ok: [test_01] => {
"ansible_ssh_user": "ansible-ssh-user"
}
ok: [test_02] => {
"ansible_ssh_user": "ansible-ssh-user"
}
TASK [debug] ****
ok: [test_01] => {
"ansible_become_pass": "ansible-become-pass"
}
ok: [test_02] => {
"ansible_become_pass": "ansible-become-pass"
}
TASK [set_fact] ****
ok: [test_01]
ok: [test_02]
PLAY [test sub-plays] ****
TASK [debug] ****
ok: [test_02] => {
"ansible_ssh_user": "ansible-ssh-user"
}
ok: [test_01] => {
"ansible_ssh_user": "ansible-ssh-user"
}
Notes
In this example, it's not important whether the variables are encrypted or not.
become and gather_facts don't influence this problem.
There might be other issues. It's a good idea to review include and import issues.
Q: "Why is the vars_files needed?"
A: The variable ansible_become_pass is needed to escalate the user's privilege when a task is sent to the remote host. As a result, when the variable vault_ansible_become_pass is declared in the task include_vars only, this variable won't be available before the tasks are executed, and the play will fail with the error
fatal: [test_01]: FAILED! => {"msg": "The field 'become_pass' has an invalid value, which includes an undefined variable. The error was: 'vault_ansible_become_pass' is undefined"}
See
Understanding variable precedence
Understanding privilege escalation: become
No vars_files is needed if there are user-defined variables only. For example, the playbook below works as expected
shell> cat inventory/prod/group_vars/all.yml
var1: "{{ vault_var1 }}"
var2: "{{ vault_var2 }}"
shell> cat vars/secrets2.yml
vault_var1: test-var1
vault_var2: test-var2
shell> cat site2.yml
- name: test plays
hosts: all
gather_facts: false
tasks:
- include_vars: secrets2.yml
- debug:
var: var1
- debug:
var: var2
- import_playbook: playbooks/infra/site2.yml
shell> cat playbooks/infra/site2.yml
- name: test sub-plays
hosts: all
gather_facts: false
tasks:
- debug:
var: var1
- debug:
var: var2
I am trying to read some custom variables created in Ansible host file, but i am not able to read it somehow and it is throwing exception
Hostfile
[webserver]
xx.xx.45.12 uname=abc123
xx.xx.45.13 uname=pqr456
Playbook yaml
- name: sample playbook
hosts: all
tasks:
- name: sample echo command
shell:
cmd: echo {{hostvars['all'].uname}}
I couldn't find any document which is talking clearly how to read host variable
When i am running above I am getting below error.
fatal: [xx.xx.45.12]: FAILED! => {"msg": "The task includes an option with an undefined variable.
The error was: \"hostvars['webserver']\" is undefined\n\nThe error appears to be in
'/mnt/c/Users/ManishBansal/Documents/work/MSS/scripts/run.yaml': line 6, column 7, but may\nbe elsewhere
in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n
- name: This command will get file list\n ^ here\n"}
Q: "How to read host variable?"
A: Simply reference the variable
- command: echo {{ uname }}
For example the inventory and the playbook below
shell> cat hosts
[webserver]
test_01 uname=abc123
test_02 uname=pqr456
shell> cat playbook.yml
- hosts: all
tasks:
- debug:
var: uname
give (abridged)
shell> ansible-playbook -i hosts playbook.yml
ok: [test_01] =>
uname: abc123
ok: [test_02] =>
uname: pqr456
Use hostvars to reference variables registered with other hosts. For example
shell> cat playbook.yml
- hosts: localhost
tasks:
- debug:
var: hostvars[item].uname
loop: "{{ groups.webserver }}"
gives (abridged)
shell> ansible-playbook -i hosts playbook.yml
ok: [localhost] => (item=test_01) =>
ansible_loop_var: item
hostvars[item].uname: abc123
item: test_01
ok: [localhost] => (item=test_02) =>
ansible_loop_var: item
hostvars[item].uname: pqr456
item: test_02
Notes
See Caching facts
Quoting from module shell notes
"... it may be better to use the command module instead ..."