Passing variables between nested playbooks - ansible

I have a playbook where I'm spinning up an instance in aws with the ec2 module. To be more flexible I ask via prompt for the hostname. I found in the ec2 examples the code snippet, which allows you to run a second playbook with newly spun up instance for further configuration.
Now I want to set the hostname via module hostname but I cannot access the variable in the second playbook.
This is how my playbook looks like:
---
- hosts: localhost
...
vars_prompt:
- name: var_hostname
prompt: "Please enter the hostname"
private: no
tasks:
- name: Spin up instance
local_action:
module: ec2
...
register: ec2
- name: Add new instance to host group
add_host: hostname={{ item.public_ip }} groupname=launched
with_items: ec2.instances
- hosts: launched
...
tasks:
- name: Set hostname
hostname: name="{{ var_hostname }}"
fatal: [launched] => One or more undefined variables: 'var_hostname' is undefined
Is there a way to pass a variable from one playbook to another one?
I found Ansible best practice for passing vars to nested playbooks? but unfortunately it didn't had a solution which I can use.

You can use set_fact and hostvars together to achieve what you want.
Do set_fact on one group of hosts( i.e localhost), and access them in a different play using hostvars
{{hostvars['localhost']["new_fact"]}}

You can use local files.
1) Write
- name: write public ip
local_action:
template:
dest: /tmp/ansible_master_public_ip.txt
src: templates/public_ip.j2
2) Retrieve with http://docs.ansible.com/ansible/playbooks_lookups.html
hostname: "{{ lookup('file', '/tmp/ansible_master_public_ip.txt') | trim }}"
PS. Ini file lookup also an option if you need more than few variables.

Add the host's variable to the parameters. For example,
- name: Add new instance to host group
add_host:
hostname: "{{ ec2.instances.0.public_ip }}"
groupname: launched
var_hostname: "{{ var_hostname }}"
See examples
Use only the first item from the list because you have only one hostname. There is no reason to iterate the list.
Example of a complete playbook for testing
- hosts: localhost
gather_facts: false
vars_prompt:
- name: var_hostname
prompt: "Please enter the hostname"
private: no
vars:
ec2:
instances:
- public_ip: AAA.BBB.CCC.DDD
tasks:
- name: Add new instance to host group
add_host:
hostname: "{{ ec2.instances.0.public_ip }}"
groupname: launched
var_hostname: "{{ var_hostname }}"
- hosts: launched
gather_facts: false
tasks:
- debug:
var: var_hostname
shell> ansible-playbook pb.yml
Please enter the hostname: host_2
PLAY [localhost] *****************************************************************************
TASK [Add new instance to host group] ********************************************************
changed: [localhost]
PLAY [launched] ******************************************************************************
TASK [debug] *********************************************************************************
ok: [AAA.BBB.CCC.DDD] =>
var_hostname: host_2
PLAY RECAP ***********************************************************************************
AAA.BBB.CCC.DDD: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Use pause instead of vars_prompt if you have more hosts. For example,
- name: Get var_hostname(s)
pause:
prompt: "Please enter the hostname for {{ item.public_ip }}"
echo: yes
loop: "{{ ec2.instances }}"
register: var_hostnames
- name: Add new instances to host group
add_host:
hostname: "{{ item.item.public_ip }}"
groupname: launched
var_hostname: "{{ item.user_input }}"
loop: "{{ var_hostnames.results }}"
loop_control:
label: "{{ item.user_input }}"
Example of a complete playbook for testing
- hosts: localhost
gather_facts: false
vars:
ec2:
instances:
- public_ip: AAA.BBB.CCC.DD1
- public_ip: AAA.BBB.CCC.DD2
tasks:
- name: Get var_hostname(s)
pause:
prompt: "Please enter the hostname for {{ item.public_ip }}"
echo: yes
loop: "{{ ec2.instances }}"
register: var_hostnames
- name: Add new instances to host group
add_host:
hostname: "{{ item.item.public_ip }}"
groupname: launched
var_hostname: "{{ item.user_input }}"
loop: "{{ var_hostnames.results }}"
loop_control:
label: "{{ item.user_input }}"
- hosts: launched
gather_facts: false
tasks:
- debug:
var: var_hostname
shell> ansible-playbook pb.yml
PLAY [localhost] *****************************************************************************
TASK [Get var_hostname(s)] *******************************************************************
[Get var_hostname(s)]
Please enter the hostname for AAA.BBB.CCC.DD1:
host_1^Mok: [localhost] => (item={'public_ip': 'AAA.BBB.CCC.DD1'})
[Get var_hostname(s)]
Please enter the hostname for AAA.BBB.CCC.DD2:
host_2^Mok: [localhost] => (item={'public_ip': 'AAA.BBB.CCC.DD2'})
TASK [Add new instances to host group] *******************************************************
ok: [localhost] => (item=host_1)
ok: [localhost] => (item=host_2)
PLAY [launched] ******************************************************************************
TASK [debug] *********************************************************************************
ok: [AAA.BBB.CCC.DD1] =>
var_hostname: host_1
ok: [AAA.BBB.CCC.DD2] =>
var_hostname: host_2
PLAY RECAP ***********************************************************************************
AAA.BBB.CCC.DD1: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
AAA.BBB.CCC.DD2: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Related

How to execute on multiple hosts in ansible

I have a script that will execute in two parts. First it will execute on localhost and query a database table to get a hostname. second part of the script should run on the host which was registered in the query before. I am not able to set the host with the set_fact I did in the first part of the code.
this is what iam trying to do:
- hosts: localhost
gather_facts: false
become: yes
become_user: oracle
vars_files:
- vars/main.yml
tasks:
- name: Get new hostname
tempfile:
state: file
register: tf
- name: create sql file
template:
src: get_hostname.sql.j2
dest:"{{ tf.path }}"
mode: 0775
- name: login
command:
argv:
- "sqlplus"
- -s
- "#{{ tf.path }}"
environment:
ORACLE_HOME: "oracle/home"
register: command_out
- set_fact:
NEW_HOST: "{{ command_out.stdout }}"
- hosts: "{{ NEW_HOST }}"
gather_facts: false
become: yes
become_user: oracle
vars_file:
- vars/main.yml
tasks:
- name: debug
command: hostname
register: new_host_out
- debug:
msg: "new host is {{ new_host_out.stdout }}"
Everything works fine in the first part of the code, but errors out at the second part saying it cannot find the NEW_HOST.
Use hostvars to reference such a variable. Create a dummy host to keep this variable. For example, given the inventory
shell> cat hosts
dummy
[test]
test_11
test_12
test_13
The playbook creates the variable. See Delegated facts
shell> cat pb.yml
- hosts: localhost
tasks:
- set_fact:
NEW_HOST: test_12
delegate_to: dummy
delegate_facts: true
- debug:
var: hostvars.dummy.NEW_HOST
- hosts: "{{ hostvars.dummy.NEW_HOST }}"
gather_facts: false
tasks:
- debug:
var: inventory_hostname
gives
shell> ansible-playbook pb.yml
PLAY [localhost] ****************************************************************************
TASK [set_fact] *****************************************************************************
ok: [localhost -> dummy]
TASK [debug] ********************************************************************************
ok: [localhost] =>
hostvars.dummy.NEW_HOST: test_12
PLAY [test_12] ******************************************************************************
TASK [debug] ********************************************************************************
ok: [test_12] =>
inventory_hostname: test_12
PLAY RECAP **********************************************************************************
localhost: ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test_12 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You can use localhost for this purpose as well. The playbook below works as expected
- hosts: localhost
tasks:
- set_fact:
NEW_HOST: test_12
- hosts: "{{ hostvars.localhost.NEW_HOST }}"
gather_facts: false
tasks:
- debug:
var: inventory_hostname

Ansible vars_prompt and msg usage prints hello world

Based on input from Zeitounator I have updated the original question. The following works
root#devenv:~/scripts/uia# cat addVlanTest.yml
---
- name: PLAY 111 Add Vlan ;
hosts: all
vars_prompt:
- name: vlanIdToAdd
prompt: vlan ID to add
private: no
default: "50"
tasks:
- name: "Debug task Take 1"
ansible.builtin.debug:
var: vlanIdToAdd
- name: "Debug task Take 2"
ansible.builtin.debug:
msg: "{{ vlanIdToAdd }}"
- name: "Debug task Take 3"
ansible.builtin.debug:
msg: "Creating vlan {{ vlanIdToAdd }}"
Correct:
msg: "Creating vlan {{ vlanIdToAdd }}"
Incorrect:
msg:"Creating vlan {{ vlanIdToAdd }}"
What is the scope ? Variables do not survive after plays. If you need to use it in a different play save it.
root#devenv:~/scripts/uia# cat addVlanTest.yml
---
- name: PLAY 111 Add Vlan ;
hosts: all
vars_prompt:
- name: vlanIdToAdd
prompt: vlan ID to add
private: no
default: "50"
tasks:
- name: "Debug vars Take 1"
debug:
var=vlanIdToAdd
- name: "Debug vars Take 1"
set_fact:
new_vlan_id="{{ vlanIdToAdd }}"
- name: PLAY 112 Add Vlan - IOS;
hosts: ios
tasks:
- name: "Debug vars Take 2"
debug:
var=new_vlan_id
As reported in my comment, vars_prompt are only available inside a given play as this is their scope. This is explained in plabooks variable documentation
From my suggestion, you have tried to use set_fact. As you will see in the same documentation, those vars are scoped to host and are only available while playing a task on host having the given fact. Meanwhile, you can access facts from other host using the hostvars magic variable
Here is one way to work arround this kind of chicken-egg problem. It might not be exactly what you are looking for but will hopefully give you some ideas for your best solution. I tried to make it self explanatory.
The following test.yml playbook:
---
- name: Play dedicated to vars_prompt and storing facts in localhost
hosts: localhost
gather_facts: false
vars_prompt:
- name: my_prompt
prompt: enter a value
private: no
tasks:
- name: store prompt in fact
set_fact:
my_prompt: "{{ my_prompt }}"
- name: Use fact on host a by directly calling it in a task
hosts: a
gather_facts: false
tasks:
- name: display the fact
debug:
msg: "{{ hostvars['localhost'].my_prompt }}"
- name: Use the fact on host b by first assigning to a play var
hosts: b
gather_facts: false
vars:
my_prompt: "{{ hostvars['localhost'].my_prompt }}"
tasks:
- name: display the fact
debug:
msg: "{{ my_prompt }}"
Gives:
$ ansible-playbook -i a,b, test.yml
enter a value: toto
PLAY [Play dedicated to vars_prompt and storing facts in localhost] ************************************************************************************************************************************************
TASK [store prompt in fact] ***************************************************************************************************************************************************************************************
ok: [localhost]
PLAY [Use fact on host a by directly calling it in a task] ********************************************************************************************************************************************************
TASK [display the fact] *******************************************************************************************************************************************************************************************
ok: [a] => {
"msg": "toto"
}
PLAY [Use the fact on host b by first assigning to a play var] ****************************************************************************************************************************************************
TASK [display the fact] *******************************************************************************************************************************************************************************************
ok: [b] => {
"msg": "toto"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
a : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
b : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Meanwhile, if this is possible in your scenario, you can as well set the fact on all hosts in your inventory so as to reuse it anywhere, this makes a much simpler playbook:
---
- name: Play dedicated to vars_prompt and storing facts on all host (except implicit localhost)
hosts: all
gather_facts: false
vars_prompt:
- name: my_prompt
prompt: enter a value
private: no
tasks:
- name: store prompt in fact
set_fact:
my_prompt: "{{ my_prompt }}"
- name: Use fact on host a
hosts: a
gather_facts: false
tasks:
- name: display the fact
debug:
msg: "{{ my_prompt }}"
- name: Use the fact on host b
hosts: b
gather_facts: false
tasks:
- name: display the fact
debug:
msg: "{{ my_prompt }}"
- name: Simple demo implicit localhost is not part of the all group and does not have the fact
hosts: localhost
gather_facts: false
tasks:
- name: show fact is undefined on localhost
debug:
var: my_prompt
Gives:
$ ansible-playbook -i a,b, test.yml
enter a value: titi
PLAY [Play dedicated to vars_prompt and storing facts on all host (except implicit localhost)] ********************************************************************************************************************
TASK [store prompt in fact] ***************************************************************************************************************************************************************************************
ok: [a]
ok: [b]
PLAY [Use fact on host a] *****************************************************************************************************************************************************************************************
TASK [display the fact] *******************************************************************************************************************************************************************************************
ok: [a] => {
"msg": "titi"
}
PLAY [Use the fact on host b] *************************************************************************************************************************************************************************************
TASK [display the fact] *******************************************************************************************************************************************************************************************
ok: [b] => {
"msg": "titi"
}
PLAY [Simple demo implicit localhost is not part of the all group and does not have the fact] *********************************************************************************************************************
TASK [show fact is undefined on localhost] ************************************************************************************************************************************************************************
ok: [localhost] => {
"my_prompt": "VARIABLE IS NOT DEFINED!"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
a : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
b : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

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

How to change play_hosts from playbook

I have a file with two playbooks. Inventory is generated dynamically and there is no possibility to change it before starting playbook.
Run with command:
ansible-playbook -b adapter.yml --limit=host_group
adapter.yml
- name: Prepare stage
hosts: all
# The problem is that the inventory contains hosts in the format "x.x.x.x" ie physical address.
# I need to run a third-party role.
# But, it needs hosts in the format "instance-alias", that is, the name of the instance.
tasks:
# for this I create a new host group
- name: Add host in new format
add_host:
name: "{{ item.alias }}"
host: "{{ item.ansible_host }}"
groups: new_format_hosts
with_items: "{{ groups.all }}"
# I create a new play host group that matches the previous one in a new format
- name: Compose new play_hosts group
add_host:
name: "{{ item.alias }}"
groups: new_play_hosts
when: item.ansible_host in play_hosts
with_items: "{{ groups.all }}"
- name: Management stage
hosts: new_format_hosts
# in this playbook I want to change the composition
# of the target hosts and launch an external role
vars:
hostvars: "{{ hostvars }}"
play_hosts: "{{ groups.new_play_hosts }}" # THIS DONT WORK
- name: Run external role
import_role:
name: role_name
tasks_from: file_name
But I can’t change play_hosts so that the launched role uses only new hosts.
How to fix it?
This works for me:
- name: Prepare stage
hosts: localhost
tasks:
- name: Show hostavers
debug:
msg: "{{ hostvars[item]['ansible_host'] }}"
with_items: "{{ groups.all }}"
# for this I create a new host group
- name: Add host in new format
add_host:
name: "{{ hostvars[item].alias }}"
ansible_host: "{{ hostvars[item].ansible_host }}"
groups: new_format_hosts
with_items: "{{ groups.all }}"
- name: Management stage
hosts: new_format_hosts
tasks:
- name: Ping New Format Hosts
ping:
- name: Show ansible_host for each host
debug:
var: ansible_host
- name: Show playhosts
debug:
var: play_hosts
delegate_to: localhost
run_once: yes
This assumes, of course, that alias and ansible_host are set for all the hosts.
The hosts were:
AnsibleTower ansible_host=192.168.124.8 alias=fred
192.168.124.111 ansible_host=192.168.124.111 alias=barney
jaxsatB ansible_host=192.168.124.111 alias=wilma
The relevant output of the playbook was:
PLAY [Management stage] *****************************************************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************************************************************
Friday 29 May 2020 18:09:26 -0400 (0:00:00.057) 0:00:01.332 ************
ok: [fred]
ok: [barney]
ok: [wilma]
TASK [New Format Hosts] *****************************************************************************************************************************************************************
Friday 29 May 2020 18:09:30 -0400 (0:00:04.308) 0:00:05.641 ************
ok: [barney]
ok: [wilma]
ok: [fred]
TASK [Show ansible_host] ****************************************************************************************************************************************************************
Friday 29 May 2020 18:09:30 -0400 (0:00:00.450) 0:00:06.091 ************
ok: [fred] => {
"ansible_host": "192.168.124.8"
}
ok: [barney] => {
"ansible_host": "192.168.124.111"
}
ok: [wilma] => {
"ansible_host": "192.168.124.111"
}
TASK [Show playhosts] *******************************************************************************************************************************************************************
Friday 29 May 2020 18:09:30 -0400 (0:00:00.109) 0:00:06.200 ************
ok: [fred -> localhost] => {
"play_hosts": [
"fred",
"barney",
"wilma"
]
}
PLAY RECAP ******************************************************************************************************************************************************************************
barney : ok=3 changed=0 unreachable=0 failed=0
fred : ok=4 changed=0 unreachable=0 failed=0
localhost : ok=3 changed=1 unreachable=0 failed=0
wilma : ok=3 changed=0 unreachable=0 failed=0
Friday 29 May 2020 18:09:30 -0400 (0:00:00.030) 0:00:06.231 ************
===============================================================================
You do not need to set the play_hosts variable. That is set by the line hosts: new_format_hosts in the second play.

Ansbile get the substring from task output

I have this URL string server https://test.example.com:6443. I need to extract only hostname test from it using Ansible task.
came up with this playbook, Is there a better way of doing this with-out using sed?
- hosts: localhost
connection: local
gather_facts: no
tasks:
- name: get the URL
shell: echo 'server https://test.example.com:6443' |sed -e 's/^.*https...//' -e 's/\..*$//'
register: result
- name: Print the var
debug:
msg: "{{ result.stdout }}"
Output
PLAY [localhost] *********************************************************************************************************************
TASK [get the URL] *******************************************************************************************************************
changed: [localhost]
TASK [Print the var] *****************************************************************************************************************
ok: [localhost] => {
"msg": "test"
}
PLAY RECAP ***************************************************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You can use split on the URL name and get the desired value, Ideally hostname should be test.example.com in your case. However, you can customize the split function based on your need also as below.
- hosts: localhost
connection: local
gather_facts: no
vars:
url: "https://test.example.com:6443"
tasks:
- name: Print the hostname var
debug:
msg: "{{ url.split(':')[1].split('//')[1] }}"
- name: Print the subdomain prefix
debug:
msg: "{{ url.split(':')[1].split('//')[1].split('.')[0] }}"

Resources