ipauser: Use PARAM_MAPPING and query state support

The ipauser module has been reworked to use the new PARAM_MAPPING added
to ansible_freeipa_module.

The member handling for manager, principal, certificate and certmapdata
has been simplified by using gen_member_add_del_lists. The member
entries in PARAM_MAPPING are now marked with "member": True. This
replaces the manual calls to gen_add_del_lists, gen_add_list and
gen_intersection_list across three separate action/state branches with
a single unified call.

The new query state allows to retrieve user information from IPA.
The query_param option controls which fields are returned: BASE for
essential fields, ALL for all fields, PKEY_ONLY for user names only,
or a list of specific field names.

Here is the updated documentation of the module:

    README-user.md

New tests for the query state can be found at:

    tests/user/test_user_query.yml
This commit is contained in:
Thomas Woerner
2026-06-16 13:10:12 +02:00
parent 964345aed8
commit fcf4a60de6
4 changed files with 718 additions and 573 deletions

View File

@@ -368,6 +368,100 @@ Example playbook to ensure users are absent:
state: absent state: absent
``` ```
Example playbook to query a user and print the base fields:
```yaml
---
- name: Playbook to query users
hosts: ipaserver
become: true
tasks:
- name: Query user pinky
ipauser:
ipaadmin_password: SomeADMINpassword
name: pinky
state: query
register: result
- name: Print user info
debug:
var: result.user
```
Example playbook to query specific fields of a user:
```yaml
---
- name: Playbook to query users
hosts: ipaserver
become: true
tasks:
- name: Query first and last name of user pinky
ipauser:
ipaadmin_password: SomeADMINpassword
name: pinky
query_param:
- first
- last
- email
state: query
register: result
- name: Print user info
debug:
var: result.user
```
Example playbook to query all fields of a user:
```yaml
---
- name: Playbook to query users
hosts: ipaserver
become: true
tasks:
- name: Query all fields of user pinky
ipauser:
ipaadmin_password: SomeADMINpassword
name: pinky
query_param: ALL
state: query
register: result
- name: Print user info
debug:
var: result.user
```
Example playbook to query only the names of all users:
```yaml
---
- name: Playbook to query users
hosts: ipaserver
become: true
tasks:
- name: Query all user names
ipauser:
ipaadmin_password: SomeADMINpassword
query_param: PKEY_ONLY
state: query
register: result
- name: Print user names
debug:
var: result.user.users
```
When using FreeIPA 4.8.0+, SMB logon script, profile, home directory and home drive can be set for users. When using FreeIPA 4.8.0+, SMB logon script, profile, home directory and home drive can be set for users.
In the example playbook to set SMB attributes note that `smb_profile_path` and `smb_home_dir` use paths in UNC format, which includes backslashes ('\\`). If the paths are quoted, the backslash needs to be escaped becoming "\\", so the path `\\server\dir` becomes `"\\\\server\\dir"`. If the paths are unquoted the slashes do not have to be escaped. In the example playbook to set SMB attributes note that `smb_profile_path` and `smb_home_dir` use paths in UNC format, which includes backslashes ('\\`). If the paths are quoted, the backslash needs to be escaped becoming "\\", so the path `\\server\dir` becomes `"\\\\server\\dir"`. If the paths are unquoted the slashes do not have to be escaped.
@@ -416,7 +510,7 @@ Variable | Description | Required
`update_password` | Set password for a user in present state only on creation or always. It can be one of `always` or `on_create` and defaults to `always`. | no `update_password` | Set password for a user in present state only on creation or always. It can be one of `always` or `on_create` and defaults to `always`. | no
`preserve` | Delete a user, keeping the entry available for future use. (bool) | no `preserve` | Delete a user, keeping the entry available for future use. (bool) | no
`action` | Work on user or member level. It can be on of `member` or `user` and defaults to `user`. | no `action` | Work on user or member level. It can be on of `member` or `user` and defaults to `user`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, `enabled`, `disabled`, `renamed`, `unlocked` or `undeleted`, default: `present`. Only `names` or `users` with only `name` set are allowed if state is not `present`. | yes `state` | The state to ensure. It can be one of `present`, `absent`, `enabled`, `disabled`, `renamed`, `unlocked`, `undeleted` or `query`, default: `present`. Only `names` or `users` with only `name` set are allowed if state is not `present`. | yes

File diff suppressed because it is too large Load Diff

View File

@@ -128,6 +128,22 @@
register: result register: result
failed_when: result.changed or result.failed failed_when: result.changed or result.failed
- name: User pinky with email "pinky"
ipauser:
ipaadmin_password: SomeADMINpassword
name: pinky
email: pinky
register: result
failed_when: not result.changed or result.failed
- name: User pinky with email "pinky", again
ipauser:
ipaadmin_password: SomeADMINpassword
name: pinky
email: pinky
register: result
failed_when: result.changed or result.failed
- name: User pinky present with changed settings - name: User pinky present with changed settings
ipauser: ipauser:
ipaadmin_password: SomeADMINpassword ipaadmin_password: SomeADMINpassword

View File

@@ -0,0 +1,181 @@
---
- name: Test user query
hosts: "{{ ipa_test_host | default('ipaserver') }}"
become: false
gather_facts: false
module_defaults:
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
tasks:
# CLEANUP
- name: Ensure users "testuser1" and "testuser2" are absent
ipauser:
name:
- testuser1
- testuser2
- non-existing-user
state: absent
# CREATE TEST ITEMS
- name: Ensure users "testuser1" and "testuser2" are present
ipauser:
users:
- name: testuser1
first: first1
last: last1
- name: testuser2
first: first2
last: last2
- name: Query user "non-existing-user"
ipauser:
name:
- non-existing-user
#query_param: PKEY_ONLY
state: query
register: result
failed_when: result.changed or result.failed
- name: Print query information
ansible.builtin.debug:
var: result.user
- name: Query all users
ipauser:
state: query
query_param: PKEY_ONLY
register: result
failed_when: result.changed or result.failed
- name: Print query information
ansible.builtin.debug:
var: result.user
- name: Fail on missing "testuser1" in query result
ansible.builtin.fail:
msg: "'testuser1' not in query result {{ result.user.users }}"
when: ("testuser1" not in result.user.users)
- name: Fail on missing "testuser2" in query result
ansible.builtin.fail:
msg: "'testuser2' not in query result {{ result.user.users }}"
when: ("testuser2" not in result.user.users)
- name: Fail on "non-existing-user" in query result
ansible.builtin.fail:
msg: "'non-existing-user' in query result {{ result.user.users }}"
when: ("non-existing-user" in result.user.users)
- name: Query users "testuser1" and "testuser2"
ipauser:
name:
- testuser1
- testuser2
state: query
register: result
failed_when: result.changed or result.failed
- name: Print query information
ansible.builtin.debug:
var: result.user
- name: Fail on missing "testuser1" in query result
ansible.builtin.fail:
msg: "'testuser1' not in query result {{ result.user }}"
when: ("testuser1" not in result.user)
- name: Fail on missing "testuser2" in query result
ansible.builtin.fail:
msg: "'testuser2' not in query result {{ result.user }}"
when: ("testuser2" not in result.user)
- name: Fail on "non-existing-user" in query result
ansible.builtin.fail:
msg: "'non-existing-user' in query result {{ result.user }}"
when: ("non-existing-user" in result.user)
- name: Query all user parameters for "testuser1"
ipauser:
name:
- testuser1
query_param: ALL
state: query
register: result
failed_when: result.changed or result.failed
- name: Print query information
ansible.builtin.debug:
var: result.user
- name: Fail on missing information in query result
ansible.builtin.fail:
msg: "Query result {{ result.user }} is incomplete"
when: (result.user.disabled or
result.user.displayname != "first1 last1" or
"uid=testuser1,cn=users,cn=accounts,dc=" not in result.user.dn or
"testuser1@" not in result.user.email or
result.user.first != "first1" or
result.user.fullname != "first1 last1" or
result.user.gecos != "first1 last1" or
result.user.gid < 1 or
result.user.homedir != "/home/testuser1" or
result.user.initials != "fl" or
result.user.last != "last1" or
result.user.name != "testuser1" or
"objectclass" not in result.user or
result.user.preserved or
result.user.principal | length != 1 or
"testuser1@" not in result.user.principal[0] or
"/bin/" not in result.user.shell or
result.user.uid < 1)
- name: Query "uid", "first" and "last" parameters for all users
ipauser:
query_param:
- uid
- first
- last
state: query
register: result
failed_when: result.changed or result.failed
- name: Print query information
ansible.builtin.debug:
var: result.user
- name: Fail on less than 3 users in result
ansible.builtin.fail:
msg: "{{ result.user }} is not empty"
when: result.user | length < 3
- name: Fail on missing "testuser1" information in query result
ansible.builtin.fail:
msg: "'testuser1' not in query result {{ result.user }}"
when: ("testuser1" not in result.user or
result.user.testuser1.first != "first1" or
result.user.testuser1.last != "last1" or
result.user.testuser1.uid < 1 or
result.user.testuser1 | length != 3)
- name: Fail on missing "testuser2" information in query result
ansible.builtin.fail:
msg: "'testuser2' not in query result {{ result.user }}"
when: ("testuser2" not in result.user or
result.user.testuser2.first != "first2" or
result.user.testuser2.last != "last2" or
result.user.testuser2.uid < 1 or
result.user.testuser2 | length != 3)
# CLEANUP
- name: Ensure users "testuser1" and "testuser2" are absent
ipauser:
name:
- testuser1
- testuser2
state: absent