lineinfile ansible module skips a line - ansible

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

Related

Getting ansible_play_hosts from previous play?

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:
:
:

How to use nested loops with sequence and a list and how get around of curly braces in Ansible

I have a code to backup config using the ios_config module. I used ios_facts to get the hostname of devices and I want to use it to put the backup file in a similarly named folder and also use it in the file name itself.
In the last task of my code, I need to loop through two items - the sequence from 0 to 1(or how many items are in my inventory) as I need to access the hostname in the results and use it in the backup options, and also loop through my inventory of devices which I extracted from a csv file. I am aware of the rule of double curly braces but I do not know how to get around it.
---
- hosts: localhost
gather_facts: false
tasks:
- name: Block
block:
- name: Use CSV
csv_to_facts:
src: '{{playbook_dir}}/NEW/Inventory.csv'
vsheets:
- INFO:
- IP
- OS
- debug:
msg: '{{item.IP}}'
loop: '{{INFO}}'
- name: Create Inventory
add_host:
hostname: '{{item.IP}}'
ansible_network_os: '{{item.OS}}'
ansible_user: cisco
ansible_ssh_pass: cisco
ansible_connection: network_cli
ansible_become: yes
ansible_become_method: enable
groups: group_01
loop: '{{INFO}}'
- name: Gather Facts (IOS)
ios_facts:
register: ios_facts_loop
delegate_to: '{{item}}'
loop: "{{groups['group_01']}}"
- name: Backup Switch (IOS)
ios_config:
backup: yes
backup_options:
dir_path: "tmp/backups/{{ ios_facts_loop.results.{{item[0]}}.ansible_facts.ansible_net_hostname }}"
filename: "{{ios_facts_loop.results.item{{[0]}}.ansible_facts.ansible_net_hostname}} {{ lookup('pipe','date +%Y-%m-%d#%H:%M:%S')}}"
register: backup_ios_location
delegate_to: '{{item[1]}}'
loop:
- with_sequence: "0-{{output|length - 3}}"
- "{{groups['group_01']}}"
TLDR; for vars notation
You cannot add double curly braces inside double curly braces like in your above code. You current var reference:
ios_facts_loop.results.{{item[0]}}.ansible_facts.ansible_net_hostname
should be turned to
ios_facts_loop.results[item[0]].ansible_facts.ansible_net_hostname
# or equivalent
ios_facts_loop.results[item.0].ansible_facts.ansible_net_hostname
Meanwhile, this will only fix your current syntax error (that you didn't share in your question) as the first element in your loop is a string 'with_sequence: "0-X"' which therefore has no index 0.
Attempt to fix the logic
If I understand correctly, for your last task, you just need to loop over the results of your ios_facts register and delegate the task to the server it was taken from. Luckilly, you should already have all the info you need in ios_facts_loop.results
It is a list so you can directly loop over it
Each element should contain an item key with the actual item that was used in the previous run at time of register (i.e. one of your groups['group_01'] element).
So I would try to write your last task like this. Disclaimer this is a pure guess as I didn't see your exact datastructure.
- name: Backup Switch (IOS)
ios_config:
backup: yes
backup_options:
dir_path: "tmp/backups/{{ item.ansible_facts.ansible_net_hostname }}"
filename: "{{ item.ansible_facts.ansible_net_hostname}}{{ lookup('pipe','date +%Y-%m-%d#%H:%M:%S')}}"
register: backup_ios_location
delegate_to: '{{item.item}}'
loop: "{{ ios_facts_loop.results }}"
Going further.
I'm not really familiar with the ios_* modules but they should be really close to other stuff I use daily and I think you could really simplify your playbook taking advantage of more ansible feature (e.g. multiple plays in a playbook). I believe the following should actually do the job:
---
- name: Construct inventory from CSV
hosts: localhost
gather_facts: false
tasks:
- name: Use CSV
csv_to_facts:
src: '{{playbook_dir}}/NEW/Inventory.csv'
vsheets:
- INFO:
- IP
- OS
- name: Create Inventory
add_host:
hostname: '{{item.IP}}'
ansible_network_os: '{{item.OS}}'
ansible_user: cisco
ansible_ssh_pass: cisco
ansible_connection: network_cli
ansible_become: yes
ansible_become_method: enable
groups: group_01
loop: '{{INFO}}'
- name: Backup switches from created inventory
hosts: group_01
gather_facts: false
tasks:
- name: Get facts from network os
ios_facts:
gather_subset: all
- name: Backup Switch (IOS)
ios_config:
backup: yes
backup_options:
dir_path: "tmp/backups/{{ ansible_net_hostname }}"
filename: "{{ ansible_net_hostname }}{{ lookup('pipe','date +%Y-%m-%d#%H:%M:%S') }}"
More background on dot and brackets notation for vars
You can basically navigate a yaml datastructure with two notation which are equivalent.
the dot notation
a_list_var.index_number
a_hasmap_var.keyname
the brackets notation
a_list_var[index_number]
a_hashmap_var['key_name']
If we take the following example:
my_servers:
hostA:
ips:
- x.x.x.x
- y.y.y.y
env:
shell: bash
home: somewhere
hostB:
ips:
- a.a.a.a
- b.b.b.b
env:
shell: sh
home: elsewhere
The following notation are all strictly equivalent:
# all vars of hostA
hostA_vars: "{{ my_servers.hostA }}"
hostA_vars: "{{ my_server['hostA'] }}"
# first IP of hostB
hostB_ip: "{{ my_servers.hostB.0 }}"
hostB_ip: "{{ my_servers.hostB[0] }}"
hostB_ip: "{{ my_servers['hostB'].0 }}"
hostB_ip: "{{ my_servers['hostB'][0] }}"
As you can see, the dot notation tends to be less verbose and more readable. Meanwhile, you cannot use a variable identifier with the dot notation. So If you want to ave the home env of a variable server you would have to use:
# set a var for server
server: hostA
# all equivalent again
server_home: "{{ my_servers[server].env.home }}"
server_home: "{{ my_servers[server]['env'].home }}"
server_home: "{{ my_servers[server].env['home'] }}"
server_home: "{{ my_servers[server]['env']['home'] }}"

how to get inventory_hostanme and ansible_ssh_hosts info by running playbook on localhost

I want to write hostname and ssh_host under a specific group from an inventory file to a file in my localhost.
I have tried this:
my_role
set_fact:
hosts: "{{ hosts_list|default({}) | combine( {inventory_hostname: ansible_ssh_host} ) }}"
when: "'my_group' in group_names"
Playbook
become: no
gather_facts: no
roles:
- my_role
But now if i try to write this to a file in another task, it will create a file for the hosts in inventory file.
Can you please suggest if there is a better way to create a file containing dictionary with inventory_hostname and ansible_ssh_host as key and value respectively, by running playbook on localhost.
An option would be to use lineinfile and delegate_to localhost.
- hosts: test_01
tasks:
- set_fact:
my_inventory_hostname: "{{ ansible_host }}"
- tempfile:
delegate_to: localhost
register: result
- lineinfile:
path: "{{ result.path }}"
line: "inventory_hostname: {{ my_inventory_hostname }}"
delegate_to: localhost
- debug:
msg: "inventory_hostname: {{ ansible_host }}
stored in {{ result.path }}"
Note: There is no need to quote the conditions. Conditions are expanded by default.
when: my_group in group_names
Thanks a lot for your answer#Vladimir Botka
I came up with another requirement to write the hosts belonging to a certain group to a file, below code can help with that (adding to #Vladimir's solution).
- hosts: all
become: no
gather_facts: no
tasks:
- set_fact:
my_inventory_hostname: "{{ ansible_host }}"
- lineinfile:
path: temp/hosts
line: "inventory_hostname: {{ my_inventory_hostname }}"
when: inventory_hostname in groups['my_group']
delegate_to: localhost
- debug:
msg: "inventory_hostname: {{ ansible_host }}"

Run ansible-playbook from localhost, and using vars from hosts file

Let's say that I want to run something locally. but I want to use the vars from the hosts file, so basically - I want to do for each line something locally.
In this example, I want to use ec2_tag from ansible.
hosts file for ansible playbook run:
[any]
123.123.123.123 region=eu-region ec2_instance_id=x-xxxxxxxxxxxxxxxxx
123.123.123.124 region=eu-region ec2_instance_id=x-xxxxxxxxxxxxxxxxx
ansible-playbook:
- name: something
hosts: any
tasks:
- name: test
ec2_tag:
region: "{{ region }}"
resource: "{{ ec2_instance_id }}""
state: list
register: ec2_tags
- debug: msg={{ ec2_tags }}
How can i loop localy on [any] vars? let's say get region?
It's running now with local_action and taking the vars from the hosts file.
- name: something
hosts: any
tasks:
- name: test
local_action: ec2_tag region={{ region }} resource={{ ec2_instance_id }} state=list
register: ec2_tags
- debug: msg={{ ec2_tags }}

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

Resources