mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
feat(macdefaults): Improve macOS defaults support
This commit is contained in:
parent
22611256aa
commit
19cbf09521
4 changed files with 608 additions and 77 deletions
|
@ -1,11 +1,21 @@
|
|||
"""
|
||||
Set defaults on Mac OS
|
||||
Set defaults on macOS.
|
||||
|
||||
This module uses defaults cli under the hood to read and write defaults on macOS.
|
||||
|
||||
So the module is limited to the capabilities of the defaults command.
|
||||
|
||||
Read macOS defaults help page for more information on the defaults command.
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
import salt.utils.data
|
||||
import salt.utils.platform
|
||||
import salt.utils.versions
|
||||
from salt.exceptions import CommandExecutionError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
__virtualname__ = "macdefaults"
|
||||
|
@ -13,16 +23,21 @@ __virtualname__ = "macdefaults"
|
|||
|
||||
def __virtual__():
|
||||
"""
|
||||
Only work on Mac OS
|
||||
Only works on macOS
|
||||
|
||||
"""
|
||||
if salt.utils.platform.is_darwin():
|
||||
return __virtualname__
|
||||
return False
|
||||
|
||||
|
||||
def write(domain, key, value, type="string", user=None):
|
||||
def write(domain, key, value, vtype=None, user=None, type=None):
|
||||
"""
|
||||
Write a default to the system
|
||||
Write a default to the system.
|
||||
|
||||
Limitations:
|
||||
- There is no multi-level support for arrays and dictionaries.
|
||||
- Internal values types for arrays and dictionaries cannot be specified.
|
||||
|
||||
CLI Example:
|
||||
|
||||
|
@ -33,31 +48,58 @@ def write(domain, key, value, type="string", user=None):
|
|||
salt '*' macdefaults.write NSGlobalDomain ApplePersistence True type=bool
|
||||
|
||||
domain
|
||||
The name of the domain to write to
|
||||
The name of the domain to write to.
|
||||
|
||||
key
|
||||
The key of the given domain to write to
|
||||
The key of the given domain to write to.
|
||||
|
||||
value
|
||||
The value to write to the given key
|
||||
The value to write to the given key.
|
||||
|
||||
type
|
||||
vtype
|
||||
The type of value to be written, valid types are string, data, int[eger],
|
||||
float, bool[ean], date, array, array-add, dict, dict-add
|
||||
|
||||
user
|
||||
The user to write the defaults to
|
||||
type
|
||||
Deprecated! Use vtype instead.
|
||||
type collides with Python's built-in type() function.
|
||||
This parameter will be removed in 3009.
|
||||
|
||||
user
|
||||
The user to write the defaults to.
|
||||
|
||||
"""
|
||||
if type == "bool" or type == "boolean":
|
||||
if value is True:
|
||||
value = "TRUE"
|
||||
elif value is False:
|
||||
value = "FALSE"
|
||||
if type is not None:
|
||||
salt.utils.versions.warn_until(
|
||||
3009,
|
||||
"The 'type' argument in macdefaults.write is deprecated. Use 'vtype' instead.",
|
||||
)
|
||||
if vtype is None:
|
||||
vtype = type
|
||||
else:
|
||||
log.warning(
|
||||
"The 'vtype' argument in macdefaults.write takes precedence over 'type'."
|
||||
)
|
||||
|
||||
cmd = f'defaults write "{domain}" "{key}" -{type} "{value}"'
|
||||
return __salt__["cmd.run_all"](cmd, runas=user)
|
||||
if vtype is None:
|
||||
vtype = "string"
|
||||
|
||||
if vtype in ("bool", "boolean"):
|
||||
value = _convert_to_defaults_boolean(value)
|
||||
|
||||
if isinstance(value, dict):
|
||||
value = list((k, v) for k, v in value.items())
|
||||
value = salt.utils.data.flatten(value)
|
||||
elif isinstance(value, (int, float, bool, str)):
|
||||
value = [value]
|
||||
elif not isinstance(value, list):
|
||||
raise ValueError("Value must be a list, dict, int, float, bool, or string")
|
||||
|
||||
# Quote values that are not integers or floats
|
||||
value = map(lambda v: str(v) if isinstance(v, (int, float)) else f'"{v}"', value)
|
||||
|
||||
cmd = f'write "{domain}" "{key}" -{vtype} {" ".join(value)}'
|
||||
return _run_defaults_cmd(cmd, runas=user)
|
||||
|
||||
|
||||
def read(domain, key, user=None):
|
||||
|
@ -82,8 +124,21 @@ def read(domain, key, user=None):
|
|||
The user to read the defaults as
|
||||
|
||||
"""
|
||||
cmd = f'defaults read "{domain}" "{key}"'
|
||||
return __salt__["cmd.run"](cmd, runas=user)
|
||||
cmd = f'read "{domain}" "{key}"'
|
||||
ret = _run_defaults_cmd(cmd, runas=user)
|
||||
|
||||
if ret["retcode"] != 0:
|
||||
if "does not exist" in ret["stderr"]:
|
||||
return None
|
||||
raise CommandExecutionError(f"Failed to read default: {ret['stderr']}")
|
||||
|
||||
# Type cast the value
|
||||
try:
|
||||
vtype = read_type(domain, key, user)
|
||||
except CommandExecutionError:
|
||||
vtype = None
|
||||
|
||||
return _default_to_python(ret["stdout"].strip(), vtype)
|
||||
|
||||
|
||||
def delete(domain, key, user=None):
|
||||
|
@ -108,5 +163,206 @@ def delete(domain, key, user=None):
|
|||
The user to delete the defaults with
|
||||
|
||||
"""
|
||||
cmd = f'defaults delete "{domain}" "{key}"'
|
||||
return __salt__["cmd.run_all"](cmd, runas=user, output_loglevel="debug")
|
||||
cmd = f'delete "{domain}" "{key}"'
|
||||
return _run_defaults_cmd(cmd, runas=user)
|
||||
|
||||
|
||||
def read_type(domain, key, user=None):
|
||||
"""
|
||||
Read the type of the given type.
|
||||
If the given key is not found, then return None.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' macdefaults.read-type com.apple.CrashReporter DialogType
|
||||
|
||||
salt '*' macdefaults.read_type NSGlobalDomain ApplePersistence
|
||||
|
||||
domain
|
||||
The name of the domain to read from.
|
||||
|
||||
key
|
||||
The key of the given domain to read the type of.
|
||||
|
||||
user
|
||||
The user to read the defaults as.
|
||||
|
||||
"""
|
||||
cmd = f'read-type "{domain}" "{key}"'
|
||||
ret = _run_defaults_cmd(cmd, runas=user)
|
||||
|
||||
if ret["retcode"] != 0:
|
||||
if "does not exist" in ret["stderr"]:
|
||||
return None
|
||||
raise CommandExecutionError(f"Failed to read type: {ret['stderr']}")
|
||||
|
||||
return re.sub(r"^Type is ", "", ret["stdout"].strip())
|
||||
|
||||
|
||||
def _default_to_python(value, vtype=None):
|
||||
"""
|
||||
Cast a value returned by defaults in vytpe to Python type.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' macdefaults.cast_value_to_type "1" int
|
||||
|
||||
salt '*' macdefaults.cast_value_to_type "1.0" float
|
||||
|
||||
salt '*' macdefaults.cast_value_to_type "TRUE" bool
|
||||
|
||||
value
|
||||
The value to cast.
|
||||
|
||||
vtype
|
||||
The type to cast the value to.
|
||||
|
||||
"""
|
||||
if vtype in ["integer", "int"]:
|
||||
return int(value)
|
||||
if vtype == "float":
|
||||
return float(value)
|
||||
if vtype in ["boolean", "bool"]:
|
||||
return value in ["1", "TRUE", "YES"]
|
||||
if vtype == "array":
|
||||
return _parse_defaults_array(value)
|
||||
if vtype in ["dict", "dictionary"]:
|
||||
return _parse_defaults_dict(value)
|
||||
return value
|
||||
|
||||
|
||||
def _parse_defaults_array(value):
|
||||
"""
|
||||
Parse an array from a string returned by `defaults read`
|
||||
and returns the array content as a list.
|
||||
|
||||
value
|
||||
A multiline string with the array content, including the surrounding parenthesis.
|
||||
|
||||
"""
|
||||
lines = value.splitlines()
|
||||
if not re.match(r"\s*\(", lines[0]) or not re.match(r"\s*\)", lines[-1]):
|
||||
raise ValueError("Invalid array format")
|
||||
|
||||
lines = lines[1:-1]
|
||||
|
||||
# Remove leading and trailing spaces
|
||||
lines = list(map(lambda line: line.strip(), lines))
|
||||
|
||||
# Remove trailing commas
|
||||
lines = list(map(lambda line: re.sub(r",?$", "", line), lines))
|
||||
|
||||
# Remove quotes
|
||||
lines = list(map(lambda line: line.strip('"'), lines))
|
||||
|
||||
# Convert to numbers if possible
|
||||
lines = list(map(_convert_to_number_if_possible, lines))
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def _parse_defaults_dict(value):
|
||||
"""
|
||||
Parse a dictionary from a string returned by `defaults read`
|
||||
|
||||
value (str):
|
||||
A multiline string with the dictionary content, including the surrounding curly braces.
|
||||
|
||||
Returns:
|
||||
dict: The dictionary content as a Python dictionary.
|
||||
"""
|
||||
lines = value.splitlines()
|
||||
if not re.match(r"\s*\{", lines[0]) or not re.match(r"\s*\}", lines[-1]):
|
||||
raise ValueError("Invalid dictionary format")
|
||||
|
||||
contents = {}
|
||||
lines = list(map(lambda line: line.strip(), lines[1:-1]))
|
||||
for line in lines:
|
||||
key, value = re.split(r"\s*=\s*", line.strip())
|
||||
if re.match(r"\s*(\(|\{)", value):
|
||||
raise ValueError("Nested arrays and dictionaries are not supported")
|
||||
|
||||
value = re.sub(r";?$", "", value)
|
||||
contents[key] = _convert_to_number_if_possible(value.strip('"'))
|
||||
|
||||
return contents
|
||||
|
||||
|
||||
def _convert_to_number_if_possible(value):
|
||||
"""
|
||||
Convert a string to a number if possible.
|
||||
|
||||
value
|
||||
The string to convert.
|
||||
|
||||
"""
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
return value
|
||||
|
||||
|
||||
def _convert_to_defaults_boolean(value):
|
||||
"""
|
||||
Convert a boolean to a string that can be used with the defaults command.
|
||||
|
||||
value
|
||||
The boolean value to convert.
|
||||
|
||||
"""
|
||||
if value is True:
|
||||
return "TRUE"
|
||||
if value is False:
|
||||
return "FALSE"
|
||||
|
||||
BOOLEAN_ALLOWED_VALUES = ["TRUE", "YES", "FALSE", "NO"]
|
||||
if value not in BOOLEAN_ALLOWED_VALUES:
|
||||
msg = "Value must be a boolean or a string of "
|
||||
msg += ", ".join(BOOLEAN_ALLOWED_VALUES)
|
||||
raise ValueError(msg)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def _run_defaults_cmd(action, runas=None):
|
||||
"""
|
||||
Run a 'defaults' command.
|
||||
|
||||
action
|
||||
The action to perform with all of its parameters.
|
||||
Example: 'write com.apple.CrashReporter DialogType "Server"'
|
||||
|
||||
runas
|
||||
The user to run the command as.
|
||||
|
||||
"""
|
||||
ret = __salt__["cmd.run_all"](f"defaults {action}", runas=runas)
|
||||
|
||||
# Remove timestamp from stderr if found
|
||||
if ret["retcode"] != 0:
|
||||
ret["stderr"] = _remove_timestamp(ret["stderr"])
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _remove_timestamp(text):
|
||||
"""
|
||||
Remove the timestamp from the output of the defaults command.
|
||||
|
||||
text
|
||||
The text to remove the timestamp from.
|
||||
|
||||
"""
|
||||
pattern = r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{3})?\s+defaults\[\d+\:\d+\]"
|
||||
if re.match(pattern, text):
|
||||
text_lines = text.strip().splitlines()
|
||||
return "\n".join(text_lines[1:])
|
||||
|
||||
return text
|
||||
|
|
|
@ -5,6 +5,7 @@ Writing/reading defaults from a macOS minion
|
|||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
import salt.utils.platform
|
||||
|
||||
|
@ -15,11 +16,11 @@ __virtualname__ = "macdefaults"
|
|||
|
||||
def __virtual__():
|
||||
"""
|
||||
Only work on Mac OS
|
||||
Only work on macOS
|
||||
"""
|
||||
if salt.utils.platform.is_darwin():
|
||||
return __virtualname__
|
||||
return (False, "Only supported on Mac OS")
|
||||
return (False, "Only supported on macOS")
|
||||
|
||||
|
||||
def write(name, domain, value, vtype="string", user=None):
|
||||
|
@ -46,30 +47,16 @@ def write(name, domain, value, vtype="string", user=None):
|
|||
"""
|
||||
ret = {"name": name, "result": True, "comment": "", "changes": {}}
|
||||
|
||||
def safe_cast(val, to_type, default=None):
|
||||
try:
|
||||
return to_type(val)
|
||||
except ValueError:
|
||||
return default
|
||||
|
||||
current_value = __salt__["macdefaults.read"](domain, name, user)
|
||||
value = _cast_value(value, vtype)
|
||||
|
||||
if (vtype in ["bool", "boolean"]) and (
|
||||
(value in [True, "TRUE", "YES"] and current_value == "1")
|
||||
or (value in [False, "FALSE", "NO"] and current_value == "0")
|
||||
):
|
||||
ret["comment"] += f"{domain} {name} is already set to {value}"
|
||||
elif vtype in ["int", "integer"] and safe_cast(current_value, int) == safe_cast(
|
||||
value, int
|
||||
):
|
||||
ret["comment"] += f"{domain} {name} is already set to {value}"
|
||||
elif current_value == value:
|
||||
if _compare_values(value, current_value, strict=re.match(r"-add$", vtype) is None):
|
||||
ret["comment"] += f"{domain} {name} is already set to {value}"
|
||||
else:
|
||||
out = __salt__["macdefaults.write"](domain, name, value, vtype, user)
|
||||
if out["retcode"] != 0:
|
||||
ret["result"] = False
|
||||
ret["comment"] = "Failed to write default. {}".format(out["stdout"])
|
||||
ret["comment"] = f"Failed to write default. {out['stdout']}"
|
||||
else:
|
||||
ret["changes"]["written"] = f"{domain} {name} is set to {value}"
|
||||
|
||||
|
@ -101,3 +88,49 @@ def absent(name, domain, user=None):
|
|||
ret["changes"]["absent"] = f"{domain} {name} is now absent"
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _compare_values(new, current, strict=True):
|
||||
"""
|
||||
Compare two values
|
||||
|
||||
new
|
||||
The new value to compare
|
||||
|
||||
current
|
||||
The current value to compare
|
||||
|
||||
strict
|
||||
If True, the values must be exactly the same, if False, the new value
|
||||
must be in the current value
|
||||
"""
|
||||
if strict:
|
||||
return new == current
|
||||
return new in current
|
||||
|
||||
|
||||
def _cast_value(value, vtype):
|
||||
def safe_cast(val, to_type, default=None):
|
||||
try:
|
||||
return to_type(val)
|
||||
except ValueError:
|
||||
return default
|
||||
|
||||
if vtype in ("bool", "boolean"):
|
||||
if value not in [True, "TRUE", "YES", False, "FALSE", "NO"]:
|
||||
raise ValueError(f"Invalid value for boolean: {value}")
|
||||
return value in [True, "TRUE", "YES"]
|
||||
|
||||
if vtype in ("int", "integer"):
|
||||
return safe_cast(value, int)
|
||||
|
||||
if vtype == "float":
|
||||
return safe_cast(value, float)
|
||||
|
||||
if vtype in ("dict", "dict-add"):
|
||||
return safe_cast(value, dict)
|
||||
|
||||
if vtype in ["array", "array-add"]:
|
||||
return safe_cast(value, list)
|
||||
|
||||
return value
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import pytest
|
||||
|
||||
import salt.modules.macdefaults as macdefaults
|
||||
from tests.support.mock import MagicMock, patch
|
||||
from tests.support.mock import MagicMock, call, patch
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -9,15 +9,45 @@ def configure_loader_modules():
|
|||
return {macdefaults: {}}
|
||||
|
||||
|
||||
def test_run_defaults_cmd():
|
||||
"""
|
||||
Test caling _run_defaults_cmd
|
||||
"""
|
||||
mock = MagicMock(return_value={"retcode": 0, "stdout": "Server", "stderr": ""})
|
||||
with patch.dict(macdefaults.__salt__, {"cmd.run_all": mock}):
|
||||
result = macdefaults._run_defaults_cmd(
|
||||
'read "com.apple.CrashReporter" "DialogType"'
|
||||
)
|
||||
mock.assert_called_once_with(
|
||||
'defaults read "com.apple.CrashReporter" "DialogType"', runas=None
|
||||
)
|
||||
assert result == {"retcode": 0, "stdout": "Server", "stderr": ""}
|
||||
|
||||
|
||||
def test_run_defaults_cmd_with_user():
|
||||
"""
|
||||
Test caling _run_defaults_cmd
|
||||
"""
|
||||
mock = MagicMock(return_value={"retcode": 0, "stdout": "Server", "stderr": ""})
|
||||
with patch.dict(macdefaults.__salt__, {"cmd.run_all": mock}):
|
||||
result = macdefaults._run_defaults_cmd(
|
||||
'read "com.apple.CrashReporter" "DialogType"', runas="frank"
|
||||
)
|
||||
mock.assert_called_once_with(
|
||||
'defaults read "com.apple.CrashReporter" "DialogType"', runas="frank"
|
||||
)
|
||||
assert result == {"retcode": 0, "stdout": "Server", "stderr": ""}
|
||||
|
||||
|
||||
def test_write_default():
|
||||
"""
|
||||
Test writing a default setting
|
||||
"""
|
||||
mock = MagicMock()
|
||||
with patch.dict(macdefaults.__salt__, {"cmd.run_all": mock}):
|
||||
mock = MagicMock(return_value={"retcode": 0})
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
macdefaults.write("com.apple.CrashReporter", "DialogType", "Server")
|
||||
mock.assert_called_once_with(
|
||||
'defaults write "com.apple.CrashReporter" "DialogType" -string "Server"',
|
||||
'write "com.apple.CrashReporter" "DialogType" -string "Server"',
|
||||
runas=None,
|
||||
)
|
||||
|
||||
|
@ -26,26 +56,108 @@ def test_write_with_user():
|
|||
"""
|
||||
Test writing a default setting with a specific user
|
||||
"""
|
||||
mock = MagicMock()
|
||||
with patch.dict(macdefaults.__salt__, {"cmd.run_all": mock}):
|
||||
mock = MagicMock(return_value={"retcode": 0})
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
macdefaults.write(
|
||||
"com.apple.CrashReporter", "DialogType", "Server", user="frank"
|
||||
)
|
||||
mock.assert_called_once_with(
|
||||
'defaults write "com.apple.CrashReporter" "DialogType" -string "Server"',
|
||||
'write "com.apple.CrashReporter" "DialogType" -string "Server"',
|
||||
runas="frank",
|
||||
)
|
||||
|
||||
|
||||
def test_write_default_boolean():
|
||||
def test_write_true_boolean():
|
||||
"""
|
||||
Test writing a default setting
|
||||
Test writing a True boolean setting
|
||||
"""
|
||||
mock = MagicMock()
|
||||
with patch.dict(macdefaults.__salt__, {"cmd.run_all": mock}):
|
||||
macdefaults.write("com.apple.CrashReporter", "Crash", True, type="boolean")
|
||||
mock = MagicMock(return_value={"retcode": 0})
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
macdefaults.write("com.apple.CrashReporter", "Crash", True, vtype="boolean")
|
||||
mock.assert_called_once_with(
|
||||
'defaults write "com.apple.CrashReporter" "Crash" -boolean "TRUE"',
|
||||
'write "com.apple.CrashReporter" "Crash" -boolean "TRUE"',
|
||||
runas=None,
|
||||
)
|
||||
|
||||
|
||||
def test_write_false_bool():
|
||||
"""
|
||||
Test writing a False boolean setting
|
||||
"""
|
||||
mock = MagicMock(return_value={"retcode": 0})
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
macdefaults.write("com.apple.CrashReporter", "Crash", False, vtype="bool")
|
||||
mock.assert_called_once_with(
|
||||
'write "com.apple.CrashReporter" "Crash" -bool "FALSE"',
|
||||
runas=None,
|
||||
)
|
||||
|
||||
|
||||
def test_write_int():
|
||||
"""
|
||||
Test writing an int setting
|
||||
"""
|
||||
mock = MagicMock(return_value={"retcode": 0})
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
macdefaults.write("com.apple.CrashReporter", "Crash", 1, vtype="int")
|
||||
mock.assert_called_once_with(
|
||||
'write "com.apple.CrashReporter" "Crash" -int 1',
|
||||
runas=None,
|
||||
)
|
||||
|
||||
|
||||
def test_write_integer():
|
||||
"""
|
||||
Test writing an integer setting
|
||||
"""
|
||||
mock = MagicMock(return_value={"retcode": 0})
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
macdefaults.write("com.apple.CrashReporter", "Crash", 1, vtype="integer")
|
||||
mock.assert_called_once_with(
|
||||
'write "com.apple.CrashReporter" "Crash" -integer 1',
|
||||
runas=None,
|
||||
)
|
||||
|
||||
|
||||
def test_write_float():
|
||||
"""
|
||||
Test writing a float setting
|
||||
"""
|
||||
mock = MagicMock(return_value={"retcode": 0})
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
macdefaults.write("com.apple.CrashReporter", "Crash", 0.85, vtype="float")
|
||||
mock.assert_called_once_with(
|
||||
'write "com.apple.CrashReporter" "Crash" -float 0.85',
|
||||
runas=None,
|
||||
)
|
||||
|
||||
|
||||
def test_write_array():
|
||||
"""
|
||||
Test writing an array setting
|
||||
"""
|
||||
mock = MagicMock(return_value={"retcode": 0})
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
macdefaults.write(
|
||||
"com.apple.CrashReporter", "Crash", [0.1, 0.2, 0.4], vtype="array"
|
||||
)
|
||||
mock.assert_called_once_with(
|
||||
'write "com.apple.CrashReporter" "Crash" -array 0.1 0.2 0.4',
|
||||
runas=None,
|
||||
)
|
||||
|
||||
|
||||
def test_write_dictionary():
|
||||
"""
|
||||
Test writing a dictionary setting
|
||||
"""
|
||||
mock = MagicMock(return_value={"retcode": 0})
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
macdefaults.write(
|
||||
"com.apple.CrashReporter", "Crash", {"foo": "bar", "baz": 0}, vtype="dict"
|
||||
)
|
||||
mock.assert_called_once_with(
|
||||
'write "com.apple.CrashReporter" "Crash" -dict "foo" "bar" "baz" 0',
|
||||
runas=None,
|
||||
)
|
||||
|
||||
|
@ -54,49 +166,179 @@ def test_read_default():
|
|||
"""
|
||||
Test reading a default setting
|
||||
"""
|
||||
mock = MagicMock()
|
||||
with patch.dict(macdefaults.__salt__, {"cmd.run": mock}):
|
||||
macdefaults.read("com.apple.CrashReporter", "Crash")
|
||||
mock.assert_called_once_with(
|
||||
'defaults read "com.apple.CrashReporter" "Crash"', runas=None
|
||||
|
||||
def custom_run_defaults_cmd(action, runas=None):
|
||||
if action == 'read-type "com.apple.CrashReporter" "Crash"':
|
||||
return {"retcode": 0, "stdout": "string"}
|
||||
elif action == 'read "com.apple.CrashReporter" "Crash"':
|
||||
return {"retcode": 0, "stdout": "Server"}
|
||||
return {"retcode": 1, "stderr": f"Unknown action: {action}", "stdout": ""}
|
||||
|
||||
mock = MagicMock(side_effect=custom_run_defaults_cmd)
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
result = macdefaults.read("com.apple.CrashReporter", "Crash")
|
||||
mock.assert_has_calls(
|
||||
[
|
||||
call('read "com.apple.CrashReporter" "Crash"', runas=None),
|
||||
call('read-type "com.apple.CrashReporter" "Crash"', runas=None),
|
||||
]
|
||||
)
|
||||
assert result == "Server"
|
||||
|
||||
|
||||
def test_read_default_with_user():
|
||||
def test_read_with_user():
|
||||
"""
|
||||
Test reading a default setting as a specific user
|
||||
"""
|
||||
mock = MagicMock()
|
||||
with patch.dict(macdefaults.__salt__, {"cmd.run": mock}):
|
||||
macdefaults.read("com.apple.CrashReporter", "Crash", user="frank")
|
||||
mock.assert_called_once_with(
|
||||
'defaults read "com.apple.CrashReporter" "Crash"', runas="frank"
|
||||
|
||||
def custom_run_defaults_cmd(action, runas=None):
|
||||
if action == 'read-type "com.apple.CrashReporter" "Crash"':
|
||||
return {"retcode": 0, "stdout": "string"}
|
||||
elif action == 'read "com.apple.CrashReporter" "Crash"':
|
||||
return {"retcode": 0, "stdout": "Server"}
|
||||
return {"retcode": 1, "stderr": f"Unknown action: {action}", "stdout": ""}
|
||||
|
||||
mock = MagicMock(side_effect=custom_run_defaults_cmd)
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
result = macdefaults.read("com.apple.CrashReporter", "Crash", user="frank")
|
||||
mock.assert_has_calls(
|
||||
[
|
||||
call('read "com.apple.CrashReporter" "Crash"', runas="frank"),
|
||||
call('read-type "com.apple.CrashReporter" "Crash"', runas="frank"),
|
||||
]
|
||||
)
|
||||
assert result == "Server"
|
||||
|
||||
|
||||
def test_read_integer():
|
||||
"""
|
||||
Test reading an integer setting
|
||||
"""
|
||||
|
||||
def custom_run_defaults_cmd(action, runas=None):
|
||||
if action == 'read-type "com.apple.CrashReporter" "Crash"':
|
||||
return {"retcode": 0, "stdout": "integer"}
|
||||
elif action == 'read "com.apple.CrashReporter" "Crash"':
|
||||
return {"retcode": 0, "stdout": "12"}
|
||||
return {"retcode": 1, "stderr": f"Unknown action: {action}", "stdout": ""}
|
||||
|
||||
mock = MagicMock(side_effect=custom_run_defaults_cmd)
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
result = macdefaults.read("com.apple.CrashReporter", "Crash")
|
||||
mock.assert_has_calls(
|
||||
[
|
||||
call('read "com.apple.CrashReporter" "Crash"', runas=None),
|
||||
call('read-type "com.apple.CrashReporter" "Crash"', runas=None),
|
||||
]
|
||||
)
|
||||
assert result == 12
|
||||
|
||||
|
||||
def test_read_float():
|
||||
"""
|
||||
Test reading a float setting
|
||||
"""
|
||||
|
||||
def custom_run_defaults_cmd(action, runas=None):
|
||||
if action == 'read-type "com.apple.CrashReporter" "Crash"':
|
||||
return {"retcode": 0, "stdout": "float"}
|
||||
elif action == 'read "com.apple.CrashReporter" "Crash"':
|
||||
return {"retcode": 0, "stdout": "0.85"}
|
||||
return {"retcode": 1, "stderr": f"Unknown action: {action}", "stdout": ""}
|
||||
|
||||
mock = MagicMock(side_effect=custom_run_defaults_cmd)
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
result = macdefaults.read("com.apple.CrashReporter", "Crash")
|
||||
mock.assert_has_calls(
|
||||
[
|
||||
call('read "com.apple.CrashReporter" "Crash"', runas=None),
|
||||
call('read-type "com.apple.CrashReporter" "Crash"', runas=None),
|
||||
]
|
||||
)
|
||||
assert result == 0.85
|
||||
|
||||
|
||||
def test_read_array():
|
||||
"""
|
||||
Test reading an array setting
|
||||
"""
|
||||
|
||||
defaults_output = """(
|
||||
element 1,
|
||||
element 2,
|
||||
0.1,
|
||||
1
|
||||
)"""
|
||||
|
||||
def custom_run_defaults_cmd(action, runas=None):
|
||||
if action == 'read-type "com.apple.CrashReporter" "Crash"':
|
||||
return {"retcode": 0, "stdout": "array"}
|
||||
elif action == 'read "com.apple.CrashReporter" "Crash"':
|
||||
return {"retcode": 0, "stdout": defaults_output}
|
||||
return {"retcode": 1, "stderr": f"Unknown action: {action}", "stdout": ""}
|
||||
|
||||
mock = MagicMock(side_effect=custom_run_defaults_cmd)
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
result = macdefaults.read("com.apple.CrashReporter", "Crash")
|
||||
mock.assert_has_calls(
|
||||
[
|
||||
call('read "com.apple.CrashReporter" "Crash"', runas=None),
|
||||
call('read-type "com.apple.CrashReporter" "Crash"', runas=None),
|
||||
]
|
||||
)
|
||||
assert result == ["element 1", "element 2", 0.1, 1]
|
||||
|
||||
|
||||
def test_read_dictionary():
|
||||
"""
|
||||
Test reading a dictionary setting
|
||||
"""
|
||||
|
||||
defaults_output = """{
|
||||
keyCode = 36;
|
||||
modifierFlags = 786432;
|
||||
}"""
|
||||
|
||||
def custom_run_defaults_cmd(action, runas=None):
|
||||
if action == 'read-type "com.apple.CrashReporter" "Crash"':
|
||||
return {"retcode": 0, "stdout": "dictionary"}
|
||||
elif action == 'read "com.apple.CrashReporter" "Crash"':
|
||||
return {"retcode": 0, "stdout": defaults_output}
|
||||
return {"retcode": 1, "stderr": f"Unknown action: {action}", "stdout": ""}
|
||||
|
||||
mock = MagicMock(side_effect=custom_run_defaults_cmd)
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
result = macdefaults.read("com.apple.CrashReporter", "Crash")
|
||||
mock.assert_has_calls(
|
||||
[
|
||||
call('read "com.apple.CrashReporter" "Crash"', runas=None),
|
||||
call('read-type "com.apple.CrashReporter" "Crash"', runas=None),
|
||||
]
|
||||
)
|
||||
assert result == {"keyCode": 36, "modifierFlags": 786432}
|
||||
|
||||
|
||||
def test_delete_default():
|
||||
"""
|
||||
Test delete a default setting
|
||||
"""
|
||||
mock = MagicMock()
|
||||
with patch.dict(macdefaults.__salt__, {"cmd.run_all": mock}):
|
||||
mock = MagicMock(return_value={"retcode": 0})
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
macdefaults.delete("com.apple.CrashReporter", "Crash")
|
||||
mock.assert_called_once_with(
|
||||
'defaults delete "com.apple.CrashReporter" "Crash"',
|
||||
output_loglevel="debug",
|
||||
'delete "com.apple.CrashReporter" "Crash"',
|
||||
runas=None,
|
||||
)
|
||||
|
||||
|
||||
def test_delete_default_with_user():
|
||||
def test_delete_with_user():
|
||||
"""
|
||||
Test delete a default setting as a specific user
|
||||
Test delete a setting as a specific user
|
||||
"""
|
||||
mock = MagicMock()
|
||||
with patch.dict(macdefaults.__salt__, {"cmd.run_all": mock}):
|
||||
mock = MagicMock(return_value={"retcode": 0})
|
||||
with patch("salt.modules.macdefaults._run_defaults_cmd", mock):
|
||||
macdefaults.delete("com.apple.CrashReporter", "Crash", user="frank")
|
||||
mock.assert_called_once_with(
|
||||
'defaults delete "com.apple.CrashReporter" "Crash"',
|
||||
output_loglevel="debug",
|
||||
'delete "com.apple.CrashReporter" "Crash"',
|
||||
runas="frank",
|
||||
)
|
||||
|
|
|
@ -88,12 +88,12 @@ def test_write_boolean_match():
|
|||
"""
|
||||
expected = {
|
||||
"changes": {},
|
||||
"comment": "com.apple.something Key is already set to YES",
|
||||
"comment": "com.apple.something Key is already set to True",
|
||||
"name": "Key",
|
||||
"result": True,
|
||||
}
|
||||
|
||||
read_mock = MagicMock(return_value="1")
|
||||
read_mock = MagicMock(return_value=True)
|
||||
write_mock = MagicMock(return_value={"retcode": 0})
|
||||
with patch.dict(
|
||||
macdefaults.__salt__,
|
||||
|
@ -141,7 +141,7 @@ def test_write_integer_match():
|
|||
"result": True,
|
||||
}
|
||||
|
||||
read_mock = MagicMock(return_value="1337")
|
||||
read_mock = MagicMock(return_value=1337)
|
||||
write_mock = MagicMock(return_value={"retcode": 0})
|
||||
with patch.dict(
|
||||
macdefaults.__salt__,
|
||||
|
|
Loading…
Add table
Reference in a new issue