Skip to content

Commit

Permalink
New utils method that returns the lists of members.
Browse files Browse the repository at this point in the history
Extend netgroup and sudorule modules to support external
users and hosts wherever possible.
Add tests for ipanetgroup and ipasudorule.

Problem statement:
```
    - name: Ensure sudorule is present with users and hosts (action member)
      ipasudorule:
        name: testrule2
        user:
          - external-user
        action: member
    - name: Ensure sudorule is present with users and hosts (action member) again
      ipasudorule:
        name: testrule2
        user:
          - external-user
        action: member
```
After execution of the first task with external users ansible
returns changed as expected, after second it still returns
changed - it's a bug. This PR fixes it. After the second task
ansible will return ok.
"External" entities are:
for `ipasudorule`: `externalhost, externaluser, ipasudorunasextuser,
ipasudorunasextgroup`
for `ipanetgroup`: `externalhost`

Signed-off-by: Denis Karpelevich <[email protected]>
  • Loading branch information
dkarpele committed Mar 28, 2023
1 parent cf27a98 commit de7568b
Show file tree
Hide file tree
Showing 7 changed files with 467 additions and 34 deletions.
18 changes: 18 additions & 0 deletions plugins/module_utils/ansible_freeipa_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,24 @@ def gen_intersection_list(user_list, res_list):
return list(set(res_list or []).intersection(set(user_list or [])))


def concat_attr_list(res, *args):
"""
Get the lists of members to pass on `gen_*` methods as a res_list argument.
This function should be used to get members (usually users,
external users, hosts, external hosts, etc.) with any action and any state.
It is returning the concatenation of all attributes provided by user.
"""
res_list = []
for attribute in args:
arg = res.get(attribute, [])
if not isinstance(arg, (list, tuple)):
arg = [arg]
res_list += arg
return list(set(res_list))


def encode_certificate(cert):
"""
Encode a certificate using base64.
Expand Down
17 changes: 13 additions & 4 deletions plugins/modules/ipanetgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@

from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \
gen_add_list, gen_intersection_list, ensure_fqdn
gen_add_list, gen_intersection_list, concat_attr_list, ensure_fqdn


def find_netgroup(module, name):
Expand Down Expand Up @@ -339,8 +339,13 @@ def main():
group_add, group_del = gen_add_del_lists(
group, res_find.get("memberuser_group"))

# `externalhost` adds an entity to the "External host"
# list for `ipanetgroup`. Hosts enrolled to IPA are in
# "Member Host" list.
host_add, host_del = gen_add_del_lists(
host, res_find.get("memberhost_host"))
host, concat_attr_list(res_find,
"memberhost_host",
"externalhost"))

hostgroup_add, hostgroup_del = gen_add_del_lists(
hostgroup, res_find.get("memberhost_hostgroup"))
Expand All @@ -360,7 +365,9 @@ def main():
group_add = gen_add_list(
group, res_find.get("memberuser_group"))
host_add = gen_add_list(
host, res_find.get("memberhost_host"))
host, concat_attr_list(res_find,
"memberhost_host",
"externalhost"))
hostgroup_add = gen_add_list(
hostgroup, res_find.get("memberhost_hostgroup"))
netgroup_add = gen_add_list(
Expand All @@ -379,7 +386,9 @@ def main():
group_del = gen_intersection_list(
group, res_find.get("memberuser_group"))
host_del = gen_intersection_list(
host, res_find.get("memberhost_host"))
host, concat_attr_list(res_find,
"memberhost_host",
"externalhost"))
hostgroup_del = gen_intersection_list(
hostgroup, res_find.get("memberhost_hostgroup"))
netgroup_del = gen_intersection_list(
Expand Down
76 changes: 46 additions & 30 deletions plugins/modules/ipasudorule.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@

from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, gen_add_list, \
gen_intersection_list, api_get_domain, ensure_fqdn, netaddr, to_text
gen_intersection_list, concat_attr_list, api_get_domain, ensure_fqdn, \
netaddr, to_text


def find_sudorule(module, name):
Expand Down Expand Up @@ -505,18 +506,28 @@ def main():
# Set res_find to empty dict for next step
res_find = {}

# Generate addition and removal lists
# Generate addition and removal lists.
# `externalhost` adds an entity to the "External host"
# list for `ipasudorule`. Hosts enrolled to IPA are in
# "Hosts" list.
host_add, host_del = gen_add_del_lists(
host, res_find.get('memberhost_host', []))
host, concat_attr_list(res_find,
"memberhost_host",
"externalhost"))

hostgroup_add, hostgroup_del = gen_add_del_lists(
hostgroup, res_find.get('memberhost_hostgroup', []))

hostmask_add, hostmask_del = gen_add_del_lists(
hostmask, res_find.get('hostmask', []))

# `externaluser` adds an entity to the "External user"
# (non-IPA users) list for `ipasudorule`. Users enrolled to
# IPA are in "Users" list.
user_add, user_del = gen_add_del_lists(
user, res_find.get('memberuser_user', []))
user, concat_attr_list(res_find,
"memberuser_user",
"externaluser"))

group_add, group_del = gen_add_del_lists(
group, res_find.get('memberuser_group', []))
Expand Down Expand Up @@ -547,10 +558,9 @@ def main():
# users list.
runasuser_add, runasuser_del = gen_add_del_lists(
runasuser,
(
res_find.get('ipasudorunas_user', [])
+ res_find.get('ipasudorunasextuser', [])
)
concat_attr_list(res_find,
'ipasudorunas_user',
'ipasudorunasextuser')
)

# runasgroup attribute can be used with both IPA and
Expand All @@ -560,10 +570,9 @@ def main():
# groups list.
runasgroup_add, runasgroup_del = gen_add_del_lists(
runasgroup,
(
res_find.get('ipasudorunasgroup_group', [])
+ res_find.get('ipasudorunasextgroup', [])
)
concat_attr_list(res_find,
'ipasudorunasgroup_group',
'ipasudorunasextgroup')
)

elif action == "member":
Expand All @@ -577,7 +586,9 @@ def main():
# the sudorule already
if host is not None:
host_add = gen_add_list(
host, res_find.get("memberhost_host"))
host, concat_attr_list(res_find,
"memberhost_host",
"externalhost"))
if hostgroup is not None:
hostgroup_add = gen_add_list(
hostgroup, res_find.get("memberhost_hostgroup"))
Expand All @@ -586,7 +597,9 @@ def main():
hostmask, res_find.get("hostmask"))
if user is not None:
user_add = gen_add_list(
user, res_find.get("memberuser_user"))
user, concat_attr_list(res_find,
"memberuser_user",
"externaluser"))
if group is not None:
group_add = gen_add_list(
group, res_find.get("memberuser_group"))
Expand Down Expand Up @@ -620,8 +633,9 @@ def main():
if runasuser is not None:
runasuser_add = gen_add_list(
runasuser,
(list(res_find.get('ipasudorunas_user', []))
+ list(res_find.get('ipasudorunasextuser', [])))
concat_attr_list(res_find,
'ipasudorunas_user',
'ipasudorunasextuser')
)
# runasgroup attribute can be used with both IPA and
# non-IPA (external) groups, so we need to compare
Expand All @@ -630,8 +644,9 @@ def main():
if runasgroup is not None:
runasgroup_add = gen_add_list(
runasgroup,
(list(res_find.get("ipasudorunasgroup_group", []))
+ list(res_find.get("ipasudorunasextgroup", [])))
concat_attr_list(res_find,
'ipasudorunasgroup_group',
'ipasudorunasextgroup')
)

elif state == "absent":
Expand All @@ -650,7 +665,9 @@ def main():
# in sudorule
if host is not None:
host_del = gen_intersection_list(
host, res_find.get("memberhost_host"))
host, concat_attr_list(res_find,
"memberhost_host",
"externalhost"))

if hostgroup is not None:
hostgroup_del = gen_intersection_list(
Expand All @@ -662,7 +679,9 @@ def main():

if user is not None:
user_del = gen_intersection_list(
user, res_find.get("memberuser_user"))
user, concat_attr_list(res_find,
"memberuser_user",
"externaluser"))

if group is not None:
group_del = gen_intersection_list(
Expand Down Expand Up @@ -698,10 +717,10 @@ def main():
if runasuser is not None:
runasuser_del = gen_intersection_list(
runasuser,
(
list(res_find.get('ipasudorunas_user', []))
+ list(res_find.get('ipasudorunasextuser', []))
)
concat_attr_list(res_find,
'ipasudorunas_user',
'ipasudorunasextuser')

)
# runasgroup attribute can be used with both IPA and
# non-IPA (external) groups, so we need to compare
Expand All @@ -710,12 +729,9 @@ def main():
if runasgroup is not None:
runasgroup_del = gen_intersection_list(
runasgroup,
(
list(res_find.get(
"ipasudorunasgroup_group", []))
+ list(res_find.get(
"ipasudorunasextgroup", []))
)
concat_attr_list(res_find,
'ipasudorunasgroup_group',
'ipasudorunasextgroup')
)

elif state == "enabled":
Expand Down
12 changes: 12 additions & 0 deletions tests/netgroup/test_netgroup_client_context.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,15 @@
when: groups['ipaclients'] is not defined or not groups['ipaclients']
vars:
ipa_context: client

- name: Test netgroup with external members using client context, in client host.
ansible.builtin.import_playbook: test_netgroup_ext_member.yml
when: groups['ipaclients']
vars:
ipa_test_host: ipaclients

- name: Test netgroup with external members using client context, in server host.
ansible.builtin.import_playbook: test_netgroup_ext_member.yml
when: groups['ipaclients'] is not defined or not groups['ipaclients']
vars:
ipa_context: client
131 changes: 131 additions & 0 deletions tests/netgroup/test_netgroup_ext_member.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---

- name: Test netgroup with external members
hosts: "{{ ipa_test_host | default('ipaserver') }}"
become: true
gather_facts: true

tasks:
- name: Test netgroup with external members
block:
# setup
- name: Ensure netgroups are absent
ipanetgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name:
- testnetgroup1
- testnetgroup2
state: absent

- name: Ensure external host is absent
ipahost:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name:
- external.host
state: absent

- name: Ensure host is present
ipahost:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "{{ ansible_facts['fqdn'] }}"

- name: Ensure netgroup testnetgroup2 is present
ipanetgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testnetgroup2

# tests
- name: Ensure netgroup is present with hosts (action netgroup)
ipanetgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testnetgroup1
host:
- "{{ ansible_facts['fqdn'] }}"
- external.host
register: result
failed_when: not result.changed or result.failed

- name: Ensure netgroup is present with hosts (action netgroup) again
ipanetgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testnetgroup1
host:
- "{{ ansible_facts['fqdn'] }}"
- external.host
register: result
failed_when: result.changed or result.failed

- name: Ensure netgroup is present with hosts (action member)
ipanetgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testnetgroup2
host:
- "{{ ansible_facts['fqdn'] }}"
- external.host
action: member
register: result
failed_when: not result.changed or result.failed

- name: Ensure netgroup is present with hosts (action member) again
ipanetgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testnetgroup2
host:
- "{{ ansible_facts['fqdn'] }}"
- external.host
action: member
register: result
failed_when: result.changed or result.failed

- name: Ensure hosts are absent in netgroup (action member)
ipanetgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testnetgroup2
host:
- "{{ ansible_facts['fqdn'] }}"
- external.host
action: member
state: absent
register: result
failed_when: not result.changed or result.failed

- name: Ensure hosts are absent in netgroup (action member) again
ipanetgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testnetgroup2
host:
- "{{ ansible_facts['fqdn'] }}"
- external.host
action: member
state: absent
register: result
failed_when: result.changed or result.failed

always:
# cleanup
- name: Ensure netgroups are absent
ipanetgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name:
- testnetgroup1
- testnetgroup2
state: absent

- name: Ensure external host is absent
ipahost:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name:
- external.host
state: absent
Loading

0 comments on commit de7568b

Please sign in to comment.