Merge branch '2017.7' into 2017.7

This commit is contained in:
mz-bmcqueen 2017-12-05 09:27:48 -08:00 committed by GitHub
commit 4ead3014b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 798 additions and 355 deletions

View File

@ -225,15 +225,16 @@ enclosing brackets ``[`` and ``]``:
Default: ``{}``
This can be used to control logging levels more specifically. The example sets
the main salt library at the 'warning' level, but sets ``salt.modules`` to log
at the ``debug`` level:
This can be used to control logging levels more specifically, based on log call name. The example sets
the main salt library at the 'warning' level, sets ``salt.modules`` to log
at the ``debug`` level, and sets a custom module to the ``all`` level:
.. code-block:: yaml
log_granular_levels:
'salt': 'warning'
'salt.modules': 'debug'
'salt.loader.saltmaster.ext.module.custom_module': 'all'
External Logging Handlers
-------------------------

View File

@ -303,6 +303,20 @@ option on the Salt master.
master_port: 4506
.. conf_minion:: publish_port
``publish_port``
---------------
Default: ``4505``
The port of the master publish server, this needs to coincide with the publish_port
option on the Salt master.
.. code-block:: yaml
publish_port: 4505
.. conf_minion:: user
``user``

View File

@ -314,3 +314,9 @@ Syncing grains can be done a number of ways, they are automatically synced when
above) the grains can be manually synced and reloaded by calling the
:mod:`saltutil.sync_grains <salt.modules.saltutil.sync_grains>` or
:mod:`saltutil.sync_all <salt.modules.saltutil.sync_all>` functions.
.. note::
When the :conf_minion:`grains_cache` is set to False, the grains dictionary is built
and stored in memory on the minion. Every time the minion restarts or
``saltutil.refresh_grains`` is run, the grain dictionary is rebuilt from scratch.

View File

@ -186,19 +186,60 @@ class Beacon(object):
else:
self.opts['beacons'][name].append({'enabled': enabled_value})
def list_beacons(self):
def _get_beacons(self,
include_opts=True,
include_pillar=True):
'''
Return the beacons data structure
'''
beacons = {}
if include_pillar:
pillar_beacons = self.opts.get('pillar', {}).get('beacons', {})
if not isinstance(pillar_beacons, dict):
raise ValueError('Beacons must be of type dict.')
beacons.update(pillar_beacons)
if include_opts:
opts_beacons = self.opts.get('beacons', {})
if not isinstance(opts_beacons, dict):
raise ValueError('Beacons must be of type dict.')
beacons.update(opts_beacons)
return beacons
def list_beacons(self,
include_pillar=True,
include_opts=True):
'''
List the beacon items
include_pillar: Whether to include beacons that are
configured in pillar, default is True.
include_opts: Whether to include beacons that are
configured in opts, default is True.
'''
beacons = self._get_beacons(include_pillar, include_opts)
# Fire the complete event back along with the list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts)
b_conf = self.functions['config.merge']('beacons')
self.opts['beacons'].update(b_conf)
evt.fire_event({'complete': True, 'beacons': self.opts['beacons']},
evt.fire_event({'complete': True, 'beacons': beacons},
tag='/salt/minion/minion_beacons_list_complete')
return True
def list_available_beacons(self):
'''
List the available beacons
'''
_beacons = ['{0}'.format(_beacon.replace('.beacon', ''))
for _beacon in self.beacons if '.beacon' in _beacon]
# Fire the complete event back along with the list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts)
evt.fire_event({'complete': True, 'beacons': _beacons},
tag='/salt/minion/minion_beacons_list_available_complete')
return True
def add_beacon(self, name, beacon_data):
'''
Add a beacon item
@ -207,16 +248,23 @@ class Beacon(object):
data = {}
data[name] = beacon_data
if name in self.opts['beacons']:
log.info('Updating settings for beacon '
'item: {0}'.format(name))
if name in self._get_beacons(include_opts=False):
comment = 'Cannot update beacon item {0}, ' \
'because it is configured in pillar.'.format(name)
complete = False
else:
log.info('Added new beacon item {0}'.format(name))
if name in self.opts['beacons']:
comment = 'Updating settings for beacon ' \
'item: {0}'.format(name)
else:
comment = 'Added new beacon item: {0}'.format(name)
complete = True
self.opts['beacons'].update(data)
# Fire the complete event back along with updated list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts)
evt.fire_event({'complete': True, 'beacons': self.opts['beacons']},
evt.fire_event({'complete': complete, 'comment': comment,
'beacons': self.opts['beacons']},
tag='/salt/minion/minion_beacon_add_complete')
return True
@ -229,15 +277,21 @@ class Beacon(object):
data = {}
data[name] = beacon_data
log.info('Updating settings for beacon '
'item: {0}'.format(name))
if name in self._get_beacons(include_opts=False):
comment = 'Cannot modify beacon item {0}, ' \
'it is configured in pillar.'.format(name)
complete = False
else:
comment = 'Updating settings for beacon ' \
'item: {0}'.format(name)
complete = True
self.opts['beacons'].update(data)
# Fire the complete event back along with updated list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts)
evt.fire_event({'complete': True, 'beacons': self.opts['beacons']},
evt.fire_event({'complete': complete, 'comment': comment,
'beacons': self.opts['beacons']},
tag='/salt/minion/minion_beacon_modify_complete')
return True
def delete_beacon(self, name):
@ -245,13 +299,22 @@ class Beacon(object):
Delete a beacon item
'''
if name in self._get_beacons(include_opts=False):
comment = 'Cannot delete beacon item {0}, ' \
'it is configured in pillar.'.format(name)
complete = False
else:
if name in self.opts['beacons']:
log.info('Deleting beacon item {0}'.format(name))
del self.opts['beacons'][name]
comment = 'Deleting beacon item: {0}'.format(name)
else:
comment = 'Beacon item {0} not found.'.format(name)
complete = True
# Fire the complete event back along with updated list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts)
evt.fire_event({'complete': True, 'beacons': self.opts['beacons']},
evt.fire_event({'complete': complete, 'comment': comment,
'beacons': self.opts['beacons']},
tag='/salt/minion/minion_beacon_delete_complete')
return True
@ -289,11 +352,19 @@ class Beacon(object):
Enable a beacon
'''
if name in self._get_beacons(include_opts=False):
comment = 'Cannot enable beacon item {0}, ' \
'it is configured in pillar.'.format(name)
complete = False
else:
self._update_enabled(name, True)
comment = 'Enabling beacon item {0}'.format(name)
complete = True
# Fire the complete event back along with updated list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts)
evt.fire_event({'complete': True, 'beacons': self.opts['beacons']},
evt.fire_event({'complete': complete, 'comment': comment,
'beacons': self.opts['beacons']},
tag='/salt/minion/minion_beacon_enabled_complete')
return True
@ -303,11 +374,19 @@ class Beacon(object):
Disable a beacon
'''
if name in self._get_beacons(include_opts=False):
comment = 'Cannot disable beacon item {0}, ' \
'it is configured in pillar.'.format(name)
complete = False
else:
self._update_enabled(name, False)
comment = 'Disabling beacon item {0}'.format(name)
complete = True
# Fire the complete event back along with updated list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts)
evt.fire_event({'complete': True, 'beacons': self.opts['beacons']},
evt.fire_event({'complete': complete, 'comment': comment,
'beacons': self.opts['beacons']},
tag='/salt/minion/minion_beacon_disabled_complete')
return True

View File

@ -1896,6 +1896,8 @@ class Minion(MinionBase):
func = data.get('func', None)
name = data.get('name', None)
beacon_data = data.get('beacon_data', None)
include_pillar = data.get(u'include_pillar', None)
include_opts = data.get(u'include_opts', None)
if func == 'add':
self.beacons.add_beacon(name, beacon_data)
@ -1912,7 +1914,9 @@ class Minion(MinionBase):
elif func == 'disable_beacon':
self.beacons.disable_beacon(name)
elif func == 'list':
self.beacons.list_beacons()
self.beacons.list_beacons(include_opts, include_pillar)
elif func == u'list_available':
self.beacons.list_available_beacons()
def environ_setenv(self, tag, data):
'''

View File

@ -27,11 +27,21 @@ __func_alias__ = {
}
def list_(return_yaml=True):
def list_(return_yaml=True,
include_pillar=True,
include_opts=True):
'''
List the beacons currently configured on the minion
:param return_yaml: Whether to return YAML formatted output, default True
:param return_yaml: Whether to return YAML formatted output,
default True
:param include_pillar: Whether to include beacons that are
configured in pillar, default is True.
:param include_opts: Whether to include beacons that are
configured in opts, default is True.
:return: List of currently configured Beacons.
CLI Example:
@ -45,7 +55,10 @@ def list_(return_yaml=True):
try:
eventer = salt.utils.event.get_event('minion', opts=__opts__)
res = __salt__['event.fire']({'func': 'list'}, 'manage_beacons')
res = __salt__['event.fire']({'func': 'list',
'include_pillar': include_pillar,
'include_opts': include_opts},
'manage_beacons')
if res:
event_ret = eventer.get_event(tag='/salt/minion/minion_beacons_list_complete', wait=30)
log.debug('event_ret {0}'.format(event_ret))
@ -69,6 +82,47 @@ def list_(return_yaml=True):
return {'beacons': {}}
def list_available(return_yaml=True):
'''
List the beacons currently available on the minion
:param return_yaml: Whether to return YAML formatted output, default True
:return: List of currently configured Beacons.
CLI Example:
.. code-block:: bash
salt '*' beacons.list_available
'''
beacons = None
try:
eventer = salt.utils.event.get_event('minion', opts=__opts__)
res = __salt__['event.fire']({'func': 'list_available'}, 'manage_beacons')
if res:
event_ret = eventer.get_event(tag='/salt/minion/minion_beacons_list_available_complete', wait=30)
if event_ret and event_ret['complete']:
beacons = event_ret['beacons']
except KeyError:
# Effectively a no-op, since we can't really return without an event system
ret = {}
ret['result'] = False
ret['comment'] = 'Event module not available. Beacon add failed.'
return ret
if beacons:
if return_yaml:
tmp = {'beacons': beacons}
yaml_out = yaml.safe_dump(tmp, default_flow_style=False)
return yaml_out
else:
return beacons
else:
return {'beacons': {}}
def add(name, beacon_data, **kwargs):
'''
Add a beacon on the minion
@ -91,6 +145,10 @@ def add(name, beacon_data, **kwargs):
ret['comment'] = 'Beacon {0} is already configured.'.format(name)
return ret
if name not in list_available(return_yaml=False):
ret['comment'] = 'Beacon "{0}" is not available.'.format(name)
return ret
if 'test' in kwargs and kwargs['test']:
ret['result'] = True
ret['comment'] = 'Beacon: {0} would be added.'.format(name)
@ -130,6 +188,9 @@ def add(name, beacon_data, **kwargs):
if name in beacons and beacons[name] == beacon_data:
ret['result'] = True
ret['comment'] = 'Added beacon: {0}.'.format(name)
else:
ret['result'] = False
ret['comment'] = event_ret['comment']
return ret
except KeyError:
# Effectively a no-op, since we can't really return without an event system
@ -215,6 +276,9 @@ def modify(name, beacon_data, **kwargs):
if name in beacons and beacons[name] == beacon_data:
ret['result'] = True
ret['comment'] = 'Modified beacon: {0}.'.format(name)
else:
ret['result'] = False
ret['comment'] = event_ret['comment']
return ret
except KeyError:
# Effectively a no-op, since we can't really return without an event system
@ -257,6 +321,9 @@ def delete(name, **kwargs):
ret['result'] = True
ret['comment'] = 'Deleted beacon: {0}.'.format(name)
return ret
else:
ret['result'] = False
ret['comment'] = event_ret['comment']
except KeyError:
# Effectively a no-op, since we can't really return without an event system
ret['comment'] = 'Event module not available. Beacon add failed.'
@ -279,7 +346,7 @@ def save():
ret = {'comment': [],
'result': True}
beacons = list_(return_yaml=False)
beacons = list_(return_yaml=False, include_pillar=False)
# move this file into an configurable opt
sfn = '{0}/{1}/beacons.conf'.format(__opts__['config_dir'],
@ -435,6 +502,9 @@ def enable_beacon(name, **kwargs):
else:
ret['result'] = False
ret['comment'] = 'Failed to enable beacon {0} on minion.'.format(name)
else:
ret['result'] = False
ret['comment'] = event_ret['comment']
return ret
except KeyError:
# Effectively a no-op, since we can't really return without an event system
@ -488,6 +558,9 @@ def disable_beacon(name, **kwargs):
else:
ret['result'] = False
ret['comment'] = 'Failed to disable beacon on minion.'
else:
ret['result'] = False
ret['comment'] = event_ret['comment']
return ret
except KeyError:
# Effectively a no-op, since we can't really return without an event system

View File

@ -1861,14 +1861,14 @@ def line(path, content=None, match=None, mode=None, location=None,
if changed:
if show_changes:
with salt.utils.fopen(path, 'r') as fp_:
path_content = _splitlines_preserving_trailing_newline(
fp_.read())
changes_diff = ''.join(difflib.unified_diff(
path_content, _splitlines_preserving_trailing_newline(body)))
path_content = fp_.read().splitlines(True)
changes_diff = ''.join(difflib.unified_diff(path_content, body.splitlines(True)))
if __opts__['test'] is False:
fh_ = None
try:
fh_ = salt.utils.atomicfile.atomic_open(path, 'w')
# Make sure we match the file mode from salt.utils.fopen
mode = 'wb' if six.PY2 and salt.utils.is_windows() else 'w'
fh_ = salt.utils.atomicfile.atomic_open(path, mode)
fh_.write(body)
finally:
if fh_:

View File

@ -24,7 +24,7 @@ Values or Entries
Values/Entries are name/data pairs. There can be many values in a key. The
(Default) value corresponds to the Key, the rest are their own value pairs.
:depends: - winreg Python module
:depends: - PyWin32
'''
# When production windows installer is using Python 3, Python 2 code can be removed
@ -35,14 +35,13 @@ from __future__ import unicode_literals
import sys
import logging
from salt.ext.six.moves import range # pylint: disable=W0622,import-error
from salt.ext import six
# Import third party libs
try:
from salt.ext.six.moves import winreg as _winreg # pylint: disable=import-error,no-name-in-module
from win32con import HWND_BROADCAST, WM_SETTINGCHANGE
from win32api import RegCreateKeyEx, RegSetValueEx, RegFlushKey, \
RegCloseKey, error as win32apiError, SendMessage
import win32gui
import win32api
import win32con
import pywintypes
HAS_WINDOWS_MODULES = True
except ImportError:
HAS_WINDOWS_MODULES = False
@ -60,7 +59,7 @@ __virtualname__ = 'reg'
def __virtual__():
'''
Only works on Windows systems with the _winreg python module
Only works on Windows systems with the PyWin32
'''
if not salt.utils.is_windows():
return (False, 'reg execution module failed to load: '
@ -69,106 +68,76 @@ def __virtual__():
if not HAS_WINDOWS_MODULES:
return (False, 'reg execution module failed to load: '
'One of the following libraries did not load: '
+ '_winreg, win32gui, win32con, win32api')
+ 'win32gui, win32con, win32api')
return __virtualname__
# winreg in python 2 is hard coded to use codex 'mbcs', which uses
# encoding that the user has assign. The function _unicode_to_mbcs
# and _unicode_to_mbcs help with this.
def _to_mbcs(vdata):
'''
Converts unicode to to current users character encoding. Use this for values
returned by reg functions
'''
return salt.utils.to_unicode(vdata, 'mbcs')
def _unicode_to_mbcs(instr):
def _to_unicode(vdata):
'''
Converts unicode to to current users character encoding.
Converts from current users character encoding to unicode. Use this for
parameters being pass to reg functions
'''
if isinstance(instr, six.text_type):
# unicode to windows utf8
return instr.encode('mbcs')
else:
# Assume its byte str or not a str/unicode
return instr
def _mbcs_to_unicode(instr):
'''
Converts from current users character encoding to unicode.
When instr has a value of None, the return value of the function
will also be None.
'''
if instr is None or isinstance(instr, six.text_type):
return instr
else:
return six.text_type(instr, 'mbcs')
def _mbcs_to_unicode_wrap(obj, vtype):
'''
Wraps _mbcs_to_unicode for use with registry vdata
'''
if vtype == 'REG_BINARY':
# We should be able to leave it alone if the user has passed binary data in yaml with
# binary !!
# In python < 3 this should have type str and in python 3+ this should be a byte array
return obj
if isinstance(obj, list):
return [_mbcs_to_unicode(x) for x in obj]
elif isinstance(obj, six.integer_types):
return obj
else:
return _mbcs_to_unicode(obj)
return salt.utils.to_unicode(vdata, 'utf-8')
class Registry(object): # pylint: disable=R0903
'''
Delay '_winreg' usage until this module is used
Delay usage until this module is used
'''
def __init__(self):
self.hkeys = {
'HKEY_CURRENT_USER': _winreg.HKEY_CURRENT_USER,
'HKEY_LOCAL_MACHINE': _winreg.HKEY_LOCAL_MACHINE,
'HKEY_USERS': _winreg.HKEY_USERS,
'HKCU': _winreg.HKEY_CURRENT_USER,
'HKLM': _winreg.HKEY_LOCAL_MACHINE,
'HKU': _winreg.HKEY_USERS,
'HKEY_CURRENT_USER': win32con.HKEY_CURRENT_USER,
'HKEY_LOCAL_MACHINE': win32con.HKEY_LOCAL_MACHINE,
'HKEY_USERS': win32con.HKEY_USERS,
'HKCU': win32con.HKEY_CURRENT_USER,
'HKLM': win32con.HKEY_LOCAL_MACHINE,
'HKU': win32con.HKEY_USERS,
}
self.vtype = {
'REG_BINARY': _winreg.REG_BINARY,
'REG_DWORD': _winreg.REG_DWORD,
'REG_EXPAND_SZ': _winreg.REG_EXPAND_SZ,
'REG_MULTI_SZ': _winreg.REG_MULTI_SZ,
'REG_SZ': _winreg.REG_SZ
'REG_BINARY': win32con.REG_BINARY,
'REG_DWORD': win32con.REG_DWORD,
'REG_EXPAND_SZ': win32con.REG_EXPAND_SZ,
'REG_MULTI_SZ': win32con.REG_MULTI_SZ,
'REG_SZ': win32con.REG_SZ,
'REG_QWORD': win32con.REG_QWORD
}
self.opttype = {
'REG_OPTION_NON_VOLATILE': _winreg.REG_OPTION_NON_VOLATILE,
'REG_OPTION_VOLATILE': _winreg.REG_OPTION_VOLATILE
'REG_OPTION_NON_VOLATILE': 0,
'REG_OPTION_VOLATILE': 1
}
# Return Unicode due to from __future__ import unicode_literals
self.vtype_reverse = {
_winreg.REG_BINARY: 'REG_BINARY',
_winreg.REG_DWORD: 'REG_DWORD',
_winreg.REG_EXPAND_SZ: 'REG_EXPAND_SZ',
_winreg.REG_MULTI_SZ: 'REG_MULTI_SZ',
_winreg.REG_SZ: 'REG_SZ',
# REG_QWORD isn't in the winreg library
11: 'REG_QWORD'
win32con.REG_BINARY: 'REG_BINARY',
win32con.REG_DWORD: 'REG_DWORD',
win32con.REG_EXPAND_SZ: 'REG_EXPAND_SZ',
win32con.REG_MULTI_SZ: 'REG_MULTI_SZ',
win32con.REG_SZ: 'REG_SZ',
win32con.REG_QWORD: 'REG_QWORD'
}
self.opttype_reverse = {
_winreg.REG_OPTION_NON_VOLATILE: 'REG_OPTION_NON_VOLATILE',
_winreg.REG_OPTION_VOLATILE: 'REG_OPTION_VOLATILE'
0: 'REG_OPTION_NON_VOLATILE',
1: 'REG_OPTION_VOLATILE'
}
# delete_key_recursive uses this to check the subkey contains enough \
# as we do not want to remove all or most of the registry
self.subkey_slash_check = {
_winreg.HKEY_CURRENT_USER: 0,
_winreg.HKEY_LOCAL_MACHINE: 1,
_winreg.HKEY_USERS: 1
win32con.HKEY_CURRENT_USER: 0,
win32con.HKEY_LOCAL_MACHINE: 1,
win32con.HKEY_USERS: 1
}
self.registry_32 = {
True: _winreg.KEY_READ | _winreg.KEY_WOW64_32KEY,
False: _winreg.KEY_READ,
True: win32con.KEY_READ | win32con.KEY_WOW64_32KEY,
False: win32con.KEY_READ,
}
def __getattr__(self, k):
@ -191,21 +160,16 @@ def _key_exists(hive, key, use_32bit_registry=False):
:return: Returns True if found, False if not found
:rtype: bool
'''
if PY2:
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
else:
local_hive = hive
local_key = key
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
registry = Registry()
hkey = registry.hkeys[local_hive]
access_mask = registry.registry_32[use_32bit_registry]
try:
handle = _winreg.OpenKey(hkey, local_key, 0, access_mask)
_winreg.CloseKey(handle)
handle = win32api.RegOpenKeyEx(hkey, local_key, 0, access_mask)
win32api.RegCloseKey(handle)
return True
except WindowsError: # pylint: disable=E0602
return False
@ -224,7 +188,10 @@ def broadcast_change():
salt '*' reg.broadcast_change
'''
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms644952(v=vs.85).aspx
return bool(SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, 0))
_, res = win32gui.SendMessageTimeout(
win32con.HWND_BROADCAST, win32con.WM_SETTINGCHANGE, 0, 0,
win32con.SMTO_ABORTIFHUNG, 5000)
return not bool(res)
def list_keys(hive, key=None, use_32bit_registry=False):
@ -253,12 +220,8 @@ def list_keys(hive, key=None, use_32bit_registry=False):
salt '*' reg.list_keys HKLM 'SOFTWARE'
'''
if PY2:
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
else:
local_hive = hive
local_key = key
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
registry = Registry()
hkey = registry.hkeys[local_hive]
@ -266,12 +229,12 @@ def list_keys(hive, key=None, use_32bit_registry=False):
subkeys = []
try:
handle = _winreg.OpenKey(hkey, local_key, 0, access_mask)
handle = win32api.RegOpenKeyEx(hkey, local_key, 0, access_mask)
for i in range(_winreg.QueryInfoKey(handle)[0]):
subkey = _winreg.EnumKey(handle, i)
for i in range(win32api.RegQueryInfoKey(handle)[0]):
subkey = win32api.RegEnumKey(handle, i)
if PY2:
subkeys.append(_mbcs_to_unicode(subkey))
subkeys.append(_to_unicode(subkey))
else:
subkeys.append(subkey)
@ -312,13 +275,8 @@ def list_values(hive, key=None, use_32bit_registry=False, include_default=True):
salt '*' reg.list_values HKLM 'SYSTEM\\CurrentControlSet\\Services\\Tcpip'
'''
if PY2:
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
else:
local_hive = hive
local_key = key
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
registry = Registry()
hkey = registry.hkeys[local_hive]
@ -327,37 +285,21 @@ def list_values(hive, key=None, use_32bit_registry=False, include_default=True):
values = list()
try:
handle = _winreg.OpenKey(hkey, local_key, 0, access_mask)
handle = win32api.RegOpenKeyEx(hkey, local_key, 0, access_mask)
for i in range(_winreg.QueryInfoKey(handle)[1]):
vname, vdata, vtype = _winreg.EnumValue(handle, i)
for i in range(win32api.RegQueryInfoKey(handle)[1]):
vname, vdata, vtype = win32api.RegEnumValue(handle, i)
if not vname:
vname = "(Default)"
value = {'hive': local_hive,
'key': local_key,
'vname': vname,
'vdata': vdata,
'vname': _to_mbcs(vname),
'vdata': _to_mbcs(vdata),
'vtype': registry.vtype_reverse[vtype],
'success': True}
values.append(value)
if include_default:
# Get the default value for the key
value = {'hive': local_hive,
'key': local_key,
'vname': '(Default)',
'vdata': None,
'success': True}
try:
# QueryValueEx returns unicode data
vdata, vtype = _winreg.QueryValueEx(handle, '(Default)')
if vdata or vdata in [0, '']:
value['vtype'] = registry.vtype_reverse[vtype]
value['vdata'] = vdata
else:
value['comment'] = 'Empty Value'
except WindowsError: # pylint: disable=E0602
value['vdata'] = ('(value not set)')
value['vtype'] = 'REG_SZ'
values.append(value)
except WindowsError as exc: # pylint: disable=E0602
log.debug(exc)
log.debug(r'Cannot find key: {0}\{1}'.format(hive, key))
@ -403,30 +345,19 @@ def read_value(hive, key, vname=None, use_32bit_registry=False):
salt '*' reg.read_value HKEY_LOCAL_MACHINE 'SOFTWARE\Salt' 'version'
'''
# If no name is passed, the default value of the key will be returned
# The value name is Default
# Setup the return array
if PY2:
ret = {'hive': _mbcs_to_unicode(hive),
'key': _mbcs_to_unicode(key),
'vname': _mbcs_to_unicode(vname),
'vdata': None,
'success': True}
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
local_vname = _unicode_to_mbcs(vname)
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
local_vname = _to_unicode(vname)
else:
ret = {'hive': hive,
'key': key,
'vname': vname,
ret = {'hive': local_hive,
'key': local_key,
'vname': local_vname,
'vdata': None,
'success': True}
local_hive = hive
local_key = key
local_vname = vname
if not vname:
ret['vname'] = '(Default)'
@ -436,19 +367,22 @@ def read_value(hive, key, vname=None, use_32bit_registry=False):
access_mask = registry.registry_32[use_32bit_registry]
try:
handle = _winreg.OpenKey(hkey, local_key, 0, access_mask)
handle = win32api.RegOpenKeyEx(hkey, local_key, 0, access_mask)
try:
# QueryValueEx returns unicode data
vdata, vtype = _winreg.QueryValueEx(handle, local_vname)
# RegQueryValueEx returns and accepts unicode data
vdata, vtype = win32api.RegQueryValueEx(handle, local_vname)
if vdata or vdata in [0, '']:
ret['vtype'] = registry.vtype_reverse[vtype]
ret['vdata'] = vdata
if vtype == 7:
ret['vdata'] = [_to_mbcs(i) for i in vdata]
else:
ret['vdata'] = _to_mbcs(vdata)
else:
ret['comment'] = 'Empty Value'
except WindowsError: # pylint: disable=E0602
ret['vdata'] = ('(value not set)')
ret['vtype'] = 'REG_SZ'
except WindowsError as exc: # pylint: disable=E0602
except pywintypes.error as exc: # pylint: disable=E0602
log.debug(exc)
log.debug('Cannot find key: {0}\\{1}'.format(local_hive, local_key))
ret['comment'] = 'Cannot find key: {0}\\{1}'.format(local_hive, local_key)
@ -555,42 +489,47 @@ def set_value(hive,
salt '*' reg.set_value HKEY_LOCAL_MACHINE 'SOFTWARE\\Salt' 'version' '2015.5.2' \\
vtype=REG_LIST vdata='[a,b,c]'
'''
if PY2:
try:
local_hive = _mbcs_to_unicode(hive)
local_key = _mbcs_to_unicode(key)
local_vname = _mbcs_to_unicode(vname)
local_vtype = _mbcs_to_unicode(vtype)
local_vdata = _mbcs_to_unicode_wrap(vdata, local_vtype)
except TypeError as exc: # pylint: disable=E0602
log.error(exc, exc_info=True)
return False
else:
local_hive = hive
local_key = key
local_vname = vname
local_vdata = vdata
local_vtype = vtype
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
local_vname = _to_unicode(vname)
local_vtype = _to_unicode(vtype)
registry = Registry()
hkey = registry.hkeys[local_hive]
vtype_value = registry.vtype[local_vtype]
access_mask = registry.registry_32[use_32bit_registry] | _winreg.KEY_ALL_ACCESS
access_mask = registry.registry_32[use_32bit_registry] | win32con.KEY_ALL_ACCESS
# Check data type and cast to expected type
# int will automatically become long on 64bit numbers
# https://www.python.org/dev/peps/pep-0237/
# String Types to Unicode
if vtype_value in [1, 2]:
local_vdata = _to_unicode(vdata)
# Don't touch binary...
elif vtype_value == 3:
local_vdata = vdata
# Make sure REG_MULTI_SZ is a list of strings
elif vtype_value == 7:
local_vdata = [_to_unicode(i) for i in vdata]
# Everything else is int
else:
local_vdata = int(vdata)
if volatile:
create_options = registry.opttype['REG_OPTION_VOLATILE']
else:
create_options = registry.opttype['REG_OPTION_NON_VOLATILE']
try:
handle, _ = RegCreateKeyEx(hkey, local_key, access_mask,
handle, _ = win32api.RegCreateKeyEx(hkey, local_key, access_mask,
Options=create_options)
RegSetValueEx(handle, local_vname, 0, vtype_value, local_vdata)
RegFlushKey(handle)
RegCloseKey(handle)
win32api.RegSetValueEx(handle, local_vname, 0, vtype_value, local_vdata)
win32api.RegFlushKey(handle)
win32api.RegCloseKey(handle)
broadcast_change()
return True
except (win32apiError, SystemError, ValueError, TypeError) as exc: # pylint: disable=E0602
except (win32api.error, SystemError, ValueError, TypeError) as exc: # pylint: disable=E0602
log.error(exc, exc_info=True)
return False
@ -626,18 +565,14 @@ def delete_key_recursive(hive, key, use_32bit_registry=False):
salt '*' reg.delete_key_recursive HKLM SOFTWARE\\salt
'''
if PY2:
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
else:
local_hive = hive
local_key = key
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
# Instantiate the registry object
registry = Registry()
hkey = registry.hkeys[local_hive]
key_path = local_key
access_mask = registry.registry_32[use_32bit_registry] | _winreg.KEY_ALL_ACCESS
access_mask = registry.registry_32[use_32bit_registry] | win32con.KEY_ALL_ACCESS
if not _key_exists(local_hive, local_key, use_32bit_registry):
return False
@ -654,17 +589,17 @@ def delete_key_recursive(hive, key, use_32bit_registry=False):
i = 0
while True:
try:
subkey = _winreg.EnumKey(_key, i)
subkey = win32api.RegEnumKey(_key, i)
yield subkey
i += 1
except WindowsError: # pylint: disable=E0602
except pywintypes.error: # pylint: disable=E0602
break
def _traverse_registry_tree(_hkey, _keypath, _ret, _access_mask):
'''
Traverse the registry tree i.e. dive into the tree
'''
_key = _winreg.OpenKey(_hkey, _keypath, 0, _access_mask)
_key = win32api.RegOpenKeyEx(_hkey, _keypath, 0, _access_mask)
for subkeyname in _subkeys(_key):
subkeypath = r'{0}\{1}'.format(_keypath, subkeyname)
_ret = _traverse_registry_tree(_hkey, subkeypath, _ret, access_mask)
@ -683,8 +618,8 @@ def delete_key_recursive(hive, key, use_32bit_registry=False):
# Delete all sub_keys
for sub_key_path in key_list:
try:
key_handle = _winreg.OpenKey(hkey, sub_key_path, 0, access_mask)
_winreg.DeleteKey(key_handle, '')
key_handle = win32api.RegOpenKeyEx(hkey, sub_key_path, 0, access_mask)
win32api.RegDeleteKey(key_handle, '')
ret['Deleted'].append(r'{0}\{1}'.format(hive, sub_key_path))
except WindowsError as exc: # pylint: disable=E0602
log.error(exc, exc_info=True)
@ -723,23 +658,18 @@ def delete_value(hive, key, vname=None, use_32bit_registry=False):
salt '*' reg.delete_value HKEY_CURRENT_USER 'SOFTWARE\\Salt' 'version'
'''
if PY2:
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
local_vname = _unicode_to_mbcs(vname)
else:
local_hive = hive
local_key = key
local_vname = vname
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
local_vname = _to_unicode(vname)
registry = Registry()
hkey = registry.hkeys[local_hive]
access_mask = registry.registry_32[use_32bit_registry] | _winreg.KEY_ALL_ACCESS
access_mask = registry.registry_32[use_32bit_registry] | win32con.KEY_ALL_ACCESS
try:
handle = _winreg.OpenKey(hkey, local_key, 0, access_mask)
_winreg.DeleteValue(handle, local_vname)
_winreg.CloseKey(handle)
handle = win32api.RegOpenKeyEx(hkey, local_key, 0, access_mask)
win32api.RegDeleteValue(handle, local_vname)
win32api.RegCloseKey(handle)
broadcast_change()
return True
except WindowsError as exc: # pylint: disable=E0602

View File

@ -493,6 +493,18 @@ def apply_(mods=None,
Values passed this way will override Pillar values set via
``pillar_roots`` or an external Pillar source.
exclude
Exclude specific states from execution. Accepts a list of sls names, a
comma-separated string of sls names, or a list of dictionaries
containing ``sls`` or ``id`` keys. Glob-patterns may be used to match
multiple states.
.. code-block:: bash
salt '*' state.apply exclude=bar,baz
salt '*' state.apply exclude=foo*
salt '*' state.apply exclude="[{'id': 'id_to_exclude'}, {'sls': 'sls_to_exclude'}]"
queue : False
Instead of failing immediately when another state run is in progress,
queue the new state run to begin running once the other has finished.
@ -758,6 +770,18 @@ def highstate(test=None, queue=False, **kwargs):
.. versionadded:: 2016.3.0
exclude
Exclude specific states from execution. Accepts a list of sls names, a
comma-separated string of sls names, or a list of dictionaries
containing ``sls`` or ``id`` keys. Glob-patterns may be used to match
multiple states.
.. code-block:: bash
salt '*' state.higstate exclude=bar,baz
salt '*' state.higstate exclude=foo*
salt '*' state.highstate exclude="[{'id': 'id_to_exclude'}, {'sls': 'sls_to_exclude'}]"
saltenv
Specify a salt fileserver environment to be used when applying states
@ -935,6 +959,18 @@ def sls(mods, test=None, exclude=None, queue=False, **kwargs):
.. versionadded:: 2016.3.0
exclude
Exclude specific states from execution. Accepts a list of sls names, a
comma-separated string of sls names, or a list of dictionaries
containing ``sls`` or ``id`` keys. Glob-patterns may be used to match
multiple states.
.. code-block:: bash
salt '*' state.sls foo,bar,baz exclude=bar,baz
salt '*' state.sls foo,bar,baz exclude=ba*
salt '*' state.sls foo,bar,baz exclude="[{'id': 'id_to_exclude'}, {'sls': 'sls_to_exclude'}]"
queue : False
Instead of failing immediately when another state run is in progress,
queue the new state run to begin running once the other has finished.

View File

@ -58,7 +58,7 @@ from salt.modules.file import (check_hash, # pylint: disable=W0611
lstat, path_exists_glob, write, pardir, join, HASHES, HASHES_REVMAP,
comment, uncomment, _add_flags, comment_line, _regex_to_static,
_get_line_indent, apply_template_on_contents, dirname, basename,
list_backups_dir)
list_backups_dir, _assert_occurrence, _starts_till)
from salt.modules.file import normpath as normpath_
from salt.utils import namespaced_function as _namespaced_function
@ -116,7 +116,7 @@ def __virtual__():
global write, pardir, join, _add_flags, apply_template_on_contents
global path_exists_glob, comment, uncomment, _mkstemp_copy
global _regex_to_static, _get_line_indent, dirname, basename
global list_backups_dir, normpath_
global list_backups_dir, normpath_, _assert_occurrence, _starts_till
replace = _namespaced_function(replace, globals())
search = _namespaced_function(search, globals())
@ -179,6 +179,8 @@ def __virtual__():
basename = _namespaced_function(basename, globals())
list_backups_dir = _namespaced_function(list_backups_dir, globals())
normpath_ = _namespaced_function(normpath_, globals())
_assert_occurrence = _namespaced_function(_assert_occurrence, globals())
_starts_till = _namespaced_function(_starts_till, globals())
else:
return False, 'Module win_file: Missing Win32 modules'

View File

@ -39,10 +39,11 @@ import logging
import os
import re
import time
import sys
from functools import cmp_to_key
# Import third party libs
import salt.ext.six as six
from salt.ext import six
# pylint: disable=import-error,no-name-in-module
from salt.ext.six.moves.urllib.parse import urlparse as _urlparse
@ -50,9 +51,12 @@ from salt.ext.six.moves.urllib.parse import urlparse as _urlparse
from salt.exceptions import (CommandExecutionError,
SaltInvocationError,
SaltRenderError)
import salt.utils
import salt.utils.pkg
import salt.utils # Can be removed once is_true, get_hash, compare_dicts are moved
import salt.utils.args
import salt.utils.files
import salt.utils.path
import salt.utils.pkg
import salt.utils.versions
import salt.syspaths
import salt.payload
from salt.exceptions import MinionError
@ -99,7 +103,7 @@ def latest_version(*names, **kwargs):
salt '*' pkg.latest_version <package name>
salt '*' pkg.latest_version <package1> <package2> <package3> ...
'''
if len(names) == 0:
if not names:
return ''
# Initialize the return dict with empty strings
@ -124,6 +128,8 @@ def latest_version(*names, **kwargs):
if name in installed_pkgs:
log.trace('Determining latest installed version of %s', name)
try:
# installed_pkgs[name] Can be version number or 'Not Found'
# 'Not Found' occurs when version number is not found in the registry
latest_installed = sorted(
installed_pkgs[name],
key=cmp_to_key(_reverse_cmp_pkg_versions)
@ -140,6 +146,8 @@ def latest_version(*names, **kwargs):
# get latest available (from winrepo_dir) version of package
pkg_info = _get_package_info(name, saltenv=saltenv)
log.trace('Raw winrepo pkg_info for {0} is {1}'.format(name, pkg_info))
# latest_available can be version number or 'latest' or even 'Not Found'
latest_available = _get_latest_pkg_version(pkg_info)
if latest_available:
log.debug('Latest available version '
@ -147,7 +155,7 @@ def latest_version(*names, **kwargs):
# check, whether latest available version
# is newer than latest installed version
if salt.utils.compare_versions(ver1=str(latest_available),
if compare_versions(ver1=str(latest_available),
oper='>',
ver2=str(latest_installed)):
log.debug('Upgrade of {0} from {1} to {2} '
@ -188,10 +196,9 @@ def upgrade_available(name, **kwargs):
# same default as latest_version
refresh = salt.utils.is_true(kwargs.get('refresh', True))
current = version(name, saltenv=saltenv, refresh=refresh).get(name)
latest = latest_version(name, saltenv=saltenv, refresh=False)
return compare_versions(latest, '>', current)
# if latest_version returns blank, the latest version is already installed or
# their is no package definition. This is a salt standard which could be improved.
return latest_version(name, saltenv=saltenv, refresh=refresh) != ''
def list_upgrades(refresh=True, **kwargs):
@ -222,9 +229,13 @@ def list_upgrades(refresh=True, **kwargs):
pkgs = {}
for pkg in installed_pkgs:
if pkg in available_pkgs:
# latest_version() will be blank if the latest version is installed.
# or the package name is wrong. Given we check available_pkgs, this
# should not be the case of wrong package name.
# Note: latest_version() is an expensive way to do this as it
# calls list_pkgs each time.
latest_ver = latest_version(pkg, refresh=False, saltenv=saltenv)
install_ver = installed_pkgs[pkg]
if compare_versions(latest_ver, '>', install_ver):
if latest_ver:
pkgs[pkg] = latest_ver
return pkgs
@ -241,7 +252,7 @@ def list_available(*names, **kwargs):
saltenv (str): The salt environment to use. Default ``base``.
refresh (bool): Refresh package metadata. Default ``True``.
refresh (bool): Refresh package metadata. Default ``False``.
return_dict_always (bool):
Default ``False`` dict when a single package name is queried.
@ -264,7 +275,7 @@ def list_available(*names, **kwargs):
return ''
saltenv = kwargs.get('saltenv', 'base')
refresh = salt.utils.is_true(kwargs.get('refresh', True))
refresh = salt.utils.is_true(kwargs.get('refresh', False))
return_dict_always = \
salt.utils.is_true(kwargs.get('return_dict_always', False))
@ -293,7 +304,9 @@ def list_available(*names, **kwargs):
def version(*names, **kwargs):
'''
Returns a version if the package is installed, else returns an empty string
Returns a string representing the package version or an empty string if not
installed. If more than one package name is specified, a dict of
name/version pairs is returned.
Args:
name (str): One or more package names
@ -303,10 +316,12 @@ def version(*names, **kwargs):
refresh (bool): Refresh package metadata. Default ``False``.
Returns:
str: version string when a single package is specified.
dict: The package name(s) with the installed versions.
.. code-block:: cfg
.. code-block:: cfg
{['<version>', '<version>', ]} OR
{'<package name>': ['<version>', '<version>', ]}
CLI Example:
@ -315,19 +330,25 @@ def version(*names, **kwargs):
salt '*' pkg.version <package name>
salt '*' pkg.version <package name01> <package name02>
'''
saltenv = kwargs.get('saltenv', 'base')
installed_pkgs = list_pkgs(refresh=kwargs.get('refresh', False))
available_pkgs = get_repo_data(saltenv).get('repo')
'''
# Standard is return empty string even if not a valid name
# TODO: Look at returning an error across all platforms with
# CommandExecutionError(msg,info={'errors': errors })
# available_pkgs = get_repo_data(saltenv).get('repo')
# for name in names:
# if name in available_pkgs:
# ret[name] = installed_pkgs.get(name, '')
saltenv = kwargs.get('saltenv', 'base')
installed_pkgs = list_pkgs(saltenv=saltenv, refresh=kwargs.get('refresh', False))
if len(names) == 1:
return installed_pkgs.get(names[0], '')
ret = {}
for name in names:
if name in available_pkgs:
ret[name] = installed_pkgs.get(name, '')
else:
ret[name] = 'not available'
return ret
@ -336,7 +357,6 @@ def list_pkgs(versions_as_list=False, **kwargs):
List the packages currently installed
Args:
version_as_list (bool): Returns the versions as a list
Kwargs:
saltenv (str): The salt environment to use. Default ``base``.
@ -424,7 +444,7 @@ def _get_reg_software():
'(value not set)',
'',
None]
#encoding = locale.getpreferredencoding()
reg_software = {}
hive = 'HKLM'
@ -462,7 +482,7 @@ def _get_reg_software():
def _refresh_db_conditional(saltenv, **kwargs):
'''
Internal use only in this module, has a different set of defaults and
returns True or False. And supports check the age of the existing
returns True or False. And supports checking the age of the existing
generated metadata db, as well as ensure metadata db exists to begin with
Args:
@ -476,8 +496,7 @@ def _refresh_db_conditional(saltenv, **kwargs):
failhard (bool):
If ``True``, an error will be raised if any repo SLS files failed to
process. If ``False``, no error will be raised, and a dictionary
containing the full results will be returned.
process.
Returns:
bool: True Fetched or Cache uptodate, False to indicate an issue
@ -695,8 +714,8 @@ def genrepo(**kwargs):
verbose (bool):
Return verbose data structure which includes 'success_list', a list
of all sls files and the package names contained within. Default
'False'
of all sls files and the package names contained within.
Default ``False``.
failhard (bool):
If ``True``, an error will be raised if any repo SLS files failed
@ -739,11 +758,13 @@ def genrepo(**kwargs):
successful_verbose
)
serial = salt.payload.Serial(__opts__)
# TODO: 2016.11 has PY2 mode as 'w+b' develop has 'w+' ? PY3 is 'wb+'
# also the reading of this is 'rb' in get_repo_data()
mode = 'w+' if six.PY2 else 'wb+'
with salt.utils.fopen(repo_details.winrepo_file, mode) as repo_cache:
repo_cache.write(serial.dumps(ret))
# save reading it back again. ! this breaks due to utf8 issues
#__context__['winrepo.data'] = ret
# For some reason we can not save ret into __context__['winrepo.data'] as this breaks due to utf8 issues
successful_count = len(successful_verbose)
error_count = len(ret['errors'])
if verbose:
@ -778,7 +799,7 @@ def genrepo(**kwargs):
return results
def _repo_process_pkg_sls(file, short_path_name, ret, successful_verbose):
def _repo_process_pkg_sls(filename, short_path_name, ret, successful_verbose):
renderers = salt.loader.render(__opts__, __salt__)
def _failed_compile(msg):
@ -788,7 +809,7 @@ def _repo_process_pkg_sls(file, short_path_name, ret, successful_verbose):
try:
config = salt.template.compile_template(
file,
filename,
renderers,
__opts__['renderer'],
__opts__.get('renderer_blacklist', ''),
@ -803,7 +824,6 @@ def _repo_process_pkg_sls(file, short_path_name, ret, successful_verbose):
if config:
revmap = {}
errors = []
pkgname_ok_list = []
for pkgname, versions in six.iteritems(config):
if pkgname in ret['repo']:
log.error(
@ -812,12 +832,12 @@ def _repo_process_pkg_sls(file, short_path_name, ret, successful_verbose):
)
errors.append('package \'{0}\' already defined'.format(pkgname))
break
for version, repodata in six.iteritems(versions):
for version_str, repodata in six.iteritems(versions):
# Ensure version is a string/unicode
if not isinstance(version, six.string_types):
if not isinstance(version_str, six.string_types):
msg = (
'package \'{0}\'{{0}}, version number {1} '
'is not a string'.format(pkgname, version)
'is not a string'.format(pkgname, version_str)
)
log.error(
msg.format(' within \'{0}\''.format(short_path_name))
@ -829,7 +849,7 @@ def _repo_process_pkg_sls(file, short_path_name, ret, successful_verbose):
msg = (
'package \'{0}\'{{0}}, repo data for '
'version number {1} is not defined as a dictionary '
.format(pkgname, version)
.format(pkgname, version_str)
)
log.error(
msg.format(' within \'{0}\''.format(short_path_name))
@ -840,8 +860,6 @@ def _repo_process_pkg_sls(file, short_path_name, ret, successful_verbose):
if errors:
ret.setdefault('errors', {})[short_path_name] = errors
else:
if pkgname not in pkgname_ok_list:
pkgname_ok_list.append(pkgname)
ret.setdefault('repo', {}).update(config)
ret.setdefault('name_map', {}).update(revmap)
successful_verbose[short_path_name] = config.keys()
@ -916,7 +934,8 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
to install. (no spaces after the commas)
refresh (bool):
Boolean value representing whether or not to refresh the winrepo db
Boolean value representing whether or not to refresh the winrepo db.
Default ``False``.
pkgs (list):
A list of packages to install from a software repository. All
@ -1072,7 +1091,7 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
for pkg in pkg_params:
pkg_params[pkg] = {'version': pkg_params[pkg]}
if pkg_params is None or len(pkg_params) == 0:
if not pkg_params:
log.error('No package definition found')
return {}
@ -1114,11 +1133,12 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
version_num = str(version_num)
if not version_num:
# following can be version number or latest
version_num = _get_latest_pkg_version(pkginfo)
# Check if the version is already installed
if version_num in old.get(pkg_name, '').split(',') \
or (old.get(pkg_name) == 'Not Found'):
or (old.get(pkg_name, '') == 'Not Found'):
# Desired version number already installed
ret[pkg_name] = {'current': version_num}
continue
@ -1244,32 +1264,32 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
options.get('extra_install_flags', '')
)
#Compute msiexec string
# Compute msiexec string
use_msiexec, msiexec = _get_msiexec(pkginfo[version_num].get('msiexec', False))
# Build cmd and arguments
# cmd and arguments must be separated for use with the task scheduler
cmd_shell = os.getenv('ComSpec', '{0}\\system32\\cmd.exe'.format(os.getenv('WINDIR')))
if use_msiexec:
cmd = msiexec
arguments = ['/i', cached_pkg]
arguments = '"{0}" /I "{1}"'.format(msiexec, cached_pkg)
if pkginfo[version_num].get('allusers', True):
arguments.append('ALLUSERS="1"')
arguments.extend(salt.utils.shlex_split(install_flags, posix=False))
arguments = '{0} ALLUSERS=1'.format(arguments)
else:
cmd = cached_pkg
arguments = salt.utils.shlex_split(install_flags, posix=False)
arguments = '"{0}"'.format(cached_pkg)
if install_flags:
arguments = '{0} {1}'.format(arguments, install_flags)
# Install the software
# Check Use Scheduler Option
if pkginfo[version_num].get('use_scheduler', False):
# Create Scheduled Task
__salt__['task.create_task'](name='update-salt-software',
user_name='System',
force=True,
action_type='Execute',
cmd=cmd,
arguments=' '.join(arguments),
cmd=cmd_shell,
arguments='/s /c "{0}"'.format(arguments),
start_in=cache_path,
trigger_type='Once',
start_date='1975-01-01',
@ -1312,13 +1332,11 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
ret[pkg_name] = {'install status': 'failed'}
else:
# Combine cmd and arguments
cmd = [cmd]
cmd.extend(arguments)
# Launch the command
result = __salt__['cmd.run_all'](cmd,
result = __salt__['cmd.run_all'](
'"{0}" /s /c "{1}"'.format(cmd_shell, arguments),
cache_path,
output_loglevel='trace',
python_shell=False,
redirect_stderr=True)
if not result['retcode']:
@ -1397,14 +1415,17 @@ def remove(name=None, pkgs=None, version=None, **kwargs):
.. versionadded:: 0.16.0
Args:
name (str): The name(s) of the package(s) to be uninstalled. Can be a
single package or a comma delimted list of packages, no spaces.
name (str):
The name(s) of the package(s) to be uninstalled. Can be a
single package or a comma delimited list of packages, no spaces.
version (str):
The version of the package to be uninstalled. If this option is
used to to uninstall multiple packages, then this version will be
applied to all targeted packages. Recommended using only when
uninstalling a single package. If this parameter is omitted, the
latest version will be uninstalled.
pkgs (list):
A list of packages to delete. Must be passed as a python list. The
``name`` parameter will be ignored if this option is passed.
@ -1541,6 +1562,7 @@ def remove(name=None, pkgs=None, version=None, **kwargs):
# Compare the hash of the cached installer to the source only if
# the file is hosted on salt:
# TODO cp.cache_file does cache and hash checking? So why do it again?
if uninstaller.startswith('salt:'):
if __salt__['cp.hash_file'](uninstaller, saltenv) != \
__salt__['cp.hash_file'](cached_pkg):
@ -1566,6 +1588,7 @@ def remove(name=None, pkgs=None, version=None, **kwargs):
# Get parameters for cmd
expanded_cached_pkg = str(os.path.expandvars(cached_pkg))
expanded_cache_path = str(os.path.expandvars(cache_path))
# Get uninstall flags
uninstall_flags = pkginfo[target].get('uninstall_flags', '')
@ -1574,31 +1597,31 @@ def remove(name=None, pkgs=None, version=None, **kwargs):
uninstall_flags = '{0} {1}'.format(
uninstall_flags, kwargs.get('extra_uninstall_flags', ''))
#Compute msiexec string
# Compute msiexec string
use_msiexec, msiexec = _get_msiexec(pkginfo[target].get('msiexec', False))
cmd_shell = os.getenv('ComSpec', '{0}\\system32\\cmd.exe'.format(os.getenv('WINDIR')))
# Build cmd and arguments
# cmd and arguments must be separated for use with the task scheduler
if use_msiexec:
cmd = msiexec
arguments = ['/x']
arguments.extend(salt.utils.shlex_split(uninstall_flags, posix=False))
arguments = '"{0}" /X "{1}"'.format(msiexec, uninstaller if uninstaller else expanded_cached_pkg)
else:
cmd = expanded_cached_pkg
arguments = salt.utils.shlex_split(uninstall_flags, posix=False)
arguments = '"{0}"'.format(expanded_cached_pkg)
if uninstall_flags:
arguments = '{0} {1}'.format(arguments, uninstall_flags)
# Uninstall the software
# Check Use Scheduler Option
if pkginfo[target].get('use_scheduler', False):
# Create Scheduled Task
__salt__['task.create_task'](name='update-salt-software',
user_name='System',
force=True,
action_type='Execute',
cmd=cmd,
arguments=' '.join(arguments),
start_in=cache_path,
cmd=cmd_shell,
arguments='/s /c "{0}"'.format(arguments),
start_in=expanded_cache_path,
trigger_type='Once',
start_date='1975-01-01',
start_time='01:00',
@ -1610,13 +1633,11 @@ def remove(name=None, pkgs=None, version=None, **kwargs):
log.error('Scheduled Task failed to run')
ret[pkgname] = {'uninstall status': 'failed'}
else:
# Build the install command
cmd = [cmd]
cmd.extend(arguments)
# Launch the command
result = __salt__['cmd.run_all'](
cmd,
'"{0}" /s /c "{1}"'.format(cmd_shell, arguments),
expanded_cache_path,
output_loglevel='trace',
python_shell=False,
redirect_stderr=True)
if not result['retcode']:
@ -1662,11 +1683,13 @@ def purge(name=None, pkgs=None, version=None, **kwargs):
name (str): The name of the package to be deleted.
version (str): The version of the package to be deleted. If this option
is used in combination with the ``pkgs`` option below, then this
version (str):
The version of the package to be deleted. If this option is
used in combination with the ``pkgs`` option below, then this
version will be applied to all targeted packages.
pkgs (list): A list of packages to delete. Must be passed as a python
pkgs (list):
A list of packages to delete. Must be passed as a python
list. The ``name`` parameter will be ignored if this option is
passed.
@ -1800,4 +1823,20 @@ def compare_versions(ver1='', oper='==', ver2=''):
salt '*' pkg.compare_versions 1.2 >= 1.3
'''
return salt.utils.compare_versions(ver1, oper, ver2)
if not ver1:
raise SaltInvocationError('compare_version, ver1 is blank')
if not ver2:
raise SaltInvocationError('compare_version, ver2 is blank')
# Support version being the special meaning of 'latest'
if ver1 == 'latest':
ver1 = str(sys.maxsize)
if ver2 == 'latest':
ver2 = str(sys.maxsize)
# Support version being the special meaning of 'Not Found'
if ver1 == 'Not Found':
ver1 = '0.0.0.0.0'
if ver2 == 'Not Found':
ver2 = '0.0.0.0.0'
return salt.utils.compare_versions(ver1, oper, ver2, ignore_epoch=True)

View File

@ -143,6 +143,17 @@ def get_printout(out, opts=None, **kwargs):
# See Issue #29796 for more information.
out = opts['output']
# Handle setting the output when --static is passed.
if not out and opts.get('static'):
if opts.get('output'):
out = opts['output']
elif opts.get('fun', '').split('.')[0] == 'state':
# --static doesn't have an output set at this point, but if we're
# running a state function and "out" hasn't already been set, we
# should set the out variable to "highstate". Otherwise state runs
# are set to "nested" below. See Issue #44556 for more information.
out = 'highstate'
if out == 'text':
out = 'txt'
elif out is None or out == '':

View File

@ -254,14 +254,14 @@ def returner(ret):
with _get_serv(ret, commit=True) as cur:
sql = '''INSERT INTO salt_returns
(fun, jid, return, id, success, full_ret, alter_time)
VALUES (%s, %s, %s, %s, %s, %s, %s)'''
VALUES (%s, %s, %s, %s, %s, %s, to_timestamp(%s))'''
cur.execute(sql, (ret['fun'], ret['jid'],
psycopg2.extras.Json(ret['return']),
ret['id'],
ret.get('success', False),
psycopg2.extras.Json(ret),
time.strftime('%Y-%m-%d %H:%M:%S %z', time.localtime())))
time.time()))
except salt.exceptions.SaltMasterError:
log.critical('Could not store return with pgjsonb returner. PostgreSQL server unavailable.')
@ -278,9 +278,9 @@ def event_return(events):
tag = event.get('tag', '')
data = event.get('data', '')
sql = '''INSERT INTO salt_events (tag, data, master_id, alter_time)
VALUES (%s, %s, %s, %s)'''
VALUES (%s, %s, %s, to_timestamp(%s))'''
cur.execute(sql, (tag, psycopg2.extras.Json(data),
__opts__['id'], time.strftime('%Y-%m-%d %H:%M:%S %z', time.localtime())))
__opts__['id'], time.time()))
def save_load(jid, load, minions=None):

View File

@ -686,7 +686,7 @@ class State(object):
except AttributeError:
pillar_enc = str(pillar_enc).lower()
self._pillar_enc = pillar_enc
if initial_pillar is not None:
if initial_pillar:
self.opts['pillar'] = initial_pillar
if self._pillar_override:
self.opts['pillar'] = salt.utils.dictupdate.merge(

View File

@ -59,6 +59,7 @@ from __future__ import absolute_import
# Import python libs
import logging
import salt.utils
log = logging.getLogger(__name__)
@ -186,13 +187,14 @@ def present(name,
use_32bit_registry=use_32bit_registry)
if vdata == reg_current['vdata'] and reg_current['success']:
ret['comment'] = '{0} in {1} is already configured'.\
format(vname if vname else '(Default)', name)
ret['comment'] = u'{0} in {1} is already configured' \
''.format(salt.utils.to_unicode(vname, 'utf-8') if vname else u'(Default)',
salt.utils.to_unicode(name, 'utf-8'))
return ret
add_change = {'Key': r'{0}\{1}'.format(hive, key),
'Entry': '{0}'.format(vname if vname else '(Default)'),
'Value': '{0}'.format(vdata)}
'Entry': u'{0}'.format(salt.utils.to_unicode(vname, 'utf-8') if vname else u'(Default)'),
'Value': salt.utils.to_unicode(vdata, 'utf-8')}
# Check for test option
if __opts__['test']:

View File

@ -65,7 +65,8 @@ def exists(name, index=None):
'''
Add the directory to the system PATH at index location
index: where the directory should be placed in the PATH (default: None)
index: where the directory should be placed in the PATH (default: None).
This is 0-indexed, so 0 means to prepend at the very start of the PATH.
[Note: Providing no index will append directory to PATH and
will not enforce its location within the PATH.]
@ -96,7 +97,7 @@ def exists(name, index=None):
try:
currIndex = sysPath.index(path)
if index:
if index is not None:
index = int(index)
if index < 0:
index = len(sysPath) + index + 1
@ -115,7 +116,7 @@ def exists(name, index=None):
except ValueError:
pass
if not index:
if index is None:
index = len(sysPath) # put it at the end
ret['changes']['added'] = '{0} will be added at index {1}'.format(name, index)
if __opts__['test']:

View File

@ -120,6 +120,8 @@ class _AtomicWFile(object):
self._fh.close()
if os.path.isfile(self._filename):
shutil.copymode(self._filename, self._tmp_filename)
st = os.stat(self._filename)
os.chown(self._tmp_filename, st.st_uid, st.st_gid)
atomic_rename(self._tmp_filename, self._filename)
def __exit__(self, exc_type, exc_value, traceback):

View File

@ -39,6 +39,16 @@ HASHES = {
HASHES_REVMAP = dict([(y, x) for x, y in six.iteritems(HASHES)])
def __clean_tmp(tmp):
'''
Remove temporary files
'''
try:
salt.utils.rm_rf(tmp)
except Exception:
pass
def guess_archive_type(name):
'''
Guess an archive type (tar, zip, or rar) by its file extension
@ -116,7 +126,15 @@ def copyfile(source, dest, backup_mode='', cachedir=''):
fstat = os.stat(dest)
except OSError:
pass
# The move could fail if the dest has xattr protections, so delete the
# temp file in this case
try:
shutil.move(tgt, dest)
except Exception:
__clean_tmp(tgt)
raise
if fstat is not None:
os.chown(dest, fstat.st_uid, fstat.st_gid)
os.chmod(dest, fstat.st_mode)
@ -134,10 +152,7 @@ def copyfile(source, dest, backup_mode='', cachedir=''):
subprocess.call(cmd, stdout=dev_null, stderr=dev_null)
if os.path.isfile(tgt):
# The temp file failed to move
try:
os.remove(tgt)
except Exception:
pass
__clean_tmp(tgt)
def rename(src, dst):

View File

@ -3,12 +3,23 @@
# Import python libs
from __future__ import absolute_import
import getpass
import grp
import pwd
import os
import shutil
import sys
# Posix only
try:
import grp
import pwd
except ImportError:
pass
# Windows only
try:
import win32file
except ImportError:
pass
# Import Salt Testing libs
from tests.support.case import ModuleCase
from tests.support.unit import skipIf
@ -18,6 +29,16 @@ from tests.support.paths import FILES, TMP
import salt.utils
def symlink(source, link_name):
'''
Handle symlinks on Windows with Python < 3.2
'''
if salt.utils.is_windows():
win32file.CreateSymbolicLink(link_name, source)
else:
os.symlink(source, link_name)
class FileModuleTest(ModuleCase):
'''
Validate the file module
@ -25,27 +46,27 @@ class FileModuleTest(ModuleCase):
def setUp(self):
self.myfile = os.path.join(TMP, 'myfile')
with salt.utils.fopen(self.myfile, 'w+') as fp:
fp.write('Hello\n')
fp.write('Hello' + os.linesep)
self.mydir = os.path.join(TMP, 'mydir/isawesome')
if not os.path.isdir(self.mydir):
# left behind... Don't fail because of this!
os.makedirs(self.mydir)
self.mysymlink = os.path.join(TMP, 'mysymlink')
if os.path.islink(self.mysymlink):
if os.path.islink(self.mysymlink) or os.path.isfile(self.mysymlink):
os.remove(self.mysymlink)
os.symlink(self.myfile, self.mysymlink)
symlink(self.myfile, self.mysymlink)
self.mybadsymlink = os.path.join(TMP, 'mybadsymlink')
if os.path.islink(self.mybadsymlink):
if os.path.islink(self.mybadsymlink) or os.path.isfile(self.mybadsymlink):
os.remove(self.mybadsymlink)
os.symlink('/nonexistentpath', self.mybadsymlink)
symlink('/nonexistentpath', self.mybadsymlink)
super(FileModuleTest, self).setUp()
def tearDown(self):
if os.path.isfile(self.myfile):
os.remove(self.myfile)
if os.path.islink(self.mysymlink):
if os.path.islink(self.mysymlink) or os.path.isfile(self.mysymlink):
os.remove(self.mysymlink)
if os.path.islink(self.mybadsymlink):
if os.path.islink(self.mybadsymlink) or os.path.isfile(self.mybadsymlink):
os.remove(self.mybadsymlink)
shutil.rmtree(self.mydir, ignore_errors=True)
super(FileModuleTest, self).tearDown()
@ -173,3 +194,20 @@ class FileModuleTest(ModuleCase):
ret = self.run_function('file.source_list', ['file://' + self.myfile,
'filehash', 'base'])
self.assertEqual(list(ret), ['file://' + self.myfile, 'filehash'])
def test_file_line_changes_format(self):
'''
Test file.line changes output formatting.
Issue #41474
'''
ret = self.minion_run('file.line', self.myfile, 'Goodbye',
mode='insert', after='Hello')
self.assertIn('Hello' + os.linesep + '+Goodbye', ret)
def test_file_line_content(self):
self.minion_run('file.line', self.myfile, 'Goodbye',
mode='insert', after='Hello')
with salt.utils.fopen(self.myfile, 'r') as fp:
content = fp.read()
self.assertEqual(content, 'Hello' + os.linesep + 'Goodbye' + os.linesep)

View File

@ -109,3 +109,59 @@ class OutputReturnTest(ShellCase):
delattr(self, 'maxDiff')
else:
self.maxDiff = old_max_diff
def test_output_highstate(self):
'''
Regression tests for the highstate outputter. Calls a basic state with various
flags. Each comparison should be identical when successful.
'''
# Test basic highstate output. No frills.
expected = ['minion:', ' ID: simple-ping', ' Function: module.run',
' Name: test.ping', ' Result: True',
' Comment: Module function test.ping executed',
' Changes: ', ' ret:', ' True',
'Summary for minion', 'Succeeded: 1 (changed=1)', 'Failed: 0',
'Total states run: 1']
state_run = self.run_salt('"minion" state.sls simple-ping')
for expected_item in expected:
self.assertIn(expected_item, state_run)
# Test highstate output while also passing --out=highstate.
# This is a regression test for Issue #29796
state_run = self.run_salt('"minion" state.sls simple-ping --out=highstate')
for expected_item in expected:
self.assertIn(expected_item, state_run)
# Test highstate output when passing --static and running a state function.
# See Issue #44556.
state_run = self.run_salt('"minion" state.sls simple-ping --static')
for expected_item in expected:
self.assertIn(expected_item, state_run)
# Test highstate output when passing --static and --out=highstate.
# See Issue #44556.
state_run = self.run_salt('"minion" state.sls simple-ping --static --out=highstate')
for expected_item in expected:
self.assertIn(expected_item, state_run)
def test_output_highstate_falls_back_nested(self):
'''
Tests outputter when passing --out=highstate with a non-state call. This should
fall back to "nested" output.
'''
expected = ['minion:', ' True']
ret = self.run_salt('"minion" test.ping --out=highstate')
self.assertEqual(ret, expected)
def test_static_simple(self):
'''
Tests passing the --static option with a basic test.ping command. This
should be the "nested" output.
'''
expected = ['minion:', ' True']
ret = self.run_salt('"minion" test.ping --static')
self.assertEqual(ret, expected)

View File

@ -25,6 +25,16 @@ import salt.grains.core as core
# Import 3rd-party libs
import salt.ext.six as six
# Globals
IPv4Address = salt.ext.ipaddress.IPv4Address
IPv6Address = salt.ext.ipaddress.IPv6Address
IP4_LOCAL = '127.0.0.1'
IP4_ADD1 = '10.0.0.1'
IP4_ADD2 = '10.0.0.2'
IP6_LOCAL = '::1'
IP6_ADD1 = '2001:4860:4860::8844'
IP6_ADD2 = '2001:4860:4860::8888'
@skipIf(NO_MOCK, NO_MOCK_REASON)
class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin):
@ -462,3 +472,127 @@ PATCHLEVEL = 3
self.assertEqual(os_grains.get('osrelease'), os_release_map['osrelease'])
self.assertListEqual(list(os_grains.get('osrelease_info')), os_release_map['osrelease_info'])
self.assertEqual(os_grains.get('osmajorrelease'), os_release_map['osmajorrelease'])
def _check_ipaddress(self, value, ip_v):
'''
check if ip address in a list is valid
'''
for val in value:
assert isinstance(val, six.string_types)
ip_method = 'is_ipv{0}'.format(ip_v)
self.assertTrue(getattr(salt.utils.network, ip_method)(val))
def _check_empty(self, key, value, empty):
'''
if empty is False and value does not exist assert error
if empty is True and value exists assert error
'''
if not empty and not value:
raise Exception("{0} is empty, expecting a value".format(key))
elif empty and value:
raise Exception("{0} is suppose to be empty. value: {1} \
exists".format(key, value))
@skipIf(not salt.utils.is_linux(), 'System is not Linux')
def test_fqdn_return(self):
'''
test ip4 and ip6 return values
'''
net_ip4_mock = [IP4_LOCAL, IP4_ADD1, IP4_ADD2]
net_ip6_mock = [IP6_LOCAL, IP6_ADD1, IP6_ADD2]
self._run_fqdn_tests(net_ip4_mock, net_ip6_mock,
ip4_empty=False, ip6_empty=False)
@skipIf(not salt.utils.is_linux(), 'System is not Linux')
def test_fqdn6_empty(self):
'''
test when ip6 is empty
'''
net_ip4_mock = [IP4_LOCAL, IP4_ADD1, IP4_ADD2]
net_ip6_mock = []
self._run_fqdn_tests(net_ip4_mock, net_ip6_mock,
ip4_empty=False)
@skipIf(not salt.utils.is_linux(), 'System is not Linux')
def test_fqdn4_empty(self):
'''
test when ip4 is empty
'''
net_ip4_mock = []
net_ip6_mock = [IP6_LOCAL, IP6_ADD1, IP6_ADD2]
self._run_fqdn_tests(net_ip4_mock, net_ip6_mock,
ip6_empty=False)
@skipIf(not salt.utils.is_linux(), 'System is not Linux')
def test_fqdn_all_empty(self):
'''
test when both ip4 and ip6 are empty
'''
net_ip4_mock = []
net_ip6_mock = []
self._run_fqdn_tests(net_ip4_mock, net_ip6_mock)
def _run_fqdn_tests(self, net_ip4_mock, net_ip6_mock,
ip6_empty=True, ip4_empty=True):
def _check_type(key, value, ip4_empty, ip6_empty):
'''
check type and other checks
'''
assert isinstance(value, list)
if '4' in key:
self._check_empty(key, value, ip4_empty)
self._check_ipaddress(value, ip_v='4')
elif '6' in key:
self._check_empty(key, value, ip6_empty)
self._check_ipaddress(value, ip_v='6')
ip4_mock = [(2, 1, 6, '', (IP4_ADD1, 0)),
(2, 3, 0, '', (IP4_ADD2, 0))]
ip6_mock = [(10, 1, 6, '', (IP6_ADD1, 0, 0, 0)),
(10, 3, 0, '', (IP6_ADD2, 0, 0, 0))]
with patch.dict(core.__opts__, {'ipv6': False}):
with patch.object(salt.utils.network, 'ip_addrs',
MagicMock(return_value=net_ip4_mock)):
with patch.object(salt.utils.network, 'ip_addrs6',
MagicMock(return_value=net_ip6_mock)):
with patch.object(core.socket, 'getaddrinfo', side_effect=[ip4_mock, ip6_mock]):
get_fqdn = core.ip_fqdn()
ret_keys = ['fqdn_ip4', 'fqdn_ip6', 'ipv4', 'ipv6']
for key in ret_keys:
value = get_fqdn[key]
_check_type(key, value, ip4_empty, ip6_empty)
@skipIf(not salt.utils.is_linux(), 'System is not Linux')
def test_dns_return(self):
'''
test the return for a dns grain. test for issue:
https://github.com/saltstack/salt/issues/41230
'''
resolv_mock = {'domain': '', 'sortlist': [], 'nameservers':
[IPv4Address(IP4_ADD1),
IPv6Address(IP6_ADD1)], 'ip4_nameservers':
[IPv4Address(IP4_ADD1)],
'search': ['test.saltstack.com'], 'ip6_nameservers':
[IPv6Address(IP6_ADD1)], 'options': []}
ret = {'dns': {'domain': '', 'sortlist': [], 'nameservers':
[IP4_ADD1, IP6_ADD1], 'ip4_nameservers':
[IP4_ADD1], 'search': ['test.saltstack.com'],
'ip6_nameservers': [IP6_ADD1], 'options':
[]}}
self._run_dns_test(resolv_mock, ret)
def _run_dns_test(self, resolv_mock, ret):
with patch.object(salt.utils, 'is_windows',
MagicMock(return_value=False)):
with patch.dict(core.__opts__, {'ipv6': False}):
with patch.object(salt.utils.dns, 'parse_resolv',
MagicMock(return_value=resolv_mock)):
get_dns = core.dns()
self.assertEqual(get_dns, ret)