Accessing contents of a fact in a loop

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

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.

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] **********************************************************************************************************
ok: [auth1.secure-ose.faa.gov] => {
“msg”: [
{
“dn”: “uid=harry.devine,cn=users,cn=accounts,dc=example,dc=com”,
“gidnumber”: [
“11111”
],
“givenname”: [
“Harry”
],
“homedirectory”: [
“/home/harry.devine”
],
“krbcanonicalname”: [
harry.devine@EXAMPLE.COM
],
“krbprincipalname”: [
harry.devine@EXAMPLE.COM
],
“loginshell”: [
“/bin/bash”
],
“mail”: [
harry.devine@example.com
],
“nsaccountlock”: false,
“sn”: [
“Devine”
],
“telephonenumber”: [
“(800) 867-5309”
],
“uid”: [
“harry.devine”
],
“uidnumber”: [
“1111”
]
},

Thanks,
Harry

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 .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

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.

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

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.

I set it as a fact using your suggestion:
user_find.json.result.result|map(attribute=‘uid’)|flatten

Harry

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:

You left out the rest of your set_fact step.
Please include the whole step. Thanks.

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.devine@EXAMPLE.COM
],
“krbprincipalname”: [
harry.devine@EXAMPLE.COM
],
“loginshell”: [
“/bin/bash”
],
“mail”: [
harry.devine@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

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.

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) / (606024) }}”

  • 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