Dynamically generate bmc hostnames from static inventory? - ansible

I have an ansible inventory that looks something like:
all:
children:
nodes:
vars:
bmc_user: root
bmc_pass: secret
hosts:
node1.example.com:
bmc_addr: node1-bmc.example.com
node2.example.com:
bmc_addr: node2-bmc.example.com
For running something like ipmitool, I can simply delegate the task to localhost, like this:
- hosts: all
gather_facts: false
tasks:
- delegate_to: localhost
command: >-
ipmitool -I lanplus -H {{ bmc_addr }} -U {{ bmc_user }} -P {{ bmc_pass }} power off
Occasionally, I need to interact with the bmc using ssh. Thsi is
tricky, because typically this requires password authentication. I
want to write something like this:
- hosts: all
gather_facts: false
tasks:
- delegate_to: "{{ bmc_addr }}"
command: >-
racadm set iDRAC.IPMILan.Enabled Enable
...but this won't work, because the credentials required to access the
bmc are not the same as the credentials necessary to access the host.
I would like to be able to do something like this in my inventory:
all:
children:
nodes:
vars:
bmc_user: root
bmc_pass: secret
hosts:
node1.example.com:
bmc_addr: node1-bmc.example.com
node2.example.com:
bmc_addr: node2-bmc.example.com
nodes_bmc:
vars:
ansible_user: root
ansible_ssh_pass: secret
hosts:
node1-bmc.example.com:
node2-bmc.example.com:
...but I would like the hosts in the nodes_bmc group to be populated
dynamically from the bmc_addr values in the nodes group, rather
than having to specify them manually.
I've been puzzling over this for a bit, and the only solution I've
come up with is to write some tooling that reads the ansible inventory
and then generates a new static inventory containing the necessary
entries. That seems clunky, and requires ensuring that the "generate
bmc inventory" tool is run whenever the main inventory is modified.

Let's try the add_host module:
- name: add hosts
hosts: all
gather_facts: no
tasks:
- add_host:
hostname: "{{ hostvars[item].bmc_addr }}"
groups:
- nodes_bmc
loop: "{{ play_hosts }}"
Now, you need a new play so you have all the hosts in the play.
- name: Show all hosts
hosts: all
gather_facts: no
tasks:
- debug:
var: play_hosts
run_once: yes
Running with your inventory (but w/o any hosts in the node_bmc group):
$ ansible-playbook -i hosts.yml ./add_hosts.yml
PLAY [add hosts] ***********************************************************************************************************
TASK [add_host] ************************************************************************************************************
ok: [node1.example.com] => (item=node1.example.com)
ok: [node1.example.com] => (item=node2.example.com)
PLAY [Show all hosts] ******************************************************************************************************
TASK [debug] ***************************************************************************************************************
ok: [node1-bmc.example.com] => {
"play_hosts": [
"node1-bmc.example.com",
"node2-bmc.example.com",
"node1.example.com",
"node2.example.com"
]
}
PLAY RECAP *****************************************************************************************************************
node1-bmc.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node1.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Now, here's a totally different way.
You will have two file in your inventory:
The one you currently have (but w/o the hosts: section under nodes_bmc)
A generate_bmc.sh file, which must be executable: chmod +x generate_bmc.sh
That file will simply be this:
#!/bin/bash
# Figure out where we are
here=$( dirname $0 )
# Write header
cat << 'end_header'
{
"nodes_bmc": {
"hosts": [
end_header
# Get hosts from inventory file
hosts=$( ansible-inventory --list -i ${here}/hosts.yml | grep '"bmc_addr":' | cut -d: -f2 | sed 's/,$//' )
# Or, if you have jq installed:
# hosts=$( ansible-inventory --list -i ${here}/hosts.yml | jq "._meta.hostvars[].bmc_addr" )
last=$( echo $hosts | sed 's/.* //' )
# Write hosts
for host in ${hosts}
do
echo -n " "$host
[ "${host}" != "${last}" ] && echo ","
done
echo
# Close output
cat << 'end_footer'
]
}
}
end_footer
In your ansible.cfg file, have the line:
inventory = /the/directory/that/has/those/two/files
You can test this with ansible-inventory --list.

Related

How store full host name as variable in ansible?

I need to use one out of two hosts as a variable. I do have inventory_hostname_short of both but I need a full host as a variable. Currently, for testing I am using a hardcoded value. My playbook will run on both hosts at a same time so How I can identify and store as a variable.
host_1_full = 123.abc.de.com
host_2_full = 345.abc.de.com
above both are hosts and I do have
---
- name: Ansible Script
hosts: all
vars:
host1_short : '123'
host2_short : '345'
tasks:
- name: set host
set_fact:
host1_full: "{{inventory_hostname}}"
when: inventory_hostname_short == host1_short
- name: print info
debug:
msg: "host - {{host1_full}}"
- name: block1
block:
- name:running PS1 file
win_shell: "script.ps1"
register: host1_output
when: inventory_hostname_short == host1_short
- name: block2
block:
- name: set host
set_fact:
IN_PARA: "{{ hostvars[host1_full]['host1_output']['stdout']}}"
- name:running PS1 file
win_shell: "main.ps1 -paramater {{ IN_PARA }}"
register: output
when: inventory_hostname_short == host2_short
SO to access any file from different host required full hostname. How can I get that full host name
Given the following inventories/test_inventory.yml
---
all:
hosts:
123.abc.de.com:
345.abc.de.com:
ansible will provide the needed result in inventory_hostname automagically as demonstrated by the following test.yml playbook
---
- name: print long and short inventory name
hosts: all
gather_facts: false
tasks:
- name: print info
debug:
msg: "Host full name is {{ inventory_hostname }}. Short name is {{ inventory_hostname_short }}"
which gives:
$ ansible-playbook -i inventories/test_inventory.yml test.yml
PLAY [print long and short inventory name] *********************************************************************************************************************************************************************************************
TASK [print info] **********************************************************************************************************************************************************************************************************************
ok: [345.abc.de.com] => {
"msg": "Host full name is 345.abc.de.com. Short name is 345"
}
ok: [123.abc.de.com] => {
"msg": "Host full name is 123.abc.de.com. Short name is 123"
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
123.abc.de.com : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
345.abc.de.com : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
As already suggested, the ansible_hostname and ansible_fqdn are automatic facts about hosts in your inventory. However, your requirement is a little unique. So we might have to apply a unique method to accomplish this. How about something like this?
Consider example inventory as below:
---
all:
hosts:
192.168.1.101: # 123.abc.de.com
192.168.1.102: # 345.abc.de.com
We can have a play like this:
- hosts: all
vars:
host1_short: 123
host2_short: 345
tasks:
- command: "hostname -f"
register: result
- block:
- set_fact:
host1_full: "{{ result.stdout }}"
- debug:
msg: "Host1 fullname: {{ host1_full }}"
when: host1_short in result.stdout

Avoid unused and undefined variable in playbook

I have following data in a variable file
data: [
{
service: "ServiceName",
var2: "file/path/value",
var3: "{{ check_var }}",
}, {
service: "ServiceName",
var2: "file/path/value",
var3: "{{ check_var }}",
}
]
I have two playbooks which require the same data. However one playbook does not require var3.
- debug: msg="{{ item['service'] }} uses - {{ item['var2'] }}"
with_items: "{{ data }}"
This gives error - "'check_var' is undefined".
TRIED:
I don't want to populate the playbook with bad standards and use
when: check_var is undefined
Or use false dummy data in playbook's vars atrribute. Is there any way around this while maintaining standards. Also the actual data is quite huge so I don't want to repeat it twice for each playbook.
In Ansible data has to be assigned to hosts not to playbooks.
You have to create two host groups. Those hosts, who needs just two variables go into the first group. And those hosts which need 3 variables go into both groups. You can include the hosts of the first group in the second group.
Then you create two group var files. In the first you put 2 variables and in the second the 3rd variable.
By this each host gets the right amount of information. Playbook 1 uses 3 variables and playbook 2 uses just 2 variables.
Update: Minimal example
$ diff -N -r none .
diff -N -r none/check_var.yaml ./check_var.yaml
0a1,4
> ---
> - hosts: localhost
> tasks:
> - debug: var=check_var
diff -N -r none/group_vars/myhosts.yaml ./group_vars/myhosts.yaml
0a1
> check_var: "Hello World!"
diff -N -r none/inventory ./inventory
0a1,2
> [myhosts]
> localhost
$ ansible-playbook -i inventory check_var.yaml
PLAY [localhost] ***************************************************************************
TASK [Gathering Facts] *********************************************************************
ok: [localhost]
TASK [debug] *******************************************************************************
ok: [localhost] => {
"check_var": "Hello World!"
}
PLAY RECAP *********************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0

How to read multiline argument in Ansible

I pass a multiline variable called host_list from Jenkins to ansible which contains the list of hosts.
I need to read each host line by line and add it to ansible's add_host module.
Below is how my multiline argument looks like.
ansible-playbook /app/upgrade_tomcat.yml -i /tmp/inventory1775725953939119720.ini -t validate -f 5 -e tomcat_home=/app/tomcat -e host_list='10.9.9.19
10.9.55.16
10.9.44.26
' -e USER=user1
I tried the below but it does not work.
---
- name: "Find the details here"
hosts: localhost
tasks:
- add_host: name={{ item }}
groups=dest_nodes
ansible_user={{ USER }}
with_items: "{{ host_list.split('\n') }}"
I even tried the following:
host_list.splitlines()
host_list.split( )
But none of them works.
Requesting suggestions.
Warning: this is absolutely ugly and should be replaced by any other correct way to acheive your result (non exhaustively including: defining your host list in a group in your inventory, including a file containing a definition of your list in yaml/json, passing var as extra var directly in yaml/json....)
After this warning, here is a working solution with your current situation. Simply quote values correctly.
The command
ansible-playbook playbook.yml -e 'my_list="toto
pipo
bingo"'
The playbook
---
- name: Passing abolutely ugly vars on command line
hosts: localhost
gather_facts: false
tasks:
- name: Split ugly extra var
debug:
msg: "{{ my_list.splitlines() }}"
The result:
PLAY [Passing abolutely ugly vars on command line] ***********************************************************
TASK [Split ugly extra var] **************************************************************************************
ok: [localhost] => {
"msg": [
"toto",
"pipo",
"bingo"
]
}
PLAY RECAP ***************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You can pass as an array in extra-vars as below
-e '{"host_list": [10.9.9.19,10.9.55.16,10.9.44.26]}'
But it is always good to add them in the inventory as a group and use the group name in playbook
Inventory:
[host_list]
10.9.9.19
10.9.55.16
10.9.44.26
Use below to loop through each host
with_items: "{{ groups['host_list'] }}"

Run playbook, pass host as arg but host is not present as an entry in /ansible/hosts

Is there any why to pass a hostname as an argument while running a playbook, and this hostname is not there in the /ansible/host file??
I did try
ansible-playbook -i hostname, playook.yml
You can somewhere dynamically define variable hostname.
export hostname=myHostAddress
Then you create file "hosts" with defined $hostname and return its name. Then you limit invokation of playbook.yml just for you $hostname.
ansible-playbook -i $(echo "$hostname" > hosts %% echo "hosts -l $hostname") playook.yml
Ensure that your hosts is all or a variable in your playbook:
---
- name: Test
hosts: all
gather_facts: True
tasks:
- name: Debug
debug:
var: groups
Then run as:
ansible-playbook --inventory=windows, debug.yml
You will get:
PLAY [Test] **************************************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************************
ok: [windows]
TASK [Debug] *************************************************************************************************************************************************************
ok: [windows] => {
"groups": {
"all": [
"windows"
],
"ungrouped": [
"windows"
]
}
}
PLAY RECAP ***************************************************************************************************************************************************************
windows : ok=2 changed=0 unreachable=0 failed=0
Other option is using a variable and pass it at runtime:
So:
---
- name: Test
hosts: "{{ host }}"
gather_facts: True
tasks:
- name: Debug
debug:
var: groups
And do:
ansible-playbook debug.yml -e host=windows
Besides that, you have the add_host or the group_by options

ansible delegation to other hosts

I use ansible 2.1 and I want to run a command to a group of hosts, using delegate_to. I use localhost as the host param and I want to delegate a “touch” command to both of cls hosts
I have the following
---
- hosts: ansible
# gather_facts: yes
tasks:
- debug: var=groups.cls
- name: touch a file to running host
shell: echo {{ item }} >> /tmp/{{ inventory_hostname }}
delegate_to: "{{ item }}"
with_items: "{{ groups.cls }}"
with output:
[root#ansible control]# ansible-playbook -i inventory test.yml
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [ansible]
TASK [debug] *******************************************************************
ok: [ansible] => {
"groups.cls": [
"cls-host-1",
"cls-host-2"
]
}
TASK [touch a file to running host] ********************************************
changed: [ansible -> cls-host-1] => (item=cls-host-1)
changed: [ansible -> cls-host-2] => (item=cls-host-2)
PLAY RECAP *********************************************************************
ansible : ok=3 changed=1 unreachable=0 failed=0
but the touch is done only on the first host:
[root#cls-host-1 ~]# more /tmp/ansible
cls-host-1
cls-host-2
Is anything wrong? Can I delegate the command with any other way?
I've tested a variation of your playbook using Ansible 2.4.0.0:
#!/usr/bin/env ansible-playbook
- hosts: stretch.fritz.box
tasks:
- name: touch
shell: echo {{item}} >>/tmp/{{inventory_hostname}}
delegate_to: "{{item}}"
with_items:
- jessie.fritz.box
- short.fritz.box
This is working fine: the touch is performed on jessie and short
jessie$ cat /tmp/stretch.fritz.box
jessie.fritz.box
short$ cat /tmp/stretch.fritz.box
short.fritz.box
Perhaps this feature was introduced in Ansible between 2.1 and 2.4.

Resources