mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
feat(macdefaults): Add support for copmplex keys and nested dictionaries
This commit is contained in:
parent
7c5baf4bbe
commit
f5067e7853
4 changed files with 1067 additions and 451 deletions
|
@ -1,18 +1,23 @@
|
|||
"""
|
||||
Set defaults settings on macOS.
|
||||
|
||||
This module uses defaults cli under the hood to read and write defaults on macOS.
|
||||
This module uses defaults cli under the hood to import and export defaults on macOS.
|
||||
|
||||
Thus, the module is limited to the capabilities of the defaults command.
|
||||
However, it uses the plistlib package to handle the conversion between the defaults
|
||||
output and Python dictionaries. It is also used to create the plist files to import
|
||||
the defaults.
|
||||
|
||||
Read macOS defaults help page for more information on defaults command.
|
||||
Read plistlib documentation for more information on how the conversion is done:
|
||||
https://docs.python.org/3/library/plistlib.html
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import plistlib
|
||||
import re
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
import salt.utils.data
|
||||
import salt.utils.platform
|
||||
import salt.utils.versions
|
||||
from salt.exceptions import CommandExecutionError
|
||||
|
@ -31,34 +36,55 @@ def __virtual__():
|
|||
return False
|
||||
|
||||
|
||||
def write(domain, key, value, vtype=None, user=None, type=None):
|
||||
def write(
|
||||
domain,
|
||||
key,
|
||||
value,
|
||||
vtype=None,
|
||||
user=None,
|
||||
dict_merge=False,
|
||||
array_add=False,
|
||||
type=None,
|
||||
):
|
||||
"""
|
||||
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:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' macdefaults.write com.apple.CrashReporter DialogType Server
|
||||
salt '*' macdefaults.write com.apple.Finder DownloadsFolderListViewSettingsVersion 1
|
||||
|
||||
salt '*' macdefaults.write NSGlobalDomain ApplePersistence True vtype=bool
|
||||
salt '*' macdefaults.write com.apple.Finder ComputerViewSettings.CustomViewStyle "icnv"
|
||||
|
||||
salt '*' macdefaults.write com.apple.Dock lastShowIndicatorTime 737720347.089987 vtype=date
|
||||
|
||||
domain
|
||||
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.
|
||||
It can be a nested key/index separated by dots.
|
||||
|
||||
value
|
||||
The value to write to the given key
|
||||
The value to write to the given key.
|
||||
Dates should be in the format 'YYYY-MM-DDTHH:MM:SSZ'
|
||||
|
||||
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
|
||||
The type of value to be written, valid types are string, int[eger],
|
||||
float, bool[ean], date and data.
|
||||
|
||||
dict and array are also valid types but are only used for validation.
|
||||
|
||||
dict-add and array-add are supported for backward compatibility.
|
||||
However, their corresponding sibling options dict_merge and array_add
|
||||
are recommended.
|
||||
|
||||
This parameter is optional. It will be used to cast the values to the
|
||||
specified type before writing them to the system. If not provided, the
|
||||
type will be inferred from the value.
|
||||
|
||||
Useful when writing values such as dates or binary data.
|
||||
|
||||
type
|
||||
Deprecated! Use vtype instead
|
||||
|
@ -68,6 +94,20 @@ def write(domain, key, value, vtype=None, user=None, type=None):
|
|||
user
|
||||
The user to write the defaults to
|
||||
|
||||
dict_merge
|
||||
Merge the value into the existing dictionary.
|
||||
If current value is not a dictionary this option will be ignored.
|
||||
This option will be set to True if vtype is dict-add.
|
||||
|
||||
array_add
|
||||
Append the value to the array.
|
||||
If current value is not a list this option will be ignored.
|
||||
This option will be set to True if vtype is array-add.
|
||||
|
||||
Raises:
|
||||
KeyError: When the key is not found in the domain
|
||||
IndexError: When the key is not a valid array index
|
||||
|
||||
"""
|
||||
if type is not None:
|
||||
salt.utils.versions.warn_until(
|
||||
|
@ -81,25 +121,44 @@ def write(domain, key, value, vtype=None, user=None, type=None):
|
|||
"The 'vtype' argument in macdefaults.write takes precedence over 'type'."
|
||||
)
|
||||
|
||||
if vtype is None:
|
||||
vtype = "string"
|
||||
plist = _load_plist(domain, user=user) or {}
|
||||
keys = key.split(".")
|
||||
last_key = keys[-1]
|
||||
|
||||
if vtype in ("bool", "boolean"):
|
||||
value = _convert_to_defaults_boolean(value)
|
||||
# Traverse the plist
|
||||
container = _traverse_keys(plist, keys[:-1])
|
||||
if container is None:
|
||||
raise KeyError(f"Key not found: {key} for domain: {domain}")
|
||||
|
||||
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")
|
||||
current_value = None
|
||||
if isinstance(container, dict):
|
||||
current_value = container.get(last_key)
|
||||
elif isinstance(container, list) and last_key.isdigit():
|
||||
last_key = int(last_key)
|
||||
if -len(container) <= last_key < len(container):
|
||||
current_value = container[last_key]
|
||||
else:
|
||||
raise IndexError(f"Index {last_key} is out of range for domain: {domain}")
|
||||
|
||||
# Quote values that are neither integers nor floats
|
||||
value = map(lambda v: str(v) if isinstance(v, (int, float)) else f'"{v}"', value)
|
||||
# Write/Update the new value
|
||||
if vtype is not None:
|
||||
if vtype == "array-add":
|
||||
array_add = True
|
||||
elif vtype == "dict-add":
|
||||
dict_merge = True
|
||||
value = cast_value_to_vtype(value, vtype)
|
||||
|
||||
cmd = f'write "{domain}" "{key}" -{vtype} {" ".join(value)}'
|
||||
return _run_defaults_cmd(cmd, runas=user)
|
||||
if isinstance(current_value, dict) and isinstance(value, dict) and dict_merge:
|
||||
container[last_key].update(value)
|
||||
elif isinstance(current_value, list) and array_add:
|
||||
if isinstance(value, list):
|
||||
container[last_key].extend(value)
|
||||
else:
|
||||
container[last_key].append(value)
|
||||
else:
|
||||
container[last_key] = value
|
||||
|
||||
return _save_plist(domain, plist, user=user)
|
||||
|
||||
|
||||
def read(domain, key, user=None):
|
||||
|
@ -110,7 +169,7 @@ def read(domain, key, user=None):
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' macdefaults.read com.apple.CrashReporter DialogType
|
||||
salt '*' macdefaults.read com.apple.Dock persistent-apps.1.title-data.file-label
|
||||
|
||||
salt '*' macdefaults.read NSGlobalDomain ApplePersistence
|
||||
|
||||
|
@ -118,27 +177,21 @@ def read(domain, key, user=None):
|
|||
The name of the domain to read from
|
||||
|
||||
key
|
||||
The key of the given domain to read from
|
||||
The key of the given domain to read from.
|
||||
It can be a nested key/index separated by dots.
|
||||
|
||||
user
|
||||
The user to read the defaults as
|
||||
The user to read the defaults from
|
||||
|
||||
Returns:
|
||||
The current value for the given key, or None if the key does not exist.
|
||||
|
||||
"""
|
||||
cmd = f'read "{domain}" "{key}"'
|
||||
ret = _run_defaults_cmd(cmd, runas=user)
|
||||
plist = _load_plist(domain, user)
|
||||
if plist is None:
|
||||
return None
|
||||
|
||||
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)
|
||||
return _traverse_keys(plist, key.split("."))
|
||||
|
||||
|
||||
def delete(domain, key, user=None):
|
||||
|
@ -157,165 +210,195 @@ def delete(domain, key, user=None):
|
|||
The name of the domain to delete from
|
||||
|
||||
key
|
||||
The key of the given domain to delete
|
||||
The key of the given domain to delete.
|
||||
It can be a nested key separated by dots.
|
||||
|
||||
user
|
||||
The user to delete the defaults with
|
||||
|
||||
"""
|
||||
cmd = f'delete "{domain}" "{key}"'
|
||||
return _run_defaults_cmd(cmd, runas=user)
|
||||
plist = _load_plist(domain, user=user)
|
||||
if plist is None:
|
||||
return None
|
||||
|
||||
keys = key.split(".")
|
||||
|
||||
# Traverse the plist til the penultimate key.
|
||||
# Last key must be handled separately since we
|
||||
# need the parent dictionary to delete that key.
|
||||
target = _traverse_keys(plist, keys[:-1])
|
||||
if target is None:
|
||||
return None
|
||||
|
||||
# Delete the last key if it exists and update defaults
|
||||
last_key = keys[-1]
|
||||
key_in_plist = False
|
||||
if isinstance(target, dict) and last_key in target:
|
||||
key_in_plist = True
|
||||
|
||||
elif (
|
||||
isinstance(target, list)
|
||||
and last_key.isdigit()
|
||||
and -len(target) <= int(last_key) < len(target)
|
||||
):
|
||||
key_in_plist = True
|
||||
last_key = int(last_key)
|
||||
|
||||
if not key_in_plist:
|
||||
return None
|
||||
|
||||
del target[last_key]
|
||||
return _save_plist(domain, plist, user=user)
|
||||
|
||||
|
||||
def read_type(domain, key, user=None):
|
||||
def cast_value_to_vtype(value, vtype):
|
||||
"""
|
||||
Read a default type from the system
|
||||
If the key is not found, None is returned.
|
||||
Convert the value to the specified vtype.
|
||||
If the value cannot be converted, it will be returned as is.
|
||||
|
||||
CLI Example:
|
||||
value
|
||||
The value to be converted
|
||||
|
||||
.. code-block:: bash
|
||||
vtype
|
||||
The type to convert the value to
|
||||
|
||||
salt '*' macdefaults.read_type com.apple.CrashReporter DialogType
|
||||
Returns:
|
||||
The converted value
|
||||
|
||||
salt '*' macdefaults.read_type NSGlobalDomain ApplePersistence
|
||||
|
||||
"""
|
||||
# Boolean
|
||||
if vtype in ("bool", "boolean"):
|
||||
if isinstance(value, str):
|
||||
if value.lower() in ("true", "yes", "1"):
|
||||
value = True
|
||||
elif value.lower() in ("false", "no", "0"):
|
||||
value = False
|
||||
else:
|
||||
raise ValueError(f"Invalid value for boolean: '{value}'")
|
||||
elif value in (0, 1):
|
||||
value = bool(value)
|
||||
elif not isinstance(value, bool):
|
||||
raise ValueError(f"Invalid value for boolean: '{value}'")
|
||||
# String
|
||||
elif vtype == "string":
|
||||
if isinstance(value, bool):
|
||||
value = "YES" if value else "NO"
|
||||
elif isinstance(value, (int, float)):
|
||||
value = str(value)
|
||||
elif isinstance(value, datetime):
|
||||
value = value.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
elif isinstance(value, bytes):
|
||||
value = value.decode()
|
||||
# Integer
|
||||
elif vtype in ("int", "integer"):
|
||||
value = int(value)
|
||||
# Float
|
||||
elif vtype == "float":
|
||||
value = float(value)
|
||||
# Date
|
||||
elif vtype == "date":
|
||||
if not isinstance(value, datetime):
|
||||
try:
|
||||
value = datetime.fromtimestamp(float(value))
|
||||
except ValueError:
|
||||
if re.match(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z", value):
|
||||
value = datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ")
|
||||
else:
|
||||
raise ValueError(f"Invalid date format: '{value}'")
|
||||
# Data
|
||||
elif vtype == "data":
|
||||
if isinstance(value, str):
|
||||
value = value.encode()
|
||||
elif not isinstance(value, bytes):
|
||||
raise ValueError(f"Invalid value for data: '{value}'")
|
||||
# Dictionary
|
||||
elif vtype in ("dict", "dict-add"):
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError(f"Invalid value for dictionary: '{value}'")
|
||||
# Array
|
||||
elif vtype in ("array", "array-add"):
|
||||
if not isinstance(value, list):
|
||||
raise ValueError(f"Invalid value for array: '{value}'")
|
||||
else:
|
||||
raise ValueError(f"Invalid type: '{vtype}'")
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def _load_plist(domain, user=None):
|
||||
"""
|
||||
Load a plist from the system and return it as a dictionary
|
||||
|
||||
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
|
||||
The user to read the defaults as. Defaults to root (None).
|
||||
|
||||
Raises:
|
||||
CommandExecutionError: When the defaults command fails
|
||||
Other exceptions thrown by plistlib.loads
|
||||
|
||||
Returns:
|
||||
A dictionary with the plist contents, or None if the domain does not exist.
|
||||
"""
|
||||
cmd = f'read-type "{domain}" "{key}"'
|
||||
cmd = f'export "{domain}" -'
|
||||
ret = _run_defaults_cmd(cmd, runas=user)
|
||||
|
||||
if ret["retcode"] != 0:
|
||||
if "does not exist" in ret["stderr"]:
|
||||
raise CommandExecutionError(f"Failed to export defaults: {ret['stderr']}")
|
||||
|
||||
plist = plistlib.loads(ret["stdout"].encode())
|
||||
if not plist:
|
||||
return None
|
||||
|
||||
return plist
|
||||
|
||||
|
||||
def _save_plist(domain, plist, user=None):
|
||||
"""
|
||||
Save a plist dictionary to the system
|
||||
|
||||
domain
|
||||
The name of the domain to read from
|
||||
|
||||
plist
|
||||
The dictionary to export as a plist
|
||||
|
||||
user
|
||||
The user to export the defaults to. Defaults to root (None).
|
||||
|
||||
Raises:
|
||||
CommandExecutionError: When the defaults command fails
|
||||
Other exceptions thrown by plistlib.dump
|
||||
|
||||
Returns:
|
||||
A dictionary with the defaults command result
|
||||
"""
|
||||
with tempfile.NamedTemporaryFile() as tmpfile:
|
||||
plistlib.dump(plist, tmpfile)
|
||||
tmpfile.flush()
|
||||
cmd = f'import "{domain}" "{tmpfile.name}"'
|
||||
return _run_defaults_cmd(cmd, runas=user)
|
||||
|
||||
|
||||
def _traverse_keys(plist, keys):
|
||||
value = plist
|
||||
for k in keys:
|
||||
if isinstance(value, dict):
|
||||
value = value.get(k)
|
||||
elif (
|
||||
isinstance(value, list)
|
||||
and k.isdigit()
|
||||
and -len(value) <= int(k) < len(value)
|
||||
):
|
||||
value = value[int(k)]
|
||||
else:
|
||||
value = None
|
||||
|
||||
if value is None:
|
||||
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 the value returned by the defaults command in vytpe to Python type
|
||||
|
||||
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`
|
||||
and returns the dictionary content as a Python dictionary
|
||||
|
||||
value (str):
|
||||
A multiline string with the dictionary content, including the surrounding curly braces
|
||||
|
||||
"""
|
||||
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 in (True, 1):
|
||||
return "TRUE"
|
||||
if value in (False, 0):
|
||||
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
|
||||
|
||||
|
|
|
@ -18,12 +18,13 @@ def __virtual__():
|
|||
return (False, "Only supported on macOS")
|
||||
|
||||
|
||||
def write(name, domain, value, vtype="string", user=None):
|
||||
def write(name, domain, value, vtype=None, user=None):
|
||||
"""
|
||||
Write a default to the system
|
||||
|
||||
name
|
||||
The key of the given domain to write to
|
||||
The key of the given domain to write to.
|
||||
It can be a nested key/index separated by dots.
|
||||
|
||||
domain
|
||||
The name of the domain to write to
|
||||
|
@ -42,7 +43,9 @@ def write(name, domain, value, vtype="string", user=None):
|
|||
ret = {"name": name, "result": True, "comment": "", "changes": {}}
|
||||
|
||||
current_value = __salt__["macdefaults.read"](domain, name, user)
|
||||
value = _cast_value(value, vtype)
|
||||
|
||||
if vtype is not None:
|
||||
value = __salt__["macdefaults.cast_value_to_vtype"](value, vtype)
|
||||
|
||||
if _compare_values(value, current_value, vtype):
|
||||
ret["comment"] += f"{domain} {name} is already set to {value}"
|
||||
|
@ -62,7 +65,8 @@ def absent(name, domain, user=None):
|
|||
Make sure the defaults value is absent
|
||||
|
||||
name
|
||||
The key of the given domain to remove
|
||||
The key of the given domain to remove.
|
||||
It can be a nested key/index separated by dots.
|
||||
|
||||
domain
|
||||
The name of the domain to remove from
|
||||
|
@ -75,7 +79,7 @@ def absent(name, domain, user=None):
|
|||
|
||||
out = __salt__["macdefaults.delete"](domain, name, user)
|
||||
|
||||
if out["retcode"] != 0:
|
||||
if out is None or out["retcode"] != 0:
|
||||
ret["comment"] += f"{domain} {name} is already absent"
|
||||
else:
|
||||
ret["changes"]["absent"] = f"{domain} {name} is now absent"
|
||||
|
@ -98,81 +102,11 @@ def _compare_values(new, current, vtype):
|
|||
|
||||
"""
|
||||
if vtype == "array-add":
|
||||
return _is_subarray(new, current)
|
||||
if isinstance(new, list):
|
||||
return new == current[-len(new) :]
|
||||
return new == current[-1]
|
||||
|
||||
if vtype == "dict-add":
|
||||
return all(key in current and new[key] == current[key] for key in new.keys())
|
||||
|
||||
return new == current
|
||||
|
||||
|
||||
def _is_subarray(new, current):
|
||||
"""
|
||||
Check if new is a subarray of current array.
|
||||
|
||||
This method does not check only whether all elements in new array
|
||||
are present in current array, but also whether the elements are in
|
||||
the same order.
|
||||
|
||||
new
|
||||
The new array to compare
|
||||
|
||||
current
|
||||
The current array to compare
|
||||
|
||||
"""
|
||||
current_len = len(current)
|
||||
new_len = len(new)
|
||||
|
||||
if new_len == 0:
|
||||
return True
|
||||
if new_len > current_len:
|
||||
return False
|
||||
|
||||
for i in range(current_len - new_len + 1):
|
||||
# Check if the new array is found at this position
|
||||
if current[i : i + new_len] == new:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _cast_value(value, vtype):
|
||||
"""
|
||||
Cast the given macOS default value to Python type
|
||||
|
||||
value
|
||||
The value to cast from macOS default
|
||||
|
||||
vtype
|
||||
The type to cast the value from
|
||||
|
||||
"""
|
||||
|
||||
def safe_cast(val, to_type, default=None):
|
||||
"""
|
||||
Auxiliary function to safely cast a value to a given type
|
||||
|
||||
"""
|
||||
try:
|
||||
return to_type(val)
|
||||
except ValueError:
|
||||
return default
|
||||
|
||||
if vtype in ("bool", "boolean"):
|
||||
if value not in [True, 1, "TRUE", "YES", False, 0, "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
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,12 +1,19 @@
|
|||
import pytest
|
||||
|
||||
import salt.modules.macdefaults as macdefaults_module
|
||||
import salt.states.macdefaults as macdefaults
|
||||
from tests.support.mock import MagicMock, patch
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def configure_loader_modules():
|
||||
return {macdefaults: {}}
|
||||
return {
|
||||
macdefaults: {
|
||||
"__salt__": {
|
||||
"macdefaults.cast_value_to_vtype": macdefaults_module.cast_value_to_vtype
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_write_default():
|
||||
|
@ -29,7 +36,7 @@ def test_write_default():
|
|||
out = macdefaults.write("DialogType", "com.apple.CrashReporter", "Server")
|
||||
read_mock.assert_called_once_with("com.apple.CrashReporter", "DialogType", None)
|
||||
write_mock.assert_called_once_with(
|
||||
"com.apple.CrashReporter", "DialogType", "Server", "string", None
|
||||
"com.apple.CrashReporter", "DialogType", "Server", None, None
|
||||
)
|
||||
assert out == expected
|
||||
|
||||
|
@ -74,7 +81,7 @@ def test_write_default_boolean():
|
|||
macdefaults.__salt__,
|
||||
{"macdefaults.read": read_mock, "macdefaults.write": write_mock},
|
||||
):
|
||||
out = macdefaults.write("Key", "com.apple.something", True, vtype="boolean")
|
||||
out = macdefaults.write("Key", "com.apple.something", "YES", vtype="boolean")
|
||||
read_mock.assert_called_once_with("com.apple.something", "Key", None)
|
||||
write_mock.assert_called_once_with(
|
||||
"com.apple.something", "Key", True, "boolean", None
|
||||
|
@ -122,10 +129,10 @@ def test_write_default_integer():
|
|||
macdefaults.__salt__,
|
||||
{"macdefaults.read": read_mock, "macdefaults.write": write_mock},
|
||||
):
|
||||
out = macdefaults.write("Key", "com.apple.something", 1337, vtype="integer")
|
||||
out = macdefaults.write("Key", "com.apple.something", 1337)
|
||||
read_mock.assert_called_once_with("com.apple.something", "Key", None)
|
||||
write_mock.assert_called_once_with(
|
||||
"com.apple.something", "Key", 1337, "integer", None
|
||||
"com.apple.something", "Key", 1337, None, None
|
||||
)
|
||||
assert out == expected
|
||||
|
||||
|
@ -170,7 +177,7 @@ def test_write_default_float():
|
|||
macdefaults.__salt__,
|
||||
{"macdefaults.read": read_mock, "macdefaults.write": write_mock},
|
||||
):
|
||||
out = macdefaults.write("Key", "com.apple.something", 0.865, vtype="float")
|
||||
out = macdefaults.write("Key", "com.apple.something", "0.8650", vtype="float")
|
||||
read_mock.assert_called_once_with("com.apple.something", "Key", None)
|
||||
write_mock.assert_called_once_with(
|
||||
"com.apple.something", "Key", 0.865, "float", None
|
||||
|
@ -195,7 +202,7 @@ def test_write_default_float_already_set():
|
|||
macdefaults.__salt__,
|
||||
{"macdefaults.read": read_mock, "macdefaults.write": write_mock},
|
||||
):
|
||||
out = macdefaults.write("Key", "com.apple.something", 0.86500, vtype="float")
|
||||
out = macdefaults.write("Key", "com.apple.something", 0.86500)
|
||||
read_mock.assert_called_once_with("com.apple.something", "Key", None)
|
||||
assert not write_mock.called
|
||||
assert out == expected
|
||||
|
@ -245,7 +252,7 @@ def test_write_default_array_already_set():
|
|||
macdefaults.__salt__,
|
||||
{"macdefaults.read": read_mock, "macdefaults.write": write_mock},
|
||||
):
|
||||
out = macdefaults.write("Key", "com.apple.something", value, vtype="array")
|
||||
out = macdefaults.write("Key", "com.apple.something", value)
|
||||
read_mock.assert_called_once_with("com.apple.something", "Key", None)
|
||||
assert not write_mock.called
|
||||
assert out == expected
|
||||
|
@ -283,9 +290,9 @@ def test_write_default_array_add():
|
|||
def test_write_default_array_add_already_set_distinct_order():
|
||||
"""
|
||||
Test writing a default setting adding an array to another that is already set
|
||||
The new array is in a different order than the existing one
|
||||
The new array is in a different order than the last elements of the existing one
|
||||
"""
|
||||
write_value = ["a", 1]
|
||||
write_value = [2, "a"]
|
||||
read_value = ["b", 1, "a", 2]
|
||||
expected = {
|
||||
"changes": {"written": f"com.apple.something Key is set to {write_value}"},
|
||||
|
@ -315,7 +322,7 @@ def test_write_default_array_add_already_set_same_order():
|
|||
Test writing a default setting adding an array to another that is already set
|
||||
The new array is already in the same order as the existing one
|
||||
"""
|
||||
write_value = ["a", 1]
|
||||
write_value = [1, 2]
|
||||
read_value = ["b", "a", 1, 2]
|
||||
expected = {
|
||||
"changes": {},
|
||||
|
@ -455,7 +462,7 @@ def test_absent_default_already():
|
|||
"result": True,
|
||||
}
|
||||
|
||||
mock = MagicMock(return_value={"retcode": 1})
|
||||
mock = MagicMock(return_value=None)
|
||||
with patch.dict(macdefaults.__salt__, {"macdefaults.delete": mock}):
|
||||
out = macdefaults.absent("Key", "com.apple.something")
|
||||
mock.assert_called_once_with("com.apple.something", "Key", None)
|
||||
|
|
Loading…
Add table
Reference in a new issue