mirror of
https://github.com/saltstack/salt.git
synced 2025-04-16 17:50:20 +00:00
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:
commit
6c99cb161f
46 changed files with 6818 additions and 229 deletions
|
@ -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
|
@ -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
|
||||
=====================================
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
32
pkg/osx/pkg-resources/welcome_py2.rtf
Normal file
32
pkg/osx/pkg-resources/welcome_py2.rtf
Normal 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.}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'] = (
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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):
|
||||
'''
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
def test(grains):
|
||||
return {'custom_grain_test': 'itworked' if 'os' in grains else 'itdidntwork'}
|
|
@ -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')
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;'),
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue