Merge branch '2018.3' into merge-2018.3

Conflicts:
	doc/man/salt-api.1
	doc/man/salt-call.1
	doc/man/salt-cloud.1
	doc/man/salt-cp.1
	doc/man/salt-key.1
	doc/man/salt-master.1
	doc/man/salt-minion.1
	doc/man/salt-proxy.1
	doc/man/salt-run.1
	doc/man/salt-ssh.1
	doc/man/salt-syndic.1
	doc/man/salt-unity.1
	doc/man/salt.1
	doc/man/salt.7
	doc/man/spm.1
	pkg/osx/build_env.sh
	salt/utils/dns.py
	tests/integration/netapi/rest_tornado/test_app.py
	tests/support/case.py
	tests/unit/utils/test_dns.py
This commit is contained in:
Gareth J. Greenaway 2019-02-11 15:45:43 -08:00
commit 6c99cb161f
No known key found for this signature in database
GPG key ID: 10B62F8A7CAD7A41
46 changed files with 6818 additions and 229 deletions

View file

@ -2646,6 +2646,8 @@ can have multiple root directories. The subdirectories in the multiple file
roots cannot match, otherwise the downloaded files will not be able to be
reliably ensured. A base environment is required to house the top file.
As of 2018.3.5 and 2019.2.1, it is possible to have `__env__` as a catch-all environment.
Example:
.. code-block:: yaml
@ -2659,6 +2661,8 @@ Example:
prod:
- /srv/salt/prod/services
- /srv/salt/prod/states
__env__:
- /srv/salt/default
.. note::
For masterless Salt, this parameter must be specified in the minion config

File diff suppressed because it is too large Load diff

View file

@ -734,6 +734,19 @@ Then the ``roots`` backend (the default backend of files in ``/srv/salt``) will
be searched first for the requested file; then, if it is not found on the
master, each configured git remote will be searched.
.. note::
This can be used together with `file_roots` accepting `__env__` as a catch-all
environment, since 2018.3.5 and 2019.2.1:
.. code-block:: yaml
file_roots:
base:
- /srv/salt
__env__:
- /srv/salt
Branches, Environments, and Top Files
=====================================

View file

@ -158,13 +158,20 @@ echo -n -e "\033]0;Build_Pkg: Add Version to .xml\007"
if [ "$PYVER" == "2" ]; then
TITLE="Salt $VERSION"
DESC="Salt $VERSION with Python 2"
SEDSTR="s/@PY2@/_py2/g"
else
TITLE="Salt $VERSION (Python 3)"
DESC="Salt $VERSION with Python 3"
SEDSTR="s/@PY2@//g"
fi
cd $PKGRESOURCES
cp distribution.xml.dist distribution.xml
# Select the appropriate welcome text
# This is only necessary until Sodium, then this can be removed
sed -E -i '' "$SEDSTR" distribution.xml
SEDSTR="s/@TITLE@/$TITLE/g"
sed -E -i '' "$SEDSTR" distribution.xml

View file

@ -16,7 +16,7 @@
mime-type="image/png"
scaling="proportional" />
<!-- Define documents displayed at various steps -->
<welcome file="welcome.rtf"
<welcome file="welcome@PY2@.rtf"
mime-type="text/rtf" />
<license file="license.rtf"
mime-type="text/rtf" />

View file

@ -0,0 +1,32 @@
{\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340
{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\froman\fcharset0 Times-Roman;\f2\fnil\fcharset0 PTMono-Regular;
}
{\colortbl;\red255\green255\blue255;\red0\green0\blue233;}
\vieww28300\viewh14680\viewkind0
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
\f0\fs24 \cf0 WARNING: Python 2 Support will be discontinued in Sodium. Salt will only ship Python 3 installers starting with the Sodium release.\
\
\pard\pardeftab720\sl280\sa240\partightenfactor0
{\field{\*\fldinst{HYPERLINK "http://saltstack.com/"}}{\fldrslt
\f1 \cf2 \expnd0\expndtw0\kerning0
\ul \ulc2 SaltStack}}
\f1 \expnd0\expndtw0\kerning0
is extremely fast and scalable systems and configuration management software for predictive orchestration, cloud and data center automation, server provisioning, application deployment and more. \
Documentation for Salt is available at {\field{\*\fldinst{HYPERLINK "http://docs.saltstack.com/"}}{\fldrslt \cf2 \ul \ulc2 http://docs.saltstack.com}} \
This package will install Salt on your Mac. Salt installs into
\f2\fs22 /opt/salt
\f1\fs24 .\
Sample configuration files (
\f2\fs22 master.dist
\f1\fs24 and
\f2\fs22 minion.dist
\f1\fs24 ) will be installed to
\f2\fs22 /etc/salt
\f1\fs24 . Create copies of them without the '
\f2\fs22 .dist
\f1\fs24 ' in the filename and edit as you see fit.\
This Salt package uses a custom-built Python. To install additional Python modules for Salt, use the associated 'pip' binary. For example, if you need LDAP support in Salt you will need the 'python-ldap' module. Install it with
\f2\fs22 /opt/salt/bin/pip install python-ldap
\f1\fs24 \
Note that some Python modules need a compiler available. Installing Apple's Xcode Command Line Tools should provide the necessary utilities. Alternatively, {\field{\*\fldinst{HYPERLINK "http://macports.org/"}}{\fldrslt \cf2 \ul \ulc2 MacPorts}}, {\field{\*\fldinst{HYPERLINK "http://finkproject.org/"}}{\fldrslt \cf2 \ul \ulc2 Fink}}, or {\field{\*\fldinst{HYPERLINK "http://brew.sh/"}}{\fldrslt \cf2 \ul \ulc2 Homebrew}} may provide what you need.}

View file

@ -70,6 +70,22 @@ ${StrStrAdv}
!define MUI_ICON "salt.ico"
!define MUI_UNICON "salt.ico"
!define MUI_WELCOMEFINISHPAGE_BITMAP "panel.bmp"
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "panel.bmp"
# This entire if block can be removed for the Sodium release... including the !define MUI_WELCOMEPAGE_TEXT
# NSIS will just use the default like it does for Python 3, which should be the same test
!if "${PYTHON_VERSION}" == "2"
!define MUI_WELCOMEPAGE_TEXT "\
WARNING: Python 2 Support will be discontinued in Sodium. Salt will only ship Python 3 \
installers starting with the Sodium release.$\r$\n\
$\r$\n\
Setup will guide you through the installation of ${PRODUCT_NAME} ${PRODUCT_VERSION}.$\r$\n\
$\r$\n\
It is recommended that you close all other applications before starting Setup. This will make it possible to \
update relevant system files without having to reboot your computer.$\r$\n\
$\r$\n\
Click Next to continue."
!endif
# Welcome page
!insertmacro MUI_PAGE_WELCOME

View file

@ -52,8 +52,8 @@ def beacon(config):
beacons:
service:
- services:
salt-master:
mysql:
salt-master: {}
mysql: {}
The config above sets up beacons to check for
the salt-master and mysql services.

View file

@ -1348,6 +1348,24 @@ class Cloud(object):
output['ret'] = action_out
return output
@staticmethod
def vm_config(name, main, provider, profile, overrides):
'''
Create vm config.
:param str name: The name of the vm
:param dict main: The main cloud config
:param dict provider: The provider config
:param dict profile: The profile config
:param dict overrides: The vm's config overrides
'''
vm = main.copy()
vm = salt.utils.dictupdate.update(vm, provider)
vm = salt.utils.dictupdate.update(vm, profile)
vm.update(overrides)
vm['name'] = name
return vm
def extras(self, extra_):
'''
Extra actions
@ -1434,12 +1452,13 @@ class Cloud(object):
ret[name] = {'Error': msg}
continue
vm_ = main_cloud_config.copy()
vm_.update(provider_details)
vm_.update(profile_details)
vm_.update(vm_overrides)
vm_['name'] = name
vm_ = self.vm_config(
name,
main_cloud_config,
provider_details,
profile_details,
vm_overrides,
)
if self.opts['parallel']:
process = multiprocessing.Process(
target=self.create,

View file

@ -1968,15 +1968,10 @@ PROVIDER_CONFIG_DEFAULTS = {
# <---- Salt Cloud Configuration Defaults ------------------------------------
def _validate_file_roots(file_roots):
def _normalize_roots(file_roots):
'''
If the file_roots option has a key that is None then we will error out,
just replace it with an empty list
Normalize file or pillar roots.
'''
if not isinstance(file_roots, dict):
log.warning('The file_roots parameter is not properly formatted,'
' using defaults')
return {'base': _expand_glob_path([salt.syspaths.BASE_FILE_ROOTS_DIR])}
for saltenv, dirs in six.iteritems(file_roots):
normalized_saltenv = six.text_type(saltenv)
if normalized_saltenv != saltenv:
@ -1988,6 +1983,30 @@ def _validate_file_roots(file_roots):
return file_roots
def _validate_pillar_roots(pillar_roots):
'''
If the pillar_roots option has a key that is None then we will error out,
just replace it with an empty list
'''
if not isinstance(pillar_roots, dict):
log.warning('The pillar_roots parameter is not properly formatted,'
' using defaults')
return {'base': _expand_glob_path([salt.syspaths.BASE_PILLAR_ROOTS_DIR])}
return _normalize_roots(pillar_roots)
def _validate_file_roots(file_roots):
'''
If the file_roots option has a key that is None then we will error out,
just replace it with an empty list
'''
if not isinstance(file_roots, dict):
log.warning('The file_roots parameter is not properly formatted,'
' using defaults')
return {'base': _expand_glob_path([salt.syspaths.BASE_FILE_ROOTS_DIR])}
return _normalize_roots(file_roots)
def _expand_glob_path(file_roots):
'''
Applies shell globbing to a set of directories and returns
@ -3799,7 +3818,7 @@ def apply_minion_config(overrides=None,
# nothing else!
opts['open_mode'] = opts['open_mode'] is True
opts['file_roots'] = _validate_file_roots(opts['file_roots'])
opts['pillar_roots'] = _validate_file_roots(opts['pillar_roots'])
opts['pillar_roots'] = _validate_pillar_roots(opts['pillar_roots'])
# Make sure ext_mods gets set if it is an untrue value
# (here to catch older bad configs)
opts['extension_modules'] = (

View file

@ -51,7 +51,11 @@ def find_file(path, saltenv='base', **kwargs):
if os.path.isabs(path):
return fnd
if saltenv not in __opts__['file_roots']:
return fnd
if '__env__' in __opts__['file_roots']:
log.debug("salt environment '%s' maps to __env__ file_roots directory", saltenv)
saltenv = '__env__'
else:
return fnd
def _add_file_stat(fnd):
'''
@ -220,6 +224,9 @@ def file_hash(load, fnd):
if 'path' not in load or 'saltenv' not in load:
return ''
path = fnd['path']
saltenv = load['saltenv']
if saltenv not in __opts__['file_roots'] and '__env__' in __opts__['file_roots']:
saltenv = '__env__'
ret = {}
# if the file doesn't exist, we can't get a hash
@ -234,7 +241,7 @@ def file_hash(load, fnd):
cache_path = os.path.join(__opts__['cachedir'],
'roots',
'hash',
load['saltenv'],
saltenv,
'{0}.hash.{1}'.format(fnd['rel'],
__opts__['hash_type']))
# if we have a cache, serve that if the mtime hasn't changed
@ -293,8 +300,13 @@ def _file_lists(load, form):
# "env" is not supported; Use "saltenv".
load.pop('env')
if load['saltenv'] not in __opts__['file_roots']:
return []
saltenv = load['saltenv']
if saltenv not in __opts__['file_roots']:
if '__env__' in __opts__['file_roots']:
log.debug("salt environment '%s' maps to __env__ file_roots directory", saltenv)
saltenv = '__env__'
else:
return []
list_cachedir = os.path.join(__opts__['cachedir'], 'file_lists', 'roots')
if not os.path.isdir(list_cachedir):
@ -303,8 +315,8 @@ def _file_lists(load, form):
except os.error:
log.critical('Unable to make cachedir %s', list_cachedir)
return []
list_cache = os.path.join(list_cachedir, '{0}.p'.format(salt.utils.files.safe_filename_leaf(load['saltenv'])))
w_lock = os.path.join(list_cachedir, '.{0}.w'.format(salt.utils.files.safe_filename_leaf(load['saltenv'])))
list_cache = os.path.join(list_cachedir, '{0}.p'.format(salt.utils.files.safe_filename_leaf(saltenv)))
w_lock = os.path.join(list_cachedir, '.{0}.w'.format(salt.utils.files.safe_filename_leaf(saltenv)))
cache_match, refresh_cache, save_cache = \
salt.fileserver.check_file_list_cache(
__opts__, form, list_cache, w_lock
@ -390,7 +402,7 @@ def _file_lists(load, form):
# (i.e. the "path" variable)
ret['links'][rel_path] = link_dest
for path in __opts__['file_roots'][load['saltenv']]:
for path in __opts__['file_roots'][saltenv]:
for root, dirs, files in salt.utils.path.os_walk(
path,
followlinks=__opts__['fileserver_followsymlinks']):
@ -445,7 +457,7 @@ def symlink_list(load):
load.pop('env')
ret = {}
if load['saltenv'] not in __opts__['file_roots']:
if load['saltenv'] not in __opts__['file_roots'] and '__env__' not in __opts__['file_roots']:
return ret
if 'prefix' in load:

View file

@ -1466,7 +1466,7 @@ def _split_repo_str(repo):
Return APT source entry as a tuple.
'''
split = sourceslist.SourceEntry(repo)
return split.type, split.uri, split.dist, split.comps
return split.type, split.architectures, split.uri, split.dist, split.comps
def _consolidate_repo_sources(sources):
@ -1650,7 +1650,7 @@ def get_repo(repo, **kwargs):
if repos:
try:
repo_type, repo_uri, repo_dist, repo_comps = _split_repo_str(repo)
repo_type, repo_architectures, repo_uri, repo_dist, repo_comps = _split_repo_str(repo)
if ppa_auth:
uri_match = re.search('(http[s]?://)(.+)', repo_uri)
if uri_match:
@ -1723,7 +1723,11 @@ def del_repo(repo, **kwargs):
if repos:
deleted_from = dict()
try:
repo_type, repo_uri, repo_dist, repo_comps = _split_repo_str(repo)
repo_type, \
repo_architectures, \
repo_uri, \
repo_dist, \
repo_comps = _split_repo_str(repo)
except SyntaxError:
raise SaltInvocationError(
'Error: repo \'{0}\' not a well formatted definition'
@ -1731,8 +1735,10 @@ def del_repo(repo, **kwargs):
)
for source in repos:
if (source.type == repo_type and source.uri == repo_uri and
source.dist == repo_dist):
if (source.type == repo_type
and source.architectures == repo_architectures
and source.uri == repo_uri
and source.dist == repo_dist):
s_comps = set(source.comps)
r_comps = set(repo_comps)
@ -2199,7 +2205,11 @@ def mod_repo(repo, saltenv='base', **kwargs):
repos = [s for s in sources if not s.invalid]
mod_source = None
try:
repo_type, repo_uri, repo_dist, repo_comps = _split_repo_str(repo)
repo_type, \
repo_architectures, \
repo_uri, \
repo_dist, \
repo_comps = _split_repo_str(repo)
except SyntaxError:
raise SyntaxError(
'Error: repo \'{0}\' not a well formatted definition'.format(repo)
@ -2270,6 +2280,8 @@ def mod_repo(repo, saltenv='base', **kwargs):
if 'architectures' in kwargs:
kwargs['architectures'] = kwargs['architectures'].split(',')
else:
kwargs['architectures'] = repo_architectures
if 'disabled' in kwargs:
kwargs['disabled'] = salt.utils.data.is_true(kwargs['disabled'])
@ -2291,6 +2303,8 @@ def mod_repo(repo, saltenv='base', **kwargs):
mod_source = source
if not source.comps:
mod_source = source
if kwargs['architectures'] != source.architectures:
mod_source = source
if mod_source:
break

View file

@ -979,7 +979,7 @@ def clone(cwd,
information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT
.. versionchanged:: 2015.8.7
@ -2093,7 +2093,7 @@ def fetch(cwd,
information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT
.. versionchanged:: 2015.8.7
@ -2905,7 +2905,7 @@ def ls_remote(cwd=None,
information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT
.. versionchanged:: 2015.8.7
@ -2998,6 +2998,7 @@ def merge(cwd,
git_opts='',
user=None,
password=None,
identity=None,
ignore_retcode=False,
output_encoding=None,
**kwargs):
@ -3041,6 +3042,22 @@ def merge(cwd,
.. versionadded:: 2016.3.4
identity
Path to a private key to use for ssh URLs. Salt will not attempt to use
passphrase-protected keys unless invoked from the minion using
``salt-call``, to prevent blocking waiting for user input. Key can also
be specified as a SaltStack file server URL, eg.
``salt://location/identity_file``.
.. note::
For greater security with passphraseless private keys, see the
`sshd(8)`_ manpage for information on securing the keypair from the
remote side in the ``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT
.. versionadded:: 2018.3.5,2019.2.1,Neon
ignore_retcode : False
If ``True``, do not log an error to the minion log if the git command
returns a nonzero exit status.
@ -3082,10 +3099,12 @@ def merge(cwd,
command.extend(_format_opts(opts))
if rev:
command.append(rev)
return _git_run(command,
cwd=cwd,
user=user,
password=password,
identity=identity,
ignore_retcode=ignore_retcode,
output_encoding=output_encoding)['stdout']
@ -3440,7 +3459,7 @@ def pull(cwd,
information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT
.. versionchanged:: 2015.8.7
@ -3566,7 +3585,7 @@ def push(cwd,
information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT
.. versionchanged:: 2015.8.7
@ -3858,7 +3877,7 @@ def remote_refs(url,
information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT
.. versionchanged:: 2015.8.7
@ -4175,6 +4194,7 @@ def reset(cwd,
git_opts='',
user=None,
password=None,
identity=None,
ignore_retcode=False,
output_encoding=None):
'''
@ -4211,6 +4231,22 @@ def reset(cwd,
.. versionadded:: 2016.3.4
identity
Path to a private key to use for ssh URLs. Salt will not attempt to use
passphrase-protected keys unless invoked from the minion using
``salt-call``, to prevent blocking waiting for user input. Key can also
be specified as a SaltStack file server URL, eg.
``salt://location/identity_file``.
.. note::
For greater security with passphraseless private keys, see the
`sshd(8)`_ manpage for information on securing the keypair from the
remote side in the ``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT
.. versionadded:: 2018.3.5,2019.2.1,Neon
ignore_retcode : False
If ``True``, do not log an error to the minion log if the git command
returns a nonzero exit status.
@ -4248,6 +4284,7 @@ def reset(cwd,
cwd=cwd,
user=user,
password=password,
identity=identity,
ignore_retcode=ignore_retcode,
output_encoding=output_encoding)['stdout']
@ -4736,7 +4773,7 @@ def submodule(cwd,
information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT
.. versionchanged:: 2015.8.7

View file

@ -1264,6 +1264,7 @@ def user_exists(user,
'''
run_verify = False
server_version = version(**connection_args)
compare_version = '10.2.0' if 'MariaDB' in server_version else '8.0.11'
dbc = _connect(**connection_args)
# Did we fail to connect with the user we are checking
# Its password might have previously change with the same command/state
@ -1295,7 +1296,7 @@ def user_exists(user,
else:
qry += ' AND ' + password_column + ' = \'\''
elif password:
if salt.utils.versions.version_cmp(server_version, '8.0.11') >= 0:
if salt.utils.versions.version_cmp(server_version, compare_version) >= 0:
run_verify = True
else:
_password = password
@ -1402,6 +1403,7 @@ def user_create(user,
salt '*' mysql.user_create 'username' 'hostname' allow_passwordless=True
'''
server_version = version(**connection_args)
compare_version = '10.2.0' if 'MariaDB' in server_version else '8.0.11'
if user_exists(user, host, **connection_args):
log.info('User \'%s\'@\'%s\' already exists', user, host)
return False
@ -1422,7 +1424,7 @@ def user_create(user,
qry += ' IDENTIFIED BY %(password)s'
args['password'] = six.text_type(password)
elif password_hash is not None:
if salt.utils.versions.version_cmp(server_version, '8.0.11') >= 0:
if salt.utils.versions.version_cmp(server_version, compare_version) >= 0:
qry += ' IDENTIFIED BY %(password)s'
else:
qry += ' IDENTIFIED BY PASSWORD %(password)s'
@ -1506,9 +1508,10 @@ def user_chpass(user,
salt '*' mysql.user_chpass frank localhost allow_passwordless=True
'''
server_version = version(**connection_args)
compare_version = '10.2.0' if 'MariaDB' in server_version else '8.0.11'
args = {}
if password is not None:
if salt.utils.versions.version_cmp(server_version, '8.0.11') >= 0:
if salt.utils.versions.version_cmp(server_version, compare_version) >= 0:
password_sql = '%(password)s'
else:
password_sql = 'PASSWORD(%(password)s)'
@ -1531,28 +1534,23 @@ def user_chpass(user,
password_column = __password_column(**connection_args)
cur = dbc.cursor()
if salt.utils.versions.version_cmp(server_version, '8.0.11') >= 0:
qry = ("ALTER USER '" + user + "'@'" + host + "'"
" IDENTIFIED BY '" + password + "';")
args = {}
args['user'] = user
args['host'] = host
if salt.utils.versions.version_cmp(server_version, compare_version) >= 0:
qry = "ALTER USER %(user)s@%(host)s IDENTIFIED BY %(password)s;"
else:
qry = ('UPDATE mysql.user SET ' + password_column + '='
+ password_sql +
qry = ('UPDATE mysql.user SET ' + password_column + '=' + password_sql +
' WHERE User=%(user)s AND Host = %(host)s;')
args['user'] = user
args['host'] = host
if salt.utils.data.is_true(allow_passwordless) and \
salt.utils.data.is_true(unix_socket):
if host == 'localhost':
if salt.utils.versions.version_cmp(server_version, '8.0.11') >= 0:
qry = ("ALTER USER '" + user + "'@'" + host + "'"
" IDENTIFIED BY '" + password + "';")
args = {}
args['unix_socket'] = 'auth_socket'
if salt.utils.versions.version_cmp(server_version, compare_version) >= 0:
qry = "ALTER USER %(user)s@%(host)s IDENTIFIED WITH %(unix_socket)s AS %(user)s;"
else:
qry = ('UPDATE mysql.user SET ' + password_column + '='
+ password_sql + ', plugin=%(unix_socket)s' +
' WHERE User=%(user)s AND Host = %(host)s;')
args['unix_socket'] = 'unix_socket'
else:
log.error('Auth via unix_socket can be set only for host=localhost')
try:
@ -1563,7 +1561,7 @@ def user_chpass(user,
log.error(err)
return False
if salt.utils.versions.version_cmp(server_version, '8.0.11') >= 0:
if salt.utils.versions.version_cmp(server_version, compare_version) >= 0:
_execute(cur, 'FLUSH PRIVILEGES;')
log.info(
'Password for user \'%s\'@\'%s\' has been %s',
@ -1867,7 +1865,8 @@ def grant_exists(grant,
server_version = version(**connection_args)
if 'ALL' in grant:
if salt.utils.versions.version_cmp(server_version, '8.0') >= 0:
if salt.utils.versions.version_cmp(server_version, '8.0') >= 0 and \
'MariaDB' not in server_version:
grant = ','.join([i for i in __all_privileges__])
else:
grant = 'ALL PRIVILEGES'

View file

@ -19,7 +19,6 @@ import re
import subprocess
# Import salt libs
from salt.ext import six
import salt.utils.decorators.path
import salt.utils.data
import salt.utils.files
@ -44,9 +43,8 @@ if six.PY3:
def __virtual__():
# TODO: This could work on windows with some love
if salt.utils.platform.is_windows():
return (False, 'The module cannot be loaded on windows.')
if not salt.utils.path.which('ssh'):
return False, 'The module requires the ssh binary.'
return True
@ -753,9 +751,10 @@ def set_auth_key(
if not os.path.isdir(os.path.dirname(fconfig)):
dpath = os.path.dirname(fconfig)
os.makedirs(dpath)
if os.geteuid() == 0:
os.chown(dpath, uinfo['uid'], uinfo['gid'])
os.chmod(dpath, 448)
if not salt.utils.platform.is_windows():
if os.geteuid() == 0:
os.chown(dpath, uinfo['uid'], uinfo['gid'])
os.chmod(dpath, 448)
# If SELINUX is available run a restorecon on the file
rcon = salt.utils.path.which('restorecon')
if rcon:
@ -784,9 +783,10 @@ def set_auth_key(
raise CommandExecutionError(msg.format(exc))
if new_file:
if os.geteuid() == 0:
os.chown(fconfig, uinfo['uid'], uinfo['gid'])
os.chmod(fconfig, 384)
if not salt.utils.platform.is_windows():
if os.geteuid() == 0:
os.chown(fconfig, uinfo['uid'], uinfo['gid'])
os.chmod(fconfig, 384)
# If SELINUX is available run a restorecon on the file
rcon = salt.utils.path.which('restorecon')
if rcon:
@ -1104,10 +1104,11 @@ def rm_known_host(user=None, hostname=None, config=None, port=None):
ssh_hostname = _hostname_and_port_to_ssh_hostname(hostname, port)
cmd = ['ssh-keygen', '-R', ssh_hostname, '-f', full]
cmd_result = __salt__['cmd.run'](cmd, python_shell=False)
# ssh-keygen creates a new file, thus a chown is required.
if os.geteuid() == 0 and user:
uinfo = __salt__['user.info'](user)
os.chown(full, uinfo['uid'], uinfo['gid'])
if not salt.utils.platform.is_windows():
# ssh-keygen creates a new file, thus a chown is required.
if os.geteuid() == 0 and user:
uinfo = __salt__['user.info'](user)
os.chown(full, uinfo['uid'], uinfo['gid'])
return {'status': 'removed', 'comment': cmd_result}
@ -1317,12 +1318,13 @@ def set_known_host(user=None,
"Couldn't append to known hosts file: '{0}'".format(exception)
)
if os.geteuid() == 0 and user:
os.chown(full, uinfo['uid'], uinfo['gid'])
if origmode:
os.chmod(full, origmode)
else:
os.chmod(full, 0o600)
if not salt.utils.platform.is_windows():
if os.geteuid() == 0 and user:
os.chown(full, uinfo['uid'], uinfo['gid'])
if origmode:
os.chmod(full, origmode)
else:
os.chmod(full, 0o600)
if key and hash_known_hosts:
cmd_result = __salt__['ssh.hash_known_hosts'](user=user, config=full)
@ -1446,10 +1448,11 @@ def hash_known_hosts(user=None, config=None):
cmd = ['ssh-keygen', '-H', '-f', full]
cmd_result = __salt__['cmd.run'](cmd, python_shell=False)
os.chmod(full, origmode)
# ssh-keygen creates a new file, thus a chown is required.
if os.geteuid() == 0 and user:
uinfo = __salt__['user.info'](user)
os.chown(full, uinfo['uid'], uinfo['gid'])
if not salt.utils.platform.is_windows():
# ssh-keygen creates a new file, thus a chown is required.
if os.geteuid() == 0 and user:
uinfo = __salt__['user.info'](user)
os.chown(full, uinfo['uid'], uinfo['gid'])
return {'status': 'updated', 'comment': cmd_result}

View file

@ -5083,8 +5083,8 @@ def _findOptionValueAdvAudit(option):
field_names = _get_audit_defaults('fieldnames')
# If the file doesn't exist anywhere, create it with default
# fieldnames
__salt__['file.touch'](f_audit)
__salt__['file.append'](f_audit, ','.join(field_names))
__salt__['file.mkdir'](os.path.dirname(f_audit))
__salt__['file.write'](f_audit, ','.join(field_names))
audit_settings = {}
with salt.utils.files.fopen(f_audit, mode='r') as csv_file:
@ -5187,6 +5187,7 @@ def _set_audit_file_data(option, value):
# Copy the temporary csv file over the existing audit.csv in both
# locations if a value was written
__salt__['file.copy'](f_temp.name, f_audit, remove_existing=True)
__salt__['file.mkdir'](os.path.dirname(f_audit_gpo))
__salt__['file.copy'](f_temp.name, f_audit_gpo, remove_existing=True)
finally:
f_temp.close()

View file

@ -205,17 +205,22 @@ def get_zone():
Returns:
str: Timezone in unix format
Raises:
CommandExecutionError: If timezone could not be gathered
CLI Example:
.. code-block:: bash
salt '*' timezone.get_zone
'''
win_zone = __utils__['reg.read_value'](
hive='HKLM',
key='SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation',
vname='TimeZoneKeyName')['vdata']
return mapper.get_unix(win_zone.lower(), 'Unknown')
cmd = ['tzutil', '/g']
res = __salt__['cmd.run_all'](cmd, python_shell=False)
if res['retcode'] or not res['stdout']:
raise CommandExecutionError('tzutil encountered an error getting '
'timezone',
info=res)
return mapper.get_unix(res['stdout'].lower(), 'Unknown')
def get_offset():

View file

@ -34,6 +34,11 @@ import salt.utils.odict
import salt.utils.stringutils
from salt.ext import six
try:
from collections.abc import Mapping
except ImportError:
from collections import Mapping
class NestDisplay(object):
'''
@ -142,7 +147,7 @@ class NestDisplay(object):
if self.retcode != 0:
color = self.RED
for ind in ret:
if isinstance(ind, (list, tuple, dict)):
if isinstance(ind, (list, tuple, Mapping)):
out.append(
self.ustring(
indent,
@ -150,11 +155,11 @@ class NestDisplay(object):
'|_'
)
)
prefix = '' if isinstance(ind, dict) else '- '
prefix = '' if isinstance(ind, Mapping) else '- '
self.display(ind, indent + 2, prefix, out)
else:
self.display(ind, indent, '- ', out)
elif isinstance(ret, dict):
elif isinstance(ret, Mapping):
if indent:
color = self.CYAN
if self.retcode != 0:

View file

@ -19,6 +19,7 @@ import salt.transport.frame
import salt.utils.immutabletypes as immutabletypes
import salt.utils.stringutils
from salt.exceptions import SaltReqTimeoutError
from salt.utils.data import CaseInsensitiveDict
# Import third party libs
from salt.ext import six
@ -209,6 +210,8 @@ class Serial(object):
elif isinstance(obj, (set, immutabletypes.ImmutableSet)):
# msgpack can't handle set so translate it to tuple
return tuple(obj)
elif isinstance(obj, CaseInsensitiveDict):
return dict(obj)
# Nothing known exceptions found. Let msgpack raise it's own.
return obj

View file

@ -225,8 +225,7 @@ def returner(ret):
'functions', job_id, job_fun
)
return
if ret.get('return', None) is None:
if ret.get('data', None) is None and ret.get('return') is None:
log.info(
'Won\'t push new data to Elasticsearch, job with jid=%s was '
'not successful', job_id

View file

@ -3782,12 +3782,14 @@ class BaseHighState(object):
statefiles = []
for saltenv, states in six.iteritems(matches):
for sls_match in states:
try:
if saltenv in self.avail:
statefiles = fnmatch.filter(self.avail[saltenv], sls_match)
except KeyError:
all_errors.extend(
['No matching salt environment for environment '
'\'{0}\' found'.format(saltenv)]
elif '__env__' in self.avail:
statefiles = fnmatch.filter(self.avail['__env__'], sls_match)
else:
all_errors.append(
'No matching salt environment for environment '
'\'{0}\' found'.format(saltenv)
)
# if we did not found any sls in the fileserver listing, this
# may be because the sls was generated or added later, we can

View file

@ -3448,7 +3448,7 @@ def directory(name,
ret, _ = __salt__['file.check_perms'](
full, ret, user, group, file_mode, None, follow_symlinks)
except CommandExecutionError as exc:
if not exc.strerror.endswith('does not exist'):
if not exc.strerror.startswith('Path not found'):
errors.append(exc.strerror)
if check_dirs:

View file

@ -704,6 +704,15 @@ def latest(name,
if https_pass is not None and not isinstance(https_pass, six.string_types):
https_pass = six.text_type(https_pass)
# Check for lfs filter settings, and setup lfs_opts accordingly. These opts
# will be passed where appropriate to ensure that these commands are
# authenticated and that the git LFS plugin can download files.
use_lfs = bool(
__salt__['git.config_get_regexp'](
r'filter\.lfs\.',
**{'global': True}))
lfs_opts = {'identity': identity} if use_lfs else {}
if os.path.isfile(target):
return _fail(
ret,
@ -1583,7 +1592,8 @@ def latest(name,
opts=['--hard', remote_rev],
user=user,
password=password,
output_encoding=output_encoding)
output_encoding=output_encoding,
**lfs_opts)
ret['changes']['forced update'] = True
if local_changes:
comments.append('Uncommitted changes were discarded')
@ -1647,7 +1657,8 @@ def latest(name,
opts=merge_opts,
user=user,
password=password,
output_encoding=output_encoding)
output_encoding=output_encoding,
**lfs_opts)
comments.append(
'Repository was fast-forwarded to {0}'
.format(remote_loc)
@ -1667,7 +1678,8 @@ def latest(name,
remote_rev if rev == 'HEAD' else rev],
user=user,
password=password,
output_encoding=output_encoding)
output_encoding=output_encoding,
**lfs_opts)
comments.append(
'Repository was reset to {0} (fast-forward)'
.format(rev)

View file

@ -30,6 +30,7 @@ except ImportError:
HAS_PKG_RESOURCES = False
# Import salt libs
import salt.utils.data
import salt.utils.versions
from salt.version import SaltStackVersion as _SaltStackVersion
from salt.exceptions import CommandExecutionError, CommandNotFoundError
@ -87,20 +88,6 @@ def __virtual__():
return False
def _find_key(prefix, pip_list):
'''
Does a case-insensitive match in the pip_list for the desired package.
'''
try:
match = next(
iter(x for x in pip_list if x.lower() == prefix.lower())
)
except StopIteration:
return None
else:
return match
def _fulfills_version_spec(version, version_spec):
'''
Check version number against version specification info and return a
@ -225,23 +212,20 @@ def _check_if_installed(prefix,
ret = {'result': False, 'comment': None}
# If we are not passed a pip list, get one:
if not pip_list:
pip_list = __salt__['pip.list'](prefix, bin_env=bin_env,
user=user, cwd=cwd,
env_vars=env_vars, **kwargs)
# Check if the requested package is already installed.
prefix_realname = _find_key(prefix, pip_list)
pip_list = salt.utils.data.CaseInsensitiveDict(
pip_list or __salt__['pip.list'](prefix, bin_env=bin_env,
user=user, cwd=cwd,
env_vars=env_vars, **kwargs)
)
# If the package was already installed, check
# the ignore_installed and force_reinstall flags
if ignore_installed is False and prefix_realname is not None:
if ignore_installed is False and prefix in pip_list:
if force_reinstall is False and not upgrade:
# Check desired version (if any) against currently-installed
if (
any(version_spec) and
_fulfills_version_spec(pip_list[prefix_realname],
version_spec)
_fulfills_version_spec(pip_list[prefix], version_spec)
) or (not any(version_spec)):
ret['result'] = True
ret['comment'] = ('Python package {0} was already '
@ -261,7 +245,7 @@ def _check_if_installed(prefix,
if 'rc' in spec[1]:
include_rc = True
available_versions = __salt__['pip.list_all_versions'](
prefix_realname, bin_env=bin_env, include_alpha=include_alpha,
prefix, bin_env=bin_env, include_alpha=include_alpha,
include_beta=include_beta, include_rc=include_rc, user=user,
cwd=cwd, index_url=index_url, extra_index_url=extra_index_url)
desired_version = ''
@ -277,9 +261,9 @@ def _check_if_installed(prefix,
ret['comment'] = ('Python package {0} was already '
'installed and\nthe available upgrade '
'doesn\'t fulfills the version '
'requirements'.format(prefix_realname))
'requirements'.format(prefix))
return ret
if _pep440_version_cmp(pip_list[prefix_realname], desired_version) == 0:
if _pep440_version_cmp(pip_list[prefix], desired_version) == 0:
ret['result'] = True
ret['comment'] = ('Python package {0} was already '
'installed'.format(state_pkg_name))
@ -908,10 +892,12 @@ def installed(name,
# Case for packages that are not an URL
if prefix:
pipsearch = __salt__['pip.list'](prefix, bin_env,
user=user, cwd=cwd,
env_vars=env_vars,
**kwargs)
pipsearch = salt.utils.data.CaseInsensitiveDict(
__salt__['pip.list'](prefix, bin_env,
user=user, cwd=cwd,
env_vars=env_vars,
**kwargs)
)
# If we didn't find the package in the system after
# installing it report it
@ -922,12 +908,10 @@ def installed(name,
'\'pip.freeze\'.'.format(pkg)
)
else:
pkg_name = _find_key(prefix, pipsearch)
if pkg_name.lower() in already_installed_packages:
continue
ver = pipsearch[pkg_name]
ret['changes']['{0}=={1}'.format(pkg_name,
ver)] = 'Installed'
if prefix in pipsearch \
and prefix.lower() not in already_installed_packages:
ver = pipsearch[prefix]
ret['changes']['{0}=={1}'.format(prefix, ver)] = 'Installed'
# Case for packages that are an URL
else:
ret['changes']['{0}==???'.format(state_name)] = 'Installed'

View file

@ -457,6 +457,9 @@ def managed(name, ppa=None, **kwargs):
sanitizedkwargs[kwarg])
if precomments != kwargcomments:
break
elif kwarg == 'architectures' and sanitizedkwargs[kwarg]:
if set(sanitizedkwargs[kwarg]) != set(pre[kwarg]):
break
else:
if __grains__['os_family'] in ('RedHat', 'Suse') \
and any(isinstance(x, bool) for x in
@ -476,11 +479,18 @@ def managed(name, ppa=None, **kwargs):
if __opts__['test']:
ret['comment'] = (
'Package repo \'{0}\' will be configured. This may cause pkg '
'Package repo \'{0}\' would be configured. This may cause pkg '
'states to behave differently than stated if this action is '
'repeated without test=True, due to the differences in the '
'configured repositories.'.format(name)
)
if pre:
for kwarg in sanitizedkwargs:
if sanitizedkwargs.get(kwarg) != pre.get(kwarg):
ret['changes'][kwarg] = {'new': sanitizedkwargs.get(kwarg),
'old': pre.get(kwarg)}
else:
ret['changes']['repo'] = name
return ret
# empty file before configure
@ -509,9 +519,8 @@ def managed(name, ppa=None, **kwargs):
if pre:
for kwarg in sanitizedkwargs:
if post.get(kwarg) != pre.get(kwarg):
change = {'new': post[kwarg],
'old': pre.get(kwarg)}
ret['changes'][kwarg] = change
ret['changes'][kwarg] = {'new': post.get(kwarg),
'old': pre.get(kwarg)}
else:
ret['changes'] = {'repo': repo}

View file

@ -13,9 +13,9 @@ import logging
import re
try:
from collections.abc import Mapping
from collections.abc import Mapping, MutableMapping, Sequence
except ImportError:
from collections import Mapping
from collections import Mapping, MutableMapping, Sequence
# Import Salt libs
import salt.utils.dictupdate
@ -24,6 +24,7 @@ import salt.utils.yaml
from salt.defaults import DEFAULT_TARGET_DELIM
from salt.exceptions import SaltException
from salt.utils.decorators.jinja import jinja_filter
from salt.utils.odict import OrderedDict
# Import 3rd-party libs
from salt.ext import six
@ -32,6 +33,87 @@ from salt.ext.six.moves import range # pylint: disable=redefined-builtin
log = logging.getLogger(__name__)
class CaseInsensitiveDict(MutableMapping):
'''
Inspired by requests' case-insensitive dict implementation, but works with
non-string keys as well.
'''
def __init__(self, init=None, **kwargs):
'''
Force internal dict to be ordered to ensure a consistent iteration
order, irrespective of case.
'''
self._data = OrderedDict()
self.update(init or {}, **kwargs)
def __len__(self):
return len(self._data)
def __setitem__(self, key, value):
# Store the case-sensitive key so it is available for dict iteration
self._data[to_lowercase(key)] = (key, value)
def __delitem__(self, key):
del self._data[to_lowercase(key)]
def __getitem__(self, key):
return self._data[to_lowercase(key)][1]
def __iter__(self):
return (item[0] for item in six.itervalues(self._data))
def __eq__(self, rval):
if not isinstance(rval, Mapping):
# Comparing to non-mapping type (e.g. int) is always False
return False
return dict(self.items_lower()) == dict(CaseInsensitiveDict(rval).items_lower())
def __repr__(self):
return repr(dict(six.iteritems(self)))
def items_lower(self):
'''
Returns a generator iterating over keys and values, with the keys all
being lowercase.
'''
return ((key, val[1]) for key, val in six.iteritems(self._data))
def copy(self):
'''
Returns a copy of the object
'''
return CaseInsensitiveDict(six.iteritems(self._data))
def __change_case(data, attr, preserve_dict_class=False):
try:
return getattr(data, attr)()
except AttributeError:
pass
data_type = data.__class__
if isinstance(data, Mapping):
return (data_type if preserve_dict_class else dict)(
(__change_case(key, attr, preserve_dict_class),
__change_case(val, attr, preserve_dict_class))
for key, val in six.iteritems(data)
)
elif isinstance(data, Sequence):
return data_type(
__change_case(item, attr, preserve_dict_class) for item in data)
else:
return data
def to_lowercase(data, preserve_dict_class=False):
return __change_case(data, 'lower', preserve_dict_class)
def to_uppercase(data, preserve_dict_class=False):
return __change_case(data, 'upper', preserve_dict_class)
@jinja_filter('compare_dicts')
def compare_dicts(old=None, new=None):
'''

View file

@ -24,6 +24,7 @@ import shlex
import socket
import ssl
import string
import functools
# Import Salt libs
import salt.utils.files
@ -50,6 +51,7 @@ try:
except ImportError:
HAS_TLDEXTRACT = False
HAS_DIG = salt.utils.path.which('dig') is not None
DIG_OPTIONS = '+search +fail +noall +answer +nocl +nottl'
HAS_DRILL = salt.utils.path.which('drill') is not None
HAS_HOST = salt.utils.path.which('host') is not None
HAS_NSLOOKUP = salt.utils.path.which('nslookup') is not None
@ -274,7 +276,7 @@ def _lookup_dig(name, rdtype, timeout=None, servers=None, secure=None):
:param servers: [] of servers to use
:return: [] of records or False if error
'''
cmd = 'dig +search +fail +noall +answer +noclass +nosplit +nottl -t {0} '.format(rdtype)
cmd = 'dig {0} -t {1} '.format(DIG_OPTIONS, rdtype)
if servers:
cmd += ''.join(['@{0} '.format(srv) for srv in servers])
if timeout is not None:
@ -409,12 +411,13 @@ def _lookup_host(name, rdtype, timeout=None, server=None):
'''
cmd = 'host -t {0} '.format(rdtype)
if server is not None:
cmd += '@{0} '.format(server)
if timeout:
cmd += '-W {0} '.format(int(timeout))
cmd += name
if server is not None:
cmd += ' {0}'.format(server)
cmd = __salt__['cmd.run_all']('{0} {1}'.format(cmd, name), python_shell=False, output_loglevel='quiet')
cmd = __salt__['cmd.run_all'](cmd, python_shell=False, output_loglevel='quiet')
if 'invalid type' in cmd['stderr']:
raise ValueError('Invalid DNS type {}'.format(rdtype))
@ -425,7 +428,8 @@ def _lookup_host(name, rdtype, timeout=None, server=None):
return []
res = []
for line in cmd['stdout'].splitlines():
_stdout = cmd['stdout'] if server is None else cmd['stdout'].split('\n\n')[-1]
for line in _stdout.splitlines():
if rdtype != 'CNAME' and 'is an alias' in line:
continue
line = line.split(' ', 3)[-1]
@ -609,12 +613,15 @@ def lookup(
timeout /= len(servers)
# Inject a wrapper for multi-server behaviour
def _multi_srvr(**res_kwargs):
for server in servers:
s_res = resolver(server=server, **res_kwargs)
if s_res:
return s_res
resolver = _multi_srvr
def _multi_srvr(resolv_func):
@functools.wraps(resolv_func)
def _wrapper(**res_kwargs):
for server in servers:
s_res = resolv_func(server=server, **res_kwargs)
if s_res:
return s_res
return _wrapper
resolver = _multi_srvr(resolver)
if not walk:
name = [name]

View file

@ -11,8 +11,8 @@
from __future__ import absolute_import, unicode_literals, print_function
# Import salt libs
from salt.ext import six
import salt.utils.files
import salt.utils.stringutils
from salt.exceptions import SaltException
@ -85,14 +85,11 @@ class BufferedReader(object):
multiplier = 1
self.__buffered = self.__buffered[self.__chunk_size:]
if six.PY3:
# Data is a byte object in Python 3
# Decode it in order to append to self.__buffered str later
data = self.__file.read(self.__chunk_size * multiplier).decode(
__salt_system_encoding__
)
else:
data = self.__file.read(self.__chunk_size * multiplier)
data = self.__file.read(self.__chunk_size * multiplier)
# Data is a byte object in Python 3
# Decode it in order to append to self.__buffered str later
# Use the salt util in case it's already a string (Windows)
data = salt.utils.stringutils.to_str(data)
if not data:
self.__file.close()

View file

@ -1410,6 +1410,19 @@ class Pygit2(GitProvider):
override_params, cache_root, role
)
def peel(self, obj):
'''
Compatibility function for pygit2.Reference objects. Older versions of
pygit2 use .get_object() to return the object to which the reference
points, while newer versions use .peel(). In pygit2 0.27.4,
.get_object() was removed. This function will try .peel() first and
fall back to .get_object().
'''
try:
return obj.peel()
except AttributeError:
return obj.get_object()
def checkout(self):
'''
Checkout the configured branch/tag
@ -1428,7 +1441,7 @@ class Pygit2(GitProvider):
return None
try:
head_sha = local_head.get_object().hex
head_sha = self.peel(local_head).hex
except AttributeError:
# Shouldn't happen, but just in case a future pygit2 API change
# breaks things, avoid a traceback and log an error.
@ -1477,7 +1490,7 @@ class Pygit2(GitProvider):
try:
if remote_ref in refs:
# Get commit id for the remote ref
oid = self.repo.lookup_reference(remote_ref).get_object().id
oid = self.peel(self.repo.lookup_reference(remote_ref)).id
if local_ref not in refs:
# No local branch for this remote, so create one and point
# it at the commit id of the remote ref
@ -1485,7 +1498,7 @@ class Pygit2(GitProvider):
try:
target_sha = \
self.repo.lookup_reference(remote_ref).get_object().hex
self.peel(self.repo.lookup_reference(remote_ref)).hex
except KeyError:
log.error(
'pygit2 was unable to get SHA for %s in %s remote '
@ -1857,8 +1870,8 @@ class Pygit2(GitProvider):
refs/remotes/origin/
'''
try:
return self.repo.lookup_reference(
'refs/remotes/origin/{0}'.format(ref)).get_object().tree
return self.peel(self.repo.lookup_reference(
'refs/remotes/origin/{0}'.format(ref))).tree
except KeyError:
return None
@ -1867,8 +1880,8 @@ class Pygit2(GitProvider):
Return a pygit2.Tree object matching a tag ref fetched into refs/tags/
'''
try:
return self.repo.lookup_reference(
'refs/tags/{0}'.format(ref)).get_object().tree
return self.peel(self.repo.lookup_reference(
'refs/tags/{0}'.format(ref))).tree
except KeyError:
return None
@ -2993,7 +3006,11 @@ class GitPillar(GitBase):
elif repo.env:
env = repo.env
else:
env = 'base' if repo.branch == repo.base else repo.get_checkout_target()
if repo.branch == repo.base:
env = 'base'
else:
tgt = repo.get_checkout_target()
env = 'base' if tgt == repo.base else tgt
if repo._mountpoint:
if self.link_mountpoint(repo):
self.pillar_dirs[repo.linkdir] = env

View file

@ -1,5 +0,0 @@
# -*- coding: utf-8 -*-
def test(grains):
return {'custom_grain_test': 'itworked' if 'os' in grains else 'itdidntwork'}

View file

@ -11,34 +11,28 @@ class DataModuleTest(ModuleCase):
'''
Validate the data module
'''
def _clear_db(self):
'''
Clear out the database
'''
def setUp(self):
self.run_function('data.clear')
self.addCleanup(self.run_function, 'data.clear')
def test_load_dump(self):
'''
data.load
data.dump
'''
self._clear_db()
self.assertTrue(self.run_function('data.dump', ['{"foo": "bar"}']))
self.assertEqual(self.run_function('data.load'), {'foo': 'bar'})
self._clear_db()
def test_get_update(self):
'''
data.get
data.update
'''
self._clear_db()
self.assertTrue(self.run_function('data.update', ['spam', 'eggs']))
self.assertEqual(self.run_function('data.get', ['spam']), 'eggs')
self.assertTrue(self.run_function('data.update', ['unladen', 'swallow']))
self.assertEqual(self.run_function('data.get', ['["spam", "unladen"]']), ['eggs', 'swallow'])
self._clear_db()
self.assertEqual(self.run_function('data.get', [["spam", "unladen"]]), ['eggs', 'swallow'])
def test_cas_update(self):
'''
@ -46,7 +40,6 @@ class DataModuleTest(ModuleCase):
data.cas
data.get
'''
self._clear_db()
self.assertTrue(self.run_function('data.update', ['spam', 'eggs']))
self.assertTrue(self.run_function('data.cas', ['spam', 'green', 'eggs']))
self.assertEqual(self.run_function('data.get', ['spam']), 'green')

View file

@ -15,6 +15,7 @@ from tests.support.helpers import skip_if_binaries_missing
# Import salt libs
import salt.utils.files
import salt.utils.platform
# Import 3rd-party libs
from tornado.httpclient import HTTPClient
@ -70,7 +71,10 @@ class SSHModuleTest(ModuleCase):
shutil.copyfile(
os.path.join(FILES, 'ssh', 'authorized_keys'),
AUTHORIZED_KEYS)
ret = self.run_function('ssh.auth_keys', ['root', AUTHORIZED_KEYS])
user = 'root'
if salt.utils.platform.is_windows():
user = 'Administrator'
ret = self.run_function('ssh.auth_keys', [user, AUTHORIZED_KEYS])
self.assertEqual(len(list(ret.items())), 1) # exactly one key is found
key_data = list(ret.items())[0][1]
try:

View file

@ -22,8 +22,12 @@ class WinDNSTest(ModuleCase):
'''
Test add and removing a dns server
'''
# Get a list of interfaces on the system
interfaces = self.run_function('network.interfaces_names')
skipIf(interfaces.count == 0, 'This test requires a network interface')
interface = interfaces[0]
dns = '8.8.8.8'
interface = 'Ethernet'
# add dns server
self.assertTrue(self.run_function('win_dns_client.add_dns', [dns, interface], index=42))

View file

@ -100,6 +100,11 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
)
response_obj = salt.utils.json.loads(response.body)
self.assertEqual(len(response_obj['return']), 1)
# If --proxy is set, it will cause an extra minion_id to be in the
# response. Since there's not a great way to know if the test
# runner's proxy minion is running, and we're not testing proxy
# minions here anyway, just remove it from the response.
response_obj['return'][0].pop('proxytest', None)
self.assertEqual(response_obj['return'][0], {'minion': True, 'sub_minion': True})
def test_simple_local_post_no_tgt(self):
@ -142,6 +147,11 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
)
response_obj = salt.utils.json.loads(response.body)
self.assertEqual(len(response_obj['return']), 1)
# If --proxy is set, it will cause an extra minion_id to be in the
# response. Since there's not a great way to know if the test
# runner's proxy minion is running, and we're not testing proxy
# minions here anyway, just remove it from the response.
response_obj['return'][0].pop('proxytest', None)
self.assertEqual(response_obj['return'][0], {'minion': True, 'sub_minion': True})
def test_simple_local_post_invalid_request(self):
@ -175,6 +185,14 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
response_obj = salt.utils.json.loads(response.body)
ret = response_obj['return']
ret[0]['minions'] = sorted(ret[0]['minions'])
try:
# If --proxy is set, it will cause an extra minion_id to be in the
# response. Since there's not a great way to know if the test
# runner's proxy minion is running, and we're not testing proxy
# minions here anyway, just remove it from the response.
ret[0]['minions'].remove('proxytest')
except ValueError:
pass
# TODO: verify pub function? Maybe look at how we test the publisher
self.assertEqual(len(ret), 1)
@ -201,6 +219,15 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
ret = response_obj['return']
ret[0]['minions'] = sorted(ret[0]['minions'])
ret[1]['minions'] = sorted(ret[1]['minions'])
try:
# If --proxy is set, it will cause an extra minion_id to be in the
# response. Since there's not a great way to know if the test
# runner's proxy minion is running, and we're not testing proxy
# minions here anyway, just remove it from the response.
ret[0]['minions'].remove('proxytest')
ret[1]['minions'].remove('proxytest')
except ValueError:
pass
self.assertEqual(len(ret), 2)
self.assertIn('jid', ret[0])
@ -235,6 +262,15 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
ret = response_obj['return']
ret[0]['minions'] = sorted(ret[0]['minions'])
ret[1]['minions'] = sorted(ret[1]['minions'])
try:
# If --proxy is set, it will cause an extra minion_id to be in the
# response. Since there's not a great way to know if the test
# runner's proxy minion is running, and we're not testing proxy
# minions here anyway, just remove it from the response.
ret[0]['minions'].remove('proxytest')
ret[1]['minions'].remove('proxytest')
except ValueError:
pass
self.assertEqual(len(ret), 3) # make sure we got 3 responses
self.assertIn('jid', ret[0]) # the first 2 are regular returns
@ -279,6 +315,13 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
request_timeout=30,
)
response_obj = salt.utils.json.loads(response.body)
self.application.opts['order_masters'] = []
self.application.opts['syndic_wait'] = 5
# If --proxy is set, it will cause an extra minion_id to be in the
# response. Since there's not a great way to know if the test runner's
# proxy minion is running, and we're not testing proxy minions here
# anyway, just remove it from the response.
response_obj[0]['return'].pop('proxytest', None)
self.assertEqual(response_obj['return'], [{'minion': True, 'sub_minion': True}])
# runner tests
@ -296,6 +339,14 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
)
response_obj = salt.utils.json.loads(response.body)
self.assertEqual(len(response_obj['return']), 1)
try:
# If --proxy is set, it will cause an extra minion_id to be in the
# response. Since there's not a great way to know if the test
# runner's proxy minion is running, and we're not testing proxy
# minions here anyway, just remove it from the response.
response_obj['return'][0].remove('proxytest')
except ValueError:
pass
self.assertEqual(sorted(response_obj['return'][0]), sorted(['minion', 'sub_minion']))
# runner_async tests

View file

@ -36,6 +36,11 @@ class NetapiClientTest(TestCase):
low.update(self.eauth_creds)
ret = self.netapi.run(low)
# If --proxy is set, it will cause an extra minion_id to be in the
# response. Since there's not a great way to know if the test
# runner's proxy minion is running, and we're not testing proxy
# minions here anyway, just remove it from the response.
ret.pop('proxytest', None)
self.assertEqual(ret, {'minion': True, 'sub_minion': True})
def test_local_batch(self):
@ -59,6 +64,14 @@ class NetapiClientTest(TestCase):
self.assertIn('jid', ret)
ret.pop('jid', None)
ret['minions'] = sorted(ret['minions'])
try:
# If --proxy is set, it will cause an extra minion_id to be in the
# response. Since there's not a great way to know if the test
# runner's proxy minion is running, and we're not testing proxy
# minions here anyway, just remove it from the response.
ret['minions'].remove('proxytest')
except ValueError:
pass
self.assertEqual(ret, {'minions': sorted(['minion', 'sub_minion'])})
def test_wheel(self):

View file

@ -5,6 +5,7 @@ tests for pkgrepo states
# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals
import os
# Import Salt Testing libs
from tests.support.case import ModuleCase
@ -12,7 +13,7 @@ from tests.support.mixins import SaltReturnAssertsMixin
from tests.support.unit import skipIf
from tests.support.helpers import (
destructiveTest,
requires_system_grains
requires_system_grains,
)
# Import Salt libs
@ -128,3 +129,137 @@ class PkgrepoTest(ModuleCase, SaltReturnAssertsMixin):
finally:
# Clean up
self.run_state('pkgrepo.absent', name=kwargs['name'])
@requires_system_grains
def test_pkgrepo_04_apt_with_architectures(self, grains):
'''
Test managing a repo with architectures specified
'''
if grains['os_family'].lower() != 'debian':
self.skipTest('APT-only test')
name = 'deb {{arch}}http://foo.com/bar/latest {oscodename} main'.format(oscodename=grains['oscodename'])
def _get_arch(arch):
return '[arch={0}] '.format(arch) if arch else ''
def _run(arch='', test=False):
ret = self.run_state(
'pkgrepo.managed',
name=name.format(arch=_get_arch(arch)),
file=fn_,
refresh=False,
test=test)
return ret[next(iter(ret))]
fn_ = salt.utils.files.mkstemp(dir='/etc/apt/sources.list.d', suffix='.list')
try:
# Run with test=True
ret = _run(test=True)
assert ret['changes'] == {'repo': name.format(arch='')}, ret['changes']
assert 'would be' in ret['comment'], ret['comment']
assert ret['result'] is None, ret['result']
# Run for real
ret = _run()
assert ret['changes'] == {'repo': name.format(arch='')}, ret['changes']
assert ret['comment'].startswith('Configured'), ret['comment']
assert ret['result'] is True, ret['result']
# Run again with test=True, should exit with no changes and a True
# result.
ret = _run(test=True)
assert not ret['changes'], ret['changes']
assert 'already' in ret['comment'], ret['comment']
assert ret['result'] is True, ret['result']
# Run for real again, results should be the same as above (i.e. we
# should never get to the point where we exit with a None result).
ret = _run()
assert not ret['changes'], ret['changes']
assert 'already' in ret['comment'], ret['comment']
assert ret['result'] is True, ret['result']
expected_changes = {
'line': {
'new': name.format(arch=_get_arch('amd64')),
'old': name.format(arch=''),
},
'architectures': {
'new': ['amd64'],
'old': [],
},
}
# Run with test=True and the architecture set. We should get a None
# result with some expected changes.
ret = _run(arch='amd64', test=True)
assert ret['changes'] == expected_changes, ret['changes']
assert 'would be' in ret['comment'], ret['comment']
assert ret['result'] is None, ret['result']
# Run for real, with the architecture set. We should get a True
# result with the same changes.
ret = _run(arch='amd64')
assert ret['changes'] == expected_changes, ret['changes']
assert ret['comment'].startswith('Configured'), ret['comment']
assert ret['result'] is True, ret['result']
# Run again with test=True, should exit with no changes and a True
# result.
ret = _run(arch='amd64', test=True)
assert not ret['changes'], ret['changes']
assert 'already' in ret['comment'], ret['comment']
assert ret['result'] is True, ret['result']
# Run for real again, results should be the same as above (i.e. we
# should never get to the point where we exit with a None result).
ret = _run(arch='amd64')
assert not ret['changes'], ret['changes']
assert 'already' in ret['comment'], ret['comment']
assert ret['result'] is True, ret['result']
expected_changes = {
'line': {
'new': name.format(arch=''),
'old': name.format(arch=_get_arch('amd64')),
},
'architectures': {
'new': [],
'old': ['amd64'],
},
}
# Run with test=True and the architecture set back to the original
# value. We should get a None result with some expected changes.
ret = _run(test=True)
assert ret['changes'] == expected_changes, ret['changes']
assert 'would be' in ret['comment'], ret['comment']
assert ret['result'] is None, ret['result']
# Run for real, with the architecture set. We should get a True
# result with the same changes.
ret = _run()
assert ret['changes'] == expected_changes, ret['changes']
assert ret['comment'].startswith('Configured'), ret['comment']
assert ret['result'] is True, ret['result']
# Run again with test=True, should exit with no changes and a True
# result.
ret = _run(test=True)
assert not ret['changes'], ret['changes']
assert 'already' in ret['comment'], ret['comment']
assert ret['result'] is True, ret['result']
# Run for real again, results should be the same as above (i.e. we
# should never get to the point where we exit with a None result).
ret = _run()
assert not ret['changes'], ret['changes']
assert 'already' in ret['comment'], ret['comment']
assert ret['result'] is True, ret['result']
finally:
try:
os.remove(fn_)
except OSError:
pass

View file

@ -275,7 +275,10 @@ class ShellTestCase(TestCase, AdaptedConfigurationTestCaseMixin):
popen_kwargs['cwd'] = os.getcwd()
if 'env' not in popen_kwargs:
popen_kwargs['env'] = os.environ.copy()
popen_kwargs['env'][b'PYTHONPATH'] = os.getcwd().encode()
if sys.version_info[0] < 3:
popen_kwargs['env'][b'PYTHONPATH'] = os.getcwd().encode()
else:
popen_kwargs['env']['PYTHONPATH'] = os.getcwd()
else:
cmd = 'PYTHONPATH='
python_path = os.environ.get('PYTHONPATH', None)

View file

@ -1 +1,70 @@
# -*- coding: utf-8 -*-
'''
tests.unit.cloud
~~~~~~~~~~~~~~~~
'''
from __future__ import absolute_import, print_function, unicode_literals
from tests.support.unit import TestCase
import salt.cloud
class CloudTest(TestCase):
def test_vm_config_merger(self):
'''
Validate the vm's config is generated correctly.
https://github.com/saltstack/salt/issues/49226
'''
main = {
'minion': {'master': '172.31.39.213'},
'log_file': 'var/log/salt/cloud.log',
'pool_size': 10
}
provider = {
'private_key': 'dwoz.pem',
'grains': {'foo1': 'bar', 'foo2': 'bang'},
'availability_zone': 'us-west-2b',
'driver': 'ec2',
'ssh_interface': 'private_ips',
'ssh_username': 'admin',
'location': 'us-west-2'
}
profile = {
'profile': 'default',
'grains': {'meh2': 'bar', 'meh1': 'foo'},
'provider': 'ec2-default:ec2',
'ssh_username': 'admin',
'image': 'ami-0a1fbca0e5b419fd1',
'size': 't2.micro'
}
vm = salt.cloud.Cloud.vm_config(
'test_vm',
main,
provider,
profile,
{}
)
self.assertEqual({
'minion': {'master': '172.31.39.213'},
'log_file': 'var/log/salt/cloud.log',
'pool_size': 10,
'private_key': 'dwoz.pem',
'grains': {
'foo1': 'bar',
'foo2': 'bang',
'meh2': 'bar',
'meh1': 'foo',
},
'availability_zone': 'us-west-2b',
'driver': 'ec2',
'ssh_interface': 'private_ips',
'ssh_username': 'admin',
'location': 'us-west-2',
'profile': 'default',
'provider': 'ec2-default:ec2',
'image': 'ami-0a1fbca0e5b419fd1',
'size': 't2.micro',
'name': 'test_vm',
}, vm)

View file

@ -1 +1,32 @@
# -*- coding: utf-8 -*-
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
# Import Salt Testing Libs
from tests.support.unit import TestCase, skipIf
import tests.support.mock as mock
import salt.config
import salt.syspaths
class ConfigTest(TestCase):
def test_validate_bad_pillar_roots(self):
expected = salt.config._expand_glob_path(
[salt.syspaths.BASE_PILLAR_ROOTS_DIR]
)
with mock.patch('salt.config._normalize_roots') as mk:
ret = salt.config._validate_pillar_roots(None)
assert not mk.called
assert ret == {'base': expected}
def test_validate_bad_file_roots(self):
expected = salt.config._expand_glob_path(
[salt.syspaths.BASE_FILE_ROOTS_DIR]
)
with mock.patch('salt.config._normalize_roots') as mk:
ret = salt.config._validate_file_roots(None)
assert not mk.called
assert ret == {'base': expected}

View file

@ -179,3 +179,20 @@ class RootsTest(TestCase, AdaptedConfigurationTestCaseMixin, LoaderModuleMockMix
finally:
if self.test_symlink_list_file_roots:
self.opts['file_roots'] = orig_file_roots
def test_dynamic_file_roots(self):
dyn_root_dir = tempfile.mkdtemp(dir=TMP)
top_sls = os.path.join(dyn_root_dir, 'top.sls')
with salt.utils.files.fopen(top_sls, 'w') as fp_:
fp_.write("{{saltenv}}:\n '*':\n - dynamo\n")
dynamo_sls = os.path.join(dyn_root_dir, 'dynamo.sls')
with salt.utils.files.fopen(dynamo_sls, 'w') as fp_:
fp_.write("foo:\n test.nop\n")
opts = {'file_roots': copy.copy(self.opts['file_roots'])}
opts['file_roots']['__env__'] = [dyn_root_dir]
with patch.dict(roots.__opts__, opts):
ret1 = roots.find_file('dynamo.sls', 'dyn')
ret2 = roots.file_list({'saltenv': 'dyn'})
self.assertEqual('dynamo.sls', ret1['rel'])
self.assertIn('top.sls', ret2)
self.assertIn('dynamo.sls', ret2)

View file

@ -99,6 +99,21 @@ class MySQLTestCase(TestCase, LoaderModuleMockMixin):
password='BLUECOW'
)
with patch.object(mysql, 'version', return_value='10.1.38-MariaDB'):
self._test_call(mysql.user_exists,
{'sql': ('SELECT User,Host FROM mysql.user WHERE '
'User = %(user)s AND Host = %(host)s AND '
'Password = PASSWORD(%(password)s)'),
'sql_args': {'host': 'localhost',
'password': 'BLUECOW',
'user': 'mytestuser'
}
},
user='mytestuser',
host='localhost',
password='BLUECOW'
)
with patch.object(mysql, 'version', return_value='8.0.11'):
self._test_call(mysql.user_exists,
{'sql': ('SELECT User,Host FROM mysql.user WHERE '
@ -125,6 +140,19 @@ class MySQLTestCase(TestCase, LoaderModuleMockMixin):
password='BLUECOW'
)
with patch.object(mysql, 'version', return_value='10.2.21-MariaDB'):
self._test_call(mysql.user_exists,
{'sql': ('SELECT User,Host FROM mysql.user WHERE '
'User = %(user)s AND Host = %(host)s'),
'sql_args': {'host': 'localhost',
'user': 'mytestuser'
}
},
user='mytestuser',
host='localhost',
password='BLUECOW'
)
# test_user_create_when_user_exists(self):
# ensure we don't try to create a user when one already exists
# mock the version of MySQL
@ -187,7 +215,11 @@ class MySQLTestCase(TestCase, LoaderModuleMockMixin):
mysql.user_chpass('testuser', password='BLUECOW')
calls = (
call().cursor().execute(
"ALTER USER 'testuser'@'localhost' IDENTIFIED BY 'BLUECOW';"
"ALTER USER %(user)s@%(host)s IDENTIFIED BY %(password)s;",
{'password': 'BLUECOW',
'user': 'testuser',
'host': 'localhost',
}
),
call().cursor().execute('FLUSH PRIVILEGES;'),
)

View file

@ -3,18 +3,15 @@
:codeauthor: Jayesh Kariya <jayeshk@saltstack.com>
'''
# Import Python Libs
from __future__ import absolute_import, unicode_literals, print_function
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase, skipIf
from tests.support.mock import (
MagicMock,
patch
)
from __future__ import absolute_import, print_function, unicode_literals
# Import Salt Libs
import salt.modules.win_timezone as win_timezone
from salt.exceptions import CommandExecutionError
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.mock import MagicMock, patch
from tests.support.unit import TestCase, skipIf
@skipIf(not win_timezone.HAS_PYTZ, 'This test requires pytz')
@ -25,20 +22,36 @@ class WinTimezoneTestCase(TestCase, LoaderModuleMockMixin):
def setup_loader_modules(self):
return {win_timezone: {}}
# 'get_zone' function tests: 1
# 'get_zone' function tests: 3
def test_get_zone(self):
'''
Test if it get current timezone (i.e. Asia/Calcutta)
'''
mock_read = MagicMock(side_effect=[{'vdata': 'India Standard Time'},
{'vdata': 'Indian Standard Time'}])
mock_read_ok = MagicMock(return_value={'pid': 78,
'retcode': 0,
'stderr': '',
'stdout': 'India Standard Time'})
with patch.dict(win_timezone.__utils__, {'reg.read_value': mock_read}):
with patch.dict(win_timezone.__salt__, {'cmd.run_all': mock_read_ok}):
self.assertEqual(win_timezone.get_zone(), 'Asia/Calcutta')
mock_read_error = MagicMock(return_value={'pid': 78,
'retcode': 0,
'stderr': '',
'stdout': 'Indian Standard Time'})
with patch.dict(win_timezone.__salt__, {'cmd.run_all': mock_read_error}):
self.assertEqual(win_timezone.get_zone(), 'Unknown')
mock_read_fatal = MagicMock(return_value={'pid': 78,
'retcode': 1,
'stderr': '',
'stdout': ''})
with patch.dict(win_timezone.__salt__, {'cmd.run_all': mock_read_fatal}):
self.assertRaises(CommandExecutionError, win_timezone.get_zone)
# 'get_offset' function tests: 1
def test_get_offset(self):
@ -49,10 +62,12 @@ class WinTimezoneTestCase(TestCase, LoaderModuleMockMixin):
# New Delhi\nIndia Standard Time')
# mock_cmd = MagicMock(side_effect=['India Standard Time', time])
# with patch.dict(win_timezone.__salt__, {'cmd.run': mock_cmd}):
mock_read = MagicMock(return_value={'pid': 78,
'retcode': 0,
'stderr': '',
'stdout': 'India Standard Time'})
mock_read = MagicMock(return_value={'vdata': 'India Standard Time'})
with patch.dict(win_timezone.__utils__, {'reg.read_value': mock_read}):
with patch.dict(win_timezone.__salt__, {'cmd.run_all': mock_read}):
self.assertEqual(win_timezone.get_offset(), '+0530')
# 'get_zonecode' function tests: 1
@ -61,9 +76,12 @@ class WinTimezoneTestCase(TestCase, LoaderModuleMockMixin):
'''
Test if it get current timezone (i.e. PST, MDT, etc)
'''
mock_read = MagicMock(return_value={'vdata': 'India Standard Time'})
mock_read = MagicMock(return_value={'pid': 78,
'retcode': 0,
'stderr': '',
'stdout': 'India Standard Time'})
with patch.dict(win_timezone.__utils__, {'reg.read_value': mock_read}):
with patch.dict(win_timezone.__salt__, {'cmd.run_all': mock_read}):
self.assertEqual(win_timezone.get_zonecode(), 'IST')
# 'set_zone' function tests: 1
@ -72,14 +90,17 @@ class WinTimezoneTestCase(TestCase, LoaderModuleMockMixin):
'''
Test if it unlinks, then symlinks /etc/localtime to the set timezone.
'''
mock_cmd = MagicMock(return_value={'pid': 78,
'retcode': 0,
'stderr': '',
'stdout': ''})
mock_read = MagicMock(return_value={'vdata': 'India Standard Time'})
mock_write = MagicMock(return_value={'pid': 78,
'retcode': 0,
'stderr': '',
'stdout': ''})
mock_read = MagicMock(return_value={'pid': 78,
'retcode': 0,
'stderr': '',
'stdout': 'India Standard Time'})
with patch.dict(win_timezone.__salt__, {'cmd.run_all': mock_cmd}), \
patch.dict(win_timezone.__utils__, {'reg.read_value': mock_read}):
with patch.dict(win_timezone.__salt__, {'cmd.run_all': mock_write}), \
patch.dict(win_timezone.__salt__, {'cmd.run_all': mock_read}):
self.assertTrue(win_timezone.set_zone('Asia/Calcutta'))
@ -91,9 +112,12 @@ class WinTimezoneTestCase(TestCase, LoaderModuleMockMixin):
the one set in /etc/localtime. Returns True if they match,
and False if not. Mostly useful for running state checks.
'''
mock_read = MagicMock(return_value={'vdata': 'India Standard Time'})
mock_read = MagicMock(return_value={'pid': 78,
'retcode': 0,
'stderr': '',
'stdout': 'India Standard Time'})
with patch.dict(win_timezone.__utils__, {'reg.read_value': mock_read}):
with patch.dict(win_timezone.__salt__, {'cmd.run_all': mock_read}):
self.assertTrue(win_timezone.zone_compare('Asia/Calcutta'))
# 'get_hwclock' function tests: 1

View file

@ -803,10 +803,15 @@ class TestFileState(TestCase, LoaderModuleMockMixin):
with patch.object(os.path, 'exists', mock_t):
with patch.dict(filestate.__opts__, {'test': True}):
ret.update({'comment': comt})
self.assertDictEqual(filestate.managed
(name, user=user,
group=group,
mode=400), ret)
if salt.utils.is_windows():
self.assertDictEqual(filestate.managed
(name, user=user,
group=group), ret)
else:
self.assertDictEqual(filestate.managed
(name, user=user,
group=group,
mode=400), ret)
# 'directory' function tests: 1

View file

@ -60,6 +60,7 @@ class GitTestCase(TestCase, LoaderModuleMockMixin):
git_diff = Mock()
dunder_salt = {
'git.current_branch': MagicMock(return_value=branches[0]),
'git.config_get_regexp': MagicMock(return_value={}),
'git.diff': git_diff,
'git.fetch': MagicMock(return_value={}),
'git.is_worktree': MagicMock(return_value=False),

View file

@ -577,6 +577,7 @@ class PubServerChannel(TestCase, AdaptedConfigurationTestCaseMixin):
executor.submit(self._send_small, opts, 3)
executor.submit(self._send_large, opts, 4)
expect = ['{}-{}'.format(a, b) for a in range(10) for b in (1, 2, 3, 4)]
time.sleep(0.1)
server_channel.publish({'tgt_type': 'glob', 'tgt': '*', 'stop': True})
gather.join()
server_channel.pub_close()

View file

@ -15,6 +15,8 @@ from salt.utils.odict import OrderedDict
import salt.utils.dns
from salt.utils.dns import _to_port, _tree, _weighted_order, _data2rec, _data2rec_group
from salt.utils.dns import _lookup_gai, _lookup_dig, _lookup_drill, _lookup_host, _lookup_nslookup
from salt.utils.dns import lookup
import salt.modules.cmdmod
# Testing
from tests.support.unit import skipIf, TestCase
@ -296,6 +298,52 @@ class DNSlookupsCase(TestCase):
msg='Error parsing DNSSEC\'d {0} returns'.format(rec_t)
)
def test_lookup_with_servers(self):
rights = {
'A': [
'Name:\tmocksrvr.example.com\nAddress: 10.1.1.1',
'Name:\tmocksrvr.example.com\nAddress: 10.1.1.1\n'
'Name:\tweb.example.com\nAddress: 10.2.2.2\n'
'Name:\tweb.example.com\nAddress: 10.3.3.3'
],
'AAAA': [
'mocksrvr.example.com\thas AAAA address 2a00:a00:b01:c02:d03:e04:f05:111',
'mocksrvr.example.com\tcanonical name = web.example.com.\n'
'web.example.com\thas AAAA address 2a00:a00:b01:c02:d03:e04:f05:111\n'
'web.example.com\thas AAAA address 2a00:a00:b01:c02:d03:e04:f05:222\n'
'web.example.com\thas AAAA address 2a00:a00:b01:c02:d03:e04:f05:333'
],
'CNAME': [
'mocksrvr.example.com\tcanonical name = web.example.com.'
],
'MX': [
'example.com\tmail exchanger = 10 mx1.example.com.',
'example.com\tmail exchanger = 10 mx1.example.com.\n'
'example.com\tmail exchanger = 20 mx2.example.eu.\n'
'example.com\tmail exchanger = 30 mx3.example.nl.'
],
'TXT': [
'example.com\ttext = "v=spf1 a include:_spf4.example.com include:mail.example.eu ip4:10.0.0.0/8 ip6:2a00:a00:b01::/48 ~all"'
]
}
for rec_t, tests in rights.items():
with self._mock_cmd_ret([dict([('stdout', dres)]) for dres in tests]):
for test_res in self.RESULTS[rec_t]:
if rec_t in ('A', 'AAAA', 'CNAME'):
rec = 'mocksrvr.example.com'
else:
rec = 'example.com'
self.assertEqual(
lookup(rec, rec_t, method='nslookup', servers='8.8.8.8'), test_res,
)
@skipIf(not salt.utils.dns.HAS_DIG, 'dig is not available')
def test_dig_options(self):
cmd = 'dig {0} -v'.format(salt.utils.dns.DIG_OPTIONS)
cmd = salt.modules.cmdmod.retcode(cmd, python_shell=False, output_loglevel='quiet')
self.assertEqual(cmd, 0)
def test_dig(self):
wrong_type = {'retcode': 0, 'stderr': ';; Warning, ignoring invalid type ABC'}