mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge branch 'master' into master-port-49214
This commit is contained in:
commit
b6ffa5d323
23 changed files with 1323 additions and 228 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -7,6 +7,20 @@ This project versioning is _similar_ to [Semantic Versioning](https://semver.org
|
|||
Versions are `MAJOR.PATCH`.
|
||||
|
||||
## 3001 - Sodium
|
||||
## 3001 - Sodium [yyyy-mm-dd]
|
||||
|
||||
### Removed
|
||||
|
||||
### Deprecated
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
### Added
|
||||
- [#56637](https://github.com/saltstack/salt/pull/56637) - Add ``win_wua.installed`` to the ``win_wua`` execution module
|
||||
|
||||
## 3000.1
|
||||
|
||||
### Removed
|
||||
|
||||
|
|
|
@ -565,6 +565,15 @@
|
|||
#
|
||||
#state_aggregate: False
|
||||
|
||||
# Disable requisites during state runs by specifying a single requisite
|
||||
# or a list of requisites to disable.
|
||||
#
|
||||
# disabled_requisites: require_in
|
||||
#
|
||||
# disabled_requisites:
|
||||
# - require
|
||||
# - require_in
|
||||
|
||||
##### File Directory Settings #####
|
||||
##########################################
|
||||
# The Salt Minion can redirect all file server operations to a local directory,
|
||||
|
|
|
@ -47,6 +47,7 @@ execution modules
|
|||
azurearm_network
|
||||
azurearm_resource
|
||||
bamboohr
|
||||
baredoc
|
||||
bcache
|
||||
beacons
|
||||
bigip
|
||||
|
|
5
doc/ref/modules/all/salt.modules.baredoc.rst
Normal file
5
doc/ref/modules/all/salt.modules.baredoc.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
salt.modules.baredoc module
|
||||
===========================
|
||||
|
||||
.. automodule:: salt.modules.baredoc
|
||||
:members:
|
|
@ -87,3 +87,9 @@ You can set this setting in your roster file like so:
|
|||
user: root
|
||||
passwd: P@ssword
|
||||
set_path: '$PATH:/usr/local/bin/'
|
||||
|
||||
|
||||
State Changes
|
||||
=============
|
||||
- Adding a new option for the State compiler, ``disabled_requisites`` will allow
|
||||
requisites to be disabled during State runs.
|
||||
|
|
|
@ -20,7 +20,7 @@ pytz==2019.1 # via babel
|
|||
requests==2.22.0 # via sphinx
|
||||
six==1.12.0 # via packaging
|
||||
snowballstemmer==1.2.1 # via sphinx
|
||||
sphinx==2.0.1
|
||||
sphinx==3.0.2
|
||||
sphinxcontrib-applehelp==1.0.1 # via sphinx
|
||||
sphinxcontrib-devhelp==1.0.1 # via sphinx
|
||||
sphinxcontrib-htmlhelp==1.0.2 # via sphinx
|
||||
|
|
|
@ -20,7 +20,7 @@ pytz==2019.1 # via babel
|
|||
requests==2.22.0 # via sphinx
|
||||
six==1.12.0 # via packaging
|
||||
snowballstemmer==1.2.1 # via sphinx
|
||||
sphinx==2.0.1
|
||||
sphinx==3.0.2
|
||||
sphinxcontrib-applehelp==1.0.1 # via sphinx
|
||||
sphinxcontrib-devhelp==1.0.1 # via sphinx
|
||||
sphinxcontrib-htmlhelp==1.0.2 # via sphinx
|
||||
|
|
|
@ -925,6 +925,7 @@ VALID_OPTS = immutabletypes.freeze(
|
|||
# Allow raw_shell option when using the ssh
|
||||
# client via the Salt API
|
||||
"netapi_allow_raw_shell": bool,
|
||||
"disabled_requisites": (six.string_types, list),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -1216,6 +1217,7 @@ DEFAULT_MINION_OPTS = immutabletypes.freeze(
|
|||
"discovery": False,
|
||||
"schedule": {},
|
||||
"ssh_merge_pillar": True,
|
||||
"disabled_requisites": [],
|
||||
}
|
||||
)
|
||||
|
||||
|
|
250
salt/modules/baredoc.py
Normal file
250
salt/modules/baredoc.py
Normal file
|
@ -0,0 +1,250 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Baredoc walks the installed module and state directories and generates
|
||||
dictionaries and lists of the function names and their arguments.
|
||||
|
||||
.. versionadded:: Sodium
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import ast
|
||||
|
||||
# Import python libs
|
||||
import logging
|
||||
import os
|
||||
|
||||
# Import salt libs
|
||||
import salt.utils.files
|
||||
from salt.ext.six.moves import zip_longest
|
||||
|
||||
# Import 3rd-party libs
|
||||
from salt.utils.odict import OrderedDict
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_module_name(tree, filename):
|
||||
"""
|
||||
Returns the value of __virtual__ if found.
|
||||
Otherwise, returns filename
|
||||
"""
|
||||
module_name = os.path.basename(filename).split(".")[0]
|
||||
assignments = [node for node in tree.body if isinstance(node, ast.Assign)]
|
||||
for assign in assignments:
|
||||
try:
|
||||
if assign.targets[0].id == "__virtualname__":
|
||||
module_name = assign.value.s
|
||||
except AttributeError:
|
||||
pass
|
||||
return module_name
|
||||
|
||||
|
||||
def _get_func_aliases(tree):
|
||||
"""
|
||||
Get __func_alias__ dict for mapping function names
|
||||
"""
|
||||
fun_aliases = {}
|
||||
assignments = [node for node in tree.body if isinstance(node, ast.Assign)]
|
||||
for assign in assignments:
|
||||
try:
|
||||
if assign.targets[0].id == "__func_alias__":
|
||||
for key, value in zip_longest(assign.value.keys, assign.value.values):
|
||||
fun_aliases.update({key.s: value.s})
|
||||
except AttributeError:
|
||||
pass
|
||||
return fun_aliases
|
||||
|
||||
|
||||
def _get_args(function):
|
||||
"""
|
||||
Given a function def, returns arguments and defaults
|
||||
"""
|
||||
# Generate list of arguments
|
||||
arg_strings = []
|
||||
list_of_arguments = function.args.args
|
||||
if list_of_arguments:
|
||||
for arg in list_of_arguments:
|
||||
arg_strings.append(arg.arg)
|
||||
|
||||
# Generate list of arg defaults
|
||||
# Values are only returned for populated items
|
||||
arg_default_strings = []
|
||||
list_arg_defaults = function.args.defaults
|
||||
if list_arg_defaults:
|
||||
for arg_default in list_arg_defaults:
|
||||
if isinstance(arg_default, ast.NameConstant):
|
||||
arg_default_strings.append(arg_default.value)
|
||||
elif isinstance(arg_default, ast.Str):
|
||||
arg_default_strings.append(arg_default.s)
|
||||
elif isinstance(arg_default, ast.Num):
|
||||
arg_default_strings.append(arg_default.n)
|
||||
|
||||
# Since only some args may have default values, need to zip in reverse order
|
||||
backwards_args = OrderedDict(
|
||||
zip_longest(reversed(arg_strings), reversed(arg_default_strings))
|
||||
)
|
||||
ordered_args = OrderedDict(reversed(list(backwards_args.items())))
|
||||
|
||||
try:
|
||||
ordered_args["args"] = function.args.vararg.arg
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
ordered_args["kwargs"] = function.args.kwarg.arg
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return ordered_args
|
||||
|
||||
|
||||
def _mods_with_args(module_py, names_only):
|
||||
"""
|
||||
Start ast parsing of modules
|
||||
"""
|
||||
ret = {}
|
||||
with salt.utils.files.fopen(module_py, "r", encoding="utf8") as cur_file:
|
||||
tree = ast.parse(cur_file.read())
|
||||
module_name = _get_module_name(tree, module_py)
|
||||
fun_aliases = _get_func_aliases(tree)
|
||||
|
||||
functions = [node for node in tree.body if isinstance(node, ast.FunctionDef)]
|
||||
func_list = []
|
||||
for fn in functions:
|
||||
if not fn.name.startswith("_"):
|
||||
function_name = fn.name
|
||||
if fun_aliases:
|
||||
# Translate name to __func_alias__ version
|
||||
for k, v in fun_aliases.items():
|
||||
if fn.name == k:
|
||||
function_name = v
|
||||
args = _get_args(fn)
|
||||
if names_only:
|
||||
func_list.append(function_name)
|
||||
else:
|
||||
fun_entry = {}
|
||||
fun_entry[function_name] = args
|
||||
func_list.append(fun_entry)
|
||||
ret[module_name] = func_list
|
||||
return ret
|
||||
|
||||
|
||||
def _modules_and_args(name=False, type="states", names_only=False):
|
||||
"""
|
||||
Determine if modules/states directories or files are requested
|
||||
"""
|
||||
ret = {}
|
||||
dirs = []
|
||||
|
||||
if type == "modules":
|
||||
dirs.append(os.path.join(__opts__["extension_modules"], "modules"))
|
||||
dirs.append(os.path.join(__grains__["saltpath"], "modules"))
|
||||
elif type == "states":
|
||||
dirs.append(os.path.join(__opts__["extension_modules"], "states"))
|
||||
dirs.append(os.path.join(__grains__["saltpath"], "states"))
|
||||
|
||||
if name:
|
||||
for dir in dirs:
|
||||
# Process custom dirs first so custom results are returned
|
||||
if os.path.exists(os.path.join(dir, name + ".py")):
|
||||
return _mods_with_args(os.path.join(dir, name + ".py"), names_only)
|
||||
else:
|
||||
for dir in reversed(dirs):
|
||||
# Process custom dirs last so they are displayed
|
||||
try:
|
||||
for module_py in os.listdir(dir):
|
||||
if module_py.endswith(".py") and module_py != "__init__.py":
|
||||
ret.update(
|
||||
_mods_with_args(os.path.join(dir, module_py), names_only)
|
||||
)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return ret
|
||||
|
||||
|
||||
def list_states(name=False, names_only=False):
|
||||
"""
|
||||
Walk the Salt install tree for state modules and return a
|
||||
dictionary or a list of their functions as well as their arguments.
|
||||
|
||||
:param name: specify a specific module to list. If not specified, all modules will be listed.
|
||||
:param names_only: Return only a list of the callable functions instead of a dictionary with arguments
|
||||
|
||||
CLI Example:
|
||||
(example truncated for brevity)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion baredoc.modules_and_args
|
||||
|
||||
myminion:
|
||||
----------
|
||||
[...]
|
||||
at:
|
||||
- present:
|
||||
name: null
|
||||
timespec: null
|
||||
tag: null
|
||||
user: null
|
||||
job: null
|
||||
unique_tag: false
|
||||
- absent:
|
||||
name: null
|
||||
jobid: null
|
||||
kwargs: kwargs
|
||||
- watch:
|
||||
name: null
|
||||
timespec: null
|
||||
tag: null
|
||||
user: null
|
||||
job: null
|
||||
unique_tag: false
|
||||
- mod_watch:
|
||||
name: null
|
||||
kwargs: kwargs
|
||||
[...]
|
||||
"""
|
||||
ret = _modules_and_args(name, type="states", names_only=names_only)
|
||||
if names_only:
|
||||
return OrderedDict(sorted(ret.items()))
|
||||
else:
|
||||
return OrderedDict(sorted(ret.items()))
|
||||
|
||||
|
||||
def list_modules(name=False, names_only=False):
|
||||
"""
|
||||
Walk the Salt install tree for execution modules and return a
|
||||
dictionary or a list of their functions as well as their arguments.
|
||||
|
||||
:param name: specify a specific module to list. If not specified, all modules will be listed.
|
||||
:param names_only: Return only a list of the callable functions instead of a dictionary with arguments
|
||||
|
||||
CLI Example:
|
||||
(example truncated for brevity)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion baredoc.modules_and_args
|
||||
|
||||
myminion:
|
||||
----------
|
||||
[...]
|
||||
at:
|
||||
- atq:
|
||||
tag: null
|
||||
- atrm:
|
||||
args: args
|
||||
- at:
|
||||
args: args
|
||||
kwargs: kwargs
|
||||
- atc:
|
||||
jobid: null
|
||||
- jobcheck:
|
||||
kwargs: kwargs
|
||||
[...]
|
||||
"""
|
||||
ret = _modules_and_args(name, type="modules", names_only=names_only)
|
||||
if names_only:
|
||||
return OrderedDict(sorted(ret.items()))
|
||||
else:
|
||||
return OrderedDict(sorted(ret.items()))
|
|
@ -571,7 +571,7 @@ def valid():
|
|||
if function_name:
|
||||
mine_data[function_alias] = {
|
||||
function_name: function_args
|
||||
+ [{key, value} for key, value in six.iteritems(function_kwargs)]
|
||||
+ [{key: value} for key, value in six.iteritems(function_kwargs)]
|
||||
}
|
||||
else:
|
||||
mine_data[function_alias] = function_data
|
||||
|
|
|
@ -303,14 +303,14 @@ from __future__ import absolute_import, print_function, unicode_literals
|
|||
|
||||
import copy
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
import time
|
||||
|
||||
# Import Salt libs
|
||||
import salt.client
|
||||
import salt.exceptions
|
||||
import salt.utils.data
|
||||
|
||||
# Import Salt libs
|
||||
import salt.utils.files
|
||||
import salt.utils.functools
|
||||
import salt.utils.path
|
||||
|
@ -323,6 +323,8 @@ from salt.utils.odict import OrderedDict
|
|||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
global_scheck = None
|
||||
|
||||
__virtualname__ = "saltcheck"
|
||||
|
||||
|
||||
|
@ -440,13 +442,25 @@ def run_state_tests(state, saltenv=None, check_all=False):
|
|||
.. code-block:: bash
|
||||
|
||||
salt '*' saltcheck.run_state_tests postfix,common
|
||||
|
||||
Tests will be run in parallel by adding "saltcheck_parallel: True" in minion config.
|
||||
When enabled, saltcheck will use up to the number of cores detected. This can be limited
|
||||
by setting the "saltcheck_processes" value to an integer to set the maximum number
|
||||
of parallel processes.
|
||||
"""
|
||||
if not saltenv:
|
||||
if "saltenv" in __opts__ and __opts__["saltenv"]:
|
||||
saltenv = __opts__["saltenv"]
|
||||
else:
|
||||
saltenv = "base"
|
||||
scheck = SaltCheck(saltenv)
|
||||
|
||||
# Use global scheck variable for reuse in each multiprocess
|
||||
global global_scheck
|
||||
global_scheck = SaltCheck(saltenv)
|
||||
|
||||
parallel = __salt__["config.get"]("saltcheck_parallel")
|
||||
num_proc = __salt__["config.get"]("saltcheck_processes")
|
||||
|
||||
stl = StateTestLoader(saltenv)
|
||||
results = OrderedDict()
|
||||
sls_list = salt.utils.args.split_input(state)
|
||||
|
@ -454,15 +468,62 @@ def run_state_tests(state, saltenv=None, check_all=False):
|
|||
stl.add_test_files_for_sls(state_name, check_all)
|
||||
stl.load_test_suite()
|
||||
results_dict = OrderedDict()
|
||||
for key, value in stl.test_dict.items():
|
||||
result = scheck.run_test(value)
|
||||
results_dict[key] = result
|
||||
|
||||
# Check for situations to disable parallization
|
||||
if parallel:
|
||||
if type(num_proc) == float:
|
||||
num_proc = int(num_proc)
|
||||
|
||||
if multiprocessing.cpu_count() < 2:
|
||||
parallel = False
|
||||
log.debug("Only 1 CPU. Disabling parallization.")
|
||||
elif num_proc == 1:
|
||||
# Don't bother with multiprocessing overhead
|
||||
parallel = False
|
||||
log.debug("Configuration limited to 1 CPU. Disabling parallization.")
|
||||
else:
|
||||
for items in stl.test_dict.values():
|
||||
if "state.apply" in items.get("module_and_function", []):
|
||||
# Multiprocessing doesn't ensure ordering, which state.apply
|
||||
# might require
|
||||
parallel = False
|
||||
log.warning(
|
||||
"Tests include state.apply. Disabling parallization."
|
||||
)
|
||||
|
||||
if parallel:
|
||||
if num_proc:
|
||||
pool_size = num_proc
|
||||
else:
|
||||
pool_size = min(len(stl.test_dict), multiprocessing.cpu_count())
|
||||
log.debug("Running tests in parallel with %s processes", pool_size)
|
||||
presults = multiprocessing.Pool(pool_size).map(
|
||||
func=parallel_scheck, iterable=stl.test_dict.items()
|
||||
)
|
||||
# Remove list and form expected data structure
|
||||
for item in presults:
|
||||
for key, value in item.items():
|
||||
results_dict[key] = value
|
||||
else:
|
||||
for key, value in stl.test_dict.items():
|
||||
result = global_scheck.run_test(value)
|
||||
results_dict[key] = result
|
||||
|
||||
# If passed a duplicate state, don't overwrite with empty res
|
||||
if not results.get(state_name):
|
||||
# If passed a duplicate state, don't overwrite with empty res
|
||||
results[state_name] = results_dict
|
||||
return _generate_out_list(results)
|
||||
|
||||
|
||||
def parallel_scheck(data):
|
||||
"""triggers salt-call in parallel"""
|
||||
key = data[0]
|
||||
value = data[1]
|
||||
results = {}
|
||||
results[key] = global_scheck.run_test(value)
|
||||
return results
|
||||
|
||||
|
||||
run_state_tests_ssh = salt.utils.functools.alias_function(
|
||||
run_state_tests, "run_state_tests_ssh"
|
||||
)
|
||||
|
|
|
@ -109,6 +109,7 @@ def available(
|
|||
skip_reboot=False,
|
||||
categories=None,
|
||||
severities=None,
|
||||
online=True,
|
||||
):
|
||||
"""
|
||||
.. versionadded:: 2017.7.0
|
||||
|
@ -125,8 +126,8 @@ def available(
|
|||
Include driver updates in the results. Default is ``True``
|
||||
|
||||
summary (bool):
|
||||
- True: Return a summary of updates available for each category.
|
||||
- False (default): Return a detailed list of available updates.
|
||||
- ``True``: Return a summary of updates available for each category.
|
||||
- ``False`` (default): Return a detailed list of available updates.
|
||||
|
||||
skip_installed (bool):
|
||||
Skip updates that are already installed. Default is ``True``
|
||||
|
@ -148,7 +149,7 @@ def available(
|
|||
|
||||
* Critical Updates
|
||||
* Definition Updates
|
||||
* Drivers (make sure you set drivers=True)
|
||||
* Drivers (make sure you set ``drivers=True``)
|
||||
* Feature Packs
|
||||
* Security Updates
|
||||
* Update Rollups
|
||||
|
@ -169,39 +170,48 @@ def available(
|
|||
* Critical
|
||||
* Important
|
||||
|
||||
online (bool):
|
||||
Tells the Windows Update Agent go online to update its local update
|
||||
database. ``True`` will go online. ``False`` will use the local
|
||||
update database as is. Default is ``True``
|
||||
|
||||
.. versionadded:: Sodium
|
||||
|
||||
Returns:
|
||||
|
||||
dict: Returns a dict containing either a summary or a list of updates:
|
||||
|
||||
.. code-block:: cfg
|
||||
|
||||
List of Updates:
|
||||
{'<GUID>': {'Title': <title>,
|
||||
'KB': <KB>,
|
||||
'GUID': <the globally unique identifier for the update>
|
||||
'Description': <description>,
|
||||
'Downloaded': <has the update been downloaded>,
|
||||
'Installed': <has the update been installed>,
|
||||
'Mandatory': <is the update mandatory>,
|
||||
'UserInput': <is user input required>,
|
||||
'EULAAccepted': <has the EULA been accepted>,
|
||||
'Severity': <update severity>,
|
||||
'NeedsReboot': <is the update installed and awaiting reboot>,
|
||||
'RebootBehavior': <will the update require a reboot>,
|
||||
'Categories': [ '<category 1>',
|
||||
'<category 2>',
|
||||
...]
|
||||
}
|
||||
}
|
||||
Dict of Updates:
|
||||
{'<GUID>': {
|
||||
'Title': <title>,
|
||||
'KB': <KB>,
|
||||
'GUID': <the globally unique identifier for the update>,
|
||||
'Description': <description>,
|
||||
'Downloaded': <has the update been downloaded>,
|
||||
'Installed': <has the update been installed>,
|
||||
'Mandatory': <is the update mandatory>,
|
||||
'UserInput': <is user input required>,
|
||||
'EULAAccepted': <has the EULA been accepted>,
|
||||
'Severity': <update severity>,
|
||||
'NeedsReboot': <is the update installed and awaiting reboot>,
|
||||
'RebootBehavior': <will the update require a reboot>,
|
||||
'Categories': [
|
||||
'<category 1>',
|
||||
'<category 2>',
|
||||
... ]
|
||||
}}
|
||||
|
||||
Summary of Updates:
|
||||
{'Total': <total number of updates returned>,
|
||||
'Available': <updates that are not downloaded or installed>,
|
||||
'Downloaded': <updates that are downloaded but not installed>,
|
||||
'Installed': <updates installed (usually 0 unless installed=True)>,
|
||||
'Categories': { <category 1>: <total for that category>,
|
||||
<category 2>: <total for category 2>,
|
||||
... }
|
||||
'Categories': {
|
||||
<category 1>: <total for that category>,
|
||||
<category 2>: <total for category 2>,
|
||||
... }
|
||||
}
|
||||
|
||||
CLI Examples:
|
||||
|
@ -228,7 +238,7 @@ def available(
|
|||
"""
|
||||
|
||||
# Create a Windows Update Agent instance
|
||||
wua = salt.utils.win_update.WindowsUpdateAgent()
|
||||
wua = salt.utils.win_update.WindowsUpdateAgent(online=online)
|
||||
|
||||
# Look for available
|
||||
updates = wua.available(
|
||||
|
@ -246,7 +256,7 @@ def available(
|
|||
return updates.summary() if summary else updates.list()
|
||||
|
||||
|
||||
def get(name, download=False, install=False):
|
||||
def get(name, download=False, install=False, online=True):
|
||||
"""
|
||||
.. versionadded:: 2017.7.0
|
||||
|
||||
|
@ -270,35 +280,44 @@ def get(name, download=False, install=False):
|
|||
first to see if the update exists, then set ``install=True`` to
|
||||
install the update.
|
||||
|
||||
online (bool):
|
||||
Tells the Windows Update Agent go online to update its local update
|
||||
database. ``True`` will go online. ``False`` will use the local
|
||||
update database as is. Default is ``True``
|
||||
|
||||
.. versionadded:: Sodium
|
||||
|
||||
Returns:
|
||||
|
||||
dict: Returns a dict containing a list of updates that match the name if
|
||||
download and install are both set to False. Should usually be a single
|
||||
update, but can return multiple if a partial name is given.
|
||||
dict:
|
||||
Returns a dict containing a list of updates that match the name if
|
||||
download and install are both set to False. Should usually be a
|
||||
single update, but can return multiple if a partial name is given.
|
||||
|
||||
If download or install is set to true it will return the results of the
|
||||
operation.
|
||||
|
||||
.. code-block:: cfg
|
||||
|
||||
List of Updates:
|
||||
{'<GUID>': {'Title': <title>,
|
||||
'KB': <KB>,
|
||||
'GUID': <the globally unique identifier for the update>
|
||||
'Description': <description>,
|
||||
'Downloaded': <has the update been downloaded>,
|
||||
'Installed': <has the update been installed>,
|
||||
'Mandatory': <is the update mandatory>,
|
||||
'UserInput': <is user input required>,
|
||||
'EULAAccepted': <has the EULA been accepted>,
|
||||
'Severity': <update severity>,
|
||||
'NeedsReboot': <is the update installed and awaiting reboot>,
|
||||
'RebootBehavior': <will the update require a reboot>,
|
||||
'Categories': [ '<category 1>',
|
||||
'<category 2>',
|
||||
...]
|
||||
}
|
||||
}
|
||||
Dict of Updates:
|
||||
{'<GUID>': {
|
||||
'Title': <title>,
|
||||
'KB': <KB>,
|
||||
'GUID': <the globally unique identifier for the update>,
|
||||
'Description': <description>,
|
||||
'Downloaded': <has the update been downloaded>,
|
||||
'Installed': <has the update been installed>,
|
||||
'Mandatory': <is the update mandatory>,
|
||||
'UserInput': <is user input required>,
|
||||
'EULAAccepted': <has the EULA been accepted>,
|
||||
'Severity': <update severity>,
|
||||
'NeedsReboot': <is the update installed and awaiting reboot>,
|
||||
'RebootBehavior': <will the update require a reboot>,
|
||||
'Categories': [
|
||||
'<category 1>',
|
||||
'<category 2>',
|
||||
... ]
|
||||
}}
|
||||
|
||||
CLI Examples:
|
||||
|
||||
|
@ -320,7 +339,7 @@ def get(name, download=False, install=False):
|
|||
salt '*' win_wua.get 'Microsoft Camera Codec Pack'
|
||||
"""
|
||||
# Create a Windows Update Agent instance
|
||||
wua = salt.utils.win_update.WindowsUpdateAgent()
|
||||
wua = salt.utils.win_update.WindowsUpdateAgent(online=online)
|
||||
|
||||
# Search for Update
|
||||
updates = wua.search(name)
|
||||
|
@ -347,12 +366,14 @@ def list(
|
|||
severities=None,
|
||||
download=False,
|
||||
install=False,
|
||||
online=True,
|
||||
):
|
||||
"""
|
||||
.. versionadded:: 2017.7.0
|
||||
|
||||
Returns a detailed list of available updates or a summary. If download or
|
||||
install is True the same list will be downloaded and/or installed.
|
||||
Returns a detailed list of available updates or a summary. If ``download``
|
||||
or ``install`` is ``True`` the same list will be downloaded and/or
|
||||
installed.
|
||||
|
||||
Args:
|
||||
|
||||
|
@ -363,8 +384,8 @@ def list(
|
|||
Include driver updates in the results. Default is ``False``
|
||||
|
||||
summary (bool):
|
||||
- True: Return a summary of updates available for each category.
|
||||
- False (default): Return a detailed list of available updates.
|
||||
- ``True``: Return a summary of updates available for each category.
|
||||
- ``False`` (default): Return a detailed list of available updates.
|
||||
|
||||
skip_installed (bool):
|
||||
Skip installed updates in the results. Default is ``True``
|
||||
|
@ -389,7 +410,7 @@ def list(
|
|||
|
||||
* Critical Updates
|
||||
* Definition Updates
|
||||
* Drivers (make sure you set drivers=True)
|
||||
* Drivers (make sure you set ``drivers=True``)
|
||||
* Feature Packs
|
||||
* Security Updates
|
||||
* Update Rollups
|
||||
|
@ -410,39 +431,48 @@ def list(
|
|||
* Critical
|
||||
* Important
|
||||
|
||||
online (bool):
|
||||
Tells the Windows Update Agent go online to update its local update
|
||||
database. ``True`` will go online. ``False`` will use the local
|
||||
update database as is. Default is ``True``
|
||||
|
||||
.. versionadded:: Sodium
|
||||
|
||||
Returns:
|
||||
|
||||
dict: Returns a dict containing either a summary or a list of updates:
|
||||
|
||||
.. code-block:: cfg
|
||||
|
||||
List of Updates:
|
||||
{'<GUID>': {'Title': <title>,
|
||||
'KB': <KB>,
|
||||
'GUID': <the globally unique identifier for the update>
|
||||
'Description': <description>,
|
||||
'Downloaded': <has the update been downloaded>,
|
||||
'Installed': <has the update been installed>,
|
||||
'Mandatory': <is the update mandatory>,
|
||||
'UserInput': <is user input required>,
|
||||
'EULAAccepted': <has the EULA been accepted>,
|
||||
'Severity': <update severity>,
|
||||
'NeedsReboot': <is the update installed and awaiting reboot>,
|
||||
'RebootBehavior': <will the update require a reboot>,
|
||||
'Categories': [ '<category 1>',
|
||||
'<category 2>',
|
||||
...]
|
||||
}
|
||||
}
|
||||
Dict of Updates:
|
||||
{'<GUID>': {
|
||||
'Title': <title>,
|
||||
'KB': <KB>,
|
||||
'GUID': <the globally unique identifier for the update>,
|
||||
'Description': <description>,
|
||||
'Downloaded': <has the update been downloaded>,
|
||||
'Installed': <has the update been installed>,
|
||||
'Mandatory': <is the update mandatory>,
|
||||
'UserInput': <is user input required>,
|
||||
'EULAAccepted': <has the EULA been accepted>,
|
||||
'Severity': <update severity>,
|
||||
'NeedsReboot': <is the update installed and awaiting reboot>,
|
||||
'RebootBehavior': <will the update require a reboot>,
|
||||
'Categories': [
|
||||
'<category 1>',
|
||||
'<category 2>',
|
||||
... ]
|
||||
}}
|
||||
|
||||
Summary of Updates:
|
||||
{'Total': <total number of updates returned>,
|
||||
'Available': <updates that are not downloaded or installed>,
|
||||
'Downloaded': <updates that are downloaded but not installed>,
|
||||
'Installed': <updates installed (usually 0 unless installed=True)>,
|
||||
'Categories': { <category 1>: <total for that category>,
|
||||
<category 2>: <total for category 2>,
|
||||
... }
|
||||
'Categories': {
|
||||
<category 1>: <total for that category>,
|
||||
<category 2>: <total for category 2>,
|
||||
... }
|
||||
}
|
||||
|
||||
CLI Examples:
|
||||
|
@ -468,7 +498,7 @@ def list(
|
|||
salt '*' win_wua.list categories=['Feature Packs','Windows 8.1'] summary=True
|
||||
"""
|
||||
# Create a Windows Update Agent instance
|
||||
wua = salt.utils.win_update.WindowsUpdateAgent()
|
||||
wua = salt.utils.win_update.WindowsUpdateAgent(online=online)
|
||||
|
||||
# Search for Update
|
||||
updates = wua.available(
|
||||
|
@ -495,12 +525,77 @@ def list(
|
|||
return ret
|
||||
|
||||
|
||||
def installed(summary=False, kbs_only=False):
|
||||
"""
|
||||
.. versionadded:: Sodium
|
||||
|
||||
Get a list of all updates that are currently installed on the system.
|
||||
|
||||
.. note::
|
||||
|
||||
This list may not necessarily match the Update History on the machine.
|
||||
This will only show the updates that apply to the current build of
|
||||
Windows. So, for example, the system may have shipped with Windows 10
|
||||
Build 1607. That machine received updates to the 1607 build. Later the
|
||||
machine was upgraded to a newer feature release, 1803 for example. Then
|
||||
more updates were applied. This will only return the updates applied to
|
||||
the 1803 build and not those applied when the system was at the 1607
|
||||
build.
|
||||
|
||||
Args:
|
||||
|
||||
summary (bool):
|
||||
Return a summary instead of a detailed list of updates. ``True``
|
||||
will return a Summary, ``False`` will return a detailed list of
|
||||
installed updates. Default is ``False``
|
||||
|
||||
kbs_only (bool):
|
||||
Only return a list of KBs installed on the system. If this parameter
|
||||
is passed, the ``summary`` parameter will be ignored. Default is
|
||||
``False``
|
||||
|
||||
Returns:
|
||||
dict:
|
||||
Returns a dictionary of either a Summary or a detailed list of
|
||||
updates installed on the system when ``kbs_only=False``
|
||||
|
||||
list:
|
||||
Returns a list of KBs installed on the system when ``kbs_only=True``
|
||||
|
||||
CLI Examples:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Get a detailed list of all applicable updates installed on the system
|
||||
salt '*' win_wua.installed
|
||||
|
||||
# Get a summary of all applicable updates installed on the system
|
||||
salt '*' win_wua.installed summary=True
|
||||
|
||||
# Get a simple list of KBs installed on the system
|
||||
salt '*' win_wua.installed kbs_only=True
|
||||
"""
|
||||
# Create a Windows Update Agent instance. Since we're only listing installed
|
||||
# updates, there's no need to go online to update the Windows Update db
|
||||
wua = salt.utils.win_update.WindowsUpdateAgent(online=False)
|
||||
updates = wua.installed() # Get installed Updates objects
|
||||
results = updates.list() # Convert to list
|
||||
|
||||
if kbs_only:
|
||||
list_kbs = set()
|
||||
for item in results:
|
||||
list_kbs.update(results[item]["KBs"])
|
||||
return sorted(list_kbs)
|
||||
|
||||
return updates.summary() if summary else results
|
||||
|
||||
|
||||
def download(names):
|
||||
"""
|
||||
.. versionadded:: 2017.7.0
|
||||
|
||||
Downloads updates that match the list of passed identifiers. It's easier to
|
||||
use this function by using list_updates and setting install=True.
|
||||
use this function by using list_updates and setting ``download=True``.
|
||||
|
||||
Args:
|
||||
|
||||
|
@ -509,15 +604,16 @@ def download(names):
|
|||
combination of GUIDs, KB numbers, or names. GUIDs or KBs are
|
||||
preferred.
|
||||
|
||||
.. note::
|
||||
An error will be raised if there are more results than there are items
|
||||
in the names parameter
|
||||
.. note::
|
||||
|
||||
An error will be raised if there are more results than there are
|
||||
items in the names parameter
|
||||
|
||||
Returns:
|
||||
|
||||
dict: A dictionary containing the details about the downloaded updates
|
||||
|
||||
CLI Examples:
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -542,7 +638,7 @@ def download(names):
|
|||
|
||||
if updates.count() > len(names):
|
||||
raise CommandExecutionError(
|
||||
"Multiple updates found, names need to be " "more specific"
|
||||
"Multiple updates found, names need to be more specific"
|
||||
)
|
||||
|
||||
return wua.download(updates)
|
||||
|
@ -553,7 +649,7 @@ def install(names):
|
|||
.. versionadded:: 2017.7.0
|
||||
|
||||
Installs updates that match the list of identifiers. It may be easier to use
|
||||
the list_updates function and set install=True.
|
||||
the list_updates function and set ``install=True``.
|
||||
|
||||
Args:
|
||||
|
||||
|
@ -563,6 +659,7 @@ def install(names):
|
|||
preferred.
|
||||
|
||||
.. note::
|
||||
|
||||
An error will be raised if there are more results than there are items
|
||||
in the names parameter
|
||||
|
||||
|
@ -595,7 +692,7 @@ def install(names):
|
|||
|
||||
if updates.count() > len(names):
|
||||
raise CommandExecutionError(
|
||||
"Multiple updates found, names need to be " "more specific"
|
||||
"Multiple updates found, names need to be more specific"
|
||||
)
|
||||
|
||||
return wua.install(updates)
|
||||
|
@ -672,7 +769,8 @@ def set_wu_settings(
|
|||
Number from 1 to 4 indicating the update level:
|
||||
|
||||
1. Never check for updates
|
||||
2. Check for updates but let me choose whether to download and install them
|
||||
2. Check for updates but let me choose whether to download and
|
||||
install them
|
||||
3. Download updates but let me choose whether to install them
|
||||
4. Install updates automatically
|
||||
|
||||
|
@ -804,7 +902,7 @@ def set_wu_settings(
|
|||
}
|
||||
if day not in days:
|
||||
ret["Comment"] = (
|
||||
"Day needs to be one of the following: Everyday,"
|
||||
"Day needs to be one of the following: Everyday, "
|
||||
"Monday, Tuesday, Wednesday, Thursday, Friday, "
|
||||
"Saturday"
|
||||
)
|
||||
|
@ -824,15 +922,15 @@ def set_wu_settings(
|
|||
# treat it as an integer
|
||||
if not isinstance(time, six.string_types):
|
||||
ret["Comment"] = (
|
||||
"Time argument needs to be a string; it may need to"
|
||||
"Time argument needs to be a string; it may need to "
|
||||
"be quoted. Passed {0}. Time not set.".format(time)
|
||||
)
|
||||
ret["Success"] = False
|
||||
# Check for colon in the time
|
||||
elif ":" not in time:
|
||||
ret["Comment"] = (
|
||||
"Time argument needs to be in 00:00 format."
|
||||
" Passed {0}. Time not set.".format(time)
|
||||
"Time argument needs to be in 00:00 format. "
|
||||
"Passed {0}. Time not set.".format(time)
|
||||
)
|
||||
ret["Success"] = False
|
||||
else:
|
||||
|
@ -906,35 +1004,46 @@ def get_wu_settings():
|
|||
Featured Updates:
|
||||
Boolean value that indicates whether to display notifications for
|
||||
featured updates.
|
||||
|
||||
Group Policy Required (Read-only):
|
||||
Boolean value that indicates whether Group Policy requires the
|
||||
Automatic Updates service.
|
||||
|
||||
Microsoft Update:
|
||||
Boolean value that indicates whether to turn on Microsoft Update for
|
||||
other Microsoft Products
|
||||
|
||||
Needs Reboot:
|
||||
Boolean value that indicates whether the machine is in a reboot
|
||||
pending state.
|
||||
|
||||
Non Admins Elevated:
|
||||
Boolean value that indicates whether non-administrators can perform
|
||||
some update-related actions without administrator approval.
|
||||
|
||||
Notification Level:
|
||||
|
||||
Number 1 to 4 indicating the update level:
|
||||
|
||||
1. Never check for updates
|
||||
2. Check for updates but let me choose whether to download and
|
||||
install them
|
||||
3. Download updates but let me choose whether to install them
|
||||
4. Install updates automatically
|
||||
|
||||
Read Only (Read-only):
|
||||
Boolean value that indicates whether the Automatic Update
|
||||
settings are read-only.
|
||||
|
||||
Recommended Updates:
|
||||
Boolean value that indicates whether to include optional or
|
||||
recommended updates when a search for updates and installation of
|
||||
updates is performed.
|
||||
|
||||
Scheduled Day:
|
||||
Days of the week on which Automatic Updates installs or uninstalls
|
||||
updates.
|
||||
|
||||
Scheduled Time:
|
||||
Time at which Automatic Updates installs or uninstalls updates.
|
||||
|
||||
|
@ -1019,7 +1128,7 @@ def get_needs_reboot():
|
|||
|
||||
Returns:
|
||||
|
||||
bool: True if the system requires a reboot, otherwise False
|
||||
bool: ``True`` if the system requires a reboot, otherwise ``False``
|
||||
|
||||
CLI Examples:
|
||||
|
||||
|
|
|
@ -2453,10 +2453,10 @@ def group_list():
|
|||
return ret
|
||||
|
||||
|
||||
def group_info(name, expand=False):
|
||||
def group_info(name, expand=False, ignore_groups=None):
|
||||
"""
|
||||
.. versionadded:: 2014.1.0
|
||||
.. versionchanged:: 2016.3.0,2015.8.4,2015.5.10
|
||||
.. versionchanged:: Sodium,2016.3.0,2015.8.4,2015.5.10
|
||||
The return data has changed. A new key ``type`` has been added to
|
||||
distinguish environment groups from package groups. Also, keys for the
|
||||
group name and group ID have been added. The ``mandatory packages``,
|
||||
|
@ -2477,6 +2477,13 @@ def group_info(name, expand=False):
|
|||
|
||||
.. versionadded:: 2016.3.0
|
||||
|
||||
ignore_groups : None
|
||||
This parameter can be used to pass a list of groups to ignore when
|
||||
expanding subgroups. It is used during recursion in order to prevent
|
||||
expanding the same group multiple times.
|
||||
|
||||
.. versionadded:: Sodium
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
@ -2511,6 +2518,7 @@ def group_info(name, expand=False):
|
|||
|
||||
ret["description"] = g_info.get("description", "")
|
||||
|
||||
completed_groups = ignore_groups or []
|
||||
pkgtypes_capturegroup = "(" + "|".join(pkgtypes) + ")"
|
||||
for pkgtype in pkgtypes:
|
||||
target_found = False
|
||||
|
@ -2530,7 +2538,19 @@ def group_info(name, expand=False):
|
|||
continue
|
||||
if target_found:
|
||||
if expand and ret["type"] == "environment group":
|
||||
expanded = group_info(line, expand=True)
|
||||
if not line or line in completed_groups:
|
||||
continue
|
||||
log.trace(
|
||||
'Adding group "%s" to completed list: %s',
|
||||
line,
|
||||
completed_groups,
|
||||
)
|
||||
completed_groups.append(line)
|
||||
# Using the @ prefix on the group here in order to prevent multiple matches
|
||||
# being returned, such as with gnome-desktop
|
||||
expanded = group_info(
|
||||
"@" + line, expand=True, ignore_groups=completed_groups
|
||||
)
|
||||
# Don't shadow the pkgtype variable from the outer loop
|
||||
for p_type in pkgtypes:
|
||||
ret[p_type].update(set(expanded[p_type]))
|
||||
|
|
|
@ -1684,6 +1684,9 @@ class State(object):
|
|||
)
|
||||
extend = {}
|
||||
errors = []
|
||||
disabled_reqs = self.opts.get("disabled_requisites", [])
|
||||
if not isinstance(disabled_reqs, list):
|
||||
disabled_reqs = [disabled_reqs]
|
||||
for id_, body in six.iteritems(high):
|
||||
if not isinstance(body, dict):
|
||||
continue
|
||||
|
@ -1702,6 +1705,11 @@ class State(object):
|
|||
key = next(iter(arg))
|
||||
if key not in req_in:
|
||||
continue
|
||||
if key in disabled_reqs:
|
||||
log.warning(
|
||||
"The %s requisite has been disabled, Ignoring.", key
|
||||
)
|
||||
continue
|
||||
rkey = key.split("_")[0]
|
||||
items = arg[key]
|
||||
if isinstance(items, dict):
|
||||
|
@ -2543,6 +2551,9 @@ class State(object):
|
|||
Look into the running data to check the status of all requisite
|
||||
states
|
||||
"""
|
||||
disabled_reqs = self.opts.get("disabled_requisites", [])
|
||||
if not isinstance(disabled_reqs, list):
|
||||
disabled_reqs = [disabled_reqs]
|
||||
present = False
|
||||
# If mod_watch is not available make it a require
|
||||
if "watch" in low:
|
||||
|
@ -2598,6 +2609,11 @@ class State(object):
|
|||
reqs["prerequired"] = []
|
||||
for r_state in reqs:
|
||||
if r_state in low and low[r_state] is not None:
|
||||
if r_state in disabled_reqs:
|
||||
log.warning(
|
||||
"The %s requisite has been disabled, Ignoring.", r_state
|
||||
)
|
||||
continue
|
||||
for req in low[r_state]:
|
||||
if isinstance(req, six.string_types):
|
||||
req = {"id": req}
|
||||
|
|
|
@ -32,7 +32,7 @@ __virtualname__ = "win_update"
|
|||
|
||||
def __virtual__():
|
||||
if not salt.utils.platform.is_windows():
|
||||
return False, "win_update: Not available on Windows"
|
||||
return False, "win_update: Only available on Windows"
|
||||
if not HAS_PYWIN32:
|
||||
return False, "win_update: Missing pywin32"
|
||||
return __virtualname__
|
||||
|
@ -43,7 +43,8 @@ class Updates(object):
|
|||
Wrapper around the 'Microsoft.Update.UpdateColl' instance
|
||||
Adds the list and summary functions. For use by the WindowUpdateAgent class.
|
||||
|
||||
Usage:
|
||||
Code Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Create an instance
|
||||
|
@ -110,24 +111,25 @@ class Updates(object):
|
|||
|
||||
.. code-block:: cfg
|
||||
|
||||
List of Updates:
|
||||
{'<GUID>': {'Title': <title>,
|
||||
'KB': <KB>,
|
||||
'GUID': <the globally unique identifier for the update>
|
||||
'Description': <description>,
|
||||
'Downloaded': <has the update been downloaded>,
|
||||
'Installed': <has the update been installed>,
|
||||
'Mandatory': <is the update mandatory>,
|
||||
'UserInput': <is user input required>,
|
||||
'EULAAccepted': <has the EULA been accepted>,
|
||||
'Severity': <update severity>,
|
||||
'NeedsReboot': <is the update installed and awaiting reboot>,
|
||||
'RebootBehavior': <will the update require a reboot>,
|
||||
'Categories': [ '<category 1>',
|
||||
'<category 2>',
|
||||
...]
|
||||
}
|
||||
}
|
||||
Dict of Updates:
|
||||
{'<GUID>': {
|
||||
'Title': <title>,
|
||||
'KB': <KB>,
|
||||
'GUID': <the globally unique identifier for the update>,
|
||||
'Description': <description>,
|
||||
'Downloaded': <has the update been downloaded>,
|
||||
'Installed': <has the update been installed>,
|
||||
'Mandatory': <is the update mandatory>,
|
||||
'UserInput': <is user input required>,
|
||||
'EULAAccepted': <has the EULA been accepted>,
|
||||
'Severity': <update severity>,
|
||||
'NeedsReboot': <is the update installed and awaiting reboot>,
|
||||
'RebootBehavior': <will the update require a reboot>,
|
||||
'Categories': [
|
||||
'<category 1>',
|
||||
'<category 2>',
|
||||
... ]
|
||||
}}
|
||||
|
||||
Code Example:
|
||||
|
||||
|
@ -182,10 +184,12 @@ class Updates(object):
|
|||
'Available': <updates that are not downloaded or installed>,
|
||||
'Downloaded': <updates that are downloaded but not installed>,
|
||||
'Installed': <updates installed (usually 0 unless installed=True)>,
|
||||
'Categories': { <category 1>: <total for that category>,
|
||||
<category 2>: <total for category 2>,
|
||||
... }
|
||||
'Categories': {
|
||||
<category 1>: <total for that category>,
|
||||
<category 2>: <total for category 2>,
|
||||
... }
|
||||
}
|
||||
|
||||
Code Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -251,7 +255,6 @@ class Updates(object):
|
|||
class WindowsUpdateAgent(object):
|
||||
"""
|
||||
Class for working with the Windows update agent
|
||||
|
||||
"""
|
||||
|
||||
# Error codes found at the following site:
|
||||
|
@ -285,12 +288,21 @@ class WindowsUpdateAgent(object):
|
|||
-4292607995: "Reboot required: 0x00240005",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, online=True):
|
||||
"""
|
||||
Initialize the session and load all updates into the ``_updates``
|
||||
collection. This collection is used by the other class functions instead
|
||||
of querying Windows update (expensive).
|
||||
|
||||
Args:
|
||||
|
||||
online (bool):
|
||||
Tells the Windows Update Agent go online to update its local
|
||||
update database. ``True`` will go online. ``False`` will use the
|
||||
local update database as is. Default is ``True``
|
||||
|
||||
.. versionadded:: Sodium
|
||||
|
||||
Need to look at the possibility of loading this into ``__context__``
|
||||
"""
|
||||
# Initialize the PyCom system
|
||||
|
@ -302,7 +314,7 @@ class WindowsUpdateAgent(object):
|
|||
# Create Collection for Updates
|
||||
self._updates = win32com.client.Dispatch("Microsoft.Update.UpdateColl")
|
||||
|
||||
self.refresh()
|
||||
self.refresh(online=online)
|
||||
|
||||
def updates(self):
|
||||
"""
|
||||
|
@ -310,8 +322,12 @@ class WindowsUpdateAgent(object):
|
|||
Updates class to expose the list and summary functions.
|
||||
|
||||
Returns:
|
||||
Updates: An instance of the Updates class with all updates for the
|
||||
system.
|
||||
|
||||
Updates:
|
||||
An instance of the Updates class with all updates for the
|
||||
system.
|
||||
|
||||
Code Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -333,12 +349,21 @@ class WindowsUpdateAgent(object):
|
|||
|
||||
return updates
|
||||
|
||||
def refresh(self):
|
||||
def refresh(self, online=True):
|
||||
"""
|
||||
Refresh the contents of the ``_updates`` collection. This gets all
|
||||
updates in the Windows Update system and loads them into the collection.
|
||||
This is the part that is slow.
|
||||
|
||||
Args:
|
||||
|
||||
online (bool):
|
||||
Tells the Windows Update Agent go online to update its local
|
||||
update database. ``True`` will go online. ``False`` will use the
|
||||
local update database as is. Default is ``True``
|
||||
|
||||
.. versionadded:: Sodium
|
||||
|
||||
Code Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -352,6 +377,7 @@ class WindowsUpdateAgent(object):
|
|||
|
||||
# Create searcher object
|
||||
searcher = self._session.CreateUpdateSearcher()
|
||||
searcher.Online = online
|
||||
self._session.ClientApplicationID = "Salt: Load Updates"
|
||||
|
||||
# Load all updates into the updates collection
|
||||
|
@ -373,6 +399,32 @@ class WindowsUpdateAgent(object):
|
|||
|
||||
self._updates = results.Updates
|
||||
|
||||
def installed(self):
|
||||
"""
|
||||
Gets a list of all updates available on the system that have the
|
||||
``IsInstalled`` attribute set to ``True``.
|
||||
|
||||
Returns:
|
||||
|
||||
Updates: An instance of Updates with the results.
|
||||
|
||||
Code Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import salt.utils.win_update
|
||||
wua = salt.utils.win_update.WindowsUpdateAgent(online=False)
|
||||
installed_updates = wua.installed()
|
||||
"""
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa386099(v=vs.85).aspx
|
||||
updates = Updates()
|
||||
|
||||
for update in self._updates:
|
||||
if salt.utils.data.is_true(update.IsInstalled):
|
||||
updates.updates.Add(update)
|
||||
|
||||
return updates
|
||||
|
||||
def available(
|
||||
self,
|
||||
skip_hidden=True,
|
||||
|
@ -390,23 +442,27 @@ class WindowsUpdateAgent(object):
|
|||
|
||||
Args:
|
||||
|
||||
skip_hidden (bool): Skip hidden updates. Default is True
|
||||
skip_hidden (bool):
|
||||
Skip hidden updates. Default is ``True``
|
||||
|
||||
skip_installed (bool): Skip installed updates. Default is True
|
||||
skip_installed (bool):
|
||||
Skip installed updates. Default is ``True``
|
||||
|
||||
skip_mandatory (bool): Skip mandatory updates. Default is False
|
||||
skip_mandatory (bool):
|
||||
Skip mandatory updates. Default is ``False``
|
||||
|
||||
skip_reboot (bool): Skip updates that can or do require reboot.
|
||||
Default is False
|
||||
skip_reboot (bool):
|
||||
Skip updates that can or do require reboot. Default is ``False``
|
||||
|
||||
software (bool): Include software updates. Default is True
|
||||
software (bool):
|
||||
Include software updates. Default is ``True``
|
||||
|
||||
drivers (bool): Include driver updates. Default is True
|
||||
drivers (bool):
|
||||
Include driver updates. Default is ``True``
|
||||
|
||||
categories (list): Include updates that have these categories.
|
||||
Default is none (all categories).
|
||||
|
||||
Categories include the following:
|
||||
categories (list):
|
||||
Include updates that have these categories. Default is none
|
||||
(all categories). Categories include the following:
|
||||
|
||||
* Critical Updates
|
||||
* Definition Updates
|
||||
|
@ -422,16 +478,17 @@ class WindowsUpdateAgent(object):
|
|||
* Windows 8.1 and later drivers
|
||||
* Windows Defender
|
||||
|
||||
severities (list): Include updates that have these severities.
|
||||
Default is none (all severities).
|
||||
|
||||
Severities include the following:
|
||||
severities (list):
|
||||
Include updates that have these severities. Default is none
|
||||
(all severities). Severities include the following:
|
||||
|
||||
* Critical
|
||||
* Important
|
||||
|
||||
.. note:: All updates are either software or driver updates. If both
|
||||
``software`` and ``drivers`` is False, nothing will be returned.
|
||||
.. note::
|
||||
|
||||
All updates are either software or driver updates. If both
|
||||
``software`` and ``drivers`` is ``False``, nothing will be returned.
|
||||
|
||||
Returns:
|
||||
|
||||
|
@ -445,7 +502,7 @@ class WindowsUpdateAgent(object):
|
|||
wua = salt.utils.win_update.WindowsUpdateAgent()
|
||||
|
||||
# Gets all updates and shows a summary
|
||||
updates = wua.available
|
||||
updates = wua.available()
|
||||
updates.summary()
|
||||
|
||||
# Get a list of Critical updates
|
||||
|
@ -502,11 +559,11 @@ class WindowsUpdateAgent(object):
|
|||
|
||||
Args:
|
||||
|
||||
search_string (str, list): The search string to use to find the
|
||||
update. This can be the GUID or KB of the update (preferred). It can
|
||||
also be the full Title of the update or any part of the Title. A
|
||||
partial Title search is less specific and can return multiple
|
||||
results.
|
||||
search_string (str, list):
|
||||
The search string to use to find the update. This can be the
|
||||
GUID or KB of the update (preferred). It can also be the full
|
||||
Title of the update or any part of the Title. A partial Title
|
||||
search is less specific and can return multiple results.
|
||||
|
||||
Returns:
|
||||
Updates: An instance of Updates with the results of the search
|
||||
|
@ -568,8 +625,9 @@ class WindowsUpdateAgent(object):
|
|||
|
||||
Args:
|
||||
|
||||
updates (Updates): An instance of the Updates class containing a
|
||||
the updates to be downloaded.
|
||||
updates (Updates):
|
||||
An instance of the Updates class containing a the updates to be
|
||||
downloaded.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the results of the download
|
||||
|
@ -681,8 +739,9 @@ class WindowsUpdateAgent(object):
|
|||
|
||||
Args:
|
||||
|
||||
updates (Updates): An instance of the Updates class containing a
|
||||
the updates to be installed.
|
||||
updates (Updates):
|
||||
An instance of the Updates class containing a the updates to be
|
||||
installed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the results of the installation
|
||||
|
@ -789,19 +848,23 @@ class WindowsUpdateAgent(object):
|
|||
Uninstall the updates passed in the updates collection. Load the updates
|
||||
collection using the ``search`` or ``available`` functions.
|
||||
|
||||
.. note:: Starting with Windows 10 the Windows Update Agent is unable to
|
||||
uninstall updates. An ``Uninstall Not Allowed`` error is returned. If
|
||||
this error is encountered this function will instead attempt to use
|
||||
``dism.exe`` to perform the uninstallation. ``dism.exe`` may fail to
|
||||
to find the KB number for the package. In that case, removal will fail.
|
||||
.. note::
|
||||
|
||||
Starting with Windows 10 the Windows Update Agent is unable to
|
||||
uninstall updates. An ``Uninstall Not Allowed`` error is returned.
|
||||
If this error is encountered this function will instead attempt to
|
||||
use ``dism.exe`` to perform the un-installation. ``dism.exe`` may
|
||||
fail to to find the KB number for the package. In that case, removal
|
||||
will fail.
|
||||
|
||||
Args:
|
||||
|
||||
updates (Updates): An instance of the Updates class containing a
|
||||
the updates to be uninstalled.
|
||||
updates (Updates):
|
||||
An instance of the Updates class containing a the updates to be
|
||||
uninstalled.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the results of the uninstallation
|
||||
dict: A dictionary containing the results of the un-installation
|
||||
|
||||
Code Example:
|
||||
|
||||
|
@ -825,7 +888,7 @@ class WindowsUpdateAgent(object):
|
|||
return ret
|
||||
|
||||
installer = self._session.CreateUpdateInstaller()
|
||||
self._session.ClientApplicationID = "Salt: Install Update"
|
||||
self._session.ClientApplicationID = "Salt: Uninstall Update"
|
||||
with salt.utils.winapi.Com():
|
||||
uninstall_list = win32com.client.Dispatch("Microsoft.Update.UpdateColl")
|
||||
|
||||
|
@ -920,7 +983,7 @@ class WindowsUpdateAgent(object):
|
|||
log.debug("NeedsReboot: %s", ret["NeedsReboot"])
|
||||
|
||||
# Refresh the Updates Table
|
||||
self.refresh()
|
||||
self.refresh(online=False)
|
||||
|
||||
reboot = {0: "Never Reboot", 1: "Always Reboot", 2: "Poss Reboot"}
|
||||
|
||||
|
@ -941,7 +1004,7 @@ class WindowsUpdateAgent(object):
|
|||
|
||||
return ret
|
||||
|
||||
# Found a differenct exception, Raise error
|
||||
# Found a different exception, Raise error
|
||||
log.error("Uninstall Failed: %s", failure_code)
|
||||
raise CommandExecutionError(failure_code)
|
||||
|
||||
|
@ -984,7 +1047,8 @@ class WindowsUpdateAgent(object):
|
|||
Internal function for running commands. Used by the uninstall function.
|
||||
|
||||
Args:
|
||||
cmd (str, list): The command to run
|
||||
cmd (str, list):
|
||||
The command to run
|
||||
|
||||
Returns:
|
||||
str: The stdout of the command
|
||||
|
@ -1012,7 +1076,7 @@ def needs_reboot():
|
|||
|
||||
Returns:
|
||||
|
||||
bool: True if the system requires a reboot, False if not
|
||||
bool: ``True`` if the system requires a reboot, ``False`` if not
|
||||
|
||||
CLI Examples:
|
||||
|
||||
|
|
76
tests/unit/modules/test_baredoc.py
Normal file
76
tests/unit/modules/test_baredoc.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
|
||||
# Import module
|
||||
import salt.modules.baredoc as baredoc
|
||||
from tests.support.mixins import LoaderModuleMockMixin
|
||||
from tests.support.runtests import RUNTIME_VARS
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from tests.support.unit import TestCase
|
||||
|
||||
|
||||
class BaredocTest(TestCase, LoaderModuleMockMixin):
|
||||
"""
|
||||
Validate baredoc module
|
||||
"""
|
||||
|
||||
def setup_loader_modules(self):
|
||||
return {
|
||||
baredoc: {
|
||||
"__opts__": {
|
||||
"extension_modules": os.path.join(RUNTIME_VARS.CODE_DIR, "salt"),
|
||||
},
|
||||
"__grains__": {"saltpath": os.path.join(RUNTIME_VARS.CODE_DIR, "salt")},
|
||||
}
|
||||
}
|
||||
|
||||
def test_baredoc_list_states(self):
|
||||
"""
|
||||
Test baredoc state module listing
|
||||
"""
|
||||
ret = baredoc.list_states(names_only=True)
|
||||
assert "value_present" in ret["xml"][0]
|
||||
|
||||
def test_baredoc_list_states_args(self):
|
||||
"""
|
||||
Test baredoc state listing with args
|
||||
"""
|
||||
ret = baredoc.list_states()
|
||||
assert "value_present" in ret["xml"][0]
|
||||
assert "xpath" in ret["xml"][0]["value_present"]
|
||||
|
||||
def test_baredoc_list_states_single(self):
|
||||
"""
|
||||
Test baredoc state listing single state module
|
||||
"""
|
||||
ret = baredoc.list_states("xml")
|
||||
assert "value_present" in ret["xml"][0]
|
||||
assert "xpath" in ret["xml"][0]["value_present"]
|
||||
|
||||
def test_baredoc_list_modules(self):
|
||||
"""
|
||||
test baredoc executiion module listing
|
||||
"""
|
||||
ret = baredoc.list_modules(names_only=True)
|
||||
assert "get_value" in ret["xml"][0]
|
||||
|
||||
def test_baredoc_list_modules_args(self):
|
||||
"""
|
||||
test baredoc execution module listing with args
|
||||
"""
|
||||
ret = baredoc.list_modules()
|
||||
assert "get_value" in ret["xml"][0]
|
||||
assert "file" in ret["xml"][0]["get_value"]
|
||||
|
||||
def test_baredoc_list_modules_single_and_alias(self):
|
||||
"""
|
||||
test baredoc single module listing
|
||||
"""
|
||||
ret = baredoc.list_modules("mdata")
|
||||
assert "put" in ret["mdata"][2]
|
||||
assert "keyname" in ret["mdata"][2]["put"]
|
|
@ -5,6 +5,8 @@
|
|||
# Import python libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import salt.modules.btrfs as btrfs
|
||||
|
||||
|
@ -17,7 +19,7 @@ from salt.exceptions import CommandExecutionError
|
|||
# Import Salt Testing Libs
|
||||
from tests.support.mixins import LoaderModuleMockMixin
|
||||
from tests.support.mock import MagicMock, mock_open, patch
|
||||
from tests.support.unit import TestCase, skipIf
|
||||
from tests.support.unit import TestCase
|
||||
|
||||
|
||||
class BtrfsTestCase(TestCase, LoaderModuleMockMixin):
|
||||
|
@ -415,7 +417,6 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin):
|
|||
subvolume_exists.return_value = True
|
||||
assert not btrfs.subvolume_create("var", dest="/mnt")
|
||||
|
||||
@skipIf(salt.utils.platform.is_windows(), "Skip on Windows")
|
||||
@patch("salt.modules.btrfs.subvolume_exists")
|
||||
def test_subvolume_create(self, subvolume_exists):
|
||||
"""
|
||||
|
@ -425,12 +426,13 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin):
|
|||
salt_mock = {
|
||||
"cmd.run_all": MagicMock(return_value={"recode": 0}),
|
||||
}
|
||||
expected_path = os.path.join("/mnt", "var")
|
||||
with patch.dict(btrfs.__salt__, salt_mock):
|
||||
assert btrfs.subvolume_create("var", dest="/mnt")
|
||||
subvolume_exists.assert_called_once()
|
||||
salt_mock["cmd.run_all"].assert_called_once()
|
||||
salt_mock["cmd.run_all"].assert_called_with(
|
||||
["btrfs", "subvolume", "create", "/mnt/var"]
|
||||
["btrfs", "subvolume", "create", expected_path]
|
||||
)
|
||||
|
||||
def test_subvolume_delete_fails_parameters(self):
|
||||
|
|
|
@ -441,7 +441,11 @@ class MineTestCase(TestCase, LoaderModuleMockMixin):
|
|||
"""
|
||||
config_mine_functions = {
|
||||
"network.ip_addrs": [],
|
||||
"kernel": [{"mine_function": "grains.get"}, "kernel"],
|
||||
"kernel": [
|
||||
{"mine_function": "grains.get"},
|
||||
"kernel",
|
||||
{"os": "win32", "v": "2018"},
|
||||
],
|
||||
"fubar": [{"mine_function": "does.not_exist"}],
|
||||
}
|
||||
with patch.dict(
|
||||
|
@ -452,9 +456,16 @@ class MineTestCase(TestCase, LoaderModuleMockMixin):
|
|||
"grains.get": lambda: True,
|
||||
},
|
||||
):
|
||||
ret = mine.valid()
|
||||
# list cant be made to set "dict can't be hashed" and order changes
|
||||
self.assertIsInstance(ret["kernel"]["grains.get"], list)
|
||||
self.assertEqual(len(ret["kernel"]["grains.get"]), 3)
|
||||
for item in ("kernel", {"os": "win32"}, {"v": "2018"}):
|
||||
self.assertTrue(item in ret["kernel"]["grains.get"])
|
||||
ret["kernel"]["grains.get"] = None
|
||||
|
||||
self.assertEqual(
|
||||
mine.valid(),
|
||||
{"network.ip_addrs": [], "kernel": {"grains.get": ["kernel"]}},
|
||||
ret, {"network.ip_addrs": [], "kernel": {"grains.get": None}}
|
||||
)
|
||||
|
||||
def test_get_docker(self):
|
||||
|
|
96
tests/unit/modules/test_win_wua.py
Normal file
96
tests/unit/modules/test_win_wua.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Test the win_wua execution module
|
||||
"""
|
||||
# Import Python Libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
# Import Salt Libs
|
||||
import salt.modules.win_wua as win_wua
|
||||
import salt.utils.platform
|
||||
import salt.utils.win_update
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from tests.support.mock import patch
|
||||
from tests.support.unit import TestCase, skipIf
|
||||
|
||||
UPDATES_LIST = {
|
||||
"ca3bb521-a8ea-4e26-a563-2ad6e3108b9a": {"KBs": ["KB4481252"]},
|
||||
"07609d43-d518-4e77-856e-d1b316d1b8a8": {"KBs": ["KB925673"]},
|
||||
"fbaa5360-a440-49d8-a3b6-0c4fc7ecaa19": {"KBs": ["KB4481252"]},
|
||||
"a873372b-7a5c-443c-8022-cd59a550bef4": {"KBs": ["KB3193497"]},
|
||||
"14075cbe-822e-4004-963b-f50e08d45563": {"KBs": ["KB4540723"]},
|
||||
"d931e99c-4dda-4d39-9905-0f6a73f7195f": {"KBs": ["KB3193497"]},
|
||||
"afda9e11-44a0-4602-9e9b-423af11ecaed": {"KBs": ["KB4541329"]},
|
||||
"a0f997b1-1abe-4a46-941f-b37f732f9fbd": {"KBs": ["KB3193497"]},
|
||||
"eac02b09-d745-4891-b80f-400e0e5e4b6d": {"KBs": ["KB4052623"]},
|
||||
"0689e74b-54d1-4f55-a916-96e3c737db90": {"KBs": ["KB890830"]},
|
||||
}
|
||||
UPDATES_SUMMARY = {"Installed": 10}
|
||||
|
||||
|
||||
class Updates(object):
|
||||
@staticmethod
|
||||
def list():
|
||||
return UPDATES_LIST
|
||||
|
||||
@staticmethod
|
||||
def summary():
|
||||
return UPDATES_SUMMARY
|
||||
|
||||
|
||||
@skipIf(not salt.utils.platform.is_windows(), "System is not Windows")
|
||||
class WinWuaInstalledTestCase(TestCase):
|
||||
"""
|
||||
Test the functions in the win_wua.installed function
|
||||
"""
|
||||
|
||||
def test_installed(self):
|
||||
"""
|
||||
Test installed function default
|
||||
"""
|
||||
expected = UPDATES_LIST
|
||||
with patch("salt.utils.winapi.Com", autospec=True), patch(
|
||||
"win32com.client.Dispatch", autospec=True
|
||||
), patch.object(
|
||||
salt.utils.win_update.WindowsUpdateAgent, "refresh", autospec=True
|
||||
), patch.object(
|
||||
salt.utils.win_update, "Updates", autospec=True, return_value=Updates()
|
||||
):
|
||||
result = win_wua.installed()
|
||||
self.assertDictEqual(result, expected)
|
||||
|
||||
def test_installed_summary(self):
|
||||
"""
|
||||
Test installed function with summary=True
|
||||
"""
|
||||
expected = UPDATES_SUMMARY
|
||||
# Remove all updates that are not installed
|
||||
with patch("salt.utils.winapi.Com", autospec=True), patch(
|
||||
"win32com.client.Dispatch", autospec=True
|
||||
), patch.object(
|
||||
salt.utils.win_update.WindowsUpdateAgent, "refresh", autospec=True
|
||||
), patch.object(
|
||||
salt.utils.win_update, "Updates", autospec=True, return_value=Updates()
|
||||
):
|
||||
result = win_wua.installed(summary=True)
|
||||
self.assertDictEqual(result, expected)
|
||||
|
||||
def test_installed_kbs_only(self):
|
||||
"""
|
||||
Test installed function with kbs_only=True
|
||||
"""
|
||||
expected = set()
|
||||
for update in UPDATES_LIST:
|
||||
expected.update(UPDATES_LIST[update]["KBs"])
|
||||
expected = sorted(expected)
|
||||
# Remove all updates that are not installed
|
||||
with patch("salt.utils.winapi.Com", autospec=True), patch(
|
||||
"win32com.client.Dispatch", autospec=True
|
||||
), patch.object(
|
||||
salt.utils.win_update.WindowsUpdateAgent, "refresh", autospec=True
|
||||
), patch.object(
|
||||
salt.utils.win_update, "Updates", autospec=True, return_value=Updates()
|
||||
):
|
||||
result = win_wua.installed(kbs_only=True)
|
||||
self.assertListEqual(result, expected)
|
|
@ -1301,64 +1301,243 @@ class YumTestCase(TestCase, LoaderModuleMockMixin):
|
|||
with pytest.raises(CommandExecutionError):
|
||||
yumpkg._get_yum_config()
|
||||
|
||||
def test_group_install(self):
|
||||
def test_group_info(self):
|
||||
"""
|
||||
Test group_install uses the correct keys from group_info and installs
|
||||
default and mandatory packages.
|
||||
Test yumpkg.group_info parsing
|
||||
"""
|
||||
groupinfo_output = """
|
||||
Group: Printing Client
|
||||
Group-Id: print-client
|
||||
Description: Tools for printing to a local printer or a remote print server.
|
||||
expected = {
|
||||
"conditional": [],
|
||||
"default": ["qgnomeplatform", "xdg-desktop-portal-gtk"],
|
||||
"description": "GNOME is a highly intuitive and user friendly desktop environment.",
|
||||
"group": "GNOME",
|
||||
"id": "gnome-desktop",
|
||||
"mandatory": [
|
||||
"NetworkManager-libreswan-gnome",
|
||||
"PackageKit-command-not-found",
|
||||
"PackageKit-gtk3-module",
|
||||
"abrt-desktop",
|
||||
"at-spi2-atk",
|
||||
"at-spi2-core",
|
||||
"avahi",
|
||||
"baobab",
|
||||
"caribou",
|
||||
"caribou-gtk2-module",
|
||||
"caribou-gtk3-module",
|
||||
"cheese",
|
||||
"chrome-gnome-shell",
|
||||
"compat-cheese314",
|
||||
"control-center",
|
||||
"dconf",
|
||||
"empathy",
|
||||
"eog",
|
||||
"evince",
|
||||
"evince-nautilus",
|
||||
"file-roller",
|
||||
"file-roller-nautilus",
|
||||
"firewall-config",
|
||||
"firstboot",
|
||||
"fprintd-pam",
|
||||
"gdm",
|
||||
"gedit",
|
||||
"glib-networking",
|
||||
"gnome-bluetooth",
|
||||
"gnome-boxes",
|
||||
"gnome-calculator",
|
||||
"gnome-classic-session",
|
||||
"gnome-clocks",
|
||||
"gnome-color-manager",
|
||||
"gnome-contacts",
|
||||
"gnome-dictionary",
|
||||
"gnome-disk-utility",
|
||||
"gnome-font-viewer",
|
||||
"gnome-getting-started-docs",
|
||||
"gnome-icon-theme",
|
||||
"gnome-icon-theme-extras",
|
||||
"gnome-icon-theme-symbolic",
|
||||
"gnome-initial-setup",
|
||||
"gnome-packagekit",
|
||||
"gnome-packagekit-updater",
|
||||
"gnome-screenshot",
|
||||
"gnome-session",
|
||||
"gnome-session-xsession",
|
||||
"gnome-settings-daemon",
|
||||
"gnome-shell",
|
||||
"gnome-software",
|
||||
"gnome-system-log",
|
||||
"gnome-system-monitor",
|
||||
"gnome-terminal",
|
||||
"gnome-terminal-nautilus",
|
||||
"gnome-themes-standard",
|
||||
"gnome-tweak-tool",
|
||||
"gnome-user-docs",
|
||||
"gnome-weather",
|
||||
"gucharmap",
|
||||
"gvfs-afc",
|
||||
"gvfs-afp",
|
||||
"gvfs-archive",
|
||||
"gvfs-fuse",
|
||||
"gvfs-goa",
|
||||
"gvfs-gphoto2",
|
||||
"gvfs-mtp",
|
||||
"gvfs-smb",
|
||||
"initial-setup-gui",
|
||||
"libcanberra-gtk2",
|
||||
"libcanberra-gtk3",
|
||||
"libproxy-mozjs",
|
||||
"librsvg2",
|
||||
"libsane-hpaio",
|
||||
"metacity",
|
||||
"mousetweaks",
|
||||
"nautilus",
|
||||
"nautilus-sendto",
|
||||
"nm-connection-editor",
|
||||
"orca",
|
||||
"redhat-access-gui",
|
||||
"sane-backends-drivers-scanners",
|
||||
"seahorse",
|
||||
"setroubleshoot",
|
||||
"sushi",
|
||||
"totem",
|
||||
"totem-nautilus",
|
||||
"vinagre",
|
||||
"vino",
|
||||
"xdg-user-dirs-gtk",
|
||||
"yelp",
|
||||
],
|
||||
"optional": [
|
||||
"",
|
||||
"alacarte",
|
||||
"dconf-editor",
|
||||
"dvgrab",
|
||||
"fonts-tweak-tool",
|
||||
"gconf-editor",
|
||||
"gedit-plugins",
|
||||
"gnote",
|
||||
"libappindicator-gtk3",
|
||||
"seahorse-nautilus",
|
||||
"seahorse-sharing",
|
||||
"vim-X11",
|
||||
"xguest",
|
||||
],
|
||||
"type": "package group",
|
||||
}
|
||||
cmd_out = """Group: GNOME
|
||||
Group-Id: gnome-desktop
|
||||
Description: GNOME is a highly intuitive and user friendly desktop environment.
|
||||
Mandatory Packages:
|
||||
+cups
|
||||
+cups-pk-helper
|
||||
+enscript
|
||||
+ghostscript-cups
|
||||
=NetworkManager-libreswan-gnome
|
||||
=PackageKit-command-not-found
|
||||
=PackageKit-gtk3-module
|
||||
abrt-desktop
|
||||
=at-spi2-atk
|
||||
=at-spi2-core
|
||||
=avahi
|
||||
=baobab
|
||||
-caribou
|
||||
-caribou-gtk2-module
|
||||
-caribou-gtk3-module
|
||||
=cheese
|
||||
=chrome-gnome-shell
|
||||
=compat-cheese314
|
||||
=control-center
|
||||
=dconf
|
||||
=empathy
|
||||
=eog
|
||||
=evince
|
||||
=evince-nautilus
|
||||
=file-roller
|
||||
=file-roller-nautilus
|
||||
=firewall-config
|
||||
=firstboot
|
||||
fprintd-pam
|
||||
=gdm
|
||||
=gedit
|
||||
=glib-networking
|
||||
=gnome-bluetooth
|
||||
=gnome-boxes
|
||||
=gnome-calculator
|
||||
=gnome-classic-session
|
||||
=gnome-clocks
|
||||
=gnome-color-manager
|
||||
=gnome-contacts
|
||||
=gnome-dictionary
|
||||
=gnome-disk-utility
|
||||
=gnome-font-viewer
|
||||
=gnome-getting-started-docs
|
||||
=gnome-icon-theme
|
||||
=gnome-icon-theme-extras
|
||||
=gnome-icon-theme-symbolic
|
||||
=gnome-initial-setup
|
||||
=gnome-packagekit
|
||||
=gnome-packagekit-updater
|
||||
=gnome-screenshot
|
||||
=gnome-session
|
||||
=gnome-session-xsession
|
||||
=gnome-settings-daemon
|
||||
=gnome-shell
|
||||
=gnome-software
|
||||
=gnome-system-log
|
||||
=gnome-system-monitor
|
||||
=gnome-terminal
|
||||
=gnome-terminal-nautilus
|
||||
=gnome-themes-standard
|
||||
=gnome-tweak-tool
|
||||
=gnome-user-docs
|
||||
=gnome-weather
|
||||
=gucharmap
|
||||
=gvfs-afc
|
||||
=gvfs-afp
|
||||
=gvfs-archive
|
||||
=gvfs-fuse
|
||||
=gvfs-goa
|
||||
=gvfs-gphoto2
|
||||
=gvfs-mtp
|
||||
=gvfs-smb
|
||||
initial-setup-gui
|
||||
=libcanberra-gtk2
|
||||
=libcanberra-gtk3
|
||||
=libproxy-mozjs
|
||||
=librsvg2
|
||||
=libsane-hpaio
|
||||
=metacity
|
||||
=mousetweaks
|
||||
=nautilus
|
||||
=nautilus-sendto
|
||||
=nm-connection-editor
|
||||
=orca
|
||||
-redhat-access-gui
|
||||
=sane-backends-drivers-scanners
|
||||
=seahorse
|
||||
=setroubleshoot
|
||||
=sushi
|
||||
=totem
|
||||
=totem-nautilus
|
||||
=vinagre
|
||||
=vino
|
||||
=xdg-user-dirs-gtk
|
||||
=yelp
|
||||
Default Packages:
|
||||
+colord
|
||||
+gutenprint
|
||||
+gutenprint-cups
|
||||
+hpijs
|
||||
+paps
|
||||
+pnm2ppa
|
||||
+python-smbc
|
||||
+system-config-printer
|
||||
+system-config-printer-udev
|
||||
=qgnomeplatform
|
||||
=xdg-desktop-portal-gtk
|
||||
Optional Packages:
|
||||
hplip
|
||||
hplip-gui
|
||||
samba-krb5-printing
|
||||
alacarte
|
||||
dconf-editor
|
||||
dvgrab
|
||||
fonts-tweak-tool
|
||||
gconf-editor
|
||||
gedit-plugins
|
||||
gnote
|
||||
libappindicator-gtk3
|
||||
seahorse-nautilus
|
||||
seahorse-sharing
|
||||
vim-X11
|
||||
xguest
|
||||
"""
|
||||
install = MagicMock()
|
||||
with patch.dict(
|
||||
yumpkg.__salt__,
|
||||
{"cmd.run_stdout": MagicMock(return_value=groupinfo_output)},
|
||||
yumpkg.__salt__, {"cmd.run_stdout": MagicMock(return_value=cmd_out)}
|
||||
):
|
||||
with patch.dict(yumpkg.__salt__, {"cmd.run": MagicMock(return_value="")}):
|
||||
with patch.dict(
|
||||
yumpkg.__salt__,
|
||||
{"pkg_resource.format_pkg_list": MagicMock(return_value={})},
|
||||
):
|
||||
with patch.object(yumpkg, "install", install):
|
||||
yumpkg.group_install("Printing Client")
|
||||
install.assert_called_once_with(
|
||||
pkgs=[
|
||||
"cups",
|
||||
"cups-pk-helper",
|
||||
"enscript",
|
||||
"ghostscript-cups",
|
||||
"colord",
|
||||
"gutenprint",
|
||||
"gutenprint-cups",
|
||||
"hpijs",
|
||||
"paps",
|
||||
"pnm2ppa",
|
||||
"python-smbc",
|
||||
"system-config-printer",
|
||||
"system-config-printer-udev",
|
||||
]
|
||||
)
|
||||
info = yumpkg.group_info("@gnome-desktop")
|
||||
self.assertDictEqual(info, expected)
|
||||
|
||||
|
||||
@skipIf(pytest is None, "PyTest is missing")
|
||||
|
|
|
@ -18,7 +18,6 @@ import textwrap
|
|||
# Import salt libs
|
||||
import salt.exceptions
|
||||
import salt.fileclient
|
||||
import salt.pillar
|
||||
import salt.utils.stringutils
|
||||
from salt.utils.files import fopen
|
||||
from tests.support.helpers import with_tempdir
|
||||
|
|
|
@ -274,6 +274,89 @@ class StateCompilerTestCase(TestCase, AdaptedConfigurationTestCaseMixin):
|
|||
with patch.object(state_obj, "_run_check", return_value=mock):
|
||||
self.assertDictContainsSubset(expected_result, state_obj.call(low_data))
|
||||
|
||||
def test_render_requisite_require_disabled(self):
|
||||
"""
|
||||
Test that the state compiler correctly deliver a rendering
|
||||
exception when a requisite cannot be resolved
|
||||
"""
|
||||
with patch("salt.state.State._gather_pillar") as state_patch:
|
||||
high_data = {
|
||||
"step_one": OrderedDict(
|
||||
[
|
||||
(
|
||||
"test",
|
||||
[
|
||||
OrderedDict(
|
||||
[("require", [OrderedDict([("test", "step_two")])])]
|
||||
),
|
||||
"succeed_with_changes",
|
||||
{"order": 10000},
|
||||
],
|
||||
),
|
||||
("__sls__", "test.disable_require"),
|
||||
("__env__", "base"),
|
||||
]
|
||||
),
|
||||
"step_two": {
|
||||
"test": ["succeed_with_changes", {"order": 10001}],
|
||||
"__env__": "base",
|
||||
"__sls__": "test.disable_require",
|
||||
},
|
||||
}
|
||||
|
||||
minion_opts = self.get_temp_config("minion")
|
||||
minion_opts["disabled_requisites"] = ["require"]
|
||||
state_obj = salt.state.State(minion_opts)
|
||||
ret = state_obj.call_high(high_data)
|
||||
run_num = ret["test_|-step_one_|-step_one_|-succeed_with_changes"][
|
||||
"__run_num__"
|
||||
]
|
||||
self.assertEqual(run_num, 0)
|
||||
|
||||
def test_render_requisite_require_in_disabled(self):
|
||||
"""
|
||||
Test that the state compiler correctly deliver a rendering
|
||||
exception when a requisite cannot be resolved
|
||||
"""
|
||||
with patch("salt.state.State._gather_pillar") as state_patch:
|
||||
high_data = {
|
||||
"step_one": {
|
||||
"test": ["succeed_with_changes", {"order": 10000}],
|
||||
"__env__": "base",
|
||||
"__sls__": "test.disable_require_in",
|
||||
},
|
||||
"step_two": OrderedDict(
|
||||
[
|
||||
(
|
||||
"test",
|
||||
[
|
||||
OrderedDict(
|
||||
[
|
||||
(
|
||||
"require_in",
|
||||
[OrderedDict([("test", "step_one")])],
|
||||
)
|
||||
]
|
||||
),
|
||||
"succeed_with_changes",
|
||||
{"order": 10001},
|
||||
],
|
||||
),
|
||||
("__sls__", "test.disable_require_in"),
|
||||
("__env__", "base"),
|
||||
]
|
||||
),
|
||||
}
|
||||
|
||||
minion_opts = self.get_temp_config("minion")
|
||||
minion_opts["disabled_requisites"] = ["require_in"]
|
||||
state_obj = salt.state.State(minion_opts)
|
||||
ret = state_obj.call_high(high_data)
|
||||
run_num = ret["test_|-step_one_|-step_one_|-succeed_with_changes"][
|
||||
"__run_num__"
|
||||
]
|
||||
self.assertEqual(run_num, 0)
|
||||
|
||||
|
||||
class HighStateTestCase(TestCase, AdaptedConfigurationTestCaseMixin):
|
||||
def setUp(self):
|
||||
|
|
92
tests/unit/utils/test_win_update.py
Normal file
92
tests/unit/utils/test_win_update.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Import Python Libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
# Import Salt Libs
|
||||
import salt.utils.platform
|
||||
import salt.utils.win_update as win_update
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from tests.support.mock import MagicMock, patch
|
||||
from tests.support.unit import TestCase, skipIf
|
||||
|
||||
|
||||
@skipIf(not salt.utils.platform.is_windows(), "System is not Windows")
|
||||
class WinUpdateTestCase(TestCase):
|
||||
"""
|
||||
Test cases for salt.utils.win_update
|
||||
"""
|
||||
|
||||
def test_installed_no_updates(self):
|
||||
"""
|
||||
Test installed when there are no updates on the system
|
||||
"""
|
||||
with patch("salt.utils.winapi.Com", autospec=True), patch(
|
||||
"win32com.client.Dispatch", autospec=True
|
||||
), patch.object(win_update.WindowsUpdateAgent, "refresh", autospec=True):
|
||||
wua = win_update.WindowsUpdateAgent(online=False)
|
||||
wua._updates = []
|
||||
|
||||
installed_updates = wua.installed()
|
||||
|
||||
assert installed_updates.updates.Add.call_count == 0
|
||||
|
||||
def test_installed_no_updates_installed(self):
|
||||
"""
|
||||
Test installed when there are no Installed updates on the system
|
||||
"""
|
||||
with patch("salt.utils.winapi.Com", autospec=True), patch(
|
||||
"win32com.client.Dispatch", autospec=True
|
||||
), patch.object(win_update.WindowsUpdateAgent, "refresh", autospec=True):
|
||||
wua = win_update.WindowsUpdateAgent(online=False)
|
||||
|
||||
wua._updates = [
|
||||
MagicMock(IsInstalled=False),
|
||||
MagicMock(IsInstalled=False),
|
||||
MagicMock(IsInstalled=False),
|
||||
]
|
||||
|
||||
installed_updates = wua.installed()
|
||||
|
||||
assert installed_updates.updates.Add.call_count == 0
|
||||
|
||||
def test_installed_updates_all_installed(self):
|
||||
"""
|
||||
Test installed when all updates on the system are Installed
|
||||
"""
|
||||
with patch("salt.utils.winapi.Com", autospec=True), patch(
|
||||
"win32com.client.Dispatch", autospec=True
|
||||
), patch.object(win_update.WindowsUpdateAgent, "refresh", autospec=True):
|
||||
wua = win_update.WindowsUpdateAgent(online=False)
|
||||
|
||||
wua._updates = [
|
||||
MagicMock(IsInstalled=True),
|
||||
MagicMock(IsInstalled=True),
|
||||
MagicMock(IsInstalled=True),
|
||||
]
|
||||
|
||||
installed_updates = wua.installed()
|
||||
|
||||
assert installed_updates.updates.Add.call_count == 3
|
||||
|
||||
def test_installed_updates_some_installed(self):
|
||||
"""
|
||||
Test installed when some updates are installed on the system
|
||||
"""
|
||||
with patch("salt.utils.winapi.Com", autospec=True), patch(
|
||||
"win32com.client.Dispatch", autospec=True
|
||||
), patch.object(win_update.WindowsUpdateAgent, "refresh", autospec=True):
|
||||
wua = win_update.WindowsUpdateAgent(online=False)
|
||||
|
||||
wua._updates = [
|
||||
MagicMock(IsInstalled=True),
|
||||
MagicMock(IsInstalled=False),
|
||||
MagicMock(IsInstalled=True),
|
||||
MagicMock(IsInstalled=False),
|
||||
MagicMock(IsInstalled=True),
|
||||
]
|
||||
|
||||
installed_updates = wua.installed()
|
||||
|
||||
assert installed_updates.updates.Add.call_count == 3
|
Loading…
Add table
Reference in a new issue