Accessing contents of a fact in a loop

114 views
Skip to first unread message

lift...@gmail.com

unread,
Nov 29, 2021, 3:05:37 PM11/29/21
to Ansible Project
I am traversing our IPA server to get find all users, then I want to loop through all of them to get their password expiration date.  I use the IPA API via the uri module and register the variable, but no matter what I try to access the uid of each found user, I get the following error:

TASK [Run user_show from IDM API using previously stored session cookie] **********************************************************
fatal: [localhost]: FAILED! => {"msg": "template error while templating string: expected name or number. String: {\"method\": \"user_show\",\"params\": [[ \"{{ item[0].['uid'] }}\"],{\"all\": true,\"version\": \"{{ api_vers }}\"}]}"}

Here's the section of my playbook that seems to be giving me issues:

  - name: Run user_find from IDM API using previously stored session cookie
    uri:
      url: "https://{{idmfqdn}}/ipa/session/json"
      method: POST
      headers:
        Cookie: "{{ login.set_cookie }}"
        Referer: "https://{{idmfqdn}}/ipa"
        Content-Type: "application/json"
        Accept: "application/json"
      body_format: json
      body: "{\"method\": \"user_find/1\",\"params\": [[],{\"version\": \"{{ api_vers }}\"}]}"
    register: user_find

  - name: Run user_show from IDM API using previously stored session cookie
    uri:
      url: "https://{{idmfqdn}}/ipa/session/json"
      method: POST
      headers:
        Cookie: "{{ login.set_cookie }}"
        Referer: "https://{{idmfqdn}}/ipa"
        Content-Type: "application/json"
        Accept: "application/json"
      body_format: json
      body: "{\"method\": \"user_show\",\"params\": [[ \"{{ item[0].['uid'] }}\"],{\"all\": true,\"version\": \"{{ api_vers }}\"}]}"
    register: user_show
    loop:
    - "{{ user_find.json.result.result }}"

Thanks,
Harry

Todd Lewis

unread,
Nov 29, 2021, 3:45:46 PM11/29/21
to Ansible Project
Before the step that's failing, insert a debug step with the msg: "{{ user_find.json.result.result }}" (really? "result.result"? maybe...) so you (and we) can be certain what your items actually look like. Otherwise, we're just guessing.

lift...@gmail.com

unread,
Nov 30, 2021, 8:26:48 AM11/30/21
to Ansible Project
I had done that previously and knew it was getting the right data, but I put the debug back in and the redacted output is below.  There are over 1600 users, so I am only showing the start of the data in a redacted form.  The debug print is printing "{{ user_find.json.result.result }}":

TASK [Print users found] **********************************************************************************************************
    "msg": [
        {
            "dn": "uid=harry.devine,cn=users,cn=accounts,dc=example,dc=com",
            "gidnumber": [
                "11111"
            ],
            "givenname": [
                "Harry"
            ],
            "homedirectory": [
                "/home/harry.devine"
            ],
            "krbcanonicalname": [
                "harry....@EXAMPLE.COM"
            ],
            "krbprincipalname": [
                "harry....@EXAMPLE.COM"
            ],
            "loginshell": [
                "/bin/bash"
            ],
            "mail": [
                "harry....@example.com"
            ],
            "nsaccountlock": false,
            "sn": [
                "Devine"
            ],
            "telephonenumber": [
                "(800) 867-5309"
            ],
            "uid": [
                "harry.devine"
            ],
            "uidnumber": [
                "1111"
            ]
        },

Thanks,
Harry

lift...@gmail.com

unread,
Dec 3, 2021, 12:01:14 PM12/3/21
to Ansible Project
So I'm still trying to get this to work.  I'm thinking that the fact is one large item, so I need to know how I can loop through those items.  I'm trying to get the UID of each user.  What the user_find IPA API call returns is <variable>.json.result.result, and the users are added to the fact in the following form:

- [ user1 ]\n
- [ user2 ]\n

I'm setting the user_find variable and fact as follows:

  - name: Run user_find from IDM API using previously stored session cookie
    uri:
      url: "https://{{idmfqdn}}/ipa/session/json"
      method: POST
      headers:
        Cookie: "{{ login.set_cookie }}"
        Referer: "https://{{idmfqdn}}/ipa"
        Content-Type: "application/json"
        Accept: "application/json"
      body_format: json
      body: "{\"method\": \"user_find/1\",\"params\": [[],{\"version\": \"{{ api_vers }}\"}]}"
    no_log: true
    register: user_find

  - name: Set fact for users
    set_fact:
      uid: "{{ user_find.json.result.result|json_query('[*].uid') | list | to_yaml }}"

The user_find information is listed earlier in this thread.  So I'm trying to got through that variable and pull out each UID.  Without the to_yaml filter, those are shown as ["user1"], ["user2"], etc.  So how do I loop through these?  Can I set up the fact as an array of user IDs and loop through that?  If so, how?

Thanks,
Harry

Todd Lewis

unread,
Dec 3, 2021, 4:17:14 PM12/3/21
to Ansible Project
I really want to love Ansible, but the fact that such a simple data manipulation completely eludes the newbie doesn't help. Worse, that I've done this (or equivalent) dozens of times and it still takes me as long as it does to come up with a working demo ... [sigh].

Anyway, here's a working demo, assuming I got your initial data shaped right. Good luck.

---
- name: Demo for processing user_find IPA API results
  hosts: localhost
  vars:
    user_find:
      json:
        result:
          result:
            - dn: "uid=harry.devine,cn=users,cn=accounts,dc=example,dc=com"
              gidnumber: [ "11111" ]
              givenname: [ "Harry" ]
              homedirectory: [ "/home/harry.devine" ]
              krbcanonicalname: [ "harry....@EXAMPLE.COM" ]
              krbprincipalname: [ "harry....@EXAMPLE.COM" ]
              loginshell: [ "/bin/bash" ]
              mail: [ "harry....@example.com" ]
              nsaccountlock: false
              sn: [ "Devine" ]
              telephonenumber: [ "(800) 867-5309" ]
              uid: [ "harry.devine" ]
              uidnumber: [ "1111" ]
            - dn: "uid=marve.devine,cn=users,cn=accounts,dc=example,dc=com"
              gidnumber: [ "11111" ]
              givenname: [ "Marve" ]
              homedirectory: [ "/home/marve.devine" ]
              krbcanonicalname: [ "marve....@EXAMPLE.COM" ]
              krbprincipalname: [ "marve....@EXAMPLE.COM" ]
              loginshell: [ "/bin/bash" ]
              mail: [ "marve....@example.com" ]
              nsaccountlock: false
              sn: [ "Devine" ]
              telephonenumber: [ "(800) 867-5309" ]
              uid: [ "marve.devine" ]
              uidnumber: [ "1111" ]

  tasks:
    - name: Look at user_find.json.result.result
      debug:
        msg: "{{ user_find.json.result.result | to_json }}"

    - name: Stash the uids with set_fact
      set_fact:
        demo_uids: "{{ user_find.json.result.result|map(attribute='uid')|flatten }}"

    - name: Look at our set fact
      debug:
        msg: "{{ demo_uids }}"

    - name: Or just loop over directly; no need to do a set_fact
      debug:
        msg: "{{ item }}"
      loop: "{{ user_find.json.result.result|map(attribute='uid')|flatten }}"

lift...@gmail.com

unread,
Dec 3, 2021, 4:32:01 PM12/3/21
to Ansible Project
That works, but when I try to then call the IPA user_show API, which takes the UID as a parameter, the entire list generated is sent in.

  - name: Run user_show from IDM API using previously stored session cookie
    uri:
      url: "https://{{idmfqdn}}/ipa/session/json"
      method: POST
      headers:
        Cookie: "{{ login.set_cookie }}"
        Referer: "https://{{idmfqdn}}/ipa"
        Content-Type: "application/json"
        Accept: "application/json"
      body_format: json
      body: "{\"method\": \"user_show\",\"params\": [[ \"{{ item }}\"],{\"all\": true,\"version\": \"{{ api_vers }}\"}]}"
    register: user_show
    loop:
    - "{{ uid }}"

TASK [Run user_show from IDM API using previously stored session cookie] *************************************************************************
ok: [localhost] => (item=[u'user1', u'user2', u'user3'])


                "invocation": {
                    "module_args": {
                        "attributes": null,
                        "backup": null,
                        "body": {
                            "method": "user_show",
                            "params": [
                                [
                                    "[u'user1', u'user2', u'user3']"
],
                                {
                                    "all": true,
                                    "version": "2.237"
                                }
                            ]
                        },

"message": "[u'user1', u'user2', u'user3']: user not found

So, why does the debug print appear to print each UID out, but when I try to reference them in the loop, they are sent over as 1 big string?

Thanks,
Harry

Todd Lewis

unread,
Dec 3, 2021, 5:08:48 PM12/3/21
to Ansible Project
I don't see where you're setting uid, the debug step, or its output. All I see is that

   loop:
     - "{{ uid }}"

is only producing one invocation of the task with the desired values all glommed into one string.
Please show your input code and resulting output. I suspect you're somehow producing a string rather than a list, but with nothing to look at, it's hard to know.

harry devine

unread,
Dec 3, 2021, 7:03:07 PM12/3/21
to ansible...@googlegroups.com
I set it as a fact using your suggestion:
user_find.json.result.result|map(attribute='uid')|flatten

Harry

--
You received this message because you are subscribed to the Google Groups "Ansible Project" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ansible-proje...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ansible-project/9471f1af-012a-4f81-bdf0-00eac932b90an%40googlegroups.com.

Todd Lewis

unread,
Dec 3, 2021, 7:27:11 PM12/3/21
to Ansible Project
You left out the rest of your set_fact step.
Please include the whole step. Thanks.

Chris Smart

unread,
Dec 3, 2021, 7:27:21 PM12/3/21
to ansible...@googlegroups.com
On Sat, 4 Dec 2021 at 09:08, Todd Lewis <uto...@gmail.com> wrote:
I don't see where you're setting uid, the debug step, or its output. All I see is that

   loop:
     - "{{ uid }}"

is only producing one invocation of the task with the desired values all glommed into one string.
Please show your input code and resulting output. I suspect you're somehow producing a string rather than a list, but with nothing to look at, it's hard to know.

Hi Harry, as Todd mentioned it looks like you might be expecting uid there (and user_find.json.result.result from your first example) to be a list? (It's printing like one.)

If so, i could be wrong but you probably don't need to reference the variable inside a list like this:

loop:
  - "{{ uid }}"

Just reference the variable directly like this (as it's already a list).

loop: "{{ uid }}"

Otherwise Ansible is looping through the list and rendering the first entry as a string (but you want that to be the list).

For example, here's a playbook crafted similarly to yours:

---
- hosts: all
  gather_facts: no
  vars:
    mylist:
      - one
      - two
  tasks:
    - debug:
        msg: "{{ item }}"
      loop:
        - "{{ mylist }}"

If I execute that, I get one result (printing out the list, like you did):

TASK [debug] *************************************************************************************************************************************************
ok: [localhost] => (item=['one', 'two']) => {
    "msg": [
        "one",
        "two"
    ]
}

However if the variable is referenced like this:

---
- hosts: all
  gather_facts: no
  vars:
    mylist:
      - one
      - two
  tasks:
    - debug:
        msg: "{{ item }}"
      loop: "{{ mylist }}"

Then I get what I expected, two results one for each item in the list:

TASK [debug] *************************************************************************************************************************************************
ok: [localhost] => (item=one) => {
    "msg": "one"
}
ok: [localhost] => (item=two) => {
    "msg": "two"
}

Hopefully that helps.

Thanks,
-c


--

lift...@gmail.com

unread,
Dec 7, 2021, 11:24:36 AM12/7/21
to Ansible Project
OK, so that works, but I'm still having issues with referencing.  So I'm using user_find from the IPA API using the uri module.  I get the user account info correctly as follows:

user_find.json.result.result returns:
                "result": [
                    {
                        "dn": "uid=harry.devine,cn=users,cn=accounts,dc=example,dc=com",
                        "gidnumber": [
                            "10000"
                        ],
                        "givenname": [
                            "Harry"
                        ],
                        "homedirectory": [
                            "/home/harry.devine"
                        ],
                        "krbcanonicalname": [
                            "harry....@EXAMPLE.COM"
                        ],
                        "krbprincipalname": [
                            "harry....@EXAMPLE.COM"
                        ],
                        "loginshell": [
                            "/bin/bash"
                        ],
                        "mail": [
                            "harry....@example.com"
                        ],
                        "nsaccountlock": false,
                        "sn": [
                            "Devine"
                        ],
                        "telephonenumber": [
                            "(xxx)yyy-zzzz"
                        ],
                        "uid": [
                            "harry.devine"
                        ],
                        "uidnumber": [
                            "1111"
                        ]
                    }

I then set the fact to pull out the user ID:

  - name: Set fact for users
    set_fact:
      uid: "{{ user_find.json.result.result|map(attribute='uid')|flatten }}"

then use that fact into the url module using the IPA API user_show.  When I print out user_show, I get the following (I left out most of the user information as it's redundant):

        "krbpasswordexpiration": [
            {
                "__datetime__": "20220220212310Z"
            }

So when I print out the password expiration, I can reference it using user_show.results[0].json.result.result.krbpasswordexpiration[0]['__datetime__'].  But when I try to set a fact with that information, I get an error that says that krbpasswordexpiration doesn't exist.  Here's that set_fact:

  - name: Set fact for password expirations
    set_fact:
      pwdexpires: "{{ user_show.results[0].json.result.result|map(attribute='krbpasswordexpiration') | flatten }}"


What I'm hoping to get to is:
1) Find all users and set the uid fact
2) Loop through those uid values and call user_show so I can retrieve each user's password expiration
3) Determine if their password has expired more than 180 days
4) Create a list of users to disable
5) Loop through that list and disable each user
6) Email each user to inform them of the disable

So I have 1 and 2 working, but transitioning to 3 using both facts (uid and pwdexpires) is what's giving me trouble.  Any thoughts/ideas on how to accomplish the retrieval of the password expiration and have it in a fact?  Or, maybe the better question is: can I have a fact with more than one value in it: 1 for uid and 1 for password expiration?  I already know the uid via the result of user_show, so I should be able to pull out both values, but how?

Thanks, and sorry for the long-winded explanation.  Just trying to be as thorough and complete with you all.
Harry

Todd Lewis

unread,
Dec 7, 2021, 12:05:54 PM12/7/21
to Ansible Project
> When I print out user_show…

Can you please show your step where you print out `user_show` (presumably a debug step), and all relevant parts of its output. "Relevant parts" would include any `[` or `{` characters, i.e. anything showing the context of "krbpasswordexpiration" in the registered data, as well as the step header (host and asterisks). Thanks.

lift...@gmail.com

unread,
Dec 7, 2021, 1:21:59 PM12/7/21
to Ansible Project
So after I sent that, i did some more playing around, and I think I have what I need for now.  I'm setting multiple facts in 1 fact as follows:

  - name: Set user_show fact
    set_fact:
      users:
      - "{{ user_show.results[0].json.result.result.uid[0] }}"
      - "{{ (user_show.results[0].json.result.result.krbpasswordexpiration[0]['__datetime__'] | to_datetime('%Y%m%d%H%M%SZ')).strftime('%s') }}"
      - "{{ (ansible_date_time.epoch|int - ((user_show.results[0].json.result.result.krbpasswordexpiration[0]['__datetime__'] | to_datetime('%Y%m%d%H%M%SZ')).strftime('%s'))|int) / (60*60*24) }}"

  - name: Print users fact
    debug:
      msg: "{{ users[0] }} : {{ users[1] }} : age={{ users[2] }}"

When the playbook runs, I get:

TASK [Print users fact] ***********************************************************************************************************
ok: [auth1.secure-ose.faa.gov] => {
    "msg": "harry.devine : 1645410190 : age=-75.3984606481"
}

I think from here I should be able to loop through that list, and start making a new fact list for anyone who's password age is >= 180.

Thanks,
Harry
Reply all
Reply to author
Forward
0 new messages