diff --git a/README-group.md b/README-group.md
index 021fc105..181e407c 100644
--- a/README-group.md
+++ b/README-group.md
@@ -8,6 +8,9 @@ The group module allows to ensure presence and absence of groups and members of
The group module is as compatible as possible to the Ansible upstream `ipa_group` module, but additionally offers to add users to a group and also to remove users from a group.
+## Note
+Ensuring presence (adding) of several groups with mixed types (`external`, `nonposix` and `posix`) requires a fix in FreeIPA. The module implements a workaround to automatically use `client` context if the fix is not present in the target node FreeIPA and if more than one group is provided to the task using the `groups` parameter. If `ipaapi_context` is forced to be `server`, the module will fail in this case.
+
Features
--------
@@ -71,6 +74,62 @@ Example playbook to add groups:
name: appops
```
+These three `ipagroup` module calls can be combined into one with the `groups` variable:
+
+```yaml
+---
+- name: Playbook to handle groups
+ hosts: ipaserver
+
+ tasks:
+ - name: Ensure groups ops, sysops and appops are present
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: ops
+ gidnumber: 1234
+ - name: sysops
+ user:
+ - pinky
+ - name: appops
+```
+
+You can also alternatively use a json file containing the groups, here `groups_present.json`:
+
+```json
+{
+ "groups": [
+ {
+ "name": "group1",
+ "description": "description group1"
+ },
+ {
+ "name": "group2",
+ "description": "description group2"
+ }
+ ]
+}
+```
+
+And ensure the presence of the groups with this example playbook:
+
+```yaml
+---
+- name: Tests
+ hosts: ipaserver
+ gather_facts: false
+
+ tasks:
+ - name: Include groups_present.json
+ include_vars:
+ file: groups_present.json
+
+ - name: Groups present
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups: "{{ groups }}"
+```
+
Example playbook to add users to a group:
```yaml
@@ -112,11 +171,11 @@ Example playbook to add group members to a group:
Example playbook to add members from a trusted realm to an external group:
```yaml
---
+---
- name: Playbook to handle groups.
hosts: ipaserver
- became: true
-
+
+ tasks:
- name: Create an external group and add members from a trust to it.
ipagroup:
ipaadmin_password: SomeADMINpassword
@@ -127,6 +186,24 @@ Example playbook to add members from a trusted realm to an external group:
- WINIPA\\Developers
```
+Example playbook to add nonposix and external groups:
+
+```yaml
+---
+- name: Playbook to add nonposix and external groups
+ hosts: ipaserver
+
+ tasks:
+ - name: Add nonposix group sysops and external group appops
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: sysops
+ nonposix: true
+ - name: appops
+ external: true
+```
+
Example playbook to remove groups:
```yaml
@@ -136,13 +213,29 @@ Example playbook to remove groups:
become: true
tasks:
- # Remove goups sysops, appops and ops
+ # Remove groups sysops, appops and ops
- ipagroup:
ipaadmin_password: SomeADMINpassword
name: sysops,appops,ops
state: absent
```
+Example playbook to ensure groups are absent:
+
+```yaml
+---
+- name: Playbook to handle groups
+ hosts: ipaserver
+
+ tasks:
+ - name: Ensure groups ops and sysops are absent
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: ops
+ - name: sysops
+ state: absent
+```
Variables
=========
@@ -152,8 +245,10 @@ Variable | Description | Required
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
-`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to yes. (bool) | no
+`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to
. (bool) | no
`name` \| `cn` | The list of group name strings. | no
+`groups` | The list of group dicts. Each `groups` dict entry can contain group variables.
There is one required option in the `groups` dict:| no
+ | `name` - The group name string of the entry. | yes
`description` | The group description string. | no
`gid` \| `gidnumber` | The GID integer. | no
`posix` | Create a non-POSIX group or change a non-POSIX to a posix group. `nonposix`, `posix` and `external` are mutually exclusive. (bool) | no
diff --git a/playbooks/user/add-group.yml b/playbooks/group/add-group.yml
similarity index 100%
rename from playbooks/user/add-group.yml
rename to playbooks/group/add-group.yml
diff --git a/playbooks/user/add-groups-to-group.yml b/playbooks/group/add-groups-to-group.yml
similarity index 100%
rename from playbooks/user/add-groups-to-group.yml
rename to playbooks/group/add-groups-to-group.yml
diff --git a/playbooks/group/add-groups.yml b/playbooks/group/add-groups.yml
new file mode 100644
index 00000000..3b1802ff
--- /dev/null
+++ b/playbooks/group/add-groups.yml
@@ -0,0 +1,32 @@
+---
+- name: Playbook to handle multiple groups
+ hosts: ipaserver
+
+ tasks:
+ - name: Create multiple groups ops, sysops
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: ops
+ gidnumber: 1234
+ - name: sysops
+
+ - name: Add user and group members to groups sysops and appops
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: sysops
+ user:
+ - user1
+ - name: appops
+ group:
+ - group2
+
+ - name: Create multiple non-POSIX and external groups
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: nongroup
+ nonposix: true
+ - name: extgroup
+ external: true
diff --git a/playbooks/user/add-user-to-group.yml b/playbooks/group/add-user-to-group.yml
similarity index 100%
rename from playbooks/user/add-user-to-group.yml
rename to playbooks/group/add-user-to-group.yml
diff --git a/playbooks/user/delete-group.yml b/playbooks/group/delete-group.yml
similarity index 100%
rename from playbooks/user/delete-group.yml
rename to playbooks/group/delete-group.yml
diff --git a/plugins/modules/ipagroup.py b/plugins/modules/ipagroup.py
index 2488f17f..e0d5bb12 100644
--- a/plugins/modules/ipagroup.py
+++ b/plugins/modules/ipagroup.py
@@ -41,8 +41,88 @@ options:
description: The group name
type: list
elements: str
- required: true
+ required: false
aliases: ["cn"]
+ groups:
+ description: The list of group dicts (internally gid).
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: The group (internally gid).
+ type: str
+ required: true
+ aliases: ["cn"]
+ description:
+ description: The group description
+ type: str
+ required: false
+ gid:
+ description: The GID
+ type: int
+ required: false
+ aliases: ["gidnumber"]
+ nonposix:
+ description: Create as a non-POSIX group
+ required: false
+ type: bool
+ external:
+ description: Allow adding external non-IPA members from trusted domains
+ required: false
+ type: bool
+ posix:
+ description:
+ Create a non-POSIX group or change a non-POSIX to a posix group.
+ required: false
+ type: bool
+ nomembers:
+ description: Suppress processing of membership attributes
+ required: false
+ type: bool
+ user:
+ description: List of user names assigned to this group.
+ required: false
+ type: list
+ elements: str
+ group:
+ description: List of group names assigned to this group.
+ required: false
+ type: list
+ elements: str
+ service:
+ description:
+ - List of service names assigned to this group.
+ - Only usable with IPA versions 4.7 and up.
+ required: false
+ type: list
+ elements: str
+ membermanager_user:
+ description:
+ - List of member manager users assigned to this group.
+ - Only usable with IPA versions 4.8.4 and up.
+ required: false
+ type: list
+ elements: str
+ membermanager_group:
+ description:
+ - List of member manager groups assigned to this group.
+ - Only usable with IPA versions 4.8.4 and up.
+ required: false
+ type: list
+ elements: str
+ externalmember:
+ description:
+ - List of members of a trusted domain in DOM\\name or name@domain form.
+ required: false
+ type: list
+ elements: str
+ aliases: ["ipaexternalmember", "external_member"]
+ idoverrideuser:
+ description:
+ - User ID overrides to add
+ required: false
+ type: list
+ elements: str
description:
description: The group description
type: str
@@ -144,6 +224,14 @@ EXAMPLES = """
ipaadmin_password: SomeADMINpassword
name: appops
+# Create multiple groups ops, sysops
+- ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: ops
+ gidnumber: 1234
+ - name: sysops
+
# Add user member pinky to group sysops
- ipagroup:
ipaadmin_password: SomeADMINpassword
@@ -160,7 +248,7 @@ EXAMPLES = """
user:
- brain
-# Add group members sysops and appops to group sysops
+# Add group members sysops and appops to group ops
- ipagroup:
ipaadmin_password: SomeADMINpassword
name: ops
@@ -168,6 +256,17 @@ EXAMPLES = """
- sysops
- appops
+# Add user and group members to groups sysops and appops
+- ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: sysops
+ user:
+ - user1
+ - name: appops
+ group:
+ - group2
+
# Create a non-POSIX group
- ipagroup:
ipaadmin_password: SomeADMINpassword
@@ -189,7 +288,16 @@ EXAMPLES = """
- WINIPA\\Web Users
- WINIPA\\Developers
-# Remove goups sysops, appops, ops and nongroup
+# Create multiple non-POSIX and external groups
+- ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: nongroup
+ nonposix: true
+ - name: extgroup
+ external: true
+
+# Remove groups sysops, appops, ops and nongroup
- ipagroup:
ipaadmin_password: SomeADMINpassword
name: sysops,appops,ops, nongroup
@@ -203,6 +311,9 @@ from ansible.module_utils._text import to_text
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \
gen_add_list, gen_intersection_list, api_check_param
+from ansible.module_utils import six
+if six.PY3:
+ unicode = str
def find_group(module, name):
@@ -257,6 +368,22 @@ def gen_member_args(user, group, service, externalmember, idoverrideuser):
return _args
+def check_parameters(module, state, action):
+ invalid = []
+ if state == "present":
+ if action == "member":
+ invalid = ["description", "gid", "posix", "nonposix", "external",
+ "nomembers"]
+
+ else:
+ invalid = ["description", "gid", "posix", "nonposix", "external",
+ "nomembers"]
+ if action == "group":
+ invalid.extend(["user", "group", "service", "externalmember"])
+
+ module.params_fail_used_invalid(invalid, state, action)
+
+
def is_external_group(res_find):
"""Verify if the result group is an external group."""
return res_find and 'ipaexternalgroup' in res_find['objectclass']
@@ -285,45 +412,63 @@ def check_objectclass_args(module, res_find, posix, external):
def main():
+ group_spec = dict(
+ # present
+ description=dict(type="str", default=None),
+ gid=dict(type="int", aliases=["gidnumber"], default=None),
+ nonposix=dict(required=False, type='bool', default=None),
+ external=dict(required=False, type='bool', default=None),
+ posix=dict(required=False, type='bool', default=None),
+ nomembers=dict(required=False, type='bool', default=None),
+ user=dict(required=False, type='list', elements="str",
+ default=None),
+ group=dict(required=False, type='list', elements="str",
+ default=None),
+ service=dict(required=False, type='list', elements="str",
+ default=None),
+ idoverrideuser=dict(required=False, type='list', elements="str",
+ default=None),
+ membermanager_user=dict(required=False, type='list',
+ elements="str", default=None),
+ membermanager_group=dict(required=False, type='list',
+ elements="str", default=None),
+ externalmember=dict(required=False, type='list', elements="str",
+ default=None,
+ aliases=[
+ "ipaexternalmember",
+ "external_member"
+ ])
+ )
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
name=dict(type="list", elements="str", aliases=["cn"],
- required=True),
- # present
- description=dict(type="str", default=None),
- gid=dict(type="int", aliases=["gidnumber"], default=None),
- nonposix=dict(required=False, type='bool', default=None),
- external=dict(required=False, type='bool', default=None),
- posix=dict(required=False, type='bool', default=None),
- nomembers=dict(required=False, type='bool', default=None),
- user=dict(required=False, type='list', elements="str",
- default=None),
- group=dict(required=False, type='list', elements="str",
- default=None),
- service=dict(required=False, type='list', elements="str",
- default=None),
- idoverrideuser=dict(required=False, type='list', elements="str",
- default=None),
- membermanager_user=dict(required=False, type='list',
- elements="str", default=None),
- membermanager_group=dict(required=False, type='list',
- elements="str", default=None),
- externalmember=dict(required=False, type='list', elements="str",
- default=None,
- aliases=[
- "ipaexternalmember",
- "external_member"
- ]),
+ default=None, required=False),
+ groups=dict(type="list",
+ default=None,
+ options=dict(
+ # Here name is a simple string
+ name=dict(type="str", required=True,
+ aliases=["cn"]),
+ # Add group specific parameters
+ **group_spec
+ ),
+ elements='dict',
+ required=False),
+ # general
action=dict(type="str", default="group",
choices=["member", "group"]),
- # state
state=dict(type="str", default="present",
choices=["present", "absent"]),
+
+ # Add group specific parameters for simple use case
+ **group_spec
),
# It does not make sense to set posix, nonposix or external at the
# same time
- mutually_exclusive=[['posix', 'nonposix', 'external']],
+ mutually_exclusive=[['posix', 'nonposix', 'external'],
+ ["name", "groups"]],
+ required_one_of=[["name", "groups"]],
supports_check_mode=True,
)
@@ -333,6 +478,7 @@ def main():
# general
names = ansible_module.params_get("name")
+ groups = ansible_module.params_get("groups")
# present
description = ansible_module.params_get("description")
@@ -354,31 +500,26 @@ def main():
state = ansible_module.params_get("state")
# Check parameters
- invalid = []
+
+ if (names is None or len(names) < 1) and \
+ (groups is None or len(groups) < 1):
+ ansible_module.fail_json(msg="At least one name or groups is required")
if state == "present":
- if len(names) != 1:
+ if names is not None and len(names) != 1:
ansible_module.fail_json(
- msg="Only one group can be added at a time.")
- if action == "member":
- invalid = ["description", "gid", "posix", "nonposix", "external",
- "nomembers"]
+ msg="Only one group can be added at a time using 'name'.")
- if state == "absent":
- if len(names) < 1:
- ansible_module.fail_json(
- msg="No name given.")
- invalid = ["description", "gid", "posix", "nonposix", "external",
- "nomembers"]
- if action == "group":
- invalid.extend(["user", "group", "service", "externalmember"])
-
- ansible_module.params_fail_used_invalid(invalid, state, action)
+ check_parameters(ansible_module, state, action)
if external is False:
ansible_module.fail_json(
msg="group can not be non-external")
+ # Use groups if names is None
+ if groups is not None:
+ names = groups
+
# Init
changed = False
@@ -415,8 +556,57 @@ def main():
"supported by your IPA version")
commands = []
+ group_set = set()
+
+ for group_name in names:
+ if isinstance(group_name, dict):
+ name = group_name.get("name")
+ if name in group_set:
+ ansible_module.fail_json(
+ msg="group '%s' is used more than once" % name)
+ group_set.add(name)
+ # present
+ description = group_name.get("description")
+ gid = group_name.get("gid")
+ nonposix = group_name.get("nonposix")
+ external = group_name.get("external")
+ idoverrideuser = group_name.get("idoverrideuser")
+ posix = group_name.get("posix")
+ # Check mutually exclusive condition for multiple groups
+ # creation. It's not possible to check it with
+ # `mutually_exclusive` argument in `IPAAnsibleModule` class
+ # because it accepts only (list[str] or list[list[str]]). Here
+ # we need to loop over all groups and fail on mutually
+ # exclusive ones.
+ if all((posix, nonposix)) or\
+ all((posix, external)) or\
+ all((nonposix, external)):
+ ansible_module.fail_json(
+ msg="parameters are mutually exclusive for group "
+ "`{0}`: posix|nonposix|external".format(name))
+ # Duplicating the condition for multiple group creation
+ if external is False:
+ ansible_module.fail_json(
+ msg="group can not be non-external")
+ # If nonposix is used, set posix as not nonposix
+ if nonposix is not None:
+ posix = not nonposix
+ user = group_name.get("user")
+ group = group_name.get("group")
+ service = group_name.get("service")
+ membermanager_user = group_name.get("membermanager_user")
+ membermanager_group = group_name.get("membermanager_group")
+ externalmember = group_name.get("externalmember")
+ nomembers = group_name.get("nomembers")
+
+ check_parameters(ansible_module, state, action)
+
+ elif isinstance(group_name, (str, unicode)):
+ name = group_name
+ else:
+ ansible_module.fail_json(msg="Group '%s' is not valid" %
+ repr(group_name))
- for name in names:
# Make sure group exists
res_find = find_group(ansible_module, name)
diff --git a/tests/group/create_groups_json.yml b/tests/group/create_groups_json.yml
new file mode 100644
index 00000000..ef9dd393
--- /dev/null
+++ b/tests/group/create_groups_json.yml
@@ -0,0 +1,13 @@
+---
+- name: Create groups.json
+ hosts: localhost
+
+ tasks:
+ - name: Check if groups.json exists
+ ansible.builtin.stat:
+ path: groups.json
+ register: register_stat_groups
+
+ - name: Create groups.json
+ ansible.builtin.command: /bin/bash groups.sh 500
+ when: not register_stat_groups.stat.exists
diff --git a/tests/group/groups.sh b/tests/group/groups.sh
new file mode 100644
index 00000000..63e36c01
--- /dev/null
+++ b/tests/group/groups.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+NUM=${1-1000}
+FILE="groups.json"
+
+echo "{" > "$FILE"
+
+echo " \"group_list\": [" >> "$FILE"
+
+for i in $(seq 1 "$NUM"); do
+ {
+ echo " {"
+ echo " \"name\": \"group$i\","
+ echo " \"description\": \"group description $i\""
+ } >> "$FILE"
+ if [ "$i" -lt "$NUM" ]; then
+ echo " }," >> "$FILE"
+ else
+ echo " }" >> "$FILE"
+ fi
+done
+
+echo " ]" >> "$FILE"
+
+echo "}" >> "$FILE"
diff --git a/tests/group/test_group_client_context.yml b/tests/group/test_group_client_context.yml
index 4a1d7ac9..8d0132de 100644
--- a/tests/group/test_group_client_context.yml
+++ b/tests/group/test_group_client_context.yml
@@ -37,3 +37,27 @@
when: groups['ipaclients'] is not defined or not groups['ipaclients']
vars:
ipa_context: client
+
+- name: Test groups using client context, in client host.
+ ansible.builtin.import_playbook: test_groups.yml
+ when: groups['ipaclients']
+ vars:
+ ipa_test_host: ipaclients
+
+- name: Test groups using client context, in server host.
+ ansible.builtin.import_playbook: test_groups.yml
+ when: groups['ipaclients'] is not defined or not groups['ipaclients']
+ vars:
+ ipa_context: client
+
+- name: Test groups with mixed types using client context, in client host.
+ ansible.builtin.import_playbook: test_groups_external_nonposix.yml
+ when: groups['ipaclients']
+ vars:
+ ipa_test_host: ipaclients
+
+- name: Test groups with mixed types using client context, in server host.
+ ansible.builtin.import_playbook: test_groups_external_nonposix.yml
+ when: groups['ipaclients'] is not defined or not groups['ipaclients']
+ vars:
+ ipa_context: client
diff --git a/tests/group/test_groups.yml b/tests/group/test_groups.yml
new file mode 100644
index 00000000..648b35b8
--- /dev/null
+++ b/tests/group/test_groups.yml
@@ -0,0 +1,143 @@
+---
+- name: Test groups
+ hosts: "{{ ipa_test_host | default('ipaserver') }}"
+ gather_facts: true
+
+ tasks:
+ # setup
+ - name: Include tasks ../env_freeipa_facts.yml
+ ansible.builtin.include_tasks: ../env_freeipa_facts.yml
+
+ # GET FQDN_AT_DOMAIN
+
+ - name: Get fqdn_at_domain
+ ansible.builtin.set_fact:
+ fqdn_at_domain: "{{ ansible_facts['fqdn'] + '@' + ipaserver_realm }}"
+
+ # CLEANUP TEST ITEMS
+
+ - name: Remove test groups
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ name: group1,group2,group3,group4,group5,group6,group7,group8,group9,group10
+ state: absent
+
+ - name: Remove test users
+ ipauser:
+ ipaadmin_password: SomeADMINpassword
+ name: user1,user2,user3
+ state: absent
+
+ # CREATE TEST ITEMS
+
+ - name: Users user1..3 present
+ ipauser:
+ ipaadmin_password: SomeADMINpassword
+ users:
+ - name: user1
+ first: user1
+ last: Last
+ - name: user2
+ first: user2
+ last: Last
+ - name: user3
+ first: user3
+ last: Last
+
+ # TESTS
+
+ - name: Groups group1..10 present
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: group1
+ - name: group2
+ user:
+ - user1
+ - user2
+ - user3
+ - name: group3
+ group:
+ - group1
+ - group2
+ - name: group4
+ - name: group5
+ - name: group6
+ - name: group7
+ - name: group8
+ - name: group9
+ - name: group10
+ register: result
+ failed_when: not result.changed or result.failed
+
+ - name: Groups group1..10 present again
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: group1
+ - name: group2
+ - name: group3
+ - name: group4
+ - name: group5
+ - name: group6
+ - name: group7
+ - name: group8
+ - name: group9
+ - name: group10
+ register: result
+ failed_when: result.changed or result.failed
+
+ # failed_when: not result.failed has been added as this test needs to
+ # fail because two groups with the same name should be added in the same
+ # task.
+ - name: Duplicate names in groups failure test
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: group1
+ - name: group2
+ - name: group3
+ - name: group3
+ register: result
+ failed_when: result.changed or not result.failed or "is used more than once" not in result.msg
+
+ - name: Groups/name and name group11 present
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ name: group11
+ groups:
+ - name: group11
+ register: result
+ failed_when: result.changed or not result.failed or "parameters are mutually exclusive" not in result.msg
+
+ - name: Groups/name and name are absent
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ register: result
+ failed_when: result.changed or not result.failed or "one of the following is required" not in result.msg
+
+ - name: Name is absent
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ name:
+ register: result
+ failed_when: result.changed or not result.failed or "At least one name or groups is required" not in result.msg
+
+ - name: Only one group can be added at a time using name.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ name: group11,group12
+ register: result
+ failed_when: result.changed or not result.failed or "Only one group can be added at a time using 'name'." not in result.msg
+
+ - name: Remove test groups
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ name: group1,group2,group3,group4,group5,group6,group7,group8,group9,group10
+ state: absent
+
+ - name: Remove test users
+ ipauser:
+ ipaadmin_password: SomeADMINpassword
+ name: user1,user2,user3
+ state: absent
diff --git a/tests/group/test_groups_absent.yml b/tests/group/test_groups_absent.yml
new file mode 100644
index 00000000..3238f4e2
--- /dev/null
+++ b/tests/group/test_groups_absent.yml
@@ -0,0 +1,35 @@
+---
+- name: Include create_groups_json.yml
+ ansible.builtin.import_playbook: create_groups_json.yml
+
+- name: Test groups absent
+ hosts: ipaserver
+ gather_facts: false
+
+ tasks:
+ - name: Include groups.json
+ ansible.builtin.include_vars:
+ file: groups.json # noqa 505
+
+ - name: Initialize groups_names
+ ansible.builtin.set_fact:
+ groups_names: []
+
+ - name: Create dict with group names
+ ansible.builtin.set_fact:
+ groups_names: "{{ groups_names | default([]) + [{'name': item.name}] }}"
+ loop: "{{ group_list }}"
+
+ - name: Groups absent len:{{ group_list | length }}
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups: "{{ groups_names }}"
+ state: absent
+
+- name: Remove groups.json
+ hosts: localhost
+ tasks:
+ - name: Remove groups.json
+ ansible.builtin.file:
+ state: absent
+ path: groups.json
diff --git a/tests/group/test_groups_external_nonposix.yml b/tests/group/test_groups_external_nonposix.yml
new file mode 100644
index 00000000..5116b723
--- /dev/null
+++ b/tests/group/test_groups_external_nonposix.yml
@@ -0,0 +1,395 @@
+---
+- name: Test multiple external and nonposix groups
+ hosts: "{{ ipa_test_host | default('ipaserver') }}"
+ gather_facts: true
+
+ tasks:
+ # setup
+ - name: Include tasks ../env_freeipa_facts.yml
+ ansible.builtin.include_tasks: ../env_freeipa_facts.yml
+
+ # GET FQDN_AT_DOMAIN
+
+ - name: Get fqdn_at_domain
+ ansible.builtin.set_fact:
+ fqdn_at_domain: "{{ ansible_facts['fqdn'] + '@' + ipaserver_realm }}"
+
+ # CLEANUP TEST ITEMS
+
+ - name: Remove testing groups.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ name:
+ - extgroup
+ - nonposixgroup
+ - posixgroup
+ - fail_group
+ - group_1
+ - posix_group_1
+ - nonposix_group_1
+ - external_group_1
+ - external_group_2
+ state: absent
+
+ - name: Ensure test users testuser1, testuser2 and testuser3 are absent
+ ipauser:
+ ipaadmin_password: SomeADMINpassword
+ name: testuser1,testuser2,testuser3
+ state: absent
+
+ # CREATE TEST ITEMS
+
+ - name: Ensure test users testuser1..testuser3 are present
+ ipauser:
+ ipaadmin_password: SomeADMINpassword
+ users:
+ - name: testuser1
+ first: testuser1
+ last: Last
+ - name: testuser2
+ first: testuser2
+ last: Last
+ - name: testuser3
+ first: testuser3
+ last: Last
+ register: result
+ failed_when: not result.changed or result.failed
+
+ - name: Add nonposix group.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: extgroup
+ nonposix: true
+ register: result
+ failed_when: result.failed or not result.changed
+
+ - name: Add nonposix group, again.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: extgroup
+ nonposix: true
+ register: result
+ failed_when: result.failed or result.changed
+
+ - name: Set group to be external
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: extgroup
+ external: true
+ register: result
+ failed_when: result.failed or not result.changed
+
+ - name: Set group to be external, again.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: extgroup
+ external: true
+ register: result
+ failed_when: result.failed or result.changed
+
+ - name: Set external group to be non-external.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: extgroup
+ external: false
+ register: result
+ failed_when: not result.failed or "group can not be non-external" not in result.msg
+
+ - name: Set external group to be posix.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: extgroup
+ posix: true
+ register: result
+ failed_when: not result.failed or "Cannot change `external` group" not in result.msg
+
+ - name: Add nonposix group.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: posixgroup
+ nonposix: true
+ register: result
+ failed_when: result.failed or not result.changed
+
+ - name: Set group to be posix
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: posixgroup
+ posix: true
+ register: result
+ failed_when: result.failed or not result.changed
+
+ - name: Set group to be posix, again.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: posixgroup
+ posix: true
+ register: result
+ failed_when: result.failed or result.changed
+
+ - name: Set posix group to be external.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: posixgroup
+ external: true
+ register: result
+ failed_when: not result.failed or "Cannot change `posix` group" not in result.msg
+
+ - name: Set posix group to be non-posix.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: posixgroup
+ posix: false
+ register: result
+ failed_when: not result.failed or "Cannot change `posix` group" not in result.msg
+
+ - name: Set posix group to be non-posix.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: posixgroup
+ nonposix: true
+ register: result
+ failed_when: not result.failed or "Cannot change `posix` group" not in result.msg
+
+ - name: Add nonposix group.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: nonposixgroup
+ posix: false
+ register: result
+ failed_when: result.failed or not result.changed
+
+ - name: Add nonposix group, again.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: nonposixgroup
+ nonposix: true
+ register: result
+ failed_when: result.failed or result.changed
+
+
+ # NONPOSIX MEMBER TEST
+
+ - name: Ensure users testuser1, testuser2 and testuser3 are present in group nonposixgroup
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: nonposixgroup
+ nonposix: true
+ user:
+ - testuser1
+ - testuser2
+ - testuser3
+ register: result
+ failed_when: not result.changed or result.failed
+
+ - name: Ensure users testuser1, testuser2 and testuser3 are present in group nonposixgroup again
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: nonposixgroup
+ nonposix: true
+ user:
+ - testuser1
+ - testuser2
+ - testuser3
+ register: result
+ failed_when: result.changed or result.failed
+
+
+ # POSIX MEMBER TEST
+
+ - name: Ensure users testuser1, testuser2 and testuser3 are present in group posixgroup
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: posixgroup
+ posix: true
+ user:
+ - testuser1
+ - testuser2
+ - testuser3
+ register: result
+ failed_when: not result.changed or result.failed
+
+ - name: Ensure users testuser1, testuser2 and testuser3 are present in group posixgroup again
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: posixgroup
+ posix: true
+ user:
+ - testuser1
+ - testuser2
+ - testuser3
+ register: result
+ failed_when: result.changed or result.failed
+
+ # EXTERNAL MEMBER TEST (REQUIRES AD)
+
+ - name: Execute group tests if trust test environment is supported
+ when: trust_test_is_supported | default(false)
+ block:
+ - name: Ensure users testuser1, testuser2 and testuser3 are present in group externalgroup
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: externalgroup
+ external: true
+ user:
+ - testuser1
+ - testuser2
+ - testuser3
+ register: result
+ failed_when: not result.changed or result.failed
+
+ - name: Ensure users testuser1, testuser2 and testuser3 are present in group externalgroup again
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: externalgroup
+ external: true
+ user:
+ - testuser1
+ - testuser2
+ - testuser3
+ register: result
+ failed_when: result.changed or result.failed
+
+
+ # CONVERT NONPOSIX TO POSIX GROUP WITH USERS
+
+ - name: Ensure nonposix group nonposixgroup as posix
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: nonposixgroup
+ posix: true
+ register: result
+ failed_when: not result.changed or result.failed
+
+ - name: Ensure nonposix group nonposixgroup as posix, again
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: nonposixgroup
+ posix: true
+ register: result
+ failed_when: result.changed or result.failed
+
+ - name: Ensure nonposix group nonposixgroup (now posix) has users still
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: nonposixgroup
+ posix: true
+ user:
+ - testuser1
+ - testuser2
+ - testuser3
+ register: result
+ failed_when: result.changed or result.failed
+
+ # FAIL ON COMBINATIONS OF NONPOSIX, POSIX AND EXTERNAL
+
+ - name: Fail to ensure group as nonposix and posix
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: firstgroup
+ nonposix: true
+ posix: false
+ - name: fail_group
+ nonposix: true
+ posix: true
+ register: result
+ failed_when: not result.failed or "parameters are mutually exclusive for group `fail_group`" not in result.msg
+
+ - name: Fail to ensure group as nonposix and external
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: firstgroup
+ nonposix: true
+ posix: false
+ - name: fail_group
+ nonposix: true
+ external: true
+ register: result
+ failed_when: not result.failed or "parameters are mutually exclusive for group `fail_group`" not in result.msg
+
+ - name: Fail to ensure group as posix and external
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: firstgroup
+ nonposix: true
+ posix: false
+ - name: fail_group
+ posix: true
+ external: true
+ register: result
+ failed_when: not result.failed or "parameters are mutually exclusive for group `fail_group`" not in result.msg
+
+ # GROUPS WITH MIXED TYPES
+
+ - name: Adding posix, nonposix and external groups in one batch
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: posix_group_1
+ posix: true
+ - name: nonposix_group_1
+ nonposix: true
+ - name: external_group_1
+ external: true
+ register: result
+ failed_when: not result.changed or result.failed
+
+ - name: Adding non-external and external groups in one batch
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups:
+ - name: non_external_group_2
+ - name: external_group_2
+ external: true
+ register: result
+ failed_when: not result.changed or result.failed
+
+ # CLEANUP
+
+ - name: Remove testing groups.
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ name:
+ - extgroup
+ - nonposixgroup
+ - posixgroup
+ - fail_group
+ - group_1
+ - posix_group_1
+ - nonposix_group_1
+ - external_group_1
+ - external_group_2
+ - non_external_group_2
+ state: absent
+
+ - name: Ensure test users testuser1, testuser2 and testuser3 are absent
+ ipauser:
+ ipaadmin_password: SomeADMINpassword
+ name: testuser1,testuser2,testuser3
+ state: absent
diff --git a/tests/group/test_groups_present.yml b/tests/group/test_groups_present.yml
new file mode 100644
index 00000000..71ed659d
--- /dev/null
+++ b/tests/group/test_groups_present.yml
@@ -0,0 +1,40 @@
+---
+- name: Include create_groups_json.yml
+ ansible.builtin.import_playbook: create_groups_json.yml
+
+- name: Test groups present
+ hosts: ipaserver
+ gather_facts: false
+
+ tasks:
+ - name: Include groups.json
+ ansible.builtin.include_vars:
+ file: groups.json # noqa 505
+
+ - name: Groups present len:{{ group_list | length }}
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups: "{{ group_list }}"
+
+ - name: Initialize groups_names
+ ansible.builtin.set_fact:
+ groups_names: []
+
+ - name: Create dict with group names
+ ansible.builtin.set_fact:
+ groups_names: "{{ groups_names | default([]) + [{'name': item.name}] }}"
+ loop: "{{ group_list }}"
+
+ - name: Remove groups
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups: "{{ groups_names }}"
+ state: absent
+
+- name: Remove groups.json
+ hosts: localhost
+ tasks:
+ - name: Remove groups.json
+ ansible.builtin.file:
+ state: absent
+ path: groups.json
diff --git a/tests/group/test_groups_present_slice.yml b/tests/group/test_groups_present_slice.yml
new file mode 100644
index 00000000..3a5593a2
--- /dev/null
+++ b/tests/group/test_groups_present_slice.yml
@@ -0,0 +1,47 @@
+---
+- name: Include create_groups_json.yml
+ ansible.builtin.import_playbook: create_groups_json.yml
+
+- name: Test groups present slice
+ hosts: ipaserver
+ gather_facts: false
+
+ vars:
+ slice_size: 500
+ tasks:
+ - name: Include groups.json
+ ansible.builtin.include_vars:
+ file: groups.json # noqa 505
+
+ - name: Size of groups slice.
+ ansible.builtin.debug:
+ msg: "{{ group_list | length }}"
+
+ - name: Groups present
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups: "{{ group_list[item : item + slice_size] }}"
+ loop: "{{ range(0, group_list | length, slice_size) | list }}"
+
+ - name: Initialize groups_names
+ ansible.builtin.set_fact:
+ groups_names: []
+
+ - name: Create dict with group names
+ ansible.builtin.set_fact:
+ groups_names: "{{ groups_names | default([]) + [{'name': item.name}] }}"
+ loop: "{{ group_list }}"
+
+ - name: Remove groups
+ ipagroup:
+ ipaadmin_password: SomeADMINpassword
+ groups: "{{ groups_names }}"
+ state: absent
+
+- name: Remove groups.json
+ hosts: localhost
+ tasks:
+ - name: Remove groups.json
+ ansible.builtin.file:
+ state: absent
+ path: groups.json
diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt
index 364d9249..e639f478 100644
--- a/tests/sanity/ignore-2.12.txt
+++ b/tests/sanity/ignore-2.12.txt
@@ -39,6 +39,7 @@ tests/pytests/conftest.py pylint:ansible-format-automatic-specification
tests/sanity/sanity.sh shebang!skip
tests/user/users.sh shebang!skip
tests/user/users_absent.sh shebang!skip
+tests/group/groups.sh shebang!skip
tests/utils.py pylint:ansible-format-automatic-specification
utils/ansible-doc-test shebang!skip
utils/build-galaxy-release.sh shebang!skip
diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt
index 0709427e..d5525e7b 100644
--- a/tests/sanity/ignore-2.13.txt
+++ b/tests/sanity/ignore-2.13.txt
@@ -21,6 +21,7 @@ tests/pytests/conftest.py pylint:ansible-format-automatic-specification
tests/sanity/sanity.sh shebang!skip
tests/user/users.sh shebang!skip
tests/user/users_absent.sh shebang!skip
+tests/group/groups.sh shebang!skip
tests/utils.py pylint:ansible-format-automatic-specification
utils/ansible-doc-test shebang!skip
utils/build-galaxy-release.sh shebang!skip
diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt
index 0709427e..d5525e7b 100644
--- a/tests/sanity/ignore-2.14.txt
+++ b/tests/sanity/ignore-2.14.txt
@@ -21,6 +21,7 @@ tests/pytests/conftest.py pylint:ansible-format-automatic-specification
tests/sanity/sanity.sh shebang!skip
tests/user/users.sh shebang!skip
tests/user/users_absent.sh shebang!skip
+tests/group/groups.sh shebang!skip
tests/utils.py pylint:ansible-format-automatic-specification
utils/ansible-doc-test shebang!skip
utils/build-galaxy-release.sh shebang!skip