mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 17:09:03 +00:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
06f12b0664
31
conf/minion
31
conf/minion
@ -119,10 +119,10 @@
|
||||
# to reconnect immediately, if the socket is disconnected (for example if
|
||||
# the master processes are restarted). In large setups this will have all
|
||||
# minions reconnect immediately which might flood the master (the ZeroMQ-default
|
||||
# is usually a 100ms delay). To prevent this, these three recon_* settings
|
||||
# is usually a 100ms delay). To prevent this, these three recon_* settings
|
||||
# can be used.
|
||||
#
|
||||
# recon_default: the interval in milliseconds that the socket should wait before
|
||||
# recon_default: the interval in milliseconds that the socket should wait before
|
||||
# trying to reconnect to the master (100ms = 1 second)
|
||||
#
|
||||
# recon_max: the maximum time a socket should wait. each interval the time to wait
|
||||
@ -136,14 +136,14 @@
|
||||
# reconnect 5: value from previous interval * 2
|
||||
# reconnect x: if value >= recon_max, it starts again with recon_default
|
||||
#
|
||||
# recon_randomize: generate a random wait time on minion start. The wait time will
|
||||
# be a random value between recon_default and recon_default +
|
||||
# recon_max. Having all minions reconnect with the same recon_default
|
||||
# and recon_max value kind of defeats the purpose of being able to
|
||||
# change these settings. If all minions have the same values and your
|
||||
# setup is quite large (several thousand minions), they will still
|
||||
# recon_randomize: generate a random wait time on minion start. The wait time will
|
||||
# be a random value between recon_default and recon_default +
|
||||
# recon_max. Having all minions reconnect with the same recon_default
|
||||
# and recon_max value kind of defeats the purpose of being able to
|
||||
# change these settings. If all minions have the same values and your
|
||||
# setup is quite large (several thousand minions), they will still
|
||||
# flood the master. The desired behaviour is to have timeframe within
|
||||
# all minions try to reconnect.
|
||||
# all minions try to reconnect.
|
||||
|
||||
# Example on how to use these settings:
|
||||
# The goal: have all minions reconnect within a 60 second timeframe on a disconnect
|
||||
@ -155,9 +155,9 @@
|
||||
#
|
||||
# Each minion will have a randomized reconnect value between 'recon_default'
|
||||
# and 'recon_default + recon_max', which in this example means between 1000ms
|
||||
# 60000ms (or between 1 and 60 seconds). The generated random-value will be
|
||||
# doubled after each attempt to reconnect. Lets say the generated random
|
||||
# value is 11 seconds (or 11000ms).
|
||||
# 60000ms (or between 1 and 60 seconds). The generated random-value will be
|
||||
# doubled after each attempt to reconnect. Lets say the generated random
|
||||
# value is 11 seconds (or 11000ms).
|
||||
#
|
||||
# reconnect 1: wait 11 seconds
|
||||
# reconnect 2: wait 22 seconds
|
||||
@ -238,6 +238,13 @@
|
||||
# Enable Cython modules searching and loading. (Default: False)
|
||||
#cython_enable: False
|
||||
#
|
||||
#
|
||||
#
|
||||
# Specify a max size (in bytes) for modules on import
|
||||
# this feature is currently only supported on *nix OSs and requires psutil
|
||||
# modules_max_memory: -1
|
||||
|
||||
|
||||
|
||||
##### State Management Settings #####
|
||||
###########################################
|
||||
|
@ -17,6 +17,15 @@ Environments
|
||||
directories. Environments can be made to be self-contained or state
|
||||
trees can be made to bleed through environments.
|
||||
|
||||
.. note::
|
||||
|
||||
Environments in Salt are very flexible, this section defines how the top
|
||||
file can be used to define what ststates from what environments are to be
|
||||
used fro specific minions.
|
||||
|
||||
If the intent is to bind minions to specific environments, then the
|
||||
`environment` option can be set in the minion configuration file.
|
||||
|
||||
The environments in the top file corresponds with the environments defined in
|
||||
the :conf_master:`file_roots` variable. In a simple, single environment setup
|
||||
you only have the ``base`` environment, and therefore only one state tree. Here
|
||||
|
@ -155,6 +155,7 @@ class Authorize(object):
|
||||
def __init__(self, opts, load, loadauth=None):
|
||||
self.opts = salt.config.master_config(opts['conf_file'])
|
||||
self.load = load
|
||||
self.ckminions = salt.utils.minions.CkMinions(opts)
|
||||
if loadauth is None:
|
||||
self.loadauth = LoadAuth(opts)
|
||||
else:
|
||||
@ -171,6 +172,112 @@ class Authorize(object):
|
||||
auth_data.append(getattr(self.loadauth.auth)())
|
||||
return auth_data
|
||||
|
||||
def token(self, adata, load):
|
||||
'''
|
||||
Determine if token auth is valid and yield the adata
|
||||
'''
|
||||
try:
|
||||
token = self.loadauth.get_tok(load['token'])
|
||||
except Exception as exc:
|
||||
log.error(
|
||||
'Exception occurred when generating auth token: {0}'.format(
|
||||
exc
|
||||
)
|
||||
)
|
||||
yield {}
|
||||
if not token:
|
||||
log.warning('Authentication failure of type "token" occurred.')
|
||||
yield {}
|
||||
for sub_auth in adata:
|
||||
if token['eauth'] not in adata:
|
||||
continue
|
||||
if not ((token['name'] in adata[token['eauth']]) |
|
||||
('*' in adata[token['eauth']])):
|
||||
continue
|
||||
yield {'sub_auth': sub_auth, 'token': token}
|
||||
yield {}
|
||||
|
||||
def eauth(self, adata, load):
|
||||
'''
|
||||
Determine if the given eauth is valid and yield the adata
|
||||
'''
|
||||
for sub_auth in adata:
|
||||
if load['eauth'] not in sub_auth:
|
||||
continue
|
||||
try:
|
||||
name = self.loadauth.load_name(load)
|
||||
if not ((name in sub_auth[load['eauth']]) |
|
||||
('*' in sub_auth[load['eauth']])):
|
||||
continue
|
||||
if not self.loadauth.time_auth(load):
|
||||
continue
|
||||
except Exception as exc:
|
||||
log.error(
|
||||
'Exception occurred while authenticating: {0}'.format(exc)
|
||||
)
|
||||
continue
|
||||
yield {'sub_auth': sub_auth, 'name': name}
|
||||
yield {}
|
||||
|
||||
def rights_check(self, form, sub_auth, name, load, eauth=None):
|
||||
'''
|
||||
Read in the access system to determine if the validated user has
|
||||
requested rights
|
||||
'''
|
||||
if load.get('eauth'):
|
||||
sub_auth = sub_auth[load['eauth']]
|
||||
good = self.ckminions.any_check(
|
||||
form,
|
||||
sub_auth[name] if name in sub_auth else sub_auth['*'],
|
||||
load.get('fun', None),
|
||||
load.get('tgt', None),
|
||||
load.get('tgt_type', 'glob'))
|
||||
if not good:
|
||||
# Accept find_job so the CLI will function cleanly
|
||||
if load.get('fun', '') != 'saltutil.find_job':
|
||||
return good
|
||||
return good
|
||||
|
||||
def rights(self, form, load):
|
||||
'''
|
||||
Determine what type of authentication is being requested and pass
|
||||
authorization
|
||||
'''
|
||||
adata = self.auth_data()
|
||||
if load.get('token', False):
|
||||
good = False
|
||||
for sub_auth in self.token(adata, load):
|
||||
if sub_auth:
|
||||
if self.rights_check(
|
||||
form,
|
||||
sub_auth['sub_auth'],
|
||||
sub_auth['token']['name'],
|
||||
load,
|
||||
sub_auth['token']['eauth']):
|
||||
good = True
|
||||
if not good:
|
||||
log.warning(
|
||||
'Authentication failure of type "token" occurred.'
|
||||
)
|
||||
return False
|
||||
elif load.get('eauth'):
|
||||
good = False
|
||||
for sub_auth in self.eauth(adata, load):
|
||||
if sub_auth:
|
||||
if self.rights_check(
|
||||
form,
|
||||
sub_auth['sub_auth'],
|
||||
sub_auth['name'],
|
||||
load,
|
||||
load['eauth']):
|
||||
good = True
|
||||
if not good:
|
||||
log.warning(
|
||||
'Authentication failure of type "eauth" occurred.'
|
||||
)
|
||||
return False
|
||||
return good
|
||||
|
||||
|
||||
class Resolver(object):
|
||||
'''
|
||||
|
@ -33,7 +33,7 @@ RSTR = '_edbc7885e4f9aac9b83b35999b68d015148caf467b78fa39c05f669c0ff89878'
|
||||
|
||||
# This shim facilitaites remote salt-call operations
|
||||
# - Explicitly invokes bourne shell for univeral compatibility
|
||||
#
|
||||
#
|
||||
# 1. Identify a suitable python
|
||||
# 2. Test for remote salt-call and version if present
|
||||
# 3. Signal to (re)deploy if missing or out of date
|
||||
@ -89,6 +89,7 @@ EOF\n'''.format(salt.__version__, RSTR)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SSH(object):
|
||||
'''
|
||||
Create an SSH execution system
|
||||
@ -430,8 +431,8 @@ class Single(object):
|
||||
def run(self, deploy_attempted=False):
|
||||
'''
|
||||
Execute the routine, the routine can be either:
|
||||
1. Execute a raw shell command
|
||||
2. Execute a wrapper func
|
||||
1. Execute a raw shell command
|
||||
2. Execute a wrapper func
|
||||
3. Execute a remote Salt command
|
||||
|
||||
If a (re)deploy is needed, then retry the operation after a deploy
|
||||
@ -566,11 +567,11 @@ class Single(object):
|
||||
"to be root or use sudo:\n {0}"
|
||||
errors = [
|
||||
("sudo: no tty present and no askpass program specified",
|
||||
"sudo expected a password, NOPASSWD required"),
|
||||
"sudo expected a password, NOPASSWD required"),
|
||||
("Python too old",
|
||||
"salt requires python 2.6 or better on target hosts"),
|
||||
"salt requires python 2.6 or better on target hosts"),
|
||||
("sudo: sorry, you must have a tty to run sudo",
|
||||
"sudo is configured with requiretty"),
|
||||
"sudo is configured with requiretty"),
|
||||
("Failed to open log file",
|
||||
perm_error_fmt.format(stderr)),
|
||||
("Permission denied:.*/salt",
|
||||
@ -584,7 +585,6 @@ class Single(object):
|
||||
return error[1]
|
||||
return None
|
||||
|
||||
|
||||
def sls_seed(self, mods, env='base', test=None, exclude=None, **kwargs):
|
||||
'''
|
||||
Create the seed file for a state.sls run
|
||||
|
@ -162,6 +162,7 @@ VALID_OPTS = {
|
||||
'win_repo': str,
|
||||
'win_repo_mastercachefile': str,
|
||||
'win_gitrepos': list,
|
||||
'modules_max_memory': int,
|
||||
}
|
||||
|
||||
# default configurations
|
||||
@ -248,12 +249,13 @@ DEFAULT_MINION_OPTS = {
|
||||
'tcp_keepalive_idle': 300,
|
||||
'tcp_keepalive_cnt': -1,
|
||||
'tcp_keepalive_intvl': -1,
|
||||
'modules_max_memory': -1,
|
||||
}
|
||||
|
||||
DEFAULT_MASTER_OPTS = {
|
||||
'interface': '0.0.0.0',
|
||||
'publish_port': '4505',
|
||||
'pub_hwm': 1,
|
||||
'pub_hwm': 100,
|
||||
'auth_mode': 1,
|
||||
'user': 'root',
|
||||
'worker_threads': 5,
|
||||
|
@ -1865,6 +1865,16 @@ class ClearFuncs(object):
|
||||
self.event.fire_event(eload, tagify(prefix='auth'))
|
||||
return ret
|
||||
|
||||
def cloud(self, clear_load):
|
||||
'''
|
||||
Hook into the salt-cloud libs and execute cloud routines
|
||||
# NOT HOOKED IN YET
|
||||
'''
|
||||
authorize = salt.auth.Authorize(self.opts, clear_load, self.loadauth)
|
||||
if not authorize.rights('cloud', clear_load):
|
||||
return False
|
||||
return True
|
||||
|
||||
def runner(self, clear_load):
|
||||
'''
|
||||
Send a master control function back to the runner system
|
||||
|
@ -36,6 +36,20 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
HAS_PSUTIL = False
|
||||
try:
|
||||
import psutil
|
||||
HAS_PSUTIL = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
HAS_RESOURCE = False
|
||||
try:
|
||||
import resource
|
||||
HAS_RESOURCE = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# Import salt libs
|
||||
from salt.exceptions import (
|
||||
AuthenticationError, CommandExecutionError, CommandNotFoundError,
|
||||
@ -504,9 +518,31 @@ class Minion(object):
|
||||
Return the functions and the returners loaded up from the loader
|
||||
module
|
||||
'''
|
||||
# if this is a *nix system AND modules_max_memory is set, lets enforce
|
||||
# a memory limit on module imports
|
||||
# this feature ONLY works on *nix like OSs (resource module doesn't work on windows)
|
||||
modules_max_memory = False
|
||||
if self.opts.get('modules_max_memory', -1) > 0 and HAS_PSUTIL and HAS_RESOURCE:
|
||||
log.debug('modules_max_memory set, enforcing a maximum of {0}'.format(self.opts['modules_max_memory']))
|
||||
modules_max_memory = True
|
||||
old_mem_limit = resource.getrlimit(resource.RLIMIT_AS)
|
||||
rss, vms = psutil.Process(os.getpid()).get_memory_info()
|
||||
mem_limit = rss + vms + self.opts['modules_max_memory']
|
||||
resource.setrlimit(resource.RLIMIT_AS, (mem_limit, mem_limit))
|
||||
elif self.opts.get('modules_max_memory', -1) > 0:
|
||||
if not HAS_PSUTIL:
|
||||
log.error('Unable to enforce modules_max_memory because psutil is missing')
|
||||
if not HAS_RESOURCE:
|
||||
log.error('Unable to enforce modules_max_memory because resource is missing')
|
||||
|
||||
self.opts['grains'] = salt.loader.grains(self.opts)
|
||||
functions = salt.loader.minion_mods(self.opts)
|
||||
returners = salt.loader.returners(self.opts, functions)
|
||||
|
||||
# we're done, reset the limits!
|
||||
if modules_max_memory is True:
|
||||
resource.setrlimit(resource.RLIMIT_AS, old_mem_limit)
|
||||
|
||||
return functions, returners
|
||||
|
||||
def _fire_master(self, data=None, tag=None, events=None, pretag=None):
|
||||
|
@ -553,7 +553,7 @@ def sed(path,
|
||||
escape_all=False,
|
||||
negate_match=False):
|
||||
'''
|
||||
.. deprecated:: 0.17
|
||||
.. deprecated:: 0.17.0
|
||||
Use :py:func:`~salt.modules.file.replace` instead.
|
||||
|
||||
Make a simple edit to a file
|
||||
@ -582,7 +582,7 @@ def sed(path,
|
||||
negate_match : False
|
||||
Negate the search command (``!``)
|
||||
|
||||
.. versionadded:: 0.17
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
Forward slashes and single quotes will be escaped automatically in the
|
||||
``before`` and ``after`` patterns.
|
||||
@ -627,7 +627,7 @@ def sed_contains(path,
|
||||
limit='',
|
||||
flags='g'):
|
||||
'''
|
||||
.. deprecated:: 0.17
|
||||
.. deprecated:: 0.17.0
|
||||
Use :func:`search` instead.
|
||||
|
||||
Return True if the file at ``path`` contains ``text``. Utilizes sed to
|
||||
@ -673,7 +673,7 @@ def psed(path,
|
||||
escape_all=False,
|
||||
multi=False):
|
||||
'''
|
||||
.. deprecated:: 0.17
|
||||
.. deprecated:: 0.17.0
|
||||
Use :py:func:`~salt.modules.file.replace` instead.
|
||||
|
||||
Make a simple edit to a file (pure Python version)
|
||||
@ -785,7 +785,7 @@ def uncomment(path,
|
||||
char='#',
|
||||
backup='.bak'):
|
||||
'''
|
||||
.. deprecated:: 0.17
|
||||
.. deprecated:: 0.17.0
|
||||
Use :py:func:`~salt.modules.file.replace` instead.
|
||||
|
||||
Uncomment specified commented lines in a file
|
||||
@ -824,7 +824,7 @@ def comment(path,
|
||||
char='#',
|
||||
backup='.bak'):
|
||||
'''
|
||||
.. deprecated:: 0.17
|
||||
.. deprecated:: 0.17.0
|
||||
Use :py:func:`~salt.modules.file.replace` instead.
|
||||
|
||||
Comment out specified lines in a file
|
||||
@ -904,7 +904,7 @@ def replace(path,
|
||||
'''
|
||||
Replace occurances of a pattern in a file
|
||||
|
||||
.. versionadded:: 0.17
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
This is a pure Python implementation that wraps Python's :py:func:`~re.sub`.
|
||||
|
||||
@ -997,7 +997,7 @@ def search(path,
|
||||
'''
|
||||
Search for occurances of a pattern in a file
|
||||
|
||||
.. versionadded:: 0.17
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
Params are identical to :py:func:`~salt.modules.file.replace`.
|
||||
|
||||
@ -1057,7 +1057,7 @@ def patch(originalfile, patchfile, options='', dry_run=False):
|
||||
|
||||
def contains(path, text):
|
||||
'''
|
||||
.. deprecated:: 0.17
|
||||
.. deprecated:: 0.17.0
|
||||
Use :func:`search` instead.
|
||||
|
||||
Return ``True`` if the file at ``path`` contains ``text``
|
||||
@ -1084,7 +1084,7 @@ def contains(path, text):
|
||||
|
||||
def contains_regex(path, regex, lchar=''):
|
||||
'''
|
||||
.. deprecated:: 0.17
|
||||
.. deprecated:: 0.17.0
|
||||
Use :func:`search` instead.
|
||||
|
||||
Return True if the given regular expression matches on any line in the text
|
||||
@ -1116,7 +1116,7 @@ def contains_regex(path, regex, lchar=''):
|
||||
|
||||
def contains_regex_multiline(path, regex):
|
||||
'''
|
||||
.. deprecated:: 0.17
|
||||
.. deprecated:: 0.17.0
|
||||
Use :func:`search` instead.
|
||||
|
||||
Return True if the given regular expression matches anything in the text
|
||||
@ -1146,7 +1146,7 @@ def contains_regex_multiline(path, regex):
|
||||
|
||||
def contains_glob(path, glob):
|
||||
'''
|
||||
.. deprecated:: 0.17
|
||||
.. deprecated:: 0.17.0
|
||||
Use :func:`search` instead.
|
||||
|
||||
Return True if the given glob matches a string in the named file
|
||||
@ -1186,7 +1186,23 @@ def append(path, *args):
|
||||
'''
|
||||
# Largely inspired by Fabric's contrib.files.append()
|
||||
|
||||
with salt.utils.fopen(path, "a") as ofile:
|
||||
with salt.utils.fopen(path, "r+") as ofile:
|
||||
# Make sure we have a newline at the end of the file
|
||||
try:
|
||||
ofile.seek(-1, os.SEEK_END)
|
||||
except IOError as exc:
|
||||
if exc.errno == errno.EINVAL:
|
||||
# Empty file, simply append lines at the beginning of the file
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
if ofile.read(1) != '\n':
|
||||
ofile.seek(0, os.SEEK_END)
|
||||
ofile.write('\n')
|
||||
else:
|
||||
ofile.seek(0, os.SEEK_END)
|
||||
# Append lines
|
||||
for line in args:
|
||||
ofile.write('{0}\n'.format(line))
|
||||
|
||||
|
@ -23,20 +23,25 @@ def _parse_return_code_powershell(string):
|
||||
'''
|
||||
return from the input string the return code of the powershell command
|
||||
'''
|
||||
|
||||
|
||||
regex = re.search(r'ReturnValue\s*: (\d*)', string)
|
||||
if not regex:
|
||||
return False
|
||||
else:
|
||||
return int(regex.group(1))
|
||||
|
||||
|
||||
def _psrdp(cmd):
|
||||
'''
|
||||
Create a Win32_TerminalServiceSetting WMI Object as $RDP and execute the command cmd
|
||||
returns the STDOUT of the command
|
||||
Create a Win32_TerminalServiceSetting WMI Object as $RDP and execute the
|
||||
command cmd returns the STDOUT of the command
|
||||
'''
|
||||
rdp = '$RDP = Get-WmiObject -Class Win32_TerminalServiceSetting -Namespace root\\CIMV2\\TerminalServices -Computer . -Authentication 6 -ErrorAction Stop'
|
||||
return __salt__['cmd.run']('{0} ; {1}'.format(rdp, cmd), shell='powershell')
|
||||
rdp = ('$RDP = Get-WmiObject -Class Win32_TerminalServiceSetting '
|
||||
'-Namespace root\\CIMV2\\TerminalServices -Computer . '
|
||||
'-Authentication 6 -ErrorAction Stop')
|
||||
return __salt__['cmd.run']('{0} ; {1}'.format(rdp, cmd),
|
||||
shell='powershell')
|
||||
|
||||
|
||||
def enable():
|
||||
'''
|
||||
@ -49,7 +54,8 @@ def enable():
|
||||
salt '*' rdp.enable
|
||||
'''
|
||||
|
||||
return _parse_return_code_powershell(_psrdp('$RDP.SetAllowTsConnections(1,1)')) == 0
|
||||
return _parse_return_code_powershell(
|
||||
_psrdp('$RDP.SetAllowTsConnections(1,1)')) == 0
|
||||
|
||||
|
||||
def disable():
|
||||
@ -63,7 +69,8 @@ def disable():
|
||||
salt '*' rdp.disable
|
||||
'''
|
||||
|
||||
return _parse_return_code_powershell(_psrdp('$RDP.SetAllowTsConnections(0,1)')) == 0
|
||||
return _parse_return_code_powershell(
|
||||
_psrdp('$RDP.SetAllowTsConnections(0,1)')) == 0
|
||||
|
||||
|
||||
def status():
|
||||
|
@ -233,11 +233,6 @@ def highstate(test=None, queue=False, **kwargs):
|
||||
if conflict:
|
||||
__context__['retcode'] = 1
|
||||
return conflict
|
||||
if not _check_pillar(kwargs):
|
||||
__context__['retcode'] = 5
|
||||
err = ['Pillar failed to render with the following messages:']
|
||||
err += __pillar__['_errors']
|
||||
return err
|
||||
opts = copy.copy(__opts__)
|
||||
|
||||
if salt.utils.test_mode(test=test, **kwargs):
|
||||
@ -256,7 +251,8 @@ def highstate(test=None, queue=False, **kwargs):
|
||||
ret = st_.call_highstate(
|
||||
exclude=kwargs.get('exclude', []),
|
||||
cache=kwargs.get('cache', None),
|
||||
cache_name=kwargs.get('cache_name', 'highstate')
|
||||
cache_name=kwargs.get('cache_name', 'highstate'),
|
||||
force=kwargs.get('force', False)
|
||||
)
|
||||
finally:
|
||||
st_.pop_active()
|
||||
|
@ -498,9 +498,8 @@ class State(object):
|
||||
if 'grains' not in opts:
|
||||
opts['grains'] = salt.loader.grains(opts)
|
||||
self.opts = opts
|
||||
self.opts['pillar'] = self.__gather_pillar()
|
||||
if pillar and isinstance(pillar, dict):
|
||||
self.opts['pillar'].update(pillar)
|
||||
self._pillar_override = pillar
|
||||
self.opts['pillar'] = self._gather_pillar()
|
||||
self.state_con = {}
|
||||
self.load_modules()
|
||||
self.active = set()
|
||||
@ -509,7 +508,7 @@ class State(object):
|
||||
self.__run_num = 0
|
||||
self.jid = jid
|
||||
|
||||
def __gather_pillar(self):
|
||||
def _gather_pillar(self):
|
||||
'''
|
||||
Whenever a state run starts, gather the pillar data fresh
|
||||
'''
|
||||
@ -518,7 +517,10 @@ class State(object):
|
||||
self.opts['grains'],
|
||||
self.opts['id']
|
||||
)
|
||||
return pillar.compile_pillar()
|
||||
ret = pillar.compile_pillar()
|
||||
if self._pillar_override and isinstance(self._pillar_override, dict):
|
||||
ret.update(self._pillar_override)
|
||||
return ret
|
||||
|
||||
def _mod_init(self, low):
|
||||
'''
|
||||
@ -1524,7 +1526,8 @@ class State(object):
|
||||
self.pre[tag] = self.call(low)
|
||||
else:
|
||||
running[tag] = self.call(low)
|
||||
self.event(running[tag])
|
||||
if tag in running:
|
||||
self.event(running[tag])
|
||||
return running
|
||||
|
||||
def call_high(self, high):
|
||||
@ -1912,6 +1915,7 @@ class BaseHighState(object):
|
||||
syncd = self.state.functions['saltutil.sync_all'](list(matches))
|
||||
if syncd['grains']:
|
||||
self.opts['grains'] = salt.loader.grains(self.opts)
|
||||
self.state.opts['pillar'] = self.state._gather_pillar()
|
||||
self.state.module_refresh()
|
||||
|
||||
def render_state(self, sls, env, mods, matches):
|
||||
@ -2238,7 +2242,19 @@ class BaseHighState(object):
|
||||
'Error when rendering state with contents: {0}'.format(state)
|
||||
)
|
||||
|
||||
def call_highstate(self, exclude=None, cache=None, cache_name='highstate'):
|
||||
def _check_pillar(self, force=False):
|
||||
'''
|
||||
Check the pillar for errors, refuse to run the state if there are
|
||||
errors in the pillar and return the pillar errors
|
||||
'''
|
||||
if force:
|
||||
return True
|
||||
if '_errors' in self.state.opts['pillar']:
|
||||
return False
|
||||
return True
|
||||
|
||||
def call_highstate(self, exclude=None, cache=None, cache_name='highstate',
|
||||
force=False):
|
||||
'''
|
||||
Run the sequence to execute the salt highstate for this minion
|
||||
'''
|
||||
@ -2276,15 +2292,19 @@ class BaseHighState(object):
|
||||
ret[tag_name]['comment'] = msg
|
||||
return ret
|
||||
self.load_dynamic(matches)
|
||||
high, errors = self.render_highstate(matches)
|
||||
if exclude:
|
||||
if isinstance(exclude, str):
|
||||
exclude = exclude.split(',')
|
||||
if '__exclude__' in high:
|
||||
high['__exclude__'].extend(exclude)
|
||||
else:
|
||||
high['__exclude__'] = exclude
|
||||
err += errors
|
||||
if not self._check_pillar(force):
|
||||
err += ['Pillar failed to render with the following messages:']
|
||||
err += self.state.opts['pillar']['_errors']
|
||||
else:
|
||||
high, errors = self.render_highstate(matches)
|
||||
if exclude:
|
||||
if isinstance(exclude, str):
|
||||
exclude = exclude.split(',')
|
||||
if '__exclude__' in high:
|
||||
high['__exclude__'].extend(exclude)
|
||||
else:
|
||||
high['__exclude__'] = exclude
|
||||
err += errors
|
||||
if err:
|
||||
return err
|
||||
if not high:
|
||||
|
@ -999,7 +999,7 @@ def managed(name,
|
||||
file of any kind. Ignores hashes and does not use a templating engine.
|
||||
|
||||
contents_pillar
|
||||
.. versionadded:: 0.17
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
Operates like ``contents``, but draws from a value stored in pillar,
|
||||
using the pillar path syntax used in :mod:`pillar.get
|
||||
@ -1739,7 +1739,7 @@ def replace(name,
|
||||
'''
|
||||
Maintain an edit in a file
|
||||
|
||||
.. versionadded:: 0.17
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
Params are identical to :py:func:`~salt.modules.file.replace`.
|
||||
|
||||
@ -1779,7 +1779,7 @@ def sed(name,
|
||||
flags='g',
|
||||
negate_match=False):
|
||||
'''
|
||||
.. deprecated:: 0.17
|
||||
.. deprecated:: 0.17.0
|
||||
Use :py:func:`~salt.states.file.replace` instead.
|
||||
|
||||
Maintain a simple edit to a file
|
||||
@ -1808,7 +1808,7 @@ def sed(name,
|
||||
negate_match : False
|
||||
Negate the search command (``!``)
|
||||
|
||||
.. versionadded:: 0.17
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
Usage::
|
||||
|
||||
|
@ -18,6 +18,7 @@ import yaml
|
||||
# Import salt libs
|
||||
import salt
|
||||
import salt.fileclient
|
||||
from salt.utils.odict import OrderedDict
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -27,6 +28,15 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
# To dump OrderedDict objects as regular dicts. Used by the yaml
|
||||
# template filter.
|
||||
class OrderedDictDumper(yaml.Dumper):
|
||||
pass
|
||||
yaml.add_representer(OrderedDict,
|
||||
yaml.representer.SafeRepresenter.represent_dict,
|
||||
Dumper=OrderedDictDumper)
|
||||
|
||||
|
||||
class SaltCacheLoader(BaseLoader):
|
||||
'''
|
||||
A special jinja Template Loader for salt.
|
||||
@ -223,7 +233,8 @@ class SerializerExtension(Extension, object):
|
||||
return Markup(json.dumps(value, sort_keys=True).strip())
|
||||
|
||||
def format_yaml(self, value):
|
||||
return Markup(yaml.dump(value, default_flow_style=True).strip())
|
||||
return Markup(yaml.dump(value, default_flow_style=True,
|
||||
Dumper=OrderedDictDumper).strip())
|
||||
|
||||
def load_yaml(self, value):
|
||||
if isinstance(value, TemplateModule):
|
||||
|
@ -393,6 +393,21 @@ class CkMinions(object):
|
||||
log.error('Invalid regular expression: {0}'.format(regex))
|
||||
return all(vals)
|
||||
|
||||
def any_auth(self, form, auth_list, fun, tgt=None, tgt_type='glob'):
|
||||
'''
|
||||
Read in the form and determine which auth check routine to execute
|
||||
'''
|
||||
if form == 'publish':
|
||||
return self.auth_check(
|
||||
auth_list,
|
||||
fun,
|
||||
tgt,
|
||||
tgt_type)
|
||||
return self.spec_check(
|
||||
auth_list,
|
||||
fun,
|
||||
form)
|
||||
|
||||
def auth_check(self, auth_list, funs, tgt, tgt_type='glob'):
|
||||
'''
|
||||
Returns a bool which defines if the requested function is authorized.
|
||||
@ -490,3 +505,34 @@ class CkMinions(object):
|
||||
if self.match_check(regex, fun):
|
||||
return True
|
||||
return False
|
||||
|
||||
def spec_check(self, auth_list, fun, form):
|
||||
'''
|
||||
Check special API permissions
|
||||
'''
|
||||
comps = fun.split('.')
|
||||
if len(comps) != 2:
|
||||
return False
|
||||
mod = comps[0]
|
||||
fun = comps[1]
|
||||
for ind in auth_list:
|
||||
if isinstance(ind, str):
|
||||
if ind.startswith('@') and ind[1:] == mod:
|
||||
return True
|
||||
if ind == '@{0}'.format(form):
|
||||
return True
|
||||
if ind == '@{0}s'.format(form):
|
||||
return True
|
||||
elif isinstance(ind, dict):
|
||||
if len(ind) != 1:
|
||||
continue
|
||||
valid = ind.keys()[0]
|
||||
if valid.startswith('@') and valid[1:] == mod:
|
||||
if isinstance(ind[valid], str):
|
||||
if self.match_check(ind[valid], fun):
|
||||
return True
|
||||
elif isinstance(ind[valid], list):
|
||||
for regex in ind[valid]:
|
||||
if self.match_check(regex, fun):
|
||||
return True
|
||||
return False
|
||||
|
@ -1599,6 +1599,10 @@ class SaltSSHOptionParser(OptionParser, ConfigDirMixIn, MergeConfigMixIn,
|
||||
'initial deployment of keys very fast and easy')
|
||||
|
||||
def _mixin_after_parsed(self):
|
||||
if not self.args:
|
||||
self.print_help()
|
||||
self.exit(1)
|
||||
|
||||
if self.options.list:
|
||||
if ',' in self.args[0]:
|
||||
self.config['tgt'] = self.args[0].split(',')
|
||||
|
@ -121,6 +121,31 @@ class FileModuleTestCase(TestCase):
|
||||
newfile.read()
|
||||
)
|
||||
|
||||
def test_append_newline_at_eof(self):
|
||||
'''
|
||||
Check that file.append works consistently on files with and without
|
||||
newlines at end of file.
|
||||
'''
|
||||
# File ending with a newline
|
||||
with tempfile.NamedTemporaryFile() as tfile:
|
||||
tfile.write('foo\n')
|
||||
tfile.flush()
|
||||
filemod.append(tfile.name, 'bar')
|
||||
with open(tfile.name) as tfile2:
|
||||
self.assertEqual(tfile2.read(), 'foo\nbar\n')
|
||||
# File not ending with a newline
|
||||
with tempfile.NamedTemporaryFile() as tfile:
|
||||
tfile.write('foo')
|
||||
tfile.flush()
|
||||
filemod.append(tfile.name, 'bar')
|
||||
with open(tfile.name) as tfile2:
|
||||
self.assertEqual(tfile2.read(), 'foo\nbar\n')
|
||||
# A newline should not be added in empty files
|
||||
with tempfile.NamedTemporaryFile() as tfile:
|
||||
filemod.append(tfile.name, 'bar')
|
||||
with open(tfile.name) as tfile2:
|
||||
self.assertEqual(tfile2.read(), 'bar\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
|
Loading…
Reference in New Issue
Block a user