mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #43051 from rallytime/merge-2017.7
[2017.7] Merge forward from 2016.11 to 2017.7
This commit is contained in:
commit
7b0c94768a
14 changed files with 988 additions and 867 deletions
|
@ -39,6 +39,13 @@ specified target expression.
|
|||
desitination will be assumed to be a directory. Finally, recursion is now
|
||||
supported, allowing for entire directories to be copied.
|
||||
|
||||
.. versionchanged:: 2016.11.7,2017.7.2
|
||||
Reverted back to the old copy mode to preserve backward compatibility. The
|
||||
new functionality added in 2016.6.6 and 2017.7.0 is now available using the
|
||||
``-C`` or ``--chunked`` CLI arguments. Note that compression, recursive
|
||||
copying, and support for copying large files is only available in chunked
|
||||
mode.
|
||||
|
||||
Options
|
||||
=======
|
||||
|
||||
|
@ -56,9 +63,16 @@ Options
|
|||
.. include:: _includes/target-selection.rst
|
||||
|
||||
|
||||
.. option:: -C, --chunked
|
||||
|
||||
Use new chunked mode to copy files. This mode supports large files, recursive
|
||||
directories copying and compression.
|
||||
|
||||
.. versionadded:: 2016.11.7,2017.7.2
|
||||
|
||||
.. option:: -n, --no-compression
|
||||
|
||||
Disable gzip compression.
|
||||
Disable gzip compression in chunked mode.
|
||||
|
||||
.. versionadded:: 2016.3.7,2016.11.6,2017.7.0
|
||||
|
||||
|
|
|
@ -4,23 +4,12 @@ Salt 2016.3.7 Release Notes
|
|||
|
||||
Version 2016.3.7 is a bugfix release for :ref:`2016.3.0 <release-2016-3-0>`.
|
||||
|
||||
New master configuration option `allow_minion_key_revoke`, defaults to True. This option
|
||||
controls whether a minion can request that the master revoke its key. When True, a minion
|
||||
can request a key revocation and the master will comply. If it is False, the key will not
|
||||
be revoked by the msater.
|
||||
Changes for v2016.3.6..v2016.3.7
|
||||
--------------------------------
|
||||
|
||||
New master configuration option `require_minion_sign_messages`
|
||||
This requires that minions cryptographically sign the messages they
|
||||
publish to the master. If minions are not signing, then log this information
|
||||
at loglevel 'INFO' and drop the message without acting on it.
|
||||
Security Fix
|
||||
============
|
||||
|
||||
New master configuration option `drop_messages_signature_fail`
|
||||
Drop messages from minions when their signatures do not validate.
|
||||
Note that when this option is False but `require_minion_sign_messages` is True
|
||||
minions MUST sign their messages but the validity of their signatures
|
||||
is ignored.
|
||||
CVE-2017-12791 Maliciously crafted minion IDs can cause unwanted directory traversals on the Salt-master
|
||||
|
||||
New minion configuration option `minion_sign_messages`
|
||||
Causes the minion to cryptographically sign the payload of messages it places
|
||||
on the event bus for the master. The payloads are signed with the minion's
|
||||
private key so the master can verify the signature with its public key.
|
||||
Correct a flaw in minion id validation which could allow certain minions to authenticate to a master despite not having the correct credentials. To exploit the vulnerability, an attacker must create a salt-minion with an ID containing characters that will cause a directory traversal. Credit for discovering the security flaw goes to: Vernhk@qq.com
|
||||
|
|
29
doc/topics/releases/2016.3.8.rst
Normal file
29
doc/topics/releases/2016.3.8.rst
Normal file
|
@ -0,0 +1,29 @@
|
|||
===========================
|
||||
Salt 2016.3.8 Release Notes
|
||||
===========================
|
||||
|
||||
Version 2016.3.8 is a bugfix release for :ref:`2016.3.0 <release-2016-3-0>`.
|
||||
|
||||
Changes for v2016.3.7..v2016.3.8
|
||||
--------------------------------
|
||||
|
||||
New master configuration option `allow_minion_key_revoke`, defaults to True. This option
|
||||
controls whether a minion can request that the master revoke its key. When True, a minion
|
||||
can request a key revocation and the master will comply. If it is False, the key will not
|
||||
be revoked by the msater.
|
||||
|
||||
New master configuration option `require_minion_sign_messages`
|
||||
This requires that minions cryptographically sign the messages they
|
||||
publish to the master. If minions are not signing, then log this information
|
||||
at loglevel 'INFO' and drop the message without acting on it.
|
||||
|
||||
New master configuration option `drop_messages_signature_fail`
|
||||
Drop messages from minions when their signatures do not validate.
|
||||
Note that when this option is False but `require_minion_sign_messages` is True
|
||||
minions MUST sign their messages but the validity of their signatures
|
||||
is ignored.
|
||||
|
||||
New minion configuration option `minion_sign_messages`
|
||||
Causes the minion to cryptographically sign the payload of messages it places
|
||||
on the event bus for the master. The payloads are signed with the minion's
|
||||
private key so the master can verify the signature with its public key.
|
|
@ -21,7 +21,7 @@ import salt.client
|
|||
import salt.utils.gzip_util
|
||||
import salt.utils.itertools
|
||||
import salt.utils.minions
|
||||
from salt.utils import parsers, to_bytes
|
||||
from salt.utils import parsers, to_bytes, print_cli
|
||||
from salt.utils.verify import verify_log
|
||||
import salt.output
|
||||
|
||||
|
@ -101,10 +101,69 @@ class SaltCP(object):
|
|||
empty_dirs.update(empty_dirs_)
|
||||
return files, sorted(empty_dirs)
|
||||
|
||||
def _file_dict(self, fn_):
|
||||
'''
|
||||
Take a path and return the contents of the file as a string
|
||||
'''
|
||||
if not os.path.isfile(fn_):
|
||||
err = 'The referenced file, {0} is not available.'.format(fn_)
|
||||
sys.stderr.write(err + '\n')
|
||||
sys.exit(42)
|
||||
with salt.utils.fopen(fn_, 'r') as fp_:
|
||||
data = fp_.read()
|
||||
return {fn_: data}
|
||||
|
||||
def _load_files(self):
|
||||
'''
|
||||
Parse the files indicated in opts['src'] and load them into a python
|
||||
object for transport
|
||||
'''
|
||||
files = {}
|
||||
for fn_ in self.opts['src']:
|
||||
if os.path.isfile(fn_):
|
||||
files.update(self._file_dict(fn_))
|
||||
elif os.path.isdir(fn_):
|
||||
print_cli(fn_ + ' is a directory, only files are supported in non-chunked mode. '
|
||||
'Use "--chunked" command line argument.')
|
||||
sys.exit(1)
|
||||
return files
|
||||
|
||||
def run(self):
|
||||
'''
|
||||
Make the salt client call
|
||||
'''
|
||||
if self.opts['chunked']:
|
||||
ret = self.run_chunked()
|
||||
else:
|
||||
ret = self.run_oldstyle()
|
||||
|
||||
salt.output.display_output(
|
||||
ret,
|
||||
self.opts.get('output', 'nested'),
|
||||
self.opts)
|
||||
|
||||
def run_oldstyle(self):
|
||||
'''
|
||||
Make the salt client call in old-style all-in-one call method
|
||||
'''
|
||||
arg = [self._load_files(), self.opts['dest']]
|
||||
local = salt.client.get_local_client(self.opts['conf_file'])
|
||||
args = [self.opts['tgt'],
|
||||
'cp.recv',
|
||||
arg,
|
||||
self.opts['timeout'],
|
||||
]
|
||||
|
||||
selected_target_option = self.opts.get('selected_target_option', None)
|
||||
if selected_target_option is not None:
|
||||
args.append(selected_target_option)
|
||||
|
||||
return local.cmd(*args)
|
||||
|
||||
def run_chunked(self):
|
||||
'''
|
||||
Make the salt client call in the new fasion chunked multi-call way
|
||||
'''
|
||||
files, empty_dirs = self._list_files()
|
||||
dest = self.opts['dest']
|
||||
gzip = self.opts['gzip']
|
||||
|
@ -166,7 +225,7 @@ class SaltCP(object):
|
|||
)
|
||||
args = [
|
||||
tgt,
|
||||
'cp.recv',
|
||||
'cp.recv_chunked',
|
||||
[remote_path, chunk, append, gzip, mode],
|
||||
timeout,
|
||||
]
|
||||
|
@ -212,14 +271,11 @@ class SaltCP(object):
|
|||
else '',
|
||||
tgt,
|
||||
)
|
||||
args = [tgt, 'cp.recv', [remote_path, None], timeout]
|
||||
args = [tgt, 'cp.recv_chunked', [remote_path, None], timeout]
|
||||
if selected_target_option is not None:
|
||||
args.append(selected_target_option)
|
||||
|
||||
for minion_id, minion_ret in six.iteritems(local.cmd(*args)):
|
||||
ret.setdefault(minion_id, {})[remote_path] = minion_ret
|
||||
|
||||
salt.output.display_output(
|
||||
ret,
|
||||
self.opts.get('output', 'nested'),
|
||||
self.opts)
|
||||
return ret
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -58,7 +58,36 @@ def _gather_pillar(pillarenv, pillar_override):
|
|||
return ret
|
||||
|
||||
|
||||
def recv(dest, chunk, append=False, compressed=True, mode=None):
|
||||
def recv(files, dest):
|
||||
'''
|
||||
Used with salt-cp, pass the files dict, and the destination.
|
||||
|
||||
This function receives small fast copy files from the master via salt-cp.
|
||||
It does not work via the CLI.
|
||||
'''
|
||||
ret = {}
|
||||
for path, data in six.iteritems(files):
|
||||
if os.path.basename(path) == os.path.basename(dest) \
|
||||
and not os.path.isdir(dest):
|
||||
final = dest
|
||||
elif os.path.isdir(dest):
|
||||
final = os.path.join(dest, os.path.basename(path))
|
||||
elif os.path.isdir(os.path.dirname(dest)):
|
||||
final = dest
|
||||
else:
|
||||
return 'Destination unavailable'
|
||||
|
||||
try:
|
||||
with salt.utils.fopen(final, 'w+') as fp_:
|
||||
fp_.write(data)
|
||||
ret[final] = True
|
||||
except IOError:
|
||||
ret[final] = False
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def recv_chunked(dest, chunk, append=False, compressed=True, mode=None):
|
||||
'''
|
||||
This function receives files copied to the minion using ``salt-cp`` and is
|
||||
not intended to be used directly on the CLI.
|
||||
|
|
|
@ -37,7 +37,7 @@ import salt.utils
|
|||
|
||||
# Import 3rd-party libs
|
||||
# pylint: disable=import-error,no-name-in-module,redefined-builtin
|
||||
from salt.exceptions import SaltInvocationError
|
||||
from salt.exceptions import CommandExecutionError, SaltInvocationError
|
||||
# pylint: enable=import-error,no-name-in-module
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -89,6 +89,19 @@ def _connect():
|
|||
password=jenkins_password)
|
||||
|
||||
|
||||
def _retrieve_config_xml(config_xml, saltenv):
|
||||
'''
|
||||
Helper to cache the config XML and raise a CommandExecutionError if we fail
|
||||
to do so. If we successfully cache the file, return the cached path.
|
||||
'''
|
||||
ret = __salt__['cp.cache_file'](config_xml, saltenv)
|
||||
|
||||
if not ret:
|
||||
raise CommandExecutionError('Failed to retrieve {0}'.format(config_xml))
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def run(script):
|
||||
'''
|
||||
.. versionadded:: Carbon
|
||||
|
@ -166,7 +179,7 @@ def job_exists(name=None):
|
|||
|
||||
'''
|
||||
if not name:
|
||||
raise SaltInvocationError('Required parameter `name` is missing.')
|
||||
raise SaltInvocationError('Required parameter \'name\' is missing')
|
||||
|
||||
server = _connect()
|
||||
if server.job_exists(name):
|
||||
|
@ -190,12 +203,12 @@ def get_job_info(name=None):
|
|||
|
||||
'''
|
||||
if not name:
|
||||
raise SaltInvocationError('Required parameter `name` is missing.')
|
||||
raise SaltInvocationError('Required parameter \'name\' is missing')
|
||||
|
||||
server = _connect()
|
||||
|
||||
if not job_exists(name):
|
||||
raise SaltInvocationError('Job `{0}` does not exist.'.format(name))
|
||||
raise CommandExecutionError('Job \'{0}\' does not exist'.format(name))
|
||||
|
||||
job_info = server.get_job_info(name)
|
||||
if job_info:
|
||||
|
@ -219,17 +232,19 @@ def build_job(name=None, parameters=None):
|
|||
|
||||
'''
|
||||
if not name:
|
||||
raise SaltInvocationError('Required parameter `name` is missing.')
|
||||
raise SaltInvocationError('Required parameter \'name\' is missing')
|
||||
|
||||
server = _connect()
|
||||
|
||||
if not job_exists(name):
|
||||
raise SaltInvocationError('Job `{0}` does not exist.'.format(name))
|
||||
raise CommandExecutionError('Job \'{0}\' does not exist.'.format(name))
|
||||
|
||||
try:
|
||||
server.build_job(name, parameters)
|
||||
except jenkins.JenkinsException as err:
|
||||
raise SaltInvocationError('Something went wrong {0}.'.format(err))
|
||||
raise CommandExecutionError(
|
||||
'Encountered error building job \'{0}\': {1}'.format(name, err)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
|
@ -254,15 +269,15 @@ def create_job(name=None,
|
|||
|
||||
'''
|
||||
if not name:
|
||||
raise SaltInvocationError('Required parameter `name` is missing.')
|
||||
raise SaltInvocationError('Required parameter \'name\' is missing')
|
||||
|
||||
if job_exists(name):
|
||||
raise SaltInvocationError('Job `{0}` already exists.'.format(name))
|
||||
raise CommandExecutionError('Job \'{0}\' already exists'.format(name))
|
||||
|
||||
if not config_xml:
|
||||
config_xml = jenkins.EMPTY_CONFIG_XML
|
||||
else:
|
||||
config_xml_file = __salt__['cp.cache_file'](config_xml, saltenv)
|
||||
config_xml_file = _retrieve_config_xml(config_xml, saltenv)
|
||||
|
||||
with salt.utils.fopen(config_xml_file) as _fp:
|
||||
config_xml = _fp.read()
|
||||
|
@ -271,7 +286,9 @@ def create_job(name=None,
|
|||
try:
|
||||
server.create_job(name, config_xml)
|
||||
except jenkins.JenkinsException as err:
|
||||
raise SaltInvocationError('Something went wrong {0}.'.format(err))
|
||||
raise CommandExecutionError(
|
||||
'Encountered error creating job \'{0}\': {1}'.format(name, err)
|
||||
)
|
||||
return config_xml
|
||||
|
||||
|
||||
|
@ -296,12 +313,12 @@ def update_job(name=None,
|
|||
|
||||
'''
|
||||
if not name:
|
||||
raise SaltInvocationError('Required parameter `name` is missing.')
|
||||
raise SaltInvocationError('Required parameter \'name\' is missing')
|
||||
|
||||
if not config_xml:
|
||||
config_xml = jenkins.EMPTY_CONFIG_XML
|
||||
else:
|
||||
config_xml_file = __salt__['cp.cache_file'](config_xml, saltenv)
|
||||
config_xml_file = _retrieve_config_xml(config_xml, saltenv)
|
||||
|
||||
with salt.utils.fopen(config_xml_file) as _fp:
|
||||
config_xml = _fp.read()
|
||||
|
@ -310,7 +327,9 @@ def update_job(name=None,
|
|||
try:
|
||||
server.reconfig_job(name, config_xml)
|
||||
except jenkins.JenkinsException as err:
|
||||
raise SaltInvocationError('Something went wrong {0}.'.format(err))
|
||||
raise CommandExecutionError(
|
||||
'Encountered error updating job \'{0}\': {1}'.format(name, err)
|
||||
)
|
||||
return config_xml
|
||||
|
||||
|
||||
|
@ -329,17 +348,19 @@ def delete_job(name=None):
|
|||
|
||||
'''
|
||||
if not name:
|
||||
raise SaltInvocationError('Required parameter `name` is missing.')
|
||||
raise SaltInvocationError('Required parameter \'name\' is missing')
|
||||
|
||||
server = _connect()
|
||||
|
||||
if not job_exists(name):
|
||||
raise SaltInvocationError('Job `{0}` does not exists.'.format(name))
|
||||
raise CommandExecutionError('Job \'{0}\' does not exist'.format(name))
|
||||
|
||||
try:
|
||||
server.delete_job(name)
|
||||
except jenkins.JenkinsException as err:
|
||||
raise SaltInvocationError('Something went wrong {0}.'.format(err))
|
||||
raise CommandExecutionError(
|
||||
'Encountered error deleting job \'{0}\': {1}'.format(name, err)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
|
@ -358,17 +379,19 @@ def enable_job(name=None):
|
|||
|
||||
'''
|
||||
if not name:
|
||||
raise SaltInvocationError('Required parameter `name` is missing.')
|
||||
raise SaltInvocationError('Required parameter \'name\' is missing')
|
||||
|
||||
server = _connect()
|
||||
|
||||
if not job_exists(name):
|
||||
raise SaltInvocationError('Job `{0}` does not exists.'.format(name))
|
||||
raise CommandExecutionError('Job \'{0}\' does not exist'.format(name))
|
||||
|
||||
try:
|
||||
server.enable_job(name)
|
||||
except jenkins.JenkinsException as err:
|
||||
raise SaltInvocationError('Something went wrong {0}.'.format(err))
|
||||
raise CommandExecutionError(
|
||||
'Encountered error enabling job \'{0}\': {1}'.format(name, err)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
|
@ -388,17 +411,19 @@ def disable_job(name=None):
|
|||
'''
|
||||
|
||||
if not name:
|
||||
raise SaltInvocationError('Required parameter `name` is missing.')
|
||||
raise SaltInvocationError('Required parameter \'name\' is missing')
|
||||
|
||||
server = _connect()
|
||||
|
||||
if not job_exists(name):
|
||||
raise SaltInvocationError('Job `{0}` does not exists.'.format(name))
|
||||
raise CommandExecutionError('Job \'{0}\' does not exist'.format(name))
|
||||
|
||||
try:
|
||||
server.disable_job(name)
|
||||
except jenkins.JenkinsException as err:
|
||||
raise SaltInvocationError('Something went wrong {0}.'.format(err))
|
||||
raise CommandExecutionError(
|
||||
'Encountered error disabling job \'{0}\': {1}'.format(name, err)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
|
@ -418,12 +443,12 @@ def job_status(name=None):
|
|||
'''
|
||||
|
||||
if not name:
|
||||
raise SaltInvocationError('Required parameter `name` is missing.')
|
||||
raise SaltInvocationError('Required parameter \'name\' is missing')
|
||||
|
||||
server = _connect()
|
||||
|
||||
if not job_exists(name):
|
||||
raise SaltInvocationError('Job `{0}` does not exists.'.format(name))
|
||||
raise CommandExecutionError('Job \'{0}\' does not exist'.format(name))
|
||||
|
||||
return server.get_job_info('empty')['buildable']
|
||||
|
||||
|
@ -444,12 +469,12 @@ def get_job_config(name=None):
|
|||
'''
|
||||
|
||||
if not name:
|
||||
raise SaltInvocationError('Required parameter `name` is missing.')
|
||||
raise SaltInvocationError('Required parameter \'name\' is missing')
|
||||
|
||||
server = _connect()
|
||||
|
||||
if not job_exists(name):
|
||||
raise SaltInvocationError('Job `{0}` does not exists.'.format(name))
|
||||
raise CommandExecutionError('Job \'{0}\' does not exist'.format(name))
|
||||
|
||||
job_info = server.get_job_config(name)
|
||||
return job_info
|
||||
|
|
|
@ -285,13 +285,17 @@ def _register_functions():
|
|||
functions, and then register them in the module namespace so that they
|
||||
can be called via salt.
|
||||
"""
|
||||
for module_ in modules.__all__:
|
||||
try:
|
||||
modules_ = [_to_snake_case(module_) for module_ in modules.__all__]
|
||||
except AttributeError:
|
||||
modules_ = [module_ for module_ in modules.modules]
|
||||
|
||||
for mod_name in modules_:
|
||||
mod_name = _to_snake_case(module_)
|
||||
mod_func = _copy_function(mod_name, str(mod_name))
|
||||
mod_func.__doc__ = _build_doc(module_)
|
||||
mod_func.__doc__ = _build_doc(mod_name)
|
||||
__all__.append(mod_name)
|
||||
globals()[mod_name] = mod_func
|
||||
|
||||
|
||||
if TESTINFRA_PRESENT:
|
||||
_register_functions()
|
||||
|
|
|
@ -330,10 +330,14 @@ def _parse_subject(subject):
|
|||
for nid_name, nid_num in six.iteritems(subject.nid):
|
||||
if nid_num in nids:
|
||||
continue
|
||||
val = getattr(subject, nid_name)
|
||||
if val:
|
||||
ret[nid_name] = val
|
||||
nids.append(nid_num)
|
||||
try:
|
||||
val = getattr(subject, nid_name)
|
||||
if val:
|
||||
ret[nid_name] = val
|
||||
nids.append(nid_num)
|
||||
except TypeError as e:
|
||||
if e.args and e.args[0] == 'No string argument provided':
|
||||
pass
|
||||
|
||||
return ret
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import logging
|
|||
import salt.ext.six as six
|
||||
from salt.ext.six.moves import zip
|
||||
import salt.utils
|
||||
from salt.exceptions import CommandExecutionError
|
||||
|
||||
# Import XML parser
|
||||
import xml.etree.ElementTree as ET
|
||||
|
@ -36,17 +37,23 @@ def _elements_equal(e1, e2):
|
|||
return all(_elements_equal(c1, c2) for c1, c2 in zip(e1, e2))
|
||||
|
||||
|
||||
def _fail(ret, msg):
|
||||
ret['comment'] = msg
|
||||
ret['result'] = False
|
||||
return ret
|
||||
|
||||
|
||||
def present(name,
|
||||
config=None,
|
||||
**kwargs):
|
||||
'''
|
||||
Ensure the job is present in the Jenkins
|
||||
configured jobs
|
||||
Ensure the job is present in the Jenkins configured jobs
|
||||
|
||||
name
|
||||
The unique name for the Jenkins job
|
||||
|
||||
config
|
||||
The Salt URL for the file to use for
|
||||
configuring the job.
|
||||
The Salt URL for the file to use for configuring the job
|
||||
'''
|
||||
|
||||
ret = {'name': name,
|
||||
|
@ -54,9 +61,7 @@ def present(name,
|
|||
'changes': {},
|
||||
'comment': ['Job {0} is up to date.'.format(name)]}
|
||||
|
||||
_job_exists = __salt__['jenkins.job_exists'](name)
|
||||
|
||||
if _job_exists:
|
||||
if __salt__['jenkins.job_exists'](name):
|
||||
_current_job_config = __salt__['jenkins.get_job_config'](name)
|
||||
buf = six.moves.StringIO(_current_job_config)
|
||||
oldXML = ET.fromstring(buf.read())
|
||||
|
@ -68,21 +73,28 @@ def present(name,
|
|||
diff = difflib.unified_diff(
|
||||
ET.tostringlist(oldXML, encoding='utf8', method='xml'),
|
||||
ET.tostringlist(newXML, encoding='utf8', method='xml'), lineterm='')
|
||||
__salt__['jenkins.update_job'](name, config, __env__)
|
||||
ret['changes'][name] = ''.join(diff)
|
||||
ret['comment'].append('Job {0} updated.'.format(name))
|
||||
try:
|
||||
__salt__['jenkins.update_job'](name, config, __env__)
|
||||
except CommandExecutionError as exc:
|
||||
return _fail(ret, exc.strerror)
|
||||
else:
|
||||
ret['changes'] = ''.join(diff)
|
||||
ret['comment'].append('Job \'{0}\' updated.'.format(name))
|
||||
|
||||
else:
|
||||
cached_source_path = __salt__['cp.cache_file'](config, __env__)
|
||||
with salt.utils.fopen(cached_source_path) as _fp:
|
||||
new_config_xml = _fp.read()
|
||||
|
||||
__salt__['jenkins.create_job'](name, config, __env__)
|
||||
try:
|
||||
__salt__['jenkins.create_job'](name, config, __env__)
|
||||
except CommandExecutionError as exc:
|
||||
return _fail(ret, exc.strerror)
|
||||
|
||||
buf = six.moves.StringIO(new_config_xml)
|
||||
diff = difflib.unified_diff('', buf.readlines(), lineterm='')
|
||||
ret['changes'][name] = ''.join(diff)
|
||||
ret['comment'].append('Job {0} added.'.format(name))
|
||||
ret['comment'].append('Job \'{0}\' added.'.format(name))
|
||||
|
||||
ret['comment'] = '\n'.join(ret['comment'])
|
||||
return ret
|
||||
|
@ -91,24 +103,23 @@ def present(name,
|
|||
def absent(name,
|
||||
**kwargs):
|
||||
'''
|
||||
Ensure the job is present in the Jenkins
|
||||
configured jobs
|
||||
Ensure the job is absent from the Jenkins configured jobs
|
||||
|
||||
name
|
||||
The name of the Jenkins job to remove.
|
||||
|
||||
The name of the Jenkins job to remove
|
||||
'''
|
||||
|
||||
ret = {'name': name,
|
||||
'result': True,
|
||||
'changes': {},
|
||||
'comment': []}
|
||||
|
||||
_job_exists = __salt__['jenkins.job_exists'](name)
|
||||
|
||||
if _job_exists:
|
||||
__salt__['jenkins.delete_job'](name)
|
||||
ret['comment'] = 'Job {0} deleted.'.format(name)
|
||||
if __salt__['jenkins.job_exists'](name):
|
||||
try:
|
||||
__salt__['jenkins.delete_job'](name)
|
||||
except CommandExecutionError as exc:
|
||||
return _fail(ret, exc.strerror)
|
||||
else:
|
||||
ret['comment'] = 'Job \'{0}\' deleted.'.format(name)
|
||||
else:
|
||||
ret['comment'] = 'Job {0} already absent.'.format(name)
|
||||
ret['comment'] = 'Job \'{0}\' already absent.'.format(name)
|
||||
return ret
|
||||
|
|
|
@ -52,8 +52,12 @@ def _to_snake_case(pascal_case):
|
|||
|
||||
|
||||
def _generate_functions():
|
||||
for module in modules.__all__:
|
||||
module_name = _to_snake_case(module)
|
||||
try:
|
||||
modules_ = [_to_snake_case(module_) for module_ in modules.__all__]
|
||||
except AttributeError:
|
||||
modules_ = [module_ for module_ in modules.modules]
|
||||
|
||||
for module_name in modules_:
|
||||
func_name = 'testinfra.{0}'.format(module_name)
|
||||
__all__.append(module_name)
|
||||
log.debug('Generating state for module %s as function %s',
|
||||
|
|
|
@ -2156,10 +2156,18 @@ class SaltCPOptionParser(six.with_metaclass(OptionParserMeta,
|
|||
|
||||
def _mixin_setup(self):
|
||||
file_opts_group = optparse.OptionGroup(self, 'File Options')
|
||||
file_opts_group.add_option(
|
||||
'-C', '--chunked',
|
||||
default=False,
|
||||
dest='chunked',
|
||||
action='store_true',
|
||||
help='Use chunked files transfer. Supports big files, recursive '
|
||||
'lookup and directories creation.'
|
||||
)
|
||||
file_opts_group.add_option(
|
||||
'-n', '--no-compression',
|
||||
default=True,
|
||||
dest='compression',
|
||||
dest='gzip',
|
||||
action='store_false',
|
||||
help='Disable gzip compression.'
|
||||
)
|
||||
|
@ -2180,7 +2188,6 @@ class SaltCPOptionParser(six.with_metaclass(OptionParserMeta,
|
|||
self.config['tgt'] = self.args[0]
|
||||
self.config['src'] = [os.path.realpath(x) for x in self.args[1:-1]]
|
||||
self.config['dest'] = self.args[-1]
|
||||
self.config['gzip'] = True
|
||||
|
||||
def setup_config(self):
|
||||
return config.master_config(self.get_config_file_path())
|
||||
|
|
|
@ -51,6 +51,7 @@ def get_invalid_docs():
|
|||
allow_failure = (
|
||||
'cmd.win_runas',
|
||||
'cp.recv',
|
||||
'cp.recv_chunked',
|
||||
'glance.warn_until',
|
||||
'ipset.long_range',
|
||||
'libcloud_dns.get_driver',
|
||||
|
|
|
@ -118,7 +118,7 @@ class NetworkTestCase(TestCase):
|
|||
(2, 1, 6, '', ('192.30.255.113', 0)),
|
||||
],
|
||||
'ipv6host.foo': [
|
||||
(10, 1, 6, '', ('2001:a71::1', 0, 0, 0)),
|
||||
(socket.AF_INET6, 1, 6, '', ('2001:a71::1', 0, 0, 0)),
|
||||
],
|
||||
}[host]
|
||||
except KeyError:
|
||||
|
|
Loading…
Add table
Reference in a new issue