Search code examples
ansiblejinja2

Generate valid dictionary in Ansible with condition depends


I want to generate valid dictionary in Ansible with condition depends for sending it by uli module (as json) and try to use Jinja2:

    - name: set_fact
      set_fact:
        request_body:
          matchers: |
            {% if "myserver" in server__fqdn %}
            - name: "host"
              value: "{{ server__fqdn }}"
              type: "EQUAL"
            - name: "node"
              value: "myserver"
              type: "EQUAL"
            {% else %}
            - name: "host"
              value: "{{ server__fqdn }}"
              type: "EQUAL"
            {% endif %}
          teamName: "{{ server__team }}"

    - name: send data to endpoint
      vars:
        endpoint_url: "http://myendpoint.org"
      uri:
        url: "{{ endpoint_url }}/api/v1/data"
        body: "{{ request_body }}"
        body_format: json
        headers:
          Content-Type: "application/json"
          Authorization: "endpointKey {{ provision__endpoint_data_token }}"
        method: POST
      register: data_status
      ignore_errors: true

I checking type of generated var request_body with debug:

    - name: debug
      debug:
        msg:
          - "{{ request_body  }}"
          - "{{ request_body |  type_debug }}"

and it show valid dict:

TASK [provision : debug] ************************************************************************************************************************************************************************
четверг 19 октября 2023  16:39:06 +0300 (0:00:00.078)       0:00:05.771 ******* 
ok: [56040] => 
  msg:
    matchers: |-
      - name: "host"
        value: "myserver.local"
        type: "EQUAL"
      - name: "node"
        value: "myserver"
        type: "EQUAL"
    teamName: superduper_team
  - dict

But task with sending var to endpoint failed:

TASK [provision : send data to endpoint] ********************************
четверг 19 октября 2023  16:39:06 +0300 (0:00:00.039)       0:00:05.810 ******* 
fatal: [myserver.local]: FAILED! => changed=false 
  connection: close
  content_length: '262'
  content_type: application/json
  date: Thu, 19 Oct 2023 13:39:07 GMT
  elapsed: 0
  json:
    code: 3
    details: []
    message: 'proto: syntax error (line 1:14): unexpected token "- name: \"host\"\n  value: \"myserver.local\"\n  type: \"EQUAL\"\n- name: \"node\"\n  value: \"myserver\"\n  type: \"EQUAL\"\n"'
  msg: 'Status code was 400 and not [200]: HTTP Error 400: Bad Request'
  redirected: false
  server: nginx
  status: 400
  url: http://myendpoint.org
  vary: Origin
...ignoring

But, when I do not use var generation it works:

    - name: send data to endpoint
      vars:
        endpoint_url: "http://myendpoint.org"
        request_body:
          matchers:
            - name: "host"
              value: "myserver.local"
              type: "EQUAL"
            - name: "node"
              value: "myserver"
              type: "EQUAL"
      uri:
        url: "{{ endpoint_url }}/api/v1/data"
        body: "{{ request_body | to_json }}"
        body_format: json
        headers:
          Content-Type: "application/json"
          Authorization: "endpointKey {{ provision__endpoint_data_token }}"
        method: POST
      register: data_status

Where is my problem?


Solution

  • Convert the YAML block to the list. Use filter

      request_body:
        matchers: |
          {% filter from_yaml %}
          {% if "myserver" in server__fqdn %}
          - name: "host"
            value: "{{ server__fqdn }}"
            type: "EQUAL"
          - name: "node"
            value: "myserver"
            type: "EQUAL"
          {% else %}
          - name: "host"
            value: "{{ server__fqdn }}"
            type: "EQUAL"
          {% endif %}
          {% endfilter %}
        teamName: "{{ server__team }}"
    

    Example of a complete playbook for testing

    - hosts: all
    
      vars:
    
        server__fqdn: myserver.local
        server__team: superduper_team
    
        request_body:
          matchers: |
            {% filter from_yaml %}
            {% if "myserver" in server__fqdn %}
            - name: "host"
              value: "{{ server__fqdn }}"
              type: "EQUAL"
            - name: "node"
              value: "myserver"
              type: "EQUAL"
            {% else %}
            - name: "host"
              value: "{{ server__fqdn }}"
              type: "EQUAL"
            {% endif %}
            {% endfilter %}
          teamName: "{{ server__team }}"
    
      tasks:
    
        - debug:
            var: request_body|type_debug
        - debug:
            var: request_body
        - debug:
            var: request_body|to_json
    

    gives

    shell> ansible-playbook pb2.yml -l test_11
    
    PLAY [all] ***********************************************************************************
    
    TASK [debug] *********************************************************************************
    ok: [test_11] => 
      request_body|type_debug: dict
    
    TASK [debug] *********************************************************************************
    ok: [test_11] => 
      request_body:
        matchers:
        - name: host
          type: EQUAL
          value: myserver.local
        - name: node
          type: EQUAL
          value: myserver
        teamName: superduper_team
    
    TASK [debug] *********************************************************************************
    ok: [test_11] => 
      request_body|to_json: '{"matchers": [{"name": "host", "value": "myserver.local", "type": "EQUAL"}, {"name": "node", "value": "myserver", "type": "EQUAL"}], "teamName": "superduper_team"}'
    
    PLAY RECAP ***********************************************************************************
    test_11: ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    

    The next option is to create a dictionary

      matchers_dict:
        true:
          - name: host
            value: "{{ server__fqdn }}"
            type: EQUAL
          - name: node
            value: myserver
            type: EQUAL
        false:
          - name: host
            value: "{{ server__fqdn }}"
            type: EQUAL
    

    and select the attribute depending on the condition

      matchers: "{{ matchers_dict['myserver' in server__fqdn] }}"
      request_body:
        teamName: "{{ server__team }}"
        matchers: "{{ matchers }}"
    

    Example of a complete playbook for testing

    - hosts: all
    
      vars:
    
        server__fqdn: myserver.local
        server__team: superduper_team
    
        matchers_dict:
          true:
            - name: host
              value: "{{ server__fqdn }}"
              type: EQUAL
            - name: node
              value: myserver
              type: EQUAL
          false:
            - name: host
              value: "{{ server__fqdn }}"
              type: EQUAL
        matchers: "{{ matchers_dict['myserver' in server__fqdn] }}"
        request_body:
          teamName: "{{ server__team }}"
          matchers: "{{ matchers }}"
    
      tasks:
    
        - debug:
            var: request_body|type_debug
        - debug:
            var: request_body
        - debug:
            var: request_body|to_json
    

    Optionally, use the filter ternary

      matchers_dict:
        A:
          - name: host
            value: "{{ server__fqdn }}"
            type: EQUAL
          - name: node
            value: myserver
            type: EQUAL
        B:
          - name: host
            value: "{{ server__fqdn }}"
            type: EQUAL
      matchers: "{{ ('myserver' in server__fqdn)|
                    ternary(matchers_dict.A, matchers_dict.B) }}"