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:
rallytime 2016-02-18 11:10:44 -07:00
commit 7ea9dacbdd
44 changed files with 542 additions and 298 deletions

View file

@ -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.

View file

@ -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

View file

@ -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
==================

View file

@ -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

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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:

View file

@ -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)

View file

@ -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):
'''

View file

@ -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]

View file

@ -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)

View file

@ -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)

View file

@ -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,

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)]

View file

@ -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)

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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(

View file

@ -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

View file

@ -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 = {}

View file

@ -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

View file

@ -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)

View file

@ -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()

View file

@ -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_)

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
'''
Common code for RPM operations
Common functions for working with RPM packages
'''
# Import python libs

View file

@ -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']))

View file

@ -0,0 +1,4 @@
add_contents_pillar_sls:
file.managed:
- name: /tmp/test-lists-content-pillars
- contents_pillar: companions:three

View file

@ -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

View file

@ -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)