mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge branch '2015.8' into '2016.3'
Conflicts: - salt/beacons/inotify.py - salt/beacons/load.py - salt/minion.py - salt/modules/win_update.py - salt/modules/yumpkg.py - salt/utils/pkg/rpm.py
This commit is contained in:
commit
7ea9dacbdd
44 changed files with 542 additions and 298 deletions
|
@ -18,9 +18,15 @@ This issue is resolved in the :ref:`2015.8.5 <2015.8.5>` release.
|
|||
Security Fix
|
||||
============
|
||||
|
||||
CVE-2016-1866: Improper handling of clear messages on the minion, which could result in executing commands not sent by the master.
|
||||
CVE-2016-1866: Improper handling of clear messages on the minion, which could
|
||||
result in executing commands not sent by the master.
|
||||
|
||||
This issue affects only the 2015.8.x releases of Salt. In order for an attacker to use this attack vector, they would have to execute a successful attack on an existing TCP connection between minion and master on the pub port. It does not allow an external attacker to obtain the shared secret or decrypt any encrypted traffic between minion and master.
|
||||
This issue affects only the 2015.8.x releases of Salt. In order for an attacker
|
||||
to use this attack vector, they would have to execute a successful attack on an
|
||||
existing TCP connection between minion and master on the pub port. It does not
|
||||
allow an external attacker to obtain the shared secret or decrypt any encrypted
|
||||
traffic between minion and master. Thank you to Sebastian Krahmer
|
||||
<krahmer@suse.com> for bringing this issue to our attention.
|
||||
|
||||
We recommend everyone upgrade to 2015.8.4 as soon as possible.
|
||||
|
||||
|
|
|
@ -21,21 +21,21 @@ Supported Operating Systems
|
|||
---------------------------
|
||||
- Amazon Linux 2012.09
|
||||
- Arch
|
||||
- CentOS 5/6
|
||||
- Debian 6.x/7.x/8(git installations only)
|
||||
- Fedora 17/18
|
||||
- FreeBSD 9.1/9.2/10
|
||||
- CentOS 5/6/7
|
||||
- Debian 6/7/8
|
||||
- Fedora 17/18/20/21/22
|
||||
- FreeBSD 9.1/9.2/10/11
|
||||
- Gentoo
|
||||
- Linaro
|
||||
- Linux Mint 13/14
|
||||
- OpenSUSE 12.x
|
||||
- OpenSUSE 12/13
|
||||
- Oracle Linux 5/5
|
||||
- Red Hat 5/6
|
||||
- Red Hat Enterprise 5/6
|
||||
- Scientific Linux 5/6
|
||||
- SmartOS
|
||||
- SuSE 11 SP1/11 SP2
|
||||
- Ubuntu 10.x/11.x/12.x/13.04/13.10
|
||||
- SUSE Linux Enterprise 11 SP1/11 SP2/11 SP3
|
||||
- Ubuntu 10.x/11.x/12.x/13.x/14.x/15.04
|
||||
- Elementary OS 0.2
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,10 @@ the Salt Virt systems.
|
|||
This project never took off, but was functional and proves the early
|
||||
viability of Salt to be a cloud controller.
|
||||
|
||||
.. warning::
|
||||
Salt Virt does not work with KVM that is running in a VM. KVM must be running
|
||||
on the base hardware.
|
||||
|
||||
Salt Virt Tutorial
|
||||
==================
|
||||
|
||||
|
|
|
@ -56,8 +56,9 @@ def validate(config):
|
|||
'''
|
||||
# Configuration for load beacon should be a list of dicts
|
||||
if not isinstance(config, dict):
|
||||
return False
|
||||
return True
|
||||
return False, ('Configuration for btmp beacon must '
|
||||
'be a list of dictionaries.')
|
||||
return True, 'Valid beacon configuration'
|
||||
|
||||
|
||||
# TODO: add support for only firing events for specific users and login times
|
||||
|
|
|
@ -37,9 +37,9 @@ def validate(config):
|
|||
'''
|
||||
# Configuration for diskusage beacon should be a list of dicts
|
||||
if not isinstance(config, dict):
|
||||
log.info('Configuration for diskusage beacon must be a dictionary.')
|
||||
return False
|
||||
return True
|
||||
return False, ('Configuration for diskusage beacon '
|
||||
'must be a dictionary.')
|
||||
return True, 'Valid beacon configuration'
|
||||
|
||||
|
||||
def beacon(config):
|
||||
|
|
|
@ -97,44 +97,39 @@ def validate(config):
|
|||
]
|
||||
|
||||
# Configuration for inotify beacon should be a dict of dicts
|
||||
log.debug('config {0}'.format(config))
|
||||
if not isinstance(config, dict):
|
||||
log.info('Configuration for inotify beacon must be a dictionary.')
|
||||
return False
|
||||
return False, 'Configuration for inotify beacon must be a dictionary.'
|
||||
else:
|
||||
for config_item in config:
|
||||
if not isinstance(config[config_item], dict):
|
||||
log.info('Configuration for inotify beacon must '
|
||||
'be a dictionary of dictionaries.')
|
||||
return False
|
||||
return False, ('Configuration for inotify beacon must '
|
||||
'be a dictionary of dictionaries.')
|
||||
else:
|
||||
if not any(j in ['mask', 'recurse', 'auto_add'] for j in config[config_item]):
|
||||
log.info('Configuration for inotify beacon must '
|
||||
'contain mask, recurse or auto_add items.')
|
||||
return False
|
||||
return False, ('Configuration for inotify beacon '
|
||||
'must contain mask, recurse or auto_add items.')
|
||||
|
||||
if 'auto_add' in config[config_item]:
|
||||
if not isinstance(config[config_item]['auto_add'], bool):
|
||||
log.info('Configuration for inotify beacon '
|
||||
'auto_add must be boolean.')
|
||||
return False
|
||||
return False, ('Configuration for inotify beacon '
|
||||
'auto_add must be boolean.')
|
||||
|
||||
if 'recurse' in config[config_item]:
|
||||
if not isinstance(config[config_item]['recurse'], bool):
|
||||
log.info('Configuration for inotify beacon '
|
||||
'recurse must be boolean.')
|
||||
return False
|
||||
return False, ('Configuration for inotify beacon '
|
||||
' recurse must be boolean.')
|
||||
|
||||
if 'mask' in config[config_item]:
|
||||
if not isinstance(config[config_item]['mask'], list):
|
||||
log.info('Configuration for inotify beacon '
|
||||
'mask must be list.')
|
||||
return False
|
||||
return False, ('Configuration for inotify beacon '
|
||||
' mask must be list.')
|
||||
|
||||
for mask in config[config_item]['mask']:
|
||||
if mask not in VALID_MASK:
|
||||
log.info('Configuration for inotify beacon '
|
||||
'invalid mask option {0}.'.format(mask))
|
||||
return False
|
||||
return True
|
||||
return False, ('Configuration for inotify beacon '
|
||||
'invalid mask option {0}.'.format(mask))
|
||||
return True, 'Valid beacon configuration'
|
||||
|
||||
|
||||
def beacon(config):
|
||||
|
|
|
@ -54,10 +54,9 @@ def validate(config):
|
|||
else:
|
||||
for item in config:
|
||||
if not isinstance(config[item], dict):
|
||||
log.info('Configuration for journald beacon must '
|
||||
'be a dictionary of dictionaries.')
|
||||
return False
|
||||
return True
|
||||
return False, ('Configuration for journald beacon must '
|
||||
'be a dictionary of dictionaries.')
|
||||
return True, 'Valid beacon configuration'
|
||||
|
||||
|
||||
def beacon(config):
|
||||
|
|
|
@ -35,36 +35,31 @@ def validate(config):
|
|||
|
||||
# Configuration for load beacon should be a list of dicts
|
||||
if not isinstance(config, list):
|
||||
log.info('Configuration for load beacon must be a list.')
|
||||
return False
|
||||
return False, ('Configuration for load beacon must be a list.')
|
||||
else:
|
||||
for config_item in config:
|
||||
if not isinstance(config_item, dict):
|
||||
log.info('Configuration for load beacon must '
|
||||
'be a list of dictionaries.')
|
||||
return False
|
||||
return False, ('Configuration for load beacon must '
|
||||
'be a list of dictionaries.')
|
||||
else:
|
||||
if not all(j in ['1m', '5m', '15m'] for j in config_item.keys()):
|
||||
log.info('Configuration for load beacon must '
|
||||
'contain 1m, 5m or 15m items.')
|
||||
return False
|
||||
return False, ('Configuration for load beacon must '
|
||||
'contain 1m, 5m or 15m items.')
|
||||
|
||||
for item in ['1m', '5m', '15m']:
|
||||
if item not in config_item:
|
||||
continue
|
||||
|
||||
if not isinstance(config_item[item], list):
|
||||
log.info('Configuration for load beacon: '
|
||||
'1m, 5m and 15m items must be '
|
||||
'a list of two items.')
|
||||
return False
|
||||
return False, ('Configuration for load beacon: '
|
||||
'1m, 5m and 15m items must be '
|
||||
'a list of two items.')
|
||||
else:
|
||||
if len(config_item[item]) != 2:
|
||||
log.info('Configuration for load beacon: '
|
||||
'1m, 5m and 15m items must be '
|
||||
'a list of two items.')
|
||||
return False
|
||||
return True
|
||||
return False, ('Configuration for load beacon: '
|
||||
'1m, 5m and 15m items must be '
|
||||
'a list of two items.')
|
||||
return True, 'Valid beacon configuration'
|
||||
|
||||
|
||||
def beacon(config):
|
||||
|
|
|
@ -48,20 +48,17 @@ def validate(config):
|
|||
|
||||
# Configuration for load beacon should be a list of dicts
|
||||
if not isinstance(config, dict):
|
||||
log.info('Configuration for load beacon must be a dictionary.')
|
||||
return False
|
||||
return False, ('Configuration for load beacon must be a dictionary.')
|
||||
else:
|
||||
for item in config:
|
||||
if not isinstance(config[item], dict):
|
||||
log.info('Configuration for load beacon must '
|
||||
'be a dictionary of dictionaries.')
|
||||
return False
|
||||
return False, ('Configuration for load beacon must '
|
||||
'be a dictionary of dictionaries.')
|
||||
else:
|
||||
if not any(j in VALID_ITEMS for j in config[item]):
|
||||
log.info('Invalid configuration item in '
|
||||
'Beacon configuration.')
|
||||
return False
|
||||
return True
|
||||
return False, ('Invalid configuration item in '
|
||||
'Beacon configuration.')
|
||||
return True, 'Valid beacon configuration'
|
||||
|
||||
|
||||
def beacon(config):
|
||||
|
|
|
@ -38,9 +38,8 @@ def validate(config):
|
|||
Validate the beacon configuration
|
||||
'''
|
||||
if not isinstance(config, dict):
|
||||
log.info('Configuration for rest_example beacon must be a dictionary.')
|
||||
return False
|
||||
return True
|
||||
return False, ('Configuration for rest_example beacon must be a dictionary.')
|
||||
return True, 'Valid beacon configuration'
|
||||
|
||||
|
||||
def beacon(config):
|
||||
|
|
|
@ -18,9 +18,8 @@ def validate(config):
|
|||
'''
|
||||
# Configuration for ps beacon should be a list of dicts
|
||||
if not isinstance(config, dict):
|
||||
log.info('Configuration for ps beacon must be a dictionary.')
|
||||
return False
|
||||
return True
|
||||
return False, ('Configuration for ps beacon must be a dictionary.')
|
||||
return True, 'Valid beacon configuration'
|
||||
|
||||
|
||||
def beacon(config):
|
||||
|
|
|
@ -20,9 +20,8 @@ def validate(config):
|
|||
'''
|
||||
# Configuration for service beacon should be a list of dicts
|
||||
if not isinstance(config, dict):
|
||||
log.info('Configuration for service beacon must be a dictionary.')
|
||||
return False
|
||||
return True
|
||||
return False, ('Configuration for service beacon must be a dictionary.')
|
||||
return True, 'Valid beacon configuration'
|
||||
|
||||
|
||||
def beacon(config):
|
||||
|
|
|
@ -47,9 +47,8 @@ def validate(config):
|
|||
'''
|
||||
# Configuration for sh beacon should be a list of dicts
|
||||
if not isinstance(config, dict):
|
||||
log.info('Configuration for sh beacon must be a dictionary.')
|
||||
return False
|
||||
return True
|
||||
return False, ('Configuration for sh beacon must be a dictionary.')
|
||||
return True, 'Valid beacon configuration'
|
||||
|
||||
|
||||
def beacon(config):
|
||||
|
|
|
@ -32,9 +32,9 @@ def validate(config):
|
|||
'''
|
||||
# Configuration for twilio_txt_msg beacon should be a list of dicts
|
||||
if not isinstance(config, dict):
|
||||
log.info('Configuration for twilio_txt_msg beacon must be a dictionary.')
|
||||
return False
|
||||
return True
|
||||
return False, ('Configuration for twilio_txt_msg beacon '
|
||||
'must be a dictionary.')
|
||||
return True, 'Valid beacon configuration'
|
||||
|
||||
|
||||
def beacon(config):
|
||||
|
|
|
@ -61,9 +61,8 @@ def validate(config):
|
|||
'''
|
||||
# Configuration for wtmp beacon should be a list of dicts
|
||||
if not isinstance(config, dict):
|
||||
log.info('Configuration for wtmp beacon must be a dictionary.')
|
||||
return False
|
||||
return True
|
||||
return False, ('Configuration for wtmp beacon must be a dictionary.')
|
||||
return True, 'Valid beacon configuration'
|
||||
|
||||
|
||||
# TODO: add support for only firing events for specific users and login times
|
||||
|
|
|
@ -55,6 +55,12 @@ class ClientFuncsDict(collections.MutableMapping):
|
|||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
def __getattr__(self, attr):
|
||||
'''
|
||||
Provide access eg. to 'pack'
|
||||
'''
|
||||
return getattr(self.client.functions, attr)
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
|
@ -2043,7 +2043,7 @@ def request_instance(vm_):
|
|||
external_ip = None
|
||||
else:
|
||||
region = '-'.join(kwargs['location'].name.split('-')[:2])
|
||||
kwargs['external_ip'] = __create_orget_address(conn, kwargs['external_ip'], region)
|
||||
external_ip = __create_orget_address(conn, external_ip, region)
|
||||
kwargs['external_ip'] = external_ip
|
||||
vm_['external_ip'] = external_ip
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ and one using cinder volumes already attached
|
|||
centos7-2-iad-rackspace:
|
||||
provider: rackspace-iad
|
||||
size: general1-2
|
||||
block_volume: <volume id>
|
||||
boot_volume: <volume id>
|
||||
|
||||
# create the volume from a snapshot
|
||||
centos7-2-iad-rackspace:
|
||||
|
|
|
@ -2681,7 +2681,7 @@ def is_profile_configured(opts, provider, profile_name, vm_=None):
|
|||
elif driver == 'vmware' or linode_cloning:
|
||||
required_keys.append('clonefrom')
|
||||
elif driver == 'nova':
|
||||
nova_image_keys = ['image', 'block_device_mapping', 'block_device']
|
||||
nova_image_keys = ['image', 'block_device_mapping', 'block_device', 'boot_volume']
|
||||
if not any([key in provider_key for key in nova_image_keys]) and not any([key in profile_key for key in nova_image_keys]):
|
||||
required_keys.extend(nova_image_keys)
|
||||
|
||||
|
|
|
@ -2567,26 +2567,28 @@ class Matcher(object):
|
|||
'''
|
||||
Matches based on IP address or CIDR notation
|
||||
'''
|
||||
|
||||
try:
|
||||
tgt = ipaddress.ip_network(tgt)
|
||||
# Target is a network
|
||||
proto = 'ipv{0}'.format(tgt.version)
|
||||
if proto not in self.opts['grains']:
|
||||
return False
|
||||
else:
|
||||
return salt.utils.network.in_subnet(tgt, self.opts['grains'][proto])
|
||||
# Target is an address?
|
||||
tgt = ipaddress.ip_address(tgt)
|
||||
except: # pylint: disable=bare-except
|
||||
try:
|
||||
# Target should be an address
|
||||
proto = 'ipv{0}'.format(ipaddress.ip_address(tgt).version)
|
||||
if proto not in self.opts['grains']:
|
||||
return False
|
||||
else:
|
||||
return tgt in self.opts['grains'][proto]
|
||||
# Target is a network?
|
||||
tgt = ipaddress.ip_network(tgt)
|
||||
except: # pylint: disable=bare-except
|
||||
log.error('Invalid IP/CIDR target {0}"'.format(tgt))
|
||||
return False
|
||||
log.error('Invalid IP/CIDR target: {0}'.format(tgt))
|
||||
return []
|
||||
proto = 'ipv{0}'.format(tgt.version)
|
||||
|
||||
grains = self.opts['grains']
|
||||
|
||||
if proto not in grains:
|
||||
match = False
|
||||
elif isinstance(tgt, (ipaddress.IPv4Address, ipaddress.IPv6Address)):
|
||||
match = str(tgt) in grains[proto]
|
||||
else:
|
||||
match = salt.utils.network.in_subnet(tgt, grains[proto])
|
||||
|
||||
return match
|
||||
|
||||
def range_match(self, tgt):
|
||||
'''
|
||||
|
|
|
@ -101,14 +101,19 @@ def add(name, beacon_data, **kwargs):
|
|||
|
||||
# Attempt to validate
|
||||
if hasattr(beacon_module, 'validate'):
|
||||
valid = beacon_module.validate(beacon_data)
|
||||
_beacon_data = beacon_data
|
||||
if 'enabled' in _beacon_data:
|
||||
del _beacon_data['enabled']
|
||||
valid, vcomment = beacon_module.validate(_beacon_data)
|
||||
else:
|
||||
log.info('Beacon {0} does not have a validate'
|
||||
' function, skipping validation.'.format(name))
|
||||
valid = True
|
||||
|
||||
if not valid:
|
||||
ret['comment'] = 'Beacon {0} configuration invalid, not adding.'.format(name)
|
||||
ret['result'] = False
|
||||
ret['comment'] = ('Beacon {0} configuration invalid, '
|
||||
'not adding.\n{1}'.format(name, vcomment))
|
||||
return ret
|
||||
|
||||
try:
|
||||
|
@ -165,14 +170,19 @@ def modify(name, beacon_data, **kwargs):
|
|||
|
||||
# Attempt to validate
|
||||
if hasattr(beacon_module, 'validate'):
|
||||
valid = beacon_module.validate(beacon_data)
|
||||
_beacon_data = beacon_data
|
||||
if 'enabled' in _beacon_data:
|
||||
del _beacon_data['enabled']
|
||||
valid, vcomment = beacon_module.validate(_beacon_data)
|
||||
else:
|
||||
log.info('Beacon {0} does not have a validate'
|
||||
' function, skipping validation.'.format(name))
|
||||
valid = True
|
||||
|
||||
if not valid:
|
||||
ret['comment'] = 'Beacon {0} configuration invalid, not modifying.'.format(name)
|
||||
ret['result'] = False
|
||||
ret['comment'] = ('Beacon {0} configuration invalid, '
|
||||
'not adding.\n{1}'.format(name, vcomment))
|
||||
return ret
|
||||
|
||||
_current = current_beacons[name]
|
||||
|
|
|
@ -272,7 +272,7 @@ def gen_locale(locale, **kwargs):
|
|||
locale_info = salt.utils.locales.split_locale(locale)
|
||||
|
||||
# if the charmap has not been supplied, normalize by appening it
|
||||
if not locale_info['charmap']:
|
||||
if not locale_info['charmap'] and not on_ubuntu:
|
||||
locale_info['charmap'] = locale_info['codeset']
|
||||
locale = salt.utils.locales.join_locale(locale_info)
|
||||
|
||||
|
|
|
@ -1106,7 +1106,12 @@ def connect(host, port=None, **kwargs):
|
|||
_proto,
|
||||
garbage,
|
||||
_address) = socket.getaddrinfo(address, port, __family, 0, __proto)[0]
|
||||
except socket.gaierror:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Unable to resolve host {0} on {1} port {2}'.format(host, proto, port)
|
||||
return ret
|
||||
|
||||
try:
|
||||
skt = socket.socket(family, socktype, _proto)
|
||||
skt.settimeout(timeout)
|
||||
|
||||
|
|
|
@ -408,6 +408,13 @@ def refresh_db(saltenv='base'):
|
|||
else:
|
||||
winrepo_source_dir = __opts__['winrepo_source_dir']
|
||||
|
||||
# Clear minion repo-ng cache
|
||||
repo_path = '{0}\\files\\{1}\\win\\repo-ng\\salt-winrepo-ng'\
|
||||
.format(__opts__['cachedir'], saltenv)
|
||||
if not __salt__['file.remove'](repo_path):
|
||||
log.error('pkg.refresh_db: failed to clear existing cache')
|
||||
|
||||
# Cache repo-ng locally
|
||||
cached_files = __salt__['cp.cache_dir'](
|
||||
winrepo_source_dir,
|
||||
saltenv,
|
||||
|
|
|
@ -9,6 +9,49 @@ Module for running windows updates.
|
|||
|
||||
.. versionadded:: 2014.7.0
|
||||
|
||||
Set windows updates to run by category. Default behavior is to install
|
||||
all updates that do not require user interaction to complete.
|
||||
Optionally set ``categories`` to a category of your choice to only
|
||||
install certain updates. Default is to set to install all available but driver updates.
|
||||
The following example will install all Security and Critical Updates,
|
||||
and download but not install standard updates.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' win_update.install_updates categories="['Critical Updates', 'Security Updates']"
|
||||
|
||||
You can also specify a number of features about the update to have a
|
||||
fine grain approach to specific types of updates. These are the following
|
||||
features/states of updates available for configuring:
|
||||
.. code-block:: text
|
||||
'UI' - User interaction required, skipped by default
|
||||
'downloaded' - Already downloaded, included by default
|
||||
'present' - Present on computer, included by default
|
||||
'installed' - Already installed, skipped by default
|
||||
'reboot' - Reboot required, included by default
|
||||
'hidden' - Skip hidden updates, skipped by default
|
||||
'software' - Software updates, included by default
|
||||
'driver' - Driver updates, included by default
|
||||
|
||||
The following example installs all updates that don't require a reboot:
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' win_update.install_updates skips="[{'reboot':True}]"
|
||||
|
||||
|
||||
Once installed Salt will return a similar output:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
2 : Windows Server 2012 Update (KB123456)
|
||||
4 : Internet Explorer Security Update (KB098765)
|
||||
2 : Malware Definition Update (KB321456)
|
||||
...
|
||||
|
||||
The number at the beginning of the line is an OperationResultCode from the Windows Update Agent,
|
||||
it's enumeration is described here: https://msdn.microsoft.com/en-us/library/windows/desktop/aa387095(v=vs.85).aspx.
|
||||
The result code is then followed by the update name and its KB identifier.
|
||||
|
||||
'''
|
||||
# pylint: disable=invalid-name,missing-docstring
|
||||
|
||||
|
@ -17,7 +60,6 @@ from __future__ import absolute_import
|
|||
import logging
|
||||
|
||||
# Import 3rd-party libs
|
||||
import salt.ext.six as six
|
||||
# pylint: disable=import-error
|
||||
from salt.ext.six.moves import range # pylint: disable=no-name-in-module,redefined-builtin
|
||||
try:
|
||||
|
@ -69,7 +111,7 @@ def _gather_update_categories(updateCollection):
|
|||
class PyWinUpdater(object):
|
||||
def __init__(self, categories=None, skipUI=True, skipDownloaded=False,
|
||||
skipInstalled=True, skipReboot=False, skipPresent=False,
|
||||
softwareUpdates=True, driverUpdates=False, skipHidden=True):
|
||||
skipSoftwareUpdates=False, skipDriverUpdates=False, skipHidden=True):
|
||||
log.debug('CoInitializing the pycom system')
|
||||
pythoncom.CoInitialize()
|
||||
|
||||
|
@ -80,8 +122,8 @@ class PyWinUpdater(object):
|
|||
self.skipPresent = skipPresent
|
||||
self.skipHidden = skipHidden
|
||||
|
||||
self.softwareUpdates = softwareUpdates
|
||||
self.driverUpdates = driverUpdates
|
||||
self.skipSoftwareUpdates = skipSoftwareUpdates
|
||||
self.skipDriverUpdates = skipDriverUpdates
|
||||
|
||||
# the list of categories that the user wants to be searched for.
|
||||
self.categories = categories
|
||||
|
@ -178,24 +220,32 @@ class PyWinUpdater(object):
|
|||
|
||||
if self.skipInstalled:
|
||||
searchParams.append('IsInstalled=0')
|
||||
else:
|
||||
searchParams.append('IsInstalled=1')
|
||||
|
||||
if self.skipHidden:
|
||||
searchParams.append('IsHidden=0')
|
||||
else:
|
||||
searchParams.append('IsHidden=1')
|
||||
|
||||
if self.skipReboot:
|
||||
searchParams.append('RebootRequired=0')
|
||||
else:
|
||||
searchParams.append('RebootRequired=1')
|
||||
|
||||
if self.skipPresent:
|
||||
searchParams.append('IsPresent=0')
|
||||
else:
|
||||
searchParams.append('IsPresent=1')
|
||||
|
||||
for i in searchParams:
|
||||
search_string += '{0} and '.format(i)
|
||||
|
||||
if self.softwareUpdates and self.driverUpdates:
|
||||
if not self.skipSoftwareUpdates and not self.skipDriverUpdates:
|
||||
search_string += 'Type=\'Software\' or Type=\'Driver\''
|
||||
elif self.softwareUpdates:
|
||||
elif not self.skipSoftwareUpdates:
|
||||
search_string += 'Type=\'Software\''
|
||||
elif self.driverUpdates:
|
||||
elif not self.skipDriverUpdates:
|
||||
search_string += 'Type=\'Driver\''
|
||||
else:
|
||||
return False
|
||||
|
@ -353,43 +403,34 @@ class PyWinUpdater(object):
|
|||
def GetAvailableCategories(self):
|
||||
return self.foundCategories
|
||||
|
||||
def SetIncludes(self, includes):
|
||||
if includes:
|
||||
for inc in includes:
|
||||
value = inc[next(six.iterkeys(inc))]
|
||||
include = next(six.iterkeys(inc))
|
||||
self.SetInclude(include, value)
|
||||
log.debug('was asked to set {0} to {1}'.format(include, value))
|
||||
def SetSkips(self, skips):
|
||||
if skips:
|
||||
for i in skips:
|
||||
value = i[next(i.iterkeys())]
|
||||
skip = next(i.iterkeys())
|
||||
self.SetSkip(skip, value)
|
||||
log.debug('was asked to set {0} to {1}'.format(skip, value))
|
||||
|
||||
def SetInclude(self, include, state):
|
||||
if include == 'UI':
|
||||
def SetSkip(self, skip, state):
|
||||
if skip == 'UI':
|
||||
self.skipUI = state
|
||||
elif include == 'downloaded':
|
||||
elif skip == 'downloaded':
|
||||
self.skipDownloaded = state
|
||||
elif include == 'installed':
|
||||
elif skip == 'installed':
|
||||
self.skipInstalled = state
|
||||
elif include == 'reboot':
|
||||
elif skip == 'reboot':
|
||||
self.skipReboot = state
|
||||
elif include == 'present':
|
||||
elif skip == 'present':
|
||||
self.skipPresent = state
|
||||
elif include == 'software':
|
||||
self.softwareUpdates = state
|
||||
elif include == 'driver':
|
||||
self.driverUpdates = state
|
||||
log.debug('new search state: \n\t'
|
||||
'UI: {0}\n\t'
|
||||
'Download: {1}\n\t'
|
||||
'Installed: {2}\n\t'
|
||||
'reboot :{3}\n\t'
|
||||
'Present: {4}\n\t'
|
||||
'software: {5}\n\t'
|
||||
'driver: {6}'.format(self.skipUI,
|
||||
self.skipDownloaded,
|
||||
self.skipInstalled,
|
||||
self.skipReboot,
|
||||
self.skipPresent,
|
||||
self.softwareUpdates,
|
||||
self.driverUpdates))
|
||||
elif skip == 'hidden':
|
||||
self.skipHidden = state
|
||||
elif skip == 'software':
|
||||
self.skipSoftwareUpdates = state
|
||||
elif skip == 'driver':
|
||||
self.skipDriverUpdates = state
|
||||
log.debug('new search state: \n\tUI: {0}\n\tDownload: {1}\n\tInstalled: {2}\n\treboot :{3}\n\tPresent: {4}\n\thidden: {5}\n\tsoftware: {6}\n\tdriver: {7}'.format(
|
||||
self.skipUI, self.skipDownloaded, self.skipInstalled, self.skipReboot,
|
||||
self.skipPresent, self.skipHidden, self.skipSoftwareUpdates, self.skipDriverUpdates))
|
||||
|
||||
def __str__(self):
|
||||
results = 'There are {0} updates, by category there are:\n'.format(
|
||||
|
@ -489,10 +530,10 @@ def _install(quidditch, retries=5):
|
|||
|
||||
# this is where the actual functions available to salt begin.
|
||||
|
||||
def list_updates(verbose=False, fields=None, includes=None, retries=5, categories=None):
|
||||
'''Return a list of available updates.
|
||||
|
||||
By default, return a list including the titles of the available updates.
|
||||
def list_updates(verbose=False, fields=None, skips=None, retries=5, categories=None):
|
||||
'''
|
||||
Returns a summary of available updates, grouped into their non-mutually
|
||||
exclusive categories.
|
||||
|
||||
verbose
|
||||
Return full set of results, including several fields from the COM.
|
||||
|
@ -540,7 +581,7 @@ def list_updates(verbose=False, fields=None, includes=None, retries=5, categorie
|
|||
updates = PyWinUpdater()
|
||||
if categories:
|
||||
updates.SetCategories(categories)
|
||||
updates.SetIncludes(includes)
|
||||
updates.SetSkips(skips)
|
||||
|
||||
# this is where we be seeking the things! yar!
|
||||
comment, passed, retries = _search(updates, retries)
|
||||
|
@ -552,7 +593,7 @@ def list_updates(verbose=False, fields=None, includes=None, retries=5, categorie
|
|||
return updates.GetSearchResults(fields=fields)
|
||||
|
||||
|
||||
def download_updates(includes=None, retries=5, categories=None):
|
||||
def download_updates(skips=None, retries=5, categories=None):
|
||||
'''
|
||||
Downloads all available updates, skipping those that require user
|
||||
interaction.
|
||||
|
@ -594,7 +635,7 @@ def download_updates(includes=None, retries=5, categories=None):
|
|||
log.debug('categories to search for are: {0}'.format(str(categories)))
|
||||
quidditch = PyWinUpdater(skipDownloaded=True)
|
||||
quidditch.SetCategories(categories)
|
||||
quidditch.SetIncludes(includes)
|
||||
quidditch.SetSkips(skips)
|
||||
|
||||
# this is where we be seeking the things! yar!
|
||||
comment, passed, retries = _search(quidditch, retries)
|
||||
|
@ -613,7 +654,7 @@ def download_updates(includes=None, retries=5, categories=None):
|
|||
return 'Windows is up to date. \n{0}'.format(comment)
|
||||
|
||||
|
||||
def install_updates(includes=None, retries=5, categories=None):
|
||||
def install_updates(skips=None, retries=5, categories=None):
|
||||
'''
|
||||
Downloads and installs all available updates, skipping those that require
|
||||
user interaction.
|
||||
|
@ -660,7 +701,7 @@ def install_updates(includes=None, retries=5, categories=None):
|
|||
log.debug('categories to search for are: {0}'.format(str(categories)))
|
||||
quidditch = PyWinUpdater()
|
||||
quidditch.SetCategories(categories)
|
||||
quidditch.SetIncludes(includes)
|
||||
quidditch.SetSkips(skips)
|
||||
|
||||
# this is where we be seeking the things! yar!
|
||||
comment, passed, retries = _search(quidditch, retries)
|
||||
|
|
|
@ -202,23 +202,23 @@ def _get_repo_options(**kwargs):
|
|||
|
||||
ret = []
|
||||
if fromrepo:
|
||||
log.info('Restricting to repo \'{0}\''.format(fromrepo))
|
||||
log.info('Restricting to repo \'%s\'', fromrepo)
|
||||
ret.extend(['--disablerepo=*', '--enablerepo=' + fromrepo])
|
||||
else:
|
||||
if disablerepo:
|
||||
targets = [disablerepo] \
|
||||
if not isinstance(disablerepo, list) \
|
||||
else disablerepo
|
||||
log.info('Disabling repo(s): {0}'.format(', '.join(disablerepo)))
|
||||
log.info('Disabling repo(s): %s', ', '.join(targets))
|
||||
ret.extend(
|
||||
['--disablerepo={0}'.format(x) for x in disablerepo]
|
||||
['--disablerepo={0}'.format(x) for x in targets]
|
||||
)
|
||||
if enablerepo:
|
||||
targets = [enablerepo] \
|
||||
if not isinstance(enablerepo, list) \
|
||||
else enablerepo
|
||||
log.info('Enabling repo(s): {0}'.format(', '.join(enablerepo)))
|
||||
ret.extend(['--enablerepo={0}'.format(x) for x in enablerepo])
|
||||
log.info('Enabling repo(s): %s', ', '.join(targets))
|
||||
ret.extend(['--enablerepo={0}'.format(x) for x in targets])
|
||||
return ret
|
||||
|
||||
|
||||
|
@ -230,8 +230,8 @@ def _get_excludes_option(**kwargs):
|
|||
disable_excludes = kwargs.get('disableexcludes', '')
|
||||
ret = []
|
||||
if disable_excludes:
|
||||
log.info('Disabling excludes for \'{0}\''.format(disable_excludes))
|
||||
ret.append(['--disableexcludes={0}'.format(disable_excludes)])
|
||||
log.info('Disabling excludes for \'%s\'', disable_excludes)
|
||||
ret.append('--disableexcludes={0}'.format(disable_excludes))
|
||||
return ret
|
||||
|
||||
|
||||
|
@ -243,7 +243,7 @@ def _get_branch_option(**kwargs):
|
|||
branch = kwargs.get('branch', '')
|
||||
ret = []
|
||||
if branch:
|
||||
log.info('Adding branch \'{0}\''.format(branch))
|
||||
log.info('Adding branch \'%s\'', branch)
|
||||
ret.append('--branch=\'{0}\''.format(branch))
|
||||
return ret
|
||||
|
||||
|
@ -311,8 +311,9 @@ def _get_yum_config():
|
|||
conf[opt] = cp.get('main', opt)
|
||||
else:
|
||||
log.warning(
|
||||
'Could not find [main] section in {0}, using internal '
|
||||
'defaults'.format(fn)
|
||||
'Could not find [main] section in %s, using internal '
|
||||
'defaults',
|
||||
fn
|
||||
)
|
||||
|
||||
return conf
|
||||
|
@ -454,10 +455,9 @@ def latest_version(*names, **kwargs):
|
|||
if not all([x in cur_pkgs for x in names]):
|
||||
log.error(
|
||||
'Problem encountered getting latest version for the '
|
||||
'following package(s): {0}. Stderr follows: \n{1}'.format(
|
||||
', '.join(names),
|
||||
out['stderr']
|
||||
)
|
||||
'following package(s): %s. Stderr follows: \n%s',
|
||||
', '.join(names),
|
||||
out['stderr']
|
||||
)
|
||||
updates = []
|
||||
else:
|
||||
|
@ -543,8 +543,8 @@ def version_cmp(pkg1, pkg2):
|
|||
return cmp_result
|
||||
except Exception as exc:
|
||||
log.warning(
|
||||
'Failed to compare version \'{0}\' to \'{1}\' using '
|
||||
'rpmUtils: {2}'.format(pkg1, pkg2, exc)
|
||||
'Failed to compare version \'%s\' to \'%s\' using '
|
||||
'rpmUtils: %s', pkg1, pkg2, exc
|
||||
)
|
||||
# Fall back to distutils.version.LooseVersion (should only need to do
|
||||
# this for RHEL5, or if an exception is raised when attempting to compare
|
||||
|
@ -786,6 +786,8 @@ list_updates = salt.utils.alias_function(list_upgrades, 'list_updates')
|
|||
|
||||
def info_installed(*names):
|
||||
'''
|
||||
.. versionadded:: 2015.8.1
|
||||
|
||||
Return the information of the named package(s), installed on the system.
|
||||
|
||||
CLI example:
|
||||
|
@ -868,6 +870,7 @@ def refresh_db(**kwargs):
|
|||
__salt__['cmd.run'](clean_cmd, python_shell=False)
|
||||
if check_update_:
|
||||
result = __salt__['cmd.retcode'](update_cmd,
|
||||
output_loglevel='trace',
|
||||
ignore_retcode=True,
|
||||
python_shell=False)
|
||||
return retcodes.get(result, False)
|
||||
|
@ -1971,7 +1974,7 @@ def list_repos(basedir=None):
|
|||
|
||||
basedirs = _normalize_basedir(basedir)
|
||||
repos = {}
|
||||
log.debug('Searching for repos in {0}'.format(basedirs))
|
||||
log.debug('Searching for repos in %s', basedirs)
|
||||
for bdir in basedirs:
|
||||
if not os.path.exists(bdir):
|
||||
continue
|
||||
|
@ -2251,8 +2254,8 @@ def _parse_repo_file(filename):
|
|||
repos[repo][comps[0].strip()] = '='.join(comps[1:])
|
||||
except KeyError:
|
||||
log.error(
|
||||
'Failed to parse line in {0}, offending line was '
|
||||
'\'{1}\''.format(filename, line.rstrip())
|
||||
'Failed to parse line in %s, offending line was '
|
||||
'\'%s\'', filename, line.rstrip()
|
||||
)
|
||||
if comps[0].strip() == 'enabled':
|
||||
repos[repo]['disabled'] = comps[1] != "1"
|
||||
|
@ -2430,11 +2433,11 @@ def download(*packages):
|
|||
for x in cached_pkgs
|
||||
if x.startswith('{0}-'.format(pkg))])
|
||||
for purge_target in set(to_purge):
|
||||
log.debug('Removing cached package {0}'.format(purge_target))
|
||||
log.debug('Removing cached package %s', purge_target)
|
||||
try:
|
||||
os.unlink(purge_target)
|
||||
except OSError as exc:
|
||||
log.error('Unable to remove {0}: {1}'.format(purge_target, exc))
|
||||
log.error('Unable to remove %s: %s', purge_target, exc)
|
||||
|
||||
cmd = ['yumdownloader', '-q', '--destdir={0}'.format(CACHE_DIR)]
|
||||
cmd.extend(packages)
|
||||
|
@ -2504,4 +2507,4 @@ def diff(*paths):
|
|||
ret[path] = __salt__['lowpkg.diff'](
|
||||
local_pkgs[pkg]['path'], path) or 'Unchanged'
|
||||
|
||||
return ret
|
||||
return ret
|
|
@ -2,7 +2,7 @@
|
|||
'''
|
||||
Package support for openSUSE via the zypper package manager
|
||||
|
||||
:depends: - ``zypp`` Python module. Install with ``zypper install python-zypp``
|
||||
:depends: - ``rpm`` Python module. Install with ``zypper install rpm-python``
|
||||
'''
|
||||
|
||||
# Import python libs
|
||||
|
@ -18,6 +18,12 @@ import salt.ext.six as six
|
|||
from salt.exceptions import SaltInvocationError
|
||||
from salt.ext.six.moves import configparser
|
||||
from salt.ext.six.moves.urllib.parse import urlparse as _urlparse
|
||||
|
||||
try:
|
||||
import rpm
|
||||
HAS_RPM = True
|
||||
except ImportError:
|
||||
HAS_RPM = False
|
||||
# pylint: enable=import-error,redefined-builtin,no-name-in-module
|
||||
|
||||
from xml.dom import minidom as dom
|
||||
|
@ -301,11 +307,90 @@ def version(*names, **kwargs):
|
|||
return __salt__['pkg_resource.version'](*names, **kwargs) or {}
|
||||
|
||||
|
||||
def _string_to_evr(verstring):
|
||||
'''
|
||||
Split the version string into epoch, version and release and
|
||||
return this as tuple.
|
||||
|
||||
epoch is always not empty.
|
||||
version and release can be an empty string if such a component
|
||||
could not be found in the version string.
|
||||
|
||||
"2:1.0-1.2" => ('2', '1.0', '1.2)
|
||||
"1.0" => ('0', '1.0', '')
|
||||
"" => ('0', '', '')
|
||||
'''
|
||||
if verstring in [None, '']:
|
||||
return ('0', '', '')
|
||||
idx_e = verstring.find(':')
|
||||
if idx_e != -1:
|
||||
try:
|
||||
epoch = str(int(verstring[:idx_e]))
|
||||
except ValueError:
|
||||
# look, garbage in the epoch field, how fun, kill it
|
||||
epoch = '0' # this is our fallback, deal
|
||||
else:
|
||||
epoch = '0'
|
||||
idx_r = verstring.find('-')
|
||||
if idx_r != -1:
|
||||
version = verstring[idx_e + 1:idx_r]
|
||||
release = verstring[idx_r + 1:]
|
||||
else:
|
||||
version = verstring[idx_e + 1:]
|
||||
release = ''
|
||||
return (epoch, version, release)
|
||||
|
||||
|
||||
def version_cmp(ver1, ver2):
|
||||
'''
|
||||
.. versionadded:: 2015.5.4
|
||||
|
||||
Do a cmp-style comparison on two packages. Return -1 if ver1 < ver2, 0 if
|
||||
ver1 == ver2, and 1 if ver1 > ver2. Return None if there was a problem
|
||||
making the comparison.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' pkg.version_cmp '0.2-001' '0.2.0.1-002'
|
||||
'''
|
||||
if HAS_RPM:
|
||||
try:
|
||||
cmp_result = rpm.labelCompare(
|
||||
_string_to_evr(ver1),
|
||||
_string_to_evr(ver2)
|
||||
)
|
||||
if cmp_result not in (-1, 0, 1):
|
||||
raise Exception(
|
||||
'cmp result \'{0}\' is invalid'.format(cmp_result)
|
||||
)
|
||||
return cmp_result
|
||||
except Exception as exc:
|
||||
log.warning(
|
||||
'Failed to compare version \'{0}\' to \'{1}\' using '
|
||||
'rpmUtils: {2}'.format(ver1, ver2, exc)
|
||||
)
|
||||
return salt.utils.version_cmp(ver1, ver2)
|
||||
|
||||
|
||||
def list_pkgs(versions_as_list=False, **kwargs):
|
||||
'''
|
||||
List the packages currently installed as a dict::
|
||||
List the packages currently installed as a dict with versions
|
||||
as a comma separated string::
|
||||
|
||||
{'<package_name>': '<version>'}
|
||||
{'<package_name>': '<version>[,<version>...]'}
|
||||
|
||||
versions_as_list:
|
||||
If set to true, the versions are provided as a list
|
||||
|
||||
{'<package_name>': ['<version>', '<version>']}
|
||||
|
||||
removed:
|
||||
not supported
|
||||
|
||||
purge_desired:
|
||||
not supported
|
||||
|
||||
CLI Example:
|
||||
|
||||
|
@ -1314,6 +1399,9 @@ def list_products(all=False):
|
|||
all
|
||||
List all products available or only installed. Default is False.
|
||||
|
||||
Includes handling for OEM products, which read the OEM productline file
|
||||
and overwrite the release value.
|
||||
|
||||
CLI Examples:
|
||||
|
||||
.. code-block:: bash
|
||||
|
@ -1322,6 +1410,7 @@ def list_products(all=False):
|
|||
salt '*' pkg.list_products all=True
|
||||
'''
|
||||
ret = list()
|
||||
OEM_PATH = "/var/lib/suseRegister/OEM"
|
||||
doc = dom.parseString(__salt__['cmd.run'](("zypper -x products{0}".format(not all and ' -i' or '')),
|
||||
output_loglevel='trace'))
|
||||
for prd in doc.getElementsByTagName('product-list')[0].getElementsByTagName('product'):
|
||||
|
@ -1335,7 +1424,13 @@ def list_products(all=False):
|
|||
prd.getElementsByTagName('description')
|
||||
).split(os.linesep)]
|
||||
)
|
||||
|
||||
if 'productline' in p_nfo and p_nfo['productline']:
|
||||
oem_file = os.path.join(OEM_PATH, p_nfo['productline'])
|
||||
if os.path.isfile(oem_file):
|
||||
with salt.utils.fopen(oem_file, 'r') as rfile:
|
||||
oem_release = rfile.readline().strip()
|
||||
if oem_release:
|
||||
p_nfo['release'] = oem_release
|
||||
ret.append(p_nfo)
|
||||
|
||||
return ret
|
||||
|
|
|
@ -43,11 +43,13 @@ intended to be used to deploy a file using ``contents_pillar`` with a
|
|||
ext_pillar:
|
||||
- file_tree:
|
||||
root_dir: /path/to/root/directory
|
||||
keep_newlines:
|
||||
keep_newline:
|
||||
- files/testdir/*
|
||||
|
||||
.. note::
|
||||
Binary files are not affected by the ``keep_newlines`` configuration.
|
||||
In earlier releases, this documentation incorrectly stated that binary
|
||||
files would not affected by the ``keep_newline`` configuration. However,
|
||||
this module does not actually distinguish between binary and text files.
|
||||
|
||||
|
||||
Assigning Pillar Data to Individual Hosts
|
||||
|
@ -203,11 +205,12 @@ def _construct_pillar(top_dir, follow_dir_links, keep_newline=False):
|
|||
# Find current path in pillar tree
|
||||
pillar_node = pillar
|
||||
norm_dir_path = os.path.normpath(dir_path)
|
||||
prefix = os.path.relpath(norm_dir_path, norm_top_dir)
|
||||
if norm_dir_path != norm_top_dir:
|
||||
prefix = rel_path = os.path.relpath(norm_dir_path, norm_top_dir)
|
||||
path_parts = []
|
||||
while rel_path:
|
||||
rel_path, tail = os.path.split(rel_path)
|
||||
head = prefix
|
||||
while head:
|
||||
head, tail = os.path.split(head)
|
||||
path_parts.insert(0, tail)
|
||||
while path_parts:
|
||||
pillar_node = pillar_node[path_parts.pop(0)]
|
||||
|
|
|
@ -216,7 +216,7 @@ def clear_mine_func(tgt=None, expr_form='glob', clear_mine_func_flag=None):
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
salt-run cache.clear_mine_func tgt='*' clear_mine_func='network.interfaces'
|
||||
salt-run cache.clear_mine_func tgt='*' clear_mine_func_flag='network.interfaces'
|
||||
'''
|
||||
return _clear_cache(tgt, expr_form, clear_mine_func_flag=clear_mine_func_flag)
|
||||
|
||||
|
|
|
@ -346,8 +346,6 @@ def extracted(name,
|
|||
if len(files) > 0:
|
||||
ret['result'] = True
|
||||
ret['changes']['directories_created'] = [name]
|
||||
if if_missing != name:
|
||||
ret['changes']['directories_created'].append(if_missing)
|
||||
ret['changes']['extracted_files'] = files
|
||||
ret['comment'] = '{0} extracted in {1}'.format(source, name)
|
||||
if not keep:
|
||||
|
|
|
@ -29,6 +29,9 @@ Management of the Salt beacons
|
|||
- 1.0
|
||||
|
||||
'''
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def present(name,
|
||||
|
@ -66,8 +69,11 @@ def present(name,
|
|||
ret['comment'] = result['comment']
|
||||
return ret
|
||||
else:
|
||||
ret['comment'].append('Modifying {0} in beacons'.format(name))
|
||||
ret['changes'] = result['changes']
|
||||
if 'changes' in result:
|
||||
ret['comment'].append('Modifying {0} in beacons'.format(name))
|
||||
ret['changes'] = result['changes']
|
||||
else:
|
||||
ret['comment'].append(result['comment'])
|
||||
else:
|
||||
if 'test' in __opts__ and __opts__['test']:
|
||||
kwargs['test'] = True
|
||||
|
|
|
@ -50,25 +50,25 @@ data in pillar. Here's an example pillar structure:
|
|||
- 'server-2': blade2
|
||||
|
||||
blades:
|
||||
blade1:
|
||||
server-1:
|
||||
idrac_password: saltstack1
|
||||
ipmi_over_lan: True
|
||||
ip: 172.17.17.132
|
||||
netmask: 255.255.0.0
|
||||
gateway: 172.17.17.1
|
||||
blade2:
|
||||
server-2:
|
||||
idrac_password: saltstack1
|
||||
ipmi_over_lan: True
|
||||
ip: 172.17.17.2
|
||||
netmask: 255.255.0.0
|
||||
gateway: 172.17.17.1
|
||||
blade3:
|
||||
server-3:
|
||||
idrac_password: saltstack1
|
||||
ipmi_over_lan: True
|
||||
ip: 172.17.17.20
|
||||
netmask: 255.255.0.0
|
||||
gateway: 172.17.17.1
|
||||
blade4:
|
||||
server-4:
|
||||
idrac_password: saltstack1
|
||||
ipmi_over_lan: True
|
||||
ip: 172.17.17.2
|
||||
|
@ -102,8 +102,8 @@ pillar stated above:
|
|||
- mode: {{ details['management_mode'] }}
|
||||
- idrac_launch: {{ details['idrac_launch'] }}
|
||||
- slot_names:
|
||||
{% for k, v in details['slot_names'].iteritems() %}
|
||||
- {{ k }}: {{ v }}
|
||||
{% for entry details['slot_names'] %}
|
||||
- {{ entry.keys()[0] }}: {{ entry[entry.keys()[0]] }}
|
||||
{% endfor %}
|
||||
|
||||
blade_powercycle:
|
||||
|
@ -121,6 +121,18 @@ pillar stated above:
|
|||
- idrac_password: {{ v['idrac_password'] }}
|
||||
{% endfor %}
|
||||
|
||||
# Set management ip addresses, passwords, and snmp strings for switches
|
||||
{% for k, v in details['switches'].iteritems() %}
|
||||
{{ k }}-switch-setup:
|
||||
dellchassis.switch:
|
||||
- name: {{ k }}
|
||||
- ip: {{ v['ip'] }}
|
||||
- netmask: {{ v['netmask'] }}
|
||||
- gateway: {{ v['gateway'] }}
|
||||
- password: {{ v['password'] }}
|
||||
- snmp: {{ v['snmp'] }}
|
||||
{% endfor %}
|
||||
|
||||
.. note::
|
||||
|
||||
This state module relies on the dracr.py execution module, which runs racadm commands on
|
||||
|
|
|
@ -5,6 +5,10 @@ States to manage git repositories and git configuration
|
|||
.. important::
|
||||
Before using git over ssh, make sure your remote host fingerprint exists in
|
||||
your ``~/.ssh/known_hosts`` file.
|
||||
|
||||
.. versionchanged:: 2015.8.8
|
||||
This state module now requires git 1.6.5 (released 10 October 2009) or
|
||||
newer.
|
||||
'''
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
@ -29,7 +33,10 @@ def __virtual__():
|
|||
'''
|
||||
Only load if git is available
|
||||
'''
|
||||
return __salt__['cmd.has_exec']('git')
|
||||
if 'git.version' not in __salt__:
|
||||
return False
|
||||
git_ver = _LooseVersion(__salt__['git.version'](versioninfo=False))
|
||||
return git_ver >= _LooseVersion('1.6.5')
|
||||
|
||||
|
||||
def _revs_equal(rev1, rev2, rev_type):
|
||||
|
@ -1008,8 +1015,16 @@ def latest(name,
|
|||
)
|
||||
branch_opts = [set_upstream, desired_upstream]
|
||||
elif upstream and desired_upstream is False:
|
||||
upstream_action = 'Tracking branch was unset'
|
||||
branch_opts = ['--unset-upstream']
|
||||
# If the remote_rev is a tag or SHA1, and there is an
|
||||
# upstream tracking branch, we will unset it. However, we
|
||||
# can only do this if the git version is 1.8.0 or newer, as
|
||||
# the --unset-upstream option was not added until that
|
||||
# version.
|
||||
if git_ver >= _LooseVersion('1.8.0'):
|
||||
upstream_action = 'Tracking branch was unset'
|
||||
branch_opts = ['--unset-upstream']
|
||||
else:
|
||||
branch_opts = None
|
||||
elif desired_upstream and upstream != desired_upstream:
|
||||
upstream_action = (
|
||||
'Tracking branch was updated to {0}'.format(
|
||||
|
@ -1151,10 +1166,24 @@ def latest(name,
|
|||
ignore_retcode=True):
|
||||
merge_rev = remote_rev if rev == 'HEAD' \
|
||||
else desired_upstream
|
||||
|
||||
if git_ver >= _LooseVersion('1.8.1.6'):
|
||||
# --ff-only added in version 1.8.1.6. It's not
|
||||
# 100% necessary, but if we can use it, we'll
|
||||
# ensure that the merge doesn't go through if
|
||||
# not a fast-forward. Granted, the logic that
|
||||
# gets us to this point shouldn't allow us to
|
||||
# attempt this merge if it's not a
|
||||
# fast-forward, but it's an extra layer of
|
||||
# protection.
|
||||
merge_opts = ['--ff-only']
|
||||
else:
|
||||
merge_opts = []
|
||||
|
||||
__salt__['git.merge'](
|
||||
target,
|
||||
rev=merge_rev,
|
||||
opts=['--ff-only'],
|
||||
opts=merge_opts,
|
||||
user=user
|
||||
)
|
||||
comments.append(
|
||||
|
@ -1420,8 +1449,16 @@ def latest(name,
|
|||
)
|
||||
branch_opts = [set_upstream, desired_upstream]
|
||||
elif upstream and desired_upstream is False:
|
||||
upstream_action = 'Tracking branch was unset'
|
||||
branch_opts = ['--unset-upstream']
|
||||
# If the remote_rev is a tag or SHA1, and there is an
|
||||
# upstream tracking branch, we will unset it. However,
|
||||
# we can only do this if the git version is 1.8.0 or
|
||||
# newer, as the --unset-upstream option was not added
|
||||
# until that version.
|
||||
if git_ver >= _LooseVersion('1.8.0'):
|
||||
upstream_action = 'Tracking branch was unset'
|
||||
branch_opts = ['--unset-upstream']
|
||||
else:
|
||||
branch_opts = None
|
||||
elif desired_upstream and upstream != desired_upstream:
|
||||
upstream_action = (
|
||||
'Tracking branch was updated to {0}'.format(
|
||||
|
|
|
@ -134,7 +134,7 @@ def list_present(name, value, delimiter=DEFAULT_TARGET_DELIM):
|
|||
'changes': {},
|
||||
'result': True,
|
||||
'comment': ''}
|
||||
grain = __grains__.get(name)
|
||||
grain = __salt__['grains.get'](name)
|
||||
|
||||
if grain:
|
||||
# check whether grain is a list
|
||||
|
@ -143,7 +143,7 @@ def list_present(name, value, delimiter=DEFAULT_TARGET_DELIM):
|
|||
ret['comment'] = 'Grain {0} is not a valid list'.format(name)
|
||||
return ret
|
||||
if isinstance(value, list):
|
||||
if set(value).issubset(set(__grains__.get(name))):
|
||||
if set(value).issubset(set(__salt__['grains.get'](name))):
|
||||
ret['comment'] = 'Value {1} is already in grain {0}'.format(name, value)
|
||||
return ret
|
||||
else:
|
||||
|
@ -163,7 +163,7 @@ def list_present(name, value, delimiter=DEFAULT_TARGET_DELIM):
|
|||
return ret
|
||||
new_grains = __salt__['grains.append'](name, value)
|
||||
if isinstance(value, list):
|
||||
if not set(value).issubset(set(__grains__.get(name))):
|
||||
if not set(value).issubset(set(__salt__['grains.get'](name))):
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Failed append value {1} to grain {0}'.format(name, value)
|
||||
return ret
|
||||
|
|
|
@ -375,27 +375,34 @@ def _find_install_targets(name=None,
|
|||
if not (name in cur_pkgs and version in (None, cur_pkgs[name]))
|
||||
])
|
||||
if not_installed:
|
||||
problems = _preflight_check(not_installed, **kwargs)
|
||||
comments = []
|
||||
if problems.get('no_suggest'):
|
||||
comments.append(
|
||||
'The following package(s) were not found, and no possible '
|
||||
'matches were found in the package db: '
|
||||
'{0}'.format(', '.join(sorted(problems['no_suggest'])))
|
||||
)
|
||||
if problems.get('suggest'):
|
||||
for pkgname, suggestions in six.iteritems(problems['suggest']):
|
||||
try:
|
||||
problems = _preflight_check(not_installed, **kwargs)
|
||||
except CommandExecutionError:
|
||||
pass
|
||||
else:
|
||||
comments = []
|
||||
if problems.get('no_suggest'):
|
||||
comments.append(
|
||||
'Package \'{0}\' not found (possible matches: {1})'
|
||||
.format(pkgname, ', '.join(suggestions))
|
||||
'The following package(s) were not found, and no '
|
||||
'possible matches were found in the package db: '
|
||||
'{0}'.format(
|
||||
', '.join(sorted(problems['no_suggest']))
|
||||
)
|
||||
)
|
||||
if comments:
|
||||
if len(comments) > 1:
|
||||
comments.append('')
|
||||
return {'name': name,
|
||||
'changes': {},
|
||||
'result': False,
|
||||
'comment': '. '.join(comments).rstrip()}
|
||||
if problems.get('suggest'):
|
||||
for pkgname, suggestions in \
|
||||
six.iteritems(problems['suggest']):
|
||||
comments.append(
|
||||
'Package \'{0}\' not found (possible matches: '
|
||||
'{1})'.format(pkgname, ', '.join(suggestions))
|
||||
)
|
||||
if comments:
|
||||
if len(comments) > 1:
|
||||
comments.append('')
|
||||
return {'name': name,
|
||||
'changes': {},
|
||||
'result': False,
|
||||
'comment': '. '.join(comments).rstrip()}
|
||||
|
||||
# Find out which packages will be targeted in the call to pkg.install
|
||||
targets = {}
|
||||
|
|
|
@ -53,17 +53,16 @@ def present(
|
|||
The user who owns the ssh authorized keys file to modify
|
||||
|
||||
fingerprint
|
||||
The fingerprint of the key which must be presented in the known_hosts
|
||||
The fingerprint of the key which must be present in the known_hosts
|
||||
file (optional if key specified)
|
||||
|
||||
key
|
||||
The public key which must be presented in the known_hosts file
|
||||
The public key which must be present in the known_hosts file
|
||||
(optional if fingerprint specified)
|
||||
|
||||
port
|
||||
optional parameter, denoting the port of the remote host, which will be
|
||||
used in case, if the public key will be requested from it. By default
|
||||
the port 22 is used.
|
||||
optional parameter, port which will be used to when requesting the
|
||||
public key from the remote host, defaults to port 22.
|
||||
|
||||
enc
|
||||
Defines what type of key is being used, can be ed25519, ecdsa ssh-rsa
|
||||
|
|
|
@ -21,9 +21,13 @@ and download but not install standard updates.
|
|||
- categories:
|
||||
- 'Critical Updates'
|
||||
- 'Security Updates'
|
||||
- skips:
|
||||
- downloaded
|
||||
win_update.downloaded:
|
||||
- categories:
|
||||
- 'Updates'
|
||||
- skips:
|
||||
- downloaded
|
||||
|
||||
You can also specify a number of features about the update to have a
|
||||
fine grain approach to specific types of updates. These are the following
|
||||
|
@ -32,21 +36,19 @@ features/states of updates available for configuring:
|
|||
.. code-block:: text
|
||||
|
||||
'UI' - User interaction required, skipped by default
|
||||
'downloaded' - Already downloaded, skipped by default (downloading)
|
||||
'present' - Present on computer, included by default (installing)
|
||||
'downloaded' - Already downloaded, included by default
|
||||
'present' - Present on computer, skipped by default
|
||||
'installed' - Already installed, skipped by default
|
||||
'reboot' - Reboot required, included by default
|
||||
'hidden' - skip those updates that have been hidden.
|
||||
'hidden' - Skip updates that have been hidden, skipped by default
|
||||
'software' - Software updates, included by default
|
||||
'driver' - driver updates, skipped by default
|
||||
'driver' - driver updates, included by default
|
||||
|
||||
The following example installs all driver updates that don't require a reboot:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
gryffindor:
|
||||
win_update.installed:
|
||||
- includes:
|
||||
- skips:
|
||||
- driver: True
|
||||
- software: False
|
||||
- reboot: False
|
||||
|
@ -54,7 +56,6 @@ The following example installs all driver updates that don't require a reboot:
|
|||
To just update your windows machine, add this your sls:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
updates:
|
||||
win_update.installed
|
||||
'''
|
||||
|
@ -64,7 +65,6 @@ from __future__ import absolute_import
|
|||
import logging
|
||||
|
||||
# Import 3rd-party libs
|
||||
import salt.ext.six as six
|
||||
# pylint: disable=import-error
|
||||
from salt.ext.six.moves import range # pylint: disable=redefined-builtin
|
||||
try:
|
||||
|
@ -113,9 +113,9 @@ def _gather_update_categories(updateCollection):
|
|||
|
||||
|
||||
class PyWinUpdater(object):
|
||||
def __init__(self, categories=None, skipUI=True, skipDownloaded=True,
|
||||
skipInstalled=True, skipReboot=False, skipPresent=True,
|
||||
softwareUpdates=True, driverUpdates=False, skipHidden=True):
|
||||
def __init__(self, categories=None, skipUI=True, skipDownloaded=False,
|
||||
skipInstalled=True, skipReboot=False, skipPresent=False,
|
||||
skipSoftwareUpdates=False, skipDriverUpdates=False, skipHidden=True):
|
||||
log.debug('CoInitializing the pycom system')
|
||||
pythoncom.CoInitialize()
|
||||
|
||||
|
@ -127,8 +127,8 @@ class PyWinUpdater(object):
|
|||
self.skipPresent = skipPresent
|
||||
self.skipHidden = skipHidden
|
||||
|
||||
self.softwareUpdates = softwareUpdates
|
||||
self.driverUpdates = driverUpdates
|
||||
self.skipSoftwareUpdates = skipSoftwareUpdates
|
||||
self.skipDriverUpdates = skipDriverUpdates
|
||||
self.categories = categories
|
||||
self.foundCategories = None
|
||||
# pylint: enable=invalid-name
|
||||
|
@ -201,9 +201,9 @@ class PyWinUpdater(object):
|
|||
searchParams.append('IsHidden=1')
|
||||
|
||||
if self.skipReboot:
|
||||
searchParams.append('RebootRequired=1')
|
||||
else:
|
||||
searchParams.append('RebootRequired=0')
|
||||
else:
|
||||
searchParams.append('RebootRequired=1')
|
||||
|
||||
if self.skipPresent:
|
||||
searchParams.append('IsPresent=0')
|
||||
|
@ -216,11 +216,11 @@ class PyWinUpdater(object):
|
|||
else:
|
||||
search_string += '{0} and '.format(searchParams[1])
|
||||
|
||||
if self.softwareUpdates and self.driverUpdates:
|
||||
if not self.skipSoftwareUpdates and not self.skipDriverUpdates:
|
||||
search_string += 'Type=\'Software\' or Type=\'Driver\''
|
||||
elif self.softwareUpdates:
|
||||
elif not self.skipSoftwareUpdates:
|
||||
search_string += 'Type=\'Software\''
|
||||
elif self.driverUpdates:
|
||||
elif not self.skipDriverUpdates:
|
||||
search_string += 'Type=\'Driver\''
|
||||
else:
|
||||
return False
|
||||
|
@ -301,32 +301,34 @@ class PyWinUpdater(object):
|
|||
def GetAvailableCategories(self):
|
||||
return self.foundCategories
|
||||
|
||||
def SetIncludes(self, includes):
|
||||
if includes:
|
||||
for i in includes:
|
||||
value = i[next(six.iterkeys(i))]
|
||||
include = next(six.iterkeys(i))
|
||||
self.SetInclude(include, value)
|
||||
log.debug('was asked to set {0} to {1}'.format(include, value))
|
||||
def SetSkips(self, skips):
|
||||
if skips:
|
||||
for i in skips:
|
||||
value = i[next(i.iterkeys())]
|
||||
skip = next(i.iterkeys())
|
||||
self.SetSkip(skip, value)
|
||||
log.debug('was asked to set {0} to {1}'.format(skip, value))
|
||||
|
||||
def SetInclude(self, include, state):
|
||||
if include == 'UI':
|
||||
def SetSkip(self, skip, state):
|
||||
if skip == 'UI':
|
||||
self.skipUI = state
|
||||
elif include == 'downloaded':
|
||||
elif skip == 'downloaded':
|
||||
self.skipDownloaded = state
|
||||
elif include == 'installed':
|
||||
elif skip == 'installed':
|
||||
self.skipInstalled = state
|
||||
elif include == 'reboot':
|
||||
elif skip == 'reboot':
|
||||
self.skipReboot = state
|
||||
elif include == 'present':
|
||||
elif skip == 'present':
|
||||
self.skipPresent = state
|
||||
elif include == 'software':
|
||||
self.softwareUpdates = state
|
||||
elif include == 'driver':
|
||||
self.driverUpdates = state
|
||||
log.debug('new search state: \n\tUI: {0}\n\tDownload: {1}\n\tInstalled: {2}\n\treboot :{3}\n\tPresent: {4}\n\tsoftware: {5}\n\tdriver: {6}'.format(
|
||||
elif skip == 'hidden':
|
||||
self.skipHidden = state
|
||||
elif skip == 'software':
|
||||
self.skipSoftwareUpdates = state
|
||||
elif skip == 'driver':
|
||||
self.skipDriverUpdates = state
|
||||
log.debug('new search state: \n\tUI: {0}\n\tDownload: {1}\n\tInstalled: {2}\n\treboot :{3}\n\tPresent: {4}\n\thidden: {5}\n\tsoftware: {6}\n\tdriver: {7}'.format(
|
||||
self.skipUI, self.skipDownloaded, self.skipInstalled, self.skipReboot,
|
||||
self.skipPresent, self.softwareUpdates, self.driverUpdates))
|
||||
self.skipPresent, self.skipHidden, self.skipSoftwareUpdates, self.skipDriverUpdates))
|
||||
|
||||
|
||||
def _search(win_updater, retries=5):
|
||||
|
@ -400,7 +402,7 @@ def _install(win_updater, retries=5):
|
|||
return (comment, True, retries)
|
||||
|
||||
|
||||
def installed(name, categories=None, includes=None, retries=10):
|
||||
def installed(name, categories=None, skips=None, retries=10):
|
||||
'''
|
||||
Install specified windows updates.
|
||||
|
||||
|
@ -421,7 +423,7 @@ def installed(name, categories=None, includes=None, retries=10):
|
|||
Security Updates
|
||||
Update Rollups
|
||||
|
||||
includes:
|
||||
skips:
|
||||
a list of features of the updates to cull by. Available features:
|
||||
|
||||
.. code-block:: text
|
||||
|
@ -448,7 +450,7 @@ def installed(name, categories=None, includes=None, retries=10):
|
|||
log.debug('categories to search for are: {0}'.format(categories))
|
||||
win_updater = PyWinUpdater()
|
||||
win_updater.SetCategories(categories)
|
||||
win_updater.SetIncludes(includes)
|
||||
win_updater.SetSkips(skips)
|
||||
|
||||
# this is where we be seeking the things! yar!
|
||||
comment, passed, retries = _search(win_updater, retries)
|
||||
|
@ -478,7 +480,7 @@ def installed(name, categories=None, includes=None, retries=10):
|
|||
return ret
|
||||
|
||||
|
||||
def downloaded(name, categories=None, includes=None, retries=10):
|
||||
def downloaded(name, categories=None, skips=None, retries=10):
|
||||
'''
|
||||
Cache updates for later install.
|
||||
|
||||
|
@ -499,7 +501,7 @@ def downloaded(name, categories=None, includes=None, retries=10):
|
|||
Security Updates
|
||||
Update Rollups
|
||||
|
||||
includes:
|
||||
skips:
|
||||
a list of features of the updates to cull by. Available features:
|
||||
|
||||
.. code-block:: text
|
||||
|
@ -526,7 +528,7 @@ def downloaded(name, categories=None, includes=None, retries=10):
|
|||
log.debug('categories to search for are: {0}'.format(categories))
|
||||
win_updater = PyWinUpdater()
|
||||
win_updater.SetCategories(categories)
|
||||
win_updater.SetIncludes(includes)
|
||||
win_updater.SetSkips(skips)
|
||||
|
||||
# this is where we be seeking the things! yar!
|
||||
comment, passed, retries = _search(win_updater, retries)
|
||||
|
|
|
@ -550,8 +550,8 @@ class ZeroMQReqServerChannel(salt.transport.mixins.auth.AESReqServerMixin, salt.
|
|||
try:
|
||||
payload = self.serial.loads(payload[0])
|
||||
payload = self._decode_payload(payload)
|
||||
except Exception as e:
|
||||
log.error('Bad load from minion')
|
||||
except Exception as exc:
|
||||
log.error('Bad load from minion: %s: %s', type(exc).__name__, exc)
|
||||
stream.send(self.serial.dumps('bad load'))
|
||||
raise tornado.gen.Return()
|
||||
|
||||
|
|
|
@ -322,12 +322,26 @@ class CkMinions(object):
|
|||
elif cache_enabled:
|
||||
minions = os.listdir(os.path.join(self.opts['cachedir'], 'minions'))
|
||||
else:
|
||||
return list()
|
||||
return []
|
||||
|
||||
if cache_enabled:
|
||||
cdir = os.path.join(self.opts['cachedir'], 'minions')
|
||||
if not os.path.isdir(cdir):
|
||||
return list(minions)
|
||||
|
||||
tgt = expr
|
||||
try:
|
||||
# Target is an address?
|
||||
tgt = ipaddress.ip_address(tgt)
|
||||
except: # pylint: disable=bare-except
|
||||
try:
|
||||
# Target is a network?
|
||||
tgt = ipaddress.ip_network(tgt)
|
||||
except: # pylint: disable=bare-except
|
||||
log.error('Invalid IP/CIDR target: {0}'.format(tgt))
|
||||
return []
|
||||
proto = 'ipv{0}'.format(tgt.version)
|
||||
|
||||
for id_ in os.listdir(cdir):
|
||||
if not greedy and id_ not in minions:
|
||||
continue
|
||||
|
@ -342,26 +356,12 @@ class CkMinions(object):
|
|||
except (IOError, OSError):
|
||||
continue
|
||||
|
||||
match = True
|
||||
tgt = expr
|
||||
try:
|
||||
tgt = ipaddress.ip_network(tgt)
|
||||
# Target is a network
|
||||
proto = 'ipv{0}'.format(tgt.version)
|
||||
if proto not in self.opts['grains']:
|
||||
match = False
|
||||
else:
|
||||
match = salt.utils.network.in_subnet(tgt, self.opts['grains'][proto])
|
||||
except: # pylint: disable=bare-except
|
||||
try:
|
||||
# Target should be an address
|
||||
proto = 'ipv{0}'.format(ipaddress.ip_address(tgt).version)
|
||||
if proto not in self.opts['grains']:
|
||||
match = False
|
||||
else:
|
||||
match = tgt in self.opts['grains'][proto]
|
||||
except: # pylint: disable=bare-except
|
||||
log.error('Invalid IP/CIDR target {0}"'.format(tgt))
|
||||
if proto not in grains:
|
||||
match = False
|
||||
elif isinstance(tgt, (ipaddress.IPv4Address, ipaddress.IPv6Address)):
|
||||
match = str(tgt) in grains[proto]
|
||||
else:
|
||||
match = salt.utils.network.in_subnet(tgt, grains[proto])
|
||||
|
||||
if not match and id_ in minions:
|
||||
minions.remove(id_)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Common code for RPM operations
|
||||
Common functions for working with RPM packages
|
||||
'''
|
||||
|
||||
# Import python libs
|
||||
|
|
|
@ -737,7 +737,10 @@ class Schedule(object):
|
|||
)
|
||||
)
|
||||
|
||||
ret['retcode'] = self.functions.pack['__context__']['retcode']
|
||||
# runners do not provide retcode
|
||||
if 'retcode' in self.functions.pack['__context__']:
|
||||
ret['retcode'] = self.functions.pack['__context__']['retcode']
|
||||
|
||||
ret['success'] = True
|
||||
except Exception:
|
||||
log.exception("Unhandled exception running {0}".format(ret['fun']))
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
add_contents_pillar_sls:
|
||||
file.managed:
|
||||
- name: /tmp/test-lists-content-pillars
|
||||
- contents_pillar: companions:three
|
|
@ -1945,6 +1945,14 @@ class FileTest(integration.ModuleCase, integration.SaltReturnAssertsMixIn):
|
|||
os.remove(source)
|
||||
os.remove(dest)
|
||||
|
||||
@destructiveTest
|
||||
def test_contents_pillar_with_pillar_list(self):
|
||||
'''
|
||||
This tests for any regressions for this issue:
|
||||
https://github.com/saltstack/salt/issues/30934
|
||||
'''
|
||||
ret = self.run_function('state.sls', mods='file_contents_pillar')
|
||||
self.assertSaltTrueReturn(ret)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
|
|
|
@ -57,6 +57,10 @@ class MockPyWinUpdater(object):
|
|||
'''
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def SetSkips(arg):
|
||||
return True
|
||||
|
||||
|
||||
@patch('salt.states.win_update.PyWinUpdater', MockPyWinUpdater)
|
||||
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
||||
|
|
Loading…
Add table
Reference in a new issue