Reputation: 3
After deployment of a VM with a DHCP IP I would like to get the IP and append it to the guests dictionary.
For the first VM (testvm2) the code perfoms as expected and updates the tempip variable for the testvm2 VM.
But with the second VM (testvm1), it updates the tempip variable of the first VM (testvm2) with the IP of the second VM (testvm1), and updates the tempip variable of the second VM (testvm1) with the code of the variable '{{ tempip_reg.stdout_lines }}'
Can anyone explain to me why this happens? I would appreciate the help.
I copied all the relevant code and output below:
guests dictionary:
---
guests:
testvm1:
mem: 512
cpus: 1
clone: template-centos
vmid: 102
tempip:
testvm2:
mem: 1536
cpus: 2
clone: template-centos
vmid: 102
tempip:
Ansible Playbook that starts the task:
---
- name: Provision VMs
hosts: infra
become: true
vars_files:
- group_vars/vms.yml
- group_vars/vars.yml
tasks:
- include_tasks: roles/tasks/provision-tasks.yml
with_dict: "{{ guests }}"
Ansible Tasks:
- name: Run proxmox-get-ip-linux.sh script to register DHCP IP of VM
script: proxmox-get-ip-linux.sh "{{ item.key }}"
register: tempip_reg
- name: temporary IP of VM "{{ item.key }}"
debug:
var: tempip_reg
- name: current host in item.key
set_fact:
current_host: "{{ item.key }}"
- name: current_host variable set to
debug:
var: current_host
- name: append item.value.tempip with the DHCP IP of the VM registered in
last task
set_fact:
guests: "{{ guests|combine({ current_host: {'tempip': '{{ tempip_reg.stdout_lines }}' }}, recursive=True) }}"
- name: temporary IP of "{{ item.key }}"
debug: var=guests
Result first VM:
"tempip_reg": {
"stdout": "192.168.1.21\r\n",
"stdout_lines": [
"192.168.1.21"
}
"current_host": "testvm2"
"guests": {
"testvm1": {
"clone": "template-centos",
"cpus": 1,
"ip": "192.168.1.60",
"mem": 512,
"tempip": null,
"vmid": 102
},
"testvm2": {
"clone": "template-centos",
"cpus": 2,
"ip": "192.168.1.61",
"mem": 1536,
"tempip": [
"192.168.1.21"
],
"vmid": 102
}
}
Result 2nd VM:
"tempip_reg": {
"stdout": "192.168.1.22\r\n",
"stdout_lines": [
"192.168.1.22"
}
"current_host": "testvm1"
"guests": {
"testvm1": {
"clone": "template-centos",
"cpus": 1,
"ip": "192.168.1.60",
"mem": 512,
"tempip": "{{ tempip_reg.stdout_lines }}",
"vmid": 102
},
"testvm2": {
"clone": "template-centos",
"cpus": 2,
"ip": "192.168.1.61",
"mem": 1536,
"tempip": [
"192.168.1.22"
],
"vmid": 102
}
}
Upvotes: 0
Views: 6092
Reputation: 68629
TL;DR
Using Ansible code, you are trying to implement what Ansible already does for you.
Your attempts superimpose with built-in functionality and you get results which look nondeterministic.
Explanation:
The main problem with your code is a completely unnecessary loop declared with with_dict: "{{ guests }}"
which causes to include the file 4 times.
It runs 4 times because you change the guests
dictionary, which it loops over inside the included tasks-file.
In effect you get something which looks like an nondeterministic result.
The second problem is a trivial one: you always replace the value of tempip
with a string {{ tempip_reg.stdout_lines }}
.
Now, because of the unnecessary with_dict
loop over a dictionary which you dynamically change, and because Jinja2 uses lazy variable evaluation, strings from previous iterations are interpreted as templates and get evaluated with incorrect values in subsequent iterations.
The last iteration leaves the string {{ tempip_reg.stdout_lines }}
intact.
You also define and print two different facts.
What you should do:
You should not declare arbitrary iterations at all. Ansible implements a loop for all hosts itself. That is, if you declare a task:
- include_tasks: roles/tasks/provision-tasks.yml
the file will be included for each of the hosts in infra
group (twice in your example).
You seem to want to have a single copy of your data structure with updated values for each VM.
At the same time, you create a fact, which is a separate data object maintained for each host separately.
So you should refer to and modify (combine
) a single fact - you can do it for example on localhost
.
You should structure your code like this:
---
- name: Provision VMs
hosts: infra
become: true
vars_files:
- group_vars/vms.yml
- group_vars/vars.yml
tasks:
- include_tasks: roles/tasks/provision-tasks.yml
- debug:
var: hostvars['localhost'].guests
and provision-tasks.yml
:
- set_fact:
guests: "{{ guests|combine({ current_host: {'tempip': tempip_reg.stdout_lines }}, recursive=True) }}"
delegate_to: localhost
This will get you the following result:
"hostvars['localhost'].guests": {
"testvm1": {
"clone": "template-centos",
"cpus": 1,
"ip": "192.168.1.60",
"mem": 512,
"tempip": [
"192.168.1.21"
],
"vmid": 102
},
"testvm2": {
"clone": "template-centos",
"cpus": 2,
"ip": "192.168.1.61",
"mem": 1536,
"tempip": [
"192.168.1.22"
],
"vmid": 102
}
}
Finally, in the above play, you used group_vars
and roles/tasks
directories in wrong context. I left the paths intact and they will work for the above code, but basically you should never use them this way, because again, they have special meaning and treatment in Ansible.
Upvotes: 2