Add flags to create local users and groups

This commit is contained in:
saville 2023-06-07 14:01:06 -06:00 committed by Megan Wilhite
parent 05d3295eba
commit ced3436053
8 changed files with 340 additions and 75 deletions

1
changelog/64256.added.md Normal file
View file

@ -0,0 +1 @@
Added flags to create local users and groups

View file

@ -48,11 +48,11 @@ def _which(cmd):
"""
_cmd = salt.utils.path.which(cmd)
if not _cmd:
raise CommandExecutionError("Command '{}' cannot be found".format(cmd))
raise CommandExecutionError(f"Command '{cmd}' cannot be found")
return _cmd
def add(name, gid=None, system=False, root=None, non_unique=False):
def add(name, gid=None, system=False, root=None, non_unique=False, local=False):
"""
.. versionchanged:: 3006.0
@ -75,21 +75,26 @@ def add(name, gid=None, system=False, root=None, non_unique=False):
.. versionadded:: 3006.0
local
Specifically add the group locally rather than through remote providers (e.g. LDAP)
.. versionadded:: 3007.0
CLI Example:
.. code-block:: bash
salt '*' group.add foo 3456
"""
cmd = [_which("groupadd")]
cmd = [_which("lgroupadd" if local else "groupadd")]
if gid:
cmd.append("-g {}".format(gid))
if non_unique:
cmd.append(f"-g {gid}")
if non_unique and not local:
cmd.append("-o")
if system and __grains__["kernel"] != "OpenBSD":
cmd.append("-r")
if root is not None:
if root is not None and not local:
cmd.extend(("-R", root))
cmd.append(name)
@ -99,7 +104,7 @@ def add(name, gid=None, system=False, root=None, non_unique=False):
return not ret["retcode"]
def delete(name, root=None):
def delete(name, root=None, local=False):
"""
Remove the named group
@ -109,15 +114,21 @@ def delete(name, root=None):
root
Directory to chroot into
local (Only on systems with lgroupdel available):
Ensure the group account is removed locally ignoring global
account management (default is False).
.. versionadded:: 3007.0
CLI Example:
.. code-block:: bash
salt '*' group.delete foo
"""
cmd = [_which("groupdel")]
cmd = [_which("lgroupdel" if local else "groupdel")]
if root is not None:
if root is not None and not local:
cmd.extend(("-R", root))
cmd.append(name)
@ -349,11 +360,11 @@ def deluser(name, username, root=None):
retcode = __salt__["cmd.retcode"](cmd, python_shell=False)
elif __grains__["kernel"] == "OpenBSD":
out = __salt__["cmd.run_stdout"](
"id -Gn {}".format(username), python_shell=False
f"id -Gn {username}", python_shell=False
)
cmd = [_which("usermod"), "-S"]
cmd.append(",".join([g for g in out.split() if g != str(name)]))
cmd.append("{}".format(username))
cmd.append(f"{username}")
retcode = __salt__["cmd.retcode"](cmd, python_shell=False)
else:
log.error("group.deluser is not yet supported on this platform")
@ -459,7 +470,7 @@ def _getgrnam(name, root=None):
comps[2] = int(comps[2])
comps[3] = comps[3].split(",") if comps[3] else []
return grp.struct_group(comps)
raise KeyError("getgrnam(): name not found: {}".format(name))
raise KeyError(f"getgrnam(): name not found: {name}")
def _getgrall(root=None):

View file

@ -106,7 +106,7 @@ def _which(cmd):
"""
_cmd = salt.utils.path.which(cmd)
if not _cmd:
raise CommandExecutionError("Command '{}' cannot be found".format(cmd))
raise CommandExecutionError(f"Command '{cmd}' cannot be found")
return _cmd
@ -157,6 +157,7 @@ def add(
nologinit=False,
root=None,
usergroup=None,
local=False,
):
"""
Add a user to the minion
@ -215,13 +216,18 @@ def add(
usergroup
Create and add the user to a new primary group of the same name
local (Only on systems with luseradd available)
Specifically add the user locally rather than possibly through remote providers (e.g. LDAP)
.. versionadded:: 3007.0
CLI Example:
.. code-block:: bash
salt '*' user.add name <uid> <gid> <groups> <home> <shell>
"""
cmd = [_which("useradd")]
cmd = [_which("luseradd" if local else "useradd")]
if shell:
cmd.extend(["-s", shell])
@ -230,9 +236,10 @@ def add(
if gid not in (None, ""):
cmd.extend(["-g", gid])
elif usergroup:
cmd.append("-U")
if __grains__["kernel"] != "Linux":
log.warning("'usergroup' is only supported on GNU/Linux hosts.")
if not local:
cmd.append("-U")
if __grains__["kernel"] != "Linux":
log.warning("'usergroup' is only supported on GNU/Linux hosts.")
elif groups is not None and name in groups:
defs_file = "/etc/login.defs"
if __grains__["kernel"] != "OpenBSD":
@ -269,14 +276,15 @@ def add(
# /etc/usermgmt.conf not present: defaults will be used
pass
# Setting usergroup to False adds the -N command argument. If
# Setting usergroup to False adds a command argument. If
# usergroup is None, no arguments are added to allow useradd to go
# with the defaults defined for the OS.
if usergroup is False:
cmd.append("-N")
cmd.append("-n" if local else "-N")
if createhome:
cmd.append("-m")
if not local:
cmd.append("-m")
elif __grains__["kernel"] != "NetBSD" and __grains__["kernel"] != "OpenBSD":
cmd.append("-M")
@ -302,7 +310,7 @@ def add(
cmd.append(name)
if root is not None and __grains__["kernel"] != "AIX":
if root is not None and not local and __grains__["kernel"] != "AIX":
cmd.extend(("-R", root))
ret = __salt__["cmd.run_all"](cmd, python_shell=False)
@ -333,7 +341,7 @@ def add(
return True
def delete(name, remove=False, force=False, root=None):
def delete(name, remove=False, force=False, root=None, local=False):
"""
Remove a user from the minion
@ -349,23 +357,34 @@ def delete(name, remove=False, force=False, root=None):
root
Directory to chroot into
local (Only on systems with luserdel available):
Ensure the user account is removed locally ignoring global
account management (default is False).
.. versionadded:: 3007.0
CLI Example:
.. code-block:: bash
salt '*' user.delete name remove=True force=True
"""
cmd = [_which("userdel")]
cmd = [_which("luserdel" if local else "userdel")]
if remove:
cmd.append("-r")
if force and __grains__["kernel"] != "OpenBSD" and __grains__["kernel"] != "AIX":
if (
force
and __grains__["kernel"] != "OpenBSD"
and __grains__["kernel"] != "AIX"
and not local
):
cmd.append("-f")
cmd.append(name)
if root is not None and __grains__["kernel"] != "AIX":
if root is not None and __grains__["kernel"] != "AIX" and not local:
cmd.extend(("-R", root))
ret = __salt__["cmd.run_all"](cmd, python_shell=False)
@ -429,7 +448,7 @@ def _chattrib(name, key, value, param, persist=False, root=None):
"""
pre_info = info(name, root=root)
if not pre_info:
raise CommandExecutionError("User '{}' does not exist".format(name))
raise CommandExecutionError(f"User '{name}' does not exist")
if value == pre_info[key]:
return True
@ -911,7 +930,7 @@ def rename(name, new_name, root=None):
salt '*' user.rename name new_name
"""
if info(new_name, root=root):
raise CommandExecutionError("User '{}' already exists".format(new_name))
raise CommandExecutionError(f"User '{new_name}' already exists")
return _chattrib(name, "name", new_name, "-l", root=root)

View file

@ -40,12 +40,21 @@ import salt.utils.platform
import salt.utils.win_functions
def _changes(name, gid=None, addusers=None, delusers=None, members=None):
def _get_root_args(local):
"""
Retrieve args to use for group.info calls depending on platform and the local flag
"""
if not local or salt.utils.platform.is_windows():
return {}
return {"root": "/"}
def _changes(name, gid=None, addusers=None, delusers=None, members=None, local=False):
"""
Return a dict of the changes required for a group if the group is present,
otherwise return False.
"""
lgrp = __salt__["group.info"](name)
lgrp = __salt__["group.info"](name, **_get_root_args(local))
if not lgrp:
return False
@ -108,6 +117,7 @@ def present(
delusers=None,
members=None,
non_unique=False,
local=False,
):
r"""
.. versionchanged:: 3006.0
@ -146,6 +156,12 @@ def present(
.. versionadded:: 3006.0
local (Only on systems with lgroupadd available):
Create the group account locally ignoring global account management
(default is False).
.. versionadded:: 3007.0
Example:
.. code-block:: yaml
@ -173,7 +189,7 @@ def present(
"name": name,
"changes": {},
"result": True,
"comment": "Group {} is present and up to date".format(name),
"comment": f"Group {name} is present and up to date",
}
if members is not None and (addusers is not None or delusers is not None):
@ -193,11 +209,11 @@ def present(
] = "Error. Same user(s) can not be added and deleted simultaneously"
return ret
changes = _changes(name, gid, addusers, delusers, members)
changes = _changes(name, gid, addusers, delusers, members, local=local)
if changes:
ret["comment"] = "The following group attributes are set to be changed:\n"
for key, val in changes.items():
ret["comment"] += "{}: {}\n".format(key, val)
ret["comment"] += f"{key}: {val}\n"
if __opts__["test"]:
ret["result"] = None
@ -222,7 +238,7 @@ def present(
sys.modules[__salt__["test.ping"].__module__].__context__.pop(
"group.getent", None
)
changes = _changes(name, gid, addusers, delusers, members)
changes = _changes(name, gid, addusers, delusers, members, local=local)
if changes:
ret["result"] = False
ret["comment"] += "Some changes could not be applied"
@ -234,7 +250,7 @@ def present(
# The group is not present, make it!
if __opts__["test"]:
ret["result"] = None
ret["comment"] = "Group {} set to be added".format(name)
ret["comment"] = f"Group {name} set to be added"
return ret
grps = __salt__["group.getent"]()
@ -255,7 +271,17 @@ def present(
return ret
# Group is not present, make it.
if __salt__["group.add"](name, gid=gid, system=system, non_unique=non_unique):
if salt.utils.platform.is_windows():
add_args = {}
else:
add_args = {"local": local}
if __salt__["group.add"](
name,
gid=gid,
system=system,
non_unique=non_unique,
**add_args,
):
# if members to be added
grp_members = None
if members:
@ -268,9 +294,9 @@ def present(
sys.modules[__salt__["test.ping"].__module__].__context__.pop(
"group.getent", None
)
ret["comment"] = "New group {} created".format(name)
ret["changes"] = __salt__["group.info"](name)
changes = _changes(name, gid, addusers, delusers, members)
ret["comment"] = f"New group {name} created"
ret["changes"] = __salt__["group.info"](name, **_get_root_args(local))
changes = _changes(name, gid, addusers, delusers, members, local=local)
if changes:
ret["result"] = False
ret["comment"] = (
@ -280,11 +306,11 @@ def present(
ret["changes"] = {"Failed": changes}
else:
ret["result"] = False
ret["comment"] = "Failed to create new group {}".format(name)
ret["comment"] = f"Failed to create new group {name}"
return ret
def absent(name):
def absent(name, local=False):
"""
Ensure that the named group is absent
@ -292,6 +318,13 @@ def absent(name):
name (str):
The name of the group to remove
local (Only on systems with lgroupdel available):
Ensure the group account is removed locally ignoring global
account management (default is False).
.. versionadded:: 3007.0
Example:
.. code-block:: yaml
@ -301,20 +334,24 @@ def absent(name):
group.absent
"""
ret = {"name": name, "changes": {}, "result": True, "comment": ""}
grp_info = __salt__["group.info"](name)
grp_info = __salt__["group.info"](name, **_get_root_args(local))
if grp_info:
# Group already exists. Remove the group.
if __opts__["test"]:
ret["result"] = None
ret["comment"] = "Group {} is set for removal".format(name)
ret["comment"] = f"Group {name} is set for removal"
return ret
ret["result"] = __salt__["group.delete"](name)
if salt.utils.platform.is_windows():
del_args = {}
else:
del_args = {"local": local}
ret["result"] = __salt__["group.delete"](name, **del_args)
if ret["result"]:
ret["changes"] = {name: ""}
ret["comment"] = "Removed group {}".format(name)
ret["comment"] = f"Removed group {name}"
return ret
else:
ret["comment"] = "Failed to remove group {}".format(name)
ret["comment"] = f"Failed to remove group {name}"
return ret
else:
ret["comment"] = "Group not present"

View file

@ -47,6 +47,15 @@ def _group_changes(cur, wanted, remove=False):
return False
def _get_root_args(local):
"""
Retrieve args to use for user.info calls depending on platform and the local flag
"""
if not local or salt.utils.platform.is_windows():
return {}
return {"root": "/"}
def _changes(
name,
uid=None,
@ -79,6 +88,7 @@ def _changes(
allow_uid_change=False,
allow_gid_change=False,
password_lock=None,
local=False,
):
"""
Return a dict of the changes required for a user if the user is present,
@ -94,7 +104,7 @@ def _changes(
if "shadow.info" in __salt__:
lshad = __salt__["shadow.info"](name)
lusr = __salt__["user.info"](name)
lusr = __salt__["user.info"](name, **_get_root_args(local))
if not lusr:
return False
@ -274,6 +284,7 @@ def present(
allow_uid_change=False,
allow_gid_change=False,
password_lock=None,
local=False,
):
"""
Ensure that the named user is present with the specified properties
@ -449,6 +460,12 @@ def present(
Date that account expires, represented in days since epoch (January 1,
1970).
local (Only on systems with luseradd available):
Create the user account locally ignoring global account management
(default is False).
.. versionadded:: 3007.0
The below parameters apply to windows only:
win_homedrive (Windows Only)
@ -530,14 +547,14 @@ def present(
"name": name,
"changes": {},
"result": True,
"comment": "User {} is present and up to date".format(name),
"comment": f"User {name} is present and up to date",
}
# the comma is used to separate field in GECOS, thus resulting into
# salt adding the end of fullname each time this function is called
for gecos_field in [fullname, roomnumber, workphone]:
if isinstance(gecos_field, str) and "," in gecos_field:
ret["comment"] = "Unsupported char ',' in {}".format(gecos_field)
ret["comment"] = f"Unsupported char ',' in {gecos_field}"
ret["result"] = False
return ret
@ -614,6 +631,7 @@ def present(
allow_uid_change,
allow_gid_change,
password_lock=password_lock,
local=local,
)
except CommandExecutionError as exc:
ret["result"] = False
@ -629,14 +647,14 @@ def present(
val = "XXX-REDACTED-XXX"
elif key == "group" and not remove_groups:
key = "ensure groups"
ret["comment"] += "{}: {}\n".format(key, val)
ret["comment"] += f"{key}: {val}\n"
return ret
# The user is present
if "shadow.info" in __salt__:
lshad = __salt__["shadow.info"](name)
if __grains__["kernel"] in ("OpenBSD", "FreeBSD"):
lcpre = __salt__["user.get_loginclass"](name)
pre = __salt__["user.info"](name)
pre = __salt__["user.info"](name, **_get_root_args(local))
# Make changes
@ -727,11 +745,9 @@ def present(
# NOTE: list(changes) required here to avoid modifying dictionary
# during iteration.
for key in [
x
for x in list(changes)
if x != "groups" and "user.ch{}".format(x) in __salt__
x for x in list(changes) if x != "groups" and f"user.ch{x}" in __salt__
]:
__salt__["user.ch{}".format(key)](name, changes.pop(key))
__salt__[f"user.ch{key}"](name, changes.pop(key))
# Do group changes last
if "groups" in changes:
@ -742,7 +758,7 @@ def present(
"Unhandled changes: {}".format(", ".join(changes))
)
post = __salt__["user.info"](name)
post = __salt__["user.info"](name, **_get_root_args(local))
spost = {}
if "shadow.info" in __salt__ and lshad["passwd"] != password:
spost = __salt__["shadow.info"](name)
@ -762,7 +778,7 @@ def present(
if __grains__["kernel"] in ("OpenBSD", "FreeBSD") and lcpost != lcpre:
ret["changes"]["loginclass"] = lcpost
if ret["changes"]:
ret["comment"] = "Updated user {}".format(name)
ret["comment"] = f"Updated user {name}"
changes = _changes(
name,
uid,
@ -795,6 +811,7 @@ def present(
allow_uid_change=True,
allow_gid_change=True,
password_lock=password_lock,
local=local,
)
# allow_uid_change and allow_gid_change passed as True to avoid race
# conditions where a uid/gid is modified outside of Salt. If an
@ -802,7 +819,7 @@ def present(
# first time we ran _changes().
if changes:
ret["comment"] = "These values could not be changed: {}".format(changes)
ret["comment"] = f"These values could not be changed: {changes}"
ret["result"] = False
return ret
@ -810,7 +827,7 @@ def present(
# The user is not present, make it!
if __opts__["test"]:
ret["result"] = None
ret["comment"] = "User {} set to be added".format(name)
ret["comment"] = f"User {name} set to be added"
return ret
if groups and present_optgroups:
groups.extend(present_optgroups)
@ -837,6 +854,7 @@ def present(
"createhome": createhome,
"nologinit": nologinit,
"loginclass": loginclass,
"local": local,
"usergroup": usergroup,
}
else:
@ -853,8 +871,8 @@ def present(
}
result = __salt__["user.add"](**params)
if result is True:
ret["comment"] = "New user {} created".format(name)
ret["changes"] = __salt__["user.info"](name)
ret["comment"] = f"New user {name} created"
ret["changes"] = __salt__["user.info"](name, **_get_root_args(local))
if not createhome:
# pwd incorrectly reports presence of home
ret["changes"]["home"] = ""
@ -880,7 +898,7 @@ def present(
if spost["passwd"] != "":
ret[
"comment"
] = "User {} created but failed to empty password".format(name)
] = f"User {name} created but failed to empty password"
ret["result"] = False
ret["changes"]["password"] = ""
if date is not None:
@ -987,12 +1005,12 @@ def present(
if isinstance(result, str):
ret["comment"] = result
else:
ret["comment"] = "Failed to create new user {}".format(name)
ret["comment"] = f"Failed to create new user {name}"
ret["result"] = False
return ret
def absent(name, purge=False, force=False):
def absent(name, purge=False, force=False, local=False):
"""
Ensure that the named user is absent
@ -1007,30 +1025,40 @@ def absent(name, purge=False, force=False):
If the user is logged in, the absent state will fail. Set the force
option to True to remove the user even if they are logged in. Not
supported in FreeBSD and Solaris, Default is ``False``.
local (Only on systems with luserdel available):
Ensure the user account is removed locally ignoring global account management
(default is False).
.. versionadded:: 3007.0
"""
ret = {"name": name, "changes": {}, "result": True, "comment": ""}
lusr = __salt__["user.info"](name)
lusr = __salt__["user.info"](name, **_get_root_args(local))
if lusr:
# The user is present, make it not present
if __opts__["test"]:
ret["result"] = None
ret["comment"] = "User {} set for removal".format(name)
ret["comment"] = f"User {name} set for removal"
return ret
beforegroups = set(salt.utils.user.get_group_list(name))
ret["result"] = __salt__["user.delete"](name, purge, force)
if salt.utils.platform.is_windows():
del_args = {}
else:
del_args = {"local": local}
ret["result"] = __salt__["user.delete"](name, purge, force, **del_args)
aftergroups = {g for g in beforegroups if __salt__["group.info"](g)}
if ret["result"]:
ret["changes"] = {}
for g in beforegroups - aftergroups:
ret["changes"]["{} group".format(g)] = "removed"
ret["changes"][f"{g} group"] = "removed"
ret["changes"][name] = "removed"
ret["comment"] = "Removed user {}".format(name)
ret["comment"] = f"Removed user {name}"
else:
ret["result"] = False
ret["comment"] = "Failed to remove user {}".format(name)
ret["comment"] = f"Failed to remove user {name}"
return ret
ret["comment"] = "User {} is not present".format(name)
ret["comment"] = f"User {name} is not present"
return ret

View file

@ -27,9 +27,10 @@ def test_add():
with patch(
"salt.utils.path.which",
MagicMock(side_effect=[None, "/bin/groupadd", "/bin/groupadd"]),
):
) as which_mock:
with pytest.raises(CommandExecutionError):
groupadd.add("test", 100)
which_mock.assert_called_once_with("groupadd")
mock = MagicMock(return_value={"retcode": 0})
with patch.dict(groupadd.__salt__, {"cmd.run_all": mock}):
@ -40,6 +41,42 @@ def test_add():
assert groupadd.add("test", 100, True) is True
def test_add_local():
"""
Tests if specified group was added with local flag
"""
with patch(
"salt.utils.path.which",
MagicMock(return_value="/bin/lgroupadd"),
) as which_mock:
mock = MagicMock(return_value={"retcode": 0})
with patch.dict(groupadd.__salt__, {"cmd.run_all": mock}):
assert groupadd.add("test", 100, local=True) is True
which_mock.assert_called_once_with("lgroupadd")
mock.assert_called_once_with(
["/bin/lgroupadd", "-g 100", "test"], python_shell=False
)
def test_add_local_with_params():
"""
Tests if specified group was added with local flag and extra parameters
"""
with patch(
"salt.utils.path.which",
MagicMock(return_value="/bin/lgroupadd"),
):
mock = MagicMock(return_value={"retcode": 0})
with patch.dict(groupadd.__salt__, {"cmd.run_all": mock}):
assert (
groupadd.add("test", 100, local=True, non_unique=True, root="ignored")
is True
)
mock.assert_called_once_with(
["/bin/lgroupadd", "-g 100", "test"], python_shell=False
)
def test_info():
"""
Tests the return of group information
@ -104,15 +141,45 @@ def test_delete():
with patch(
"salt.utils.path.which",
MagicMock(side_effect=[None, "/bin/groupdel"]),
):
) as which_mock:
with pytest.raises(CommandExecutionError):
groupadd.delete("test")
which_mock.assert_called_once_with("groupdel")
mock_ret = MagicMock(return_value={"retcode": 0})
with patch.dict(groupadd.__salt__, {"cmd.run_all": mock_ret}):
assert groupadd.delete("test") is True
def test_delete_local():
"""
Tests if the specified group was deleted with a local flag
"""
with patch(
"salt.utils.path.which",
MagicMock(return_value="/bin/lgroupdel"),
) as which_mock:
mock = MagicMock(return_value={"retcode": 0})
with patch.dict(groupadd.__salt__, {"cmd.run_all": mock}):
assert groupadd.delete("test", local=True) is True
which_mock.assert_called_once_with("lgroupdel")
mock.assert_called_once_with(["/bin/lgroupdel", "test"], python_shell=False)
def test_delete_local_with_params():
"""
Tests if the specified group was deleted with a local flag and params
"""
with patch(
"salt.utils.path.which",
MagicMock(return_value="/bin/lgroupdel"),
):
mock = MagicMock(return_value={"retcode": 0})
with patch.dict(groupadd.__salt__, {"cmd.run_all": mock}):
assert groupadd.delete("test", local=True, root="ignored") is True
mock.assert_called_once_with(["/bin/lgroupdel", "test"], python_shell=False)
def test_adduser():
"""
Tests if specified user gets added in the group.

View file

@ -26,8 +26,9 @@ def test_add():
mock = MagicMock(return_value={"retcode": 0})
with patch(
"salt.utils.path.which", MagicMock(return_value="/sbin/useradd")
), patch.dict(useradd.__salt__, {"cmd.run_all": mock}):
) as which_mock, patch.dict(useradd.__salt__, {"cmd.run_all": mock}):
assert useradd.add("Salt") is True
which_mock.assert_called_once_with("useradd")
mock.assert_called_once_with(["/sbin/useradd", "-m", "Salt"], python_shell=False)
# command found and unsuccessful run
@ -48,13 +49,33 @@ def test_add():
mock.assert_not_called()
def test_add_local():
mock = MagicMock(return_value={"retcode": 0})
with patch(
"salt.utils.path.which", MagicMock(return_value="/sbin/luseradd")
) as which_mock, patch.dict(useradd.__salt__, {"cmd.run_all": mock}):
assert useradd.add("Salt", local=True) is True
which_mock.assert_called_once_with("luseradd")
mock.assert_called_once_with(["/sbin/luseradd", "Salt"], python_shell=False)
def test_add_local_with_params():
mock = MagicMock(return_value={"retcode": 0})
with patch(
"salt.utils.path.which", MagicMock(return_value="/sbin/luseradd")
), patch.dict(useradd.__salt__, {"cmd.run_all": mock}):
assert useradd.add("Salt", local=True, usergroup=False, root="ignored") is True
mock.assert_called_once_with(["/sbin/luseradd", "-n", "Salt"], python_shell=False)
def test_delete():
# command found and successful run
mock = MagicMock(return_value={"retcode": 0})
with patch(
"salt.utils.path.which", MagicMock(return_value="/sbin/userdel")
), patch.dict(useradd.__salt__, {"cmd.run_all": mock}):
) as which_mock, patch.dict(useradd.__salt__, {"cmd.run_all": mock}):
assert useradd.delete("Salt") is True
which_mock.assert_called_once_with("userdel")
mock.assert_called_once_with(["/sbin/userdel", "Salt"], python_shell=False)
# command found and unsuccessful run
@ -75,6 +96,28 @@ def test_delete():
mock.assert_not_called()
def test_delete_local():
# command found and successful run
mock = MagicMock(return_value={"retcode": 0})
with patch(
"salt.utils.path.which", MagicMock(return_value="/sbin/luserdel")
) as which_mock, patch.dict(useradd.__salt__, {"cmd.run_all": mock}):
assert useradd.delete("Salt", local=True) is True
which_mock.assert_called_once_with("luserdel")
mock.assert_called_once_with(["/sbin/luserdel", "Salt"], python_shell=False)
def test_delete_local_with_params():
# command found and successful run
mock = MagicMock(return_value={"retcode": 0})
with patch(
"salt.utils.path.which", MagicMock(return_value="/sbin/luserdel")
) as which_mock, patch.dict(useradd.__salt__, {"cmd.run_all": mock}):
assert useradd.delete("Salt", local=True, force=True, root="ignored") is True
which_mock.assert_called_once_with("luserdel")
mock.assert_called_once_with(["/sbin/luserdel", "Salt"], python_shell=False)
def test_chgroups():
# groups matched - no command run
mock = MagicMock()

View file

@ -1,7 +1,8 @@
import pytest
import salt.states.group as group
from tests.support.mock import MagicMock, patch
import salt.utils.platform
from tests.support.mock import MagicMock, call, patch
__context__ = {}
@ -42,6 +43,43 @@ def test_present_with_non_unique_gid():
}
def test_present_with_local():
group_add_mock = MagicMock(return_value=True)
group_info_mock = MagicMock(return_value={"things": "stuff"})
with patch(
"salt.states.group._changes", MagicMock(side_effect=[False, {}, False])
) as changes_mock, patch.dict(
group.__salt__,
{
"group.getent": MagicMock(
side_effect=[[{"name": "salt", "gid": 1}], [{"name": "salt", "gid": 1}]]
)
},
), patch.dict(
group.__salt__, {"group.add": group_add_mock}
), patch.dict(
group.__salt__, {"group.chgid": MagicMock(return_value=True)}
), patch.dict(
group.__salt__, {"group.info": group_info_mock}
):
ret = group.present("salt", gid=1, non_unique=True, local=True)
assert ret["result"]
assert changes_mock.call_args_list == [
call("salt", 1, None, None, None, local=True),
call("salt", 1, None, None, None, local=True),
]
if salt.utils.platform.is_windows():
group_info_mock.assert_called_once_with("salt")
group_add_mock.assert_called_once_with(
"salt", gid=1, system=False, non_unique=True
)
else:
group_info_mock.assert_called_once_with("salt", root="/")
group_add_mock.assert_called_once_with(
"salt", gid=1, system=False, local=True, non_unique=True
)
def test_present_with_existing_group_and_non_unique_gid():
with patch(
"salt.states.group._changes",
@ -76,3 +114,24 @@ def test_present_with_existing_group_and_non_unique_gid():
"name": "salt",
"result": False,
}
def test_absent_with_local():
group_delete_mock = MagicMock(return_value=True)
group_info_mock = MagicMock(return_value={"things": "stuff"})
with patch.dict(group.__salt__, {"group.delete": group_delete_mock}), patch.dict(
group.__salt__, {"group.info": group_info_mock}
):
ret = group.absent("salt", local=True)
assert ret == {
"changes": {"salt": ""},
"comment": "Removed group salt",
"name": "salt",
"result": True,
}
if salt.utils.platform.is_windows():
group_info_mock.assert_called_once_with("salt")
group_delete_mock.assert_called_once_with("salt")
else:
group_info_mock.assert_called_once_with("salt", root="/")
group_delete_mock.assert_called_once_with("salt", local=True)