Merge branch 'develop' into yumpkg-install-performance

This commit is contained in:
Glaberrd 2017-08-08 09:31:14 +02:00 committed by GitHub
commit 89c7ad0d93
53 changed files with 923 additions and 362 deletions

View File

@ -197,6 +197,7 @@ execution modules
keyboard
keystone
kmod
kubernetes
launchctl
layman
ldap3

View File

@ -0,0 +1,6 @@
=======================
salt.modules.kubernetes
=======================
.. automodule:: salt.modules.kubernetes
:members:

View File

@ -433,6 +433,29 @@ similar to the following:
return __virtualname__
return False
The ``__virtual__()`` function can return a ``True`` or ``False`` boolean, a tuple,
or a string. If it returns a ``True`` value, this ``__virtualname__`` module-level
attribute can be set as seen in the above example. This is the string that the module
should be referred to as.
When ``__virtual__()`` returns a tuple, the first item should be a boolean and the
second should be a string. This is typically done when the module should not load. The
first value of the tuple is ``False`` and the second is the error message to display
for why the module did not load.
For example:
.. code-block:: python
def __virtual__():
'''
Only load if git exists on the system
'''
if salt.utils.which('git') is None:
return (False,
'The git execution module cannot be loaded: git unavailable.')
else:
return True
Documentation
=============

View File

@ -136,6 +136,7 @@ state modules
keyboard
keystone
kmod
kubernetes
layman
ldap
libcloud_dns

View File

@ -0,0 +1,6 @@
=======================
salt.modules.kubernetes
=======================
.. automodule:: salt.modules.kubernetes
:members:

View File

@ -519,7 +519,8 @@ runas
.. versionadded:: 2017.7.0
The ``runas`` global option is used to set the user which will be used to run the command in the ``cmd.run`` module.
The ``runas`` global option is used to set the user which will be used to run
the command in the ``cmd.run`` module.
.. code-block:: yaml
@ -532,6 +533,26 @@ The ``runas`` global option is used to set the user which will be used to run th
In the above state, the pip command run by ``cmd.run`` will be run by the daniel user.
runas_password
~~~~~~~~~~~~~~
.. versionadded:: 2017.7.2
The ``runas_password`` global option is used to set the password used by the
runas global option. This is required by ``cmd.run`` on Windows when ``runas``
is specified. It will be set when ``runas_password`` is defined in the state.
.. code-block:: yaml
run_script:
cmd.run:
- name: Powershell -NonInteractive -ExecutionPolicy Bypass -File C:\\Temp\\script.ps1
- runas: frank
- runas_password: supersecret
In the above state, the Powershell script run by ``cmd.run`` will be run by the
frank user with the password ``supersecret``.
.. _requisites-require-in:
.. _requisites-watch-in:
.. _requisites-onchanges-in:

View File

@ -122,13 +122,12 @@ State Module Changes
# After
run_something:
module.run:
mymodule.something:
- mymodule.something:
- name: some name
- first_arg: one
- second_arg: two
- do_stuff: True
Since a lot of users are already using :py:func:`module.run
<salt.states.module.run>` states, this new behavior must currently be
explicitly turned on, to allow users to take their time updating their SLS
@ -136,6 +135,36 @@ State Module Changes
the next feature release of Salt (Oxygen) and the old usage will no longer be
supported at that time.
Another feature of the new :py:func:`module.run <salt.states.module.run>` is that
it allows calling many functions in a single batch, such as:
.. code-block:: yaml
run_something:
module.run:
- mymodule.function_without_parameters:
- mymodule.another_function:
- myparam
- my_other_param
In a rare case that you have a function that needs to be called several times but
with the different parameters, an additional feature of "tagging" is to the
rescue. In order to tag a function, use a colon delimeter. For example:
.. code-block:: yaml
run_something:
module.run:
- mymodule.same_function:1:
- mymodule.same_function:2:
- myparam
- my_other_param
- mymodule.same_function:3:
- foo: bar
The example above will run `mymodule.same_function` three times with the
different parameters.
To enable the new behavior for :py:func:`module.run <salt.states.module.run>`,
add the following to the minion config file:
@ -143,6 +172,7 @@ State Module Changes
use_superseded:
- module.run
- The default for the ``fingerprint_hash_type`` option used in the ``present``
function in the :mod:`ssh <salt.states.ssh_know_hosts>` state changed from
``md5`` to ``sha256``.
@ -676,6 +706,7 @@ Execution modules
- :mod:`salt.modules.grafana4 <salt.modules.grafana4>`
- :mod:`salt.modules.heat <salt.modules.heat>`
- :mod:`salt.modules.icinga2 <salt.modules.icinga2>`
- :mod:`salt.modules.kubernetes <salt.modules.kubernetes>`
- :mod:`salt.modules.logmod <salt.modules.logmod>`
- :mod:`salt.modules.mattermost <salt.modules.mattermost>`
- :mod:`salt.modules.namecheap_dns <salt.modules.namecheap_dns>`
@ -754,6 +785,7 @@ States
- :mod:`salt.states.icinga2 <salt.states.icinga2>`
- :mod:`salt.states.influxdb_continuous_query <salt.states.influxdb_continuous_query>`
- :mod:`salt.states.influxdb_retention_policy <salt.states.influxdb_retention_policy>`
- :mod:`salt.states.kubernetes <salt.states.kubernetes>`
- :mod:`salt.states.logadm <salt.states.logadm>`
- :mod:`salt.states.logrotate <salt.states.logrotate>`
- :mod:`salt.states.msteams <salt.states.msteams>`

View File

@ -75,7 +75,7 @@ The default location for the pillar is in /srv/pillar.
.. note::
The pillar location can be configured via the `pillar_roots` option inside
The pillar location can be configured via the ``pillar_roots`` option inside
the master configuration file. It must not be in a subdirectory of the state
tree or file_roots. If the pillar is under file_roots, any pillar targeting
can be bypassed by minions.
@ -242,7 +242,7 @@ set in the minion's pillar, then the default of ``httpd`` will be used.
.. note::
Under the hood, pillar is just a Python dict, so Python dict methods such
as `get` and `items` can be used.
as ``get`` and ``items`` can be used.
Pillar Makes Simple States Grow Easily
======================================
@ -303,6 +303,18 @@ Where the vimrc source location can now be changed via pillar:
Ensuring that the right vimrc is sent out to the correct minions.
The pillar top file must include a reference to the new sls pillar file:
``/srv/pillar/top.sls``:
.. code-block:: yaml
base:
'*':
- pkg
- edit.vim
Setting Pillar Data on the Command Line
=======================================

View File

@ -108,9 +108,9 @@ xcopy /E /Q "%PyDir%" "%BinDir%\"
@echo Copying configs to buildenv\conf...
@echo ----------------------------------------------------------------------
@echo xcopy /E /Q "%SrcDir%\conf\master" "%CnfDir%\"
xcopy /Q "%SrcDir%\conf\master" "%CnfDir%\"
xcopy /Q /Y "%SrcDir%\conf\master" "%CnfDir%\"
@echo xcopy /E /Q "%SrcDir%\conf\minion" "%CnfDir%\"
xcopy /Q "%SrcDir%\conf\minion" "%CnfDir%\"
xcopy /Q /Y "%SrcDir%\conf\minion" "%CnfDir%\"
@echo.
@echo Copying VCRedist to Prerequisites
@ -582,6 +582,10 @@ If Exist "%BinDir%\Scripts\salt-run*"^
If Exist "%BldDir%\salt-run.bat"^
del /Q "%BldDir%\salt-run.bat" 1>nul
:: Remove the master config file
if Exist "%CnfDir%\master"^
del /Q "%CnfDir%\master" 1>nul
:: Make the Salt Minion Installer
makensis.exe /DSaltVersion=%Version% /DPythonVersion=%Python% "%InsDir%\Salt-Minion-Setup.nsi"
@echo.

View File

@ -9,5 +9,4 @@ Set Python=%SaltDir%\bin\python.exe
Set Script=%SaltDir%\bin\Scripts\salt-call
:: Launch Script
"%Python%" "%Script%" %*
"%Python%" -E -s "%Script%" %*

View File

@ -9,5 +9,4 @@ Set Python=%SaltDir%\bin\python.exe
Set Script=%SaltDir%\bin\Scripts\salt-cp
:: Launch Script
"%Python%" "%Script%" %*
"%Python%" -E -s "%Script%" %*

View File

@ -9,5 +9,4 @@ Set Python=%SaltDir%\bin\python.exe
Set Script=%SaltDir%\bin\Scripts\salt-key
:: Launch Script
"%Python%" "%Script%" %*
"%Python%" -E -s "%Script%" %*

View File

@ -9,5 +9,4 @@ Set Python=%SaltDir%\bin\python.exe
Set Script=%SaltDir%\bin\Scripts\salt-master
:: Launch Script
"%Python%" "%Script%" %*
"%Python%" -E -s "%Script%" %*

View File

@ -12,5 +12,4 @@ Set Script=%SaltDir%\bin\Scripts\salt-minion
net stop salt-minion
:: Launch Script
"%Python%" "%Script%" -l debug
"%Python%" -E -s "%Script%" -l debug

View File

@ -9,5 +9,4 @@ Set Python=%SaltDir%\bin\python.exe
Set Script=%SaltDir%\bin\Scripts\salt-minion
:: Launch Script
"%Python%" "%Script%" %*
"%Python%" -E -s "%Script%" %*

View File

@ -9,5 +9,4 @@ Set Python=%SaltDir%\bin\python.exe
Set Script=%SaltDir%\bin\Scripts\salt-run
:: Launch Script
"%Python%" "%Script%" %*
"%Python%" -E -s "%Script%" %*

View File

@ -9,5 +9,4 @@ Set Python=%SaltDir%\bin\python.exe
Set Script=%SaltDir%\bin\Scripts\salt
:: Launch Script
"%Python%" "%Script%" %*
"%Python%" -E -s "%Script%" %*

View File

@ -379,8 +379,7 @@ Section -Post
WriteRegStr HKLM "${PRODUCT_MINION_REGKEY}" "Path" "$INSTDIR\bin\"
; Register the Salt-Minion Service
nsExec::Exec "nssm.exe install salt-minion $INSTDIR\bin\python.exe $INSTDIR\bin\Scripts\salt-minion -c $INSTDIR\conf -l quiet"
nsExec::Exec "nssm.exe set salt-minion AppEnvironmentExtra PYTHONHOME="
nsExec::Exec "nssm.exe install salt-minion $INSTDIR\bin\python.exe -E -s $INSTDIR\bin\Scripts\salt-minion -c $INSTDIR\conf -l quiet"
nsExec::Exec "nssm.exe set salt-minion Description Salt Minion from saltstack.com"
nsExec::Exec "nssm.exe set salt-minion Start SERVICE_AUTO_START"
nsExec::Exec "nssm.exe set salt-minion AppNoConsole 1"

View File

@ -545,6 +545,7 @@ class LocalClient(object):
{'stewart': {...}}
'''
if 'expr_form' in kwargs:
import salt
salt.utils.warn_until(
'Fluorine',
'The target type should be passed using the \'tgt_type\' '
@ -742,7 +743,7 @@ class LocalClient(object):
ret[mid] = (data if full_return
else data.get('ret', {}))
for failed in list(set(pub_data['minions']) ^ set(ret)):
for failed in list(set(pub_data['minions']) - set(ret)):
ret[failed] = False
return ret
finally:

View File

@ -1028,10 +1028,18 @@ def ssh_interface(vm_):
Return the ssh_interface type to connect to. Either 'public_ips' (default)
or 'private_ips'.
'''
return config.get_cloud_config_value(
ret = config.get_cloud_config_value(
'ssh_interface', vm_, __opts__, default='public_ips',
search_global=False
)
if ret not in ('public_ips', 'private_ips'):
log.warning((
'Invalid ssh_interface: {0}. '
'Allowed options are ("public_ips", "private_ips"). '
'Defaulting to "public_ips".'
).format(ret))
ret = 'public_ips'
return ret
def get_ssh_gateway_config(vm_):

View File

@ -389,17 +389,18 @@ class AsyncAuth(object):
loop_instance_map = AsyncAuth.instance_map[io_loop]
key = cls.__key(opts)
if key not in loop_instance_map:
auth = loop_instance_map.get(key)
if auth is None:
log.debug('Initializing new AsyncAuth for {0}'.format(key))
# we need to make a local variable for this, as we are going to store
# it in a WeakValueDictionary-- which will remove the item if no one
# references it-- this forces a reference while we return to the caller
new_auth = object.__new__(cls)
new_auth.__singleton_init__(opts, io_loop=io_loop)
loop_instance_map[key] = new_auth
auth = object.__new__(cls)
auth.__singleton_init__(opts, io_loop=io_loop)
loop_instance_map[key] = auth
else:
log.debug('Re-using AsyncAuth for {0}'.format(key))
return loop_instance_map[key]
return auth
@classmethod
def __key(cls, opts, io_loop=None):
@ -1025,14 +1026,15 @@ class SAuth(AsyncAuth):
Only create one instance of SAuth per __key()
'''
key = cls.__key(opts)
if key not in SAuth.instances:
auth = SAuth.instances.get(key)
if auth is None:
log.debug('Initializing new SAuth for {0}'.format(key))
new_auth = object.__new__(cls)
new_auth.__singleton_init__(opts)
SAuth.instances[key] = new_auth
auth = object.__new__(cls)
auth.__singleton_init__(opts)
SAuth.instances[key] = auth
else:
log.debug('Re-using SAuth for {0}'.format(key))
return SAuth.instances[key]
return auth
@classmethod
def __key(cls, opts, io_loop=None):

View File

@ -2378,6 +2378,10 @@ def _zpool_data(grains):
if salt.utils.is_windows() or 'proxyminion' in __opts__:
return {}
# quickly return if NetBSD (ZFS still under development)
if salt.utils.is_netbsd():
return {}
# quickly return if no zpool and zfs command
if not salt.utils.which('zpool'):
return {}

View File

@ -18,7 +18,8 @@ import salt.utils.files
import salt.modules.cmdmod
__salt__ = {
'cmd.run': salt.modules.cmdmod._run_quiet
'cmd.run': salt.modules.cmdmod._run_quiet,
'cmd.run_all': salt.modules.cmdmod._run_all_quiet
}
log = logging.getLogger(__name__)
@ -32,6 +33,8 @@ def disks():
return _freebsd_geom()
elif salt.utils.is_linux():
return _linux_disks()
elif salt.utils.is_windows():
return _windows_disks()
else:
log.trace('Disk grain does not support OS')
@ -141,3 +144,39 @@ def _linux_disks():
log.trace('Unable to identify device {0} as an SSD or HDD.'
' It does not report 0 or 1'.format(device))
return ret
def _windows_disks():
wmic = salt.utils.which('wmic')
namespace = r'\\root\microsoft\windows\storage'
path = 'MSFT_PhysicalDisk'
where = '(MediaType=3 or MediaType=4)'
get = 'DeviceID,MediaType'
ret = {'disks': [], 'SSDs': []}
cmdret = __salt__['cmd.run_all'](
'{0} /namespace:{1} path {2} where {3} get {4} /format:table'.format(
wmic, namespace, path, where, get))
if cmdret['retcode'] != 0:
log.trace('Disk grain does not support this version of Windows')
else:
for line in cmdret['stdout'].splitlines():
info = line.split()
if len(info) != 2 or not info[0].isdigit() or not info[1].isdigit():
continue
device = r'\\.\PhysicalDrive{0}'.format(info[0])
mediatype = info[1]
if mediatype == '3':
log.trace('Device {0} reports itself as an HDD'.format(device))
ret['disks'].append(device)
elif mediatype == '4':
log.trace('Device {0} reports itself as an SSD'.format(device))
ret['SSDs'].append(device)
else:
log.trace('Unable to identify device {0} as an SSD or HDD.'
'It does not report 3 or 4'.format(device))
return ret

View File

@ -38,6 +38,9 @@ import salt.utils.files
import salt.utils.itertools
import salt.utils.templates
if salt.utils.is_windows():
import win32file
# TODO: Check that the passed arguments are correct
# Don't shadow built-in's.
@ -1059,15 +1062,19 @@ def unzip(zip_file,
continue
zfile.extract(target, dest, password)
if extract_perms:
perm = zfile.getinfo(target).external_attr >> 16
if perm == 0:
umask_ = os.umask(0)
os.umask(umask_)
if target.endswith('/'):
perm = 0o777 & ~umask_
else:
perm = 0o666 & ~umask_
os.chmod(os.path.join(dest, target), perm)
if not salt.utils.is_windows():
perm = zfile.getinfo(target).external_attr >> 16
if perm == 0:
umask_ = os.umask(0)
os.umask(umask_)
if target.endswith('/'):
perm = 0o777 & ~umask_
else:
perm = 0o666 & ~umask_
os.chmod(os.path.join(dest, target), perm)
else:
win32_attr = zfile.getinfo(target).external_attr & 0xFF
win32file.SetFileAttributes(os.path.join(dest, target), win32_attr)
except Exception as exc:
if runas:
os.seteuid(euid)

View File

@ -158,7 +158,7 @@ def create_file_system(name,
import os
import base64
creation_token = base64.b64encode(os.urandom(46), ['-', '_'])
tags = {"Key": "Name", "Value": name}
tags = [{"Key": "Name", "Value": name}]
client = _get_conn(key=key, keyid=keyid, profile=profile, region=region)

View File

@ -294,6 +294,9 @@ def _run(cmd,
if runas is None and '__context__' in globals():
runas = __context__.get('runas')
if password is None and '__context__' in globals():
password = __context__.get('runas_password')
# Set the default working directory to the home directory of the user
# salt-minion is running as. Defaults to home directory of user under which
# the minion is running.

View File

@ -559,6 +559,21 @@ def _prep_pull():
__context__['docker._pull_status'] = [x[:12] for x in images(all=True)]
def _scrub_links(links, name):
'''
Remove container name from HostConfig:Links values to enable comparing
container configurations correctly.
'''
if isinstance(links, list):
ret = []
for l in links:
ret.append(l.replace('/{0}/'.format(name), '/', 1))
else:
ret = links
return ret
def _size_fmt(num):
'''
Format bytes as human-readable file sizes
@ -884,8 +899,15 @@ def compare_container(first, second, ignore=None):
continue
val1 = result1[conf_dict][item]
val2 = result2[conf_dict].get(item)
if val1 != val2:
ret.setdefault(conf_dict, {})[item] = {'old': val1, 'new': val2}
if item in ('OomKillDisable',):
if bool(val1) != bool(val2):
ret.setdefault(conf_dict, {})[item] = {'old': val1, 'new': val2}
else:
if item == 'Links':
val1 = _scrub_links(val1, first)
val2 = _scrub_links(val2, second)
if val1 != val2:
ret.setdefault(conf_dict, {})[item] = {'old': val1, 'new': val2}
# Check for optionally-present items that were in the second container
# and not the first.
for item in result2[conf_dict]:
@ -895,8 +917,15 @@ def compare_container(first, second, ignore=None):
continue
val1 = result1[conf_dict].get(item)
val2 = result2[conf_dict][item]
if val1 != val2:
ret.setdefault(conf_dict, {})[item] = {'old': val1, 'new': val2}
if item in ('OomKillDisable',):
if bool(val1) != bool(val2):
ret.setdefault(conf_dict, {})[item] = {'old': val1, 'new': val2}
else:
if item == 'Links':
val1 = _scrub_links(val1, first)
val2 = _scrub_links(val2, second)
if val1 != val2:
ret.setdefault(conf_dict, {})[item] = {'old': val1, 'new': val2}
return ret

View File

@ -844,18 +844,21 @@ def check_hash(path, file_hash):
hash
The hash to check against the file specified in the ``path`` argument.
For versions 2016.11.4 and newer, the hash can be specified without an
.. versionchanged:: 2016.11.4
For this and newer versions the hash can be specified without an
accompanying hash type (e.g. ``e138491e9d5b97023cea823fe17bac22``),
but for earlier releases it is necessary to also specify the hash type
in the format ``<hash_type>:<hash_value>`` (e.g.
``md5:e138491e9d5b97023cea823fe17bac22``).
in the format ``<hash_type>=<hash_value>`` (e.g.
``md5=e138491e9d5b97023cea823fe17bac22``).
CLI Example:
.. code-block:: bash
salt '*' file.check_hash /etc/fstab e138491e9d5b97023cea823fe17bac22
salt '*' file.check_hash /etc/fstab md5:e138491e9d5b97023cea823fe17bac22
salt '*' file.check_hash /etc/fstab md5=e138491e9d5b97023cea823fe17bac22
'''
path = os.path.expanduser(path)
@ -2032,6 +2035,7 @@ def replace(path,
show_changes=True,
ignore_if_missing=False,
preserve_inode=True,
backslash_literal=False,
):
'''
.. versionadded:: 0.17.0
@ -2132,6 +2136,14 @@ def replace(path,
filename. Hard links will then share an inode with the backup, instead
(if using ``backup`` to create a backup copy).
backslash_literal : False
.. versionadded:: 2016.11.7
Interpret backslashes as literal backslashes for the repl and not
escape characters. This will help when using append/prepend so that
the backslashes are not interpreted for the repl on the second run of
the state.
If an equal sign (``=``) appears in an argument to a Salt command it is
interpreted as a keyword argument in the format ``key=val``. That
processing can be bypassed in order to pass an equal sign through to the
@ -2228,7 +2240,10 @@ def replace(path,
if re.search(cpattern, r_data):
return True # `with` block handles file closure
else:
result, nrepl = re.subn(cpattern, repl, r_data, count)
result, nrepl = re.subn(cpattern,
repl.replace('\\', '\\\\') if backslash_literal else repl,
r_data,
count)
# found anything? (even if no change)
if nrepl > 0:
@ -2282,8 +2297,10 @@ def replace(path,
r_data = mmap.mmap(r_file.fileno(),
0,
access=mmap.ACCESS_READ)
result, nrepl = re.subn(cpattern, repl,
r_data, count)
result, nrepl = re.subn(cpattern,
repl.replace('\\', '\\\\') if backslash_literal else repl,
r_data,
count)
try:
w_file.write(salt.utils.to_str(result))
except (OSError, IOError) as exc:

View File

@ -30,6 +30,7 @@ In case both are provided the `file` entry is prefered.
.. code-block:: bash
salt '*' kubernetes.nodes api_url=http://k8s-api-server:port api_user=myuser api_password=pass
.. versionadded: 2017.7.0
'''
# Import Python Futures

View File

@ -216,7 +216,7 @@ def align_check(device, part_type, partition):
'Invalid partition passed to partition.align_check'
)
cmd = 'parted -m -s {0} align-check {1} {2}'.format(
cmd = 'parted -m {0} align-check {1} {2}'.format(
device, part_type, partition
)
out = __salt__['cmd.run'](cmd).splitlines()

View File

@ -135,6 +135,7 @@ def _safe_output(line):
'''
return not any([
line.startswith('Listing') and line.endswith('...'),
line.startswith('Listing') and '\t' not in line,
'...done' in line,
line.startswith('WARNING:')
])

View File

@ -3983,78 +3983,77 @@ def _write_regpol_data(data_to_write,
gpt_extension_guid: admx registry extension guid for the class
'''
try:
if data_to_write:
reg_pol_header = u'\u5250\u6765\x01\x00'
if not os.path.exists(policy_file_path):
ret = __salt__['file.makedirs'](policy_file_path)
with salt.utils.files.fopen(policy_file_path, 'wb') as pol_file:
if not data_to_write.startswith(reg_pol_header):
pol_file.write(reg_pol_header.encode('utf-16-le'))
pol_file.write(data_to_write.encode('utf-16-le'))
try:
gpt_ini_data = ''
if os.path.exists(gpt_ini_path):
with salt.utils.files.fopen(gpt_ini_path, 'rb') as gpt_file:
gpt_ini_data = gpt_file.read()
if not _regexSearchRegPolData(r'\[General\]\r\n', gpt_ini_data):
gpt_ini_data = '[General]\r\n' + gpt_ini_data
if _regexSearchRegPolData(r'{0}='.format(re.escape(gpt_extension)), gpt_ini_data):
# ensure the line contains the ADM guid
gpt_ext_loc = re.search(r'^{0}=.*\r\n'.format(re.escape(gpt_extension)),
gpt_ini_data,
re.IGNORECASE | re.MULTILINE)
gpt_ext_str = gpt_ini_data[gpt_ext_loc.start():gpt_ext_loc.end()]
if not _regexSearchRegPolData(r'{0}'.format(re.escape(gpt_extension_guid)),
gpt_ext_str):
gpt_ext_str = gpt_ext_str.split('=')
gpt_ext_str[1] = gpt_extension_guid + gpt_ext_str[1]
gpt_ext_str = '='.join(gpt_ext_str)
gpt_ini_data = gpt_ini_data[0:gpt_ext_loc.start()] + gpt_ext_str + gpt_ini_data[gpt_ext_loc.end():]
else:
general_location = re.search(r'^\[General\]\r\n',
gpt_ini_data,
re.IGNORECASE | re.MULTILINE)
gpt_ini_data = "{0}{1}={2}\r\n{3}".format(
gpt_ini_data[general_location.start():general_location.end()],
gpt_extension, gpt_extension_guid,
gpt_ini_data[general_location.end():])
# https://technet.microsoft.com/en-us/library/cc978247.aspx
if _regexSearchRegPolData(r'Version=', gpt_ini_data):
version_loc = re.search(r'^Version=.*\r\n',
gpt_ini_data,
re.IGNORECASE | re.MULTILINE)
version_str = gpt_ini_data[version_loc.start():version_loc.end()]
version_str = version_str.split('=')
version_nums = struct.unpack('>2H', struct.pack('>I', int(version_str[1])))
if gpt_extension.lower() == 'gPCMachineExtensionNames'.lower():
version_nums = (version_nums[0], version_nums[1] + 1)
elif gpt_extension.lower() == 'gPCUserExtensionNames'.lower():
version_nums = (version_nums[0] + 1, version_nums[1])
version_num = struct.unpack('>I', struct.pack('>2H', *version_nums))[0]
gpt_ini_data = "{0}{1}={2}\r\n{3}".format(
gpt_ini_data[0:version_loc.start()],
'Version', version_num,
gpt_ini_data[version_loc.end():])
else:
general_location = re.search(r'^\[General\]\r\n',
gpt_ini_data,
re.IGNORECASE | re.MULTILINE)
if gpt_extension.lower() == 'gPCMachineExtensionNames'.lower():
version_nums = (0, 1)
elif gpt_extension.lower() == 'gPCUserExtensionNames'.lower():
version_nums = (1, 0)
gpt_ini_data = "{0}{1}={2}\r\n{3}".format(
gpt_ini_data[general_location.start():general_location.end()],
'Version',
int("{0}{1}".format(str(version_nums[0]).zfill(4), str(version_nums[1]).zfill(4)), 16),
gpt_ini_data[general_location.end():])
if gpt_ini_data:
with salt.utils.files.fopen(gpt_ini_path, 'wb') as gpt_file:
gpt_file.write(gpt_ini_data)
except Exception as e:
msg = 'An error occurred attempting to write to {0}, the exception was {1}'.format(
gpt_ini_path, e)
raise CommandExecutionError(msg)
reg_pol_header = u'\u5250\u6765\x01\x00'
if not os.path.exists(policy_file_path):
ret = __salt__['file.makedirs'](policy_file_path)
with salt.utils.files.fopen(policy_file_path, 'wb') as pol_file:
if not data_to_write.startswith(reg_pol_header):
pol_file.write(reg_pol_header.encode('utf-16-le'))
pol_file.write(data_to_write.encode('utf-16-le'))
try:
gpt_ini_data = ''
if os.path.exists(gpt_ini_path):
with salt.utils.files.fopen(gpt_ini_path, 'rb') as gpt_file:
gpt_ini_data = gpt_file.read()
if not _regexSearchRegPolData(r'\[General\]\r\n', gpt_ini_data):
gpt_ini_data = '[General]\r\n' + gpt_ini_data
if _regexSearchRegPolData(r'{0}='.format(re.escape(gpt_extension)), gpt_ini_data):
# ensure the line contains the ADM guid
gpt_ext_loc = re.search(r'^{0}=.*\r\n'.format(re.escape(gpt_extension)),
gpt_ini_data,
re.IGNORECASE | re.MULTILINE)
gpt_ext_str = gpt_ini_data[gpt_ext_loc.start():gpt_ext_loc.end()]
if not _regexSearchRegPolData(r'{0}'.format(re.escape(gpt_extension_guid)),
gpt_ext_str):
gpt_ext_str = gpt_ext_str.split('=')
gpt_ext_str[1] = gpt_extension_guid + gpt_ext_str[1]
gpt_ext_str = '='.join(gpt_ext_str)
gpt_ini_data = gpt_ini_data[0:gpt_ext_loc.start()] + gpt_ext_str + gpt_ini_data[gpt_ext_loc.end():]
else:
general_location = re.search(r'^\[General\]\r\n',
gpt_ini_data,
re.IGNORECASE | re.MULTILINE)
gpt_ini_data = "{0}{1}={2}\r\n{3}".format(
gpt_ini_data[general_location.start():general_location.end()],
gpt_extension, gpt_extension_guid,
gpt_ini_data[general_location.end():])
# https://technet.microsoft.com/en-us/library/cc978247.aspx
if _regexSearchRegPolData(r'Version=', gpt_ini_data):
version_loc = re.search(r'^Version=.*\r\n',
gpt_ini_data,
re.IGNORECASE | re.MULTILINE)
version_str = gpt_ini_data[version_loc.start():version_loc.end()]
version_str = version_str.split('=')
version_nums = struct.unpack('>2H', struct.pack('>I', int(version_str[1])))
if gpt_extension.lower() == 'gPCMachineExtensionNames'.lower():
version_nums = (version_nums[0], version_nums[1] + 1)
elif gpt_extension.lower() == 'gPCUserExtensionNames'.lower():
version_nums = (version_nums[0] + 1, version_nums[1])
version_num = struct.unpack('>I', struct.pack('>2H', *version_nums))[0]
gpt_ini_data = "{0}{1}={2}\r\n{3}".format(
gpt_ini_data[0:version_loc.start()],
'Version', version_num,
gpt_ini_data[version_loc.end():])
else:
general_location = re.search(r'^\[General\]\r\n',
gpt_ini_data,
re.IGNORECASE | re.MULTILINE)
if gpt_extension.lower() == 'gPCMachineExtensionNames'.lower():
version_nums = (0, 1)
elif gpt_extension.lower() == 'gPCUserExtensionNames'.lower():
version_nums = (1, 0)
gpt_ini_data = "{0}{1}={2}\r\n{3}".format(
gpt_ini_data[general_location.start():general_location.end()],
'Version',
int("{0}{1}".format(str(version_nums[0]).zfill(4), str(version_nums[1]).zfill(4)), 16),
gpt_ini_data[general_location.end():])
if gpt_ini_data:
with salt.utils.files.fopen(gpt_ini_path, 'wb') as gpt_file:
gpt_file.write(gpt_ini_data)
except Exception as e:
msg = 'An error occurred attempting to write to {0}, the exception was {1}'.format(
gpt_ini_path, e)
raise CommandExecutionError(msg)
except Exception as e:
msg = 'An error occurred attempting to write to {0}, the exception was {1}'.format(policy_file_path, e)
raise CommandExecutionError(msg)

View File

@ -2,6 +2,49 @@
'''
Module for managing Windows Updates using the Windows Update Agent.
List updates on the system using the following functions:
- :ref:`available`
- :ref:`list`
This is an easy way to find additional information about updates available to
to the system, such as the GUID, KB number, or description.
Once you have the GUID or a KB number for the update you can get information
about the update, download, install, or uninstall it using these functions:
- :ref:`get`
- :ref:`download`
- :ref:`install`
- :ref:`uninstall`
The get function expects a name in the form of a GUID, KB, or Title and should
return information about a single update. The other functions accept either a
single item or a list of items for downloading/installing/uninstalling a
specific list of items.
The :ref:`list` and :ref:`get` functions are utility functions. In addition to
returning information about updates they can also download and install updates
by setting ``download=True`` or ``install=True``. So, with :ref:`list` for
example, you could run the function with the filters you want to see what is
available. Then just add ``install=True`` to install everything on that list.
If you want to download, install, or uninstall specific updates, use
:ref:`download`, :ref:`install`, or :ref:`uninstall`. To update your system
with the latest updates use :ref:`list` and set ``install=True``
You can also adjust the Windows Update settings using the :ref:`set_wu_settings`
function. This function is only supported on the following operating systems:
- Windows Vista / Server 2008
- Windows 7 / Server 2008R2
- Windows 8 / Server 2012
- Windows 8.1 / Server 2012R2
As of Windows 10 and Windows Server 2016, the ability to modify the Windows
Update settings has been restricted. The settings can be modified in the Local
Group Policy using the ``lgpo`` module.
.. versionadded:: 2015.8.0
:depends:
@ -54,36 +97,40 @@ def available(software=True,
skip_mandatory=False,
skip_reboot=False,
categories=None,
severities=None,
):
severities=None,):
'''
.. versionadded:: 2017.7.0
List updates that match the passed criteria.
List updates that match the passed criteria. This allows for more filter
options than :func:`list`. Good for finding a specific GUID or KB.
Args:
software (bool): Include software updates in the results (default is
True)
software (bool):
Include software updates in the results (default is True)
drivers (bool): Include driver updates in the results (default is False)
drivers (bool):
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 updates that are already installed. Default
is False.
skip_installed (bool):
Skip updates that are already installed. Default is False.
skip_hidden (bool): Skip updates that have been hidden. Default is True.
skip_hidden (bool):
Skip updates that have been hidden. 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 require a reboot. Default is
False.
skip_reboot (bool):
Skip updates that require a reboot. Default is False.
categories (list): Specify the categories to list. Must be passed as a
list. All categories returned by default.
categories (list):
Specify the categories to list. Must be passed as a list. All
categories returned by default.
Categories include the following:
@ -101,8 +148,9 @@ def available(software=True,
* Windows 8.1 and later drivers
* Windows Defender
severities (list): Specify the severities to include. Must be passed as
a list. All severities returned by default.
severities (list):
Specify the severities to include. Must be passed as a list. All
severities returned by default.
Severities include the following:
@ -152,28 +200,30 @@ def available(software=True,
salt '*' win_wua.available
# List all updates with categories of Critical Updates and Drivers
salt '*' win_wua.available categories=['Critical Updates','Drivers']
salt '*' win_wua.available categories=["Critical Updates","Drivers"]
# List all Critical Security Updates
salt '*' win_wua.available categories=['Security Updates'] severities=['Critical']
salt '*' win_wua.available categories=["Security Updates"] severities=["Critical"]
# List all updates with a severity of Critical
salt '*' win_wua.available severities=['Critical']
salt '*' win_wua.available severities=["Critical"]
# A summary of all available updates
salt '*' win_wua.available summary=True
# A summary of all Feature Packs and Windows 8.1 Updates
salt '*' win_wua.available categories=['Feature Packs','Windows 8.1'] summary=True
salt '*' win_wua.available categories=["Feature Packs","Windows 8.1"] summary=True
'''
# Create a Windows Update Agent instance
wua = salt.utils.win_update.WindowsUpdateAgent()
# Look for available
updates = wua.available(skip_hidden, skip_installed, skip_mandatory,
skip_reboot, software, drivers, categories,
severities)
updates = wua.available(
skip_hidden=skip_hidden, skip_installed=skip_installed,
skip_mandatory=skip_mandatory, skip_reboot=skip_reboot,
software=software, drivers=drivers, categories=categories,
severities=severities)
# Return results as Summary or Details
return updates.summary() if summary else updates.list()
@ -183,23 +233,29 @@ def list_update(name, download=False, install=False):
'''
.. deprecated:: 2017.7.0
Use :func:`get` instead
Returns details for all updates that match the search criteria
Args:
name (str): The name of the update you're searching for. This can be the
GUID, a KB number, or any part of the name of the update. GUIDs and
KBs are preferred. Run ``list_updates`` to get the GUID for the update
you're looking for.
download (bool): Download the update returned by this function. Run this
function first to see if the update exists, then set ``download=True``
to download the update.
name (str):
The name of the update you're searching for. This can be the GUID, a
KB number, or any part of the name of the update. GUIDs and KBs are
preferred. Run ``list_updates`` to get the GUID for the update
you're looking for.
install (bool): Install the update returned by this function. Run this
function first to see if the update exists, then set ``install=True`` to
install the update.
download (bool):
Download the update returned by this function. Run this function
first to see if the update exists, then set ``download=True`` to
download the update.
install (bool):
Install the update returned by this function. Run this function
first to see if the update exists, then set ``install=True`` to
install the update.
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.
@ -258,23 +314,28 @@ def get(name, download=False, install=False):
'''
.. versionadded:: 2017.7.0
Returns details for all updates that match the search criteria
Returns details for the named update
Args:
name (str): The name of the update you're searching for. This can be the
GUID, a KB number, or any part of the name of the update. GUIDs and
KBs are preferred. Run ``list`` to get the GUID for the update
you're looking for.
download (bool): Download the update returned by this function. Run this
function first to see if the update exists, then set ``download=True``
to download the update.
name (str):
The name of the update you're searching for. This can be the GUID, a
KB number, or any part of the name of the update. GUIDs and KBs are
preferred. Run ``list`` to get the GUID for the update you're
looking for.
install (bool): Install the update returned by this function. Run this
function first to see if the update exists, then set ``install=True`` to
install the update.
download (bool):
Download the update returned by this function. Run this function
first to see if the update exists, then set ``download=True`` to
download the update.
install (bool):
Install the update returned by this function. Run this function
first to see if the update exists, then set ``install=True`` to
install the update.
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.
@ -357,30 +418,35 @@ def list_updates(software=True,
install is True the same list will be downloaded and/or installed.
Args:
software (bool): Include software updates in the results (default is
True)
drivers (bool): Include driver updates in the results (default is False)
software (bool):
Include software updates in the results (default is True)
drivers (bool):
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
False)
skip_installed (bool):
Skip installed updates in the results (default is False)
download (bool): (Overrides reporting functionality) Download the list
of updates returned by this function. Run this function first with
``download=False`` to see what will be downloaded, then set
``download=True`` to download the updates.
download (bool):
(Overrides reporting functionality) Download the list of updates
returned by this function. Run this function first with
``download=False`` to see what will be downloaded, then set
``download=True`` to download the updates.
install (bool): (Overrides reporting functionality) Install the list of
updates returned by this function. Run this function first with
``install=False`` to see what will be installed, then set
``install=True`` to install the updates.
install (bool):
(Overrides reporting functionality) Install the list of updates
returned by this function. Run this function first with
``install=False`` to see what will be installed, then set
``install=True`` to install the updates.
categories (list): Specify the categories to list. Must be passed as a
list. All categories returned by default.
categories (list):
Specify the categories to list. Must be passed as a list. All
categories returned by default.
Categories include the following:
@ -398,8 +464,9 @@ def list_updates(software=True,
* Windows 8.1 and later drivers
* Windows Defender
severities (list): Specify the severities to include. Must be passed as
a list. All severities returned by default.
severities (list):
Specify the severities to include. Must be passed as a list. All
severities returned by default.
Severities include the following:
@ -486,30 +553,35 @@ def list(software=True,
install is True the same list will be downloaded and/or installed.
Args:
software (bool): Include software updates in the results (default is
True)
drivers (bool): Include driver updates in the results (default is False)
software (bool):
Include software updates in the results (default is True)
drivers (bool):
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
False)
skip_installed (bool):
Skip installed updates in the results (default is False)
download (bool): (Overrides reporting functionality) Download the list
of updates returned by this function. Run this function first with
``download=False`` to see what will be downloaded, then set
``download=True`` to download the updates.
download (bool):
(Overrides reporting functionality) Download the list of updates
returned by this function. Run this function first with
``download=False`` to see what will be downloaded, then set
``download=True`` to download the updates.
install (bool): (Overrides reporting functionality) Install the list of
updates returned by this function. Run this function first with
``install=False`` to see what will be installed, then set
``install=True`` to install the updates.
install (bool):
(Overrides reporting functionality) Install the list of updates
returned by this function. Run this function first with
``install=False`` to see what will be installed, then set
``install=True`` to install the updates.
categories (list): Specify the categories to list. Must be passed as a
list. All categories returned by default.
categories (list):
Specify the categories to list. Must be passed as a list. All
categories returned by default.
Categories include the following:
@ -527,8 +599,9 @@ def list(software=True,
* Windows 8.1 and later drivers
* Windows Defender
severities (list): Specify the severities to include. Must be passed as
a list. All severities returned by default.
severities (list):
Specify the severities to include. Must be passed as a list. All
severities returned by default.
Severities include the following:
@ -575,22 +648,22 @@ def list(software=True,
.. code-block:: bash
# Normal Usage (list all software updates)
salt '*' win_wua.list_updates
salt '*' win_wua.list
# List all updates with categories of Critical Updates and Drivers
salt '*' win_wua.list_updates categories=['Critical Updates','Drivers']
salt '*' win_wua.list categories=['Critical Updates','Drivers']
# List all Critical Security Updates
salt '*' win_wua.list_updates categories=['Security Updates'] severities=['Critical']
salt '*' win_wua.list categories=['Security Updates'] severities=['Critical']
# List all updates with a severity of Critical
salt '*' win_wua.list_updates severities=['Critical']
salt '*' win_wua.list severities=['Critical']
# A summary of all available updates
salt '*' win_wua.list_updates summary=True
salt '*' win_wua.list summary=True
# A summary of all Feature Packs and Windows 8.1 Updates
salt '*' win_wua.list_updates categories=['Feature Packs','Windows 8.1'] summary=True
salt '*' win_wua.list categories=['Feature Packs','Windows 8.1'] summary=True
'''
# Create a Windows Update Agent instance
wua = salt.utils.win_update.WindowsUpdateAgent()
@ -604,11 +677,11 @@ def list(software=True,
# Download
if download or install:
ret['Download'] = wua.download(updates.updates)
ret['Download'] = wua.download(updates)
# Install
if install:
ret['Install'] = wua.install(updates.updates)
ret['Install'] = wua.install(updates)
if not ret:
return updates.summary() if summary else updates.list()
@ -625,13 +698,16 @@ def download_update(name):
Args:
name (str): The name of the update to download. This can be a GUID, a KB
number, or any part of the name. To ensure a single item is matched the
GUID is preferred.
name (str):
The name of the update to download. This can be a GUID, a KB number,
or any part of the name. To ensure a single item is matched the GUID
is preferred.
.. note:: If more than one result is returned an error will be raised.
.. note::
If more than one result is returned an error will be raised.
Returns:
dict: A dictionary containing the results of the download
CLI Examples:
@ -641,7 +717,6 @@ def download_update(name):
salt '*' win_wua.download_update 12345678-abcd-1234-abcd-1234567890ab
salt '*' win_wua.download_update KB12312321
'''
salt.utils.warn_until(
'Fluorine',
@ -660,8 +735,9 @@ def download_updates(names):
Args:
names (list): A list of updates to download. This can be any combination
of GUIDs, KB numbers, or names. GUIDs or KBs are preferred.
names (list):
A list of updates to download. This can be any combination of GUIDs,
KB numbers, or names. GUIDs or KBs are preferred.
Returns:
@ -672,7 +748,7 @@ def download_updates(names):
.. code-block:: bash
# Normal Usage
salt '*' win_wua.download guid=['12345678-abcd-1234-abcd-1234567890ab', 'KB2131233']
salt '*' win_wua.download_updates guid=['12345678-abcd-1234-abcd-1234567890ab', 'KB2131233']
'''
salt.utils.warn_until(
'Fluorine',
@ -690,9 +766,14 @@ def download(names):
Args:
names (str, list): A single update or a list of updates to download.
This can be any combination of GUIDs, KB numbers, or names. GUIDs or KBs
are preferred.
names (str, list):
A single update or a list of updates to download. This can be any
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
Returns:
@ -703,7 +784,7 @@ def download(names):
.. code-block:: bash
# Normal Usage
salt '*' win_wua.download guid=['12345678-abcd-1234-abcd-1234567890ab', 'KB2131233']
salt '*' win_wua.download names=['12345678-abcd-1234-abcd-1234567890ab', 'KB2131233']
'''
# Create a Windows Update Agent instance
wua = salt.utils.win_update.WindowsUpdateAgent()
@ -714,6 +795,13 @@ def download(names):
if updates.count() == 0:
raise CommandExecutionError('No updates found')
# Make sure it's a list so count comparison is correct
if isinstance(names, six.string_types):
names = [names]
if isinstance(names, six.integer_types):
names = [str(names)]
if updates.count() > len(names):
raise CommandExecutionError('Multiple updates found, names need to be '
'more specific')
@ -734,10 +822,12 @@ def install_update(name):
number, or any part of the name. To ensure a single item is matched the
GUID is preferred.
.. note:: If no results or more than one result is returned an error
will be raised.
.. note::
If no results or more than one result is returned an error will be
raised.
Returns:
dict: A dictionary containing the results of the install
CLI Examples:
@ -795,9 +885,14 @@ def install(names):
Args:
names (str, list): A single update or a list of updates to install.
This can be any combination of GUIDs, KB numbers, or names. GUIDs or KBs
are preferred.
names (str, list):
A single update or a list of updates to install. This can be any
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
Returns:
@ -808,7 +903,7 @@ def install(names):
.. code-block:: bash
# Normal Usage
salt '*' win_wua.install_updates guid=['12345678-abcd-1234-abcd-1234567890ab', 'KB12323211']
salt '*' win_wua.install KB12323211
'''
# Create a Windows Update Agent instance
wua = salt.utils.win_update.WindowsUpdateAgent()
@ -819,6 +914,13 @@ def install(names):
if updates.count() == 0:
raise CommandExecutionError('No updates found')
# Make sure it's a list so count comparison is correct
if isinstance(names, six.string_types):
names = [names]
if isinstance(names, six.integer_types):
names = [str(names)]
if updates.count() > len(names):
raise CommandExecutionError('Multiple updates found, names need to be '
'more specific')
@ -834,9 +936,10 @@ def uninstall(names):
Args:
names (str, list): A single update or a list of updates to uninstall.
This can be any combination of GUIDs, KB numbers, or names. GUIDs or KBs
are preferred.
names (str, list):
A single update or a list of updates to uninstall. This can be any
combination of GUIDs, KB numbers, or names. GUIDs or KBs are
preferred.
Returns:
@ -875,33 +978,50 @@ def set_wu_settings(level=None,
Change Windows Update settings. If no parameters are passed, the current
value will be returned.
:param int level:
Number from 1 to 4 indicating the update level:
Supported:
- Windows Vista / Server 2008
- Windows 7 / Server 2008R2
- Windows 8 / Server 2012
- Windows 8.1 / Server 2012R2
.. note:
Microsoft began using the Unified Update Platform (UUP) starting with
Windows 10 / Server 2016. The Windows Update settings have changed and
the ability to 'Save' Windows Update settings has been removed. Windows
Update settings are read-only. See MSDN documentation:
https://msdn.microsoft.com/en-us/library/aa385829(v=vs.85).aspx
Args:
level (int):
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
3. Download updates but let me choose whether to install them
4. Install updates automatically
:param bool recommended:
Boolean value that indicates whether to include optional or recommended
updates when a search for updates and installation of updates is
performed.
:param bool featured:
Boolean value that indicates whether to display notifications for
featured updates.
recommended (bool):
Boolean value that indicates whether to include optional or
recommended updates when a search for updates and installation of
updates is performed.
:param bool elevated:
Boolean value that indicates whether non-administrators can perform some
update-related actions without administrator approval.
featured (bool):
Boolean value that indicates whether to display notifications for
featured updates.
:param bool msupdate:
Boolean value that indicates whether to turn on Microsoft Update for
other Microsoft products
elevated (bool):
Boolean value that indicates whether non-administrators can perform
some update-related actions without administrator approval.
msupdate (bool):
Boolean value that indicates whether to turn on Microsoft Update for
other Microsoft products
day (str):
Days of the week on which Automatic Updates installs or uninstalls
updates. Accepted values:
:param str day:
Days of the week on which Automatic Updates installs or uninstalls
updates.
Accepted values:
- Everyday
- Monday
- Tuesday
@ -910,21 +1030,43 @@ def set_wu_settings(level=None,
- Friday
- Saturday
:param str time:
Time at which Automatic Updates installs or uninstalls updates. Must be
in the ##:## 24hr format, eg. 3:00 PM would be 15:00
time (str):
Time at which Automatic Updates installs or uninstalls updates. Must
be in the ##:## 24hr format, eg. 3:00 PM would be 15:00. Must be in
1 hour increments.
:return: Returns a dictionary containing the results.
Returns:
dict: Returns a dictionary containing the results.
CLI Examples:
.. code-block:: bash
salt '*' win_wua.set_wu_settings level=4 recommended=True featured=False
'''
ret = {}
ret['Success'] = True
# The AutomaticUpdateSettings.Save() method used in this function does not
# work on Windows 10 / Server 2016. It is called in throughout this function
# like this:
#
# obj_au = win32com.client.Dispatch('Microsoft.Update.AutoUpdate')
# obj_au_settings = obj_au.Settings
# obj_au_settings.Save()
#
# The `Save()` method reports success but doesn't actually change anything.
# Windows Update settings are read-only in Windows 10 / Server 2016. There's
# a little blurb on MSDN that mentions this, but gives no alternative for
# changing these settings in Windows 10 / Server 2016.
#
# https://msdn.microsoft.com/en-us/library/aa385829(v=vs.85).aspx
#
# Apparently the Windows Update framework in Windows Vista - Windows 8.1 has
# been changed quite a bit in Windows 10 / Server 2016. It is now called the
# Unified Update Platform (UUP). I haven't found an API or a Powershell
# commandlet for working with the the UUP. Perhaps there will be something
# forthcoming. The `win_lgpo` module might be an option for changing the
# Windows Update settings using local group policy.
ret = {'Success': True}
# Initialize the PyCom system
pythoncom.CoInitialize()
@ -1076,30 +1218,31 @@ def get_wu_settings():
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.
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.
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.
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
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.
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.
@ -1182,13 +1325,12 @@ def get_needs_reboot():
Returns:
bool: True if the system requires a reboot, False if not
bool: True if the system requires a reboot, otherwise False
CLI Examples:
.. code-block:: bash
salt '*' win_wua.get_needs_reboot
'''
return salt.utils.win_update.needs_reboot()

View File

@ -183,7 +183,16 @@ def _check_versionlock():
Ensure that the appropriate versionlock plugin is present
'''
if _yum() == 'dnf':
vl_plugin = 'python-dnf-plugins-extras-versionlock'
if int(__grains__.get('osmajorrelease')) >= 26:
if six.PY3:
vl_plugin = 'python3-dnf-plugin-versionlock'
else:
vl_plugin = 'python2-dnf-plugin-versionlock'
else:
if six.PY3:
vl_plugin = 'python3-dnf-plugins-extras-versionlock'
else:
vl_plugin = 'python-dnf-plugins-extras-versionlock'
else:
vl_plugin = 'yum-versionlock' \
if __grains__.get('osmajorrelease') == '5' \
@ -1036,6 +1045,11 @@ def refresh_db(**kwargs):
clean_cmd = [_yum(), '--quiet', 'clean', 'expire-cache']
update_cmd = [_yum(), '--quiet', 'check-update']
if __grains__.get('os_family') == 'RedHat' and __grains__.get('osmajorrelease') == '7':
# This feature is disable because it is not used by Salt and lasts a lot with using large repo like EPEL
update_cmd.append('--setopt=autocheck_running_kernel=false')
for args in (repo_arg, exclude_arg, branch_arg):
if args:
clean_cmd.extend(args)

View File

@ -38,6 +38,7 @@ def sync_all(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
'''
log.debug('Syncing all')
ret = {}
ret['clouds'] = sync_clouds(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['modules'] = sync_modules(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['states'] = sync_states(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['grains'] = sync_grains(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)

View File

@ -99,6 +99,7 @@ STATE_RUNTIME_KEYWORDS = frozenset([
'reload_grains',
'reload_pillar',
'runas',
'runas_password',
'fire_event',
'saltenv',
'use',
@ -1754,6 +1755,11 @@ class State(object):
self.state_con['runas'] = low.get('runas', None)
if low['state'] == 'cmd' and 'password' in low:
self.state_con['runas_password'] = low['password']
else:
self.state_con['runas_password'] = low.get('runas_password', None)
if not low.get('__prereq__'):
log.info(
'Executing state {0}.{1} for [{2}]'.format(
@ -1866,6 +1872,9 @@ class State(object):
sys.modules[self.states[cdata['full']].__module__].__opts__[
'test'] = test
self.state_con.pop('runas')
self.state_con.pop('runas_password')
# If format_call got any warnings, let's show them to the user
if 'warnings' in cdata:
ret.setdefault('warnings', []).extend(cdata['warnings'])

View File

@ -1194,7 +1194,7 @@ def extracted(name,
return ret
if not os.path.isdir(name):
__salt__['file.makedirs'](name, user=user)
__states__['file.directory'](name, user=user, makedirs=True)
created_destdir = True
log.debug('Extracting {0} to {1}'.format(cached_source, name))
@ -1218,6 +1218,7 @@ def extracted(name,
options=options,
trim_output=trim_output,
password=password,
extract_perms=extract_perms,
**kwargs)
elif archive_format == 'rar':
try:

View File

@ -105,8 +105,20 @@ def present(name,
# map containers to container's Ids.
containers = [__salt__['docker.inspect_container'](c)['Id'] for c in containers]
networks = __salt__['docker.networks'](names=[name])
log.trace(
'docker_network.present: current networks: {0}'.format(networks)
)
# networks will contain all Docker networks which partially match 'name'.
# We need to loop through to find the matching network, if there is one.
network = None
if networks:
network = networks[0] # we expect network's name to be unique
for network_iter in networks:
if network_iter['Name'] == name:
network = network_iter
break
if network is not None:
if all(c in network['Containers'] for c in containers):
ret['result'] = True
ret['comment'] = 'Network \'{0}\' already exists.'.format(name)
@ -173,7 +185,20 @@ def absent(name, driver=None):
'comment': ''}
networks = __salt__['docker.networks'](names=[name])
if not networks:
log.trace(
'docker_network.absent: current networks: {0}'.format(networks)
)
# networks will contain all Docker networks which partially match 'name'.
# We need to loop through to find the matching network, if there is one.
network = None
if networks:
for network_iter in networks:
if network_iter['Name'] == name:
network = network_iter
break
if network is None:
ret['result'] = True
ret['comment'] = 'Network \'{0}\' already absent'.format(name)
return ret

View File

@ -3763,7 +3763,13 @@ def line(name, content=None, match=None, mode=None, location=None,
if not name:
return _error(ret, 'Must provide name to file.line')
managed(name, create=create, user=user, group=group, mode=file_mode)
managed(
name,
create=create,
user=user,
group=group,
mode=file_mode,
replace=False)
check_res, check_msg = _check_file(name)
if not check_res:
@ -3811,7 +3817,8 @@ def replace(name,
not_found_content=None,
backup='.bak',
show_changes=True,
ignore_if_missing=False):
ignore_if_missing=False,
backslash_literal=False):
r'''
Maintain an edit in a file.
@ -3911,6 +3918,14 @@ def replace(name,
state will display an error raised by the execution module. If set to
``True``, the state will simply report no changes.
backslash_literal : False
.. versionadded:: 2016.11.7
Interpret backslashes as literal backslashes for the repl and not
escape characters. This will help when using append/prepend so that
the backslashes are not interpreted for the repl on the second run of
the state.
For complex regex patterns, it can be useful to avoid the need for complex
quoting and escape sequences by making use of YAML's multiline string
syntax.
@ -3959,7 +3974,8 @@ def replace(name,
backup=backup,
dry_run=__opts__['test'],
show_changes=show_changes,
ignore_if_missing=ignore_if_missing)
ignore_if_missing=ignore_if_missing,
backslash_literal=backslash_literal)
if changes:
ret['pchanges']['diff'] = changes

View File

@ -73,6 +73,8 @@ The kubernetes module is used to manage different kubernetes resources.
key1: value1
key2: value2
key3: value3
.. versionadded: 2017.7.0
'''
from __future__ import absolute_import

View File

@ -262,6 +262,7 @@ def run(**kwargs):
missing = []
tests = []
for func in functions:
func = func.split(':')[0]
if func not in __salt__:
missing.append(func)
elif __opts__['test']:
@ -284,8 +285,9 @@ def run(**kwargs):
failures = []
success = []
for func in functions:
_func = func.split(':')[0]
try:
func_ret = _call_function(func, returner=kwargs.get('returner'),
func_ret = _call_function(_func, returner=kwargs.get('returner'),
func_args=kwargs.get(func))
if not _get_result(func_ret, ret['changes'].get('ret', {})):
if isinstance(func_ret, dict):
@ -313,22 +315,35 @@ def _call_function(name, returner=None, **kwargs):
'''
argspec = salt.utils.args.get_function_argspec(__salt__[name])
func_kw = dict(zip(argspec.args[-len(argspec.defaults or []):], # pylint: disable=incompatible-py3-code
argspec.defaults or []))
func_args = []
for funcset in kwargs.get('func_args') or {}:
if isinstance(funcset, dict):
func_kw.update(funcset)
argspec.defaults or []))
arg_type, na_type, kw_type = [], {}, False
for funcset in reversed(kwargs.get('func_args') or []):
if not isinstance(funcset, dict):
kw_type = True
if kw_type:
if isinstance(funcset, dict):
arg_type += funcset.values()
na_type.update(funcset)
else:
arg_type.append(funcset)
else:
func_args.append(funcset)
func_kw.update(funcset)
arg_type.reverse()
_exp_prm = len(argspec.args or []) - len(argspec.defaults or [])
_passed_prm = len(arg_type)
missing = []
for arg in argspec.args:
if arg not in func_kw:
missing.append(arg)
if na_type and _exp_prm > _passed_prm:
for arg in argspec.args:
if arg not in func_kw:
missing.append(arg)
if missing:
raise SaltInvocationError('Missing arguments: {0}'.format(', '.join(missing)))
elif _exp_prm > _passed_prm:
raise SaltInvocationError('Function expects {0} parameters, got only {1}'.format(
_exp_prm, _passed_prm))
mret = __salt__[name](*func_args, **func_kw)
mret = __salt__[name](*arg_type, **func_kw)
if returner is not None:
returners = salt.loader.returners(__opts__, __salt__)
if returner in returners:

View File

@ -250,15 +250,16 @@ class IPCClient(object):
# FIXME
key = str(socket_path)
if key not in loop_instance_map:
client = loop_instance_map.get(key)
if client is None:
log.debug('Initializing new IPCClient for path: {0}'.format(key))
new_client = object.__new__(cls)
client = object.__new__(cls)
# FIXME
new_client.__singleton_init__(io_loop=io_loop, socket_path=socket_path)
loop_instance_map[key] = new_client
client.__singleton_init__(io_loop=io_loop, socket_path=socket_path)
loop_instance_map[key] = client
else:
log.debug('Re-using IPCClient for {0}'.format(key))
return loop_instance_map[key]
return client
def __singleton_init__(self, socket_path, io_loop=None):
'''

View File

@ -221,17 +221,18 @@ class AsyncTCPReqChannel(salt.transport.client.ReqChannel):
loop_instance_map = cls.instance_map[io_loop]
key = cls.__key(opts, **kwargs)
if key not in loop_instance_map:
obj = loop_instance_map.get(key)
if obj is None:
log.debug('Initializing new AsyncTCPReqChannel for {0}'.format(key))
# we need to make a local variable for this, as we are going to store
# it in a WeakValueDictionary-- which will remove the item if no one
# references it-- this forces a reference while we return to the caller
new_obj = object.__new__(cls)
new_obj.__singleton_init__(opts, **kwargs)
loop_instance_map[key] = new_obj
obj = object.__new__(cls)
obj.__singleton_init__(opts, **kwargs)
loop_instance_map[key] = obj
else:
log.debug('Re-using AsyncTCPReqChannel for {0}'.format(key))
return loop_instance_map[key]
return obj
@classmethod
def __key(cls, opts, **kwargs):

View File

@ -80,28 +80,19 @@ class AsyncZeroMQReqChannel(salt.transport.client.ReqChannel):
loop_instance_map = cls.instance_map[io_loop]
key = cls.__key(opts, **kwargs)
if key not in loop_instance_map:
obj = loop_instance_map.get(key)
if obj is None:
log.debug('Initializing new AsyncZeroMQReqChannel for {0}'.format(key))
# we need to make a local variable for this, as we are going to store
# it in a WeakValueDictionary-- which will remove the item if no one
# references it-- this forces a reference while we return to the caller
new_obj = object.__new__(cls)
new_obj.__singleton_init__(opts, **kwargs)
loop_instance_map[key] = new_obj
obj = object.__new__(cls)
obj.__singleton_init__(opts, **kwargs)
loop_instance_map[key] = obj
log.trace('Inserted key into loop_instance_map id {0} for key {1} and process {2}'.format(id(loop_instance_map), key, os.getpid()))
else:
log.debug('Re-using AsyncZeroMQReqChannel for {0}'.format(key))
try:
return loop_instance_map[key]
except KeyError:
# In iterating over the loop_instance_map, we may have triggered
# garbage collection. Therefore, the key is no longer present in
# the map. Re-gen and add to map.
log.debug('Initializing new AsyncZeroMQReqChannel due to GC for {0}'.format(key))
new_obj = object.__new__(cls)
new_obj.__singleton_init__(opts, **kwargs)
loop_instance_map[key] = new_obj
return loop_instance_map[key]
return obj
def __deepcopy__(self, memo):
cls = self.__class__

View File

@ -542,8 +542,14 @@ class _WithDeprecated(_DeprecationDecorator):
f_name=function.__name__))
opts = self._globals.get('__opts__', '{}')
use_deprecated = full_name in opts.get(self.CFG_USE_DEPRECATED, list())
use_superseded = full_name in opts.get(self.CFG_USE_SUPERSEDED, list())
pillar = self._globals.get('__pillar__', '{}')
use_deprecated = (full_name in opts.get(self.CFG_USE_DEPRECATED, list()) or
full_name in pillar.get(self.CFG_USE_DEPRECATED, list()))
use_superseded = (full_name in opts.get(self.CFG_USE_SUPERSEDED, list()) or
full_name in pillar.get(self.CFG_USE_SUPERSEDED, list()))
if use_deprecated and use_superseded:
raise SaltConfigurationError("Function '{0}' is mentioned both in deprecated "
"and superseded sections. Please remove any of that.".format(full_name))
@ -565,8 +571,11 @@ class _WithDeprecated(_DeprecationDecorator):
f_name=self._orig_f_name)
return func_path in self._globals.get('__opts__').get(
self.CFG_USE_DEPRECATED, list()) or func_path in self._globals.get('__pillar__').get(
self.CFG_USE_DEPRECATED, list()) or (self._policy == self.OPT_IN
and not (func_path in self._globals.get('__opts__', {}).get(
self.CFG_USE_SUPERSEDED, list()))
and not (func_path in self._globals.get('__pillar__', {}).get(
self.CFG_USE_SUPERSEDED, list()))), func_path
def __call__(self, function):

View File

@ -17,6 +17,7 @@ except ImportError:
import yaml
import collections
from salt.utils.odict import OrderedDict
try:
@ -27,7 +28,7 @@ except ImportError:
HAS_IOFLO = False
class IndentMixin(object):
class IndentMixin(Dumper):
'''
Mixin that improves YAML dumped list readability
by indenting them by two spaces,

View File

@ -52,6 +52,7 @@ class Test_Junos_Module(TestCase, LoaderModuleMockMixin, XMLEqualityMixin):
host='1.1.1.1',
user='test',
password='test123',
fact_style='old',
gather_facts=False)
self.dev.open()
self.dev.timeout = 30

View File

@ -102,16 +102,17 @@ class ArchiveTestCase(TestCase, LoaderModuleMockMixin):
'cmd.run_all': mock_run,
'archive.list': list_mock,
'file.source_list': mock_source_list}):
with patch.object(os.path, 'isfile', isfile_mock):
for test_opts, ret_opts in zip(test_tar_opts, ret_tar_opts):
ret = archive.extracted(tmp_dir,
source,
options=test_opts,
enforce_toplevel=False)
ret_opts.append(source)
mock_run.assert_called_with(ret_opts,
cwd=tmp_dir + os.sep,
python_shell=False)
with patch.dict(archive.__states__, {'file.directory': mock_true}):
with patch.object(os.path, 'isfile', isfile_mock):
for test_opts, ret_opts in zip(test_tar_opts, ret_tar_opts):
ret = archive.extracted(tmp_dir,
source,
options=test_opts,
enforce_toplevel=False)
ret_opts.append(source)
mock_run.assert_called_with(ret_opts,
cwd=tmp_dir + os.sep,
python_shell=False)
def test_tar_gnutar(self):
'''
@ -142,13 +143,14 @@ class ArchiveTestCase(TestCase, LoaderModuleMockMixin):
'cmd.run_all': run_all,
'archive.list': list_mock,
'file.source_list': mock_source_list}):
with patch.object(os.path, 'isfile', isfile_mock):
ret = archive.extracted('/tmp/out',
source,
options='xvzf',
enforce_toplevel=False,
keep=True)
self.assertEqual(ret['changes']['extracted_files'], 'stdout')
with patch.dict(archive.__states__, {'file.directory': mock_true}):
with patch.object(os.path, 'isfile', isfile_mock):
ret = archive.extracted('/tmp/out',
source,
options='xvzf',
enforce_toplevel=False,
keep=True)
self.assertEqual(ret['changes']['extracted_files'], 'stdout')
def test_tar_bsdtar(self):
'''
@ -179,10 +181,11 @@ class ArchiveTestCase(TestCase, LoaderModuleMockMixin):
'cmd.run_all': run_all,
'archive.list': list_mock,
'file.source_list': mock_source_list}):
with patch.object(os.path, 'isfile', isfile_mock):
ret = archive.extracted('/tmp/out',
source,
options='xvzf',
enforce_toplevel=False,
keep=True)
self.assertEqual(ret['changes']['extracted_files'], 'stderr')
with patch.dict(archive.__states__, {'file.directory': mock_true}):
with patch.object(os.path, 'isfile', isfile_mock):
ret = archive.extracted('/tmp/out',
source,
options='xvzf',
enforce_toplevel=False,
keep=True)
self.assertEqual(ret['changes']['extracted_files'], 'stderr')

View File

@ -43,10 +43,18 @@ class DockerNetworkTestCase(TestCase, LoaderModuleMockMixin):
docker_create_network = Mock(return_value='created')
docker_connect_container_to_network = Mock(return_value='connected')
docker_inspect_container = Mock(return_value={'Id': 'abcd'})
# Get docker.networks to return a network with a name which is a superset of the name of
# the network which is to be created, despite this network existing we should still expect
# that the new network will be created.
# Regression test for #41982.
docker_networks = Mock(return_value=[{
'Name': 'network_foobar',
'Containers': {'container': {}}
}])
__salt__ = {'docker.create_network': docker_create_network,
'docker.inspect_container': docker_inspect_container,
'docker.connect_container_to_network': docker_connect_container_to_network,
'docker.networks': Mock(return_value=[]),
'docker.networks': docker_networks,
}
with patch.dict(docker_state.__dict__,
{'__salt__': __salt__}):
@ -72,10 +80,14 @@ class DockerNetworkTestCase(TestCase, LoaderModuleMockMixin):
'''
docker_remove_network = Mock(return_value='removed')
docker_disconnect_container_from_network = Mock(return_value='disconnected')
docker_networks = Mock(return_value=[{
'Name': 'network_foo',
'Containers': {'container': {}}
}])
__salt__ = {
'docker.remove_network': docker_remove_network,
'docker.disconnect_container_from_network': docker_disconnect_container_from_network,
'docker.networks': Mock(return_value=[{'Containers': {'container': {}}}]),
'docker.networks': docker_networks,
}
with patch.dict(docker_state.__dict__,
{'__salt__': __salt__}):
@ -88,3 +100,32 @@ class DockerNetworkTestCase(TestCase, LoaderModuleMockMixin):
'changes': {'disconnected': 'disconnected',
'removed': 'removed'},
'result': True})
def test_absent_with_matching_network(self):
'''
Test docker_network.absent when the specified network does not exist,
but another network with a name which is a superset of the specified
name does exist. In this case we expect there to be no attempt to remove
any network.
Regression test for #41982.
'''
docker_remove_network = Mock(return_value='removed')
docker_disconnect_container_from_network = Mock(return_value='disconnected')
docker_networks = Mock(return_value=[{
'Name': 'network_foobar',
'Containers': {'container': {}}
}])
__salt__ = {
'docker.remove_network': docker_remove_network,
'docker.disconnect_container_from_network': docker_disconnect_container_from_network,
'docker.networks': docker_networks,
}
with patch.dict(docker_state.__dict__,
{'__salt__': __salt__}):
ret = docker_state.absent('network_foo')
docker_disconnect_container_from_network.assert_not_called()
docker_remove_network.assert_not_called()
self.assertEqual(ret, {'name': 'network_foo',
'comment': 'Network \'network_foo\' already absent',
'changes': {},
'result': True})

View File

@ -122,7 +122,7 @@ class ModuleStateTest(TestCase, LoaderModuleMockMixin):
with patch.dict(module.__salt__, {CMD: _mocked_func_named}):
with patch.dict(module.__opts__, {'use_superseded': ['module.run']}):
ret = module.run(**{CMD: None})
assert ret['comment'] == "'{0}' failed: Missing arguments: name".format(CMD)
assert ret['comment'] == "'{0}' failed: Function expects 1 parameters, got only 0".format(CMD)
def test_run_correct_arg(self):
'''
@ -131,7 +131,7 @@ class ModuleStateTest(TestCase, LoaderModuleMockMixin):
'''
with patch.dict(module.__salt__, {CMD: _mocked_func_named}):
with patch.dict(module.__opts__, {'use_superseded': ['module.run']}):
ret = module.run(**{CMD: [{'name': 'Fred'}]})
ret = module.run(**{CMD: ['Fred']})
assert ret['comment'] == '{0}: Success'.format(CMD)
assert ret['result']

View File

@ -537,7 +537,7 @@ class TestCustomExtensions(TestCase):
env.filters.update(JinjaFilter.salt_jinja_filters)
if six.PY3:
rendered = env.from_string('{{ dataset|unique }}').render(dataset=dataset).strip("'{}").split("', '")
self.assertEqual(rendered, list(unique))
self.assertEqual(sorted(rendered), sorted(list(unique)))
else:
rendered = env.from_string('{{ dataset|unique }}').render(dataset=dataset)
self.assertEqual(rendered, u"{0}".format(unique))

View File

@ -89,13 +89,13 @@ SIG = (
class CryptTestCase(TestCase):
def test_gen_keys(self):
with patch.multiple(os, umask=MagicMock(), chmod=MagicMock(), chown=MagicMock,
with patch.multiple(os, umask=MagicMock(), chmod=MagicMock(),
access=MagicMock(return_value=True)):
with patch('salt.utils.files.fopen', mock_open()):
open_priv_wb = call('/keydir/keyname.pem', 'wb+')
open_pub_wb = call('/keydir/keyname.pub', 'wb+')
open_priv_wb = call('/keydir{0}keyname.pem'.format(os.sep), 'wb+')
open_pub_wb = call('/keydir{0}keyname.pub'.format(os.sep), 'wb+')
with patch('os.path.isfile', return_value=True):
self.assertEqual(crypt.gen_keys('/keydir', 'keyname', 2048), '/keydir/keyname.pem')
self.assertEqual(crypt.gen_keys('/keydir', 'keyname', 2048), '/keydir{0}keyname.pem'.format(os.sep))
self.assertNotIn(open_priv_wb, salt.utils.files.fopen.mock_calls)
self.assertNotIn(open_pub_wb, salt.utils.files.fopen.mock_calls)
with patch('os.path.isfile', return_value=False):

View File

@ -59,6 +59,7 @@ class DecoratorsTest(TestCase):
self.globs = {
'__virtualname__': 'test',
'__opts__': {},
'__pillar__': {},
'old_function': self.old_function,
'new_function': self.new_function,
'_new_function': self._new_function,
@ -149,6 +150,23 @@ class DecoratorsTest(TestCase):
['The function "test.new_function" is using its deprecated '
'version and will expire in version "Beryllium".'])
def test_with_deprecated_notfound_in_pillar(self):
'''
Test with_deprecated should raise an exception, if a same name
function with the "_" prefix not implemented.
:return:
'''
del self.globs['_new_function']
self.globs['__pillar__']['use_deprecated'] = ['test.new_function']
depr = decorators.with_deprecated(self.globs, "Beryllium")
depr._curr_version = self._mk_version("Helium")[1]
with self.assertRaises(CommandExecutionError):
depr(self.new_function)()
self.assertEqual(self.messages,
['The function "test.new_function" is using its deprecated '
'version and will expire in version "Beryllium".'])
def test_with_deprecated_found(self):
'''
Test with_deprecated should not raise an exception, if a same name
@ -166,6 +184,23 @@ class DecoratorsTest(TestCase):
'and will expire in version "Beryllium".']
self.assertEqual(self.messages, log_msg)
def test_with_deprecated_found_in_pillar(self):
'''
Test with_deprecated should not raise an exception, if a same name
function with the "_" prefix is implemented, but should use
an old version instead, if "use_deprecated" is requested.
:return:
'''
self.globs['__pillar__']['use_deprecated'] = ['test.new_function']
self.globs['_new_function'] = self.old_function
depr = decorators.with_deprecated(self.globs, "Beryllium")
depr._curr_version = self._mk_version("Helium")[1]
self.assertEqual(depr(self.new_function)(), self.old_function())
log_msg = ['The function "test.new_function" is using its deprecated version '
'and will expire in version "Beryllium".']
self.assertEqual(self.messages, log_msg)
def test_with_deprecated_found_eol(self):
'''
Test with_deprecated should raise an exception, if a same name
@ -185,6 +220,25 @@ class DecoratorsTest(TestCase):
'is configured as its deprecated version. The lifetime of the function '
'"new_function" expired. Please use its successor "new_function" instead.'])
def test_with_deprecated_found_eol_in_pillar(self):
'''
Test with_deprecated should raise an exception, if a same name
function with the "_" prefix is implemented, "use_deprecated" is requested
and EOL is reached.
:return:
'''
self.globs['__pillar__']['use_deprecated'] = ['test.new_function']
self.globs['_new_function'] = self.old_function
depr = decorators.with_deprecated(self.globs, "Helium")
depr._curr_version = self._mk_version("Beryllium")[1]
with self.assertRaises(CommandExecutionError):
depr(self.new_function)()
self.assertEqual(self.messages,
['Although function "new_function" is called, an alias "new_function" '
'is configured as its deprecated version. The lifetime of the function '
'"new_function" expired. Please use its successor "new_function" instead.'])
def test_with_deprecated_no_conf(self):
'''
Test with_deprecated should not raise an exception, if a same name
@ -260,6 +314,19 @@ class DecoratorsTest(TestCase):
assert depr(self.new_function)() == self.new_function()
assert not self.messages
def test_with_deprecated_opt_in_use_superseded_in_pillar(self):
'''
Test with_deprecated using opt-in policy,
where newer function is used as per configuration.
:return:
'''
self.globs['__pillar__']['use_superseded'] = ['test.new_function']
depr = decorators.with_deprecated(self.globs, "Beryllium", policy=decorators._DeprecationDecorator.OPT_IN)
depr._curr_version = self._mk_version("Helium")[1]
assert depr(self.new_function)() == self.new_function()
assert not self.messages
def test_with_deprecated_opt_in_use_superseded_and_deprecated(self):
'''
Test with_deprecated misconfiguration.
@ -272,3 +339,16 @@ class DecoratorsTest(TestCase):
depr._curr_version = self._mk_version("Helium")[1]
with self.assertRaises(SaltConfigurationError):
assert depr(self.new_function)() == self.new_function()
def test_with_deprecated_opt_in_use_superseded_and_deprecated_in_pillar(self):
'''
Test with_deprecated misconfiguration.
:return:
'''
self.globs['__pillar__']['use_deprecated'] = ['test.new_function']
self.globs['__pillar__']['use_superseded'] = ['test.new_function']
depr = decorators.with_deprecated(self.globs, "Beryllium")
depr._curr_version = self._mk_version("Helium")[1]
with self.assertRaises(SaltConfigurationError):
assert depr(self.new_function)() == self.new_function()