Merge branch 'master' into master-port-49214

This commit is contained in:
Joseph Hall 2020-04-23 10:36:32 -06:00 committed by GitHub
commit b6ffa5d323
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1323 additions and 228 deletions

View file

@ -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

View file

@ -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,

View file

@ -47,6 +47,7 @@ execution modules
azurearm_network
azurearm_resource
bamboohr
baredoc
bcache
beacons
bigip

View file

@ -0,0 +1,5 @@
salt.modules.baredoc module
===========================
.. automodule:: salt.modules.baredoc
:members:

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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
View 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()))

View file

@ -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

View file

@ -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"
)

View file

@ -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:

View file

@ -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]))

View file

@ -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}

View file

@ -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:

View 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"]

View file

@ -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):

View file

@ -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):

View 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)

View file

@ -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")

View file

@ -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

View file

@ -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):

View 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