According to the Ansible documentation, the setup module is
This module is automatically called by playbooks to gather useful variables about remote hosts that can be used in playbooks. It can also be executed directly by /usr/bin/ansible to check what variables are available to a host. Ansible provides many facts about the system, automatically.
And there are some parameters which include gather_subset.
If supplied, restrict the additional facts collected to the given subset. Possible values: all, min, hardware, network, virtual, ohai, and facter. Can specify a list of values to specify a larger subset. Values can also be used with an initial ! to specify that that specific subset should not be collected. For instance: !hardware,!network,!virtual,!ohai,!facter. If !all is specified then only the min subset is collected. To avoid collecting even the min subset, specify !all,!min. To collect only specific facts, use !all,!min, and specify the particular fact subsets. Use the filter parameter if you do not want to display some collected facts.
I want to know the exact list of fact that min subset would collect.
Thanks
It will depend on your environment and setup what is available and can be collected.
You could just have a short test with
---
- hosts: localhost
become: false
gather_facts: true
gather_subset:
- "min"
tasks:
- name: Show Gathered Facts
debug:
msg: "{{ ansible_facts }}"
and check the output, in example for a RHEL system the keys are
_ansible_facts_gathered: true
ansible_apparmor:
status:
ansible_architecture:
ansible_cmdline:
BOOT_IMAGE:
LANG:
elevator:
quiet:
rhgb:
ro:
root:
ansible_date_time:
date: ''
day: ''
epoch: ''
hour:
iso8601: ''
iso8601_basic:
iso8601_basic_short:
iso8601_micro: ''
minute: ''
month: ''
second: ''
time:
tz:
tz_offset: ''
weekday:
weekday_number: ''
weeknumber: ''
year: ''
ansible_distribution:
ansible_distribution_file_parsed:
ansible_distribution_file_path:
ansible_distribution_file_search_string:
ansible_distribution_file_variety:
ansible_distribution_major_version: ''
ansible_distribution_release:
ansible_distribution_version: ''
ansible_dns:
nameservers:
-
search:
-
ansible_domain:
ansible_effective_group_id:
ansible_effective_user_id:
ansible_env:
HISTCONTROL:
HISTSIZE: ''
HOME:
HOSTNAME:
KRB5CCNAME:
LANG:
LESSOPEN: ''
LOGNAME:
LS_COLORS:
MAIL:
PATH:
PWD:
SELINUX_LEVEL_REQUESTED: ''
SELINUX_ROLE_REQUESTED: ''
SELINUX_USE_CURRENT_RANGE: ''
SHELL:
SHLVL: ''
SSH_CLIENT:
SSH_CONNECTION:
SSH_TTY:
TERM:
TZ:
USER:
XDG_RUNTIME_DIR:
XDG_SESSION_ID: ''
_:
ansible_fips:
ansible_fqdn:
ansible_hostname:
ansible_kernel:
ansible_kernel_version: ''
ansible_local: {}
ansible_lsb: {}
ansible_machine:
ansible_machine_id:
ansible_nodename:
ansible_os_family:
ansible_pkg_mgr:
ansible_proc_cmdline:
BOOT_IMAGE:
LANG:
elevator:
quiet:
rhgb:
ro:
root:
ansible_python:
executable:
has_sslcontext:
type:
version:
major:
micro:
minor:
releaselevel:
serial:
version_info:
-
ansible_python_version:
ansible_real_group_id:
ansible_real_user_id:
ansible_selinux:
config_mode:
mode:
policyvers:
status:
type:
ansible_selinux_python_present:
ansible_service_mgr:
ansible_ssh_host_key_ecdsa_public:
ansible_ssh_host_key_ed25519_public:
ansible_ssh_host_key_rsa_public:
ansible_system:
ansible_system_capabilities:
- ''
ansible_system_capabilities_enforced: ''
ansible_user_dir:
ansible_user_gecos:
ansible_user_gid:
ansible_user_id:
ansible_user_shell:
ansible_user_uid:
ansible_userspace_architecture:
ansible_userspace_bits: ''
gather_subset:
- min
module_setup: true
For min the /ansible/modules/setup.py tries to gather information from
minimal_gather_subset = frozenset(['apparmor', 'caps', 'cmdline', 'date_time',
'distribution', 'dns', 'env', 'fips', 'local',
'lsb', 'pkg_mgr', 'platform', 'python', 'selinux',
'service_mgr', 'ssh_pub_keys', 'user'])
so that can be considered as
the exact list of fact that min subset would collect.
As one can see, blocks of the information are coming from different modules, in example for ansible_distribution from facts/system/distribution.py.
In my case in example, the module for ansible_env, facts/system/env.py will create keys which can not be found in any other environment.
For more background information of what is collected for specific environments and setups you have a look into /ansible/module_utils/facts.
Further Q&A
How Ansible gather_facts and sets variables
Getting full name of the OS using ansible_facts
Q: "I want to know the exact list of facts that the "min" subset would collect."
A: Run the module separately by ansible. You'll see the list of the facts collected by this module
shell> ansible localhost -m setup -a 'gather_subset=min'
As a side note, the facts differ among the systems. For example, compare the collected minimal facts among FreeBSD and Ubuntu
shell> grep PRETTY_NAME /etc/os-release
PRETTY_NAME="FreeBSD 13.0-RELEASE"
Store the output in a file
shell> ansible localhost -m setup -a 'gather_subset=min' > /scratch/freebsd.json
shell> cat /scratch/freebsd.json
localhost | SUCCESS => {
"ansible_facts": {
Remove 'localhost | SUCCESS => ` from the file
shell> cat /scratch/freebsd.json
{
"ansible_facts": {
Create a file with the keys (variables) only
shell> cat freebs.json | jq '.ansible_facts | keys[]' > freebsd_keys.txt
Repeat the procedure in Ubuntu and create ubuntu_keys.txt
shell> grep DISTRIB_DESCRIPTION /etc/lsb-release
DISTRIB_DESCRIPTION="Ubuntu 20.04.2 LTS"
Diff the files
shell> diff ubuntu_keys.txt freebsd_keys.txt
3d2
< "ansible_cmdline"
6,8d4
< "ansible_distribution_file_parsed"
< "ansible_distribution_file_path"
< "ansible_distribution_file_variety"
25d20
< "ansible_machine_id"
29d23
< "ansible_proc_cmdline"
44,45d37
< "ansible_system_capabilities"
< "ansible_system_capabilities_enforced"
52d43
< "ansible_userspace_architecture"
There are also differences among the Linux distributions. For example, Ubuntu 20.04 and Centos 8
shell> diff ubuntu_keys.txt centos_keys.txt
38d37
< "ansible_ssh_host_key_ecdsa_public_keytype"
40d38
< "ansible_ssh_host_key_ed25519_public_keytype"
42d39
< "ansible_ssh_host_key_rsa_public_keytype"
Related
Based on a questions
How to search for a string in a file using Ansible?
Ansible: How to pull a specific string out of the contents of a file?
Can slurp be used as a direct replacement for lookup?
and considerations like
By using the slurp module one is going to transfer the whole file from the Remote Node to the Control Node over the network just in order to process it and looking up a string. For log files these can be several MB and whereby one is mostly interested only in the information if the file on the Remote Node contains a specific string and therefore one would only need to transfer that kind of information, true or false.
How to execute a script on a Remote Node using Ansible?
I was wondering how this can be solved instead of using the shell module?
---
- hosts: localhost
become: false
gather_facts: false
vars:
SEARCH_STRING: "test"
SEARCH_FILE: "test.file"
tasks:
- name: Search for string in file
command:
cmd: "grep '{{ SEARCH_STRING }}' {{ SEARCH_FILE }}"
register: result
# Since it is a reporting task
# which needs to deliver a result in any case
failed_when: result.rc != 0 and result.rc != 1
check_mode: false
changed_when: false
Or instead of using a workaround with the lineinfile module?
---
- hosts: localhost
become: false
gather_facts: false
vars:
SEARCH_STRING: "test"
SEARCH_FILE: "test.file"
tasks:
- name: Search for string
lineinfile:
path: "{{ SEARCH_FILE }}"
regexp: "{{ SEARCH_STRING }}"
line: "SEARCH_STRING FOUND"
state: present
register: result
# Since it is a reporting task
changed_when: false
failed_when: "'replaced' not in result.msg" # as it means SEARCH_STRING NOT FOUND
check_mode: true # to prevent changes and to do a dry-run only
- name: Show result, if not found
debug:
var: result
when: "'added' in result.msg" # as it means SEARCH_STRING NOT FOUND
Since I am looking for a more generic approach, could it be a feasible case for Should you develop a module?
Following Developing modules and Creating a module I've found the following simple solution with
Custom Module library/pygrep.py
#!/usr/bin/python
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.module_utils.basic import AnsibleModule
def run_module():
module_args = dict(
path=dict(type='str', required=True),
search_string=dict(type='str', required=True)
)
result = dict(
changed=False,
found_lines='',
found=False
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
with open(module.params['path'], 'r') as f:
for line in f.readlines():
if module.params['search_string'] in line:
result['found_lines'] = result['found_lines'] + line
result['found'] = True
result['changed'] = False
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()
Playbook pygrep.yml
---
- hosts: localhost
become: false
gather_facts: false
vars:
SEARCH_FILE: "test.file"
SEARCH_STRING: "test"
tasks:
- name: Grep string from file
pygrep:
path: "{{ SEARCH_FILE }}"
search_string: "{{ SEARCH_STRING }}"
register: search
- name: Show search
debug:
var: search
when: search.found
For a simple test.file
NOTEST
This is a test file.
It contains several test lines.
321tset
123test
cbatset
abctest
testabc
test123
END OF TEST
it will result into an output of
TASK [Show search] ******************
ok: [localhost] =>
search:
changed: false
failed: false
found: true
found_lines: |-
This is a test file.
It contains several test lines.
123test
abctest
testabc
test123
Some Links
What is the grep equivalent in Python?
cat, grep and cut - translated to Python
What is the Python code equivalent to Linux command grep -A?
Pulling my hair out. I know I've done this eons ago with much older versions of ansible. I haven't touched ansible much so don't recall how I did this. This seems so ridiculously easy at first glance so obviously something stupid i'm overlooking.
Given sample inventory, nothing fancy:
Note: removing FQDN's for brevity.
Note: this automation is working with a large number of hosts and many many more variables than I'm representing here. This is a majorly dumbed down example.
[nodes]
foo1
foo2
foo3
[all:vars]
minio_proto=http
disks_per_node=64
Ultimately I need to build a string that wraps text around the hostnames. This is for minio if you are familiar with it.
You start a server by speficying all the nodes and the disk paths:
minio server http://foo1/data/disk{0...79} http://foo2/data/disk{0...63} http://foo3/data/disk{0...63}
So inside my play I need to construct this string that eventually gets passed to a shell command to start the server on each node.
So I have a playbook (eventually this will be a role) that will be run once, not for every host, just to construct this server parameter.
I've tried so many permutations of stuff I can't possibly list them all but here's just one of my latest stabs:
---
- hosts: all
gather_facts: false
become: true
ignore_errors: false
vars:
minio_startup1: []
minio_startup2: []
tasks:
- name: Verifying hosts are up and we can become root
become: true
ping:
# THIS WORKS FINE, APPENDING A SIMPLE VARIABLE WORKS
- name: Building minio startup parameter
set_fact:
minio_startup1: "{{ minio_startup1 + [item] }}"
with_items:
- "{{ ansible_play_hosts }}"
- debug: var=minio_startup1
# DOING STUFF LIKE THIS DOES NOT WORK
- name: Building minio startup parameter
set_fact:
minio_startup2: "{{ minio_startup2 }} + [{{ minio_proto }}//{{ item }}/data{0...{{ disks_per_node|int - 1 }}}] }}"
with_items:
- "{{ ansible_play_hosts }}"
- debug: var=minio_startup2
Basically at this point in the playbook I want a list that looks like this:
[ 'http://foo1/data{0...63}', 'http://foo2/data{0...63}', 'http://foo2/data{0...63}']
Then later in the playbook, I can concatenate this into a single string I can feed my minio container thru a shell command:
{{ minio_startup|join(' ') }}
I know I did this years ago but after 3 hrs of hair pulling it eludes me.
* UPDATE *
Well I figured out a way, not sure if this is the best. If interested, this is what I did. It looks like you can use '~' to concatenate items inside the []'s. I still had to do the math of determinig the ending disk index number in a separate variable. (disk_ending_index)
minio_startup2: "{{ minio_startup2 + [ minio_proto ~ '//' ~ item ~ '/data{0...' ~ disk_ending_index ~ '}' ] }}"
Use Single-Quoted Style. The playbook below
- hosts: foo1:foo2:foo3
gather_facts: false
vars:
minio_proto: http
disks_per_node: 64
minio_startup: []
tasks:
- set_fact:
minio_startup: '{{ minio_startup +
[minio_proto ~ "://" ~ item ~
"/data{0.." ~ disks_per_node ~ "}"] }}'
loop: "{{ ansible_play_hosts }}"
run_once: true
- debug:
var: minio_startup
run_once: true
- debug:
msg: "{{ minio_startup|join(' ') }}"
run_once: true
gives (abridged)
ok: [foo1] => {
"minio_startup": [
"http://foo1/data{0..63}",
"http://foo2/data{0..63}",
"http://foo3/data{0..63}"
]
}
ok: [foo1] => {
"msg": "http://foo1/data{0..63} http://foo2/data{0..63} http://foo3/data{0..63}"
}
Quoting from Single-Quoted Style:
... the “\” and “"” characters may be freely used. This restricts single-quoted scalars to printable characters ...
I am fairly new to Ansible and today while writing a new playbook I came across some behaviour I can't explain. I have been through the documentation and done my best Google-Fu, but I can't seem to find the answer.
The behaviour relates to variables defined in a playbook's "var" section and their lack of expansion into proper values when using other variables or arithmetic.
Take an example playbook with "static" values in vars:
--- # Test playbook vars
- hosts: 'all'
connection: ssh
gather_facts: false
vars:
- max_size_bytes: 10240
tasks:
- name: debug!
debug:
msg: |
"max_size_bytes: {{max_size_bytes}}"
"vars: {{vars['max_size_bytes']}}"
This outputs:
# ansible-playbook -i host1, test_vars.yml
PLAY [all] *************************************************************************************************************************
TASK [debug!] **********************************************************************************************************************
ok: [host1] => {}
MSG:
"max_size_bytes: 10240"
"vars: 10240"
Which is exactly what I would expect.
However, let's say I want to calculate that 10240 number dynamically:
--- # Test playbook vars
- hosts: 'all'
connection: ssh
gather_facts: false
vars:
- max_size_bytes: "{{10 * 1024}}"
tasks:
- name: debug!
debug:
msg: |
"max_size_bytes: {{max_size_bytes}}"
"vars: {{vars['max_size_bytes']}}"
And the new result is:
# ansible-playbook -i host1, test_vars.yml
PLAY [all] *************************************************************************************************************************
TASK [debug!] **********************************************************************************************************************
ok: [host1] => {}
MSG:
"max_size_bytes: 10240"
"vars: {{10 * 1024}}"
I get a similar output if I try to use another variable within the assignment. I came across this issue when I wanted to allow a playbook user to quick change some settings, without requiring them to calculate anything. For example:
vars:
- max_size_in_megabytes: 100 #change me if required
- max_size_bytes: "{{max_size_in_megabytes * 1024 * 1024}}"
But this didn't work as I expected, as above.
In some places the variable is expanded correctly and gives the result I would expect (i.e. the calculated value). At other times, it seems the variable is not expanded and is treated as a string as per the output for vars['max_size_bytes'].
What is the reason for this behaviour? Variable expansion and calculated values seem to work elsewhere in a playbook - why not for pre-defined variables?
If this behaviour is considered normal, what is the proper way of creating global/reusable variables that may need to be calculated on the fly?
UPDATE
I realise there may be some confusion as to what my issue actually is. That's my fault.
To try and explain better, take a look at another example:
--- # Test playbook vars
- hosts: 'localhost'
connection: local
gather_facts: false
vars:
- multiplier: 10 #change me as required
- some_value: 300
- max_size_dynamic: "{{10 * 20}}"
- max_size_static: 200
tasks:
- set_fact:
is_bigger_dynamic: "{{some_value > max_size_dynamic}}"
is_bigger_static: "{{some_value > max_size_static}}"
- name: debug!
debug:
msg: |
multiplier: {{multiplier}}
some_value {{some_value}}
Is {{some_value}} bigger than {{max_size_static}}?
is_bigger_static: {{is_bigger_static}} <-- hooray
Is {{some_value}} bigger than {{max_size_dynamic}}?
is_bigger_dynamic: {{is_bigger_dynamic}} <-- woops!
When running this playbook, you can see that the value of the conditional clauses in the set_fact task differs based on how a variable is created in vars.
Output:
PLAY [localhost] **********************************************************************************************************************
TASK [set_fact] ***********************************************************************************************************************
ok: [localhost]
TASK [debug!] *************************************************************************************************************************
ok: [localhost] => {}
MSG:
multiplier: 10
some_value 300
Is 300 bigger than 200?
is_bigger_static: True <-- hooray
Is 300 bigger than 200?
is_bigger_dynamic: False <-- woops!
The conditionals are checking the same expression: 300 > 200, but if the value 200 is derived from another expression in vars the condition is wrong.
I suspect in the case of {{some_value > max_size_dynamic}} the variable isn't being expanded (much like using vars['name\] as mentioned in the comments). So it looks like the conditional ends up being {{some_value > "{{10 * 20}}"}}.
Is this expected behaviour with set_fact? Is there anything I can do to allow expressions in vars which can be further used in set_fact tasks?
This is running the latest version of Ansible on MacOS High Sierra:
# ansible --version
ansible 2.5.0
config file = /Users/xxx/.ansible.cfg
configured module search path = [u'/Users/xxx/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/Cellar/ansible/2.5.0/libexec/lib/python2.7/site-packages/ansible
executable location = /usr/local/bin/ansible
python version = 2.7.14 (default, Apr 9 2018, 16:44:39) [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.1)]
Your update to the question is completely different to original post.
And the issue with update part is this: Why a string in always "greater than" a number?
Just do the type casting:
- set_fact:
is_bigger_dynamic: "{{some_value > max_size_dynamic | int }}"
is_bigger_static: "{{some_value > max_size_static }}"
In your case some_value and max_size_static are numbers, but max_size_dynamic is a string (you can't make simple integer outside of {{...}} in ansible – only strings/booleans/lists/dictionaries).
Given the following playbook (deployment.yml):
---
- name: Debug
hosts: applicationservers
tasks:
- debug: msg="{{add_host_entries | default('false')}}"
- debug: msg="{{add_host_entries | default('false') == 'true'}}"
- debug: msg="Add host entries = {{add_host_entries | default('false') == 'true'}}"
- include: add_host_entries.yml
when: add_host_entries | default('false') == 'true'
The condition to include add_host_entries.yml always fails, even if all of the above debug messages print some sort of true (I know that in the first debug message it's a String, whereas the other two result in Booleans).
When I omit the part with the default value, add_host_entries.yml will be executed:
when: add_host_entries
I need this default value behaviour though, because it's an optional value which is only set on certain stages.
Other Attempts (without success)
Brackets
when: (add_host_entries | default('false')) == 'true'
Casting to boolean
when: add_host_entries|default('false')|bool
Other Sources and Information
Here are all the resources needed to reproduce the problem.
add_host_entries.yml
---
- name: add_host_entries
hosts: applicationservers
gather_facts: false
tasks:
- debug: msg="Add Host Entries"
inventory
[applicationservers]
127.0.0.1
[all:vars]
add_host_entries=true
Call
markus#lubuntu:~/foobar$ ansible-playbook deployment.yml -i inventory
Versions
markus#lubuntu:~/foobar$ ansible --version
ansible 2.1.1.0
config file = /etc/ansible/ansible.cfg
configured module search path = Default w/o overrides
markus#lubuntu:~/foobar$ ansible-playbook --version
ansible-playbook 2.1.1.0
config file = /etc/ansible/ansible.cfg
configured module search path = Default w/o overrides
You try to conditionally include playbook. See my other answer about different include types.
The thing is, this only works when variable is defined before Ansible parses your playbook.
But you try to define add_host_entries as host-level fact (group variable) – these variables are not yet defined during parse time.
If you call your playbook with -e add_host_entries=true your condition will work as expected, because extra-vars are known during parse time.
Use bool to convert the string value of add_host_entries into a boolean and then the condition will work.
---
- name: Debug
hosts: applicationservers
tasks:
- debug: msg="{{add_host_entries | default('false')}}"
- debug: msg="{{add_host_entries | default('false') == 'true'}}"
- debug: msg="Add host entries = {{add_host_entries | default('false') == 'true'}}"
- include: add_host_entries.yml
when: add_host_entries | default('false') | bool
I have 3 variables named IPOctet, ServerIPRange and epcrange.
If I perform the following operation in my terminal, it works perfectly
IPOctet=$(echo "$ServerIPRange/$epcrange+$IPOctet" | bc)
How to I do something similar in a ansible inside a task, for e.g
---
- hosts: localhost
gather_facts: False
vars_prompt:
- name: epcrange
prompt: Enter the number of EPCs that you want to configure
private: False
default: "1"
- name: serverrange
prompt: Enter the number of Clients that you want to configure
private: False
default: "1"
- name: ServerIPRange
prompt: Enter the ServerIP range
private: False
default: '128'
- name: LastIPOctet
prompt: Enter The last Octet of the IP you just entered
private: False
default: '10'
pre_tasks:
- name: Set some facts
set_fact:
ServerIP1: "{{ServerIP}}"
ServerIPRange1: "{{ServerIPRange}}"
IPOctet: "{{LastIPOctet}}"
- name: local action math
local_action: shell {{IPOctet}}=$(echo "${{ServerIPRange}}/${{epcrange}}+${{IPOctet}}" | bc) # Proper Syntax?
with_sequence: start=1 end=4
register: result
ignore_errors: yes
What is the proper syntax for this command? Maybe using shell echo "......." . I just need to save the contents of this command into the IPOctet variable and IPOctet will change with each loop iteration and the results should be stored in my result register
P.S: how can I access the individual items in the array separately?
Edit: Is anything like this possible, currently it just does the calculation once and stores it 4 times in the register...
- name: bashless math
set_fact:
IPOctet: "{{ (ServerIPRange|int/epcrange|int)+IPOctet|int }}"
register: IPOctet
with_sequence: "start=1 end={{stop}} "
register: my_ip_octet
Your terminal expression reassigns the IPOctet shell variable, so it gives a different result each time it is executed. This is fine, but difficult to reproduce in Ansible:
$ IPOctet=10 ServerIPRange=128 epcrange=1
$ IPOctet=$(echo "$ServerIPRange/$epcrange+$IPOctet" | bc); echo $IPOctet
138
$ IPOctet=$(echo "$ServerIPRange/$epcrange+$IPOctet" | bc); echo $IPOctet
266
The syntax: "shell {{IPOctet}}=$(echo ..." does NOT assign to the Ansible variable.
The shell attempts to execute a command like "10=138", which is not found.
When register is used within a loop, the target variable is not set until the loop completes - so your expression always sees the original value for {{IPOctet}}.
A solution is to run the whole loop as a single shell command:
- name: local action math2
local_action: shell IPOctet={{IPOctet}}; for i in 1 2 3 4; do IPOctet=$(expr {{ServerIPRange}} / {{epcrange}} + $IPOctet); echo $IPOctet; done
register: result
NOTE: I've used the expr command rather than bc, but the results are the same.
You can iterate over these results using result.stdout_lines:
- name: iterate results
local_action: debug msg={{item}}
with_items: result.stdout_lines
Firstly your Jinja template is incorrect, every single variable needs to be surrounded with a pair of brackets. You can not use multiple variables within single pair of brackets. For example,
{{ ServerIPRange }}
Secondly, set_fact is used only to set a fact value. You can not run shell commands using set_fact. You should use shell module instead.
- name: local action math
local_action: shell {{ IPOctet }}=$(echo {{ ServerIPRange|int }}/{{ epcrange|int }}+{{ IPOctet|int }}" | bc)
with_sequence: start=1 end=4
register: result
ignore_errors: yes
Ansible will do the calculation 4 times and store it in a list as 4 different elements. You can check what all is stored inside this list and can even access it by looping over it.
- debug: msg={{ result }}
Hope this helps :)