Merge branch '2017.7' into fix_46433

This commit is contained in:
Nicole Thomas 2018-04-03 15:47:59 -04:00 committed by GitHub
commit 89af0a6222
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 942 additions and 301 deletions

View File

@ -250,9 +250,9 @@ on_saltstack = 'SALT_ON_SALTSTACK' in os.environ
project = 'Salt'
version = salt.version.__version__
latest_release = '2017.7.4' # latest release
previous_release = '2016.11.9' # latest release from previous branch
previous_release_dir = '2016.11' # path on web server for previous branch
latest_release = '2018.3.0' # latest release
previous_release = '2017.7.5' # latest release from previous branch
previous_release_dir = '2017.7' # path on web server for previous branch
next_release = '' # next release
next_release_dir = '' # path on web server for next release branch
@ -263,8 +263,8 @@ if on_saltstack:
copyright = time.strftime("%Y")
# < --- START do not merge these settings to other branches START ---> #
build_type = 'latest' # latest, previous, develop, next
release = latest_release # version, latest_release, previous_release
build_type = 'previous' # latest, previous, develop, next
release = previous_release # version, latest_release, previous_release
# < --- END do not merge these settings to other branches END ---> #
# Set google custom search engine

View File

@ -49,17 +49,13 @@ the SaltStack Repository.
Installation from the Community-Maintained Repository
=====================================================
Beginning with version 0.9.4, Salt has been available in `EPEL`_. For
RHEL/CentOS 5, `Fedora COPR`_ is a single community repository that provides
Salt packages due to the removal from EPEL5.
Beginning with version 0.9.4, Salt has been available in `EPEL`_.
.. note::
Packages in these repositories are built by community, and it can
take a little while until the latest stable SaltStack release become
available.
Packages in this repository are built by community, and it can take a little
while until the latest stable SaltStack release become available.
.. _`EPEL`: http://fedoraproject.org/wiki/EPEL
.. _`Fedora COPR`: https://copr.fedorainfracloud.org/coprs/saltstack/salt-el5/
RHEL/CentOS 6 and 7, Scientific Linux, etc.
-------------------------------------------
@ -146,26 +142,13 @@ ZeroMQ 4
========
We recommend using ZeroMQ 4 where available. SaltStack provides ZeroMQ 4.0.5
and pyzmq 14.5.0 in the :ref:`SaltStack Repository <installation-rhel-repo>`
as well as a separate `zeromq4 COPR`_ repository.
.. _`zeromq4 COPR`: http://copr.fedorainfracloud.org/coprs/saltstack/zeromq4/
and ``pyzmq`` 14.5.0 in the :ref:`SaltStack Repository
<installation-rhel-repo>`.
If this repository is added *before* Salt is installed, then installing either
``salt-master`` or ``salt-minion`` will automatically pull in ZeroMQ 4.0.5, and
additional steps to upgrade ZeroMQ and pyzmq are unnecessary.
.. warning:: RHEL/CentOS 5 Users
Using COPR repos on RHEL/CentOS 5 requires that the ``python-hashlib``
package be installed. Not having it present will result in checksum errors
because YUM will not be able to process the SHA256 checksums used by COPR.
.. note::
For RHEL/CentOS 5 installations, if using the SaltStack repo or Fedora COPR
to install Salt (as described :ref:`above <installation-rhel-repo>`),
then it is not necessary to enable the `zeromq4 COPR`_, because those
repositories already include ZeroMQ 4.
Package Management
==================

View File

@ -37,8 +37,8 @@ import logging
log = logging.getLogger(__name__)
def auth(username, sharedsecret, **kwargs):
def auth(username, password):
'''
Shared secret authentication
'''
return sharedsecret == __opts__.get('sharedsecret')
return password == __opts__.get('sharedsecret')

View File

@ -2336,6 +2336,9 @@ def wait_for_instance(
use_winrm = config.get_cloud_config_value(
'use_winrm', vm_, __opts__, default=False
)
winrm_verify_ssl = config.get_cloud_config_value(
'winrm_verify_ssl', vm_, __opts__, default=True
)
if win_passwd and win_passwd == 'auto':
log.debug('Waiting for auto-generated Windows EC2 password')
@ -2407,7 +2410,8 @@ def wait_for_instance(
winrm_port,
username,
win_passwd,
timeout=ssh_connect_timeout):
timeout=ssh_connect_timeout,
verify=winrm_verify_ssl):
raise SaltCloudSystemExit(
'Failed to authenticate against remote windows host'
)

View File

@ -14,6 +14,7 @@ import logging
import inspect
import tempfile
import functools
import threading
import types
from collections import MutableMapping
from zipimport import zipimporter
@ -1100,7 +1101,8 @@ class LazyLoader(salt.utils.lazy.LazyDict):
self.disabled = set(self.opts.get('disable_{0}{1}'.format(
self.tag, '' if self.tag[-1] == 's' else 's'), []))
self.refresh_file_mapping()
self._lock = threading.RLock()
self._refresh_file_mapping()
super(LazyLoader, self).__init__() # late init the lazy loader
# create all of the import namespaces
@ -1167,7 +1169,7 @@ class LazyLoader(salt.utils.lazy.LazyDict):
else:
return '\'{0}\' __virtual__ returned False'.format(mod_name)
def refresh_file_mapping(self):
def _refresh_file_mapping(self):
'''
refresh the mapping of the FS on disk
'''
@ -1285,6 +1287,7 @@ class LazyLoader(salt.utils.lazy.LazyDict):
'''
Clear the dict
'''
with self._lock:
super(LazyLoader, self).clear() # clear the lazy loader
self.loaded_files = set()
self.missing_modules = {}
@ -1292,7 +1295,7 @@ class LazyLoader(salt.utils.lazy.LazyDict):
# if we have been loaded before, lets clear the file mapping since
# we obviously want a re-do
if hasattr(self, 'opts'):
self.refresh_file_mapping()
self._refresh_file_mapping()
self.initial_load = False
def __prep_mod_opts(self, opts):
@ -1504,14 +1507,14 @@ class LazyLoader(salt.utils.lazy.LazyDict):
virtual_funcs_to_process = ['__virtual__'] + self.virtual_funcs
for virtual_func in virtual_funcs_to_process:
virtual_ret, module_name, virtual_err, virtual_aliases = \
self.process_virtual(mod, module_name)
self._process_virtual(mod, module_name)
if virtual_err is not None:
log.trace(
'Error loading %s.%s: %s',
self.tag, module_name, virtual_err
)
# if process_virtual returned a non-True value then we are
# if _process_virtual returned a non-True value then we are
# supposed to not process this module
if virtual_ret is not True and module_name not in self.missing_modules:
# If a module has information about why it could not be loaded, record it
@ -1601,7 +1604,10 @@ class LazyLoader(salt.utils.lazy.LazyDict):
if not isinstance(key, six.string_types) or '.' not in key:
raise KeyError
mod_name, _ = key.split('.', 1)
if mod_name in self.missing_modules:
with self._lock:
# It is possible that the key is in the dictionary after
# acquiring the lock due to another thread loading it.
if mod_name in self.missing_modules or key in self._dict:
return True
# if the modulename isn't in the whitelist, don't bother
if self.whitelist and mod_name not in self.whitelist:
@ -1625,13 +1631,13 @@ class LazyLoader(salt.utils.lazy.LazyDict):
try:
ret = _inner_load(mod_name)
if not reloaded and ret is not True:
self.refresh_file_mapping()
self._refresh_file_mapping()
reloaded = True
continue
break
except IOError:
if not reloaded:
self.refresh_file_mapping()
self._refresh_file_mapping()
reloaded = True
continue
@ -1641,6 +1647,7 @@ class LazyLoader(salt.utils.lazy.LazyDict):
'''
Load all of them
'''
with self._lock:
for name in self.file_mapping:
if name in self.loaded_files or name in self.missing_modules:
continue
@ -1649,6 +1656,7 @@ class LazyLoader(salt.utils.lazy.LazyDict):
self.loaded = True
def reload_modules(self):
with self._lock:
self.loaded_files = set()
self._load_all()
@ -1661,7 +1669,7 @@ class LazyLoader(salt.utils.lazy.LazyDict):
if func.__name__ in outp:
func.__outputter__ = outp[func.__name__]
def process_virtual(self, mod, module_name, virtual_func='__virtual__'):
def _process_virtual(self, mod, module_name, virtual_func='__virtual__'):
'''
Given a loaded module and its default name determine its virtual name

View File

@ -3038,6 +3038,7 @@ def rm_(name, force=False, volumes=False, **kwargs):
'''
kwargs = salt.utils.clean_kwargs(**kwargs)
stop_ = kwargs.pop('stop', False)
auto_remove = False
if kwargs:
salt.utils.invalid_kwargs(kwargs)
@ -3047,8 +3048,18 @@ def rm_(name, force=False, volumes=False, **kwargs):
'remove this container'.format(name)
)
if stop_ and not force:
inspect_results = inspect_container(name)
try:
auto_remove = inspect_results['HostConfig']['AutoRemove']
except KeyError:
log.error(
'Failed to find AutoRemove in inspect results, Docker API may '
'have changed. Full results: %s', inspect_results
)
stop(name)
pre = ps_(all=True)
if not auto_remove:
_client_wrapper('remove_container', name, v=volumes, force=force)
_clear_context()
return [x for x in pre if x not in ps_(all=True)]

View File

@ -25,7 +25,6 @@ import logging
try:
from sense_hat import SenseHat
_sensehat = SenseHat()
has_sense_hat = True
except (ImportError, NameError):
_sensehat = None
@ -39,14 +38,19 @@ def __virtual__():
Only load the module if SenseHat is available
'''
if has_sense_hat:
try:
_sensehat = SenseHat()
except OSError:
return False, 'This module can only be used on a Raspberry Pi with a SenseHat.'
rotation = __salt__['pillar.get']('sensehat:rotation', 0)
if rotation in [0, 90, 180, 270]:
_sensehat.set_rotation(rotation, False)
else:
log.error("{0} is not a valid rotation. Using default rotation.".format(rotation))
log.error('{0} is not a valid rotation. Using default rotation.'.format(rotation))
return True
else:
return False, "The SenseHat excecution module can not be loaded: SenseHat unavailable.\nThis module can only be used on a Raspberry Pi with a SenseHat. Also make sure that the sense_hat python library is installed!"
return False, 'The SenseHat execution module cannot be loaded: \'sense_hat\' python library unavailable.'
def set_pixels(pixels):

View File

@ -2030,6 +2030,7 @@ def event(tagmatch='*',
indent=None if not pretty else 4)))
sys.stdout.flush()
if count > 0:
count -= 1
log.debug('Remaining event matches: %s', count)

View File

@ -3439,7 +3439,7 @@ def _processValueItem(element, reg_key, reg_valuename, policy, parent_element,
element_valuenames = []
element_values = this_element_value
if this_element_value is not None:
element_valuenames = list(range(1, len(this_element_value) + 1))
element_valuenames = list([str(z) for z in range(1, len(this_element_value) + 1)])
if 'additive' in element.attrib:
if element.attrib['additive'].lower() == 'false':
# a delete values will be added before all the other
@ -3464,11 +3464,18 @@ def _processValueItem(element, reg_key, reg_valuename, policy, parent_element,
if this_element_value is not None:
element_valuenames = this_element_value.keys()
element_values = this_element_value.values()
if 'valuePrefix' in element.attrib and element.attrib['valuePrefix'] != '':
if 'valuePrefix' in element.attrib:
# if the valuePrefix attribute exists, the valuenames are <prefix><number>
# most prefixes attributes are empty in the admx files, so the valuenames
# end up being just numbers
if element.attrib['valuePrefix'] != '':
if this_element_value is not None:
element_valuenames = ['{0}{1}'.format(element.attrib['valuePrefix'],
k) for k in element_valuenames]
else:
# if there is no valuePrefix attribute, the valuename is the value
if element_values is not None:
element_valuenames = [str(z) for z in element_values]
if not check_deleted:
if this_element_value is not None:
log.debug('_processValueItem has an explicit element_value of {0}'.format(this_element_value))

View File

@ -2582,7 +2582,8 @@ def mod_repo(repo, basedir=None, **kwargs):
# Build a list of keys to be deleted
todelete = []
for key in repo_opts:
# list() of keys because the dict could be shrinking in the for loop.
for key in list(repo_opts):
if repo_opts[key] != 0 and not repo_opts[key]:
del repo_opts[key]
todelete.append(key)

View File

@ -43,7 +43,12 @@ def _handle_interrupt(exc, original_exc, hardfail=False, trace=''):
def _handle_signals(client, signum, sigframe):
try:
# This raises AttributeError on Python 3.4 and 3.5 if there is no current exception.
# Ref: https://bugs.python.org/issue23003
trace = traceback.format_exc()
except AttributeError:
trace = ''
try:
hardcrash = client.options.hard_crash
except (AttributeError, KeyError):

View File

@ -2434,6 +2434,7 @@ class State(object):
'__run_num__': self.__run_num,
'__sls__': low['__sls__']
}
self.pre[tag] = running[tag]
self.__run_num += 1
elif status == 'change' and not low.get('__prereq__'):
ret = self.call(low, chunks, running)
@ -2817,6 +2818,7 @@ class BaseHighState(object):
'top_file_merging_strategy set to \'same\', but no '
'default_top configuration option was set'
)
self.opts['environment'] = self.opts['default_top']
if self.opts['environment']:
contents = self.client.cache_file(

View File

@ -20,6 +20,15 @@ def __virtual__():
return True
def _norm_key(key):
'''
Normalize windows environment keys
'''
if utils.is_windows():
return key.upper()
return key
def setenv(name,
value,
false_unsets=False,
@ -124,12 +133,11 @@ def setenv(name,
permanent_hive = 'HKLM'
permanent_key = r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
out = __salt__['reg.read_value'](permanent_hive, permanent_key, key)
out = __salt__['reg.read_value'](permanent_hive, permanent_key, _norm_key(key))
return out['success'] is True
else:
return False
if current_environ.get(key, None) is None and not key_exists():
if current_environ.get(_norm_key(key), None) is None and not key_exists():
# The key does not exist in environment
if false_unsets is not True:
# This key will be added with value ''
@ -138,13 +146,13 @@ def setenv(name,
# The key exists.
if false_unsets is not True:
# Check to see if the value will change
if current_environ.get(key, None) != '':
if current_environ.get(_norm_key(key), None) != '':
# This key value will change to ''
ret['changes'].update({key: ''})
else:
# We're going to delete the key
ret['changes'].update({key: None})
elif current_environ.get(key, '') == val:
elif current_environ.get(_norm_key(key), '') == val:
already_set.append(key)
else:
ret['changes'].update({key: val})

View File

@ -607,28 +607,34 @@ def _check_file(name):
return ret, msg
def _clean_dir(root, keep, exclude_pat):
def _find_keep_files(root, keep):
'''
Clean out all of the files and directories in a directory (root) while
preserving the files in a list (keep) and part of exclude_pat
Compile a list of valid keep files (and directories).
'''
removed = set()
real_keep = set()
real_keep.add(root)
if isinstance(keep, list):
for fn_ in keep:
if not os.path.isabs(fn_):
continue
fn_ = os.path.normcase(os.path.abspath(fn_))
real_keep.add(fn_)
while True:
fn_ = os.path.dirname(fn_)
fn_ = os.path.abspath(os.path.dirname(fn_))
real_keep.add(fn_)
if fn_ in [
os.sep,
''.join([os.path.splitdrive(fn_)[0], os.sep]),
''.join([os.path.splitdrive(fn_)[0], os.sep, os.sep])
]:
drive, path = os.path.splitdrive(fn_)
if not path.lstrip(os.sep):
break
return real_keep
def _clean_dir(root, keep, exclude_pat):
'''
Clean out all of the files and directories in a directory (root) while
preserving the files in a list (keep) and part of exclude_pat
'''
real_keep = _find_keep_files(root, keep)
removed = set()
def _delete_not_kept(nfn):
if nfn not in real_keep:

View File

@ -265,8 +265,13 @@ def managed(name,
ret['comment'] = ' '.join(errors)
return ret
try:
currently_enabled = __salt__['ip.is_enabled'](name)
except CommandExecutionError:
currently_enabled = False
if not enabled:
if __salt__['ip.is_enabled'](name):
if currently_enabled:
if __opts__['test']:
ret['result'] = None
ret['comment'] = ('Interface \'{0}\' will be disabled'
@ -280,18 +285,13 @@ def managed(name,
ret['comment'] += ' (already disabled)'
return ret
else:
try:
currently_enabled = __salt__['ip.is_disabled'](name)
except CommandExecutionError:
currently_enabled = False
if not currently_enabled:
if __opts__['test']:
ret['result'] = None
ret['comment'] = ('Interface \'{0}\' will be enabled'
.format(name))
else:
result = __salt__['ip.enable'](name)
if not result:
if not __salt__['ip.enable'](name):
ret['result'] = False
ret['comment'] = ('Failed to enable interface \'{0}\' to '
'make changes'.format(name))

View File

@ -517,6 +517,9 @@ def bootstrap(vm_, opts=None):
deploy_kwargs['winrm_use_ssl'] = salt.config.get_cloud_config_value(
'winrm_use_ssl', vm_, opts, default=True
)
deploy_kwargs['winrm_verify_ssl'] = salt.config.get_cloud_config_value(
'winrm_verify_ssl', vm_, opts, default=True
)
if saltify_driver:
deploy_kwargs['port_timeout'] = 1 # No need to wait/retry with Saltify
@ -843,7 +846,7 @@ def wait_for_winexesvc(host, port, username, password, timeout=900):
time.sleep(1)
def wait_for_winrm(host, port, username, password, timeout=900, use_ssl=True):
def wait_for_winrm(host, port, username, password, timeout=900, use_ssl=True, verify=True):
'''
Wait until WinRM connection can be established.
'''
@ -853,14 +856,20 @@ def wait_for_winrm(host, port, username, password, timeout=900, use_ssl=True):
host, port
)
)
transport = 'ssl'
if not use_ssl:
transport = 'plaintext'
trycount = 0
while True:
trycount += 1
try:
transport = 'ssl'
if not use_ssl:
transport = 'plaintext'
s = winrm.Session(host, auth=(username, password), transport=transport)
winrm_kwargs = {'target': host,
'auth': (username, password),
'transport': transport}
if not verify:
log.debug("SSL validation for WinRM disabled.")
winrm_kwargs['server_cert_validation'] = 'ignore'
s = winrm.Session(**winrm_kwargs)
if hasattr(s.protocol, 'set_timeout'):
s.protocol.set_timeout(15)
log.trace('WinRM endpoint url: {0}'.format(s.url))
@ -1008,6 +1017,7 @@ def deploy_windows(host,
use_winrm=False,
winrm_port=5986,
winrm_use_ssl=True,
winrm_verify_ssl=True,
**kwargs):
'''
Copy the install files to a remote Windows box, and execute them
@ -1034,7 +1044,8 @@ def deploy_windows(host,
if HAS_WINRM and use_winrm:
winrm_session = wait_for_winrm(host=host, port=winrm_port,
username=username, password=password,
timeout=port_timeout * 60, use_ssl=winrm_use_ssl)
timeout=port_timeout * 60, use_ssl=winrm_use_ssl,
verify=winrm_verify_ssl)
if winrm_session is not None:
service_available = True
else:

View File

@ -35,6 +35,7 @@ import salt.utils.args
import salt.utils.xdg
import salt.utils.jid
import salt.utils.files
import salt.utils.win_functions
from salt.utils import kinds
from salt.defaults import DEFAULT_TARGET_DELIM
from salt.utils.validate.path import is_writeable
@ -1017,11 +1018,11 @@ class DaemonMixIn(six.with_metaclass(MixInMeta, object)):
if self.check_pidfile():
pid = self.get_pidfile()
if not salt.utils.is_windows():
if self.check_pidfile() and self.is_daemonized(pid) and not os.getppid() == pid:
if self.check_pidfile() and self.is_daemonized(pid) and os.getppid() != pid:
return True
else:
# We have no os.getppid() on Windows. Best effort.
if self.check_pidfile() and self.is_daemonized(pid):
# We have no os.getppid() on Windows. Use salt.utils.win_functions.get_parent_pid
if self.check_pidfile() and self.is_daemonized(pid) and salt.utils.win_functions.get_parent_pid() != pid:
return True
return False

View File

@ -147,7 +147,7 @@ def get_pidfile(pidfile):
pid = pdf.read().strip()
return int(pid)
except (OSError, IOError, TypeError, ValueError):
return None
return -1
def clean_proc(proc, wait_for_kill=10):

View File

@ -8,14 +8,18 @@ from __future__ import absolute_import
import os
import random
import string
import yaml
# Import Salt Libs
from salt.config import cloud_providers_config
import salt.utils
# Import Salt Testing Libs
from tests.support.case import ShellCase
from tests.support.paths import FILES
from tests.support.helpers import expensiveTest
from tests.support.unit import expectedFailure
from tests.support import win_installer
# Import Third-Party Libs
from salt.ext.six.moves import range # pylint: disable=import-error,redefined-builtin
@ -39,6 +43,38 @@ class EC2Test(ShellCase):
'''
Integration tests for the EC2 cloud provider in Salt-Cloud
'''
TIMEOUT = 500
def _installer_name(self):
'''
Determine the downloaded installer name by searching the files
directory for the firt file that loosk like an installer.
'''
for path, dirs, files in os.walk(FILES):
for file in files:
if file.startswith(win_installer.PREFIX):
return file
break
return
def _fetch_latest_installer(self):
'''
Download the latest Windows installer executable
'''
name = win_installer.latest_installer_name()
path = os.path.join(FILES, name)
with salt.utils.fopen(path, 'wb') as fp:
win_installer.download_and_verify(fp, name)
return name
def _ensure_installer(self):
'''
Make sure the testing environment has a Windows installer executbale.
'''
name = self._installer_name()
if name:
return name
return self._fetch_latest_installer()
@expensiveTest
def setUp(self):
@ -90,24 +126,51 @@ class EC2Test(ShellCase):
'missing. Check tests/integration/files/conf/cloud.providers.d/{0}.conf'
.format(PROVIDER_NAME)
)
self.INSTALLER = self._ensure_installer()
def test_instance(self):
def override_profile_config(self, name, data):
conf_path = os.path.join(self.get_config_dir(), 'cloud.profiles.d', 'ec2.conf')
with salt.utils.fopen(conf_path, 'r') as fp:
conf = yaml.safe_load(fp)
conf[name].update(data)
with salt.utils.fopen(conf_path, 'w') as fp:
yaml.dump(conf, fp)
def copy_file(self, name):
'''
Copy a file from tests/integration/files to a test's temporary
configuration directory. The path to the file which is created will be
returned.
'''
src = os.path.join(FILES, name)
dst = os.path.join(self.get_config_dir(), name)
with salt.utils.fopen(src, 'rb') as sfp:
with salt.utils.fopen(dst, 'wb') as dfp:
dfp.write(sfp.read())
return dst
def _test_instance(self, profile='ec2-test', debug=False, timeout=TIMEOUT):
'''
Tests creating and deleting an instance on EC2 (classic)
'''
# create the instance
instance = self.run_cloud('-p ec2-test {0}'.format(INSTANCE_NAME), timeout=500)
cmd = '-p {0}'.format(profile)
if debug:
cmd += ' -l debug'
cmd += ' {0}'.format(INSTANCE_NAME)
instance = self.run_cloud(cmd, timeout=timeout)
ret_str = '{0}:'.format(INSTANCE_NAME)
# check if instance returned with salt installed
try:
self.assertIn(ret_str, instance)
except AssertionError:
self.run_cloud('-d {0} --assume-yes'.format(INSTANCE_NAME), timeout=500)
self.run_cloud('-d {0} --assume-yes'.format(INSTANCE_NAME), timeout=timeout)
raise
# delete the instance
delete = self.run_cloud('-d {0} --assume-yes'.format(INSTANCE_NAME), timeout=500)
delete = self.run_cloud('-d {0} --assume-yes'.format(INSTANCE_NAME), timeout=timeout)
ret_str = ' shutting-down'
# check if deletion was performed appropriately
@ -151,6 +214,80 @@ class EC2Test(ShellCase):
# check if deletion was performed appropriately
self.assertIn(ret_str, delete)
def test_instance(self):
'''
Tests creating and deleting an instance on EC2 (classic)
'''
self._test_instance('ec2-test')
@expectedFailure
def test_win2012r2_winexe(self):
'''
Tests creating and deleting a Windows 2012r2instance on EC2 using
winexe (classic)
'''
# TODO: winexe calls hang and the test fails by timing out. The same
# same calls succeed when run outside of the test environment.
self.override_profile_config(
'ec2-win2012-test',
{
'use_winrm': False,
'user_data': self.copy_file('windows-firewall-winexe.ps1'),
'win_installer': self.copy_file(self.INSTALLER),
},
)
self._test_instance('ec2-win2012r2-test', debug=True, timeout=500)
def test_win2012r2_winrm(self):
'''
Tests creating and deleting a Windows 2012r2 instance on EC2 using
winrm (classic)
'''
self.override_profile_config(
'ec2-win2016-test',
{
'user_data': self.copy_file('windows-firewall.ps1'),
'win_installer': self.copy_file(self.INSTALLER),
'winrm_ssl_verify': False,
}
)
self._test_instance('ec2-win2012r2-test', debug=True, timeout=500)
@expectedFailure
def test_win2016_winexe(self):
'''
Tests creating and deleting a Windows 2016 instance on EC2 using winrm
(classic)
'''
# TODO: winexe calls hang and the test fails by timing out. The same
# same calls succeed when run outside of the test environment.
self.override_profile_config(
'ec2-win2016-test',
{
'use_winrm': False,
'user_data': self.copy_file('windows-firewall-winexe.ps1'),
'win_installer': self.copy_file(self.INSTALLER),
},
)
self._test_instance('ec2-win2016-test', debug=True, timeout=500)
def test_win2016_winrm(self):
'''
Tests creating and deleting a Windows 2016 instance on EC2 using winrm
(classic)
'''
self.override_profile_config(
'ec2-win2016-test',
{
'user_data': self.copy_file('windows-firewall.ps1'),
'win_installer': self.copy_file(self.INSTALLER),
'winrm_ssl_verify': False,
}
)
self._test_instance('ec2-win2016-test', debug=True, timeout=500)
def tearDown(self):
'''
Clean up after tests
@ -160,4 +297,4 @@ class EC2Test(ShellCase):
# if test instance is still present, delete it
if ret_str in query:
self.run_cloud('-d {0} --assume-yes'.format(INSTANCE_NAME), timeout=500)
self.run_cloud('-d {0} --assume-yes'.format(INSTANCE_NAME), timeout=self.TIMEOUT)

View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
'''
Tests for existence of manpages
'''
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import os
import shutil
# Import Salt libs
import salt.utils
# Import Salt Testing libs
from tests.support.case import ModuleCase
from tests.support.paths import TMP
from tests.support.unit import skipIf
@skipIf(salt.utils.is_windows(), 'minion is windows')
class ManTest(ModuleCase):
rootdir = os.path.join(TMP, 'mantest')
# Map filenames to search strings which should be in the manpage
manpages = {
'salt-cp.1': [
'salt-cp Documentation',
'copies files from the master',
],
'salt-cloud.1': [
'Salt Cloud Command',
'Provision virtual machines in the cloud',
],
'salt-call.1': [
'salt-call Documentation',
'run module functions locally',
],
'salt-api.1': [
'salt-api Command',
'Start interfaces used to remotely connect',
],
'salt-unity.1': [
'salt-unity Command',
'unified invocation wrapper',
],
'salt-syndic.1': [
'salt-syndic Documentation',
'Salt syndic daemon',
],
'salt-ssh.1': [
'salt-ssh Documentation',
'executed using only SSH',
],
'salt-run.1': [
'salt-run Documentation',
'frontend command for executing',
],
'salt-proxy.1': [
'salt-proxy Documentation',
'proxies these commands',
],
'salt-minion.1': [
'salt-minion Documentation',
'Salt minion daemon',
],
'salt-master.1': [
'salt-master Documentation',
'Salt master daemon',
],
'salt-key.1': [
'salt-key Documentation',
'management of Salt server public keys',
],
'salt.1': [
'allows for commands to be executed',
],
'salt.7': [
'Salt Documentation',
],
'spm.1': [
'Salt Package Manager Command',
'command for managing Salt packages',
],
}
def setUp(self):
if not self.run_function('mantest.install', [self.rootdir]):
self.fail('Failed to install salt to {0}'.format(self.rootdir))
@classmethod
def tearDownClass(cls):
try:
shutil.rmtree(cls.rootdir)
except OSError:
pass
def test_man(self):
'''
Make sure that man pages are installed
'''
ret = self.run_function('mantest.search', [self.manpages, self.rootdir])
# The above function returns True if successful and an exception (which
# will manifest in the return as a stringified exception) if
# unsuccessful. Therefore, a simple assertTrue is not sufficient.
if ret is not True:
self.fail(ret)

View File

@ -4,3 +4,31 @@ ec2-test:
size: t1.micro
sh_username: ec2-user
script_args: '-P -Z'
ec2-win2012r2-test:
provider: ec2-config
size: t2.micro
image: ami-eb1ecd96
smb_port: 445
win_installer: ''
win_username: Administrator
win_password: auto
userdata_file: ''
userdata_template: False
use_winrm: True
winrm_verify_ssl: False
ssh_interface: private_ips
deploy: True
ec2-win2016-test:
provider: ec2-config
size: t2.micro
image: ami-ed14c790
smb_port: 445
win_installer: ''
win_username: Administrator
win_password: auto
userdata_file: ''
userdata_template: False
use_winrm: True
winrm_verify_ssl: False
ssh_interface: private_ips
deploy: True

View File

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
'''
Helpers for testing man pages
'''
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import os
import sys
# Import Salt libs
import salt.utils
from salt.exceptions import CommandExecutionError
# Import Salt Tesing libs
from tests.support.paths import CODE_DIR
def install(rootdir):
if not os.path.exists(rootdir):
os.makedirs(rootdir)
return __salt__['cmd.retcode'](
[sys.executable,
os.path.join(CODE_DIR, 'setup.py'),
'install', '--root={0}'.format(rootdir)]) == 0
return True
def search(manpages, rootdir):
manpage_fns = set(manpages)
manpage_paths = {}
for root, _, files in os.walk(rootdir):
if not manpage_fns:
# All manpages found, no need to keep walking
break
# Using list because we will be modifying the set during iteration
for manpage_fn in list(manpage_fns):
if manpage_fn in files:
manpage_path = salt.utils.path_join(root, manpage_fn)
manpage_paths[manpage_fn] = manpage_path
manpage_fns.remove(manpage_fn)
if manpage_fns:
raise CommandExecutionError(
'The following manpages were not found under {0}: {1}'.format(
rootdir,
', '.join(sorted(manpage_fns))
)
)
failed = {}
for manpage in sorted(manpages):
with salt.utils.fopen(manpage_paths[manpage]) as fp_:
contents = salt.utils.to_unicode(fp_.read())
# Check for search string in contents
for search_string in manpages[manpage]:
if search_string not in contents:
failed.setdefault(manpage, []).append(
'No match for search string \'{0}\' found in {1}'.format(
search_string, manpage_paths[manpage]
)
)
# Check for correct install dir
path = '/man{0}/'.format(manpage.rsplit('.', 1)[-1])
if path not in manpage_paths[manpage]:
failed.setdefault(manpage, []).append(
'{0} not found in manpage path {1}'.format(
path, manpage_paths[manpage]
)
)
if failed:
raise CommandExecutionError('One or more manpages failed', info=failed)
return True

View File

@ -63,6 +63,8 @@ def get_invalid_docs():
'log.warning',
'lowpkg.bin_pkg_info',
'lxc.run_cmd',
'mantest.install',
'mantest.search',
'nspawn.restart',
'nspawn.stop',
'pkg.expand_repo_def',

View File

@ -0,0 +1,5 @@
<powershell>
New-NetFirewallRule -Name "SMB445" -DisplayName "SMB445" -Protocol TCP -LocalPort 445
Set-Item (dir wsman:\localhost\Listener\*\Port -Recurse).pspath 445 -Force
Restart-Service winrm
</powershell>

View File

@ -0,0 +1,33 @@
<powershell>
New-NetFirewallRule -Name "SMB445" -DisplayName "SMB445" -Protocol TCP -LocalPort 445
New-NetFirewallRule -Name "WINRM5986" -DisplayName "WINRM5986" -Protocol TCP -LocalPort 5986
winrm quickconfig -q
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}'
winrm set winrm/config '@{MaxTimeoutms="1800000"}'
winrm set winrm/config/service/auth '@{Basic="true"}'
$SourceStoreScope = 'LocalMachine'
$SourceStorename = 'Remote Desktop'
$SourceStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $SourceStorename, $SourceStoreScope
$SourceStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)
$cert = $SourceStore.Certificates | Where-Object -FilterScript {
$_.subject -like '*'
}
$DestStoreScope = 'LocalMachine'
$DestStoreName = 'My'
$DestStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $DestStoreName, $DestStoreScope
$DestStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$DestStore.Add($cert)
$SourceStore.Close()
$DestStore.Close()
winrm create winrm/config/listener?Address=*+Transport=HTTPS `@`{Hostname=`"($certId)`"`;CertificateThumbprint=`"($cert.Thumbprint)`"`}
Restart-Service winrm
</powershell>

View File

@ -4,7 +4,6 @@
from __future__ import absolute_import
import os
import uuid
import shutil
import hashlib
import logging
import psutil
@ -15,7 +14,10 @@ import textwrap
# Import Salt Testing libs
from tests.support.case import ModuleCase
from tests.support.helpers import get_unused_localhost_port, skip_if_not_root
from tests.support.helpers import (
get_unused_localhost_port,
skip_if_not_root,
with_tempfile)
from tests.support.unit import skipIf
import tests.support.paths as paths
@ -30,11 +32,11 @@ class CPModuleTest(ModuleCase):
'''
Validate the cp module
'''
def test_get_file(self):
@with_tempfile
def test_get_file(self, tgt):
'''
cp.get_file
'''
tgt = os.path.join(paths.TMP, 'scene33')
self.run_function(
'cp.get_file',
[
@ -46,11 +48,11 @@ class CPModuleTest(ModuleCase):
self.assertIn('KNIGHT: They\'re nervous, sire.', data)
self.assertNotIn('bacon', data)
def test_get_file_templated_paths(self):
@with_tempfile
def test_get_file_templated_paths(self, tgt):
'''
cp.get_file
'''
tgt = os.path.join(paths.TMP, 'cheese')
self.run_function(
'cp.get_file',
[
@ -64,11 +66,11 @@ class CPModuleTest(ModuleCase):
self.assertIn('Gromit', data)
self.assertNotIn('bacon', data)
def test_get_file_gzipped(self):
@with_tempfile
def test_get_file_gzipped(self, tgt):
'''
cp.get_file
'''
tgt = os.path.join(paths.TMP, 'file.big')
src = os.path.join(paths.FILES, 'file', 'base', 'file.big')
with salt.utils.fopen(src, 'r') as fp_:
data = fp_.read()
@ -111,11 +113,11 @@ class CPModuleTest(ModuleCase):
self.assertIn('KNIGHT: They\'re nervous, sire.', data)
self.assertNotIn('bacon', data)
def test_get_template(self):
@with_tempfile
def test_get_template(self, tgt):
'''
cp.get_template
'''
tgt = os.path.join(paths.TMP, 'scene33')
self.run_function(
'cp.get_template',
['salt://grail/scene33', tgt],
@ -160,11 +162,11 @@ class CPModuleTest(ModuleCase):
# cp.get_url tests
def test_get_url(self):
@with_tempfile
def test_get_url(self, tgt):
'''
cp.get_url with salt:// source given
'''
tgt = os.path.join(paths.TMP, 'scene33')
self.run_function(
'cp.get_url',
[
@ -235,11 +237,11 @@ class CPModuleTest(ModuleCase):
])
self.assertEqual(ret, False)
def test_get_url_https(self):
@with_tempfile
def test_get_url_https(self, tgt):
'''
cp.get_url with https:// source given
'''
tgt = os.path.join(paths.TMP, 'test_get_url_https')
self.run_function(
'cp.get_url',
[
@ -576,30 +578,24 @@ class CPModuleTest(ModuleCase):
self.assertEqual(
sha256_hash['hsum'], hashlib.sha256(data).hexdigest())
def test_get_file_from_env_predefined(self):
@with_tempfile
def test_get_file_from_env_predefined(self, tgt):
'''
cp.get_file
'''
tgt = os.path.join(paths.TMP, 'cheese')
try:
self.run_function('cp.get_file', ['salt://cheese', tgt])
with salt.utils.fopen(tgt, 'r') as cheese:
data = cheese.read()
self.assertIn('Gromit', data)
self.assertNotIn('Comte', data)
finally:
os.unlink(tgt)
def test_get_file_from_env_in_url(self):
tgt = os.path.join(paths.TMP, 'cheese')
try:
@with_tempfile
def test_get_file_from_env_in_url(self, tgt):
self.run_function('cp.get_file', ['salt://cheese?saltenv=prod', tgt])
with salt.utils.fopen(tgt, 'r') as cheese:
data = cheese.read()
self.assertIn('Gromit', data)
self.assertIn('Comte', data)
finally:
os.unlink(tgt)
def test_push(self):
log_to_xfer = os.path.join(paths.TMP, uuid.uuid4().hex)

View File

@ -8,9 +8,11 @@ import random
# Import Salt Testing libs
from tests.support.case import ModuleCase
from tests.support.helpers import destructiveTest, skip_if_not_root
from tests.support.unit import skipIf
# Import 3rd-party libs
from salt.ext.six.moves import range
import salt.utils
@skip_if_not_root
@ -30,13 +32,13 @@ class GroupModuleTest(ModuleCase):
self._no_user = self.__random_string()
self._group = self.__random_string()
self._no_group = self.__random_string()
self._gid = 64989
self._new_gid = 64998
os_grain = self.run_function('grains.item', ['kernel'])
if os_grain['kernel'] not in 'Linux':
self.os_grain = self.run_function('grains.item', ['kernel'])
self._gid = 64989 if 'Windows' not in self.os_grain['kernel'] else None
self._new_gid = 64998 if 'Windows' not in self.os_grain['kernel'] else None
if self.os_grain['kernel'] not in ('Linux', 'Windows'):
self.skipTest(
'Test not applicable to \'{kernel}\' kernel'.format(
**os_grain
**self.os_grain
)
)
@ -62,12 +64,18 @@ class GroupModuleTest(ModuleCase):
Test the add group function
'''
# add a new group
self.assertTrue(self.run_function('group.add', [self._group, self._gid]))
self.assertTrue(self.run_function('group.add', [self._group], gid=self._gid))
group_info = self.run_function('group.info', [self._group])
self.assertEqual(group_info['name'], self._group)
self.assertEqual(group_info['gid'], self._gid)
self.assertEqual(group_info['name'], self._group)
# try adding the group again
self.assertFalse(self.run_function('group.add', [self._group, self._gid]))
if self.os_grain['kernel'] == 'Windows':
add_group = self.run_function('group.add', [self._group], gid=self._gid)
self.assertEqual(add_group['result'], None)
self.assertEqual(add_group['comment'], 'The group {0} already exists.'.format(self._group))
self.assertEqual(add_group['changes'], [])
else:
self.assertFalse(self.run_function('group.add', [self._group], gid=self._gid))
def test_delete(self):
'''
@ -79,26 +87,32 @@ class GroupModuleTest(ModuleCase):
self.assertTrue(self.run_function('group.delete', [self._group]))
# group does not exist
if self.os_grain['kernel'] == 'Windows':
del_group = self.run_function('group.delete', [self._no_group])
self.assertEqual(del_group['changes'], [])
self.assertEqual(del_group['comment'], 'The group {0} does not exists.'.format(self._no_group))
else:
self.assertFalse(self.run_function('group.delete', [self._no_group]))
def test_info(self):
'''
Test the info group function
'''
self.run_function('group.add', [self._group, self._gid])
self.run_function('group.add', [self._group], gid=self._gid)
self.run_function('user.add', [self._user])
self.run_function('group.adduser', [self._group, self._user])
group_info = self.run_function('group.info', [self._group])
self.assertEqual(group_info['name'], self._group)
self.assertEqual(group_info['gid'], self._gid)
self.assertIn(self._user, group_info['members'])
self.assertIn(self._user, str(group_info['members']))
@skipIf(salt.utils.is_windows(), 'gid test skipped on windows')
def test_chgid(self):
'''
Test the change gid function
'''
self.run_function('group.add', [self._group, self._gid])
self.run_function('group.add', [self._group], gid=self._gid)
self.assertTrue(self.run_function('group.chgid', [self._group, self._new_gid]))
group_info = self.run_function('group.info', [self._group])
self.assertEqual(group_info['gid'], self._new_gid)
@ -107,11 +121,19 @@ class GroupModuleTest(ModuleCase):
'''
Test the add user to group function
'''
self.run_function('group.add', [self._group, self._gid])
self.run_function('group.add', [self._group], gid=self._gid)
self.run_function('user.add', [self._user])
self.assertTrue(self.run_function('group.adduser', [self._group, self._user]))
group_info = self.run_function('group.info', [self._group])
self.assertIn(self._user, group_info['members'])
self.assertIn(self._user, str(group_info['members']))
if self.os_grain['kernel'] == 'Windows':
no_group = self.run_function('group.adduser', [self._no_group, self._no_user])
no_user = self.run_function('group.adduser', [self._group, self._no_user])
funcs = [no_group, no_user]
for func in funcs:
self.assertIn('Fail', func['comment'])
self.assertFalse(func['result'])
else:
# try add a non existing user
self.assertFalse(self.run_function('group.adduser', [self._group, self._no_user]))
# try add a user to non existing group
@ -123,31 +145,31 @@ class GroupModuleTest(ModuleCase):
'''
Test the delete user from group function
'''
self.run_function('group.add', [self._group, self._gid])
self.run_function('group.add', [self._group], gid=self._gid)
self.run_function('user.add', [self._user])
self.run_function('group.adduser', [self._group, self._user])
self.assertTrue(self.run_function('group.deluser', [self._group, self._user]))
group_info = self.run_function('group.info', [self._group])
self.assertNotIn(self._user, group_info['members'])
self.assertNotIn(self._user, str(group_info['members']))
def test_members(self):
'''
Test the members function
'''
self.run_function('group.add', [self._group, self._gid])
self.run_function('group.add', [self._group], gid=self._gid)
self.run_function('user.add', [self._user])
self.run_function('user.add', [self._user1])
m = '{0},{1}'.format(self._user, self._user1)
self.assertTrue(self.run_function('group.members', [self._group, m]))
group_info = self.run_function('group.info', [self._group])
self.assertIn(self._user, group_info['members'])
self.assertIn(self._user1, group_info['members'])
self.assertIn(self._user, str(group_info['members']))
self.assertIn(self._user1, str(group_info['members']))
def test_getent(self):
'''
Test the getent function
'''
self.run_function('group.add', [self._group, self._gid])
self.run_function('group.add', [self._group], gid=self._gid)
self.run_function('user.add', [self._user])
self.run_function('group.adduser', [self._group, self._user])
ginfo = self.run_function('user.getent')

View File

@ -84,7 +84,8 @@ class SaltUtilSyncModuleTest(ModuleCase):
'beacons': [],
'utils': [],
'returners': [],
'modules': ['modules.override_test',
'modules': ['modules.mantest',
'modules.override_test',
'modules.runtests_decorators',
'modules.runtests_helpers',
'modules.salttest'],
@ -127,7 +128,8 @@ class SaltUtilSyncModuleTest(ModuleCase):
'beacons': [],
'utils': [],
'returners': [],
'modules': ['modules.override_test',
'modules': ['modules.mantest',
'modules.override_test',
'modules.runtests_helpers',
'modules.salttest'],
'renderers': [],

View File

@ -1,44 +0,0 @@
# -*- coding: utf-8 -*-
'''
Tests man spm
'''
# Import python libs
from __future__ import absolute_import
import os
import shutil
import sys
import tempfile
# Import Salt Testing libs
from tests.support.case import ModuleCase
from tests.support.helpers import destructiveTest
from tests.support.paths import CODE_DIR
@destructiveTest
class SPMManTest(ModuleCase):
'''
Validate man spm
'''
def setUp(self):
self.tmpdir = tempfile.mktemp()
os.mkdir(self.tmpdir)
self.run_function('cmd.run', ['{0} {1} install --root={2}'.format(
sys.executable,
os.path.join(CODE_DIR, 'setup.py'),
self.tmpdir
)])
def tearDown(self):
shutil.rmtree(self.tmpdir)
def test_man_spm(self):
'''
test man spm
'''
manpath = self.run_function('cmd.run', ['find {0} -name spm.1'.format(self.tmpdir)])
self.assertIn('/man1/', manpath)
cmd = self.run_function('cmd.run', ['man {0}'.format(manpath)])
self.assertIn('Salt Package Manager', cmd)
self.assertIn('command for managing Salt packages', cmd)

View File

@ -33,7 +33,7 @@ class NpmStateTest(ModuleCase, SaltReturnAssertsMixin):
Basic test to determine if NPM module was successfully installed and
removed.
'''
ret = self.run_state('npm.installed', name='pm2')
ret = self.run_state('npm.installed', name='pm2', registry="http://registry.npmjs.org/")
self.assertSaltTrueReturn(ret)
ret = self.run_state('npm.removed', name='pm2')
self.assertSaltTrueReturn(ret)
@ -51,7 +51,11 @@ class NpmStateTest(ModuleCase, SaltReturnAssertsMixin):
else:
user = None
npm_dir = None
ret = self.run_state('npm.installed', name='request/request#v2.81.1', runas=user, dir=npm_dir)
ret = self.run_state('npm.installed',
name='request/request#v2.81.1',
runas=user,
dir=npm_dir,
registry="http://registry.npmjs.org/")
self.assertSaltTrueReturn(ret)
ret = self.run_state('npm.removed', name='git://github.com/request/request', runas=user, dir=npm_dir)
self.assertSaltTrueReturn(ret)
@ -65,7 +69,7 @@ class NpmStateTest(ModuleCase, SaltReturnAssertsMixin):
Basic test to determine if NPM module successfully installs multiple
packages.
'''
ret = self.run_state('npm.installed', name=None, pkgs=['pm2', 'grunt'])
ret = self.run_state('npm.installed', name=None, pkgs=['pm2', 'grunt'], registry="http://registry.npmjs.org/")
self.assertSaltTrueReturn(ret)
@skipIf(salt.utils.which('npm') and LooseVersion(cmd.run('npm -v')) >= LooseVersion(MAX_NPM_VERSION),

View File

@ -112,6 +112,9 @@ TEST_SUITES = {
'client':
{'display_name': 'Client',
'path': 'integration/client'},
'doc':
{'display_name': 'Documentation',
'path': 'integration/doc'},
'ext_pillar':
{'display_name': 'External Pillar',
'path': 'integration/pillar'},
@ -277,6 +280,15 @@ class SaltTestsuiteParser(SaltCoverageTestingParser):
action='store_true',
help='Run tests for client'
)
self.test_selection_group.add_option(
'-d',
'--doc',
'--doc-tests',
dest='doc',
default=False,
action='store_true',
help='Run tests for documentation'
)
self.test_selection_group.add_option(
'-I',
'--ext-pillar',

View File

@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
'''
:copyright: Copyright 2013-2017 by the SaltStack Team, see AUTHORS for more details.
:license: Apache 2.0, see LICENSE for more details.
tests.support.win_installer
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Fetches the binary Windows installer
'''
from __future__ import absolute_import
import hashlib
import requests
import re
PREFIX = 'Salt-Minion-'
REPO = "https://repo.saltstack.com/windows"
def iter_installers(content):
'''
Parse a list of windows installer links and their corresponding md5
checksum links.
'''
HREF_RE = "<a href=\"(.*?)\">"
installer, md5 = None, None
for m in re.finditer(HREF_RE, content):
x = m.groups()[0]
if not x.startswith(PREFIX):
continue
if x.endswith('zip'):
continue
if installer:
if x != installer + '.md5':
raise Exception("Unable to parse response")
md5 = x
yield installer, md5
installer, md5 = None, None
else:
installer = x
def split_installer(name):
'''
Return a tuple of the salt version, python verison and architecture from an
installer name.
'''
x = name[len(PREFIX):]
return x.split('-')[:3]
def latest_version(repo=REPO):
'''
Return the latest version found on the salt repository webpage.
'''
for name, md5 in iter_installers(requests.get(repo).content):
pass
return split_installer(name)[0]
def installer_name(salt_ver, py_ver='Py2', arch='AMD64'):
'''
Create an installer file name
'''
return "Salt-Minion-{}-{}-{}-Setup.exe".format(salt_ver, py_ver, arch)
def latest_installer_name(repo=REPO, **kwargs):
'''
Fetch the latest installer name
'''
return installer_name(latest_version(repo), **kwargs)
def download_and_verify(fp, name, repo=REPO):
'''
Download an installer and verify it's contents.
'''
md5 = "{}.md5".format(name)
url = lambda x: "{}/{}".format(repo, x)
resp = requests.get(url(md5))
if resp.status_code != 200:
raise Exception("Unable to fetch installer md5")
installer_md5 = resp.text.strip().split()[0].lower()
resp = requests.get(url(name), stream=True)
if resp.status_code != 200:
raise Exception("Unable to fetch installer")
md5hsh = hashlib.md5()
for chunk in resp.iter_content(chunk_size=1024):
md5hsh.update(chunk)
fp.write(chunk)
if md5hsh.hexdigest() != installer_md5:
raise Exception("Installer's hash does not match {} != {}".format(
md5hsh.hexdigest(), installer_md5
))

View File

@ -19,6 +19,8 @@
# Import Python Libs
from __future__ import absolute_import
import os
import errno
import subprocess
# Import Salt Testing Libs
from tests.support.unit import TestCase, skipIf
@ -33,7 +35,33 @@ from tests.support.mock import (
from salt.modules.inspectlib.collector import Inspector
HAS_SYMLINKS = None
def no_symlinks():
'''
Check if git is installed and has symlinks enabled in the configuration.
'''
global HAS_SYMLINKS
if HAS_SYMLINKS is not None:
return not HAS_SYMLINKS
output = ''
try:
output = subprocess.check_output('git config --get core.symlinks', shell=True)
except OSError as exc:
if exc.errno != errno.ENOENT:
raise
except subprocess.CalledProcessError:
# git returned non-zero status
pass
HAS_SYMLINKS = False
if output.strip() == 'true':
HAS_SYMLINKS = True
return not HAS_SYMLINKS
@skipIf(NO_MOCK, NO_MOCK_REASON)
@skipIf(no_symlinks(), "Git missing 'core.symlinks=true' config")
class InspectorCollectorTestCase(TestCase):
'''
Test inspectlib:collector:Inspector

View File

@ -6,7 +6,7 @@ import os
# Import Salt Testing libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase
from tests.support.unit import TestCase, skipIf
from tests.support.mock import (
MagicMock,
patch
@ -14,6 +14,8 @@ from tests.support.mock import (
# Import salt libs
import salt.states.environ as envstate
import salt.modules.environ as envmodule
import salt.modules.reg
import salt.utils
class TestEnvironState(TestCase, LoaderModuleMockMixin):
@ -22,17 +24,21 @@ class TestEnvironState(TestCase, LoaderModuleMockMixin):
loader_globals = {
'__env__': 'base',
'__opts__': {'test': False},
'__salt__': {'environ.setenv': envmodule.setenv}
'__salt__': {
'environ.setenv': envmodule.setenv,
'reg.read_value': salt.modules.reg.read_value,
}
}
return {envstate: loader_globals, envmodule: loader_globals}
def setUp(self):
original_environ = os.environ.copy()
os.environ = {'INITIAL': 'initial'}
patcher = patch.dict(os.environ, {'INITIAL': 'initial'}, clear=True)
patcher.start()
def reset_environ(original_environ):
os.environ = original_environ
self.addCleanup(reset_environ, original_environ)
def reset_environ(patcher):
patcher.stop()
self.addCleanup(reset_environ, patcher)
def test_setenv(self):
'''test that a subsequent calls of setenv changes nothing'''
@ -47,9 +53,10 @@ class TestEnvironState(TestCase, LoaderModuleMockMixin):
ret = envstate.setenv('test', 'other')
self.assertEqual(ret['changes'], {})
@skipIf(not salt.utils.is_windows(), 'Windows only')
def test_setenv_permanent(self):
with patch.dict(envmodule.__salt__, {'reg.set_value': MagicMock(), 'reg.delete_value': MagicMock()}), \
patch('salt.utils.is_windows', MagicMock(return_value=True)):
'''test that we can set perminent environment variables (requires pywin32)'''
with patch.dict(envmodule.__salt__, {'reg.set_value': MagicMock(), 'reg.delete_value': MagicMock()}):
ret = envstate.setenv('test', 'value', permanent=True)
self.assertEqual(ret['changes'], {'test': 'value'})
envmodule.__salt__['reg.set_value'].assert_called_with("HKCU", "Environment", 'test', 'value')
@ -82,6 +89,9 @@ class TestEnvironState(TestCase, LoaderModuleMockMixin):
'''test that ``clear_all`` option sets other values to '' '''
ret = envstate.setenv('test', 'value', clear_all=True)
self.assertEqual(ret['changes'], {'test': 'value', 'INITIAL': ''})
if salt.utils.is_windows():
self.assertEqual(envstate.os.environ, {'TEST': 'value', 'INITIAL': ''})
else:
self.assertEqual(envstate.os.environ, {'test': 'value', 'INITIAL': ''})
def test_setenv_clearall_with_unset(self):
@ -89,6 +99,9 @@ class TestEnvironState(TestCase, LoaderModuleMockMixin):
unsets other values from environment'''
ret = envstate.setenv('test', 'value', false_unsets=True, clear_all=True)
self.assertEqual(ret['changes'], {'test': 'value', 'INITIAL': None})
if salt.utils.is_windows():
self.assertEqual(envstate.os.environ, {'TEST': 'value'})
else:
self.assertEqual(envstate.os.environ, {'test': 'value'})
def test_setenv_unset_multi(self):
@ -100,11 +113,17 @@ class TestEnvironState(TestCase, LoaderModuleMockMixin):
ret = envstate.setenv(
'notimportant', {'test': False, 'foo': 'baz'}, false_unsets=True)
self.assertEqual(ret['changes'], {'test': None, 'foo': 'baz'})
if salt.utils.is_windows():
self.assertEqual(envstate.os.environ, {'INITIAL': 'initial', 'FOO': 'baz'})
else:
self.assertEqual(envstate.os.environ, {'INITIAL': 'initial', 'foo': 'baz'})
with patch.dict(envstate.__salt__, {'reg.read_value': MagicMock()}):
ret = envstate.setenv('notimportant', {'test': False, 'foo': 'bax'})
self.assertEqual(ret['changes'], {'test': '', 'foo': 'bax'})
if salt.utils.is_windows():
self.assertEqual(envstate.os.environ, {'INITIAL': 'initial', 'FOO': 'bax', 'TEST': ''})
else:
self.assertEqual(envstate.os.environ, {'INITIAL': 'initial', 'foo': 'bax', 'test': ''})
def test_setenv_test_mode(self):

View File

@ -1843,3 +1843,36 @@ class TestFileState(TestCase, LoaderModuleMockMixin):
run_checks(test=True)
run_checks(strptime_format=fake_strptime_format)
run_checks(strptime_format=fake_strptime_format, test=True)
class TestFindKeepFiles(TestCase):
@skipIf(salt.utils.is_windows(), 'Do not run on Windows')
def test__find_keep_files_unix(self):
keep = filestate._find_keep_files(
'/test/parent_folder',
['/test/parent_folder/meh.txt']
)
expected = [
'/',
'/test',
'/test/parent_folder',
'/test/parent_folder/meh.txt',
]
actual = sorted(list(keep))
assert actual == expected, actual
@skipIf(not salt.utils.is_windows(), 'Only run on Windows')
def test__find_keep_files_win32(self):
keep = filestate._find_keep_files(
'c:\\test\\parent_folder',
['C:\\test\\parent_folder\\meh-2.txt']
)
expected = [
'c:\\',
'c:\\test',
'c:\\test\\parent_folder',
'c:\\test\\parent_folder\\meh-2.txt'
]
actual = sorted(list(keep))
assert actual == expected, actual

View File

@ -41,17 +41,18 @@ class WinNetworkTestCase(TestCase, LoaderModuleMockMixin):
' static, dhcp.'})
self.assertDictEqual(win_network.managed('salt'), ret)
mock = MagicMock(return_value=False)
mock_false = MagicMock(return_value=False)
mock_true = MagicMock(return_value=True)
mock1 = MagicMock(side_effect=[False, True, True, True, True, True,
True])
mock2 = MagicMock(side_effect=[False, True, True, {'salt': 'True'},
{'salt': 'True'}])
with patch.dict(win_network.__salt__, {"ip.is_enabled": mock,
with patch.dict(win_network.__salt__, {"ip.is_enabled": mock_false,
"ip.is_disabled": mock1,
"ip.enable": mock,
"ip.enable": mock_false,
"ip.get_interface": mock2,
"ip.set_dhcp_dns": mock,
"ip.set_dhcp_ip": mock}):
"ip.set_dhcp_dns": mock_false,
"ip.set_dhcp_ip": mock_false}):
ret.update({'comment': "Interface 'salt' is up to date."
" (already disabled)", 'result': True})
self.assertDictEqual(win_network.managed('salt',
@ -66,9 +67,11 @@ class WinNetworkTestCase(TestCase, LoaderModuleMockMixin):
dns_proto='static',
ip_proto='static'),
ret)
mock = MagicMock(side_effect=['True', False, False, False, False,
mock_false = MagicMock(side_effect=['True', False, False, False, False,
False])
with patch.object(win_network, '_validate', mock):
with patch.dict(win_network.__salt__, {"ip.is_enabled": mock_true}):
with patch.object(win_network, '_validate', mock_false):
ret.update({'comment': 'The following SLS configuration'
' errors were detected: T r u e'})
self.assertDictEqual(win_network.managed('salt',
@ -84,14 +87,14 @@ class WinNetworkTestCase(TestCase, LoaderModuleMockMixin):
ip_proto='dhcp'),
ret)
mock = MagicMock(side_effect=[False, [''],
mock_false = MagicMock(side_effect=[False, [''],
{'dns_proto': 'dhcp',
'ip_proto': 'dhcp'},
{'dns_proto': 'dhcp',
'ip_proto': 'dhcp'}])
ret.update({'comment': "Interface 'salt' is up to date.",
'result': True})
with patch.object(win_network, '_changes', mock):
with patch.object(win_network, '_changes', mock_false):
self.assertDictEqual(win_network.managed('salt',
dns_proto='dhcp',
ip_proto='dhcp'

View File

@ -13,7 +13,10 @@ from tests.support.mock import patch, NO_MOCK, NO_MOCK_REASON
# Import Salt libs
from salt import client
from salt.exceptions import EauthAuthenticationError, SaltInvocationError, SaltClientError
import salt.utils
from salt.exceptions import (
EauthAuthenticationError, SaltInvocationError, SaltClientError, SaltReqTimeoutError
)
@skipIf(NO_MOCK, NO_MOCK_REASON)
@ -67,7 +70,13 @@ class LocalClientTestCase(TestCase,
kwarg=None, tgt_type='list',
ret='')
@skipIf(salt.utils.is_windows(), 'Not supported on Windows')
def test_pub(self):
'''
Tests that the client cleanly returns when the publisher is not running
Note: Requires ZeroMQ's IPC transport which is not supported on windows.
'''
if self.get_config('minion')['transport'] != 'zeromq':
self.skipTest('This test only works with ZeroMQ')
# Make sure we cleanly return if the publisher isn't running
@ -83,3 +92,27 @@ class LocalClientTestCase(TestCase,
self.assertRaises(SaltInvocationError,
self.client.pub,
'non_existent_group', 'test.ping', tgt_type='nodegroup')
@skipIf(not salt.utils.is_windows(), 'Windows only test')
def test_pub_win32(self):
'''
Tests that the client raises a timeout error when using ZeroMQ's TCP
transport and publisher is not running.
Note: Requires ZeroMQ's TCP transport, this is only the default on Windows.
'''
if self.get_config('minion')['transport'] != 'zeromq':
self.skipTest('This test only works with ZeroMQ')
# Make sure we cleanly return if the publisher isn't running
with patch('os.path.exists', return_value=False):
self.assertRaises(SaltReqTimeoutError, lambda: self.client.pub('*', 'test.ping'))
# Check nodegroups behavior
with patch('os.path.exists', return_value=True):
with patch.dict(self.client.opts,
{'nodegroups':
{'group1': 'L@foo.domain.com,bar.domain.com,baz.domain.com or bl*.domain.com'}}):
# Do we raise an exception if the nodegroup can't be matched?
self.assertRaises(SaltInvocationError,
self.client.pub,
'non_existent_group', 'test.ping', tgt_type='nodegroup')

View File

@ -10,6 +10,7 @@ integration.modules.test_data
integration.modules.test_disk
integration.modules.test_git
integration.modules.test_grains
integration.modules.test_groupadd
integration.modules.test_hosts
integration.modules.test_mine
integration.modules.test_pillar