How to make `with_fileglob` conditional - ansible

I am testing with this:
#test.yml
- hosts: localhost
tasks:
- set_fact:
host_name: "project-specific"
- stat:
path: /home/user/work/infrastructure/{{ host_name }}
register: file_exists
- debug:
var: file_exists
- name: Dont create a file
template:
src: "{{item}}"
dest: /home/user/work/infrastructure/project-specific-2/
mode: 0755
with_fileglob: /home/user/work/infrastructure/{{ host_name }}/*
when: file_exists
I want to copy all the files in project-specific to project-specific-2 (as a demonstration), but only if the project-specific directory actually exists. The project-specific directory does not exist, so it should do skip this step.
This is the output:
user#laptop:~/work/infrastructure/ansible$ ansible-playbook test.yml
PLAY [localhost] *************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************
ok: [localhost]
TASK [set_fact] **************************************************************************************************************************************
ok: [localhost]
TASK [stat] ******************************************************************************************************************************************
ok: [localhost]
TASK [debug] *****************************************************************************************************************************************
ok: [localhost] => {
"file_exists": {
"changed": false,
"failed": false,
"stat": {
"exists": false
}
}
}
TASK [Dont create a file] ****************************************************************************************************************************
[WARNING]: Unable to find '/home/user/work/infrastructure/project-specific' in expected paths (use -vvvvv to see paths)
PLAY RECAP *******************************************************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
https://github.com/ansible/ansible/issues/13296
states that with_ clauses get executed before when. How do I make it so that it skips this step if the path does not exist? Or is it fine to leave it to 'execute' with a warning as no files get copied anyway?

I think you are stuck with this one. As noted in the documentation the when statement is evaluated separately for each item in the loop.
When combining Conditionals with a loop, the when: statement is processed separately for each item.
So what you're seeing is expected behavior.
The end result in your case is that the task is actually skipped when the directory can't be found, no action is performed. It just does this while printing a warning.

Bit late but the question is still relevant. My solution to this problem is to put with_fileglob tasks in a separate file and put the conditional on an include_tasks statement. E.g.
- name: Do fileglob operation
include_tasks: fileglob.yml
when:
- dir_info.stat.exists
- dir_info.stat.isdir

Related

Using ansible variable inside gathered fact list

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.

How to skip a template copy in Ansible if the file has a pattern?

Trying to only copy an Nginx config file if the destination file does not have a string in it.
I thought this would work:
- name: Copy nginx config file
template:
src: templates/nginx.conf
dest: /etc/nginx/sites-enabled/default
validate: grep -l 'managed by Certbot' %s
But this task fails if "managed by Certbot" isn't in the file and stops the playbook run.
How can I just skip the template copy if the destination file already has that pattern? Maybe there's a better way to get the same result?
Inspired from this other answer
You can check for the presence of a content in a file using the lineinfile module in check mode. Then you can use the result as a condition to your template task. The default in the condition is to cope with the case when the file does not exists and the found attribute is not in the registered result.
---
- name: Check for presence of "managed by Certbot" in file
lineinfile:
path: /etc/nginx/sites-enabled/default
regexp: ".*# managed by Certbot.*"
state: absent
check_mode: yes
changed_when: false
register: certbot_managed
- name: Copy nginx config file when not certbot managed
template:
src: templates/nginx.conf
dest: /etc/nginx/sites-enabled/default
when: certbot_managed.found | default(0) == 0
You could use the failed_when condition and base it on the fail message that validate generate — failed to validate — to act upon:
- name: Copy nginx config file
template:
src: templates/nginx.conf
dest: /etc/nginx/sites-enabled/default
validate: grep -l 'managed by Certbot' %s
failed_when:
- copy_config_file.failed
- copy_config_file.msg != 'failed to validate'
register: copy_config_file
Note: in when and *_when, having a list of conditions is like doing list.0 and list.1 and ...
Given the playbook:
- hosts: all
gather_facts: no
tasks:
- copy:
dest: templates/nginx.conf
content: "{{ content | default('some random content') }}"
- copy:
dest: /etc/nginx/sites-enabled/default
content: "blank"
- template:
src: templates/nginx.conf
dest: /etc/nginx/sites-enabled/default
validate: grep -l 'managed by Certbot' %s
failed_when:
- copy_config_file.failed
- copy_config_file.msg != 'failed to validate'
register: copy_config_file
- shell: cat templates/nginx.conf
register: template_content
failed_when: false
- shell: cat /etc/nginx/sites-enabled/default
register: file_content
failed_when: false
- debug:
var: template_content.stdout
- debug:
var: file_content.stdout
When run via:
ansible-playbook play.yml
It gives:
PLAY [all] *******************************************************************************************************
TASK [copy] ******************************************************************************************************
changed: [localhost]
TASK [copy] ******************************************************************************************************
changed: [localhost]
TASK [template] **************************************************************************************************
ok: [localhost]
TASK [shell] *****************************************************************************************************
changed: [localhost]
TASK [shell] *****************************************************************************************************
changed: [localhost]
TASK [debug] *****************************************************************************************************
ok: [localhost] => {
"template_content.stdout": "some random content"
}
TASK [debug] *****************************************************************************************************
ok: [localhost] => {
"file_content.stdout": "blank"
}
PLAY RECAP *******************************************************************************************************
localhost : ok=7 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Now, when run with
ansible-playbook play.yml -e "content='managed by Certbot\nsome other content'"
With an extra parameter to modify the content of the template, it gives:
PLAY [all] *******************************************************************************************************
TASK [copy] ******************************************************************************************************
ok: [localhost]
TASK [copy] ******************************************************************************************************
changed: [localhost]
TASK [template] **************************************************************************************************
changed: [localhost]
TASK [shell] *****************************************************************************************************
changed: [localhost]
TASK [shell] *****************************************************************************************************
changed: [localhost]
TASK [debug] *****************************************************************************************************
ok: [localhost] => {
"template_content.stdout": "managed by Certbot\nsome other content"
}
TASK [debug] *****************************************************************************************************
ok: [localhost] => {
"file_content.stdout": "managed by Certbot\nsome other content"
}
PLAY RECAP *******************************************************************************************************
localhost : ok=7 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Ansible loses variable content

I've a role with some tasks to call docker_compose. When I use a variable twice, the second task fails with error: The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'base_dir'.
It looks to me (using copy&paste) that the second call to docker_compose can't access the variable in the same scope. With other tasks (file, command, shell) it works well.
The variable is stored in roles/collabora/default/main.yml:
collabora:
base_dir: /srv/docker/collabora
and this is the inventory:
...
collabora:
hosts:
server1:
collabora_customer:
customer1:
port: 2000
customer2:
port: 2001
server2:
collabora_customer:
customer1:
port: 2000
...
This is a part of my role roles/collabora/tasks/main.yml. It iterates over all entries in host var collabora_customer.
- name: Configure Collabora instances
include_tasks: manage_collabora.yml
loop: "{{ collabora_customer | default({}) | dict2items }}"
And the manage_collabora.yml makes the work per instance.
- debug:
msg: "path: {{collabora.base_dir}}"
- name: Define location
set_fact:
instance: "{{ item }}"
collabora_path: "{{ collabora.base_dir }}/{{ item.key }}"
- name: Create "Collabora" directory
file:
path: "{{collabora_path}}"
state: directory
- name: Copy docker-compose folder "Collabora"
template:
src: "docker-compose.yml.j2"
dest: "{{collabora_path}}/docker-compose.yml"
owner: "root"
group: "root"
mode: "0640"
- name: Run docker-compose pull for "Collabora"
docker_compose:
project_src: "{{collabora_path}}"
state: present
The loop works well for the first trip per host. But the second loop failes for customer2 with error message from above in the debug message.
The funny part is, when I remove the last docker_compose task, then everything works as expected. It looks to me, as the docker_compose removes the variable from the environments - why ever.
The current result is:
PLAY [Install Collabora]
****************************************************
TASK [Gathering Facts]
****************************************************
ok: [server1]
ok: [server2]
TASK [docker_collabora : Configure Collabora instances]
****************************************************
included: roles/docker_collabora/tasks/manage_collabora.yml for server1
included: roles/docker_collabora/tasks/manage_collabora.yml for server1
included: roles/docker_collabora/tasks/manage_collabora.yml for server2
TASK [docker_collabora : debug]
****************************************************
ok: [server2] => {
"msg": "path: /srv/docker/collabora"
}
TASK [docker_collabora : Define location]
****************************************************
ok: [server2]
TASK [docker_collabora : Create "Collabora" directory]
****************************************************
ok: [server2]
TASK [docker_collabora : Copy docker-compose folder "Collabora"]
****************************************************
ok: [server2]
TASK [docker_collabora : Run docker-compose pull for "Collabora"]
****************************************************
ok: [server2]
TASK [docker_collabora : debug]
****************************************************
ok: [server1] => {
"msg": "path: /srv/docker/collabora"
}
TASK [docker_collabora : Define location]
****************************************************
ok: [server1]
TASK [docker_collabora : Create "Collabora" directory]
****************************************************
ok: [server1]
TASK [docker_collabora : Copy docker-compose folder "Collabora"]
****************************************************
ok: [server1]
TASK [docker_collabora : Run docker-compose pull for "Collabora"]
****************************************************
ok: [server1]
TASK [docker_collabora : debug]
****************************************************
fatal: [server1]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'base_dir'\n\nThe error appears to be in 'roles/docker_collabora/tasks/manage_collabora.yml': line 2, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n---\n- debug:\n ^ here\n"}
PLAY RECAP
****************************************************
server1 : ok=9 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
server2 : ok=7 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
I would expect, that this would work.

ansible loop over shell command output

I am learning ansible and I would like to know how to iterate of the results of a shell command. Here is what I have tried. I have this playbook:
[root#d61311ae17e2 /]# cat loop.yaml
---
- name: Loop Example
hosts: localhost
tasks:
- name:
command: cat /vcs.txt
register: vcs
- name: Nonsense to demo loop
template:
src: /foo.j2
dest: /foo.{{ item.1 }}
with_indexed_items: "{{ vcs }}"
The file /vcs.txt contains this:
[root#d61311ae17e2 /]# cat vcs.txt
vc-one
vc-two
vc-three
vc-four
What I was hoping would happen was the creation of four files: foo.vc-one, foo.vc-two, foo.vc-three and foo.vc-four. But what happens instead when I run ansible-playbook loop.yaml is this:
PLAY [Loop Example] *********************************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************************************
ok: [127.0.0.1]
TASK [command] **************************************************************************************************************************************************
changed: [127.0.0.1]
TASK [Nonsense to demo loop] ************************************************************************************************************************************
fatal: [127.0.0.1]: FAILED! => {"msg": "with_indexed_items expects a list"}
to retry, use: --limit #/loop.retry
PLAY RECAP ******************************************************************************************************************************************************
127.0.0.1 : ok=2 changed=1 unreachable=0 failed=1
I needed to do this with_indexed_items: "{{ vcs.stdout.split('\n')}}"
If you need stdout on a line-by-line basis, with_indexed_items: "{{ vcs.stdout_lines }}" is equivalent to .split('\n') and likely simpler/clearer.

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