ansible assign shell results specific hosts in hostvars using with_items - ansible

Basically i want modify an existing hostvars.
I have a dynamically generated array of hosts named "flash_hosts"
flash_hosts = ['host1', 'host2']
and a shell script which looks up a value for each host.
In following non-working code i try to assign each host the specific result of the script
- name: Assign values to to host var
shell: "get-assigned-value.sh {{ item }}"
register: "{{ hostvars[item].mac=stdout }}"
with_items: "{{ flash_hosts }}"
How can i make this work in in ansible? Basically i understand that register will not allow me to assign the value to hostvars directly, but how can this be solved then, as i need to iterate over the hosts?

Use set_fact to debug your register results on host wise..
- name: Assign values to to host var
shell: "/path/to/get-assigned-value.sh {{ item }}"
register: fileout
with_items:
- host1
- host2
- set_fact:
firstHost: "{{ fileout.results[0] }}"
secondHost: "{{ fileout.results[1] }}"
- debug:
var: firstHost.stdout
- debug:
var: secondHost.stdout
in the above example, firstHost is whole result of shell script running on first host, and firstHost.stdout gives the output of corresponding host shell script result.

It's possible set_facts and delegate_to the flash_hosts with delegate_facts.
But, to create a variable in hostvars the host must be declared in the inventory(static or dynamic). It's not necessary the host is accessible. For example
$ cat hosts
host1
host2
The play below
- hosts: localhost
vars:
flash_hosts: ['host1', 'host2']
tasks:
- name: Assign values to to host var
command: "{{ playbook_dir }}/get-assigned-value.sh {{ item }}"
register: result
loop: "{{ flash_hosts }}"
- set_fact:
mac: "{{ item.stdout }}"
loop: "{{ result.results }}"
delegate_to: "{{ item.item }}"
delegate_facts: true
- debug:
msg: "{{ hostvars[item].mac }}"
loop: "{{ flash_hosts }}"
gives
ok: [localhost] => (item=host1) =>
msg: mac address of host1
ok: [localhost] => (item=host2) =>
msg: mac address of host2
with the script
$ cat get-assigned-value.sh
#!/bin/sh
case $1 in
host1)
printf "mac address of host1"
;;
host2)
printf "mac address of host2"
;;
*)
printf "unknown host"
exit 1
;;
esac
exit 0

Related

List name server from resolv.conf with hostname in one line per host

I need to get the DNS server(s) from my network, I tried using:
- hosts: localhost
gather_facts: no
tasks:
- name: check resolv.conf exists
stat:
path: /etc/resolv.conf
register: resolv_conf
- name: check nameservers list in resolv.conf
debug:
msg: "{{ contents }}"
vars:
contents: "{{ lookup('file', '/etc/resolv.conf') | regex_findall('\\s*nameserver\\s*(.*)') }}"
when: resolv_conf.stat.exists == True
But this does not quite gives the result I need.
Will it be possible to write a playbook in such a way that the result looks like the below?
hostname;dns1;dns2;dnsN
The declaration below gives the list of nameservers
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('split', ' ')|
map('last')|list }}"
You can join the hostname and the items on the list
msg: "{{ inventory_hostname }};{{ nameservers|join(';') }}"
Notes
Example of a complete playbook for testing
- hosts: localhost
vars:
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('split', ' ')|
map('last')|list }}"
tasks:
- debug:
var: nameservers
- debug:
msg: |
{{ inventory_hostname }};{{ nameservers|join(';') }}
The simplified declaration below works fine if there is no nameserver.* in the comments
nameservers: "{{ lookup('file', '/etc/resolv.conf')|
regex_findall('\\s*nameserver\\s*(.*)') }}"
Unfortunately, the Linux default file /etc/resolv.conf contains the comment:
| # run "systemd-resolve --status" to see details about the actual nameservers.
This regex will match nameservers.
nameservers:
- s.
You can solve this problem by putting at least one space behind the keyword nameserver.
regex_findall('\\s*nameserver\\s+(.*)') }}"
However, this won't help if there is the keyword nameserver in the comment.
Q: "No filter named 'split'"
A: There is no filter split in Ansible less than 2.11. Use regex_replace instead
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('regex_replace', '^(.*) (.*)$', '\\2')|list }}"
Since your regex_findall already creates you a list with all DNS servers, you just need to add the hostname to that list and join the whole list with a semicolon.
- name: check nameservers list in resolv.conf
debug:
msg: >-
{{
(
[ ansible_hostname ] +
lookup('file', '/etc/resolv.conf', errors='ignore')
| regex_findall('\s*nameserver\s*(.*)')
) | join(';')
}}
Which will result in something like (b176263884e6 being the actual hostname of a container):
TASK [check nameservers list in resolv.conf] *****************************
ok: [localhost] =>
msg: b176263884e6;1.1.1.1;4.4.4.4;8.8.8.8
Note that you don't even need the stat task, as you can ignore errors of the lookup with errors='ignore'.
This will, then, give you only the hostname, along with a warning:
TASK [check nameservers list in resolv.conf] *****************************
[WARNING]: Unable to find '/etc/resolv.conf' in expected paths
(use -vvvvv to see paths)
ok: [localhost] =>
msg: b176263884e6

Ansible nested braces or nested quotes

Using ansible 2.9 I set a variable like this to store part of a group name
- name: set group
set_fact:
ansible_group: aaaa
I then want to use this variable in the following with_items clause:
- name: get
uri:
url: "http://{{ item }}:5324/kjhfg"
with_items: "{{ groups['thisgroup_{{ ansible_group }}'] }}"
However, using nested curly braces gives me the following error:
FAILED! => {"msg": "'dict object' has no attribute 'thisgroup_{{ ansible_group }}'"}
I also tried the following syntax variations
with_items: "{{ groups['thisgroup_ansible_group'] }}"
with_items: "groups['thisgroup_{{ ansible_group }}']"
with_items: "{{ groups['thisgroup_hostvars['localhost']['ansible_group']'] }}"
with_items: "{{ groups['thisgroup_hostvars[''localhost''][''ansible_group'']'] }}"
with_items: "{{ groups['thisgroup_hostvars[`localhost`][`ansible_group`]'] }}"
and probably one hundred other variations which all of them produced various errors
Can someone help me figure out the right syntax?
Simply concatenate the name of the group, e.g. given the inventory
shell> cat hosts
[thisgroup_aaaa]
host1
host2
host3
the playbook
- hosts: all
gather_facts: false
vars:
ansible_group: aaaa
_group: "{{ groups['thisgroup_' ~ ansible_group] }}"
tasks:
- debug:
var: item
loop: "{{ _group }}"
run_once: true
gives
item: host1
item: host2
item: host3

ansible to perform a task based on hostname value

Hi I am trying below.
task:
- name: Perform on primary server
blockinfile:
path: '/home/conf'
marker: "#-- {mark} Adding Values --"
block: |
{{ conf }}
when: "'host01' in inventory_hostname"
- name: Perform on stdby server
blockinfile:
path: '/home/conf'
marker: "#-- {mark} Adding Values --"
block: |
{{ conf_stdby }}
when: "'host02' in inventory_hostname"
As every task is performed simultaneously on all hosts, i was expecting it would change host01 in 1st task and skip host02 and vice-versa in second task, However it changed in both hosts in both tasks and when i checked the servers both had conf_stdby.
Also there are many more tasks in my playbook which are common to both the hosts.
inventory_hostname wouldn't work as in inventory file of playbook there is ip no hostname, so is there a way i can use actual host's hostname in when condition?
Even tried this
vars:
my_conf:
host01: "{{ conf }}"
host02: "{{ conf_stdby }}"
task:
- name: Perform on primary server
blockinfile:
path: '/home/conf'
marker: "#-- {mark} Adding Values --"
block: |
{{ conf }}
when: ""{{hostvars[inventory_hostname].ansible_hostname}} in myconf"
Still both host add the same block
You're doing all right. The code should work. To test the strings are equal, usually "==" is used instead of the inclusion "in". But, you can reference the configuration data in a dictionary and simplify the code, e.g.
vars:
my_conf:
host01: "{{ conf }}"
host02: "{{ conf_stdby }}"
task:
- blockinfile:
path: /home/conf
marker: "#-- {mark} Adding Values --"
block: |
{{ my_conf[inventory_hostname] }}
when: "inventory_hostname in my_conf"
Q: "In the inventory, there is no hostname but ip."
A: Whatever you have got in the inventory put it into the dictionary. There are no restrictions on keys in the YAML dictionaries, e.g. the inventory
shell> cat hosts
10.1.0.61
10.1.0.62
and the playbook
shell> cat playbook.yml
- hosts: all
gather_facts: false
vars:
my_conf:
10.1.0.61: conf
10.1.0.62: conf_sdtdby
tasks:
- debug:
msg: "{{ my_conf[inventory_hostname] }}"
gives
ok: [10.1.0.61] =>
msg: conf
ok: [10.1.0.62] =>
msg: conf_sdtdby

get ansible groups by looping with_items

I'm trying to get the inventory host group names by below process which is not working
- debug:
msg: "{{ groups['{{ item }}'] }}"
with_items: "{{ vm.stdout_lines }}"
this is what actually I'm trying to do
I will get a list of servers by a shell script
- name: Getting the servers list
shell: |
sh getServers.sh
register: vm
Then adding them into inventory by add_host
- name: Creating Logical host_group for each server
add_host:
name: "{{ item }}"
groups: ["{{item }}"]
with_items: "{{ vm.stdout_lines }}"
register: inv
Here I'm trying to get the only groups I've added in above step instead of all groups
- debug:
msg: "{{ groups['{{ item }}'] }}"
with_items: "{{ vm.stdout_lines }}"
Error is
{"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute '{{ item }}'
Appreciate help on this!
Fix the syntax
msg: "{{ groups[item] }}"

Issue passing variable to include_task file having add_host in Ansible

I am dealing with nested loops inorder to build dynamic host using add_host.
Outer Loop: with_items: "{{ command_result.stdout_lines }}" // gets me the list of users
Inner Loop: with_items: "{{ dest_ip.split(',') }}" // gets me the list of IP addresses seperated by comma (,)
The below playbook works fine.
- name: Add hosts
include_tasks: "{{ playbook_dir }}/gethosts.yml"
vars:
dest_ip: "{{ item.split('\t')[0] }}"
file_dets: "{{ item.split('\t')[1] }}"
USER_pass: "anystring"
# USER_pass: "{% if item.split('\t')[3] == 'FrontEnd' %}user1{% elif item.split('\t')[3] == 'BackEnd' %}user2{% else %}{% endif %}"
ansible_host: localhost
install_dir_pass: "anystring"
# install_dir_pass: "{{ item.split('\t')[2] }}"
with_items: "{{ command_result.stdout_lines }}"
Below is my include_task gethost.yml file:
---
- name: Generate JSON data
add_host:
name: "{{ item }}"
groups: dest_nodes
ansible_user: "{{ USER_pass }}"
install_dir: "{{ install_dir_pass }}"
with_items: "{{ dest_ip.split(',') }}"
I get the below error if I uncomment either USER_pass or install_dir_pass and comment the existing value:
TASK [Generate JSON data]
*********************************************************************************************************************************** task path: /app/deployment/gethosts.yml:2 [WARNING]: The loop
variable 'item' is already in use. You should set the loop_var value
in the loop_control option for the task to something else to avoid
variable collisions and unexpected behavior.
fatal: [localhost]: FAILED! => {
"msg": "The task includes an option with an undefined variable. The error was: list object has no element 2\n\nThe error appears to be
in '/app/deployment/gethosts.yml': line 2, column 4, but may\nbe
elsewhere in the file depending on the exact syntax problem.\n\nThe
offending line appears to be:\n\n---\n - name: Generate JSON data\n
^ here\n" }
NO MORE HOSTS LEFT
PLAY RECAP
************************************************************************************************************************************************** localhost : ok=12 changed=1 unreachable=0
failed=1 skipped=0 rescued=0 ignored=0
Requesting a solution to this issue and an explanation of a few questions I have.
The dest_ip is read and works fine with .split(,) method inside of include_task get_hosts.yml file when other variables like install_dir_pass dont seem to work.
When USER_pass and install_dir_pass are given simple string "AnyString" it works and is read fine inside of get_hosts.yml where as if they are assigned values using item.split('\t')[] the playbook errors as above.
I have already tested using debug that all the entries in command_result are good and the values should be populated correctly as below.
- debug:
msg: "{{ item.split('\t')[0] }}"
# msg: "{{ item.split('\t')[1] }}"
#msg: "{{ item.split('\t')[2] }}"
# msg: "{{ item.split('\t')[3] }}"
with_items: "{{ command_result.stdout_lines }}"
As this is very well explained in your error message:
There is a name collision between the item of each nested loops.
You have to set the loop_var name in the loop_control option to resolve the conflict in either of the loops.
I suggest you do this on the include_task so that it solves the problem for any other loop inside your included file. As I have no idea what your command_result.stdout_lines contains exactly, I used in the below example the var name my_result that you should change with something fitting your situation. Note that this var will be available directly in the included file if necessary.
- name: Add hosts
include_tasks: "{{ playbook_dir }}/gethosts.yml"
vars:
dest_ip: "{{ my_result.split('\t')[0] }}"
file_dets: "{{ my_result.split('\t')[1] }}"
USER_pass: "{% if my_result.split('\t')[3] == 'FrontEnd' %}user1{% elif my_result.split('\t')[3] == 'BackEnd' %}user2{% else %}{% endif %}"
ansible_host: localhost
install_dir_pass: "{{ my_result.split('\t')[2] }}"
with_items: "{{ command_result.stdout_lines }}"
loop_control:
loop_var: my_result

Resources