Inserting default values into list of dicts in Ansible

385 views Asked by At

I have a list of web applications and their configs. Simplified data structure would look like this (list of dicts)

web_app_details: 
- web_sourcedir: UWT_Optimus_UI
  web_destdir: 'E:\alexsapp'
  loadUserProfile: false

- web_sourcedir: UWT_Optimus_UI
  web_destdir: 'E:\bodhi'

Now, there are some default values I don't want the user to specify every single time (single dict)

defaults_only:
  identityType: LocalSystem
  enable32BitAppOnWin64: false
  loadUserProfile: true

If the user does not provide a value in any field within the defaults_only dictionary, the defaults should apply. Otherwise, the defaults should be discarded.

So the result would be:

web_app_details: 
- web_sourcedir: UWT_Optimus_UI
  web_destdir: 'E:\alexsapp'
  identityType: LocalSystem
  enable32BitAppOnWin64: false
  loadUserProfile: false  # Because user provided the override value

- web_sourcedir: UWT_Optimus_UI
  web_destdir: 'E:\bodhi'
  identityType: LocalSystem
  enable32BitAppOnWin64: false
  loadUserProfile: true

I tried a few things such as

  1. Call a Powershell script to do this outside Ansible
  2. Duplicate the default dictionary as many times as in web_app_detail and then use combine filter
  3. Insert null values with a JMESPath query:
- name: Pad web_app_details with null values
  set_fact:   
    web_app_details_with_defaults: "{{ web_app_details | json_query(jmesquery) }}"
  vars:
    jmesquery: 
      "[*].{web_sourcedir: web_sourcedir, web_destdir: web_destdir, web_cleancopy: web_cleancopy }"

But nothing works.
Can someone please help?

2

There are 2 answers

0
β.εηοιτ.βε On BEST ANSWER

You could recreate that list adding the default with a combine filter.

This will take advantage of the fact that a property defined in both dictionaries would be overridden by the dictionary you are combining with.

So, you'll end up with this set_fact task:

- set_fact:
    web_app_details_with_defaults: >-
      {{
        web_app_details_with_defaults | default([]) + [
          defaults_only | combine(item)
        ]
      }}
  loop: "{{ web_app_details }}"

Given the couple of tasks:

- set_fact:
    web_app_details_with_defaults: >-
      {{
        web_app_details_with_defaults | default([]) + [
          defaults_only | combine(item)
        ]
      }}
  loop: "{{ web_app_details }}"
  vars:
    defaults_only:
      identityType: LocalSystem
      enable32BitAppOnWin64: false
      loadUserProfile: true
      
    web_app_details:
      - web_sourcedir: UWT_Optimus_UI
        web_destdir: 'E:\alexsapp'
        loadUserProfile: false

      - web_sourcedir: UWT_Optimus_UI
        web_destdir: 'E:\bodhi'

- debug:
    var: web_app_details_with_defaults

This yields the expected:

web_app_details_with_defaults:
- enable32BitAppOnWin64: false
  identityType: LocalSystem
  loadUserProfile: false
  web_destdir: E:\alexsapp
  web_sourcedir: UWT_Optimus_UI
- enable32BitAppOnWin64: false
  identityType: LocalSystem
  loadUserProfile: true
  web_destdir: E:\bodhi
  web_sourcedir: UWT_Optimus_UI
0
flowerysong On

There are multiple ways to approach this, but my preferred solution would be:

- hosts: localhost
  vars:
    web_app_details:
    - web_sourcedir: UWT_Optimus_UI
      web_destdir: 'E:\alexsapp'
      loadUserProfile: false
    - web_sourcedir: UWT_Optimus_UI
      web_destdir: 'E:\bodhi'

    defaults_only:
      identityType: LocalSystem
      enable32BitAppOnWin64: false
      loadUserProfile: true

    web_app_details_with_defaults: "{{ [defaults_only] | product(web_app_details) | map('combine') }}"

  tasks:
    - debug:
        msg: "{{ web_app_details_with_defaults }}"

The expression {{ [defaults_only] | product(web_app_details) | map('combine') }} creates a product of the lists [defaults_only] (an inline single-element list) and web_app_details, then uses map to combine each list of dictionaries into a single dictionary.

PLAY [localhost] ***************************************************************

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": [
        {
            "enable32BitAppOnWin64": false,
            "identityType": "LocalSystem",
            "loadUserProfile": false,
            "web_destdir": "E:\\alexsapp",
            "web_sourcedir": "UWT_Optimus_UI"
        },
        {
            "enable32BitAppOnWin64": false,
            "identityType": "LocalSystem",
            "loadUserProfile": true,
            "web_destdir": "E:\\bodhi",
            "web_sourcedir": "UWT_Optimus_UI"
        }
    ]
}

If you are on an ancient version of Ansible without automatic unrolling you will need to add | list at some points in the expression to convert generators into lists. Probably just {{ [defaults_only] | product(web_app_details) | map('combine') | list }}, but I haven't tested this.