Improving use of add_host in ansible [duplicate] - ansible

This question already has answers here:
Ansible - Write multiple line output to file
(2 answers)
local_action: shell error concatenate files
(1 answer)
Closed 4 years ago.
I want to put hosts based on info I found on it in a in-memory group that I'll use on a later play.
I use this way based on writing the hostname of the target on the control node and then using this list to loop over with add_host, as add_host "bypasses the host loop and only runs once for all the hosts in the play" (I quote the documentation).
simplified example:
---
- hosts: servers
tasks:
- name: check if this server has foo role based on file presence
stat:
path: /tmp/foo
register: role
- name: write server name in file on control node
lineinfile:
path: /tmp/servers_foo.txt
state: present
line: "{{ inventory_hostname }}"
delegate_to: 127.0.0.1
when: role.stat.isfile is defined and role.stat.isfile
- name: assign target to group
add_host:
name: "{{ item }}"
groups:
- foo
with_lines: cat /tmp/servers_foo.txt
- hosts: foo
tasks:
- name: test which servers are part of foo
ping:
...
The problem of this playbook is the lineinfile task seems buggy because not all servers are written in the control node file.
…
TASK [lineinfile] ******************************************************************************
changed: [toto.foo -> 127.0.0.1]
changed: [tata.foo -> 127.0.0.1]
changed: [titi.foo -> 127.0.0.1]
changed: [tutu.foo -> 127.0.0.1]
TASK [assign target to group]
changed: [toto.foo] => (item=tata.foo)
changed: [toto.foo] => (item=tutu.foo)
…
this is confirmed by checking the file content on the control node
cat /tmp/servers_foo.txt
tata.foo
tutu.foo
I'm not sure why I have this problem (race condition on file access?) but to workaround this I added a serial: 1 to the first play, so this way it works but this is terribly slow if I have tens of servers and different check to implement.
Do you have a better and faster implementation of such use case or how could I fix the lineinfile task to not having the problem described.

Related

Passing hostname to ansible playbook through extravars

I have to pass the host on which the Ansible command will be executed through extra vars.
I don't know in advance to which hosts the tasks will be applied to, and, therefore, my inventory file is currently missing the hosts: variable.
If I understood from the article "How to pass extra variables to an Ansible playbook" correctly, overwriting hosts is only possible by having already composed groups of hosts.
From the post Ansible issuing warning about localhost I gathered that referencing hosts to be managed in an Ansible inventory is a must, however, I still have doubts about it since the usage of extra vars was not mentioned in the given question.
So my question is: What can i do in order to make this playbook work?
- hosts: "{{ host }}"
tasks:
- name: KLIST COMMAND
command: klist
register: klist_result
- name: TEST COMMAND
ansible.builtin.shell: echo hi > /tmp/test_result.txt
... referencing hosts to be managed in an Ansible inventory is a must
Yes, that's the case. Regarding your question
What can I do in order to make this playbook work? (annot. without a "valid" inventory file)
you could try with the following workaround.
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- add_host:
hostname: "{{ target_hosts }}"
group: dynamic
- hosts: dynamic
become: true
gather_facts: true
tasks:
- name: Show hostname
shell:
cmd: "hostname && who am i"
register: result
- name: Show result
debug:
var: result
A call with
ansible-playbook hosts.yml --extra-vars="target_hosts=test.example.com"
resulting into execution on
TASK [add_host] ***********
changed: [localhost]
PLAY [dynamic] ************
TASK [Show hostname] ******
changed: [test.example.com]
In any case it is recommended to check how to build your inventory.
Further Documentation
add_host module – Add a host (and alternatively a group) to the ansible-playbook in-memory inventory

Why does the ansible find module work differently in a role when on localhost?

I have a role, called newrole. It sits in my ansible directory:
ansible
- roles
- newrole
- tasks
main.yml
- templates
template.j2
playbook1.yml
My playbook is defined quite simply:
---
- hosts: host1
roles:
- newrole
And the main.yml in tasks for the newrole:
- name: Find templates in the role
find:
path: "templates"
pattern: "*.j2"
register: result
- debug:
msg: "{{ result }}"
I then invoke the playbook from the ansible directory: ansible-playbook playbook1.yml
This works as expected: it runs on host1 and matches the template.j2 file. However, if I change the playbook ever so slightly to run on localhost instead of host1 (and specify connection: local), things go off the rail. It says it can't find the path templates. If I instead put in the path from the ansible directory (that I am running from), e.g. roles/newrole/templates, then it matches the template.j2 file. However, I can't figure out why that difference is occurring.
Aren't roles supposed to be providing a insulation from the directory structure I am running them from?
Even more confusing is that even on localhost, the template module (ansible.builtin.template) will search for templates only in the templates directory of the newrole (as I would originally expect). Can someone explain this behavior difference?
Is there a way to get the find module in my example to start searching at the root of the role regardless of localhost or not invocation?
When Ansible runs on nodes, it does not ships the whole playbook, nor the whole role on the said node, it packages a Python script that get send to the node and executed there.
So, if you do not send your files over to a node, a task executed on a node is not going to find the said file.
Now, for what you do need, you don't need a find task, you need the fileglob lookup, as lookups do tend to execute on the controller, rather than on the nodes.
Here is an example of usage:
- debug:
var: lookup('fileglob', 'templates/*.j2')
If we go beyond your localised issue, then, you can also use a delegation to the controller, in order to achieve the same.
In your case, that would translate in:
- find:
path: "templates"
pattern: "*.j2"
delegate_to: localhost
run_once: true
register: result
Given the playbook:
- hosts: node1
gather_facts: no
tasks:
- debug:
var: lookup('fileglob', 'templates/*.j2')
- find:
path: "templates"
pattern: "*.j2"
delegate_to: localhost
run_once: true
register: result
- debug:
var: result.files.0.path
This yields:
PLAY [node1] ******************************************************************
TASK [debug] ******************************************************************
ok: [node1] =>
lookup('fileglob', 'templates/*.j2'): /absolute/path/to/templates/template.j2
TASK [find] *******************************************************************
ok: [node1 -> localhost]
TASK [debug] ******************************************************************
ok: [node1] =>
result.files.0.path: templates/template.j2

Merging multiple tasks to reduce the output

We are trying to get lesser output while executing a playbook on multiple OS flavours. But unable to find a solution hence posting is here for a better answer.
As we get multiple task executed, is it possible to merge into one. We are collecting the output in a file & then will veryfy the same with different tags.
- name: verify hostname
block:
- name: read hostname [PRE]
shell: hostname
register: hostname
- name: set fact [hostname]
set_fact:
results_pre: "{{ results_pre | combine({'hostname': hostname.stdout.replace(\"'\", '\"')|quote }) }}"
- name: write hostname
copy:
dest: "{{ remote_logs_path }}/{{ ansible_ssh_host }}/pre/hostname"
content: "{{ hostname.stdout }}"
tags:
- pre
Current output
TASK [role : read hostname [PRE]] ***************************************************************************
changed: [ip]
TASK [role : set fact [hostname]] ***************************************************************************
ok: [ip]
TASK [role : write hostname] ********************************************************************************
changed: [ip]
Required Output
TASK [role : Hostname Collected] ********************************************************************************
changed: [ip]
Generally, it's a bad idea to parse Ansible output. You may get some runtime warnings or unexpected additional lines.
If you really want to stick to Ansible output, there are a so-called callback plugins, you may try to implement your own if you want.
If you need some report from Ansible playbook, the common pattern is to have a separate task, which reports into a file (usually, on a controller host, using delegate: localhost).
Finally, if you want to check for idempotence, Molecule provides this feature.

Trying to get a task having delegate_to to show inventory alias instead of hostname in the output of a playbook

I have a task in my playbook to take backups of a directory on each remote host itself, as :
- name: Copy files to backup
synchronize:
src: /opt/myDir/
dest: /opt/myBackupDir/
archive: yes
ignore_errors: no
delegate_to: "{{ inventory_hostname }}"
register: sync_out
And my inventory is like :
myWeb1 ansible_host=prodvm1 ansible_user=testuser
myApp1 ansible_host=prodvm2 ansible_user=testuser
myApp2 ansible_host=prodvm3 ansible_user=testuser
The issue is that the output shown is like below :
TASK [Copy files to backup] **********************************************************************************************************************************************************
ok: [myWeb1 -> prodvm1]
ok: [myApp1 -> prodvm2]
ok: [myApp2 -> prodvm3]
Since I am delegating the task there are two fields shown in the output, separated by the arrow mark, but I would like to have it show the name / alias specified in the inventory , i.e., myApp1/myWeb1 instead of the actual hostname ( prodvm* ) after the arrows ( to avoid showing the hostname / IPs ).
I tried to use debug module to see what ansible is evaulating inventory_hostname to , but it gives the expected result, i.e., myWeb1.
How can I get similar behaviour when using the delegate_to module ?
It's really annoying behavior for Ansible. Delegation always 'cuts to the point' of where things happens.
If it's really annoy you, you can try disable delegation and use ansible_host trick, but at my opinion, delegation is better (even with junk in output).
This is the ansible_host trick:
- name: Copy files to backup
synchronize:
src: /opt/myDir/
dest: /opt/myBackupDir/
archive: true
var:
ansible_host: '{{ hostvars[name_where_its_delegated].ansible_host }}'
register: sync_out
I warn you again, delegation is better than this.

Ansible: Limit a task in a playbook to localhost

I want to restrict an Ansible play to a specific host
Here's a cut down version of what I want:
- hosts some_host_group
tasks:
- name: Remove existing server files
hosts: 127.0.0.1
file:
dest: /tmp/test_file
state: present
- name: DO some other stuff
file:
...
I want to (as an early task), remove a local directory (I've created a file in the example as it's a more easily observed test). I was under the impression that I could limit a play to a set of hosts with the "hosts" parameter to the task -
but I get this error:
ERROR! 'hosts' is not a valid attribute for a Task
$ansible --version
ansible 2.3.1.0
Thanks.
PS I could wrap the ansible in a shell fragment, but that's ugly.
You should use delegate_to or local_action and tell Ansible to run the task only once (otherwise it will try to delete the directory as many times as target hosts in your play, although it won't be a problem).
You should also use absent not present if you want to remove directory, as you stated.
- name: Remove existing server files
delegate_to: 127.0.0.1
run_once: true
file:
dest: /tmp/test_file
state: absent
There are syntax errors in your playbook, have a look at Ansible Intro, Local Playbooks and Delegation.
- hosts: some_host_group
tasks:
- name: Remove existing server files
- hosts: localhost
tasks:
- file:
dest: /tmp/test_file
state: present
- name: DO some other stuff
file:

Resources