How to access key/pair values within objects inside of an array in Ansible?

So, we are wanting to propagate a list of logged-in users to our hosts. We have a batch script that does this for us and parses it to JSON format.

    - name: Process win_shell output
      set_fact:
        qusers: "{{ qusers_ps.stdout | from_json }}"

    - debug:
        var: qusers
        verbosity: 2

This debug outputs the following:

{
    "qusers": {
        "Server1": []
    },
    "_ansible_verbose_always": true,
    "_ansible_no_log": false,
    "changed": false
}
{
    "qusers": {
        "Server2": [
            {
                "USERNAME": "user102",
                "SESSIONNAME": "rdp-tcp#0",
                "ID": "6",
                "STATE": "Active",
                "IDLE TIME": "32",
                "LOGON TIME": "5/29/2020 9:13 AM"
            }
        ]
    },
    "_ansible_verbose_always": true,
    "_ansible_no_log": false,
    "changed": false
}
{
    "qusers": {
        "Server3": [
            {
                "USERNAME": "user183",
                "SESSIONNAME": "",
                "ID": "49",
                "STATE": "Disc",
                "IDLE TIME": "14:34",
                "LOGON TIME": "5/28/2020 7:58 AM"
            },
            {
                "USERNAME": "user103",
                "SESSIONNAME": "",
                "ID": "51",
                "STATE": "Disc",
                "IDLE TIME": "18:26",
                "LOGON TIME": "5/28/2020 8:18 AM"
            },
            {
                "USERNAME": "user148",
                "SESSIONNAME": "",
                "ID": "52",
                "STATE": "Disc",
                "IDLE TIME": "17:10",
                "LOGON TIME": "5/28/2020 9:08 AM"
            }
        ]
    },
    "_ansible_verbose_always": true,
    "_ansible_no_log": false,
    "changed": false
}

So you can see that debug returns JSON with the server name as the root element, and then an array of objects with key/pair values. We need to be able to query values within these array objects, such as 'USERNAME' and 'IDLE TIME' for each server. I've been able to do this statically (shown below) to grab the user of the first array object, but I can't figure out how to do this dynamically.

    - name: Test selecting JSON output and register as array
      debug:
        msg: "{{ item.value }}"
      loop: "{{ q('dict', qusers) }}"
      register: user_op

    - name: Set array variable
      set_fact:
        user_array: "{{ user_op.results }}"

    - name: Print item value # Prints array object as is
      debug:
        msg: "{{ item.item.value }} "
      with_items: "{{ user_array }}"

    - name: Test pulling JSON from array with conditional # This one works, but only grabs the first user
      debug: 
        msg: "{{ item.item.value[0]['USERNAME'] }} is logged into {{item.item.key }}"
      with_items: "{{ user_array }}"
      when: item.item.value != [] 

The last play is the only time I've ever been able to reach a specific key/pair element. How do I modify this to dynamically perform this operation for every user session object in the array instead of only the first, and without hard coding numbers??

Upvotes: 1

Views: 2742

Answers (1)

Zeitounator
Zeitounator

Reputation: 44615

I cannot easily reproduce your play loop over your three hosts, so I made a demo with your example data and a prompt allowing to choose which host to display. Of course this will be taken in charge directly for you when you repoduce this in your current playbook.

The idea is to:

  1. transform the dict to a list as you already did using dict2items (same result as the dict lookup you used).
  2. grab only the first item of the list as this is your data model
  3. grab the key of the item as the name of the current server to display
  4. grab the value of the item and loop over it since it is our list of users
  5. display the info we need without even carring about the list being empty or not. Empty list means empty loop therefore nothing will happen in that case.

Below my MCVE and a play for the third element. You can play yourself for the other ones. As you can see this can actually fit in a single task once you have loaded the info from your json output.

---
- hosts: localhost
  gather_facts: false

  vars_prompt:
    - name: host
      prompt: "display host ? (1-3)"
      private: no
      default: 1

  vars:
    "qusers1": {
      "Server1": []
    }
    "qusers2": {
      "Server2": [
        {
          "USERNAME": "user102",
          "SESSIONNAME": "rdp-tcp#0",
          "ID": "6",
          "STATE": "Active",
          "IDLE TIME": "32",
          "LOGON TIME": "5/29/2020 9:13 AM"
        }
      ]
    }
    "qusers3": {
      "Server3": [
      {
        "USERNAME": "user183",
        "SESSIONNAME": "",
        "ID": "49",
        "STATE": "Disc",
        "IDLE TIME": "14:34",
        "LOGON TIME": "5/28/2020 7:58 AM"
      },
      {
        "USERNAME": "user103",
        "SESSIONNAME": "",
        "ID": "51",
        "STATE": "Disc",
        "IDLE TIME": "18:26",
        "LOGON TIME": "5/28/2020 8:18 AM"
      },
      {
        "USERNAME": "user148",
        "SESSIONNAME": "",
        "ID": "52",
        "STATE": "Disc",
        "IDLE TIME": "17:10",
        "LOGON TIME": "5/28/2020 9:08 AM"
      }
      ]
    }

  tasks:
    - name: Load the relevent host data for demo to be in same condition as in original play
      set_fact:
        qusers: "{{ lookup('vars', 'qusers' +  host) }}"

    - name: Show info about users
      vars:
        host_users_info: "{{ (qusers | dict2items).0 }}"
        servername: "{{ host_users_info.key }}"
        loguedin_users: "{{ host_users_info.value }}"
      debug:
        msg: "{{ item.USERNAME }} is logged into {{ servername }} and is idle for {{ item['IDLE TIME'] }}"
      loop: "{{ loguedin_users }}"

Which gives for element 3

$ ansible-playbook test.yml 
display host ? (1-3) [1]: 3

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

TASK [Load the relevent host data for demo to be in same condition as in original play] *************************************************************
ok: [localhost]

TASK [Show info about users] ************************************************************************************************************************
ok: [localhost] => (item={'USERNAME': 'user183', 'SESSIONNAME': '', 'ID': '49', 'STATE': 'Disc', 'IDLE TIME': '14:34', 'LOGON TIME': '5/28/2020 7:58 AM'}) => {
    "msg": "user183 is logged into Server3 and is idle for 14:34"
}
ok: [localhost] => (item={'USERNAME': 'user103', 'SESSIONNAME': '', 'ID': '51', 'STATE': 'Disc', 'IDLE TIME': '18:26', 'LOGON TIME': '5/28/2020 8:18 AM'}) => {
    "msg": "user103 is logged into Server3 and is idle for 18:26"
}
ok: [localhost] => (item={'USERNAME': 'user148', 'SESSIONNAME': '', 'ID': '52', 'STATE': 'Disc', 'IDLE TIME': '17:10', 'LOGON TIME': '5/28/2020 9:08 AM'}) => {
    "msg": "user148 is logged into Server3 and is idle for 17:10"
}

PLAY RECAP ******************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Upvotes: 1

Related Questions