I'm having trouble using hostvars to pass a variable to another host in a vagrant environment, the code I did:
Vagrant.configure("2") do |config|
config.vm.define "server_1" do |server_1|
server_1.vm.hostname = "n1"
server_1.vm.box = "centos/7"
server_1.vm.network "public_network", bridge: "wlp1s0", ip: "192.168.0.50"
end
config.vm.define "worker_1" do |worker_1|
worker_1.vm.hostname = "n2"
worker_1.vm.box = "centos/7"
worker_1.vm.network "public_network", bridge: "wlp1s0", ip: "192.168.0.51"
end
config.vm.provider "virtualbox" do |vb|
vb.memory = 1024
end
config.vm.provision "ansible" do |ansible|
ansible.playbook = "t0a.yml"
end
end
t0a.yml
---
- hosts: server*
tasks:
- set_fact: hello=world
- hosts: worker*
tasks:
- debug:
msg: "{{ hostvars['server_1']['hello'] }}"
expected:
TASK [show] *******************************************************************
ok: [worker_1] => {
"msg": [
"works"
]
}
actual:
TASK [debug] ********************************************************************
fatal: [worker_1]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'hello'\n\nThe error appears to have been in '/home/kayke/Documentos/vm-vagrant/provision-ansible/centos/t0_tests/t0a.yml': line 8, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n - debug:\n ^ here\n"}
What you want is a single playbook that targets both server and worker, matching on their host patterns, because that way there is only a single ansible run, versus what's happening now is that ansible is being run twice, once for each host. Thus:
- hosts: server*
tasks:
- set_fact: hello=world
- hosts: worker*
tasks:
- debug:
msg: "{{ hostvars["server_1"]["hello"] }}"
and invoked at the very end, like you see here: https://github.com/kubernetes-sigs/kubespray/blob/v2.8.3/Vagrantfile#L177-L193
I haven't studied Vagrant enough to know if it will write out an inventory file for you, or what, but if you need an example kubespray is also generating an inventory file from all the known vms, too: https://github.com/kubernetes-sigs/kubespray/blob/v2.8.3/Vagrantfile#L69-L75
If you don't like that approach, you can also use fact caching plugins to cause ansible to write out the fact cache for the hosts in a way that the worker playbooks can read in, but as you might suspect, that's a ton more work.
Related
I need to use variable defined for some hosts (in inventory), on another host.
Here i define it in my inventory
[mygroup:vars]
service_url=my_front_url
Where mygroup contain other groups, containing my hosts.
Then my playbook :
- name: Get variable
hosts: 127.0.0.1
tasks:
- debug:
var: hostvars[groups['{{ platform }}'][0]]['service_url']
- debug:
msg: "{{ hostvars[groups['\"{{ platform }}\"'][0]]['service_url'] }}"
Where platform is an extra-var (setting which "mygroup" to use)
and where 127.0.0.1 is my ansible host, distinct from my target hosts.
ex:
ansible-playbook test.yaml --extra-vars='platform=my-group'
TASK [debug] ********************************************************************************************************************************************************************
ok: [127.0.0.1] => {
"hostvars[groups['idi_se_prod'][0]]['service_url']": "my-front-url"
}
TASK [debug] ********************************************************************************************************************************************************************
fatal: [127.0.0.1]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute '\"{{ platform }}\"'\n\nThe error appears to have been in 'XXXX/ansible/test.yaml': line 6, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n var: hostvars[groups['{{ platform }}'][0]]['service_url']\n - debug:\n ^ here\n"}
If i set static group name in yaml, this work fine.
- name: Get variable
hosts: 127.0.0.1
tasks:
- debug:
var: hostvars[groups['{{ platform }}'][0]]['service_url']
- debug:
msg: "{{ hostvars[groups['mygroup'][0]]['service_url'] }}"
TASK [debug] ********************************************************************************************************************************************************************
ok: [127.0.0.1] => {
"hostvars[groups['my-group'][0]]['service_url']": "my-front-url"
}
TASK [debug] ********************************************************************************************************************************************************************
ok: [127.0.0.1] => {
"msg": "my_front_url"
}
It look like a syntax probleme but i tried so many ways that i think i could use some help.
Thank you
Nicolas
Everything inside {{ and }} is more or less just python, so don't use recursive templates like you have:
msg: "{{ hostvars[groups['\"{{ platform }}\"'][0]]['service_url'] }}"
instead just reference the variable like it is, a variable:
msg: "{{ hostvars[groups[platform][0]]['service_url'] }}"
Our inventory in INI style looks like this:
foo-host ansible_host=1.2.3.4 some_var=bla
bar-host ansible_host=5.6.7.8 some_var=blup
I can limit a playbook run to a single host by using the host alias:
$ ansible-playbook playbook.yml --limit foo-host
But I can't limit the run by mentioning the host's IP address from the ansible_host variable:
$ ansible-playbook playbook.yml --limit 1.2.3.4
ERROR! Specified hosts and/or --limit does not match any hosts
The reason I want to do that is because Ansible is triggered by an external system that only knows the IP address, but not the alias.
Is there a way to make this work? Mangling the IP address (e.g. ip_1_2_3_4) would be acceptable.
Things I've considered:
Turn it on its head and identify all hosts by IP address:
1.2.3.4 some_var=bla
5.6.7.8 some_var=blup
But now we can't use the nice host aliases anymore, and the inventory file is less readable too.
Write a custom inventory script that is run after the regular inventory, and creates a group like ip_1_2_3_4 containing only that single host, so we can use --limit ip_1_2_3_4. But there's no way to access previously loaded inventory from inventory scripts, so I don't know which groups to create.
Create the new groups dynamically using the group_by module. But because this is a task, it is run only after --limit has already decided that there are no hosts matching the pattern, and at that point Ansible just gives up and doesn't run the group_by task anymore.
Better solutions still welcome, but currently I'm doing it with a small inventory plugin, which (as opposed to an inventory script) does have access to previously added inventory:
plugins/inventory/ip_based_groups.py
import os.path
import re
from ansible.plugins.inventory import BaseInventoryPlugin
from ansible.inventory.group import Group
PATH_PLACEHOLDER = 'IP_BASED_GROUPS'
IP_RE = re.compile('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
class InventoryModule(BaseInventoryPlugin):
'''
This inventory plugin does not create any hosts, but just adds groups based
on IP addresses. For each host whose ansible_host looks like an IPv4
address (e.g. 1.2.3.4), a corresponding group is created by prefixing the
IP address with 'ip_' and replacing dots by underscores (e.g. ip_1_2_3_4).
Use it by putting the literal string IP_BASED_GROUPS at the end of the list
of inventory sources.
'''
NAME = 'ip_based_groups'
def verify_file(self, path):
return self._is_path_placeholder(path)
def parse(self, inventory, loader, path, cache=True):
if not self._is_path_placeholder(path):
return
for host_name, host in inventory.hosts.items():
ansible_host = host.vars.get('ansible_host', '')
if self._is_ip_address(ansible_host):
group = 'ip_' + ansible_host.replace('.', '_')
inventory.add_group(group)
inventory.add_host(host_name, group)
def _is_path_placeholder(self, path):
return os.path.basename(path) == PATH_PLACEHOLDER
def _is_ip_address(self, s):
return bool(IP_RE.match(s))
ansible.cfg
[defaults]
# Load plugins from these directories.
inventory_plugins = plugins/inventory
# Directory that contains all inventory files, and placeholder to create
# IP-based groups.
inventory = inventory/,IP_BASED_GROUPS
[inventory]
# Enable our custom inventory plugin.
enable_plugins = ip_based_groups, host_list, script, auto, yaml, ini, toml
Q: Playbook running a script running a playbook... it would work, but it's a bit hacky
A: FWIW. It's possible to use json_query and avoid the script. For example
- hosts: all
gather_facts: false
tasks:
- set_fact:
my_host: "{{ hostvars|dict2items|json_query(query)|first }}"
vars:
query: "[?value.ansible_host == '{{ my_host_ip }}' ].key"
run_once: true
- add_host:
hostname: "{{ my_host }}"
groups: my_group
run_once: true
- hosts: my_group
gather_facts: false
tasks:
- debug:
var: inventory_hostname
Q: "Unfortunately I'm running ansible-playbook from AWX, so no wrapper scripts allowed."
A: It is possible to run the script from the playbook. For example the playbook below
- hosts: all
gather_facts: false
tasks:
- set_fact:
my_host: "{{ lookup('pipe', playbook_dir ~ '/script.sh ' ~ my_host_ip) }}"
delegate_to: localhost
run_once: true
- add_host:
hostname: "{{ my_host }}"
groups: my_group
run_once: true
- hosts: my_group
gather_facts: false
tasks:
- debug:
var: inventory_hostname
gives
$ ansible-playbook -e 'my_host_ip=10.1.0.53' play.yml
PLAY [all] ********************
TASK [set_fact] ***************
ok: [test_01 -> localhost]
TASK [add_host] ***************
changed: [test_01]
PLAY [my_group] ***************
TASK [debug] ************
ok: [test_03] => {
"inventory_hostname": "test_03"
}
(Fit the script to print the hostname only.)
Q: I can limit a playbook run to a single host by using the host alias:
$ ansible-playbook playbook.yml --limit foo-host
But I can't limit the run by mentioning the host's IP address from the ansible_host variable
$ ansible-playbook playbook.yml --limit 1.2.3.4
A: ansible-inventory and jq are able to resolve the host. For example the script
#!/bin/bash
my_host="$(ansible-inventory --list | jq '._meta.hostvars | to_entries[] | select (.value.ansible_host=="'"$1"'") | .key')"
my_host="$(echo $my_host | sed -e 's/^"//' -e 's/"$//')"
echo host: $my_host
ansible-playbook -l $my_host play.yml
with the inventory
test_01 ansible_host=10.1.0.51
test_02 ansible_host=10.1.0.52
test_03 ansible_host=10.1.0.53
and with the playbook.yml
- hosts: all
gather_facts: false
tasks:
- debug:
var: inventory_hostname
gives
$ ./script.bash 10.1.0.53
host: test_03
PLAY [all] **********
TASK [debug] ************
ok: [test_03] => {
"inventory_hostname": "test_03"
}
I have to goals with my plabook:
a) get mac address from a specific VM running on vsphere
b) add it to my inventory file
My test environment is:
-Vsphere 6.5
-Ansible 2.7 running on Centos 7.6
I have been able to make point a) following this post How retrieve the name of specific dictionary - Ansible
Playbook:
# Deploy a guest from a template*
- hosts: 127.0.0.1
vars:
vcenter_hostname: virtvcsami1.virtlab.local
vcenter_user: administrator#vsphere.local
vcenter_pass: Esxilab!1
vcenter_datastore: vsanDatastore
vcenter_datacenter: DatacenterMI
tasks:
- name: Gather VMware guest facts
vmware_vm_facts:
validate_certs: False
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_pass }}"
vm_type: vm
delegate_to: localhost
register: vm_guest_facts
- debug: msg="{{ item.value.mac_address }}"
loop: "{{ vm_guest_facts.virtual_machines|dict2items }}"
but now I still have two problems to solve:
Problem 1)
Playbook gets ALL virtual machines while I need to get just a VM named "testvm"
[root#nlnxmi1 testvmcdromiso]# ansible-playbook getvminfo.yml
PLAY [127.0.0.1] ***********************************************************************************************************************************************************************************************
TASK [Gathering Facts] *****************************************************************************************************************************************************************************************
ok: [127.0.0.1]
TASK [Gather VMware guest facts] *******************************************************************************************************************************************************************************
ok: [127.0.0.1 -> localhost]
TASK [debug] ***************************************************************************************************************************************************************************************************
ok: [127.0.0.1] => (item={'key': u'kubemst01', 'value': {u'guest_fullname': u'CentOS 7 (64-bit)', u'vm_network': {u'00:50:56:be:de:b9': {u'ipv4': [u'192.168.40.31'], u'ipv6': [u'fe80::250:56ff:febe:deb9']}, u'52:54:00:62:fe:fe': {u'ipv4': [u'192.168.122.1'], u'ipv6': []}}, u'cluster': u'VirtlabMI', u'esxi_hostname': u'virtesxmi3.virtlab.local', u'mac_address': [u'00:50:56:be:de:b9'], u'power_state': u'poweredOn', u'ip_address': u'192.168.40.31', u'uuid': u'423e7580-1ca4-a6ca-5cb4-5b8fa963d527'}}) => {
"msg": [
"00:50:56:be:de:b9"
]
}
ok: [127.0.0.1] => (item={'key': u'testvm', 'value': {u'guest_fullname': >u'CentOS 7 (64-bit)', u'vm_network': {}, u'cluster': u'VirtlabMI', >u'esxi_hostname': u'virtesxmi1.virtlab.local', u'mac_address': >[u'00:50:56:be:a3:a0'], u'power_state': u'poweredOn', u'ip_address': u'', >u'uuid': u'423e8645-ca2a-1097-2e1c-624810a461d1'}}) => {
"msg": [
"00:50:56:be:a3:a0"
]
}
......
Problem 2)
Add mac address to existing inventory file or, if not possible, at least in some file
I tried adding the following code at the end:
- set_fact: vm_mac_address="prova"
- name: Register host to Inventory
lineinfile:
path: /etc/ansible/testvmcdromiso/inventory
regexp: '(testvm)'
line: '\1 macaddres={{ vm_mac_address }}'
backrefs: yes
[root#nlnxmi1 testvmcdromiso]# cat inventory
[testhost]
testvm macaddress=prova
but as you can see I just used a "fixed" string instead I need to get the mac address from the running vm but never figure it out even after 2 days of attempts :(
I'm just a beginner with ansible. Could you please help me?
best
Marco
Playbook gets ALL virtual machines while I need to get just a VM named "testvm"
That's not a problem. It looks like the vmware_vm_facts module returns a dictionary, so just ask for the vm you want:
- debug: msg="{{ vm_guest_facts.virtual_machines['testvm'].mac_address }}"
Add mac address to existing inventory file or, if not possible, at least in some file
Since you now know how to retrieve the MAC address, you modify your lineinfile task make use of that information:
- name: Register host to Inventory
lineinfile:
path: /etc/ansible/testvmcdromiso/inventory
regexp: '(testvm)'
line: '\1 macaddres={{ vm_guest_facts.virtual_machines['testvm'].mac_address }}'
backrefs: yes
Recently started looking into ansible. So for testing purpose i try to provision a box. I am using vagrant, virtual box and ansible to proviosion my box but when i do vagrant provision its showing
skipping: no hosts matched
My Vagrantfile
config.vm.define"deploymaster" do |deploymaster|
deploymaster.vm.hostname = "deploymaster"
deploymaster.vm.network :private_network, ip: "192.168.33.10"
deploymaster.vm.network "forwarded_port", guest: 80, host: 6080, id: "http", auto_corect: true
deploymaster.vm.network "forwarded_port", guest: 443, host: 2201, id: "https"
deploymaster.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
end
deploymaster.vm.provision :ansible do |ansible|
ansible.playbook = "../playbooks/inventory-change-callback.yml"
ansible.inventory_path = "../inventory/ansible/hosts.yml"
ansible.become = true
ansible.verbose = "vvv"
end
end
inventory hosts.yml file
deploymaster:
hosts:
192.168.33.10:
hostname: deploymaster
elk:
hosts:
192.168.33.12:
hostname: elk
vars:
retention: 30
Update 1
Updated the inventory script
deploymaster:
hosts:
deploy-master:
ansible_host: 192.168.33.10
elk:
hosts:
elk-node:
ansible_host: 192.168.33.12
Playbook
---
- name: Set inventory path
hosts: deploymaster
gather_facts: no
- include: some-play-book.yml
I am importing my inventory file in vagrant file as mentioned here and here but still not able to fix it.What am i missing here.
I've been trying to debug this for a while now, and I thought I had it working, but then made some other changes, and now back again.
Basically, I have Vagrant looping over a list of machines definitions and while my Ansible inventory looks perfectly fine, I find that only one host is actually being provisioned.
Generated Ansible Inventory -- The SSH ports are all different, groups are correct
# Generated by Vagrant
kafka.cp.vagrant ansible_host=127.0.0.1 ansible_port=2200 ansible_user='vagrant' ansible_ssh_private_key_file='/workspace/confluent/cp-ansible/vagrant/.vagrant/machines/kafka.cp.vagrant/virtualbox/private_key' kafka='{"broker": {"id": 1}}'
zk.cp.vagrant ansible_host=127.0.0.1 ansible_port=2222 ansible_user='vagrant' ansible_ssh_private_key_file='/workspace/confluent/cp-ansible/vagrant/.vagrant/machines/zk.cp.vagrant/virtualbox/private_key'
connect.cp.vagrant ansible_host=127.0.0.1 ansible_port=2201 ansible_user='vagrant' ansible_ssh_private_key_file='/workspace/confluent/cp-ansible/vagrant/.vagrant/machines/connect.cp.vagrant/virtualbox/private_key'
[preflight]
zk.cp.vagrant
kafka.cp.vagrant
connect.cp.vagrant
[zookeeper]
zk.cp.vagrant
[broker]
kafka.cp.vagrant
[schema-registry]
kafka.cp.vagrant
[connect-distributed]
connect.cp.vagrant
Generated hosts file -- IPs and hostnames are correct
## vagrant-hostmanager-start id: aca1499c-a63f-4747-b39e-0e71ae289576
192.168.100.101 zk.cp.vagrant
192.168.100.102 kafka.cp.vagrant
192.168.100.103 connect.cp.vagrant
## vagrant-hostmanager-end
Ansible Playbook I want to run -- Correctly correspond to the groups in my inventory
- hosts: preflight
tasks:
- import_role:
name: confluent.preflight
- hosts: zookeeper
tasks:
- import_role:
name: confluent.zookeeper
- hosts: broker
tasks:
- import_role:
name: confluent.kafka-broker
- hosts: schema-registry
tasks:
- import_role:
name: confluent.schema-registry
- hosts: connect-distributed
tasks:
- import_role:
name: confluent.connect-distributed
For any code missing here, see Confluent :: cp-ansible.
The following is a sample of my Vagrantfile. (I made a fork, but haven't committed until I get this working...)
I know that this if index == machines.length - 1 should work according to the Vagrant documentation, and it does start all the machines, then only runs Ansible on the last machine, but its just all the tasks are executed on first one for some reason.
machines = {"zk"=>{"ports"=>{2181=>nil}, "groups"=>["preflight", "zookeeper"]}, "kafka"=>{"memory"=>3072, "cpus"=>2, "ports"=>{9092=>nil, 8081=>nil}, "groups"=>["preflight", "broker", "schema-registry"], "vars"=>{"kafka"=>"{\"broker\": {\"id\": 1}}"}}, "connect"=>{"ports"=>{8083=>nil}, "groups"=>["preflight", "connect-distributed"]}}
Vagrant.configure("2") do |config|
if Vagrant.has_plugin?("vagrant-hostmanager")
config.hostmanager.enabled = true
config.hostmanager.manage_host = true
config.hostmanager.ignore_private_ip = false
config.hostmanager.include_offline = true
end
# More info on http://fgrehm.viewdocs.io/vagrant-cachier/usage
if Vagrant.has_plugin?("vagrant-cachier")
config.cache.scope = :box
end
if Vagrant.has_plugin?("vagrant-vbguest")
config.vbguest.auto_update = false
end
config.vm.box = VAGRANT_BOX
config.vm.box_check_update = false
config.vm.synced_folder '.', '/vagrant', disabled: true
machines.each_with_index do |(machine, machine_conf), index|
hostname = getFqdn(machine.to_s)
config.vm.define hostname do |v|
v.vm.network "private_network", ip: "192.168.100.#{101+index}"
v.vm.hostname = hostname
machine_conf['ports'].each do |guest_port, host_port|
if host_port.nil?
host_port = guest_port
end
v.vm.network "forwarded_port", guest: guest_port, host: host_port
end
v.vm.provider "virtualbox" do |vb|
vb.memory = machine_conf['memory'] || 1536 # Give overhead for 1G default java heaps
vb.cpus = machine_conf['cpus'] || 1
end
if index == machines.length - 1
v.vm.provision "ansible" do |ansible|
ansible.compatibility_mode = '2.0'
ansible.limit = 'all'
ansible.playbook = "../plaintext/all.yml"
ansible.become = true
ansible.verbose = "vv"
# ... defined host and group variables here
end # Ansible provisioner
end # If last machine
end # machine configuration
end # for each machine
end
I setup an Ansible task like this
- debug:
msg: "FQDN: {{ansible_fqdn}}; Hostname: {{inventory_hostname}}; IPv4: {{ansible_default_ipv4.address}}"
Just with that task, notice that the following ansible_fqdn is always zk.cp.vagrant, and this lines up with the fact that only that VM is getting provisioned by Ansible.
ok: [zk.cp.vagrant] => {
"msg": "FQDN: zk.cp.vagrant; Hostname: zk.cp.vagrant; IPv4: 10.0.2.15"
}
ok: [kafka.cp.vagrant] => {
"msg": "FQDN: zk.cp.vagrant; Hostname: kafka.cp.vagrant; IPv4: 10.0.2.15"
}
ok: [connect.cp.vagrant] => {
"msg": "FQDN: zk.cp.vagrant; Hostname: connect.cp.vagrant; IPv4: 10.0.2.15"
}
Update with minimal example: hostname -f is only one host, and I assume that's what gather_facts is running for ansible_fqdn
ansible all --private-key=~/.vagrant.d/insecure_private_key --inventory-file=/workspace/confluent/cp-ansible/vagrant/.vagrant/provisioners/ansible/inventory -a 'hostname -f' -f1
zk.cp.vagrant | SUCCESS | rc=0 >>
kafka.cp.vagrant
connect.cp.vagrant | SUCCESS | rc=0 >>
kafka.cp.vagrant
kafka.cp.vagrant | SUCCESS | rc=0 >>
kafka.cp.vagrant
Turns out I can get around the problem with not having this section in my ansible.cfg
[ssh_connection]
control_path = %(directory)s/%%h-%%r