mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 00:55:19 +00:00
Merge branch '2018.3' into update-tests
This commit is contained in:
commit
36a4a1af49
109
.ci/lint
109
.ci/lint
@ -3,7 +3,7 @@ pipeline {
|
||||
options {
|
||||
timestamps()
|
||||
ansiColor('xterm')
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
timeout(time: 3, unit: 'HOURS')
|
||||
}
|
||||
environment {
|
||||
PYENV_ROOT = "/usr/local/pyenv"
|
||||
@ -14,7 +14,7 @@ pipeline {
|
||||
stage('github-pending') {
|
||||
steps {
|
||||
githubNotify credentialsId: 'test-jenkins-credentials',
|
||||
description: 'Testing lint...',
|
||||
description: 'Python lint on changes...',
|
||||
status: 'PENDING',
|
||||
context: "jenkins/pr/lint"
|
||||
}
|
||||
@ -24,12 +24,14 @@ pipeline {
|
||||
sh '''
|
||||
# Need -M to detect renames otherwise they are reported as Delete and Add, need -C to detect copies, -C includes -M
|
||||
# -M is on by default in git 2.9+
|
||||
git diff --name-status -l99999 -C "origin/$CHANGE_TARGET" "origin/$BRANCH_NAME" > file-list-status.log
|
||||
git diff --name-status -l99999 -C "origin/$CHANGE_TARGET" > file-list-status.log
|
||||
# the -l increase the search limit, lets use awk so we do not need to repeat the search above.
|
||||
gawk 'BEGIN {FS="\\t"} {if ($1 != "D") {print $NF}}' file-list-status.log > file-list-changed.log
|
||||
gawk 'BEGIN {FS="\\t"} {if ($1 == "D") {print $NF}}' file-list-status.log > file-list-deleted.log
|
||||
(git diff --name-status -l99999 -C "origin/$CHANGE_TARGET";echo "---";git diff --name-status -l99999 -C "origin/$BRANCH_NAME";printenv|grep -E '=[0-9a-z]{40,}+$|COMMIT=|BRANCH') > file-list-experiment.log
|
||||
(git diff --name-status -l99999 -C "origin/$CHANGE_TARGET" "origin/$BRANCH_NAME";echo "---";git diff --name-status -l99999 -C "origin/$BRANCH_NAME";printenv|grep -E '=[0-9a-z]{40,}+$|COMMIT=|BRANCH') > file-list-experiment.log
|
||||
touch pylint-report-salt.log pylint-report-tests.log
|
||||
echo 254 > pylint-salt-chg.exit # assume failure
|
||||
echo 254 > pylint-tests-chg.exit # assume failure
|
||||
eval "$(pyenv init -)"
|
||||
pyenv --version
|
||||
pyenv install --skip-existing 2.7.14
|
||||
@ -41,52 +43,117 @@ pipeline {
|
||||
archiveArtifacts artifacts: 'file-list-status.log,file-list-changed.log,file-list-deleted.log,file-list-experiment.log'
|
||||
}
|
||||
}
|
||||
stage('linting') {
|
||||
failFast false
|
||||
stage('linting chg') {
|
||||
parallel {
|
||||
stage('salt linting') {
|
||||
stage('lint salt') {
|
||||
when {
|
||||
expression { return readFile('file-list-changed.log') =~ /(?i)(^|\n)(salt\/.*\.py|setup\.py)\n/ }
|
||||
}
|
||||
steps {
|
||||
sh '''
|
||||
eval "$(pyenv init - --no-rehash)"
|
||||
grep -Ei '^salt/.*\\.py$|^setup\\.py$' file-list-changed.log | xargs -r '--delimiter=\\n' tox -e pylint-salt | tee pylint-report-salt.log
|
||||
# tee makes the exit/return code always 0
|
||||
grep -Ei '^salt/.*\\.py$|^setup\\.py$' file-list-changed.log | (xargs -r '--delimiter=\\n' tox -e pylint-salt;echo "$?" > pylint-salt-chg.exit) | tee pylint-report-salt.log
|
||||
# remove color escape coding
|
||||
sed -ri 's/\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g' pylint-report-salt.log
|
||||
read rc_exit < pylint-salt-chg.exit
|
||||
exit "$rc_exit"
|
||||
'''
|
||||
archiveArtifacts artifacts: 'pylint-report-salt.log'
|
||||
}
|
||||
}
|
||||
stage('test linting') {
|
||||
stage('lint test') {
|
||||
when {
|
||||
expression { return readFile('file-list-changed.log') =~ /(?i)(^|\n)tests\/.*\.py\n/ }
|
||||
}
|
||||
steps {
|
||||
sh '''
|
||||
eval "$(pyenv init - --no-rehash)"
|
||||
grep -Ei '^tests/.*\\.py$' file-list-changed.log | xargs -r '--delimiter=\\n' tox -e pylint-tests | tee pylint-report-tests.log
|
||||
# tee makes the exit/return code always 0
|
||||
grep -Ei '^tests/.*\\.py$' file-list-changed.log | (xargs -r '--delimiter=\\n' tox -e pylint-tests;echo "$?" > pylint-tests-chg.exit) | tee pylint-report-tests.log
|
||||
# remove color escape coding
|
||||
sed -ri 's/\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g' pylint-report-tests.log
|
||||
read rc_exit < pylint-tests-chg.exit
|
||||
exit "$rc_exit"
|
||||
'''
|
||||
archiveArtifacts artifacts: 'pylint-report-tests.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
step([$class: 'WarningsPublisher',
|
||||
parserConfigurations: [[
|
||||
parserName: 'PyLint',
|
||||
pattern: 'pylint-report*chg.log'
|
||||
]],
|
||||
failedTotalAll: '0',
|
||||
useDeltaValues: false,
|
||||
canRunOnFailed: true,
|
||||
usePreviousBuildAsReference: true
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('lint all') {
|
||||
// perform a full linit if this is a merge forward and the change only lint passed.
|
||||
when {
|
||||
expression { return params.BRANCH_NAME =~ /(?i)^merge-/ && readFile('file-list-changed.log') =~ /^0/ }
|
||||
}
|
||||
parallel {
|
||||
stage('begin') {
|
||||
steps {
|
||||
githubNotify credentialsId: 'test-jenkins-credentials',
|
||||
description: 'Python lint on everything...',
|
||||
status: 'PENDING',
|
||||
context: "jenkins/pr/lint"
|
||||
}
|
||||
}
|
||||
stage('lint salt') {
|
||||
steps {
|
||||
sh '''
|
||||
eval "$(pyenv init - --no-rehash)"
|
||||
(tox -e pylint-salt ; echo "$?" > pylint-salt-full.exit) | tee pylint-report-salt-full.log
|
||||
# remove color escape coding
|
||||
sed -ri 's/\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g' pylint-report-salt-full.log
|
||||
read rc_exit < pylint-salt-full.exit
|
||||
exit "$rc_exit"
|
||||
'''
|
||||
archiveArtifacts artifacts: 'pylint-report-salt-full.log'
|
||||
}
|
||||
}
|
||||
stage('lint test') {
|
||||
steps {
|
||||
sh '''
|
||||
eval "$(pyenv init - --no-rehash)"
|
||||
(tox -e pylint-tests ; echo "$?" > pylint-tests-full.exit) | tee pylint-report-tests-full.log
|
||||
# remove color escape coding
|
||||
sed -ri 's/\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g' pylint-report-tests-full.log
|
||||
read rc_exit < pylint-tests-full.exit
|
||||
exit "$rc_exit"
|
||||
'''
|
||||
archiveArtifacts artifacts: 'pylint-report-tests-full.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
step([$class: 'WarningsPublisher',
|
||||
parserConfigurations: [[
|
||||
parserName: 'PyLint',
|
||||
pattern: 'pylint-report*full.log'
|
||||
]],
|
||||
failedTotalAll: '0',
|
||||
useDeltaValues: false,
|
||||
canRunOnFailed: true,
|
||||
usePreviousBuildAsReference: true
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
step([$class: 'WarningsPublisher',
|
||||
parserConfigurations: [[
|
||||
parserName: 'PyLint',
|
||||
pattern: 'pylint-report*.log'
|
||||
]],
|
||||
failedTotalAll: '0',
|
||||
useDeltaValues: false,
|
||||
canRunOnFailed: true,
|
||||
usePreviousBuildAsReference: true
|
||||
])
|
||||
cleanWs()
|
||||
}
|
||||
success {
|
||||
@ -97,7 +164,7 @@ pipeline {
|
||||
}
|
||||
failure {
|
||||
githubNotify credentialsId: 'test-jenkins-credentials',
|
||||
description: 'The lint job has failed',
|
||||
description: 'The lint test has failed',
|
||||
status: 'FAILURE',
|
||||
context: "jenkins/pr/lint"
|
||||
slackSend channel: "#jenkins-prod-pr",
|
||||
|
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@ -13,6 +13,7 @@ salt/*/*boto* @saltstack/team-boto
|
||||
|
||||
# Team Core
|
||||
requirements/* @saltstack/team-core
|
||||
rfcs/* @saltstack/team-core
|
||||
salt/auth/* @saltstack/team-core
|
||||
salt/cache/* @saltstack/team-core
|
||||
salt/cli/* @saltstack/team-core
|
||||
@ -73,3 +74,6 @@ salt/modules/reg.py @saltstack/team-windows
|
||||
salt/states/reg.py @saltstack/team-windows
|
||||
tests/*/*win* @saltstack/team-windows
|
||||
tests/*/test_reg.py @saltstack/team-windows
|
||||
|
||||
# Jenkins Integration
|
||||
.ci/* @saltstack/saltstack-release-engineering @saltstack/team-core @saltstack/team-windows
|
||||
|
@ -4,3 +4,11 @@ In Progress: Salt 2018.3.4 Release Notes
|
||||
|
||||
Version 2018.3.4 is an **unreleased** bugfix release for :ref:`2018.3.0 <release-2018-3-0>`.
|
||||
This release is still in progress and has not been released yet.
|
||||
|
||||
|
||||
State Changes
|
||||
=============
|
||||
|
||||
- The :py:func:`host.present <salt.states.host.present>` state can now remove
|
||||
the specified hostname from IPs not specified in the state. This can be done
|
||||
by setting the newly-added ``clean`` argument to ``True``.
|
||||
|
@ -169,7 +169,13 @@ def cert(name,
|
||||
res = __salt__['cmd.run_all'](' '.join(cmd))
|
||||
|
||||
if res['retcode'] != 0:
|
||||
return {'result': False, 'comment': 'Certificate {0} renewal failed with:\n{1}'.format(name, res['stderr'])}
|
||||
if 'expand' in res['stderr']:
|
||||
cmd.append('--expand')
|
||||
res = __salt__['cmd.run_all'](' '.join(cmd))
|
||||
if res['retcode'] != 0:
|
||||
return {'result': False, 'comment': 'Certificate {0} renewal failed with:\n{1}'.format(name, res['stderr'])}
|
||||
else:
|
||||
return {'result': False, 'comment': 'Certificate {0} renewal failed with:\n{1}'.format(name, res['stderr'])}
|
||||
|
||||
if 'no action taken' in res['stdout']:
|
||||
comment = 'Certificate {0} unchanged'.format(cert_file)
|
||||
|
@ -43,6 +43,7 @@ except ImportError:
|
||||
# Import salt libs
|
||||
import salt.utils.args
|
||||
import salt.utils.data
|
||||
import salt.utils.stringutils
|
||||
from salt.exceptions import SaltInvocationError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -494,7 +495,7 @@ def ls(path, load_path=None): # pylint: disable=C0103
|
||||
def _match(path):
|
||||
''' Internal match function '''
|
||||
try:
|
||||
matches = aug.match(path)
|
||||
matches = aug.match(salt.utils.stringutils.to_str(path))
|
||||
except RuntimeError:
|
||||
return {}
|
||||
|
||||
|
@ -2277,6 +2277,8 @@ def replace(path,
|
||||
# Just search; bail as early as a match is found
|
||||
if re.search(cpattern, r_data):
|
||||
return True # `with` block handles file closure
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
result, nrepl = re.subn(cpattern,
|
||||
repl.replace('\\', '\\\\') if backslash_literal else repl,
|
||||
|
@ -5,6 +5,7 @@ Manage the information in the hosts file
|
||||
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
import errno
|
||||
import os
|
||||
|
||||
# Import salt libs
|
||||
@ -22,7 +23,12 @@ def __get_hosts_filename():
|
||||
'''
|
||||
Return the path to the appropriate hosts file
|
||||
'''
|
||||
return __salt__['config.option']('hosts.file')
|
||||
try:
|
||||
return __context__['hosts.__get_hosts_filename']
|
||||
except KeyError:
|
||||
__context__['hosts.__get_hosts_filename'] = \
|
||||
__salt__['config.option']('hosts.file')
|
||||
return __context__['hosts.__get_hosts_filename']
|
||||
|
||||
|
||||
def _get_or_create_hostfile():
|
||||
@ -43,26 +49,35 @@ def _list_hosts():
|
||||
'''
|
||||
Return the hosts found in the hosts file in as an OrderedDict
|
||||
'''
|
||||
count = 0
|
||||
hfn = __get_hosts_filename()
|
||||
ret = odict.OrderedDict()
|
||||
if not os.path.isfile(hfn):
|
||||
try:
|
||||
return __context__['hosts._list_hosts']
|
||||
except KeyError:
|
||||
count = 0
|
||||
hfn = __get_hosts_filename()
|
||||
ret = odict.OrderedDict()
|
||||
try:
|
||||
with salt.utils.files.fopen(hfn) as ifile:
|
||||
for line in ifile:
|
||||
line = salt.utils.stringutils.to_unicode(line).strip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith('#'):
|
||||
ret.setdefault('comment-{0}'.format(count), []).append(line)
|
||||
count += 1
|
||||
continue
|
||||
if '#' in line:
|
||||
line = line[:line.index('#')].strip()
|
||||
comps = line.split()
|
||||
ip = comps.pop(0)
|
||||
ret.setdefault(ip, []).extend(comps)
|
||||
except (IOError, OSError) as exc:
|
||||
salt.utils.files.process_read_exception(exc, hfn, ignore=errno.ENOENT)
|
||||
# Don't set __context__ since we weren't able to read from the
|
||||
# hosts file.
|
||||
return ret
|
||||
|
||||
__context__['hosts._list_hosts'] = ret
|
||||
return ret
|
||||
with salt.utils.files.fopen(hfn) as ifile:
|
||||
for line in ifile:
|
||||
line = salt.utils.stringutils.to_unicode(line).strip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith('#'):
|
||||
ret.setdefault('comment-{0}'.format(count), []).append(line)
|
||||
count += 1
|
||||
continue
|
||||
if '#' in line:
|
||||
line = line[:line.index('#')].strip()
|
||||
comps = line.split()
|
||||
ip = comps.pop(0)
|
||||
ret.setdefault(ip, []).extend(comps)
|
||||
return ret
|
||||
|
||||
|
||||
def list_hosts():
|
||||
@ -133,7 +148,10 @@ def has_pair(ip, alias):
|
||||
salt '*' hosts.has_pair <ip> <alias>
|
||||
'''
|
||||
hosts = _list_hosts()
|
||||
return ip in hosts and alias in hosts[ip]
|
||||
try:
|
||||
return alias in hosts[ip]
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
|
||||
def set_host(ip, alias):
|
||||
@ -157,6 +175,9 @@ def set_host(ip, alias):
|
||||
if not os.path.isfile(hfn):
|
||||
return False
|
||||
|
||||
# Make sure future calls to _list_hosts() will re-read the file
|
||||
__context__.pop('hosts._list_hosts', None)
|
||||
|
||||
line_to_add = salt.utils.stringutils.to_bytes(
|
||||
ip + '\t\t' + alias + os.linesep
|
||||
)
|
||||
@ -203,6 +224,8 @@ def rm_host(ip, alias):
|
||||
'''
|
||||
if not has_pair(ip, alias):
|
||||
return True
|
||||
# Make sure future calls to _list_hosts() will re-read the file
|
||||
__context__.pop('hosts._list_hosts', None)
|
||||
hfn = _get_or_create_hostfile()
|
||||
with salt.utils.files.fopen(hfn, 'rb') as fp_:
|
||||
lines = fp_.readlines()
|
||||
@ -251,6 +274,10 @@ def add_host(ip, alias):
|
||||
return True
|
||||
|
||||
hosts = _list_hosts()
|
||||
|
||||
# Make sure future calls to _list_hosts() will re-read the file
|
||||
__context__.pop('hosts._list_hosts', None)
|
||||
|
||||
inserted = False
|
||||
for i, h in six.iteritems(hosts):
|
||||
for j in range(len(h)):
|
||||
|
@ -16,6 +16,9 @@ you can specify what ruby version and gemset to target.
|
||||
'''
|
||||
from __future__ import absolute_import, unicode_literals, print_function
|
||||
|
||||
import salt.utils
|
||||
|
||||
import re
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -84,10 +87,29 @@ def installed(name, # pylint: disable=C0103
|
||||
'Use of argument ruby found, but neither rvm or rbenv is installed'
|
||||
)
|
||||
gems = __salt__['gem.list'](name, ruby, gem_bin=gem_bin, runas=user)
|
||||
if name in gems and version is not None and str(version) in gems[name]:
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Gem is already installed.'
|
||||
return ret
|
||||
if name in gems and version is not None:
|
||||
match = re.match(r'(>=|>|<|<=)', version)
|
||||
if match:
|
||||
# Grab the comparison
|
||||
cmpr = match.group()
|
||||
|
||||
# Clear out 'default:' and any whitespace
|
||||
installed_version = re.sub('default: ', '', gems[name][0]).strip()
|
||||
|
||||
# Clear out comparison from version and whitespace
|
||||
desired_version = re.sub(cmpr, '', version).strip()
|
||||
|
||||
if salt.utils.compare_versions(installed_version,
|
||||
cmpr,
|
||||
desired_version):
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Installed Gem meets version requirements.'
|
||||
return ret
|
||||
else:
|
||||
if str(version) in gems[name]:
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Gem is already installed.'
|
||||
return ret
|
||||
elif name in gems and version is None:
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Gem is already installed.'
|
||||
|
@ -67,7 +67,7 @@ from salt.ext import six
|
||||
import salt.utils.validate.net
|
||||
|
||||
|
||||
def present(name, ip): # pylint: disable=C0103
|
||||
def present(name, ip, clean=False): # pylint: disable=C0103
|
||||
'''
|
||||
Ensures that the named host is present with the given ip
|
||||
|
||||
@ -75,36 +75,92 @@ def present(name, ip): # pylint: disable=C0103
|
||||
The host to assign an ip to
|
||||
|
||||
ip
|
||||
The ip addr(s) to apply to the host
|
||||
The ip addr(s) to apply to the host. Can be a single IP or a list of IP
|
||||
addresses.
|
||||
|
||||
clean : False
|
||||
Remove any entries which don't match those configured in the ``ip``
|
||||
option.
|
||||
|
||||
.. versionadded:: 2018.3.4
|
||||
'''
|
||||
ret = {'name': name,
|
||||
'changes': {},
|
||||
'result': None,
|
||||
'result': None if __opts__['test'] else True,
|
||||
'comment': ''}
|
||||
|
||||
if not isinstance(ip, list):
|
||||
ip = [ip]
|
||||
|
||||
all_hosts = __salt__['hosts.list_hosts']()
|
||||
comments = []
|
||||
for _ip in ip:
|
||||
if __salt__['hosts.has_pair'](_ip, name):
|
||||
ret['result'] = True
|
||||
comments.append('Host {0} ({1}) already present'.format(name, _ip))
|
||||
to_add = set()
|
||||
to_remove = set()
|
||||
|
||||
# First check for IPs not currently in the hosts file
|
||||
to_add.update([(addr, name) for addr in ip if addr not in all_hosts])
|
||||
|
||||
# Now sweep through the hosts file and look for entries matching either the
|
||||
# IP address(es) or hostname.
|
||||
for addr, aliases in six.iteritems(all_hosts):
|
||||
if addr not in ip:
|
||||
if name in aliases:
|
||||
# Found match for hostname, but the corresponding IP is not in
|
||||
# our list, so we need to remove it.
|
||||
if clean:
|
||||
to_remove.add((addr, name))
|
||||
else:
|
||||
ret.setdefault('warnings', []).append(
|
||||
'Host {0} present for IP address {1}. To get rid of '
|
||||
'this warning, either run this state with \'clean\' '
|
||||
'set to True to remove {0} from {1}, or add {1} to '
|
||||
'the \'ip\' argument.'.format(name, addr)
|
||||
)
|
||||
else:
|
||||
if __opts__['test']:
|
||||
comments.append('Host {0} ({1}) needs to be added/updated'.format(name, _ip))
|
||||
if name in aliases:
|
||||
# No changes needed for this IP address and hostname
|
||||
comments.append(
|
||||
'Host {0} ({1}) already present'.format(name, addr)
|
||||
)
|
||||
else:
|
||||
if salt.utils.validate.net.ipv4_addr(_ip) or salt.utils.validate.net.ipv6_addr(_ip):
|
||||
if __salt__['hosts.add_host'](_ip, name):
|
||||
ret['changes'] = {'host': name}
|
||||
ret['result'] = True
|
||||
comments.append('Added host {0} ({1})'.format(name, _ip))
|
||||
else:
|
||||
ret['result'] = False
|
||||
comments.append('Failed to set host')
|
||||
# IP address listed in hosts file, but hostname is not present.
|
||||
# We will need to add it.
|
||||
if salt.utils.validate.net.ip_addr(addr):
|
||||
to_add.add((addr, name))
|
||||
else:
|
||||
ret['result'] = False
|
||||
comments.append('Invalid IP Address for {0} ({1})'.format(name, _ip))
|
||||
comments.append(
|
||||
'Invalid IP Address for {0} ({1})'.format(name, addr)
|
||||
)
|
||||
|
||||
for addr, name in to_add:
|
||||
if __opts__['test']:
|
||||
comments.append(
|
||||
'Host {0} ({1}) would be added'.format(name, addr)
|
||||
)
|
||||
else:
|
||||
if __salt__['hosts.add_host'](addr, name):
|
||||
comments.append('Added host {0} ({1})'.format(name, addr))
|
||||
else:
|
||||
ret['result'] = False
|
||||
comments.append('Failed to add host {0} ({1})'.format(name, addr))
|
||||
continue
|
||||
ret['changes'].setdefault('added', {}).setdefault(addr, []).append(name)
|
||||
|
||||
for addr, name in to_remove:
|
||||
if __opts__['test']:
|
||||
comments.append(
|
||||
'Host {0} ({1}) would be removed'.format(name, addr)
|
||||
)
|
||||
else:
|
||||
if __salt__['hosts.rm_host'](addr, name):
|
||||
comments.append('Removed host {0} ({1})'.format(name, addr))
|
||||
else:
|
||||
ret['result'] = False
|
||||
comments.append('Failed to remove host {0} ({1})'.format(name, addr))
|
||||
continue
|
||||
ret['changes'].setdefault('removed', {}).setdefault(addr, []).append(name)
|
||||
|
||||
ret['comment'] = '\n'.join(comments)
|
||||
return ret
|
||||
|
||||
|
@ -338,6 +338,7 @@ def dead(name,
|
||||
else:
|
||||
# process name doesn't exist
|
||||
ret['comment'] = "Service {0} doesn't exist".format(name)
|
||||
return ret
|
||||
|
||||
if is_stopped is True:
|
||||
ret['comment'] = "Service {0} is not running".format(name)
|
||||
|
@ -205,10 +205,22 @@ def rename(src, dst):
|
||||
os.rename(src, dst)
|
||||
|
||||
|
||||
def process_read_exception(exc, path):
|
||||
def process_read_exception(exc, path, ignore=None):
|
||||
'''
|
||||
Common code for raising exceptions when reading a file fails
|
||||
|
||||
The ignore argument can be an iterable of integer error codes (or a single
|
||||
integer error code) that should be ignored.
|
||||
'''
|
||||
if ignore is not None:
|
||||
if isinstance(ignore, six.integer_types):
|
||||
ignore = (ignore,)
|
||||
else:
|
||||
ignore = ()
|
||||
|
||||
if exc.errno in ignore:
|
||||
return
|
||||
|
||||
if exc.errno == errno.ENOENT:
|
||||
raise CommandExecutionError('{0} does not exist'.format(path))
|
||||
elif exc.errno == errno.EACCES:
|
||||
|
@ -81,6 +81,14 @@ def ipv6_addr(addr):
|
||||
return __ip_addr(addr, socket.AF_INET6)
|
||||
|
||||
|
||||
def ip_addr(addr):
|
||||
'''
|
||||
Returns True if the IPv4 or IPv6 address (and optional subnet) are valid,
|
||||
otherwise returns False.
|
||||
'''
|
||||
return ipv4_addr(addr) or ipv6_addr(addr)
|
||||
|
||||
|
||||
def netmask(mask):
|
||||
'''
|
||||
Returns True if the value passed is a valid netmask, otherwise return False
|
||||
|
@ -226,6 +226,22 @@ class FileReplaceTestCase(TestCase, LoaderModuleMockMixin):
|
||||
'''
|
||||
filemod.replace(self.tfile.name, r'Etiam', 123)
|
||||
|
||||
def test_search_only_return_true(self):
|
||||
ret = filemod.replace(self.tfile.name,
|
||||
r'Etiam', 'Salticus',
|
||||
search_only=True)
|
||||
|
||||
self.assertIsInstance(ret, bool)
|
||||
self.assertEqual(ret, True)
|
||||
|
||||
def test_search_only_return_false(self):
|
||||
ret = filemod.replace(self.tfile.name,
|
||||
r'Etian', 'Salticus',
|
||||
search_only=True)
|
||||
|
||||
self.assertIsInstance(ret, bool)
|
||||
self.assertEqual(ret, False)
|
||||
|
||||
|
||||
class FileBlockReplaceTestCase(TestCase, LoaderModuleMockMixin):
|
||||
def setup_loader_modules(self):
|
||||
|
@ -47,6 +47,19 @@ class TestGemState(TestCase, LoaderModuleMockMixin):
|
||||
ri=False, gem_bin=None
|
||||
)
|
||||
|
||||
def test_installed_version(self):
|
||||
gems = {'foo': ['1.0'], 'bar': ['2.0']}
|
||||
gem_list = MagicMock(return_value=gems)
|
||||
gem_install_succeeds = MagicMock(return_value=True)
|
||||
|
||||
with patch.dict(gem.__salt__, {'gem.list': gem_list}):
|
||||
with patch.dict(gem.__salt__,
|
||||
{'gem.install': gem_install_succeeds}):
|
||||
ret = gem.installed('foo', version='>= 1.0')
|
||||
self.assertEqual(True, ret['result'])
|
||||
self.assertEqual('Installed Gem meets version requirements.',
|
||||
ret['comment'])
|
||||
|
||||
def test_removed(self):
|
||||
gems = ['foo', 'bar']
|
||||
gem_list = MagicMock(return_value=gems)
|
||||
|
@ -15,7 +15,8 @@ from tests.support.mock import (
|
||||
NO_MOCK,
|
||||
NO_MOCK_REASON,
|
||||
MagicMock,
|
||||
patch
|
||||
call,
|
||||
patch,
|
||||
)
|
||||
|
||||
|
||||
@ -25,19 +26,260 @@ class HostTestCase(TestCase, LoaderModuleMockMixin):
|
||||
Validate the host state
|
||||
'''
|
||||
def setup_loader_modules(self):
|
||||
return {host: {}}
|
||||
return {
|
||||
host: {
|
||||
'__opts__': {
|
||||
'test': False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def test_present(self):
|
||||
'''
|
||||
Test to ensures that the named host is present with the given ip
|
||||
'''
|
||||
ret = {'changes': {},
|
||||
'comment': 'Host salt (127.0.0.1) already present',
|
||||
'name': 'salt', 'result': True}
|
||||
add_host = MagicMock(return_value=True)
|
||||
rm_host = MagicMock(return_value=True)
|
||||
hostname = 'salt'
|
||||
ip_str = '127.0.0.1'
|
||||
ip_list = ['10.1.2.3', '10.4.5.6']
|
||||
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(host.__salt__, {'hosts.has_pair': mock}):
|
||||
self.assertDictEqual(host.present("salt", "127.0.0.1"), ret)
|
||||
# Case 1: No match for hostname. Single IP address passed to the state.
|
||||
list_hosts = MagicMock(return_value={
|
||||
'127.0.0.1': ['localhost'],
|
||||
})
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_str)
|
||||
assert ret['result'] is True
|
||||
assert ret['comment'] == 'Added host {0} ({1})'.format(hostname, ip_str), ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_str: [hostname],
|
||||
}
|
||||
}, ret['changes']
|
||||
expected = [call(ip_str, hostname)]
|
||||
assert add_host.mock_calls == expected, add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
# Case 2: No match for hostname. Multiple IP addresses passed to the
|
||||
# state.
|
||||
list_hosts = MagicMock(return_value={
|
||||
'127.0.0.1': ['localhost'],
|
||||
})
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_list)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[0]) in ret['comment']
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_list[0]: [hostname],
|
||||
ip_list[1]: [hostname],
|
||||
}
|
||||
}, ret['changes']
|
||||
expected = sorted([call(x, hostname) for x in ip_list])
|
||||
assert sorted(add_host.mock_calls) == expected, add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
# Case 3: Match for hostname, but no matching IP. Single IP address
|
||||
# passed to the state.
|
||||
list_hosts = MagicMock(return_value={
|
||||
'127.0.0.1': ['localhost'],
|
||||
ip_list[0]: [hostname],
|
||||
})
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_str)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_str) in ret['comment']
|
||||
assert 'Host {0} present for IP address {1}'.format(hostname, ip_list[0]) in ret['warnings'][0]
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_str: [hostname],
|
||||
},
|
||||
}, ret['changes']
|
||||
expected = [call(ip_str, hostname)]
|
||||
assert add_host.mock_calls == expected, add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
# Case 3a: Repeat the above with clean=True
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_str, clean=True)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_str) in ret['comment']
|
||||
assert 'Removed host {0} ({1})'.format(hostname, ip_list[0]) in ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_str: [hostname],
|
||||
},
|
||||
'removed': {
|
||||
ip_list[0]: [hostname],
|
||||
}
|
||||
}, ret['changes']
|
||||
expected = [call(ip_str, hostname)]
|
||||
assert add_host.mock_calls == expected, add_host.mock_calls
|
||||
expected = [call(ip_list[0], hostname)]
|
||||
assert rm_host.mock_calls == expected, rm_host.mock_calls
|
||||
|
||||
# Case 4: Match for hostname, but no matching IP. Multiple IP addresses
|
||||
# passed to the state.
|
||||
cur_ip = '1.2.3.4'
|
||||
list_hosts = MagicMock(return_value={
|
||||
'127.0.0.1': ['localhost'],
|
||||
cur_ip: [hostname],
|
||||
})
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_list)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[0]) in ret['comment']
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_list[0]: [hostname],
|
||||
ip_list[1]: [hostname],
|
||||
},
|
||||
}, ret['changes']
|
||||
expected = sorted([call(x, hostname) for x in ip_list])
|
||||
assert sorted(add_host.mock_calls) == expected, add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
# Case 4a: Repeat the above with clean=True
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_list, clean=True)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[0]) in ret['comment']
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
|
||||
assert 'Removed host {0} ({1})'.format(hostname, cur_ip) in ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_list[0]: [hostname],
|
||||
ip_list[1]: [hostname],
|
||||
},
|
||||
'removed': {
|
||||
cur_ip: [hostname],
|
||||
}
|
||||
}, ret['changes']
|
||||
expected = sorted([call(x, hostname) for x in ip_list])
|
||||
assert sorted(add_host.mock_calls) == expected, add_host.mock_calls
|
||||
expected = [call(cur_ip, hostname)]
|
||||
assert rm_host.mock_calls == expected, rm_host.mock_calls
|
||||
|
||||
# Case 5: Multiple IP addresses passed to the state. One of them
|
||||
# matches, the other does not. There is also a non-matching IP that
|
||||
# must be removed.
|
||||
cur_ip = '1.2.3.4'
|
||||
list_hosts = MagicMock(return_value={
|
||||
'127.0.0.1': ['localhost'],
|
||||
cur_ip: [hostname],
|
||||
ip_list[0]: [hostname],
|
||||
})
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_list)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_list[1]: [hostname],
|
||||
},
|
||||
}, ret['changes']
|
||||
expected = [call(ip_list[1], hostname)]
|
||||
assert add_host.mock_calls == expected, add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
# Case 5a: Repeat the above with clean=True
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_list, clean=True)
|
||||
assert ret['result'] is True
|
||||
assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
|
||||
assert 'Removed host {0} ({1})'.format(hostname, cur_ip) in ret['comment']
|
||||
assert ret['changes'] == {
|
||||
'added': {
|
||||
ip_list[1]: [hostname],
|
||||
},
|
||||
'removed': {
|
||||
cur_ip: [hostname],
|
||||
}
|
||||
}, ret['changes']
|
||||
expected = [call(ip_list[1], hostname)]
|
||||
assert add_host.mock_calls == expected, add_host.mock_calls
|
||||
expected = [call(cur_ip, hostname)]
|
||||
assert rm_host.mock_calls == expected, rm_host.mock_calls
|
||||
|
||||
# Case 6: Single IP address passed to the state, which matches the
|
||||
# current configuration for that hostname. No changes should be made.
|
||||
list_hosts = MagicMock(return_value={
|
||||
ip_str: [hostname],
|
||||
})
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_str)
|
||||
assert ret['result'] is True
|
||||
assert ret['comment'] == 'Host {0} ({1}) already present'.format(hostname, ip_str) in ret['comment']
|
||||
assert ret['changes'] == {}, ret['changes']
|
||||
assert add_host.mock_calls == [], add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
# Case 7: Multiple IP addresses passed to the state, which both match
|
||||
# the current configuration for that hostname. No changes should be
|
||||
# made.
|
||||
list_hosts = MagicMock(return_value={
|
||||
ip_list[0]: [hostname],
|
||||
ip_list[1]: [hostname],
|
||||
})
|
||||
add_host.reset_mock()
|
||||
rm_host.reset_mock()
|
||||
with patch.dict(host.__salt__,
|
||||
{'hosts.list_hosts': list_hosts,
|
||||
'hosts.add_host': add_host,
|
||||
'hosts.rm_host': rm_host}):
|
||||
ret = host.present(hostname, ip_list)
|
||||
assert ret['result'] is True
|
||||
assert 'Host {0} ({1}) already present'.format(hostname, ip_list[0]) in ret['comment']
|
||||
assert 'Host {0} ({1}) already present'.format(hostname, ip_list[1]) in ret['comment']
|
||||
assert ret['changes'] == {}, ret['changes']
|
||||
assert add_host.mock_calls == [], add_host.mock_calls
|
||||
assert rm_host.mock_calls == [], rm_host.mock_calls
|
||||
|
||||
def test_absent(self):
|
||||
'''
|
||||
|
@ -31,6 +31,7 @@ import salt.config
|
||||
import salt.log.setup
|
||||
from salt.ext import six
|
||||
import salt.utils.process
|
||||
import salt.utils.platform
|
||||
import salt.transport.server
|
||||
import salt.transport.client
|
||||
import salt.exceptions
|
||||
@ -434,7 +435,7 @@ class PubServerChannel(TestCase, AdaptedConfigurationTestCaseMixin):
|
||||
results.append(payload['jid'])
|
||||
return results
|
||||
|
||||
@skipIf(salt.utils.is_windows(), 'Skip on Windows OS')
|
||||
@skipIf(salt.utils.platform.is_windows(), 'Skip on Windows OS')
|
||||
def test_publish_to_pubserv_ipc(self):
|
||||
'''
|
||||
Test sending 10K messags to ZeroMQPubServerChannel using IPC transport
|
||||
@ -534,7 +535,7 @@ class PubServerChannel(TestCase, AdaptedConfigurationTestCaseMixin):
|
||||
server_channel.pub_close()
|
||||
assert len(results) == send_num, (len(results), set(expect).difference(results))
|
||||
|
||||
@skipIf(salt.utils.is_windows(), 'Skip on Windows OS')
|
||||
@skipIf(salt.utils.platform.is_windows(), 'Skip on Windows OS')
|
||||
def test_issue_36469_udp(self):
|
||||
'''
|
||||
Test sending both large and small messags to publisher using UDP
|
||||
|
Loading…
Reference in New Issue
Block a user