mirror of
https://github.com/valitydev/salt.git
synced 2024-11-08 01:18:58 +00:00
Merge branch '2017.7' into 2017.7
This commit is contained in:
commit
4ead3014b7
@ -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
|
||||
-------------------------
|
||||
|
@ -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``
|
||||
|
@ -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.
|
||||
|
@ -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))
|
||||
self.opts['beacons'].update(data)
|
||||
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))
|
||||
self.opts['beacons'].update(data)
|
||||
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.opts['beacons']:
|
||||
log.info('Deleting beacon item {0}'.format(name))
|
||||
del self.opts['beacons'][name]
|
||||
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']:
|
||||
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
|
||||
'''
|
||||
|
||||
self._update_enabled(name, True)
|
||||
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
|
||||
'''
|
||||
|
||||
self._update_enabled(name, False)
|
||||
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
|
||||
|
@ -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):
|
||||
'''
|
||||
|
@ -27,12 +27,22 @@ __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
|
||||
:return: List of currently configured Beacons.
|
||||
: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,7 +188,10 @@ 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)
|
||||
return ret
|
||||
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
|
||||
ret['comment'] = 'Event module not available. Beacon add failed.'
|
||||
@ -215,7 +276,10 @@ 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)
|
||||
return ret
|
||||
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
|
||||
ret['comment'] = 'Event module not available. Beacon add failed.'
|
||||
@ -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'],
|
||||
@ -332,7 +399,7 @@ def enable(**kwargs):
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Failed to enable beacons on minion.'
|
||||
return ret
|
||||
return ret
|
||||
except KeyError:
|
||||
# Effectively a no-op, since we can't really return without an event system
|
||||
ret['comment'] = 'Event module not available. Beacons enable job failed.'
|
||||
@ -372,7 +439,7 @@ def disable(**kwargs):
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Failed to disable beacons on minion.'
|
||||
return ret
|
||||
return ret
|
||||
except KeyError:
|
||||
# Effectively a no-op, since we can't really return without an event system
|
||||
ret['comment'] = 'Event module not available. Beacons enable job failed.'
|
||||
@ -435,7 +502,10 @@ def enable_beacon(name, **kwargs):
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Failed to enable beacon {0} on minion.'.format(name)
|
||||
return ret
|
||||
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
|
||||
ret['comment'] = 'Event module not available. Beacon enable job failed.'
|
||||
@ -488,7 +558,10 @@ def disable_beacon(name, **kwargs):
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Failed to disable beacon on minion.'
|
||||
return ret
|
||||
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
|
||||
ret['comment'] = 'Event module not available. Beacon disable job failed.'
|
||||
|
@ -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_:
|
||||
|
@ -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,
|
||||
'vdata': None,
|
||||
'success': True}
|
||||
local_hive = hive
|
||||
local_key = key
|
||||
local_vname = vname
|
||||
ret = {'hive': local_hive,
|
||||
'key': local_key,
|
||||
'vname': local_vname,
|
||||
'vdata': None,
|
||||
'success': True}
|
||||
|
||||
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
|
||||
|
@ -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.
|
||||
|
@ -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'
|
||||
|
@ -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,9 +155,9 @@ 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),
|
||||
oper='>',
|
||||
ver2=str(latest_installed)):
|
||||
if compare_versions(ver1=str(latest_available),
|
||||
oper='>',
|
||||
ver2=str(latest_installed)):
|
||||
log.debug('Upgrade of {0} from {1} to {2} '
|
||||
'is available'.format(name,
|
||||
latest_installed,
|
||||
@ -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'
|
||||
|
||||
ret[name] = installed_pkgs.get(name, '')
|
||||
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,15 +1332,13 @@ 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,
|
||||
cache_path,
|
||||
python_shell=False,
|
||||
redirect_stderr=True)
|
||||
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']:
|
||||
ret[pkg_name] = {'install status': 'success'}
|
||||
changed.append(pkg_name)
|
||||
@ -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)
|
||||
|
@ -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 == '':
|
||||
|
@ -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):
|
||||
|
@ -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(
|
||||
|
@ -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']:
|
||||
|
@ -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']:
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
shutil.move(tgt, dest)
|
||||
|
||||
# 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):
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user