post header image. some inline jinja code

Using inline Jinja templates in Ansible set_fact module

Inline Jinja templates used with set_fact can be used to convert one complex data structure in to another without using a loop in Ansible, but I initially found doing so quite tricky.

The challenge

Today I had a requirement to read the data structure output from one module (the vmware_guest_disk_facts module), and refactor that in to a different data structure for input in to another module (the vmware_guest module).

I am deploying VMware VMs to a vCenter server using a template and the vmware_guest Ansible module. While doing so, I want to add one or more additional disks to the deployed VM. I can use the disks: option on the vmware_guest module to override the template’s disks, but to supplement them, I need to make sure I include the existing disks as well. Fortunately I can get details of the existing disks in the template using Ansible’s vmware_guest_disk_facts module.

Using the example given in the docs (https://docs.ansible.com/ansible/2.6/modules/vmware_guest_disk_facts_module.html) Here’s what the vmware_guest_disk_facts module might return:

{
  '0': {
    'backing_filename': '[datastore2] xxxx/xxxx.vmdk',
    'capacity_in_kb': 10240,
    'backing_disk_mode': 'persistent',
    'backing_eagerlyscrub': False,
    'backing_writethrough': False,
    'label': 'Hard disk 1',
    'backing_datastore': 'datastore2',
    'key': 2000,
    'capacity_in_bytes': 10485760,
    'backing_thinprovisioned': False,
    'controller_key': 1000,
    'summary': '10,240 KB',
    'unit_number': 0
  }
}

To add an additional 16Gb virtual hard disk to this VM during deployment, I need to pass the following to the vmware_guest module:

vmware_guest:
  ...
  hostname: "{{ vcenter_server_ip }}"
  username: "{{ vcenter_server_username }}"
  password: "{{ vcenter_server_password }}"
  name: "{{ inventory_hostname }}"
  template: "{{ template_name }}"
  disk:
    - size_kb: 10240                # <-- this is the disk in the template
      autoselect_datastore: true    #
    - size_gb: 16                   # <-- this is the disk I want to add
      autoselect_datastore: true    #
  ...

The challenge I set myself was to work out what to pass in the disk option to make sure the existing disk does not get overridden with the additional disk I want to add.

The solution

I am able to grab information about the existing disks in a template with the following task:

- name: "Get facts for named template"
  vmware_guest_disk_facts:
    hostname: "{{ vcenter_server_ip }}"
    username: "{{ vcenter_server_username }}"
    password: "{{ vcenter_server_password }}"
    validate_certs: no
    datacenter: "{{ vcenter_datacenter_name }}"
    name: "{{ template_name }}"
  register: template_disk_facts
  delegate_to: localhost

Using set_fact in an Ansible loop to convert the output of this in to the format I need is relatively simple:

- name: "Define new disk structure"
  set_fact:
    vm_disks: (vm_disks|default([])) + {'size_kb': {{ item[1].capacity_in_kb }}, 'autoselect_datastore': true}
  loop: template_disk_facts

But like all Ansible loops, this results in running the task once for each value in the loop. For elegance, I want to create the data structure in a single pass. An inline Jinja template is the solution:

- name: "Define new disk structure"
  set_fact:

    # Create the list of disks to be deployed. This needs to include
    # the disks present in the template plus any additional disks
    # specified for this host in (for example) an attribute called
    # additional_disks specified in the host's host_vars file.
    vm_disks: >-
      [{% for disk in (template_disk_facts.guest_disk_facts|dictsort) %}{
        'size_kb': {{ disk[1].capacity_in_kb }},
        'autoselect_datastore': true},
      {% endfor %}
      {% for disk in additional_disks|default([]) %}{
        {% if disk.size_gb is defined %}'size_gb': {{ disk.size_gb }},{% endif %}
        {% if disk.size_mb is defined %}'size_mb': {{ disk.size_mb }},{% endif %}
        {% if disk.size_kb is defined %}'size_kb': {{ disk.size_kb }},{% endif %}
        'autoselect_datastore': true},
      {% endfor %}]
  delegate_to: localhost

Now, deploying the template complete with the additional disk, I just need to pass the set fact in the vmware_guest task:

- vmware_guest: 
    ...   
    hostname: "{{ vcenter_server_ip }}"
    username: "{{ vcenter_server_username }}"
    password: "{{ vcenter_server_password }}"
    name: "{{ inventory_hostname }}"
    template: "{{ template_name }}"
    disk: "{{ vm_disks }}"
    ...

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.