Skip to content

Commit de7568b

Browse files
committed
New utils method that returns the lists of members.
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 <dkarpele@redhat.com>
1 parent cf27a98 commit de7568b

7 files changed

Lines changed: 467 additions & 34 deletions

File tree

plugins/module_utils/ansible_freeipa_module.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,24 @@ def gen_intersection_list(user_list, res_list):
566566
return list(set(res_list or []).intersection(set(user_list or [])))
567567

568568

569+
def concat_attr_list(res, *args):
570+
"""
571+
Get the lists of members to pass on `gen_*` methods as a res_list argument.
572+
573+
This function should be used to get members (usually users,
574+
external users, hosts, external hosts, etc.) with any action and any state.
575+
576+
It is returning the concatenation of all attributes provided by user.
577+
"""
578+
res_list = []
579+
for attribute in args:
580+
arg = res.get(attribute, [])
581+
if not isinstance(arg, (list, tuple)):
582+
arg = [arg]
583+
res_list += arg
584+
return list(set(res_list))
585+
586+
569587
def encode_certificate(cert):
570588
"""
571589
Encode a certificate using base64.

plugins/modules/ipanetgroup.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@
157157

158158
from ansible.module_utils.ansible_freeipa_module import \
159159
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \
160-
gen_add_list, gen_intersection_list, ensure_fqdn
160+
gen_add_list, gen_intersection_list, concat_attr_list, ensure_fqdn
161161

162162

163163
def find_netgroup(module, name):
@@ -339,8 +339,13 @@ def main():
339339
group_add, group_del = gen_add_del_lists(
340340
group, res_find.get("memberuser_group"))
341341

342+
# `externalhost` adds an entity to the "External host"
343+
# list for `ipanetgroup`. Hosts enrolled to IPA are in
344+
# "Member Host" list.
342345
host_add, host_del = gen_add_del_lists(
343-
host, res_find.get("memberhost_host"))
346+
host, concat_attr_list(res_find,
347+
"memberhost_host",
348+
"externalhost"))
344349

345350
hostgroup_add, hostgroup_del = gen_add_del_lists(
346351
hostgroup, res_find.get("memberhost_hostgroup"))
@@ -360,7 +365,9 @@ def main():
360365
group_add = gen_add_list(
361366
group, res_find.get("memberuser_group"))
362367
host_add = gen_add_list(
363-
host, res_find.get("memberhost_host"))
368+
host, concat_attr_list(res_find,
369+
"memberhost_host",
370+
"externalhost"))
364371
hostgroup_add = gen_add_list(
365372
hostgroup, res_find.get("memberhost_hostgroup"))
366373
netgroup_add = gen_add_list(
@@ -379,7 +386,9 @@ def main():
379386
group_del = gen_intersection_list(
380387
group, res_find.get("memberuser_group"))
381388
host_del = gen_intersection_list(
382-
host, res_find.get("memberhost_host"))
389+
host, concat_attr_list(res_find,
390+
"memberhost_host",
391+
"externalhost"))
383392
hostgroup_del = gen_intersection_list(
384393
hostgroup, res_find.get("memberhost_hostgroup"))
385394
netgroup_del = gen_intersection_list(

plugins/modules/ipasudorule.py

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,8 @@
228228

229229
from ansible.module_utils.ansible_freeipa_module import \
230230
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, gen_add_list, \
231-
gen_intersection_list, api_get_domain, ensure_fqdn, netaddr, to_text
231+
gen_intersection_list, concat_attr_list, api_get_domain, ensure_fqdn, \
232+
netaddr, to_text
232233

233234

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

508-
# Generate addition and removal lists
509+
# Generate addition and removal lists.
510+
# `externalhost` adds an entity to the "External host"
511+
# list for `ipasudorule`. Hosts enrolled to IPA are in
512+
# "Hosts" list.
509513
host_add, host_del = gen_add_del_lists(
510-
host, res_find.get('memberhost_host', []))
514+
host, concat_attr_list(res_find,
515+
"memberhost_host",
516+
"externalhost"))
511517

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

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

524+
# `externaluser` adds an entity to the "External user"
525+
# (non-IPA users) list for `ipasudorule`. Users enrolled to
526+
# IPA are in "Users" list.
518527
user_add, user_del = gen_add_del_lists(
519-
user, res_find.get('memberuser_user', []))
528+
user, concat_attr_list(res_find,
529+
"memberuser_user",
530+
"externaluser"))
520531

521532
group_add, group_del = gen_add_del_lists(
522533
group, res_find.get('memberuser_group', []))
@@ -547,10 +558,9 @@ def main():
547558
# users list.
548559
runasuser_add, runasuser_del = gen_add_del_lists(
549560
runasuser,
550-
(
551-
res_find.get('ipasudorunas_user', [])
552-
+ res_find.get('ipasudorunasextuser', [])
553-
)
561+
concat_attr_list(res_find,
562+
'ipasudorunas_user',
563+
'ipasudorunasextuser')
554564
)
555565

556566
# runasgroup attribute can be used with both IPA and
@@ -560,10 +570,9 @@ def main():
560570
# groups list.
561571
runasgroup_add, runasgroup_del = gen_add_del_lists(
562572
runasgroup,
563-
(
564-
res_find.get('ipasudorunasgroup_group', [])
565-
+ res_find.get('ipasudorunasextgroup', [])
566-
)
573+
concat_attr_list(res_find,
574+
'ipasudorunasgroup_group',
575+
'ipasudorunasextgroup')
567576
)
568577

569578
elif action == "member":
@@ -577,7 +586,9 @@ def main():
577586
# the sudorule already
578587
if host is not None:
579588
host_add = gen_add_list(
580-
host, res_find.get("memberhost_host"))
589+
host, concat_attr_list(res_find,
590+
"memberhost_host",
591+
"externalhost"))
581592
if hostgroup is not None:
582593
hostgroup_add = gen_add_list(
583594
hostgroup, res_find.get("memberhost_hostgroup"))
@@ -586,7 +597,9 @@ def main():
586597
hostmask, res_find.get("hostmask"))
587598
if user is not None:
588599
user_add = gen_add_list(
589-
user, res_find.get("memberuser_user"))
600+
user, concat_attr_list(res_find,
601+
"memberuser_user",
602+
"externaluser"))
590603
if group is not None:
591604
group_add = gen_add_list(
592605
group, res_find.get("memberuser_group"))
@@ -620,8 +633,9 @@ def main():
620633
if runasuser is not None:
621634
runasuser_add = gen_add_list(
622635
runasuser,
623-
(list(res_find.get('ipasudorunas_user', []))
624-
+ list(res_find.get('ipasudorunasextuser', [])))
636+
concat_attr_list(res_find,
637+
'ipasudorunas_user',
638+
'ipasudorunasextuser')
625639
)
626640
# runasgroup attribute can be used with both IPA and
627641
# non-IPA (external) groups, so we need to compare
@@ -630,8 +644,9 @@ def main():
630644
if runasgroup is not None:
631645
runasgroup_add = gen_add_list(
632646
runasgroup,
633-
(list(res_find.get("ipasudorunasgroup_group", []))
634-
+ list(res_find.get("ipasudorunasextgroup", [])))
647+
concat_attr_list(res_find,
648+
'ipasudorunasgroup_group',
649+
'ipasudorunasextgroup')
635650
)
636651

637652
elif state == "absent":
@@ -650,7 +665,9 @@ def main():
650665
# in sudorule
651666
if host is not None:
652667
host_del = gen_intersection_list(
653-
host, res_find.get("memberhost_host"))
668+
host, concat_attr_list(res_find,
669+
"memberhost_host",
670+
"externalhost"))
654671

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

663680
if user is not None:
664681
user_del = gen_intersection_list(
665-
user, res_find.get("memberuser_user"))
682+
user, concat_attr_list(res_find,
683+
"memberuser_user",
684+
"externaluser"))
666685

667686
if group is not None:
668687
group_del = gen_intersection_list(
@@ -698,10 +717,10 @@ def main():
698717
if runasuser is not None:
699718
runasuser_del = gen_intersection_list(
700719
runasuser,
701-
(
702-
list(res_find.get('ipasudorunas_user', []))
703-
+ list(res_find.get('ipasudorunasextuser', []))
704-
)
720+
concat_attr_list(res_find,
721+
'ipasudorunas_user',
722+
'ipasudorunasextuser')
723+
705724
)
706725
# runasgroup attribute can be used with both IPA and
707726
# non-IPA (external) groups, so we need to compare
@@ -710,12 +729,9 @@ def main():
710729
if runasgroup is not None:
711730
runasgroup_del = gen_intersection_list(
712731
runasgroup,
713-
(
714-
list(res_find.get(
715-
"ipasudorunasgroup_group", []))
716-
+ list(res_find.get(
717-
"ipasudorunasextgroup", []))
718-
)
732+
concat_attr_list(res_find,
733+
'ipasudorunasgroup_group',
734+
'ipasudorunasextgroup')
719735
)
720736

721737
elif state == "enabled":

tests/netgroup/test_netgroup_client_context.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,15 @@
4949
when: groups['ipaclients'] is not defined or not groups['ipaclients']
5050
vars:
5151
ipa_context: client
52+
53+
- name: Test netgroup with external members using client context, in client host.
54+
ansible.builtin.import_playbook: test_netgroup_ext_member.yml
55+
when: groups['ipaclients']
56+
vars:
57+
ipa_test_host: ipaclients
58+
59+
- name: Test netgroup with external members using client context, in server host.
60+
ansible.builtin.import_playbook: test_netgroup_ext_member.yml
61+
when: groups['ipaclients'] is not defined or not groups['ipaclients']
62+
vars:
63+
ipa_context: client
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
---
2+
3+
- name: Test netgroup with external members
4+
hosts: "{{ ipa_test_host | default('ipaserver') }}"
5+
become: true
6+
gather_facts: true
7+
8+
tasks:
9+
- name: Test netgroup with external members
10+
block:
11+
# setup
12+
- name: Ensure netgroups are absent
13+
ipanetgroup:
14+
ipaadmin_password: SomeADMINpassword
15+
ipaapi_context: "{{ ipa_context | default(omit) }}"
16+
name:
17+
- testnetgroup1
18+
- testnetgroup2
19+
state: absent
20+
21+
- name: Ensure external host is absent
22+
ipahost:
23+
ipaadmin_password: SomeADMINpassword
24+
ipaapi_context: "{{ ipa_context | default(omit) }}"
25+
name:
26+
- external.host
27+
state: absent
28+
29+
- name: Ensure host is present
30+
ipahost:
31+
ipaadmin_password: SomeADMINpassword
32+
ipaapi_context: "{{ ipa_context | default(omit) }}"
33+
name: "{{ ansible_facts['fqdn'] }}"
34+
35+
- name: Ensure netgroup testnetgroup2 is present
36+
ipanetgroup:
37+
ipaadmin_password: SomeADMINpassword
38+
ipaapi_context: "{{ ipa_context | default(omit) }}"
39+
name: testnetgroup2
40+
41+
# tests
42+
- name: Ensure netgroup is present with hosts (action netgroup)
43+
ipanetgroup:
44+
ipaadmin_password: SomeADMINpassword
45+
ipaapi_context: "{{ ipa_context | default(omit) }}"
46+
name: testnetgroup1
47+
host:
48+
- "{{ ansible_facts['fqdn'] }}"
49+
- external.host
50+
register: result
51+
failed_when: not result.changed or result.failed
52+
53+
- name: Ensure netgroup is present with hosts (action netgroup) again
54+
ipanetgroup:
55+
ipaadmin_password: SomeADMINpassword
56+
ipaapi_context: "{{ ipa_context | default(omit) }}"
57+
name: testnetgroup1
58+
host:
59+
- "{{ ansible_facts['fqdn'] }}"
60+
- external.host
61+
register: result
62+
failed_when: result.changed or result.failed
63+
64+
- name: Ensure netgroup is present with hosts (action member)
65+
ipanetgroup:
66+
ipaadmin_password: SomeADMINpassword
67+
ipaapi_context: "{{ ipa_context | default(omit) }}"
68+
name: testnetgroup2
69+
host:
70+
- "{{ ansible_facts['fqdn'] }}"
71+
- external.host
72+
action: member
73+
register: result
74+
failed_when: not result.changed or result.failed
75+
76+
- name: Ensure netgroup is present with hosts (action member) again
77+
ipanetgroup:
78+
ipaadmin_password: SomeADMINpassword
79+
ipaapi_context: "{{ ipa_context | default(omit) }}"
80+
name: testnetgroup2
81+
host:
82+
- "{{ ansible_facts['fqdn'] }}"
83+
- external.host
84+
action: member
85+
register: result
86+
failed_when: result.changed or result.failed
87+
88+
- name: Ensure hosts are absent in netgroup (action member)
89+
ipanetgroup:
90+
ipaadmin_password: SomeADMINpassword
91+
ipaapi_context: "{{ ipa_context | default(omit) }}"
92+
name: testnetgroup2
93+
host:
94+
- "{{ ansible_facts['fqdn'] }}"
95+
- external.host
96+
action: member
97+
state: absent
98+
register: result
99+
failed_when: not result.changed or result.failed
100+
101+
- name: Ensure hosts are absent in netgroup (action member) again
102+
ipanetgroup:
103+
ipaadmin_password: SomeADMINpassword
104+
ipaapi_context: "{{ ipa_context | default(omit) }}"
105+
name: testnetgroup2
106+
host:
107+
- "{{ ansible_facts['fqdn'] }}"
108+
- external.host
109+
action: member
110+
state: absent
111+
register: result
112+
failed_when: result.changed or result.failed
113+
114+
always:
115+
# cleanup
116+
- name: Ensure netgroups are absent
117+
ipanetgroup:
118+
ipaadmin_password: SomeADMINpassword
119+
ipaapi_context: "{{ ipa_context | default(omit) }}"
120+
name:
121+
- testnetgroup1
122+
- testnetgroup2
123+
state: absent
124+
125+
- name: Ensure external host is absent
126+
ipahost:
127+
ipaadmin_password: SomeADMINpassword
128+
ipaapi_context: "{{ ipa_context | default(omit) }}"
129+
name:
130+
- external.host
131+
state: absent

0 commit comments

Comments
 (0)