Ansible: print only user defined variables - ansible

How can I print variables declared only at group_vars, host_vars without ansible facts?
such code is good:
- name: "Ansible | List all known variables and facts"
debug:
var: hostvars[inventory_hostname]
But I don't need host IPs,disks, etc.
I mean to check all my variables one more time before continue to execute Play.

There are 3 categories of variables: ansible facts, special variables, and user's variables. Remove both ansible facts and special variables from hostvars and what is left are user's variables. The list of the ansible facts is available in the variables ansible_facts. The list of the special variables must be created (I think).
Create a list of special variables
If you run the playbook below you'll see the list of the special variables and user's variables
- hosts: localhost
tasks:
- debug:
msg: "{{ hostvars[inventory_hostname]|
difference(ansible_facts) }}"
Eliminate the user's vars and put the list of the special variables into a file. For example
shell> cat special_vars.yml
special_vars:
- ansible_python_interpreter
- ansible_connection
- inventory_hostname
...
This list of special variables might be not complete and will serve the purpose of this host only.
Remove ansible facts and special variables from hostvars
- hosts: localhost
vars_files:
- special_vars.yml
tasks:
- set_fact:
user_var1: AAA
- debug:
msg: "{{ hostvars[inventory_hostname]|
difference(ansible_facts)
difference(special_vars) }}"
gives the list of user's variables only
msg:
- user_var1
The user's variables will include also the configuration variables set by the user (e.g. connection variables: ansible_user or priviledge escalation: ansible_become).
Name-space
A better practice is to "name-space" variables. For example
- hosts: localhost
vars:
prj51_var1: AAA
prj51_var2: BBB
tasks:
- debug:
msg: "{{ item }}: {{ query('vars', item)|first }}"
loop: "{{ query('varnames', 'prj51_.+$') }}"
gives
msg: 'prj51_var1: AAA'
msg: 'prj51_var2: BBB'

Related

Use value of CLI variable as the name of a host_vars variable?

Is there a way to use the value of one Ansible variable as the name of another variable so I can extract a value from its list?
host_vars:
this:
does: walk
says: hi
that:
does: run
says: hello
On the CLI when I run the playbook, I add -e="thing=this".
In the playbook, I've tried all manner of things to expand the variable thing to its value this, then use this to extract the value of does in the host_vars file.
Using the variable name directly obviously works:
- name: Check what the thing does
debug:
msg: "{{ this['does'] }}"
But the following do not:
{{ thing['does'] }}
{{ {{ thing }}['does'] }}
Those, plus several other iterations I've tried all either throw an error or print out the literal string.
You need the vars lookup plugin to address variables indirectly. See
shell> ansible-doc -t lookup vars
For example,
- debug:
msg: "{{ lookup('vars', thing).does }}"
should give (abridged)
shell> ansible-playbook pb.yml -e "thing=this"
...
msg: walk
Example of a complete playbook for testing
- hosts: localhost
vars:
this:
does: walk
says: hi
that:
does: run
says: hello
tasks:
- debug:
msg: "{{ lookup('vars', thing).does }}"
You can simplify the code further by putting all declarations into the vars. For example, into the group_vars/all
shell> cat group_vars/all/thing.yml
this:
does: walk
says: hi
that:
does: run
says: hello
_thing: "{{ lookup('vars', thing) }}"
Then, the simplified playbook below gives the same results
- hosts: localhost
tasks:
- debug:
var: _thing.does

Ansible task vars override set_fact vars

In one of my ansible playbook, i set a variable for all the playbook by doing :
set_fact:
domain_path: "{{ X.X.domain_path }}"
During the playbook, one of my tasks uses a role, that needs a domain_path variable that is different than the one i defined in the beginning
include_role:
name: role_X
vars:
domain_path: "/a/different/path"
When i try to use domain_path, in another bloc after the role, it doesn't give me the set_fact value but the tasks value, which i do not want.
I can give more details if needed
Q: "task vars override set_fact vars"
A: task vars (precedence 17) can't override set_facts (precedence 19) for the rest of the play. See Variable precedence: Where should I put a variable?. Review the role. Very probably role (and include_role) params (precedence 20) override the variable.
I can't reproduce the problem. I created a fresh role with single task only
shell> cat roles/role_X/tasks/main.yml
- debug:
var: domain_path
The playbook
shell> cat pb.yml
- hosts: localhost
tasks:
- set_fact:
domain_path: "X.X.domain_path"
- debug:
var: domain_path
- include_role:
name: role_X
vars:
domain_path: "/a/different/path"
- debug:
var: domain_path
gives as expected
shell> ansible-playbook pb.yml | grep domain_path
domain_path: X.X.domain_path
domain_path: /a/different/path
domain_path: X.X.domain_path
It is not good practice to use the same variable name for different use cases. You should change the name of one (or both) of your variables so they do not conflict.
If there is a need, you can always use one value to set the other if that is your goal at some point in the task.

Passing a variable to an included Ansible playbook

I'd like to pass a variable to an included Ansible playbook as follows:
---
- hosts: localhost
connection: local
vars:
my_group: foo
- include: site.yml hosts={{ my_group }}
Then, in site.yml...
---
- hosts: "{{ hosts }}"
...
Unfortunately, I get an error saying that my_group is undefined in site.yml. Ansible docs do say that:
Note that you cannot do variable substitution when including one playbook inside another.
Is this my case? Is there a way around it?
You can use this syntax, but my_group has to be defined at the global level. Now it's local to the first play - it's even clear from the indentation.
You can confirm this by running your playbook with --extra-vars my_group=foo.
But generally what you seem to want to achieve is done using in-memory inventory files and add_host module. Take this as an example:
- hosts: localhost
gather_facts: no
vars:
target_host: foo
some_other_variable: bar
tasks:
- add_host:
name: "{{ target_host }}"
groups: dynamically_created_hosts
some_other_variable: "{{ some_other_variable }}"
- include: site.yml
with site.yml:
---
- hosts: dynamically_created_hosts
tasks:
- debug:
var: some_other_variable
I added some_other_variable to answer the question from your comment "how do I make a variable globally available from inside a play". It's not global, but it's passed to another play as a "hostvar".
From what I see (and I can't explain why) in Ansible >=2.1.1.0, there must be an inventory file specified for the dynamic in-memory inventory to work. In older versions it worked with Ansible executed ad hoc, without an inventory file, but now you must run ansible-playbook with -i inventory_file or have an inventory file defined through ansible.cfg.

Registering Each Host Specific Value from Dictionary

We wanted to have a single playbook for all the deployments and the multiple hosts will be looped in. Ansible calls will be made from Jenkins pipeline by passing in the environments, for example dev6 and dev8
env1=dev6
env2=dev8
Pipeline Call:
ansible-playbook -i hosts --limit $env1:$env2 deploy_test.yml -e "env1={{$env1}} env2={{$env2}}"
I defined all the host specific variables (dev1,dev2......PERF8 etc.) in single file so it is easy to manage and maintain,
dev6:
- { deploy_domain: "Dev6Domain",
WL_Admin: "DEV6WLAdmin",
WL_Managed: "DEV6Managed" }
dev7:
- { deploy_domain: "Dev7Domain",
WL_Admin: "Dev7WLAdmin",
WL_Managed: "Dev7Managed" }
Playbook "Deploy_test.yml"
- hosts: all
vars_files:
- host_variables.yml
tasks:
- debug: msg='Target Domain is "{{ item[0].deploy_domain }}"'
with_nested:
- "{{ env1 }}"
- "{{ env2 }}"
The env1 and env2 values are being read from jenkins, no issues there
Problem-1: When the playbook runs on dev6 first, it takes dev8 values as well since it is defined under with_nested items.
Problem-2: How do I register the values specific to every environment?
for example, down the playbook when I say, mkdir /tmp/{{deploy_domain}, I need seperate values for dev6 and dev8.
Here is an example how you can read name-specific variable for every host:
hosts:
[dev6]
box1
[dev8]
box2
host_variables.yml:
dev6:
deploy_domain: "Dev6Domain"
WL_Admin: "DEV6WLAdmin"
WL_Managed: "DEV6Managed"
dev8:
deploy_domain: "Dev8Domain"
WL_Admin: "Dev8WLAdmin"
WL_Managed: "Dev8Managed"
I stripped out list level from original host_variables.yml, because it is not necessary in this case, there is always single element in the list.
deploy_test.yml:
- hosts: all
tasks:
- include_vars: host_variables.yml
- set_fact:
my_env: "{{ hostvars[inventory_hostname][group_names[0]] }}"
- debug: msg="My domain = {{ my_env.deploy_domain }}"
execution: ansible-playbook -i hosts --limit $env1:$env2 deploy_test.yml
This will execute deploy_test.yml for all hosts in groups set in env vars env1 and env2.
In the begining of playbook, we load everything from host_variables.yml as host facts.
And with set_fact extract variable named after current host's group name as my_env.
So box1 will have dev6 as my_env and box2 will have dev8.

If set_fact is scoped to a host, can I use 'dummy' host as a global variable map?

I have defined two group of hosts: wmaster and wnodes. Each group runs in its play:
- hosts: wmaster
roles:
- all
- swarm-mode
vars:
- swarm_master: true
- hosts: wnodes
roles:
- all
- swarm-mode
I use host variables (swarm_master) to define different behavior of some role.
Now, my first playbook performs some initialization and I need to share data with the nodes. What I did is to use set_fact in first play, and then to lookup in the second play:
- set_fact:
docker_worker_token: "{{ hostvars[smarm_master_ip].foo }}"
I don't like using the swarm_master_ip. How about to add a dummy host: global with e.g. address 1.1.1.1 that does not get any role, and serves just for holding the global facts/variables?
If you're using Ansible 2 then you can take use of delegate_facts during your first play:
- name: set fact on swarm nodes
set_fact: docker_worker_token="{{ some_var }}"
delegate_to: "{{ item }}"
delegate_facts: True
with_items: "{{ groups['wnodes'] }}"
This should delegate the set_fact task to every host in the wnodes group and will also delegate the resulting fact to those hosts as well instead of setting the fact on the inventory host currently being targeted by the first play.
How about to add a dummy host: global
I have actually found this suggestion to be quite useful in certain circumstances.
---
- hosts: my_server
tasks:
# create server_fact somehow
- add_host:
name: global
my_server_fact: "{{ server_fact }}"
- hosts: host_group
tasks:
- debug: var=hostvars['global']['my_server_fact']

Resources