Getting ansible_play_hosts from previous play? - ansible

I have an ansible playbook that interacts with the management card in a bunch of servers, and then produces a report based on that information. Structurally it looks like:
- hosts: all
tasks:
- name: do something with redfish
uri:
...
register: something
- hosts: localhost
tasks:
- name: produce report
template:
...
loop: "{{ SOME_LIST_OF_HOSTS }}"
Originally, the template task in the second was looping over groups.all, but that causes a number of complications if we limited the target hosts using -l on the command line (like ansible-playbook -l only_cluster_a ...). In that case, I would like the template task to loop over only the hosts targeted by the first play. In other words, I want to know ansible_play_hosts_all from the previous play.
This is what I've come up with:
- hosts: all
gather_facts: false
tasks:
- delegate_to: localhost
delegate_facts: true
run_once: true
set_fact:
saved_play_hosts: "{{ ansible_play_hosts_all }}"
...other tasks go here...
- hosts: localhost
gather_facts: false
tasks:
- debug:
msg:
play_hosts: "{{ saved_play_hosts }}"
Is that the best way of doing this?

you could use add_host module: at the end of first play you add a task:
- name: add variables to dummy host
add_host:
name: "variable_holder"
shared_variable: "{{ saved_play_hosts }}"
and you could trap the value in second play:
- name: second play
hosts: localhost
vars:
play_hosts: "{{ hostvars['variable_holder']['shared_variable'] }}"
tasks:
:
:

Related

Where to put (-) hyphen in yaml ansible play books?

I am very confused, in the play book where actually we put single hyphen (-). I found similar threads here, but still confused, so decided to draft one new. I have read it will be used to indicate start of a list item. again i have difficulty in understanding where is the start of list and where is start of dictionary.
Can some experts explain me where should i put hyphen in below code. and why is that?
---
connection: local
gather_facts: false
hosts: rtr
tasks:
name: "read configs"
read_csv:
path: "{{ aws_config }}"
register: aws_requests
run_once: true
debug:
msg: "{{ aws_requests.list }}"
name: "display awsconfigs requests"
run_once: true
name: "set awsconfigs requests"
run_once: true
set_fact:
aws_configs: "{{ aws_requests.list }}"
name: "build template"
template:
dest: "{{ config_filename }}"
lstrip_blocks: true
src: "{{ template }}"
I recommend that you read the "Intro to playbooks", which should answer your questions, but below is a summary.
As you noted correctly, hyphens are list items in YAML. YAML documents start with ---, which is why there are hyphens at the start of the file.
The starting point for any Ansible playbook is the playbook itself in the file. The playbook file itself may contain one or more so-called "plays", each as its own list element. Each play typically contains a hosts and a tasks part. In many playbooks, there is just one "play", so your typical minimal playbook looks like this:
---
- hosts: webservers
tasks:
- name: Task 1
...
As you can see above, each "play" then has a list of tasks, each starting with a hyphen. So in the following example there are two tasks, each with a name and the module (yum and service in this case):
---
- hosts: webservers
tasks:
- name: ensure apache is at the latest version
yum:
name: httpd
state: latest
- name: ensure apache is running
service:
name: httpd
state: started
Each Ansible module has different arguments, so you'll need to check the modules documentation for each one how to specify these arguments.
So the correct version for your playbook above would look like this:
---
- connection: local
gather_facts: false
hosts: rtr
tasks:
- name: "read configs"
read_csv:
path: "{{ aws_config }}"
register: aws_requests
run_once: true
- name: "display awsconfigs requests"
debug:
msg: "{{ aws_requests.list }}"
run_once: true
- name: "set awsconfigs requests"
run_once: true
set_fact:
aws_configs: "{{ aws_requests.list }}"
- name: "build template"
template:
dest: "{{ config_filename }}"
lstrip_blocks: true
src: "{{ template }}"

Ansible print created hosts

I wrote an Ansible Playbook that creates multiple VMs. The Playbook is split in two files. Main.yaml and vars.yaml. Its creating the VMs and it seems to work nicely. Im not getting any errors so i assume it successfully added the created hosts to the Inventory. I want to check if the created hosts were added to the Inventory. How can i print/list the Hosts of the inventory? My goal is to run scripts at a later stage on the created VMs. Thanks.
**Main.yaml**
#########CREATING VM#########
---
- hosts: localhost
vars:
http_port: 80
max_clients: 200
vars_files:
- vars.yaml
tasks:
- name: create VM
os_server:
name: "{{ item.name }}"
state: present
image: "{{ item.image }}"
boot_from_volume: True
security_groups: ssh
flavor: "{{ item.flavor }}"
key_name: mykey
region_name: "{{ lookup('env', 'OS_REGION_NAME') }}"
nics:
- net-name: private
wait: yes
register: instances
with_items: "{{ instance_definitions }}"
############################################
- name: whait 15 seconds
pause: seconds=15
when: instances.changed
######DEBUG#################################
- name: display results
debug:
msg: "{{ item }}"
with_items: "{{ instances.results }}"
############################################
- name: Add new VM to ansible Inventory
add_host:
name: "{{ item.server.name}}"
ansible_host: "{{item.server.public_v4}}"
ansible_user: "{{ansible_user}}"
ansible_ssh_common_args: -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
groups: just_created
with_items: "{{ instances.results }}"
**vars.yaml**
---
instance_definitions:
- { name: Debian Jessie, image: Debian Jessie 8, flavor: c1.small, loginame: debian }
- { name: Debian Stretch, image: Debian Stretch 9, flavor: c1.small, loginame: debian }
This is what magic variables are for.
All your hosts will be in a list:
groups['just_created']
The example below illustrates how to create in-memory hosts and list them. The magic sauce is that add_hosts needs to be in a separate play.
---
- name: adding host playbook
hosts: localhost
connection: local
tasks:
- name: add host to in-memory inventory
add_host:
name: awesome_host_name
groups: in_memory
- name: checking hosts
hosts: in_memory
connection: local
gather_facts: false
tasks:
- debug: var=group_names
- debug: msg="{{ inventory_hostname }}"
- debug: var=hostvars[inventory_hostname]
You can print the the content of the inventory file using from the playbook using:
- debug: msg="the hosts are is {{lookup('file', '/etc/ansible/hosts') }}"
Alternatively, you can list the hosts from command line using:
ansible --list-hosts all
or use this command from the playbook:
tasks:
- name: list hosts
command: ansible --list-hosts all
register: hosts
- debug:
msg: "{{hosts.stdout_lines}}"

how to iterate over all specified hosts

I have a playbook that is supposed to create a config file for all specified hosts, on my monitoring_sever.
- hosts: all
gather_facts: True
hosts: monitoring_server
tasks:
- command: touch {{ hostvars[item]['ansible_fqdn'] }}
with_items: "{{ groups['all'] }}"
I execute the playbook with ansible-playbook main.yml -l "new_client, new_client2, monitoring_server"
The Resulting files on the monitoring server should look like the following:
client1.conf client2.conf
But I get an error about missing quotes, I've tried all sorts of syntax changes but I can't seem to find the problem.
Updated:
- hosts: all
gather_facts: True
tasks:
- file:
path: "{{ hostvars[item]['ansible_fqdn'] }}"
state: touch
delegate_to: host_name # Delegate task to specific host
with_items: "{{ groups['all'] }}"
You have typos in your original playbook.
with:items should be with_items
items should be item
Usage of delegate_to: http://docs.ansible.com/ansible/playbooks_delegation.html#delegation
As long as you are targeting all hosts, you don't have to make a loop as Ansible executes tasks on all targeted hosts unless excluded by condition.
On the other hand, I would suggest using file module instead of command to touch the file.
- hosts: all
tasks:
- name: Touch a file
file:
path: "{{ ansible_fqdn }}"
state: touch
PS. I assumed ansible_fqdn is a host var you defined for each host.
You need to fix:
with_items: instead of with:items:
item instead of items
a single hosts: declaration in each item on the list of plays
This should work for your case:
---
- hosts: all
gather_facts: true
- hosts: monitoring_server
tasks:
- command: touch {{ hostvars[item]['ansible_fqdn'] }}
with_items: "{{ groups['all'] }}"
Alternatively you can use delegate_to: localhost and completely drop the loop as well as the reference to hostvars:
---
- hosts: all
gather_facts: true
tasks:
- command: touch {{ ansible_fqdn }}
delegate_to: localhost

lineinfile ansible module skips a line

I have a need to know the index of host names in the inventory. I am using the below code to create a variable file that I can use in a subsequent play book
- name: Debug me
hosts: hosts
tasks:
- debug: msg="{{ inventory_hostname }}"
- debug: msg="{{ play_hosts.index(inventory_hostname) }}"
- local_action: 'lineinfile create=yes dest=/tmp/test.conf
line="host{{ play_hosts.index(inventory_hostname) }}=
{{ inventory_hostname }}"'
I have the following inventory file
[hosts]
my.host1.com
my.host2.com
Now when I run this, the test.conf that gets generated under /tmp sometimes has both hostnames like this
host1= my.host2.com
host0= my.host1.com
when I run the same playbook a few times each time emptying the test.conf before running. quite a few times the file only has one entry
host1= my.host2.com
or
host0= my.host1.com
how come the same ansible playbook behaving differently?
I believe the issue is your running two threads against different hosts, and using local_action is not thread safe.
Try using the serial keyword:
- name: Debug me
hosts: hosts
serial: 1
tasks:
- debug: msg="{{ inventory_hostname }}"
- debug: msg="{{ play_hosts.index(inventory_hostname) }}"
- local_action: 'lineinfile create=yes dest=/tmp/test.conf
line="host{{ play_hosts.index(inventory_hostname) }}=
{{ inventory_hostname }}"'
Edit: A better way to do this if just trying to operate on the list of host in inventory on the localhost, would be to avoid doing the action on the host and using local_action in the first place.
- name: Debug me
hosts: localhost
tasks:
- lineinfile:
create: yes
dest: /tmp/test.conf
line: "host{{ groups['hosts'].index(item)}}={{ item }}"
with_items: " {{ groups['hosts'] }}"
This will get you the results you desire. Then you can add another play to do operations against the hosts themselves.
The solution I am trying to avoid problems with race conditions with non-thread safe Local_action: lineinfile to write gathered data to local file. Split it across 2 different plays in the same file.
eg:
- name: gather_date
hosts: all
any_errors_fatal: false
gather_facts: no
tasks:
- name: get_Aptus_device_count_list
shell: gather_data.sh
become: true
register: Aptus_device_count_list
changed_when: false
- name: Log_gathered_date
hosts: all
any_errors_fatal: false
gather_facts: no
tasks:
- name: log_gathered_info
local_action:
module: lineinfile
dest: /home/rms-mit/MyAnsible/record_Device_count_collection.out
line: "\n--- {{ inventory_hostname }} --- \n
{{ Aptus_device_count_list.stdout }} \n.\n---\n"
changed_when: false

Ansible: how to call module `add_host` for all hosts of the play

I'm creating a playbook with this play:
On hosts hypervisors:
retrieve list of virtual machines from all hosts
use module add_host to add all of them in a new inventory group called guests
My inventory:
[hypervisors]
host1
host2
My playbook:
- hosts: hypervisors
- shell: virsh list | awk 'NR>2' | awk '{print $2}'
register: result_virsh
- add_host:
name: "{{ item }}"
group: "guests"
with_items: "{{ result_virsh.stdout_lines }}"
Module add_host bypasses the play host loop and only runs once for all the hosts in the play.
Then it is called once (for host1), it's a particular case of the use of this module (see link above), as if the variable run_once was implicitly fixed to true.
How can I use it for all hosts in group hypervisors ?
EDIT: Example to reproduce it on your computer with only localhost
Create file /tmp/host1_test to simulate a return of guests vm1 and vm2:
vm1
vm2
Create file /tmp/host2_test to simulate a return of guests vm3 and vm4:
vm3
vm4
Use this inventory (test_add_host.ini) with two hosts, both with fixed IP address 127.0.0.1:
[hypervisors]
host1 ansible_host=127.0.0.1 test_filename=/tmp/host1_test
host2 ansible_host=127.0.0.1 test_filename=/tmp/host2_test
Use this playbook (test_add_host.yml):
- hosts: hypervisors
gather_facts: no
tasks:
- shell: "cat {{ test_filename }}"
register: result_virsh
- add_host:
name: "{{ item }}"
group: "guests"
with_items: "{{ result_virsh.stdout_lines }}"
- hosts: guests
gather_facts: no
tasks:
- local_action: ping
Call this playbook locally with command:
ansible-playbook -c local -i test_add_host.ini test_add_host.yml
First play call hosts host1 and host2
Second play call hosts vm1 and vm2
What should I do to call all hosts (vm1, vm2, vm3 and vm4) in second play ?
As you noted, there's a thing about add_host: BYPASS_HOST_LOOP = True.
So it's a kind of forced run_once.
If you don't mind running over hypervisors in sequential manner, you can simply use serial: 1:
- hosts: hypervisors
serial: 1
tasks:
- shell: virsh list | awk 'NR>2' | awk '{print $2}'
register: result_virsh
- add_host:
name: "{{ item }}"
group: "guests"
with_items: "{{ result_virsh.stdout_lines }}"
This ensures that every play batch consists of only one host, so add_host executes for every host.
If you don't want to run the play serially you can aggregate the results with ansible_play_hosts and map. The results can be used in the next play.
- hosts: all
gather_facts: false
tasks:
- shell: virsh list | awk 'NR>2' | awk '{print $2}'
register: result_virsh
changed_when: false
- add_host:
name: "{{ item }}"
group: guests
changed_when: false
loop: "{{ ansible_play_hosts | map('extract', hostvars, 'result_virsh') | map(attribute='stdout_lines') | flatten }}"
- hosts: guests
gather_facts: false
tasks:
- ping:
This answer was derived from Ansible: Accumulate output across multiple hosts on task run.
I solved this problem (with my localhost example) with following playbook. This solution is very complex, if you have a simpler one, shared it!
I didn't want to use dynamic inventories
# Get list of virtual machines in hostvars[inventory_hostname].vms
- hosts: hypervisors
gather_facts: no
tasks:
- shell: "cat {{ test_filename }}"
register: result_virsh
- set_fact:
vms: "{{ result_virsh.stdout_lines }}"
# Remove previous vm_hosts file
- hosts: localhost
gather_facts: no
tasks:
- file:
path: /tmp/vm_hosts
state: absent
# Build file vm_hosts with list of virtual machines in serial (working in parallele with same file cause some troubles)
- hosts: hypervisors
gather_facts: no
serial: 1
tasks:
- block:
- file:
path: /tmp/vm_hosts
mode: 0644
state: touch
run_once: yes
- lineinfile:
dest: /tmp/vm_hosts
line: '{{ item }}'
with_items: "{{ hostvars[inventory_hostname].vms }}"
delegate_to: localhost
# Add list of virtual machines from file vm_hosts to in-memory inventory
- hosts: localhost
gather_facts: no
tasks:
- add_host:
name: "{{ item }}"
group: "guests"
with_lines: cat /tmp/vm_hosts
- hosts: guests
gather_facts: no
tasks:
- local_action: ping

Resources