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: ``{}`` Default: ``{}``
This can be used to control logging levels more specifically. The example sets 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, but sets ``salt.modules`` to log the main salt library at the 'warning' level, sets ``salt.modules`` to log
at the ``debug`` level: at the ``debug`` level, and sets a custom module to the ``all`` level:
.. code-block:: yaml .. code-block:: yaml
log_granular_levels: log_granular_levels:
'salt': 'warning' 'salt': 'warning'
'salt.modules': 'debug' 'salt.modules': 'debug'
'salt.loader.saltmaster.ext.module.custom_module': 'all'
External Logging Handlers External Logging Handlers
------------------------- -------------------------

View File

@ -303,6 +303,20 @@ option on the Salt master.
master_port: 4506 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 .. conf_minion:: user
``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 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_grains <salt.modules.saltutil.sync_grains>` or
:mod:`saltutil.sync_all <salt.modules.saltutil.sync_all>` functions. :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: else:
self.opts['beacons'][name].append({'enabled': enabled_value}) 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 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 # Fire the complete event back along with the list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts) evt = salt.utils.event.get_event('minion', opts=self.opts)
b_conf = self.functions['config.merge']('beacons') evt.fire_event({'complete': True, 'beacons': beacons},
self.opts['beacons'].update(b_conf)
evt.fire_event({'complete': True, 'beacons': self.opts['beacons']},
tag='/salt/minion/minion_beacons_list_complete') tag='/salt/minion/minion_beacons_list_complete')
return True 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): def add_beacon(self, name, beacon_data):
''' '''
Add a beacon item Add a beacon item
@ -207,16 +248,23 @@ class Beacon(object):
data = {} data = {}
data[name] = beacon_data data[name] = beacon_data
if name in self.opts['beacons']: if name in self._get_beacons(include_opts=False):
log.info('Updating settings for beacon ' comment = 'Cannot update beacon item {0}, ' \
'item: {0}'.format(name)) 'because it is configured in pillar.'.format(name)
complete = False
else: 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) self.opts['beacons'].update(data)
# Fire the complete event back along with updated list of beacons # Fire the complete event back along with updated list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts) 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') tag='/salt/minion/minion_beacon_add_complete')
return True return True
@ -229,15 +277,21 @@ class Beacon(object):
data = {} data = {}
data[name] = beacon_data data[name] = beacon_data
log.info('Updating settings for beacon ' if name in self._get_beacons(include_opts=False):
'item: {0}'.format(name)) 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) self.opts['beacons'].update(data)
# Fire the complete event back along with updated list of beacons # Fire the complete event back along with updated list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts) 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') tag='/salt/minion/minion_beacon_modify_complete')
return True return True
def delete_beacon(self, name): def delete_beacon(self, name):
@ -245,13 +299,22 @@ class Beacon(object):
Delete a beacon item 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']: if name in self.opts['beacons']:
log.info('Deleting beacon item {0}'.format(name))
del self.opts['beacons'][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 # Fire the complete event back along with updated list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts) 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') tag='/salt/minion/minion_beacon_delete_complete')
return True return True
@ -289,11 +352,19 @@ class Beacon(object):
Enable a beacon 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) 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 # Fire the complete event back along with updated list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts) 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') tag='/salt/minion/minion_beacon_enabled_complete')
return True return True
@ -303,11 +374,19 @@ class Beacon(object):
Disable a beacon 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) 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 # Fire the complete event back along with updated list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts) 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') tag='/salt/minion/minion_beacon_disabled_complete')
return True return True

View File

@ -1896,6 +1896,8 @@ class Minion(MinionBase):
func = data.get('func', None) func = data.get('func', None)
name = data.get('name', None) name = data.get('name', None)
beacon_data = data.get('beacon_data', 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': if func == 'add':
self.beacons.add_beacon(name, beacon_data) self.beacons.add_beacon(name, beacon_data)
@ -1912,7 +1914,9 @@ class Minion(MinionBase):
elif func == 'disable_beacon': elif func == 'disable_beacon':
self.beacons.disable_beacon(name) self.beacons.disable_beacon(name)
elif func == 'list': 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): 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 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. :return: List of currently configured Beacons.
CLI Example: CLI Example:
@ -45,7 +55,10 @@ def list_(return_yaml=True):
try: try:
eventer = salt.utils.event.get_event('minion', opts=__opts__) 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: if res:
event_ret = eventer.get_event(tag='/salt/minion/minion_beacons_list_complete', wait=30) event_ret = eventer.get_event(tag='/salt/minion/minion_beacons_list_complete', wait=30)
log.debug('event_ret {0}'.format(event_ret)) log.debug('event_ret {0}'.format(event_ret))
@ -69,6 +82,47 @@ def list_(return_yaml=True):
return {'beacons': {}} 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): def add(name, beacon_data, **kwargs):
''' '''
Add a beacon on the minion 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) ret['comment'] = 'Beacon {0} is already configured.'.format(name)
return ret 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']: if 'test' in kwargs and kwargs['test']:
ret['result'] = True ret['result'] = True
ret['comment'] = 'Beacon: {0} would be added.'.format(name) 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: if name in beacons and beacons[name] == beacon_data:
ret['result'] = True ret['result'] = True
ret['comment'] = 'Added beacon: {0}.'.format(name) ret['comment'] = 'Added beacon: {0}.'.format(name)
else:
ret['result'] = False
ret['comment'] = event_ret['comment']
return ret return ret
except KeyError: except KeyError:
# Effectively a no-op, since we can't really return without an event system # 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: if name in beacons and beacons[name] == beacon_data:
ret['result'] = True ret['result'] = True
ret['comment'] = 'Modified beacon: {0}.'.format(name) ret['comment'] = 'Modified beacon: {0}.'.format(name)
else:
ret['result'] = False
ret['comment'] = event_ret['comment']
return ret return ret
except KeyError: except KeyError:
# Effectively a no-op, since we can't really return without an event system # 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['result'] = True
ret['comment'] = 'Deleted beacon: {0}.'.format(name) ret['comment'] = 'Deleted beacon: {0}.'.format(name)
return ret return ret
else:
ret['result'] = False
ret['comment'] = event_ret['comment']
except KeyError: except KeyError:
# Effectively a no-op, since we can't really return without an event system # Effectively a no-op, since we can't really return without an event system
ret['comment'] = 'Event module not available. Beacon add failed.' ret['comment'] = 'Event module not available. Beacon add failed.'
@ -279,7 +346,7 @@ def save():
ret = {'comment': [], ret = {'comment': [],
'result': True} 'result': True}
beacons = list_(return_yaml=False) beacons = list_(return_yaml=False, include_pillar=False)
# move this file into an configurable opt # move this file into an configurable opt
sfn = '{0}/{1}/beacons.conf'.format(__opts__['config_dir'], sfn = '{0}/{1}/beacons.conf'.format(__opts__['config_dir'],
@ -435,6 +502,9 @@ def enable_beacon(name, **kwargs):
else: else:
ret['result'] = False ret['result'] = False
ret['comment'] = 'Failed to enable beacon {0} on minion.'.format(name) ret['comment'] = 'Failed to enable beacon {0} on minion.'.format(name)
else:
ret['result'] = False
ret['comment'] = event_ret['comment']
return ret return ret
except KeyError: except KeyError:
# Effectively a no-op, since we can't really return without an event system # Effectively a no-op, since we can't really return without an event system
@ -488,6 +558,9 @@ def disable_beacon(name, **kwargs):
else: else:
ret['result'] = False ret['result'] = False
ret['comment'] = 'Failed to disable beacon on minion.' ret['comment'] = 'Failed to disable beacon on minion.'
else:
ret['result'] = False
ret['comment'] = event_ret['comment']
return ret return ret
except KeyError: except KeyError:
# Effectively a no-op, since we can't really return without an event system # 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 changed:
if show_changes: if show_changes:
with salt.utils.fopen(path, 'r') as fp_: with salt.utils.fopen(path, 'r') as fp_:
path_content = _splitlines_preserving_trailing_newline( path_content = fp_.read().splitlines(True)
fp_.read()) changes_diff = ''.join(difflib.unified_diff(path_content, body.splitlines(True)))
changes_diff = ''.join(difflib.unified_diff(
path_content, _splitlines_preserving_trailing_newline(body)))
if __opts__['test'] is False: if __opts__['test'] is False:
fh_ = None fh_ = None
try: 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) fh_.write(body)
finally: finally:
if fh_: 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 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. (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 # 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 sys
import logging import logging
from salt.ext.six.moves import range # pylint: disable=W0622,import-error from salt.ext.six.moves import range # pylint: disable=W0622,import-error
from salt.ext import six
# Import third party libs # Import third party libs
try: try:
from salt.ext.six.moves import winreg as _winreg # pylint: disable=import-error,no-name-in-module import win32gui
from win32con import HWND_BROADCAST, WM_SETTINGCHANGE import win32api
from win32api import RegCreateKeyEx, RegSetValueEx, RegFlushKey, \ import win32con
RegCloseKey, error as win32apiError, SendMessage import pywintypes
HAS_WINDOWS_MODULES = True HAS_WINDOWS_MODULES = True
except ImportError: except ImportError:
HAS_WINDOWS_MODULES = False HAS_WINDOWS_MODULES = False
@ -60,7 +59,7 @@ __virtualname__ = 'reg'
def __virtual__(): 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(): if not salt.utils.is_windows():
return (False, 'reg execution module failed to load: ' return (False, 'reg execution module failed to load: '
@ -69,106 +68,76 @@ def __virtual__():
if not HAS_WINDOWS_MODULES: if not HAS_WINDOWS_MODULES:
return (False, 'reg execution module failed to load: ' return (False, 'reg execution module failed to load: '
'One of the following libraries did not load: ' 'One of the following libraries did not load: '
+ '_winreg, win32gui, win32con, win32api') + 'win32gui, win32con, win32api')
return __virtualname__ return __virtualname__
# winreg in python 2 is hard coded to use codex 'mbcs', which uses def _to_mbcs(vdata):
# encoding that the user has assign. The function _unicode_to_mbcs '''
# and _unicode_to_mbcs help with this. 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): return salt.utils.to_unicode(vdata, 'utf-8')
# 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)
class Registry(object): # pylint: disable=R0903 class Registry(object): # pylint: disable=R0903
''' '''
Delay '_winreg' usage until this module is used Delay usage until this module is used
''' '''
def __init__(self): def __init__(self):
self.hkeys = { self.hkeys = {
'HKEY_CURRENT_USER': _winreg.HKEY_CURRENT_USER, 'HKEY_CURRENT_USER': win32con.HKEY_CURRENT_USER,
'HKEY_LOCAL_MACHINE': _winreg.HKEY_LOCAL_MACHINE, 'HKEY_LOCAL_MACHINE': win32con.HKEY_LOCAL_MACHINE,
'HKEY_USERS': _winreg.HKEY_USERS, 'HKEY_USERS': win32con.HKEY_USERS,
'HKCU': _winreg.HKEY_CURRENT_USER, 'HKCU': win32con.HKEY_CURRENT_USER,
'HKLM': _winreg.HKEY_LOCAL_MACHINE, 'HKLM': win32con.HKEY_LOCAL_MACHINE,
'HKU': _winreg.HKEY_USERS, 'HKU': win32con.HKEY_USERS,
} }
self.vtype = { self.vtype = {
'REG_BINARY': _winreg.REG_BINARY, 'REG_BINARY': win32con.REG_BINARY,
'REG_DWORD': _winreg.REG_DWORD, 'REG_DWORD': win32con.REG_DWORD,
'REG_EXPAND_SZ': _winreg.REG_EXPAND_SZ, 'REG_EXPAND_SZ': win32con.REG_EXPAND_SZ,
'REG_MULTI_SZ': _winreg.REG_MULTI_SZ, 'REG_MULTI_SZ': win32con.REG_MULTI_SZ,
'REG_SZ': _winreg.REG_SZ 'REG_SZ': win32con.REG_SZ,
'REG_QWORD': win32con.REG_QWORD
} }
self.opttype = { self.opttype = {
'REG_OPTION_NON_VOLATILE': _winreg.REG_OPTION_NON_VOLATILE, 'REG_OPTION_NON_VOLATILE': 0,
'REG_OPTION_VOLATILE': _winreg.REG_OPTION_VOLATILE 'REG_OPTION_VOLATILE': 1
} }
# Return Unicode due to from __future__ import unicode_literals # Return Unicode due to from __future__ import unicode_literals
self.vtype_reverse = { self.vtype_reverse = {
_winreg.REG_BINARY: 'REG_BINARY', win32con.REG_BINARY: 'REG_BINARY',
_winreg.REG_DWORD: 'REG_DWORD', win32con.REG_DWORD: 'REG_DWORD',
_winreg.REG_EXPAND_SZ: 'REG_EXPAND_SZ', win32con.REG_EXPAND_SZ: 'REG_EXPAND_SZ',
_winreg.REG_MULTI_SZ: 'REG_MULTI_SZ', win32con.REG_MULTI_SZ: 'REG_MULTI_SZ',
_winreg.REG_SZ: 'REG_SZ', win32con.REG_SZ: 'REG_SZ',
# REG_QWORD isn't in the winreg library win32con.REG_QWORD: 'REG_QWORD'
11: 'REG_QWORD'
} }
self.opttype_reverse = { self.opttype_reverse = {
_winreg.REG_OPTION_NON_VOLATILE: 'REG_OPTION_NON_VOLATILE', 0: 'REG_OPTION_NON_VOLATILE',
_winreg.REG_OPTION_VOLATILE: 'REG_OPTION_VOLATILE' 1: 'REG_OPTION_VOLATILE'
} }
# delete_key_recursive uses this to check the subkey contains enough \ # delete_key_recursive uses this to check the subkey contains enough \
# as we do not want to remove all or most of the registry # as we do not want to remove all or most of the registry
self.subkey_slash_check = { self.subkey_slash_check = {
_winreg.HKEY_CURRENT_USER: 0, win32con.HKEY_CURRENT_USER: 0,
_winreg.HKEY_LOCAL_MACHINE: 1, win32con.HKEY_LOCAL_MACHINE: 1,
_winreg.HKEY_USERS: 1 win32con.HKEY_USERS: 1
} }
self.registry_32 = { self.registry_32 = {
True: _winreg.KEY_READ | _winreg.KEY_WOW64_32KEY, True: win32con.KEY_READ | win32con.KEY_WOW64_32KEY,
False: _winreg.KEY_READ, False: win32con.KEY_READ,
} }
def __getattr__(self, k): 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 :return: Returns True if found, False if not found
:rtype: bool :rtype: bool
''' '''
local_hive = _to_unicode(hive)
if PY2: local_key = _to_unicode(key)
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
else:
local_hive = hive
local_key = key
registry = Registry() registry = Registry()
hkey = registry.hkeys[local_hive] hkey = registry.hkeys[local_hive]
access_mask = registry.registry_32[use_32bit_registry] access_mask = registry.registry_32[use_32bit_registry]
try: try:
handle = _winreg.OpenKey(hkey, local_key, 0, access_mask) handle = win32api.RegOpenKeyEx(hkey, local_key, 0, access_mask)
_winreg.CloseKey(handle) win32api.RegCloseKey(handle)
return True return True
except WindowsError: # pylint: disable=E0602 except WindowsError: # pylint: disable=E0602
return False return False
@ -224,7 +188,10 @@ def broadcast_change():
salt '*' reg.broadcast_change salt '*' reg.broadcast_change
''' '''
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms644952(v=vs.85).aspx # 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): 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' salt '*' reg.list_keys HKLM 'SOFTWARE'
''' '''
if PY2: local_hive = _to_unicode(hive)
local_hive = _mbcs_to_unicode(hive) local_key = _to_unicode(key)
local_key = _unicode_to_mbcs(key)
else:
local_hive = hive
local_key = key
registry = Registry() registry = Registry()
hkey = registry.hkeys[local_hive] hkey = registry.hkeys[local_hive]
@ -266,12 +229,12 @@ def list_keys(hive, key=None, use_32bit_registry=False):
subkeys = [] subkeys = []
try: 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]): for i in range(win32api.RegQueryInfoKey(handle)[0]):
subkey = _winreg.EnumKey(handle, i) subkey = win32api.RegEnumKey(handle, i)
if PY2: if PY2:
subkeys.append(_mbcs_to_unicode(subkey)) subkeys.append(_to_unicode(subkey))
else: else:
subkeys.append(subkey) 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' salt '*' reg.list_values HKLM 'SYSTEM\\CurrentControlSet\\Services\\Tcpip'
''' '''
local_hive = _to_unicode(hive)
if PY2: local_key = _to_unicode(key)
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
else:
local_hive = hive
local_key = key
registry = Registry() registry = Registry()
hkey = registry.hkeys[local_hive] hkey = registry.hkeys[local_hive]
@ -327,37 +285,21 @@ def list_values(hive, key=None, use_32bit_registry=False, include_default=True):
values = list() values = list()
try: 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]): for i in range(win32api.RegQueryInfoKey(handle)[1]):
vname, vdata, vtype = _winreg.EnumValue(handle, i) vname, vdata, vtype = win32api.RegEnumValue(handle, i)
if not vname:
vname = "(Default)"
value = {'hive': local_hive, value = {'hive': local_hive,
'key': local_key, 'key': local_key,
'vname': vname, 'vname': _to_mbcs(vname),
'vdata': vdata, 'vdata': _to_mbcs(vdata),
'vtype': registry.vtype_reverse[vtype], 'vtype': registry.vtype_reverse[vtype],
'success': True} 'success': True}
values.append(value) 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 except WindowsError as exc: # pylint: disable=E0602
log.debug(exc) log.debug(exc)
log.debug(r'Cannot find key: {0}\{1}'.format(hive, key)) 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' salt '*' reg.read_value HKEY_LOCAL_MACHINE 'SOFTWARE\Salt' 'version'
''' '''
# If no name is passed, the default value of the key will be returned # If no name is passed, the default value of the key will be returned
# The value name is Default # The value name is Default
# Setup the return array # Setup the return array
if PY2: local_hive = _to_unicode(hive)
ret = {'hive': _mbcs_to_unicode(hive), local_key = _to_unicode(key)
'key': _mbcs_to_unicode(key), local_vname = _to_unicode(vname)
'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)
else: ret = {'hive': local_hive,
ret = {'hive': hive, 'key': local_key,
'key': key, 'vname': local_vname,
'vname': vname,
'vdata': None, 'vdata': None,
'success': True} 'success': True}
local_hive = hive
local_key = key
local_vname = vname
if not vname: if not vname:
ret['vname'] = '(Default)' 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] access_mask = registry.registry_32[use_32bit_registry]
try: try:
handle = _winreg.OpenKey(hkey, local_key, 0, access_mask) handle = win32api.RegOpenKeyEx(hkey, local_key, 0, access_mask)
try: try:
# QueryValueEx returns unicode data # RegQueryValueEx returns and accepts unicode data
vdata, vtype = _winreg.QueryValueEx(handle, local_vname) vdata, vtype = win32api.RegQueryValueEx(handle, local_vname)
if vdata or vdata in [0, '']: if vdata or vdata in [0, '']:
ret['vtype'] = registry.vtype_reverse[vtype] 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: else:
ret['comment'] = 'Empty Value' ret['comment'] = 'Empty Value'
except WindowsError: # pylint: disable=E0602 except WindowsError: # pylint: disable=E0602
ret['vdata'] = ('(value not set)') ret['vdata'] = ('(value not set)')
ret['vtype'] = 'REG_SZ' ret['vtype'] = 'REG_SZ'
except WindowsError as exc: # pylint: disable=E0602 except pywintypes.error as exc: # pylint: disable=E0602
log.debug(exc) log.debug(exc)
log.debug('Cannot find key: {0}\\{1}'.format(local_hive, local_key)) log.debug('Cannot find key: {0}\\{1}'.format(local_hive, local_key))
ret['comment'] = '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' \\ salt '*' reg.set_value HKEY_LOCAL_MACHINE 'SOFTWARE\\Salt' 'version' '2015.5.2' \\
vtype=REG_LIST vdata='[a,b,c]' vtype=REG_LIST vdata='[a,b,c]'
''' '''
local_hive = _to_unicode(hive)
if PY2: local_key = _to_unicode(key)
try: local_vname = _to_unicode(vname)
local_hive = _mbcs_to_unicode(hive) local_vtype = _to_unicode(vtype)
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
registry = Registry() registry = Registry()
hkey = registry.hkeys[local_hive] hkey = registry.hkeys[local_hive]
vtype_value = registry.vtype[local_vtype] 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: if volatile:
create_options = registry.opttype['REG_OPTION_VOLATILE'] create_options = registry.opttype['REG_OPTION_VOLATILE']
else: else:
create_options = registry.opttype['REG_OPTION_NON_VOLATILE'] create_options = registry.opttype['REG_OPTION_NON_VOLATILE']
try: try:
handle, _ = RegCreateKeyEx(hkey, local_key, access_mask, handle, _ = win32api.RegCreateKeyEx(hkey, local_key, access_mask,
Options=create_options) Options=create_options)
RegSetValueEx(handle, local_vname, 0, vtype_value, local_vdata) win32api.RegSetValueEx(handle, local_vname, 0, vtype_value, local_vdata)
RegFlushKey(handle) win32api.RegFlushKey(handle)
RegCloseKey(handle) win32api.RegCloseKey(handle)
broadcast_change() broadcast_change()
return True 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) log.error(exc, exc_info=True)
return False return False
@ -626,18 +565,14 @@ def delete_key_recursive(hive, key, use_32bit_registry=False):
salt '*' reg.delete_key_recursive HKLM SOFTWARE\\salt salt '*' reg.delete_key_recursive HKLM SOFTWARE\\salt
''' '''
if PY2: local_hive = _to_unicode(hive)
local_hive = _mbcs_to_unicode(hive) local_key = _to_unicode(key)
local_key = _unicode_to_mbcs(key)
else:
local_hive = hive
local_key = key
# Instantiate the registry object # Instantiate the registry object
registry = Registry() registry = Registry()
hkey = registry.hkeys[local_hive] hkey = registry.hkeys[local_hive]
key_path = local_key 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): if not _key_exists(local_hive, local_key, use_32bit_registry):
return False return False
@ -654,17 +589,17 @@ def delete_key_recursive(hive, key, use_32bit_registry=False):
i = 0 i = 0
while True: while True:
try: try:
subkey = _winreg.EnumKey(_key, i) subkey = win32api.RegEnumKey(_key, i)
yield subkey yield subkey
i += 1 i += 1
except WindowsError: # pylint: disable=E0602 except pywintypes.error: # pylint: disable=E0602
break break
def _traverse_registry_tree(_hkey, _keypath, _ret, _access_mask): def _traverse_registry_tree(_hkey, _keypath, _ret, _access_mask):
''' '''
Traverse the registry tree i.e. dive into the tree 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): for subkeyname in _subkeys(_key):
subkeypath = r'{0}\{1}'.format(_keypath, subkeyname) subkeypath = r'{0}\{1}'.format(_keypath, subkeyname)
_ret = _traverse_registry_tree(_hkey, subkeypath, _ret, access_mask) _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 # Delete all sub_keys
for sub_key_path in key_list: for sub_key_path in key_list:
try: try:
key_handle = _winreg.OpenKey(hkey, sub_key_path, 0, access_mask) key_handle = win32api.RegOpenKeyEx(hkey, sub_key_path, 0, access_mask)
_winreg.DeleteKey(key_handle, '') win32api.RegDeleteKey(key_handle, '')
ret['Deleted'].append(r'{0}\{1}'.format(hive, sub_key_path)) ret['Deleted'].append(r'{0}\{1}'.format(hive, sub_key_path))
except WindowsError as exc: # pylint: disable=E0602 except WindowsError as exc: # pylint: disable=E0602
log.error(exc, exc_info=True) 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' salt '*' reg.delete_value HKEY_CURRENT_USER 'SOFTWARE\\Salt' 'version'
''' '''
if PY2: local_hive = _to_unicode(hive)
local_hive = _mbcs_to_unicode(hive) local_key = _to_unicode(key)
local_key = _unicode_to_mbcs(key) local_vname = _to_unicode(vname)
local_vname = _unicode_to_mbcs(vname)
else:
local_hive = hive
local_key = key
local_vname = vname
registry = Registry() registry = Registry()
hkey = registry.hkeys[local_hive] 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: try:
handle = _winreg.OpenKey(hkey, local_key, 0, access_mask) handle = win32api.RegOpenKeyEx(hkey, local_key, 0, access_mask)
_winreg.DeleteValue(handle, local_vname) win32api.RegDeleteValue(handle, local_vname)
_winreg.CloseKey(handle) win32api.RegCloseKey(handle)
broadcast_change() broadcast_change()
return True return True
except WindowsError as exc: # pylint: disable=E0602 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 Values passed this way will override Pillar values set via
``pillar_roots`` or an external Pillar source. ``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 queue : False
Instead of failing immediately when another state run is in progress, Instead of failing immediately when another state run is in progress,
queue the new state run to begin running once the other has finished. 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 .. 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 saltenv
Specify a salt fileserver environment to be used when applying states 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 .. 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 queue : False
Instead of failing immediately when another state run is in progress, Instead of failing immediately when another state run is in progress,
queue the new state run to begin running once the other has finished. 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, lstat, path_exists_glob, write, pardir, join, HASHES, HASHES_REVMAP,
comment, uncomment, _add_flags, comment_line, _regex_to_static, comment, uncomment, _add_flags, comment_line, _regex_to_static,
_get_line_indent, apply_template_on_contents, dirname, basename, _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.modules.file import normpath as normpath_
from salt.utils import namespaced_function as _namespaced_function 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 write, pardir, join, _add_flags, apply_template_on_contents
global path_exists_glob, comment, uncomment, _mkstemp_copy global path_exists_glob, comment, uncomment, _mkstemp_copy
global _regex_to_static, _get_line_indent, dirname, basename 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()) replace = _namespaced_function(replace, globals())
search = _namespaced_function(search, globals()) search = _namespaced_function(search, globals())
@ -179,6 +179,8 @@ def __virtual__():
basename = _namespaced_function(basename, globals()) basename = _namespaced_function(basename, globals())
list_backups_dir = _namespaced_function(list_backups_dir, globals()) list_backups_dir = _namespaced_function(list_backups_dir, globals())
normpath_ = _namespaced_function(normpath_, globals()) normpath_ = _namespaced_function(normpath_, globals())
_assert_occurrence = _namespaced_function(_assert_occurrence, globals())
_starts_till = _namespaced_function(_starts_till, globals())
else: else:
return False, 'Module win_file: Missing Win32 modules' return False, 'Module win_file: Missing Win32 modules'

View File

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

View File

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

View File

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

View File

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

View File

@ -65,7 +65,8 @@ def exists(name, index=None):
''' '''
Add the directory to the system PATH at index location 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 [Note: Providing no index will append directory to PATH and
will not enforce its location within the PATH.] will not enforce its location within the PATH.]
@ -96,7 +97,7 @@ def exists(name, index=None):
try: try:
currIndex = sysPath.index(path) currIndex = sysPath.index(path)
if index: if index is not None:
index = int(index) index = int(index)
if index < 0: if index < 0:
index = len(sysPath) + index + 1 index = len(sysPath) + index + 1
@ -115,7 +116,7 @@ def exists(name, index=None):
except ValueError: except ValueError:
pass pass
if not index: if index is None:
index = len(sysPath) # put it at the end index = len(sysPath) # put it at the end
ret['changes']['added'] = '{0} will be added at index {1}'.format(name, index) ret['changes']['added'] = '{0} will be added at index {1}'.format(name, index)
if __opts__['test']: if __opts__['test']:

View File

@ -120,6 +120,8 @@ class _AtomicWFile(object):
self._fh.close() self._fh.close()
if os.path.isfile(self._filename): if os.path.isfile(self._filename):
shutil.copymode(self._filename, self._tmp_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) atomic_rename(self._tmp_filename, self._filename)
def __exit__(self, exc_type, exc_value, traceback): 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)]) 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): def guess_archive_type(name):
''' '''
Guess an archive type (tar, zip, or rar) by its file extension 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) fstat = os.stat(dest)
except OSError: except OSError:
pass pass
# The move could fail if the dest has xattr protections, so delete the
# temp file in this case
try:
shutil.move(tgt, dest) shutil.move(tgt, dest)
except Exception:
__clean_tmp(tgt)
raise
if fstat is not None: if fstat is not None:
os.chown(dest, fstat.st_uid, fstat.st_gid) os.chown(dest, fstat.st_uid, fstat.st_gid)
os.chmod(dest, fstat.st_mode) 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) subprocess.call(cmd, stdout=dev_null, stderr=dev_null)
if os.path.isfile(tgt): if os.path.isfile(tgt):
# The temp file failed to move # The temp file failed to move
try: __clean_tmp(tgt)
os.remove(tgt)
except Exception:
pass
def rename(src, dst): def rename(src, dst):

View File

@ -3,12 +3,23 @@
# Import python libs # Import python libs
from __future__ import absolute_import from __future__ import absolute_import
import getpass import getpass
import grp
import pwd
import os import os
import shutil import shutil
import sys import sys
# Posix only
try:
import grp
import pwd
except ImportError:
pass
# Windows only
try:
import win32file
except ImportError:
pass
# Import Salt Testing libs # Import Salt Testing libs
from tests.support.case import ModuleCase from tests.support.case import ModuleCase
from tests.support.unit import skipIf from tests.support.unit import skipIf
@ -18,6 +29,16 @@ from tests.support.paths import FILES, TMP
import salt.utils 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): class FileModuleTest(ModuleCase):
''' '''
Validate the file module Validate the file module
@ -25,27 +46,27 @@ class FileModuleTest(ModuleCase):
def setUp(self): def setUp(self):
self.myfile = os.path.join(TMP, 'myfile') self.myfile = os.path.join(TMP, 'myfile')
with salt.utils.fopen(self.myfile, 'w+') as fp: 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') self.mydir = os.path.join(TMP, 'mydir/isawesome')
if not os.path.isdir(self.mydir): if not os.path.isdir(self.mydir):
# left behind... Don't fail because of this! # left behind... Don't fail because of this!
os.makedirs(self.mydir) os.makedirs(self.mydir)
self.mysymlink = os.path.join(TMP, 'mysymlink') 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.remove(self.mysymlink)
os.symlink(self.myfile, self.mysymlink) symlink(self.myfile, self.mysymlink)
self.mybadsymlink = os.path.join(TMP, 'mybadsymlink') 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.remove(self.mybadsymlink)
os.symlink('/nonexistentpath', self.mybadsymlink) symlink('/nonexistentpath', self.mybadsymlink)
super(FileModuleTest, self).setUp() super(FileModuleTest, self).setUp()
def tearDown(self): def tearDown(self):
if os.path.isfile(self.myfile): if os.path.isfile(self.myfile):
os.remove(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) 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) os.remove(self.mybadsymlink)
shutil.rmtree(self.mydir, ignore_errors=True) shutil.rmtree(self.mydir, ignore_errors=True)
super(FileModuleTest, self).tearDown() super(FileModuleTest, self).tearDown()
@ -173,3 +194,20 @@ class FileModuleTest(ModuleCase):
ret = self.run_function('file.source_list', ['file://' + self.myfile, ret = self.run_function('file.source_list', ['file://' + self.myfile,
'filehash', 'base']) 'filehash', 'base'])
self.assertEqual(list(ret), ['file://' + self.myfile, 'filehash']) 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') delattr(self, 'maxDiff')
else: else:
self.maxDiff = old_max_diff 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 3rd-party libs
import salt.ext.six as six 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) @skipIf(NO_MOCK, NO_MOCK_REASON)
class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin): class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin):
@ -462,3 +472,127 @@ PATCHLEVEL = 3
self.assertEqual(os_grains.get('osrelease'), os_release_map['osrelease']) self.assertEqual(os_grains.get('osrelease'), os_release_map['osrelease'])
self.assertListEqual(list(os_grains.get('osrelease_info')), os_release_map['osrelease_info']) self.assertListEqual(list(os_grains.get('osrelease_info')), os_release_map['osrelease_info'])
self.assertEqual(os_grains.get('osmajorrelease'), os_release_map['osmajorrelease']) 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)