I trying to set an environment variable for an ansible play.
Based on this, I should be able to do something like:
---
- hosts: localhost
connection: local
environment:
test_var: "a vault here"
tasks:
- debug:
msg: "Test var is: {{lookup('env', 'test_var')}}"
- debug:
msg: "Home is here {{lookup('env', 'HOME')}}"
But I am clearly missing something or encountered a bug?
osboxes#osboxes:~$ ansible-playbook --version
ansible-playbook 2.9.6
<snip...>
osboxes#osboxes:~$ ansible-playbook test.yaml
<snip...>
TASK [debug] ****************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Test var is: " # where is the env variable?
}
TASK [debug] ****************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Home is here /home/osboxes"
}
When you set env using environment plugin, you do not affect the ansible session which is already running.
You would have to do as follow:
---
- hosts: localhost
connection: local
environment:
test_var: "a vault here"
tasks:
- shell: echo $test_var
register: test_var
- debug:
msg: "Test var is: {{ test_var.stdout }}"
- debug:
msg: "Home is here {{lookup('env', 'HOME')}}"
The above playbook would return the following output:
PLAY [localhost] ****************************************************************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************************************************
ok: [localhost]
TASK [shell] ********************************************************************************************************************************************************
changed: [localhost]
TASK [debug] ********************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Test var is: a vault here"
}
TASK [debug] ********************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Home is here /home/ps"
}
PLAY RECAP **********************************************************************************************************************************************************
localhost : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Note: See this link to official documents.
When you set a value with environment: at the play or block level, it
is available only to tasks within the play or block that are executed
by the same user. The environment: keyword does not affect Ansible
itself, Ansible configuration settings, the environment for other
users, or the execution of other plugins like lookups and filters.
Variables set with environment: do not automatically become Ansible
facts, even when you set them at the play level. You must include an
explicit gather_facts task in your playbook and set the environment
keyword on that task to turn these values into Ansible facts.
Important points to highlight in the above text:
The environment: keyword does not affect Ansible itself, Ansible
configuration settings, the environment for other users, or the
execution of other plugins like lookups and filters
Related
I'm stuck to get data from gathered fact, using calculated data as part of query.
I am using 2.9 ansible and here is my task
---
- hosts: ios
connection: network_cli
gather_facts: true
tasks:
- name: CEF OUTPUT
ios_command:
commands: sh ip cef 0.0.0.0 0.0.0.0 | i nexthop
register: cef
- set_fact:
reg_result: "{{ cef.stdout |string| regex_search('Tunnel[0-9]+')}}"
- name: IT WORKS!
debug:
msg: "{{ reg_result }}"
- name: MANUAL LIST
debug:
var: ansible_facts.net_interfaces.Tunnel3.description
- name: AUTO LIST
debug:
var: ansible_facts.net_interfaces.[reg_result].description
and here is output
PLAY [ios] **********************************************
TASK [Gathering Facts] **********************************
ok: [10.77.3.1]
TASK [CEF OUTPUT] ***************************************
ok: [10.77.3.1]
TASK [set_fact] *****************************************
ok: [10.77.3.1]
TASK [IT WORKS!] ****************************************
ok: [10.77.3.1] => {
"msg": "Tunnel3"
}
TASK [MANUAL LIST] **************************************
ok: [10.77.3.1] => {
"ansible_facts.net_interfaces.Tunnel3.description": "DMVPN via MTS"
}
TASK [AUTO LIST] ****************************************
fatal: [10.77.3.1]: FAILED! => {"msg": "template error while templating string: expected name or number. String: {{ansible_facts.net_interfaces.[reg_result].description}}"}
to retry, use: --limit #/home/user/ansible/retry/ios_find_gw_int.retry
PLAY RECAP **********************************************
10.77.3.1 : ok=5 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
You see. Now I know that my default gateway is pointing to "Tunnel3", and it is possible to get some data placing this "Tunnel3" in {{ ansible_facts.net_interfaces.Tunnel3.description }} but how to get this automatically? And I feel such nested variable in the list is a very handy tool.
Remove the dot if you use the indirect addressing
- name: AUTO LIST
debug:
var: ansible_facts.net_interfaces[reg_result].description
See Referencing key:value dictionary variables.
When using notify on a task using import_tasks the handlers are not fired. I'm wondering why. tags are working like expected.
How to trigger handlers on imported tasks?
Example:
playbook test.yml:
- hosts: all
gather_facts: no
handlers:
- name: restart service
debug:
msg: restart service
tasks:
- import_tasks: test_imports.yml
notify: restart service
tags: test
test_imports.yml
- name: test
debug:
msg: test
changed_when: yes
- name: test2
debug:
msg: test2
changed_when: yes
Expected:
> ansible-playbook -i localhost, test.yml
PLAY [all] *************************************************************************************************************
TASK [test] ************************************************************************************************************
changed: [localhost] => {
"msg": "test"
}
TASK [test2] ***********************************************************************************************************
changed: [localhost] => {
"msg": "test2"
}
RUNNING HANDLER [restart service] **************************************************************************************
ok: [localhost] => {
"msg": "restart service"
}
...
Actual:
> ansible-playbook -i localhost, test.yml
PLAY [all] *************************************************************************************************************
TASK [test] ************************************************************************************************************
changed: [localhost] => {
"msg": "test"
}
TASK [test2] ***********************************************************************************************************
changed: [localhost] => {
"msg": "test2"
}
...
This question has been partially answered in Ansible's bug tracker here:
import_tasks is processed at parse time and is effectively replaced by the tasks it imports. So when using import_tasks in handlers you would need to notify the task names within.
Source: mkrizek's comment: https://github.com/ansible/ansible/issues/59706#issuecomment-515879321
This has been also further explained here:
imports do not work like tasks, and they do not have an association between the import_tasks task and the tasks within the imported file really. import_tasks is a pre-processing trigger, that is handled during playbook parsing time. When the parser encounters it, the tasks within are retrieved, and inserted where the import_tasks task was located. There is a proposal to "taskify" includes at ansible/proposals#136 but I don't see that being implemented any time soon.
Source: sivel comment: https://github.com/ansible/ansible/issues/64935#issuecomment-573062042
And for the good part of it, it seems it has been recently fixed: https://github.com/ansible/ansible/pull/73572
But, for now, what would work would be:
- hosts: all
gather_facts: no
handlers:
- name: restart service
debug:
msg: restart service
tasks:
- import_tasks: test_imports.yml
tags: test
And a test_imports.yml file like:
- name: test
debug:
msg: test
changed_when: yes
notify: restart service
- name: test2
debug:
msg: test2
changed_when: yes
notify: restart service
This all yields:
PLAY [all] *******************************************************************************************************
TASK [test] ******************************************************************************************************
changed: [localhost] =>
msg: test
TASK [test2] *****************************************************************************************************
changed: [localhost] =>
msg: test2
RUNNING HANDLER [restart service] ********************************************************************************
ok: [localhost] =>
msg: restart service
PLAY RECAP *******************************************************************************************************
localhost : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Then if you want to import those tasks somewhere this handler is not defined you could use the environment variable ERROR_ON_MISSING_HANDLER that helps you transform the error thrown when there is a missing handler to a "simple" warning.
e.g.
$ ANSIBLE_ERROR_ON_MISSING_HANDLER=false ansible-playbook play.yml -i inventory.yml
PLAY [all] *******************************************************************************************************
TASK [test] ******************************************************************************************************
[WARNING]: The requested handler 'restart service' was not found in either the main handlers list nor in the
listening handlers list
changed: [localhost] =>
msg: test
TASK [test2] *****************************************************************************************************
changed: [localhost] =>
msg: test2
PLAY RECAP *******************************************************************************************************
localhost : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
I have a very simple play which saves vars and looks them up in hostvars.
- name: Set hostvars
hosts: localhost
vars:
var_one: "I am a"
var_two: "test"
tasks:
- debug: var=hostvars['localhost']['var_one']
- debug: var=hostvars['localhost']['var_two']
However, when I run this play the vars aren't defined:
PLAY [Set hostvars] ************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => {
"hostvars['localhost']['var_one']": "VARIABLE IS NOT DEFINED!"
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"hostvars['localhost']['var_two']": "VARIABLE IS NOT DEFINED!"
}
How can I save these vars in hostvars?
You can set host facts runtime using set_fact module:
---
- name: Set hostvars
hosts: localhost
tasks:
- set_fact: var_one="I am a"
- set_fact: var_two="test"
- debug: var=hostvars['localhost']['var_one']
- debug: var=hostvars['localhost']['var_two']
Quoting the documentation:
These variables will survive between plays during an Ansible run, but will not be saved across executions even if you use a fact cache.
This is where the difference between facts (variables bound to Ansible target hosts) and regular variables can be seen.
Variables are internally stored in vars structure, so you can access them with:
tasks:
- debug: var=vars['var_one']
- debug: var=vars['var_two']
Facts, on the other hand are stored in hostvars.
In either case, unless you were referring to a variable name with a dynamic name, or a fact bound to another host than the one executing the task, you can simply use the variable/fact name by using its name:
tasks:
- debug: var=var_one
- debug: var=var_two
Try using
msg=
instead of var=. As per help of debug module
var - A variable name to debug. Mutually exclusive with the 'msg' option.
- name: Set hostvars
hosts: localhost
vars:
var_one: I am a
var_two: est
tasks:
- debug: msg=hostvars['localhost']['var_one']
- debug: msg=hostvars['localhost']['var_two']
...
PLAY [Set hostvars] ************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": "hostvars['localhost']['var_one']"
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": "hostvars['localhost']['var_two']"
}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
Background information:
I need to dynamically set a variable on a set of hosts (web1) and then check the same on a different set of hosts. Once they match, I can perform further actions.
Code
My hosts file looks like this:
[web1]
web1.ttv.mydomain.com
[web1:vars]
primary_count=0
[web2]
web2.ttv.mydomain.com
[web2:vars]
secondary_count=0
[web]
web1
web2
And this is the playbook:
- hosts: web1
tasks:
- name: query primary servers
shell: psql -U widget widget -c 'SELECT COUNT(*) FROM test' -t
register: result
- set_fact: primary_count={{result.stdout}}
- hosts: web
tasks:
- name: retrieve variable from previous play
shell: echo hello
- debug: var=primary_count
This playbook produces the following results:
TASK [setup] *******************************************************************
ok: [web1.ttv.mydomain.com]
TASK [query primary servers] ****************************************************
changed: [web1.ttv.mydomain.com]
TASK [debug] *******************************************************************
ok: [web1.ttv.mydomain.com] => {
"primary_count": 0
}
TASK [set_fact] ****************************************************************
ok: [web1.ttv.mydomain.com]
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [web1.ttv.mydomain.com]
ok: [web2.ttv.mydomain.com]
TASK [retrieve variable from previous play] ************************************
changed: [web1.ttv.mydomain.com]
changed: [web2.ttv.mydomain.com]
TASK [debug] *******************************************************************
ok: [web2.ttv.mydomain.com] => {
"primary_count": "VARIABLE IS NOT DEFINED!"
}
ok: [web1.ttv.mydomain.com] => {
"primary_count": " 2"
}
Problem
Now I need a way to do the following in the second play:
run the same select statement on web2.ttv.mydomain.com
save the value to secondary_count variable
check if secondary_count matches the value of the "primary_count" on web1.mydomain.com. (Notice how right now, since I'm looping through more than just the web1 servers in play 2, I get an error about the "primary_count" not being defined on web2 servers.)
when the values match then restart various services on secondary
Questions:
How do I evaluate the "primary_count" variable on the web1 host with the matching web2 host name on? In the future my hosts file will look like this:
[web1]
web1.ttv.mydomain.com
web1.ttx.mydomain.com
[web2]
web2.ttv.mydomain.com
web2.ttx.mydomain.com
[web]
web1
web2
So I need to write some sort of an eval statement that does this:
(pseudocode)
while looping through ***ALL*** web servers
if primary_count on web1.ttv.mydomain.com matches secondary_count on web2.ttx.mydomain.com then
restart service x on web2.ttx.mydomain.com
else
wait a few seconds and repeat
end
end loop
I think the solution lies with my hosts / inventory file. Somehow I need this playbook to run on all web1 servers and all web2 servers... but I also need a way to associate web1.ttv with just web2.ttv and web1.ttx with just web2.ttx and so on.
I'm just learning ansible as I go along, so if this approach is entirely wrong, please let me know!
Thanks.
EDIT 1
On doing some research about group_vars, it looks like group_vars doesn't really help me because I still have the same problem. While looping through all web servers (play 2), the variables I set on web1 servers in play 1 are not visible from web2 servers.
EDIT 2:
- hosts: web1
tasks:
- name: query primary servers
shell: psql -U widget widget -c 'SELECT COUNT(*) FROM widget' -t
register: result
- local_action: shell echo {{ result.stdout }} > varacrossplay.txt
That fails on the local_action line with this error:
fatal: [web1.ttv.mydomain.com -> localhost]: FAILED! => {"changed": true, "cmd": "echo 2 > varacrossplay.txt", "delta": "0:00:00.001641", "end":
": "echo 2 > varacrossplay.txt", "_uses_shell": true, "chdir": null, "creates": null, "executable": null, "removes": null, "warn": true}, "mod
1: cannot create varacrossplay.txt: Permission denied", "stdout": "", "stdout_lines": [], "warnings": []}
Try with this example playbook:
[jenkins#batman ansible]$ cat testplaybook.yml
- hosts: web1
tasks:
- name: query primary servers
shell: echo "TEST"
register: result
- local_action: shell echo {{ result.stdout }} > varacrossplay.txt
- hosts: web
tasks:
- local_action: shell cat varacrossplay.txt
register: result
- set_fact: other_fact="{{ result.stdout }}"
- debug: var=other_fact
With my servers all works fine xD
[jenkins#batman ansible]$ ansible-playbook -i inventory testplaybook.yml
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [10.0.0.100]
TASK [query primary servers] ***************************************************
changed: [10.0.0.100]
TASK [command] *****************************************************************
changed: [10.0.0.100 -> localhost]
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [10.0.0.2]
ok: [10.0.0.1]
TASK [command] *****************************************************************
changed: [10.0.0.1 -> localhost]
changed: [10.0.0.2 -> localhost]
TASK [set_fact] ****************************************************************
ok: [10.0.0.1]
ok: [10.0.0.2]
TASK [debug] *******************************************************************
ok: [10.0.0.2] => {
"other_fact": "TEST"
}
ok: [10.0.0.1] => {
"other_fact": "TEST"
}
PLAY RECAP *********************************************************************
10.0.0.100 : ok=3 changed=2 unreachable=0 failed=0
10.0.0.1 : ok=4 changed=1 unreachable=0 failed=0
10.0.0.2 : ok=4 changed=1 unreachable=0 failed=0
I'm trying to access the variable called "count" from the first "play" in my playbook in the second playbook. I found some other posts here about the same issue and I thought I was following the right steps, but the code below is still failing.
The Code
- hosts: group1
tasks:
- name: count registrations on primary node
shell: psql -U widgets widgets -c 'SELECT COUNT(*) FROM location' -t
register: count
- debug: var=count.stdout
- hosts: group2
tasks:
#the line below works...
# - debug: msg={{ hostvars['myserver1.mydomain.com']['count']['stdout'] }}
# but this one fails
- debug: msg={{ hostvars['group1']['count']['stdout'] }}
This produces the following output:
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [myserver1.mydomain.com]
TASK [count registrations on node] **************************************
changed: [myserver1.mydomain.com]
TASK [debug] *******************************************************************
ok: [myserver1.mydomain.com] => {
"count.stdout": " 2"
}
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [myserver2.mydomain.com]
TASK [debug] *******************************************************************
fatal: [myserver1.mydomain.com]: FAILED! => {"failed": true, "msg": "'ansible.vars.hostvars.HostVars object' has no attribute 'can_sip1'"}
NO MORE HOSTS LEFT *************************************************************
[ERROR]: Could not create retry file 'playbooks/test.retry'. The error was: [Errno 13] Permission denied: 'playbooks/test.retry'
PLAY RECAP *********************************************************************
myserver1.mydomain.com : ok=3 changed=1 unreachable=0 failed=0
myserver2.mydomain.com : ok=1 changed=0 unreachable=0 failed=1
The other post that I referring to is found here:
How do I set register a variable to persist between plays in ansible?
It's probably something simple, but I can't see where the bug lies.
Thanks.
EDIT 1
I've also tried to use set_fact like this:
- hosts: group1
tasks:
- name: count registrations on primary node
shell: psql -U widget widget -c 'SELECT COUNT(*) FROM location' -t
register: result
- debug: var=result.stdout
- set_fact: the_count=result.stdout
- debug: var={{the_count}}
- hosts: group2
tasks:
- name: retrieve variable from previous play
shell: echo hello
- debug: var={{hostvars}}
The results I get are:
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [myserver1.mydomain.com]
TASK [count reg on primary] ****************************************************
changed: [myserver1.mydomain.com]
TASK [debug] *******************************************************************
ok: [myserver1.mydomain.com] => {
"result.stdout": " 2"
}
TASK [set_fact] ****************************************************************
ok: [myserver1.mydomain.com]
TASK [debug] *******************************************************************
ok: [myserver1.mydomain.com] => {
"result.stdout": " 2"
}
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [myserver2.mydomain.com]
TASK [retrieve variable from previous play] ************************************
changed: [myserver2.mydomain.com]
TASK [debug] *******************************************************************
ok: [myserver2.mydomain.com] => {
"<ansible.vars.hostvars.HostVars object at 0x7f3b6602b290>": "VARIABLE IS NOT DEFINED!"
}
PLAY RECAP *********************************************************************
myserver1.mydomain.com : ok=5 changed=1 unreachable=0 failed=0
myserver2.mydomain.com : ok=3 changed=1 unreachable=0 failed=0
So It looks like there are no objects in the hostvars...
EDIT 3
This is what the playbook looks like this morning.
- hosts: group1
tasks:
- name: count reg on primary
shell: psql -U widgets widgets -c 'SELECT COUNT(*) FROM location' -t
register: result
- debug: var=result.stdout
- set_fact: the_count={{result.stdout}}
- debug: var={{the_count}}
- hosts: group2
tasks:
- name: retrieve variable from previous play
shell: echo hello
- debug: var={{hostvars}}
The "debug: var={{the_count}}" line from the first play prints out the correct value for the count but it also says the VARIABLE IS NOT DEFINED... like so:
TASK [set_fact] ****************************************************************
task path: /etc/ansible/playbooks/test.yml:8
ok: [myserver1.mydomain.com] => {"ansible_facts": {"the_count": " 2"}, "changed": false, "invocation": {"module_args": {"the_count": " 2"}, "module_name": "set_fact"}}
TASK [debug] *******************************************************************
task path: /etc/ansible/playbooks/test.yml:10
ok: [myserver1.mydomain.com] => {
" 2": "VARIABLE IS NOT DEFINED!"
}
And then once I hit the second play, I still get the message
TASK [debug] *******************************************************************
task path: /etc/ansible/playbooks/test.yml:16
ok: [myserver2.mydomain.com] => {
"<ansible.vars.hostvars.HostVars object at 0x7fb077fdc310>": "VARIABLE IS NOT DEFINED!"
}
In your example, you are suggestion that I use "debug: var={{hostlers}}". If you can clarify that for me please. It looks like it's a typo.
EDIT 4:
If you take a look at Edit 3 carefully, you will see that I have implemented "debug:var={{hostvars}}" as you suggest in your answer. But it gives me the same error that the variable is not defined.
I'm not just trying to pass variables from one play to another.. but from one set of hosts to another. Notice how play 1 uses group1 and play two applies only to group2.
Register variables, like facts, are per host. The values can differ depending on the machine. So you can only use host/ip defined in the inventory as key, not the group name. I think you have already knowed this, as you marked this in code snippet 1.
In the code snippet 2, the set_fact line (- set_fact: the_count=result.stdout) actually set the key the_count to the text value result.stdout, since result.stdout is treated as plain text, not a variable. If you want to treat it as a variable, you'd better use {{ result.stdout }}. You can verify this via running the playbook with -v option.
Tasks:
set_fact: the_content1=content.stdout
set_fact: the_content2={{ content.stdout }}
Output:
TASK [set_fact] ****************************************************************
ok: [192.168.1.58] => {"ansible_facts": {"the_content1": "content.stdout"}, "changed": false}
TASK [set_fact] ****************************************************************
ok: [192.168.1.58] => {"ansible_facts": {"the_content2": "hello world"}, "changed": false}
The debug module has two possible parameter: var and msg. The var parameter expect a variable name.
debug: var={{hostvars}}
In this line, first of all, Ansible extracts the value of hostvars, since it is enclosed with two brackets. Secondly, it tries to find a variable whose name is the value of hostvars, since var parameter expects a variable name directly. That is why you see the following strange output. This means Ansible couldn't find a variable whose name is <ansible.vars.hostvars.HostVars object at 0x7f3b6602b290>.
"<ansible.vars.hostvars.HostVars object at 0x7f3b6602b290>": "VARIABLE IS NOT DEFINED!"
You can use the following:
debug: var=hostvars
debug: msg={{hostvars}}
References:
Register variables don't survive across plays with different hosts
set_fact - Set host facts from a task
debug - Print statements during execution