How to use if/else condition in JMESPath

130 Views Asked by At

I'm trying to get the name and is_encrypted attributes of a JSON object based on condition. I.e.:

is_encrypted == YES if data_encryption.drive_protection_enabled == true or data_encryption.software_encryption_enabled == true else NO

This is how the facts variable looks like:

{

    "facts": {
        "changed": true,
        "failed": false,
        "response": {
            "num_records": 1,
            "records": [
                {
                    "data_encryption": {
                        "drive_protection_enabled": false,
                        "software_encryption_enabled": false
                    },
                    "name": "egg",
                    "uuid": "xxx-xxx"
                }

            ]
        },
        "status_code": 200
    }
}

Output like list of name and is_encrypted:

{
  "aggregate_name": "egg",
  "is_encrypted": "NO"
},

I'm trying below format with json_query

egg_info: "{{ egg_info | default ([]) + facts | json_query(\"response.records[*].{name: name, is_encrypted: 'YES' if (data_encryption.software_encryption_enabled == 'true' || data_encryption.drive_protection_enabled == 'true') else 'NO' }\") }}"

Ending up the error

{"msg": "JMESPathError in json_query filter plugin:\nExpecting: colon, got: lparen: Parse error at column 66, token "(" (LPAREN), for expression:\n"response.records[*].{name: name, is_encrypted: 'YES' if (data_encryption.software_encryption_enabled == 'true' || data_encryption.drive_protection_enabled == 'true') else 'NO' }"\n ^"}

2

There are 2 best solutions below

2
Vladimir Botka On

Q: "Use If Else condition in single JMESPath query."

A: There is no If Else condition in JMESPath. As a result, it is not possible to convert a boolean to an arbitrary string in json_query. A single query nearest to what you want would be

  egg_query: '[].{aggregate_name: name,
                  is_encrypted:
                  data_encryption.drive_protection_enabled ||
                  data_encryption.software_encryption_enabled}'
  egg_info: "{{ facts.response.records|json_query(egg_query) }}"

gives (see the below data for testing)

  egg_info:
  - aggregate_name: egg
    is_encrypted: false
  - aggregate_name: egg2
    is_encrypted: true
  - aggregate_name: egg3
    is_encrypted: true

Given the below data for testing
    facts:
      changed: true
      failed: false
      response:
        num_records: 1
        records:
        - data_encryption:
            drive_protection_enabled: false
            software_encryption_enabled: false
          name: egg
          uuid: 22-9e35-6c6ee18157b
        - data_encryption:
            drive_protection_enabled: true
            software_encryption_enabled: false
          name: egg2
          uuid: 22-9e35-6c6ee18157c
        - data_encryption:
            drive_protection_enabled: false
            software_encryption_enabled: true
          name: egg3
          uuid: 22-9e35-6c6ee18157d
      status_code: 200

Workaround: If you for whatever reason need strings ('YES', 'NO') instead of booleans (true, false) use json_query and create a dictionary

  egg_query: '[].[name,
                  data_encryption.drive_protection_enabled ||
                  data_encryption.software_encryption_enabled]'
  egg_dct1: "{{ dict(facts.response.records|json_query(egg_query)) }}"

gives

  egg_dct1:
    egg: false
    egg2: true
    egg3: true

Convert it to a list if you want to

  egg_inf1: "{{ egg_dct1|dict2items(key_name='aggregate_name',
                                    value_name='is_encrypted') }}"

gives

  egg_inf1:
    - {aggregate_name: egg, is_encrypted: false}
    - {aggregate_name: egg2, is_encrypted: true}
    - {aggregate_name: egg3, is_encrypted: true}

If you need strings instead of booleans create a dictionary

  egg_keys: "{{ egg_dct1.keys() }}"
  egg_vals: "{{ egg_dct1.values()|map('ternary', 'YES', 'NO') }}"
  egg_dct2: "{{ dict(egg_keys|zip(egg_vals)) }}"

gives

  egg_dct2:
    egg: 'NO'
    egg2: 'YES'
    egg3: 'YES'

and convert it to a list if you want to

  egg_inf2: "{{ egg_dct2|dict2items(key_name='aggregate_name',
                                    value_name='is_encrypted') }}"

gives

  egg_inf2:
    - {aggregate_name: egg, is_encrypted: 'NO'}
    - {aggregate_name: egg2, is_encrypted: 'YES'}
    - {aggregate_name: egg3, is_encrypted: 'YES'}

Example of a complete playbook for testing

- hosts: all

  vars:

    facts:
      changed: true
      failed: false
      response:
        num_records: 1
        records:
        - data_encryption:
            drive_protection_enabled: false
            software_encryption_enabled: false
          name: egg
          uuid: 22-9e35-6c6ee18157b
        - data_encryption:
            drive_protection_enabled: true
            software_encryption_enabled: false
          name: egg2
          uuid: 22-9e35-6c6ee18157c
        - data_encryption:
            drive_protection_enabled: false
            software_encryption_enabled: true
          name: egg3
          uuid: 22-9e35-6c6ee18157d
      status_code: 200

    egg_query: '[].[name,
                    data_encryption.drive_protection_enabled ||
                    data_encryption.software_encryption_enabled]'
    egg_dct1: "{{ dict(facts.response.records|json_query(egg_query)) }}"
    egg_inf1: "{{ egg_dct1|dict2items(key_name='aggregate_name',
                                      value_name='is_encrypted') }}"

    egg_keys: "{{ egg_dct1.keys() }}"
    egg_vals: "{{ egg_dct1.values()|map('ternary', 'YES', 'NO') }}"
    egg_dct2: "{{ dict(egg_keys|zip(egg_vals)) }}"
    egg_inf2: "{{ egg_dct2|dict2items(key_name='aggregate_name',
                                      value_name='is_encrypted') }}"

  tasks:

    - debug:
        var: egg_dct1
    - debug:
        var: egg_inf1|to_yaml

    - debug:
        var: egg_dct2
    - debug:
        var: egg_inf2|to_yaml
0
β.εηοιτ.βε On

You can use a list of JSON objects in order to find the string representation of a boolean and do a conversion to whatever value you are interested in.

For example:

[
  {
    key: 'false', 
    value: 'NO', 
    current_encryption: `false`
  },{
    key: 'true', 
    value: 'YES', 
    current_encryption: `false`
  }
][?key==to_string(current_encryption)].value | [0]

Would yield: "NO".

So, applying this to your input data:

- set_fact:
    egg_info: >-
      {{
        egg_info | default ([]) + facts | json_query(
          "response.records[*].{
            name: name,
            is_encrypted: [{
              key: 'false',
              value: 'NO',
              current_encryption: data_encryption.software_encryption_enabled
                || data_encryption.drive_protection_enabled
            },{
              key: 'true',
              value: 'YES',
              current_encryption: data_encryption.software_encryption_enabled
                || data_encryption.drive_protection_enabled
            }][?key==to_string(current_encryption)].value | [0]
          }"
        )
      }}

Would yield:

ok: [localhost] => changed=false 
  ansible_facts:
    egg_info:
    - is_encrypted: 'NO'
      name: egg

Note: the above output was generated running the playbook with the option -v, which, amongst other useful information, shows the result of a set_fact task.