Use Jinja2 dict as part of an Ansible modules options - ansible

I have the following dict:
endpoint:
esxi_hostname: servername.domain.com
I'm trying to use it as an option via jinja2 for the vmware_guest but have been unsuccessful. The reason I'm trying to do it this way is because the dict is dynamic...it can either be cluster: clustername or esxi_hostname: hostname, both mutually exclusive in the vmware_guest module.
Here is how I'm presenting it to the module:
- name: Create VM pysphere
vmware_guest:
hostname: "{{ vcenter_hostname }}"
username: "{{ username }}"
password: "{{ password }}"
validate_certs: no
datacenter: "{{ ansible_host_datacenter }}"
folder: "/DCC/{{ ansible_host_datacenter }}/vm"
"{{ endpoint }}"
name: "{{ guest }}"
state: present
guest_id: "{{ osid }}"
disk: "{{ disks }}"
networks: "{{ niclist }}"
hardware:
memory_mb: "{{ memory_gb|int * 1024 }}"
num_cpus: "{{ num_cpus|int }}"
scsi: "{{ scsi }}"
customvalues: "{{ customvalues }}"
cdrom:
type: client
delegate_to: localhost
And here is the error I'm getting when including the tasks file:
TASK [Preparation : Include VM tasks] *********************************************************************************************************************************************************************************
fatal: [10.10.10.10]: FAILED! => {"reason": "Syntax Error while loading YAML.
The error appears to have been in '/data01/home/hit/tools/ansible/playbooks/roles/Preparation/tasks/prepareVM.yml': line 36, column 4, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
"{{ endpoint }}"
hostname: "{{ vcenter_hostname }}"
^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
exception type: <class 'yaml.parser.ParserError'>
exception: while parsing a block mapping
in "<unicode string>", line 33, column 3
did not find expected key
in "<unicode string>", line 36, column 4"}
So in summary, I'm not sure how to format this or if it is even possible.

The post from techraf sums up your problem, but for a possible solution, in the docs, especially regarding Jinja filters, there is the following bit:
Omitting Parameters
As of Ansible 1.8, it is possible to use the default filter to omit
module parameters using the special omit variable:
- name: touch files with an optional mode
file: dest={{item.path}} state=touch mode={{item.mode|default(omit)}} > with_items:
- path: /tmp/foo
- path: /tmp/bar
- path: /tmp/baz
mode: "0444"
For the first two files in the list, the default mode will be
determined by the umask of the system as the mode= parameter will not
be sent to the file module while the final file will receive the
mode=0444 option.
So it looks like what should be tried is:
esxi_hostname: "{{ endpoint.esxi_hostname | default(omit) }}"
# however you want the alternative cluster settings done.
# I dont know this module.
cluster: "{{ cluster | default(omit) }}"
This is obviously reliant on the vars to only have one choice set.

There is no way you could ever use the syntax you tried in the question, because firstly and foremostly Ansible requires a valid YAML file.
The closest workaround would be to use a YAML anchor/alias although it would work only with literals:
# ...
vars:
endpoint: &endpoint
esxi_hostname: servername.domain.com
tasks:
- name: Create VM pysphere
vmware_guest:
hostname: "{{ vcenter_hostname }}"
username: "{{ username }}"
password: "{{ password }}"
validate_certs: no
datacenter: "{{ ansible_host_datacenter }}"
folder: "/DCC/{{ ansible_host_datacenter }}/vm"
<<: *endpoint
name: "{{ guest }}"
state: present
guest_id: "{{ osid }}"
disk: "{{ disks }}"
networks: "{{ niclist }}"
hardware:
memory_mb: "{{ memory_gb|int * 1024 }}"
num_cpus: "{{ num_cpus|int }}"
scsi: "{{ scsi }}"
customvalues: "{{ customvalues }}"
cdrom:
type: client
delegate_to: localhost

Related

Ansible vmware_guest runonce not being executed

I have the following tasks to deploy a Windows 2016 VM from template. All my customization is done correctly except the runonce command that is not executed. While running the playbook even in debug mode there is no error or warning.
- name: "[DEEPLOY-INSTANCE] Create a VM Win From a template"
vmware_guest:
hostname: "{{ vmware.host }}"
username: "{{ vmware.username }}"
password: "{{ vmware.password }}"
validate_certs: False
datacenter: "{{ vm_datacenter }}"
cluster: "{{ vm_cluster }}"
name: "{{ vm_name }}"
template: "{{ vm_template_full }}"
folder: "{{ vm_folder }}"
datastore: "{{ vm_datastore }}"
hardware:
num_cpus: "{{ vm_cpu }}"
memory_mb: "{{ vm_ram }}"
networks:
- name: "{{ vm_netname }}"
start_connected: yes
ip: "{{ vm_ip }}"
netmask: "{{ vm_mask }}"
gateway: "{{ vm_gw }}"
type: static
customization:
fullname: "Windows Server"
orgname: "My Company"
timezone: 110
dns_servers:
- 10.100.100.10
- 10.100.110.10
domain: "{{ domain }}"
joindomain: "{{ domain }}"
password: "{{ template_pass }}"
domainadmin: "{{ vmware.username }}"
domainadminpassword: "{{ vmware.password }}"
runonce:
- 'powershell.exe -ExecutionPolicy Unrestricted -File C:\Admin\Scripts\RunOnce.bat'
register: vm_facts
delegate_to: localhost
I tried putting the whole runonce command between single and double quotes but the output is the same. If I check the log C:\WINDOWS\TEMP\vmware-imc\guestcust.log there is no mention to even trying to attempt the execution of the runone but there is information about some customization being performed.
Ansible version is 2.9
EDIT
Changed indexation of runonce to how I have it now although it does not work either, with or without the quotes. As a workaround, I managed to launch that .bat file on a separate task with the module vmware_vm_shell
Can you correct your indentation like below and try it once
runonce:
- powershell.exe -ExecutionPolicy Unrestricted -File C:\Admin\Scripts\RunOnce.bat
Refer this issue:
https://github.com/ansible/ansible/issues/28312

Building a VM using Ansible Tower and I want to have the ability to create additional disks only when their variables are defined

I'm currently building Ansible image playbook templates across AWS and VSphere and I'd like be able to define multiple additional disks but only when they're defined via the variables.
Playbook:
---
- hosts: localhost
connection: local
gather_facts: False
tasks:
- name: Launch Windows 2016 VM Instance
vmware_guest:
validate_certs: no
datacenter: "{{ vm_datacenter }}"
folder: "{{ vm_folder }}"
name: "{{ vm_servername }}"
state: poweredon
template: "{{ vm_template }}"
cluster: "{{ vm_cluster }}"
disk:
- size_gb: "{{ vm_disk_size0 | default(80) }}"
type: "{{ vm_disk_type0 | default(thin) }}"
datastore: "{{ vm_disk_datastore0 }}"
- size_gb: "{{ vm_disk_size1 }}"
type: "{{ vm_disk_type1 }}"
datastore: "{{ vm_disk_datastore1 }}"
- size_gb: "{{ vm_disk_size2 }}"
type: "{{ vm_disk_type2 }}"
datastore: "{{ vm_disk_datastore2 }}"
- size_gb: "{{ vm_disk_size3 }}"
type: "{{ vm_disk_type3 }}"
datastore: "{{ vm_disk_datastore3 }}"
hardware:
memory_mb: "{{ vm_memory_mb | default(8192) }}"
num_cpus: "{{ vm_num_cpus | default(4) }}"
networks:
- name: "{{ vm_network }}"
start_connected: yes
vlan: "{{ vm_network }}"
device_type: vmxnet3
type: dhcp
domain: "{{ vm_domain }}"
customization:
hostname: "{{ vm_servername }}"
orgname: Redacted
password: "{{ winlocal_admin_pass }}"
timezone: 255
wait_for_ip_address: yes
register: vm
Variables:
vm_disk_datastore0: C6200_T2_FCP_3Days
vm_disk_size0: 80
vm_disk_type0: thin
vm_disk_datastore1: "C6200_T2_FCP_3Days"
vm_disk_size1: "50"
vm_disk_type1: "thin"
vm_disk_datastore2: "C6200_T2_FCP_3Days"
vm_disk_size2: "20"
vm_disk_type2: "thin"
vm_disk_datastore3: ""
vm_disk_size3: ""
vm_disk_type3: ""
Error:
{
"_ansible_parsed": false,
"exception": "Traceback (most recent call last):\n File \"/var/lib/awx/.ansible/tmp/ansible-tmp-1568948672.23-135785453591577/AnsiballZ_vmware_guest.py\", line 113, in <module>\n _ansiballz_main()\n File \"/var/lib/awx/.ansible/tmp/ansible-tmp-1568948672.23-135785453591577/AnsiballZ_vmware_guest.py\", line 105, in _ansiballz_main\n invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n File \"/var/lib/awx/.ansible/tmp/ansible-tmp-1568948672.23-135785453591577/AnsiballZ_vmware_guest.py\", line 48, in invoke_module\n imp.load_module('__main__', mod, module, MOD_DESC)\n File \"/tmp/ansible_vmware_guest_payload_6vidiw/__main__.py\", line 2396, in <module>\n File \"/tmp/ansible_vmware_guest_payload_6vidiw/__main__.py\", line 2385, in main\n File \"/tmp/ansible_vmware_guest_payload_6vidiw/__main__.py\", line 2008, in deploy_vm\n File \"/tmp/ansible_vmware_guest_payload_6vidiw/__main__.py\", line 1690, in configure_disks\n File \"/tmp/ansible_vmware_guest_payload_6vidiw/__main__.py\", line 1608, in get_configured_disk_size\nValueError: invalid literal for int() with base 10: ''\n",
"_ansible_no_log": false,
"module_stderr": "Traceback (most recent call last):\n File \"/var/lib/awx/.ansible/tmp/ansible-tmp-1568948672.23-135785453591577/AnsiballZ_vmware_guest.py\", line 113, in <module>\n _ansiballz_main()\n File \"/var/lib/awx/.ansible/tmp/ansible-tmp-1568948672.23-135785453591577/AnsiballZ_vmware_guest.py\", line 105, in _ansiballz_main\n invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n File \"/var/lib/awx/.ansible/tmp/ansible-tmp-1568948672.23-135785453591577/AnsiballZ_vmware_guest.py\", line 48, in invoke_module\n imp.load_module('__main__', mod, module, MOD_DESC)\n File \"/tmp/ansible_vmware_guest_payload_6vidiw/__main__.py\", line 2396, in <module>\n File \"/tmp/ansible_vmware_guest_payload_6vidiw/__main__.py\", line 2385, in main\n File \"/tmp/ansible_vmware_guest_payload_6vidiw/__main__.py\", line 2008, in deploy_vm\n File \"/tmp/ansible_vmware_guest_payload_6vidiw/__main__.py\", line 1690, in configure_disks\n File \"/tmp/ansible_vmware_guest_payload_6vidiw/__main__.py\", line 1608, in get_configured_disk_size\nValueError: invalid literal for int() with base 10: ''\n",
"changed": false,
"module_stdout": "",
"rc": 1,
"msg": "MODULE FAILURE\nSee stdout/stderr for the exact error"
}
The idea being, if the variable isn't defined when the job is launched, it will be skipped by the vm_guest module.
Is this the best method? Is anyone able to suggest a successful way forward?
Update: Probably the best method would be to build the instance and then use vm_guest_disk to add additional disks using the method suggested below. This module is available in v2.8.
Our version of Tower is 2.7.9, so I'm going to use a more verbose method of multiple vm_guest calls via a disk_num variable:
Variables
num_disks: 0
vm_cluster: C6200_NPE_PC_ST
vm_datacenter: DC2
vm_disk_datastore0: C6200_T2_FCP_3Days
vm_disk_datastore1: ''
vm_disk_datastore2: ''
vm_disk_datastore3: ''
vm_disk_datastore4: ''
vm_disk_size0: 80
vm_disk_type0: thin
vm_disk_type1: ''
vm_disk_type2: ''
vm_disk_type3: ''
vm_disk_type4: ''
vm_domain: corp.local
vm_folder: /DC2/vm/ap-dev
vm_hostname: xyzvmserver.corp.local
vm_memory_mb: 16000
vm_network: C6200_10.110.64.0_24_VL1750
vm_num_cpus: 4
vm_servername: server05
vm_template: Windows2016_x64_AN_ESX_v1.1
Playbook:
---
- hosts: localhost
connection: local
gather_facts: False
tasks:
- name: Launch Windows 2016 VM Instance - No additional Disk
vmware_guest:
validate_certs: no
datacenter: "{{ vm_datacenter }}"
folder: "{{ vm_folder }}"
name: "{{ vm_servername }}"
state: poweredon
template: "{{ vm_template }}"
cluster: "{{ vm_cluster }}"
disk:
- size_gb: "{{ vm_disk_size0 }}"
type: "{{ vm_disk_type0 }}"
datastore: "{{ vm_disk_datastore0 }}"
hardware:
memory_mb: "{{ vm_memory_mb | default(8192) }}"
num_cpus: "{{ vm_num_cpus }}"
networks:
- name: "{{ vm_network }}"
start_connected: yes
vlan: "{{ vm_network }}"
device_type: vmxnet3
type: dhcp
domain: "{{ vm_domain }}"
customization:
hostname: "{{ vm_servername }}"
orgname: redacted
password: "{{ winlocal_admin_pass }}"
timezone: 255
wait_for_ip_address: yes
register: vm
when: num_disks == 0
- name: Launch Windows 2016 VM Instance 1 Additional Disk
vmware_guest:
validate_certs: no
datacenter: "{{ vm_datacenter }}"
folder: "{{ vm_folder }}"
name: "{{ vm_servername }}"
state: poweredon
template: "{{ vm_template }}"
cluster: "{{ vm_cluster }}"
disk:
- size_gb: "{{ vm_disk_size0 }}"
type: "{{ vm_disk_type0 }}"
datastore: "{{ vm_disk_datastore0 }}"
- size_gb: "{{ vm_disk_size1 }}"
type: "{{ vm_disk_type1 }}"
datastore: "{{ vm_disk_datastore1 }}"
hardware:
memory_mb: "{{ vm_memory_mb | default(8192) }}"
num_cpus: "{{ vm_num_cpus }}"
networks:
- name: "{{ vm_network }}"
start_connected: yes
vlan: "{{ vm_network }}"
device_type: vmxnet3
type: dhcp
domain: "{{ vm_domain }}"
customization:
hostname: "{{ vm_servername }}"
orgname: redacted
password: "{{ winlocal_admin_pass }}"
timezone: 255
wait_for_ip_address: yes
register: vm
when: num_disks == 1
- name: Launch Windows 2016 VM Instance 2 Additional Disks
vmware_guest:
validate_certs: no
datacenter: "{{ vm_datacenter }}"
folder: "{{ vm_folder }}"
name: "{{ vm_servername }}"
state: poweredon
template: "{{ vm_template }}"
cluster: "{{ vm_cluster }}"
disk:
- size_gb: "{{ vm_disk_size0 }}"
type: "{{ vm_disk_type0 }}"
datastore: "{{ vm_disk_datastore0 }}"
- size_gb: "{{ vm_disk_size1 }}"
type: "{{ vm_disk_type1 }}"
datastore: "{{ vm_disk_datastore1 }}"
- size_gb: "{{ vm_disk_size2 }}"
type: "{{ vm_disk_type2 }}"
datastore: "{{ vm_disk_datastore2 }}"
hardware:
memory_mb: "{{ vm_memory_mb | default(8192) }}"
num_cpus: "{{ vm_num_cpus }}"
networks:
- name: "{{ vm_network }}"
start_connected: yes
vlan: "{{ vm_network }}"
device_type: vmxnet3
type: dhcp
domain: "{{ vm_domain }}"
customization:
hostname: "{{ vm_servername }}"
orgname: redacted
password: "{{ winlocal_admin_pass }}"
timezone: 255
wait_for_ip_address: yes
register: vm
when: num_disks == 2
etc
It looks like you should build your vm with 1 disk, that you know for sure will always be there when you run the play, which you could make to be the first variable in this list:
vms:
0:
vm_disk_datastore: "C6200_T2_FCP_3Days"
vm_disk_size: "80"
vm_disk_type: "thin"
1:
vm_disk_datastore: "C6200_T2_FCP_3Days"
vm_disk_size: "50"
vm_disk_type: "thin"
2:
vm_disk_datastore: "C6200_T2_FCP_3Days"
vm_disk_size: "20"
vm_disk_type: "thin"
3:
vm_disk_datastore: ""
vm_disk_size: ""
vm_disk_type: ""
Since I don't know of a way that you can loop through sub module arguments to make them present or not, build your vm with the one disk you know for sure will be there. Which mentioned above, for consistency, should be the first one in your list. Or you will need to rebuild the logic to drill into the list looking for a specific value that you can key in on with jinja filters like selectattr and map.
- name: Launch Windows 2016 VM Instance
vmware_guest:
validate_certs: no
datacenter: "{{ vm_datacenter }}"
folder: "{{ vm_folder }}"
name: "{{ vm_servername }}"
state: poweredon
template: "{{ vm_template }}"
cluster: "{{ vm_cluster }}"
disk:
- size_gb: "{{ vms.0.vm_disk_size }}"
type: "{{ vms.0.vm_disk_type }}"
datastore: "{{ vms.0.vm_disk_datastore }}"
hardware:
memory_mb: "{{ vm_memory_mb | default(8192) }}"
num_cpus: "{{ vm_num_cpus | default(4) }}"
networks:
- name: "{{ vm_network }}"
start_connected: yes
vlan: "{{ vm_network }}"
device_type: vmxnet3
type: dhcp
domain: "{{ vm_domain }}"
customization:
hostname: "{{ vm_servername }}"
orgname: Redacted
password: "{{ winlocal_admin_pass }}"
timezone: 255
wait_for_ip_address: yes
register: vm
Once its built, then use this different module to drill into adding additional disks
- name: Add disks to virtual machine
vmware_guest_disk:
hostname: "{{ vm_servername }}"
datacenter: "{{ vm_datacenter }}"
disk:
- size_gb: "{{ item.vm_disk_size }}"
type: "{{ item.vm_disk_type }}"
datastore: "{{ item.vm_disk_datastore }}"
state: present
loop: "{{ vms }}"
loop_control:
label: "Disk {{ my_idx}} - {{ item.vm_disk_datastore }}"
index_var: my_idx
when:
- item.vm_disk_datastore != ""
- item.vm_disk_size != ""
- item.vm_disk_type != ""
- my_idx > 0
That will feed through a loop pulling that variable in from where ever you decided to define it, and then loop through that list of dictonaries pulling the values with the correct labels, but only doing so if they have a value (if leaving blanks in that list is something you plan on doing, if not, the when isn't even needed). This will also allow you to expand upward by just putting more items in the list without having to expand out your task. If you need to edit the first task because you can't rely on list order, but need to key into a variable, then you need to make sure you make a change to the disks on the second task as well to omit using the disk added in the first task.
Also, the documentation on this module states that existing disks called called with this module will be adjusted to that size, and can't be reduced using the module, so if you want to avoid that, put in some data checks to compare the configured disk size to what is in your variable, so you can skip the task if it is going to be reduced (or if you just don't want an existing vm to be altered).

I am struggling with the with_dict module

I am trying to automate VMware builds using Ansible. I am expecting a workflow engine to output a file that would act as a var_file and have all of the objects that can be used to build the VM using the vmware_guest module. It works great until you get to the networks dictionary portion of the module then it falls apart.
I initially tried setting up a vars_file with all of the variables like this:
---
validate_certs: no
datacenter: this is the DC
cluster: this is the cluster
folder: "this is the folder"
name: some-server
template: template-name
datastore: "datastore-name"
netname: This is the network
ip: 10.6.6.10
netmask: 255.255.255.0
gateway: 10.6.6.1
mac: aa:bb:dd:aa:00:14
domain: domain.com
However, that returned:
argument networks is of type <type 'dict'> and we were unable to convert to list: <type 'dict'> cannot be converted to a list"}
Where the code fails is on this task:
- name: Clone a virtual machine from Windows template and customize
vmware_guest:
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ password }}"
validate_certs: "{{ validate_certs }}"
datacenter: "{{ datacenter }}"
cluster: "{{ cluster }}"
folder: "{{ folder }}"
name: "{{name }}"
template: "{{ template }}"
datastore: "{{ datastore}}"
networks:
name: "{{ netname }}"
ip: "{{ ip }}"
netmask: "{{ netmask }}"
gateway: "{{ gateway }}"
mac: "{{ mac }}"
domain: "{{ domain }}"```
I tried creating a dictionary in the variable file like this:
---
validate_certs: no
datacenter: this is the DC
cluster: this is the cluster
folder: "this is the folder"
name: some-server
template: template-name
datastore: "datastore-name"
bnetworks:
name: This is the network
ip: 10.6.6.10
netmask: 255.255.255.0
gateway: 10.6.6.1
mac: aa:bb:dd:aa:00:14
domain: americas.global-legal.com
And changed the task to include this:
networks:
name: "{{ item.value.name }}"
ip: "{{ item.value.ip }}"
netmask: "{{ item.value.netmask }}"
gateway: "{{ item.value.gateway }}"
mac: "{{ item.value.mac }}"
domain: "{{ item.value.domain }}"
with_dict: bnetworks```
And I get this error:
The task includes an option with an undefined variable. The error was: 'item' is undefined
Any help would be appreciated.
argument networks is of type <type 'dict'> and we were unable to convert to list: cannot be converted to a list"}
There might be more networks in one VM therefor a list is needed. The corect syntax is below
- name: Clone a virtual machine from Windows template and customize
vmware_guest:
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ password }}"
validate_certs: "{{ validate_certs }}"
datacenter: "{{ datacenter }}"
cluster: "{{ cluster }}"
folder: "{{ folder }}"
name: "{{name }}"
template: "{{ template }}"
datastore: "{{ datastore}}"
networks:
- name: "{{ netname }}"
ip: "{{ ip }}"
netmask: "{{ netmask }}"
gateway: "{{ gateway }}"
mac: "{{ mac }}"
domain: "{{ domain }}"```
This is a list
list:
- key: value
This is a dictionary
dictionary:
key: value
This is a list of dictionaries
dictionary:
- key1: value-1-1
key2: value-2-1
- key1: value-2-1
key2: value-2-2
The task includes an option with an undefined variable. The error was: 'item' is undefined
The indentation of with_dict is wrong. The correct syntax is below.
vmware_guest:
...
networks:
- name: "{{ item.value.name }}"
ip: "{{ item.value.ip }}"
netmask: "{{ item.value.netmask }}"
gateway: "{{ item.value.gateway }}"
mac: "{{ item.value.mac }}"
domain: "{{ item.value.domain }}"
with_dict: "{{ bnetworks }}"

Ansible - skip undefined variable in dict

I`m using ipa_user module to setup users. There is variable passsword which force new password.
For some users (when var is not in dict) I would like to skip it in iteration, but it always fail.
This is snippet from my playbook. Ansible version is 2.7
task:
- name: adding ipa users
ipa_user:
name: "{{ item.value.login }}"
state: "{{ item.value.state }}"
givenname: "{{ item.value.givenname }}"
sn: "{{ item.value.surname }}"
mail: "{{ item.value.mail }}"
telephonenumber: "{{ item.value.telephonenumber }}"
title: "{{ item.value.title }}"
password: "{{ item.value.password }}" <<- to be skipped if not found
ipa_host: ipa.gdi.telekom.de
ipa_user: admin
ipa_pass: "{{ ipa_pass }}"
with_dict: "{{ipausers}}"
when: item.key in ipausers.keys()
register: output_ipa_users
Log:
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'password'\n\nThe error appears to have been in '/builds/gitlab/infra/user-management/roles/free-ipa/tasks/main.yml': line 13, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: adding ipa users\n ^ here\n"}
Note: I tried it with:
with_dict: "{{ipausers|default({})}}"
ignore_errors: yes
without success
Not sure if it'll be much help to you now but for others than stumble on this post, I ended up with something like below for a similar problem. I'm using Ansible 2.7.8.
- name: Creating user accounts...
user:
name: "{{ item.name }}"
state: "{{ item.state }}"
comment: "{{ item.comment | default(omit) }}"
group: "{{ item.groups is defined | ternary((item.groups|default([]))[0], omit) }}"
groups: "{{ item.groups | default(omit) }}"
password: "{{ item.password_hash | default(omit) }}"
uid: "{{ item.uid | default(omit) }}"
with_items: "{{ managed_users }}"
The solution is
group: "{{ item.groups is defined | ternary((item.groups|default([]))[0], omit) }}"
If groups isn't in item then Ansible will omit the group part of this tasks but jinja2 will evaluate item.groups[0] anyway. So to allow for this we have to use item.groups|default([]) so jinja2 uses an empty list when groups isn't defined instead of throwing a 'dict object' has no attribute error. The omit part is similar to the default(omit) filter where Ansible simply omits the option from the task.
Lubo's problem is a little simpler so using just default(omit) filter should work. That said as password is required so the entire task should be skipped with a conditional.
- name: adding ipa users
ipa_user:
name: "{{ item.value.login }}"
state: "{{ item.value.state }}"
givenname: "{{ item.value.givenname }}"
sn: "{{ item.value.surname }}"
mail: "{{ item.value.mail }}"
telephonenumber: "{{ item.value.telephonenumber }}"
title: "{{ item.value.title }}"
password: "{{ item.value.password | default(omit) }}" #<-- would be omitted
ipa_host: ipa.gdi.telekom.de
ipa_user: admin
ipa_pass: "{{ ipa_pass }}"
with_dict: "{{ipausers}}"
when: item.key in ipausers.keys() and item.key.password is defined #<-- second check for when password is not defined.
register: output_ipa_users
If you want to completely skip the ipa_user module execution when password is not defined, check for its presence in your when clause:
when: item.value.password | default('') | length > 0
If you want to execute the ipa_user module without specifying a password for user if it does not exists, use the omit placeholder in your module params:
password: "{{ item.value.password | default(omit) }}"
Note: your current when clause can be removed. It will always return true as you are looping over a dict and later checking if the current key in the loop is part of that dict.
There is a special omit variable to omit module parameters.
password: "{{ item.value.password|default(omit) }}"
To make a playbook or a role reusable it is a good idea to declare all parameters of a module in the task and default(omit) parameters that are not required.

Loop through variables in task that has another list embedded in the variables

I am looking to loop through a list of variables. I have it looping through the of variables using with_items, however the catch is there is a list within that variables list that needs to have a different subset / number of variables that i need to iterate through as well.
I have tried different filters to include with_nested, with_subelements, and with_items. I know that they are moving towards loops as the primary driver moving forward so any solution ideally would leverage the ansible path moving forward. I am looking at having an "inner" loop or an external task that will iterate through the vlans_list and input that data as its to that point.
group Variables
vnic_templates:
- name: vNIC-A
fabric: A
mac_pool: testmac1
mtu: 1500
org_dn: org-root
redundancy_type: none
state: present
template_type: initial-template
vlans_list: ### THE PROBLEM CHILD
- name: vlan2
native: 'no'
state: present
- name: vlan3
native: 'no'
state: present
The actual task - i have issues when i have to input multiple vlans. The vnic template will have a 1 to one relationship however the vlans_list could be 1 vnic_template to many vlans.
ucs_vnic_template:
hostname: "{{ ucs_manager_hostname }}"
username: "{{ ucs_manager_username }}"
password: "{{ ucs_manager_password }}"
name: "{{ item.name }}"
fabric: "{{ item.fabric }}"
mac_pool: "{{ item.mac_pool }}"
mtu: "{{ item.mtu }}"
org_dn: "{{ item.org_dn }}"
redundancy_type: "{{ item.redundancy_type }}"
state: "{{ item.state }}"
template_type: "{{ item.template_type }}"
vlans_list:
- name: "{{ item.1.name }}"
native: "{{ item.1.native }}"
state: "{{ item.1.present }}"
# loop: "{{ vnic_templates | subelements('vlans_list') }}"
with_items:
- "{{ vnic_templates }}"
I am starting down the road of adding an include vlan_list.yml outside of this task but no familiar with out to do that.
Actual results are
The task includes an option with an undefined variable. The error was: 'item' is undefined\n\n
I need the create a single vnic template with multiple vlans defined in that list.
Another engineer i work with was able to solve the question. By the way the variables are laid out we were able to easily just change the code
Change this:
vlans_list:
- name: "{{ item.1.name }}"
native: "{{ item.1.native }}"
state: "{{ item.1.present }}"
To this:
vlans_list: "{{ item.vlans_list }}"
Full Code listed below.
- name: Add vNIC Templates
ucs_vnic_template:
hostname: "{{ ucs_manager_hostname }}"
username: "{{ ucs_manager_username }}"
password: "{{ ucs_manager_password }}"
name: "{{ item.name }}"
fabric: "{{ item.fabric }}"
mac_pool: "{{ item.mac_pool }}"
mtu: "{{ item.mtu }}"
org_dn: "{{ item.org_dn }}"
redundancy_type: "{{ item.redundancy_type }}"
state: "{{ item.state }}"
template_type: "{{ item.template_type }}"
vlans_list: "{{ item.vlans_list }}"
with_items:
- "{{ vnic_templates }}"

Resources