Ansible: include_role on a loop running unexpected number of times - ansible

I am trying to use include_role with items
---
- hosts: cluster
tasks:
- block:
- name: Execute test role
include_role:
name: testrole
with_items:
- 'one'
...
My role is
---
- name: Just debugging
debug:
...
The issue is that it seems that the role is being ran by each host X times per item where X is the number of hosts.
PLAY [cluster] *****************************************************************
TASK [setup] *******************************************************************
ok: [thisNode]
ok: [dww]
TASK [Execute test role] *******************************************************
TASK [testrole : Just debugging] ***********************************************
ok: [thisNode] => {
"msg": "Hello world!"
}
ok: [dww] => {
"msg": "Hello world!"
}
TASK [testrole : Just debugging] ***********************************************
ok: [thisNode] => {
"msg": "Hello world!"
}
ok: [dww] => {
"msg": "Hello world!"
}
PLAY RECAP *********************************************************************
dww : ok=3 changed=0 unreachable=0 failed=0
thisNode : ok=3 changed=0 unreachable=0 failed=0
Why is this happening and how can I fix it?
Ansible hosts:
[cluster]
thisNode ansible_host=localhost ansible_connection=local
dww
I cannot delegate the task because, in the real role, the task must be executed in each of the hosts.
Using allow_duplicates: no still outputs the same.
---
- hosts: cluster
tasks:
- name: Execute test role
include_role:
name: testrole
allow_duplicates: False
with_items:
- 'one'
...

As a workaround you can add allow_duplicates: false to prevent Ansible from running the same role twice with the same parameters.
Clearly the module is looped twice: once with hosts, the other time with the specified items. As it is supposed to runs the action against all hosts, the outer loop gets executed twice.
It's a new module in preview state and this behaviour should probably be files as an issue.
Ansible has an internal parameter BYPASS_HOST_LOOP to avoid such situations and this mechanism should probably be used by this module.

I had the same problem and allow_duplicates: False did not change anything.
It seems that setting serial: 1 on the play and it somehow solved it. This is a workaround that probably will work for a small number of hosts.

Related

How to trigger handlers (notify) when using import_tasks?

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

Ansible not setting environment variable correctly

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

Ansible import_playbook fails with variable undefined error

I am trying to conditionally import a playbook by dynamically generating the name of the playbook to import.
I have the following 2 files:
root#ubuntu:~/test# ls -ltr
total 8
-rw-r--r-- 1 root root 117 sij 2 12:07 child_playbook_1.yaml
-rw-r--r-- 1 root root 210 sij 2 12:11 start.yaml
root#ubuntu:~/test#
start.yaml:
---
- name: main playbook
hosts: localhost
tasks:
- set_fact:
var: "child_playbook"
- debug:
msg: "{{ var }}"
- import_playbook: "{{ var + '_1.yaml' }}"
when: var is defined
child_playbook_1.yaml:
---
- name: child_playbook
hosts: localhost
tasks:
- debug:
msg: "Message from the child playbook"
When I execute start.yaml, I get this as output:
root#ubuntu:~/test# ansible-playbook start.yaml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
ERROR! 'var' is undefined
root#ubuntu:~/test#
Why is var not seen and why do I get this message?
How to overcome this?
To put things into a bit more perspective, the 'var' variable in start.yaml is being dynamically read which means that the name of the playbook to be imported will also be dynamic.
var is a fact defined for localhost (i.e. the only host in the play) in the first play. So it does not exist "globally", only for the given host.
Moreover, imports are made statically at time of playbook parsing (by opposition to includes which are dynamic but to not exist for playbooks).
One could then try to use the var defined for localhost by referencing hostvars['localhost'].var but:
set_fact has not yet run since it is an import.
Moreover hostvars is not yet defined at time of import.
Lastly, you are miss-interpreting how a when clause is actually working. Basically, the condition is passed to all the tasks contained in your included/imported object. So the imported object must exist. If you use an undefined var to get its name, it will always fire an error.
The only solution I currently see to pass a variable playbook name is to use an extra var on the command line. If you want to be able to 'skip' the import when no variable is defined, you could default to an empty playbook.
Here is a dummy empty.yml playbook
---
- name: Dummy empty playbook
hosts: localhost
gather_facts: false
For my tests, I used the same child playbook as in your question except I disabled facts gathering not needed here:
---
- name: child_playbook
hosts: localhost
gather_facts: false
tasks:
- debug: msg="Message from the child playbook"
The new main playbook start.yml looks like this:
---
- name: Play written in start.yml directly
hosts: localhost
gather_facts: false
tasks:
- debug:
msg: "Message from start playbook"
- import_playbook: "{{ var is defined | ternary( var | default('') + '_1.yml', 'empty.yml') }}"
Note than even though we use var is defined as a condition, var will still be interpreted in the ternary filter in all cases (defined or not). So we have to use a default value to make sure we don't have an error when we don't pass a value.
We can now call the playbook with or without an extra var. Here is the result:
$ ansible-playbook start.yml
PLAY [Play written in start.yml directly] *************************************************************************************************************************************************************************
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Message from main playbook"
}
PLAY [Dummy empty playbook] ***************************************************************************************************************************************************************************************
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$ ansible-playbook start.yml -e var=child_playbook
PLAY [Play written in start.yml directly] *************************************************************************************************************************************************************************
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Message from main playbook"
}
PLAY [child_playbook] *********************************************************************************************************************************************************************************************
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Message from the child playbook"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Ansible: iterating and pairing inventory items with variables

This is directly related to the following link question, and the answer from larsks, that I tried, but it does not work: "how to loop through inventory and assign value in ansible"
I was trying to do the same, and I tested to rename 2 VMs controlled by Ansible, but I get errors like the next one when I try to run the Playbook (ansible-playbook -i hosts test_iterate_win.yml -vvv), I would say that it is literally taking by name 'System.Object[]' instead of, for example, wCloud2:
failed: [oldVM2] (item=[u'oldVM2', u'wCloud2']) => {
"ansible_loop_var": "item",
"changed": false,
"item": [
"oldVM2",
"wCloud2"
],
"msg": "Failed to rename computer to 'System.Object[]': Skip computer 'oldVM2' with new name 'System.Object[]' because the new name is not valid. The new computer name entered is not properly formatted. Standard names may contain letters (a-z, A-Z), numbers (0-9), and hyphens (-), but no spaces or periods (.). The name may not consist entirely of digits, and may not be longer than 63 characters.",
"old_name": "oldVM2",
"reboot_required": false
}
In my inventory file:
[windows]
oldVM1 ansible_host=192.168.122.6
oldVM2 ansible_host=192.168.122.139
My Playbook:
---
- hosts: windows
gather_facts: false
vars:
hostnames:
- wCloud1
- wCloud2
tasks:
- name: change hostname
win_hostname:
name: "{{ item }}"
loop: "{{ groups.windows|zip(hostnames)|list }}"
What I am doing wrong?
TL;DR;
I would say, you are making this super complex for yourself for nothing, when there could be a simple solution about it.
Your task could be easily resolved by just using host variables in your inventory:
[windows]
oldVM1 ansible_host=192.168.122.6 newName=wCloud1
oldVM2 ansible_host=192.168.122.139 newName=wCloud2
Then your playbook is as easy as:
---
- hosts: windows
gather_facts: false
tasks:
- name: change hostname
win_hostname:
name: "{{ newName }}"
Now, the reason why your attempt didn't work, is actually coming from a bit of a misconception you have on task(s) run on multiple hosts by Ansible, I would say.
Namely, when Ansible have a task (or a set of tasks) to run on multiple hosts, one and only one task need to be defined.
Based on the said task, when the inventory hosts is actually a group of hosts, the task will be run on the host 1, then the host 2, ... until the host n, before it goes to run the next task (if any).
Nota: still, don't take this for granted, there has been know issues where Ansible would not follow the order of definition of the hosts in the inventory (see: https://github.com/ansible/ansible/issues/34861), so really it could end up being host 2, host n, host 1.
Consider this playbook with the above inventory:
---
- hosts: windows
gather_facts: false
tasks:
- name: change hostname
debug:
msg: '{{ newName }}'
- name: another task
debug:
msg: 'some example'
The output of it will be
$ansible-playbook test.yml
PLAY [windows] *****************************************************************
TASK [change hostname] *********************************************************
ok: [oldVM1] => {
"msg": "wCloud1"
}
ok: [oldVM2] => {
"msg": "wCloud2"
}
TASK [another task] ************************************************************
ok: [oldVM1] => {
"msg": "some example"
}
ok: [oldVM2] => {
"msg": "some example"
}
PLAY RECAP *********************************************************************
oldVM1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
oldVM2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Where you can clearly see that the first task (named, change hostname) is processed on all hosts before Ansible could move forward to other tasks in the play.
And really what your System.Object[] error means is that you are trying to feed an object (namely a list) in the win_hostname module's name, that tries to convert it as string somehow and fails badly, because, here is the list that your item variable contains:
[
"oldVM1",
"wCloud1"
]

ansible: accessing register variables from other plays within same playbook

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

Resources