Loop through List

Hi,

I would like to add a VM with vmware guest. I would like to keep it flexibel, some VMs will have one disk, some two disks. Currently I am using 2 tasks with a when: 1disk or 2 condition but that is no nice solution.

Example Task:

  • name: Create a VM with multiple disks of different disk controller types
    community.vmware.vmware_guest:
    hostname: “{{ vcenter_hostname }}”
    username: “{{ vcenter_username }}”

    datastore: datastore1
    disk:
  • size_gb: “{{ disks[0].size_gb }}” #30
    unit_number: 0
  • size_gb: “{{ disks[1].size_gb }}” #10
    unit_number: 1
    networks:
  • name: VM Network
    device_type: vmxnet3
    delegate_to: localhost
    register: deploy_vm

host A vars file:

disks:

  • size_gb: 30
  • size_gb: 10

host B vars file:

disks:

  • size_gb: 30

Any ideas how to achieve this in a nicer way ? I tried also using loops but without success. Leaving the 2nd “size_gb:” empty does not work.

Many thanks
Rainer

We created a JSON list that specifies a number of things for different server types. One JSON list provides Linux disk specs. One JSON list provides Windows disk specs.

fs_spec: [

linux apache web server servers

{ profile: apache , name: system , device: a, size: 75, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 0, owner: root, group: root, perms: 0755 }, # /
{ profile: apache , name: sites , device: b, size: 32, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 0, owner: root, group: root, perms: 0755 }, # /sites

linux commvault media servers

{ profile: commvault , name: system , device: a, size: 75, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 0, owner: root, group: root, perms: 0755 }, # /
{ profile: commvault , name: ddb , device: b, size: 512, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 0, owner: root, group: root, perms: 0755 }, # /ddb
{ profile: commvault , name: index , device: c, size: 512, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 1, owner: root, group: root, perms: 0755 }, # /index

linux docker container servers

{ profile: docker , name: system , device: a, size: 75, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 0, owner: root, group: root, perms: 0755 }, # /
{ profile: docker , name: containers , device: b, size: 100, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 0, owner: root, group: root, perms: 0755 }, # /containers

linux general purpose servers

{ profile: general , name: system , device: a, size: 75, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 0, owner: root, group: root, perms: 0755 }, # /

linux CBS INFO servers

{ profile: cbsinfo , name: system , device: a, size: 80, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 0, owner: root, group: root, perms: 0755 }, # /
{ profile: cbsinfo , name: apps , device: b, size: 64, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 0, owner: oranist, group: dba, perms: 0755 }, # /apps
{ profile: cbsinfo , name: ftp , device: c, size: 64, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 1, owner: ftp, group: ftp, perms: 0750 }, # /ftp

linux MySQL database servers

{ profile: mysql , name: system , device: a, size: 75, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 0, owner: root, group: root, perms: 0755 }, # /
{ profile: mysql , name: tmp , device: b, size: 100, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 1, owner: root, group: root, perms: 0755 }, # /tmp
{ profile: mysql , name: apps , device: c, size: 512, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 0, owner: oranist, group: dba, perms: 0755 }, # /apps
{ profile: mysql , name: archive , device: d, size: 512, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 1, owner: oranist, group: dba, perms: 0755 }, # /archive
{ profile: mysql , name: mydata , device: e, size: 512, type: thin, ctrl_type: paravirtual, ctrl: 2, unit: 0, owner: mysql, group: dba, perms: 0755 }, # /mydata
{ profile: mysql , name: mybackups , device: f, size: 1024, type: thin, ctrl_type: paravirtual, ctrl: 2, unit: 1, owner: mysql, group: dba, perms: 0755 }, # /mybackups

linux nginx web server servers

{ profile: nginx , name: system , device: a, size: 75, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 0, owner: root, group: root, perms: 0755 }, # /
{ profile: nginx , name: sites , device: b, size: 32, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 0, owner: root, group: root, perms: 0755 }, # /sites

linux Oracle database servers

{ profile: oracle , name: system , device: a, size: 80, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 0, owner: root, group: root, perms: 0755 }, # /
{ profile: oracle , name: swap , device: b, size: 64, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 1, owner: root, group: root, perms: 0755 }, # swap
{ profile: oracle , name: tmp , device: c, size: 64, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 2, owner: root, group: root, perms: 0755 }, # /tmp
{ profile: oracle , name: apps , device: d, size: 192, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 0, owner: oranist, group: dba, perms: 0755 }, # /apps
{ profile: oracle , name: external , device: e, size: 32, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 1, owner: oranist, group: dba, perms: 0755 }, # /external
{ profile: oracle , name: archive , device: f, size: 128, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 2, owner: oranist, group: dba, perms: 0755 }, # /archive
{ profile: oracle , name: oradata , device: g, size: 1024, type: thin, ctrl_type: paravirtual, ctrl: 2, unit: 0, owner: oranist, group: dba, perms: 0750 }, # /oradata
{ profile: oracle , name: orafra , device: h, size: 1024, type: thin, ctrl_type: paravirtual, ctrl: 2, unit: 1, owner: oranist, group: dba, perms: 0750 }, # /orafra

linux Oracle + MySQL database servers

{ profile: oracle_mysql , name: system , device: a, size: 75, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 0, owner: root, group: root, perms: 0755 }, # /
{ profile: oracle_mysql , name: swap , device: b, size: 64, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 1, owner: root, group: root, perms: 0755 }, # swap
{ profile: oracle_mysql , name: tmp , device: c, size: 64, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 2, owner: root, group: root, perms: 0755 }, # /tmp
{ profile: oracle_mysql , name: apps , device: d, size: 192, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 0, owner: oranist, group: dba, perms: 0755 }, # /apps
{ profile: oracle_mysql , name: external , device: e, size: 50, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 1, owner: oranist, group: dba, perms: 0755 }, # /external
{ profile: oracle_mysql , name: archive , device: d, size: 512, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 2, owner: oranist, group: dba, perms: 0755 }, # /archive
{ profile: oracle_mysql , name: oradata , device: f, size: 768, type: thin, ctrl_type: paravirtual, ctrl: 2, unit: 0, owner: oranist, group: dba, perms: 0750 }, # /oradata
{ profile: oracle_mysql , name: orafra , device: g, size: 1024, type: thin, ctrl_type: paravirtual, ctrl: 2, unit: 1, owner: oranist, group: dba, perms: 0750 }, # /orafra
{ profile: oracle_mysql , name: mydata , device: h, size: 640, type: thin, ctrl_type: paravirtual, ctrl: 3, unit: 0, owner: mysql, group: dba, perms: 0755 }, # /mydata
{ profile: oracle_mysql , name: mybackups , device: i, size: 960, type: thin, ctrl_type: paravirtual, ctrl: 3, unit: 1, owner: mysql, group: dba, perms: 0755 }, # /mybackups

linux tomcat application servers

{ profile: tomcat , name: system , device: a, size: 75, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 0, owner: root, group: root, perms: 0755 }, # /
{ profile: tomcat , name: sites , device: b, size: 32, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 0, owner: root, group: root, perms: 0755 }, # /sites

linux ASD tomcat/weblogic application servers

{ profile: tomweb , name: system , device: a, size: 75, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 0, owner: root, group: root, perms: 0755 }, # /
{ profile: tomweb , name: u01 , device: b, size: 100, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 0, owner: root, group: root, perms: 0755 }, # /u01
{ profile: tomweb , name: data , device: c, size: 100, type: thin, ctrl_type: paravirtual, ctrl: 2, unit: 0, owner: root, group: root, perms: 0755 }, # /data

linux ASD APPNODE (tomcat/weblogic) application servers

{ profile: appnode , name: system , device: a, size: 75, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 0, owner: root, group: root, perms: 0755 }, # /
{ profile: appnode , name: u01 , device: b, size: 100, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 0, owner: root, group: root, perms: 0755 }, # /u01
{ profile: appnode , name: data , device: c, size: 100, type: thin, ctrl_type: paravirtual, ctrl: 2, unit: 0, owner: root, group: root, perms: 0755 }, # /data
{ profile: appnode , name: apps , device: d, size: 100, type: thin, ctrl_type: paravirtual, ctrl: 3, unit: 0, owner: root, group: root, perms: 0755 }, # /apps

linux ASD subversion/jira

{ profile: subversion , name: system , device: a, size: 75, type: thin, ctrl_type: paravirtual, ctrl: 0, unit: 0, owner: root, group: root, perms: 0755 }, # /
{ profile: subversion , name: u01 , device: b, size: 20, type: thin, ctrl_type: paravirtual, ctrl: 1, unit: 0, owner: root, group: root, perms: 0755 }, # /u01
{ profile: subversion , name: data , device: c, size: 200, type: thin, ctrl_type: paravirtual, ctrl: 2, unit: 0, owner: root, group: root, perms: 0755 }, # /data

all preceding lines must end with a comma - makes it easier to copy sections above to create new server roles - json_query ignores this

{ the: end }
]

use [0:-1] to drop off the last line “{ the: end }”

valid_linux_types: “{{ fs_spec[0:-1] | map(attribute=‘profile’) | sort | unique }}”

fs_spec_windows: [

Windows commvault media servers

{ profile: commvault , disk_number: 0 , name: system , device: c, size: 100, type: thin, ctrl_type: lsilogicsas, ctrl: 0, unit: 0, block: 4096 }, # OS/Sys
{ profile: commvault , disk_number: 1 , name: Index , device: e, size: 100, type: thin, ctrl_type: lsilogicsas, ctrl: 1, unit: 0, block: 4096 }, # Index
{ profile: commvault , disk_number: 2 , name: DDB , device: f, size: 100, type: thin, ctrl_type: lsilogicsas, ctrl: 2, unit: 0, block: 4096 }, # DDB

Windows general purpose servers

{ profile: general , disk_number: 0 , name: system , device: c, size: 100, type: thin, ctrl_type: lsilogicsas, ctrl: 0, unit: 0, block: 4096 }, # OS/Sys
{ profile: general , disk_number: 1 , name: Data1 , device: e, size: 100, type: thin, ctrl_type: lsilogicsas, ctrl: 1, unit: 0, block: 4096 }, # Data1

Windows IIS web servers

{ profile: iis , disk_number: 0 , name: system , device: c, size: 100, type: thin, ctrl_type: lsilogicsas, ctrl: 0, unit: 0, block: 4096 }, # OS/Sys
{ profile: iis , disk_number: 1 , name: Data1 , device: e, size: 300, type: thin, ctrl_type: lsilogicsas, ctrl: 1, unit: 0, block: 4096 }, # Data1

Windows MS SQL servers

{ profile: mssql , disk_number: 0 , name: system , device: c, size: 100, type: thin, ctrl_type: lsilogicsas, ctrl: 0, unit: 0, block: 4096 }, # OS/Sys
{ profile: mssql , disk_number: 1 , name: Data1 , device: e, size: 100, type: thin, ctrl_type: lsilogicsas, ctrl: 0, unit: 1, block: 65536 }, # Data1
{ profile: mssql , disk_number: 2 , name: Data2 , device: f, size: 100, type: thin, ctrl_type: lsilogicsas, ctrl: 0, unit: 2, block: 65536 }, # Data2
{ profile: mssql , disk_number: 3 , name: Backup , device: g, size: 100, type: thin, ctrl_type: lsilogicsas, ctrl: 1, unit: 0, block: 4096 }, # Backup
{ profile: mssql , disk_number: 4 , name: Index , device: i, size: 5, type: thin, ctrl_type: lsilogicsas, ctrl: 1, unit: 1, block: 65536 }, # Index
{ profile: mssql , disk_number: 5 , name: Log , device: l, size: 50, type: thin, ctrl_type: lsilogicsas, ctrl: 2, unit: 0, block: 65536 }, # Log
{ profile: mssql , disk_number: 6 , name: SQL , device: p, size: 75, type: thin, ctrl_type: lsilogicsas, ctrl: 2, unit: 1, block: 4096 }, # SQL Program
{ profile: mssql , disk_number: 7 , name: TempDB , device: t, size: 50, type: thin, ctrl_type: lsilogicsas, ctrl: 3, unit: 0, block: 65536 }, # TempDB

all preceding lines must end with a comma - makes it easier to copy sections above to create new server roles - json_query ignores this

{ the: end }

]

use [0:-1] to drop off the last line “{ the: end }”

valid_windows_types: “{{ fs_spec_windows[0:-1] | map(attribute=‘profile’) | sort | unique }}”

We build our VMs from templates that all have a single system disk. Note the template param references an os_type var. We have templates for windows2016, windows2019, redhat7, redhat8, redhat9, ubuntu20, ubuntu22, etc. Our service portal lets the user choose which OS they want and that sends is one of these names as the template to source for the new VM.

NOTE that we create the VM “poweredoff”. We don’t power it on until we have added the disks it will need. We do that so that the first boot from the template already has all the raw storage attached to it.

  • name: create the guest vm using template
    community.vmware.vmware_guest:
    validate_certs: no
    hostname: “{{ vcenter[location|lower].vc }}”
    datacenter: “{{ vcenter[location|lower].dc }}”
    cluster: “{{ vcenter[location|lower].cl }}”
    name: “{{ vm_guest_name | lower }}”
    state: poweredoff
    template: “{{ os_type }}”
    folder: “{{ esx_folder }}”
    datastore: “{{ vcenter[location|lower].ds }}”
    hardware:
    hotadd_cpu: yes
    hotadd_memory: yes
    memory_mb: “{{ vm_spec[vm_size].ram }}”
    num_cpus: “{{ vm_spec[vm_size].cpu }}”
    networks:
  • name: “VLAN_{{ vlan }}”
    type: dhcp
    start_connected: yes
    connected: yes
    wait_for_ip_address: no
    delegate_to: localhost
    register: newvm

We use a json_query filter to extract the specific items from the list that we need for a given server type. This task sets fs_list to just the list of disks associated with a given server type. Note the start of the jinja template selects which JSON list to source. We get a param from our service portal for fs_type that we use to filter the JSON list via json_query.

  • name: collect disk list for {{ fs_type }} machine
    set_fact:
    fs_list: “{{ (fs_spec if my_family != ‘windows’ else fs_spec_windows) | json_query(‘[?profile=='+fs_type+']’) }}”

We then take our fs_list and eliminate the system disk (since that comes with the template). We also create an empty disk list that will hold the final disk list we want to add.

  • name: Initialize disk lists
    set_fact:
    type_list: “{{ (fs_spec if my_family != ‘windows’ else fs_spec_windows) | json_query(‘[?profile=='+fs_type+' && name!=system]’) }}”
    disk_list:

We then massage a “additional disks” list into a disk param we can send to VMware.

  • name: Build vmware_guest_disk list from filesystem list
    set_fact:
    disk_list: “{{ disk_list + this_list }}”
    loop: “{{ type_list }}”
    vars:
    this_list: [ { size_gb: “{{ item.size }}”, type: “{{ item.type }}”, datastore: “{{ vcenter[location|lower].ds }}”, scsi_type: “{{ item.ctrl_type }}”, scsi_controller: “{{ item.ctrl }}”, unit_number: “{{ item.unit }}” } ]

We then call vmware_guest_disk to add the disks (on the condition the system has more than one).

  • name: Add disk(s) to {{ vm_guest_name | lower }}
    community.vmware.vmware_guest_disk:
    datacenter: “{{ vcenter[location|lower].dc }}”
    hostname: “{{ vcenter[location|lower].vc }}”
    name: “{{ vm_guest_name | lower }}”
    validate_certs: no
    disk: “{{ disk_list }}”
    when: (disk_list|length) > 0

Lastly we power on the new VM. When the VM boots for the first time and does its device discovery it will find all the raw disks needed for the type of server we are standing up.

  • name: Power Up New Machine ({{ vm_guest_name | lower }})
    community.vmware.vmware_guest:
    hostname: “{{ vcenter[location|lower].vc }}”
    datacenter: “{{ vcenter[location|lower].dc }}”
    cluster: “{{ vcenter[location|lower].cl }}”
    validate_certs: no
    name: “{{ vm_guest_name | lower }}”
    state: poweredon
    delegate_to: localhost

Every server we stand up on VMware goes through these tasks. We have more tasks that add tags, etc, but this is the basic framework.

The disk spec JSON lists are in a vars file. We easily can add more disk configurations by expanding the JSON lists. The ‘profile’ is the key in the JSON lists that we match to ‘os_type’ we receive from our service portal. If we want to create a new server type with a different disk layout then we offer a new os_type value in the service portal and defined a new disk list with a new profile key in the JSON list. We do this for resource settings too (CPU, RAM), and all the other parameters we use in our vmware_guest task.

Our philosophy is to make absolutely everything parameter driven. This means the tasks never have to change. The source dictionaries and JSON lists that we source for our parameters are what we change, along with the corresponding values we offer in our service portal. We let users choose the VLAN, CPU/RAM, OS, server purpose, location (VMware instance), and backup strategy.

Walter

Hi, Walter Rowe,
thanks for your provided solution. For sure I will have a closer look at it.
Rainer

Your other option is to look up a properly described disk list in a dictionary before you run vmware_guest and use that disk list in vmware_guest as you create the new machine. You need to specify all the attributes required.

Walter Rowe