Search code examples
jsonansibleansible-factsjmespathjson-query

Ansible - issue with loop and json_query's


Question

I need to get the ID from the GET because it's needed in the URL in the PUT task to edit a specific "input" entry. I'm using the Ansible URI to talk to a REST API to manage this.

playbook

*host_vars/host.yml *

---
inputs:
  - title: "test_input_api"
    type: "org.graylog2.inputs.syslog.udp.SyslogUDPInput"
    global: false
    configuration:
      allow_override_date: false
      bind_address: "0.0.0.0"
      expand_structured_data: false
      force_rdns: false
      number_worker_threads: 8
      override_source: null
      port: 5999
      recv_buffer_size: null
      store_full_message: true
  - title: "test_input_api_2"
    type: "org.graylog2.inputs.syslog.udp.SyslogUDPInput"
    global: false
    configuration:
      allow_override_date: false
      bind_address: "0.0.0.0"
      expand_structured_data: false
      force_rdns: false
      number_worker_threads: 8
      override_source: null
      port: 5998
      recv_buffer_size: null
      store_full_message: true

playbook.yml

---
- name: Configure system
  hosts: graylog
  connection: local
  gather_facts: no
  roles:
    - graylog/inputs

roles/graylog/inputs/tasks/main.yml

---
- include_tasks: get_inputs.yml
- include_tasks: put_inputs.yml

roles/graylog/inputs/tasks/get_inputs.yml

--- 
- name: "API GET System Inputs"
    uri:
      url: http://{{ ansible_host }}:9000/api/system/inputs
      url_username : "{{ system.users.triple_admin.api_token }}"
      url_password: token
      method: GET
      return_content: yes
    register: get_graylog_inputs

- name: Set Fact
  set_fact:
    get_input_id: "{{ get_graylog_inputs.content | from_json | json_query('inputs[?title == `{}`] | [0].id '.format(input.title)) }}"
  loop: "{{ inputs }}"
  loop_control:
    loop_var: input

The registered var from the get show's the following

{
    "json": {
        "inputs": [
            {
                "attributes": {
                    "allow_override_date": "False",
                    "bind_address": "0.0.0.0",
                    "expand_structured_data": "False",
                    "force_rdns": "False",
                    "number_worker_threads": 8,
                    "override_source": "",
                    "port": 5999,
                    "recv_buffer_size": "",
                    "store_full_message": "True"
                },
                "content_pack": null,
                "created_at": "2021-07-30T15:21:47.590Z",
                "creator_user_id": "triple_admin",
                "global": false,
                "id": "6104170beca15547502665d6",
                "name": "Syslog UDP",
                "node": "ba52ad48-0b13-419d-b957-d47d8911b413",
                "static_fields": {},
                "title": "test_input_api",
                "type": "org.graylog2.inputs.syslog.udp.SyslogUDPInput"
            },

roles/graylog/inputs/tasks/put_inputs.yml

---
  - name: "API PUT System Inputs"
    uri:
      url: http://{{ ansible_host }}:9000/api/system/inputs/{{ get_input_id }}
      url_username : "{{ system.users.triple_admin.api_token }}"
      url_password: token
      headers:
        X-Requested-By: X-Ansible
      method: PUT
      body_format: json
      body: "{{ lookup('template', 'templates/post_template.j2') }}"
      status_code: 201
      return_content: yes
    loop: "{{ inputs }}"
    loop_control:
      loop_var: input
    "ansible_facts": {
        "get_input_id": "61015085eca1554750236084",
        "get_input_titles": "test_input_api"
    },
    "ansible_facts": {
        "get_input_id": "610282d0eca155475024ac91",
        "get_input_titles": "test_input_api_2"

Results of running the play

loop 1 - this needs to be matched to the title and therefor get id "61015085eca1554750236084"

        "title": "test_input_api",
            "url": "http://192.168.21.82:9000/api/system/inputs/610282d0eca155475024ac91",

loop 2

        "title": "test_input_api_2",
            "url": "http://192.168.21.82:9000/api/system/inputs/610282d0eca155475024ac91",

All help is welcome !


Solution

    1. (not related) You don't need to json_decode the result get_graylog_inputs.content. If the server on the over side sends the correct Content-type: application/json header, you should have a get_graylog_inputs.json entry containing the already decoded json result.
    2. You don't need to loop twice. Remove the set_fact loop (which is not correct anyway) in your first file and use the value from your register directly in the second loop.
    3. You did not show any example of your input data so I have to guess a bit here from your jmespath expression... but you basically don't need json_query at all and can stick to generic core ansible filters.

    Here is how I see the solution in the second file once you cleaned-up the first:

    ---
      - name: "API PUT System Inputs"
        vars:
          get_input_id: "{{ get_graylog_inputs.json.inputs | selectattr('title', '==', input.title) | map(attribute='id') | first }}"
        uri:
          url: http://{{ ansible_host }}:9000/api/system/inputs/{{ get_input_id }}
          url_username : "{{ system.users.triple_admin.api_token }}"
          url_password: token
          headers:
            X-Requested-By: X-Ansible
          method: PUT
          body_format: json
          body: "{{ lookup('template', 'templates/post_template.j2') }}"
          status_code: 201
          return_content: yes
        loop: "{{ inputs }}"
        loop_control:
          loop_var: input
    

    You will probably have to debug and tune the expression to get the input id as I could not do it myself against an example data structure.