mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-07-25 00:44:42 +00:00
@@ -23,7 +23,7 @@ kinds:
|
|||||||
- tasks: '**/tasks_*.yml'
|
- tasks: '**/tasks_*.yml'
|
||||||
- tasks: '**/env_*.yml'
|
- tasks: '**/env_*.yml'
|
||||||
|
|
||||||
parseable: true
|
# parseable: true
|
||||||
|
|
||||||
quiet: false
|
quiet: false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/ansible/ansible-lint.git
|
- repo: https://github.com/ansible/ansible-lint.git
|
||||||
rev: v25.9.2
|
rev: v26.4.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: ansible-lint
|
- id: ansible-lint
|
||||||
always_run: false
|
always_run: false
|
||||||
@@ -18,10 +18,9 @@ repos:
|
|||||||
--profile production
|
--profile production
|
||||||
--exclude tests/integration/
|
--exclude tests/integration/
|
||||||
--exclude tests/unit/
|
--exclude tests/unit/
|
||||||
--parseable
|
|
||||||
--nocolor
|
--nocolor
|
||||||
- repo: https://github.com/adrienverge/yamllint.git
|
- repo: https://github.com/adrienverge/yamllint.git
|
||||||
rev: v1.37.1
|
rev: v1.38.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: yamllint
|
- id: yamllint
|
||||||
files: \.(yaml|yml)$
|
files: \.(yaml|yml)$
|
||||||
@@ -30,7 +29,7 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
- repo: https://github.com/pycqa/pylint
|
- repo: https://github.com/pycqa/pylint
|
||||||
rev: v4.0.2
|
rev: v4.0.6
|
||||||
hooks:
|
hooks:
|
||||||
- id: pylint
|
- id: pylint
|
||||||
args:
|
args:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -666,6 +666,108 @@ def gen_intersection_list(user_list, res_list):
|
|||||||
return list(set(res_list or []).intersection(set(user_list or [])))
|
return list(set(res_list or []).intersection(set(user_list or [])))
|
||||||
|
|
||||||
|
|
||||||
|
def gen_member_add_del_lists(param_mapping, params, res_find,
|
||||||
|
action, state):
|
||||||
|
"""
|
||||||
|
Compute member add/del lists for all member params in param_mapping.
|
||||||
|
|
||||||
|
Scans param_mapping for entries with "member": True and computes
|
||||||
|
add and del lists based on action and state:
|
||||||
|
|
||||||
|
- action != "member", state == "present": sync mode using
|
||||||
|
gen_add_del_lists (both additions and removals)
|
||||||
|
- action == "member", state == "present": add-only mode using
|
||||||
|
gen_add_list
|
||||||
|
- action == "member", state == "absent": remove-only mode using
|
||||||
|
gen_intersection_list
|
||||||
|
|
||||||
|
For any other action/state combination an empty dict is returned.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
param_mapping: dict
|
||||||
|
The module's PARAM_MAPPING dict. Entries with "member": True
|
||||||
|
are processed.
|
||||||
|
params: dict
|
||||||
|
Extracted parameter values keyed by ansible parameter name.
|
||||||
|
res_find: dict
|
||||||
|
The IPA find result for the entity. May be an empty dict for
|
||||||
|
newly created entities.
|
||||||
|
action: str
|
||||||
|
The module's action value ("member" or the entity action name).
|
||||||
|
state: str
|
||||||
|
The module's state value ("present" or "absent").
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict
|
||||||
|
Mapping of ansible parameter names to (add_list, del_list)
|
||||||
|
tuples.
|
||||||
|
"""
|
||||||
|
if state == "present" and action != "member":
|
||||||
|
mode = "sync"
|
||||||
|
elif state == "present" and action == "member":
|
||||||
|
mode = "add"
|
||||||
|
elif state == "absent" and action == "member":
|
||||||
|
mode = "remove"
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
for ansible_name, entry in param_mapping.items():
|
||||||
|
if not entry.get("member"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
value = params.get(ansible_name)
|
||||||
|
find_key = entry.get("api_name", ansible_name)
|
||||||
|
existing = res_find.get(find_key)
|
||||||
|
|
||||||
|
if mode == "sync":
|
||||||
|
result[ansible_name] = gen_add_del_lists(value, existing)
|
||||||
|
elif mode == "add":
|
||||||
|
result[ansible_name] = (
|
||||||
|
gen_add_list(value, existing), [])
|
||||||
|
else:
|
||||||
|
result[ansible_name] = (
|
||||||
|
[], gen_intersection_list(value, existing))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def gen_member_args_from_mapping(param_mapping, params):
|
||||||
|
"""
|
||||||
|
Build a member args dict for compare_args_ipa() from param_mapping.
|
||||||
|
|
||||||
|
Scans param_mapping for entries with "member": True and builds a
|
||||||
|
dict mapping the IPA attribute name (api_name) to the parameter
|
||||||
|
value for all member params where the value is not None.
|
||||||
|
|
||||||
|
This replaces per-module gen_member_args() functions.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
param_mapping: dict
|
||||||
|
The module's PARAM_MAPPING dict.
|
||||||
|
params: dict
|
||||||
|
Extracted parameter values keyed by ansible parameter name.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict
|
||||||
|
IPA attribute names as keys, parameter values as values.
|
||||||
|
Suitable for passing to compare_args_ipa().
|
||||||
|
"""
|
||||||
|
_args = {}
|
||||||
|
for ansible_name, entry in param_mapping.items():
|
||||||
|
if not entry.get("member"):
|
||||||
|
continue
|
||||||
|
value = params.get(ansible_name)
|
||||||
|
if value is not None:
|
||||||
|
find_key = entry.get("api_name", ansible_name)
|
||||||
|
_args[find_key] = value
|
||||||
|
return _args
|
||||||
|
|
||||||
|
|
||||||
def encode_certificate(cert):
|
def encode_certificate(cert):
|
||||||
"""
|
"""
|
||||||
Encode a certificate using base64.
|
Encode a certificate using base64.
|
||||||
@@ -1226,7 +1328,8 @@ class IPAAnsibleModule(AnsibleModule):
|
|||||||
return module_params_get_with_type_cast(
|
return module_params_get_with_type_cast(
|
||||||
self, name, datatype, allow_empty)
|
self, name, datatype, allow_empty)
|
||||||
|
|
||||||
def params_fail_used_invalid(self, invalid_params, state, action=None):
|
def params_fail_used_invalid(self, invalid_params, state, action=None,
|
||||||
|
params=None, param_mapping=None):
|
||||||
"""
|
"""
|
||||||
Fail module execution if one of the invalid parameters is not None.
|
Fail module execution if one of the invalid parameters is not None.
|
||||||
|
|
||||||
@@ -1238,6 +1341,12 @@ class IPAAnsibleModule(AnsibleModule):
|
|||||||
State being tested.
|
State being tested.
|
||||||
action:
|
action:
|
||||||
Action being tested (optional).
|
Action being tested (optional).
|
||||||
|
params:
|
||||||
|
Extracted params dict to check (optional, defaults to
|
||||||
|
self.params).
|
||||||
|
param_mapping:
|
||||||
|
Parameter mapping dict (optional). When provided, params
|
||||||
|
marked with "module_param" are checked via self.params.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if action is None:
|
if action is None:
|
||||||
@@ -1246,8 +1355,15 @@ class IPAAnsibleModule(AnsibleModule):
|
|||||||
msg = "Argument '{0}' can not be used with action "\
|
msg = "Argument '{0}' can not be used with action "\
|
||||||
"'{2}' and state '{1}'"
|
"'{2}' and state '{1}'"
|
||||||
|
|
||||||
|
_params = params if params is not None else self.params
|
||||||
for param in invalid_params:
|
for param in invalid_params:
|
||||||
if self.params.get(param) is not None:
|
if param_mapping is not None \
|
||||||
|
and param in param_mapping \
|
||||||
|
and param_mapping[param].get("module_param"):
|
||||||
|
value = self.params.get(param)
|
||||||
|
else:
|
||||||
|
value = _params.get(param)
|
||||||
|
if value is not None:
|
||||||
self.fail_json(msg=msg.format(param, state, action))
|
self.fail_json(msg=msg.format(param, state, action))
|
||||||
|
|
||||||
def ipa_command(self, command, name, args):
|
def ipa_command(self, command, name, args):
|
||||||
@@ -1605,6 +1721,301 @@ class IPAAnsibleModule(AnsibleModule):
|
|||||||
|
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_mapping_entry(ansible_name, entry):
|
||||||
|
"""
|
||||||
|
Return (ansible_name, ipa_name) from a dict-of-dicts mapping entry.
|
||||||
|
|
||||||
|
If entry has "api_name", that is the IPA attribute name.
|
||||||
|
Otherwise, the IPA name equals the ansible name.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return ansible_name, entry.get("api_name", ansible_name)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def gen_args_from_mapping(param_mapping, params):
|
||||||
|
"""
|
||||||
|
Generate IPA command args dict from a parameter mapping.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
param_mapping: dict
|
||||||
|
The module's parameter mapping dict. Keys are ansible param
|
||||||
|
names, values are dicts with optional keys: "api_name",
|
||||||
|
"nonempty_list", "convert_to", "gen_args", "return_only".
|
||||||
|
params: dict
|
||||||
|
Parameter values keyed by ansible name.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict
|
||||||
|
IPA attribute names as keys, suitable for IPA API commands
|
||||||
|
and compare_args_ipa().
|
||||||
|
|
||||||
|
"""
|
||||||
|
_args = {}
|
||||||
|
for ansible_name, entry in param_mapping.items():
|
||||||
|
if entry.get("gen_args") is False \
|
||||||
|
or entry.get("return_only") \
|
||||||
|
or entry.get("module_param"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
ipa_name = entry.get("api_name", ansible_name)
|
||||||
|
value = params.get(ansible_name)
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if entry.get("nonempty_list") and len(value) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
convert = entry.get("convert_to")
|
||||||
|
if convert == "text":
|
||||||
|
value = to_text(str(value))
|
||||||
|
|
||||||
|
_args[ipa_name] = value
|
||||||
|
|
||||||
|
return _args
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def extract_params(module, param_mapping):
|
||||||
|
"""Extract parameter values from module params using mapping."""
|
||||||
|
params = {}
|
||||||
|
for ansible_name, entry in param_mapping.items():
|
||||||
|
if entry.get("return_only") or entry.get("module_param"):
|
||||||
|
continue
|
||||||
|
type_cast = entry.get("type_cast")
|
||||||
|
if type_cast is not None:
|
||||||
|
params[ansible_name] = module.params_get_with_type_cast(
|
||||||
|
ansible_name, type_cast,
|
||||||
|
allow_empty=entry.get("allow_empty", False))
|
||||||
|
elif entry.get("lowercase"):
|
||||||
|
allow_empty = entry.get("allow_empty_list_item")
|
||||||
|
params[ansible_name] = module.params_get_lowercase(
|
||||||
|
ansible_name, allow_empty_list_item=bool(allow_empty))
|
||||||
|
elif entry.get("allow_empty_list_item"):
|
||||||
|
params[ansible_name] = module.params_get(
|
||||||
|
ansible_name, allow_empty_list_item=True)
|
||||||
|
else:
|
||||||
|
params[ansible_name] = module.params_get(ansible_name)
|
||||||
|
return params
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def extract_params_from_entry(entry_dict, param_mapping):
|
||||||
|
"""Extract parameter values from a dict using mapping."""
|
||||||
|
params = {}
|
||||||
|
for ansible_name, entry in param_mapping.items():
|
||||||
|
if entry.get("return_only") or entry.get("module_param"):
|
||||||
|
continue
|
||||||
|
value = entry_dict.get(ansible_name)
|
||||||
|
if value is not None and entry.get("lowercase"):
|
||||||
|
value = convert_param_value_to_lowercase(value)
|
||||||
|
params[ansible_name] = value
|
||||||
|
return params
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_query_param_settings(param_mapping, query_fields):
|
||||||
|
"""
|
||||||
|
Build query_param_settings from a parameter mapping.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
param_mapping: dict
|
||||||
|
The module's parameter mapping dict.
|
||||||
|
query_fields: list of str
|
||||||
|
Ansible-name fields for the BASE query set.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict
|
||||||
|
Dict with "ALL", "BASE", "mapping", and "param_mapping"
|
||||||
|
keys, compatible with execute_query().
|
||||||
|
|
||||||
|
"""
|
||||||
|
all_fields = []
|
||||||
|
mapping = {}
|
||||||
|
for ansible_name, entry in param_mapping.items():
|
||||||
|
if entry.get("query") is False \
|
||||||
|
or entry.get("module_param"):
|
||||||
|
continue
|
||||||
|
all_fields.append(ansible_name)
|
||||||
|
ipa_name = entry.get("api_name", ansible_name)
|
||||||
|
if ansible_name != ipa_name:
|
||||||
|
mapping[ansible_name] = ipa_name
|
||||||
|
return {
|
||||||
|
"ALL": all_fields,
|
||||||
|
"BASE": query_fields.get("base", []),
|
||||||
|
"PRIMARY_KEY": query_fields.get("primary_key", []),
|
||||||
|
"PREFIX": query_fields.get("prefix"),
|
||||||
|
"mapping": mapping,
|
||||||
|
"param_mapping": param_mapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _extract_query_fields(self, result, fields, mapping,
|
||||||
|
param_mapping=None):
|
||||||
|
"""
|
||||||
|
Extract query fields from an IPA result using name mapping.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
result: dict
|
||||||
|
The IPA result dict (after convert_result if provided).
|
||||||
|
fields: list of str
|
||||||
|
The Ansible-friendly field names to extract.
|
||||||
|
mapping: dict
|
||||||
|
Mapping of ansible_name -> ipa_attribute_name.
|
||||||
|
param_mapping: dict or None
|
||||||
|
The full parameter mapping dict. When provided, entry
|
||||||
|
metadata such as "type" is used for value conversion.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict
|
||||||
|
Extracted fields with Ansible-friendly names as keys.
|
||||||
|
|
||||||
|
"""
|
||||||
|
output = {}
|
||||||
|
for field in fields:
|
||||||
|
ipa_field = mapping.get(field, field)
|
||||||
|
if ipa_field in result:
|
||||||
|
value = result[ipa_field]
|
||||||
|
if param_mapping is not None:
|
||||||
|
entry = param_mapping.get(field, {})
|
||||||
|
field_type = entry.get("type")
|
||||||
|
try:
|
||||||
|
if field_type == "bool":
|
||||||
|
if isinstance(
|
||||||
|
value,
|
||||||
|
(str, unicode) # pylint: disable=W0012,E0606
|
||||||
|
):
|
||||||
|
value = value.lower() in ("true", "1", "yes")
|
||||||
|
else:
|
||||||
|
value = bool(value)
|
||||||
|
elif field_type == "int":
|
||||||
|
value = int(value)
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
self.fail_json(
|
||||||
|
msg="Parameter '%s' could not be converted "
|
||||||
|
"to %s: %s" % (field, field_type, str(e))
|
||||||
|
)
|
||||||
|
output[field] = value
|
||||||
|
return output
|
||||||
|
|
||||||
|
def execute_query(self, names, query_param, find_command,
|
||||||
|
query_param_settings, convert_result=None):
|
||||||
|
"""
|
||||||
|
Execute query state.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
names: list of str or None
|
||||||
|
The item names to query. If None or empty, all items are
|
||||||
|
queried using find_command(module, None).
|
||||||
|
prefix: str
|
||||||
|
The grouping key for name-only results (e.g., "users").
|
||||||
|
name_ipa_param: str
|
||||||
|
The IPA attribute name for the item name (e.g., "uid").
|
||||||
|
query_param: list of str or None
|
||||||
|
The fields to return. ["ALL"] for all fields, ["BASE"] for
|
||||||
|
base fields, a custom list for specific fields, or None to
|
||||||
|
return only item names.
|
||||||
|
find_command: callable
|
||||||
|
Module function: find_command(module, name) returning a dict
|
||||||
|
for a single item or a list of dicts for all items (when
|
||||||
|
name is None). Returns None if not found.
|
||||||
|
query_param_settings: dict
|
||||||
|
Module-defined dict with "ALL" (list of all Ansible field
|
||||||
|
names), "BASE" (list of essential field names), and
|
||||||
|
"mapping" (dict of ansible_name -> ipa_attribute_name for
|
||||||
|
names that differ).
|
||||||
|
convert_result: callable or None
|
||||||
|
Optional function to convert a raw IPA result dict to
|
||||||
|
Ansible-friendly values (e.g., unwrap single-element lists,
|
||||||
|
encode certificates). Applied to each result before field
|
||||||
|
extraction. Default: None.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict
|
||||||
|
The query results structured for exit_json.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if query_param is not None and \
|
||||||
|
set(query_param).intersection(["PKEY_ONLY", "BASE", "ALL"]):
|
||||||
|
if len(query_param) > 1:
|
||||||
|
self.fail_json(
|
||||||
|
msg="query_params PKEY_ONLY, BASE or ALL can "
|
||||||
|
"only be used alone"
|
||||||
|
)
|
||||||
|
exit_args = {}
|
||||||
|
mapping = query_param_settings.get("mapping", {})
|
||||||
|
param_mapping = query_param_settings.get("param_mapping")
|
||||||
|
|
||||||
|
prefix = query_param_settings.get("PREFIX")
|
||||||
|
name_ipa_param = query_param_settings.get("PRIMARY_KEY")
|
||||||
|
|
||||||
|
# Resolve query_param to a concrete field list
|
||||||
|
if query_param == ["PKEY_ONLY"]:
|
||||||
|
resolved_fields = None
|
||||||
|
elif query_param == ["BASE"]:
|
||||||
|
# explicit BASE
|
||||||
|
resolved_fields = query_param_settings["BASE"]
|
||||||
|
elif query_param == ["ALL"]:
|
||||||
|
resolved_fields = query_param_settings["ALL"]
|
||||||
|
elif query_param is not None:
|
||||||
|
resolved_fields = query_param
|
||||||
|
else:
|
||||||
|
# Use BASE always if query_param is not set
|
||||||
|
resolved_fields = query_param_settings["BASE"]
|
||||||
|
|
||||||
|
# Validate requested fields
|
||||||
|
if resolved_fields is not None:
|
||||||
|
all_fields = query_param_settings["ALL"]
|
||||||
|
for field in resolved_fields:
|
||||||
|
if field not in all_fields:
|
||||||
|
self.fail_json(
|
||||||
|
msg="query_param '%s' is not supported" % field)
|
||||||
|
|
||||||
|
# If no names have been given, use [None] to get all items with the
|
||||||
|
# find command
|
||||||
|
_names = names
|
||||||
|
if _names is None or not isinstance(_names, list):
|
||||||
|
_names = [None]
|
||||||
|
single_item = len(_names) == 1
|
||||||
|
|
||||||
|
# For all names in _names convert results and add the requested
|
||||||
|
# fields to exit_args
|
||||||
|
for name in _names:
|
||||||
|
results = find_command(self, name)
|
||||||
|
if not isinstance(results, list):
|
||||||
|
results = [results]
|
||||||
|
for result in results:
|
||||||
|
if result is None:
|
||||||
|
continue
|
||||||
|
if convert_result is not None:
|
||||||
|
result = convert_result(result)
|
||||||
|
if name_ipa_param not in result:
|
||||||
|
self.fail_json(
|
||||||
|
msg="execute_query: primary key '%s' missing "
|
||||||
|
"from result for '%s'" % (name_ipa_param, name)
|
||||||
|
)
|
||||||
|
item_name = result[name_ipa_param]
|
||||||
|
if query_param == ["PKEY_ONLY"]:
|
||||||
|
exit_args.setdefault(prefix, []).append(item_name)
|
||||||
|
else:
|
||||||
|
fields = self._extract_query_fields(
|
||||||
|
result, resolved_fields, mapping,
|
||||||
|
param_mapping)
|
||||||
|
if name is not None:
|
||||||
|
if single_item:
|
||||||
|
exit_args = fields
|
||||||
|
else:
|
||||||
|
exit_args[name] = fields
|
||||||
|
else:
|
||||||
|
exit_args[item_name] = fields
|
||||||
|
|
||||||
|
return exit_args
|
||||||
|
|
||||||
def tm_warn(self, warning):
|
def tm_warn(self, warning):
|
||||||
ts = time.time()
|
ts = time.time()
|
||||||
# pylint: disable=super-with-arguments
|
# pylint: disable=super-with-arguments
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -89,6 +89,7 @@ ignored-modules =
|
|||||||
|
|
||||||
[pylint.DESIGN]
|
[pylint.DESIGN]
|
||||||
max-attributes=12
|
max-attributes=12
|
||||||
|
max-public-methods=25
|
||||||
|
|
||||||
[pylint.REFACTORING]
|
[pylint.REFACTORING]
|
||||||
max-nested-blocks = 9
|
max-nested-blocks = 9
|
||||||
|
|||||||
@@ -136,7 +136,7 @@
|
|||||||
|
|
||||||
- name: "Ensure test users test_users are absent"
|
- name: "Ensure test users test_users are absent"
|
||||||
ipauser:
|
ipauser:
|
||||||
users: "{{ test_users }}"
|
name: "{{ user_names }}"
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
- name: Test in all IPA versions 8.4.4+
|
- name: Test in all IPA versions 8.4.4+
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
181
tests/user/test_user_query.yml
Normal file
181
tests/user/test_user_query.yml
Normal 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
|
||||||
Reference in New Issue
Block a user