mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge branch '2017.7' into bugfix-grain-virtual_subtype
This commit is contained in:
commit
9eb6f5c0d0
23 changed files with 531 additions and 145 deletions
10
doc/conf.py
10
doc/conf.py
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
|
|
110
salt/loader.py
110
salt/loader.py
|
@ -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,15 +1287,16 @@ class LazyLoader(salt.utils.lazy.LazyDict):
|
|||
'''
|
||||
Clear the dict
|
||||
'''
|
||||
super(LazyLoader, self).clear() # clear the lazy loader
|
||||
self.loaded_files = set()
|
||||
self.missing_modules = {}
|
||||
self.loaded_modules = {}
|
||||
# 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.initial_load = False
|
||||
with self._lock:
|
||||
super(LazyLoader, self).clear() # clear the lazy loader
|
||||
self.loaded_files = set()
|
||||
self.missing_modules = {}
|
||||
self.loaded_modules = {}
|
||||
# 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.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,39 +1604,42 @@ 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:
|
||||
return True
|
||||
# if the modulename isn't in the whitelist, don't bother
|
||||
if self.whitelist and mod_name not in self.whitelist:
|
||||
raise KeyError
|
||||
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:
|
||||
raise KeyError
|
||||
|
||||
def _inner_load(mod_name):
|
||||
for name in self._iter_files(mod_name):
|
||||
if name in self.loaded_files:
|
||||
continue
|
||||
# if we got what we wanted, we are done
|
||||
if self._load_module(name) and key in self._dict:
|
||||
return True
|
||||
return False
|
||||
def _inner_load(mod_name):
|
||||
for name in self._iter_files(mod_name):
|
||||
if name in self.loaded_files:
|
||||
continue
|
||||
# if we got what we wanted, we are done
|
||||
if self._load_module(name) and key in self._dict:
|
||||
return True
|
||||
return False
|
||||
|
||||
# try to load the module
|
||||
ret = None
|
||||
reloaded = False
|
||||
# re-scan up to once, IOErrors or a failed load cause re-scans of the
|
||||
# filesystem
|
||||
while True:
|
||||
try:
|
||||
ret = _inner_load(mod_name)
|
||||
if not reloaded and ret is not True:
|
||||
self.refresh_file_mapping()
|
||||
reloaded = True
|
||||
# try to load the module
|
||||
ret = None
|
||||
reloaded = False
|
||||
# re-scan up to once, IOErrors or a failed load cause re-scans of the
|
||||
# filesystem
|
||||
while True:
|
||||
try:
|
||||
ret = _inner_load(mod_name)
|
||||
if not reloaded and ret is not True:
|
||||
self._refresh_file_mapping()
|
||||
reloaded = True
|
||||
continue
|
||||
break
|
||||
except IOError:
|
||||
if not reloaded:
|
||||
self._refresh_file_mapping()
|
||||
reloaded = True
|
||||
continue
|
||||
break
|
||||
except IOError:
|
||||
if not reloaded:
|
||||
self.refresh_file_mapping()
|
||||
reloaded = True
|
||||
continue
|
||||
|
||||
return ret
|
||||
|
||||
|
@ -1641,16 +1647,18 @@ class LazyLoader(salt.utils.lazy.LazyDict):
|
|||
'''
|
||||
Load all of them
|
||||
'''
|
||||
for name in self.file_mapping:
|
||||
if name in self.loaded_files or name in self.missing_modules:
|
||||
continue
|
||||
self._load_module(name)
|
||||
with self._lock:
|
||||
for name in self.file_mapping:
|
||||
if name in self.loaded_files or name in self.missing_modules:
|
||||
continue
|
||||
self._load_module(name)
|
||||
|
||||
self.loaded = True
|
||||
self.loaded = True
|
||||
|
||||
def reload_modules(self):
|
||||
self.loaded_files = set()
|
||||
self._load_all()
|
||||
with self._lock:
|
||||
self.loaded_files = set()
|
||||
self._load_all()
|
||||
|
||||
def _apply_outputter(self, func, mod):
|
||||
'''
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -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,9 +3048,19 @@ 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)
|
||||
_client_wrapper('remove_container', name, v=volumes, force=force)
|
||||
|
||||
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)]
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -2030,8 +2030,9 @@ def event(tagmatch='*',
|
|||
indent=None if not pretty else 4)))
|
||||
sys.stdout.flush()
|
||||
|
||||
count -= 1
|
||||
log.debug('Remaining event matches: %s', count)
|
||||
if count > 0:
|
||||
count -= 1
|
||||
log.debug('Remaining event matches: %s', count)
|
||||
|
||||
if count == 0:
|
||||
break
|
||||
|
|
|
@ -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 this_element_value is not None:
|
||||
element_valuenames = ['{0}{1}'.format(element.attrib['valuePrefix'],
|
||||
k) for k in element_valuenames]
|
||||
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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -515,7 +515,10 @@ def bootstrap(vm_, opts=None):
|
|||
'winrm_port', vm_, opts, default=5986
|
||||
)
|
||||
deploy_kwargs['winrm_use_ssl'] = salt.config.get_cloud_config_value(
|
||||
'winrm_use_ssl', vm_, opts, default=True
|
||||
'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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
5
tests/integration/files/windows-firewall-winexe.ps1
Normal file
5
tests/integration/files/windows-firewall-winexe.ps1
Normal 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>
|
33
tests/integration/files/windows-firewall.ps1
Normal file
33
tests/integration/files/windows-firewall.ps1
Normal 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>
|
|
@ -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': [],
|
||||
|
|
|
@ -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),
|
||||
|
|
96
tests/support/win_installer.py
Normal file
96
tests/support/win_installer.py
Normal 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
|
||||
))
|
|
@ -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
|
||||
|
|
|
@ -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,52 +67,54 @@ 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):
|
||||
ret.update({'comment': 'The following SLS configuration'
|
||||
' errors were detected: T r u e'})
|
||||
self.assertDictEqual(win_network.managed('salt',
|
||||
dns_proto='static',
|
||||
ip_proto='static'),
|
||||
ret)
|
||||
|
||||
ret.update({'comment': "Unable to get current"
|
||||
" configuration for interface 'salt'",
|
||||
'result': False})
|
||||
self.assertDictEqual(win_network.managed('salt',
|
||||
dns_proto='dhcp',
|
||||
ip_proto='dhcp'),
|
||||
ret)
|
||||
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',
|
||||
dns_proto='static',
|
||||
ip_proto='static'),
|
||||
ret)
|
||||
|
||||
mock = 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):
|
||||
ret.update({'comment': "Unable to get current"
|
||||
" configuration for interface 'salt'",
|
||||
'result': False})
|
||||
self.assertDictEqual(win_network.managed('salt',
|
||||
dns_proto='dhcp',
|
||||
ip_proto='dhcp'
|
||||
), ret)
|
||||
ip_proto='dhcp'),
|
||||
ret)
|
||||
|
||||
ret.update({'comment': "The following changes will be made"
|
||||
" to interface 'salt': ", 'result': None})
|
||||
with patch.dict(win_network.__opts__, {"test": True}):
|
||||
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_false):
|
||||
self.assertDictEqual(win_network.managed('salt',
|
||||
dns_proto='dh'
|
||||
'cp',
|
||||
dns_proto='dhcp',
|
||||
ip_proto='dhcp'
|
||||
), ret)
|
||||
|
||||
with patch.dict(win_network.__opts__, {"test": False}):
|
||||
ret.update({'comment': "Failed to set desired"
|
||||
" configuration settings for interface"
|
||||
" 'salt'", 'result': False})
|
||||
self.assertDictEqual(win_network.managed('salt',
|
||||
dns_proto='dh'
|
||||
'cp',
|
||||
ip_proto='dhcp'
|
||||
), ret)
|
||||
ret.update({'comment': "The following changes will be made"
|
||||
" to interface 'salt': ", 'result': None})
|
||||
with patch.dict(win_network.__opts__, {"test": True}):
|
||||
self.assertDictEqual(win_network.managed('salt',
|
||||
dns_proto='dh'
|
||||
'cp',
|
||||
ip_proto='dhcp'
|
||||
), ret)
|
||||
|
||||
with patch.dict(win_network.__opts__, {"test": False}):
|
||||
ret.update({'comment': "Failed to set desired"
|
||||
" configuration settings for interface"
|
||||
" 'salt'", 'result': False})
|
||||
self.assertDictEqual(win_network.managed('salt',
|
||||
dns_proto='dh'
|
||||
'cp',
|
||||
ip_proto='dhcp'
|
||||
), ret)
|
||||
|
|
Loading…
Add table
Reference in a new issue