Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Mickey Malone 2013-10-08 05:54:08 -05:00
commit 06f12b0664
16 changed files with 363 additions and 67 deletions

View File

@ -119,10 +119,10 @@
# to reconnect immediately, if the socket is disconnected (for example if # to reconnect immediately, if the socket is disconnected (for example if
# the master processes are restarted). In large setups this will have all # the master processes are restarted). In large setups this will have all
# minions reconnect immediately which might flood the master (the ZeroMQ-default # 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. # 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) # trying to reconnect to the master (100ms = 1 second)
# #
# recon_max: the maximum time a socket should wait. each interval the time to wait # 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 5: value from previous interval * 2
# reconnect x: if value >= recon_max, it starts again with recon_default # 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 # recon_randomize: generate a random wait time on minion start. The wait time will
# be a random value between recon_default and recon_default + # be a random value between recon_default and recon_default +
# recon_max. Having all minions reconnect with the same 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 # 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 # change these settings. If all minions have the same values and your
# setup is quite large (several thousand minions), they will still # setup is quite large (several thousand minions), they will still
# flood the master. The desired behaviour is to have timeframe within # 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: # Example on how to use these settings:
# The goal: have all minions reconnect within a 60 second timeframe on a disconnect # 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' # Each minion will have a randomized reconnect value between 'recon_default'
# and 'recon_default + recon_max', which in this example means between 1000ms # 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 # 60000ms (or between 1 and 60 seconds). The generated random-value will be
# doubled after each attempt to reconnect. Lets say the generated random # doubled after each attempt to reconnect. Lets say the generated random
# value is 11 seconds (or 11000ms). # value is 11 seconds (or 11000ms).
# #
# reconnect 1: wait 11 seconds # reconnect 1: wait 11 seconds
# reconnect 2: wait 22 seconds # reconnect 2: wait 22 seconds
@ -238,6 +238,13 @@
# Enable Cython modules searching and loading. (Default: False) # Enable Cython modules searching and loading. (Default: False)
#cython_enable: 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 ##### ##### State Management Settings #####
########################################### ###########################################

View File

@ -17,6 +17,15 @@ Environments
directories. Environments can be made to be self-contained or state directories. Environments can be made to be self-contained or state
trees can be made to bleed through environments. 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 environments in the top file corresponds with the environments defined in
the :conf_master:`file_roots` variable. In a simple, single environment setup 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 you only have the ``base`` environment, and therefore only one state tree. Here

View File

@ -155,6 +155,7 @@ class Authorize(object):
def __init__(self, opts, load, loadauth=None): def __init__(self, opts, load, loadauth=None):
self.opts = salt.config.master_config(opts['conf_file']) self.opts = salt.config.master_config(opts['conf_file'])
self.load = load self.load = load
self.ckminions = salt.utils.minions.CkMinions(opts)
if loadauth is None: if loadauth is None:
self.loadauth = LoadAuth(opts) self.loadauth = LoadAuth(opts)
else: else:
@ -171,6 +172,112 @@ class Authorize(object):
auth_data.append(getattr(self.loadauth.auth)()) auth_data.append(getattr(self.loadauth.auth)())
return auth_data 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): class Resolver(object):
''' '''

View File

@ -33,7 +33,7 @@ RSTR = '_edbc7885e4f9aac9b83b35999b68d015148caf467b78fa39c05f669c0ff89878'
# This shim facilitaites remote salt-call operations # This shim facilitaites remote salt-call operations
# - Explicitly invokes bourne shell for univeral compatibility # - Explicitly invokes bourne shell for univeral compatibility
# #
# 1. Identify a suitable python # 1. Identify a suitable python
# 2. Test for remote salt-call and version if present # 2. Test for remote salt-call and version if present
# 3. Signal to (re)deploy if missing or out of date # 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__) log = logging.getLogger(__name__)
class SSH(object): class SSH(object):
''' '''
Create an SSH execution system Create an SSH execution system
@ -430,8 +431,8 @@ class Single(object):
def run(self, deploy_attempted=False): def run(self, deploy_attempted=False):
''' '''
Execute the routine, the routine can be either: Execute the routine, the routine can be either:
1. Execute a raw shell command 1. Execute a raw shell command
2. Execute a wrapper func 2. Execute a wrapper func
3. Execute a remote Salt command 3. Execute a remote Salt command
If a (re)deploy is needed, then retry the operation after a deploy 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}" "to be root or use sudo:\n {0}"
errors = [ errors = [
("sudo: no tty present and no askpass program specified", ("sudo: no tty present and no askpass program specified",
"sudo expected a password, NOPASSWD required"), "sudo expected a password, NOPASSWD required"),
("Python too old", ("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: sorry, you must have a tty to run sudo",
"sudo is configured with requiretty"), "sudo is configured with requiretty"),
("Failed to open log file", ("Failed to open log file",
perm_error_fmt.format(stderr)), perm_error_fmt.format(stderr)),
("Permission denied:.*/salt", ("Permission denied:.*/salt",
@ -584,7 +585,6 @@ class Single(object):
return error[1] return error[1]
return None return None
def sls_seed(self, mods, env='base', test=None, exclude=None, **kwargs): def sls_seed(self, mods, env='base', test=None, exclude=None, **kwargs):
''' '''
Create the seed file for a state.sls run Create the seed file for a state.sls run

View File

@ -162,6 +162,7 @@ VALID_OPTS = {
'win_repo': str, 'win_repo': str,
'win_repo_mastercachefile': str, 'win_repo_mastercachefile': str,
'win_gitrepos': list, 'win_gitrepos': list,
'modules_max_memory': int,
} }
# default configurations # default configurations
@ -248,12 +249,13 @@ DEFAULT_MINION_OPTS = {
'tcp_keepalive_idle': 300, 'tcp_keepalive_idle': 300,
'tcp_keepalive_cnt': -1, 'tcp_keepalive_cnt': -1,
'tcp_keepalive_intvl': -1, 'tcp_keepalive_intvl': -1,
'modules_max_memory': -1,
} }
DEFAULT_MASTER_OPTS = { DEFAULT_MASTER_OPTS = {
'interface': '0.0.0.0', 'interface': '0.0.0.0',
'publish_port': '4505', 'publish_port': '4505',
'pub_hwm': 1, 'pub_hwm': 100,
'auth_mode': 1, 'auth_mode': 1,
'user': 'root', 'user': 'root',
'worker_threads': 5, 'worker_threads': 5,

View File

@ -1865,6 +1865,16 @@ class ClearFuncs(object):
self.event.fire_event(eload, tagify(prefix='auth')) self.event.fire_event(eload, tagify(prefix='auth'))
return ret 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): def runner(self, clear_load):
''' '''
Send a master control function back to the runner system Send a master control function back to the runner system

View File

@ -36,6 +36,20 @@ try:
except ImportError: except ImportError:
pass 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 # Import salt libs
from salt.exceptions import ( from salt.exceptions import (
AuthenticationError, CommandExecutionError, CommandNotFoundError, AuthenticationError, CommandExecutionError, CommandNotFoundError,
@ -504,9 +518,31 @@ class Minion(object):
Return the functions and the returners loaded up from the loader Return the functions and the returners loaded up from the loader
module 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) self.opts['grains'] = salt.loader.grains(self.opts)
functions = salt.loader.minion_mods(self.opts) functions = salt.loader.minion_mods(self.opts)
returners = salt.loader.returners(self.opts, functions) 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 return functions, returners
def _fire_master(self, data=None, tag=None, events=None, pretag=None): def _fire_master(self, data=None, tag=None, events=None, pretag=None):

View File

@ -553,7 +553,7 @@ def sed(path,
escape_all=False, escape_all=False,
negate_match=False): negate_match=False):
''' '''
.. deprecated:: 0.17 .. deprecated:: 0.17.0
Use :py:func:`~salt.modules.file.replace` instead. Use :py:func:`~salt.modules.file.replace` instead.
Make a simple edit to a file Make a simple edit to a file
@ -582,7 +582,7 @@ def sed(path,
negate_match : False negate_match : False
Negate the search command (``!``) Negate the search command (``!``)
.. versionadded:: 0.17 .. versionadded:: 0.17.0
Forward slashes and single quotes will be escaped automatically in the Forward slashes and single quotes will be escaped automatically in the
``before`` and ``after`` patterns. ``before`` and ``after`` patterns.
@ -627,7 +627,7 @@ def sed_contains(path,
limit='', limit='',
flags='g'): flags='g'):
''' '''
.. deprecated:: 0.17 .. deprecated:: 0.17.0
Use :func:`search` instead. Use :func:`search` instead.
Return True if the file at ``path`` contains ``text``. Utilizes sed to Return True if the file at ``path`` contains ``text``. Utilizes sed to
@ -673,7 +673,7 @@ def psed(path,
escape_all=False, escape_all=False,
multi=False): multi=False):
''' '''
.. deprecated:: 0.17 .. deprecated:: 0.17.0
Use :py:func:`~salt.modules.file.replace` instead. Use :py:func:`~salt.modules.file.replace` instead.
Make a simple edit to a file (pure Python version) Make a simple edit to a file (pure Python version)
@ -785,7 +785,7 @@ def uncomment(path,
char='#', char='#',
backup='.bak'): backup='.bak'):
''' '''
.. deprecated:: 0.17 .. deprecated:: 0.17.0
Use :py:func:`~salt.modules.file.replace` instead. Use :py:func:`~salt.modules.file.replace` instead.
Uncomment specified commented lines in a file Uncomment specified commented lines in a file
@ -824,7 +824,7 @@ def comment(path,
char='#', char='#',
backup='.bak'): backup='.bak'):
''' '''
.. deprecated:: 0.17 .. deprecated:: 0.17.0
Use :py:func:`~salt.modules.file.replace` instead. Use :py:func:`~salt.modules.file.replace` instead.
Comment out specified lines in a file Comment out specified lines in a file
@ -904,7 +904,7 @@ def replace(path,
''' '''
Replace occurances of a pattern in a file 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`. 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 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`. 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): def contains(path, text):
''' '''
.. deprecated:: 0.17 .. deprecated:: 0.17.0
Use :func:`search` instead. Use :func:`search` instead.
Return ``True`` if the file at ``path`` contains ``text`` Return ``True`` if the file at ``path`` contains ``text``
@ -1084,7 +1084,7 @@ def contains(path, text):
def contains_regex(path, regex, lchar=''): def contains_regex(path, regex, lchar=''):
''' '''
.. deprecated:: 0.17 .. deprecated:: 0.17.0
Use :func:`search` instead. Use :func:`search` instead.
Return True if the given regular expression matches on any line in the text 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): def contains_regex_multiline(path, regex):
''' '''
.. deprecated:: 0.17 .. deprecated:: 0.17.0
Use :func:`search` instead. Use :func:`search` instead.
Return True if the given regular expression matches anything in the text 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): def contains_glob(path, glob):
''' '''
.. deprecated:: 0.17 .. deprecated:: 0.17.0
Use :func:`search` instead. Use :func:`search` instead.
Return True if the given glob matches a string in the named file 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() # 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: for line in args:
ofile.write('{0}\n'.format(line)) ofile.write('{0}\n'.format(line))

View File

@ -23,20 +23,25 @@ def _parse_return_code_powershell(string):
''' '''
return from the input string the return code of the powershell command return from the input string the return code of the powershell command
''' '''
regex = re.search(r'ReturnValue\s*: (\d*)', string) regex = re.search(r'ReturnValue\s*: (\d*)', string)
if not regex: if not regex:
return False return False
else: else:
return int(regex.group(1)) return int(regex.group(1))
def _psrdp(cmd): def _psrdp(cmd):
''' '''
Create a Win32_TerminalServiceSetting WMI Object as $RDP and execute the command cmd Create a Win32_TerminalServiceSetting WMI Object as $RDP and execute the
returns the STDOUT of the command command cmd returns the STDOUT of the command
''' '''
rdp = '$RDP = Get-WmiObject -Class Win32_TerminalServiceSetting -Namespace root\\CIMV2\\TerminalServices -Computer . -Authentication 6 -ErrorAction Stop' rdp = ('$RDP = Get-WmiObject -Class Win32_TerminalServiceSetting '
return __salt__['cmd.run']('{0} ; {1}'.format(rdp, cmd), shell='powershell') '-Namespace root\\CIMV2\\TerminalServices -Computer . '
'-Authentication 6 -ErrorAction Stop')
return __salt__['cmd.run']('{0} ; {1}'.format(rdp, cmd),
shell='powershell')
def enable(): def enable():
''' '''
@ -49,7 +54,8 @@ def enable():
salt '*' rdp.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(): def disable():
@ -63,7 +69,8 @@ def disable():
salt '*' rdp.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(): def status():

View File

@ -233,11 +233,6 @@ def highstate(test=None, queue=False, **kwargs):
if conflict: if conflict:
__context__['retcode'] = 1 __context__['retcode'] = 1
return conflict 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__) opts = copy.copy(__opts__)
if salt.utils.test_mode(test=test, **kwargs): if salt.utils.test_mode(test=test, **kwargs):
@ -256,7 +251,8 @@ def highstate(test=None, queue=False, **kwargs):
ret = st_.call_highstate( ret = st_.call_highstate(
exclude=kwargs.get('exclude', []), exclude=kwargs.get('exclude', []),
cache=kwargs.get('cache', None), 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: finally:
st_.pop_active() st_.pop_active()

View File

@ -498,9 +498,8 @@ class State(object):
if 'grains' not in opts: if 'grains' not in opts:
opts['grains'] = salt.loader.grains(opts) opts['grains'] = salt.loader.grains(opts)
self.opts = opts self.opts = opts
self.opts['pillar'] = self.__gather_pillar() self._pillar_override = pillar
if pillar and isinstance(pillar, dict): self.opts['pillar'] = self._gather_pillar()
self.opts['pillar'].update(pillar)
self.state_con = {} self.state_con = {}
self.load_modules() self.load_modules()
self.active = set() self.active = set()
@ -509,7 +508,7 @@ class State(object):
self.__run_num = 0 self.__run_num = 0
self.jid = jid self.jid = jid
def __gather_pillar(self): def _gather_pillar(self):
''' '''
Whenever a state run starts, gather the pillar data fresh Whenever a state run starts, gather the pillar data fresh
''' '''
@ -518,7 +517,10 @@ class State(object):
self.opts['grains'], self.opts['grains'],
self.opts['id'] 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): def _mod_init(self, low):
''' '''
@ -1524,7 +1526,8 @@ class State(object):
self.pre[tag] = self.call(low) self.pre[tag] = self.call(low)
else: else:
running[tag] = self.call(low) running[tag] = self.call(low)
self.event(running[tag]) if tag in running:
self.event(running[tag])
return running return running
def call_high(self, high): def call_high(self, high):
@ -1912,6 +1915,7 @@ class BaseHighState(object):
syncd = self.state.functions['saltutil.sync_all'](list(matches)) syncd = self.state.functions['saltutil.sync_all'](list(matches))
if syncd['grains']: if syncd['grains']:
self.opts['grains'] = salt.loader.grains(self.opts) self.opts['grains'] = salt.loader.grains(self.opts)
self.state.opts['pillar'] = self.state._gather_pillar()
self.state.module_refresh() self.state.module_refresh()
def render_state(self, sls, env, mods, matches): def render_state(self, sls, env, mods, matches):
@ -2238,7 +2242,19 @@ class BaseHighState(object):
'Error when rendering state with contents: {0}'.format(state) '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 Run the sequence to execute the salt highstate for this minion
''' '''
@ -2276,15 +2292,19 @@ class BaseHighState(object):
ret[tag_name]['comment'] = msg ret[tag_name]['comment'] = msg
return ret return ret
self.load_dynamic(matches) self.load_dynamic(matches)
high, errors = self.render_highstate(matches) if not self._check_pillar(force):
if exclude: err += ['Pillar failed to render with the following messages:']
if isinstance(exclude, str): err += self.state.opts['pillar']['_errors']
exclude = exclude.split(',') else:
if '__exclude__' in high: high, errors = self.render_highstate(matches)
high['__exclude__'].extend(exclude) if exclude:
else: if isinstance(exclude, str):
high['__exclude__'] = exclude exclude = exclude.split(',')
err += errors if '__exclude__' in high:
high['__exclude__'].extend(exclude)
else:
high['__exclude__'] = exclude
err += errors
if err: if err:
return err return err
if not high: if not high:

View File

@ -999,7 +999,7 @@ def managed(name,
file of any kind. Ignores hashes and does not use a templating engine. file of any kind. Ignores hashes and does not use a templating engine.
contents_pillar contents_pillar
.. versionadded:: 0.17 .. versionadded:: 0.17.0
Operates like ``contents``, but draws from a value stored in pillar, Operates like ``contents``, but draws from a value stored in pillar,
using the pillar path syntax used in :mod:`pillar.get using the pillar path syntax used in :mod:`pillar.get
@ -1739,7 +1739,7 @@ def replace(name,
''' '''
Maintain an edit in a file Maintain an edit in a file
.. versionadded:: 0.17 .. versionadded:: 0.17.0
Params are identical to :py:func:`~salt.modules.file.replace`. Params are identical to :py:func:`~salt.modules.file.replace`.
@ -1779,7 +1779,7 @@ def sed(name,
flags='g', flags='g',
negate_match=False): negate_match=False):
''' '''
.. deprecated:: 0.17 .. deprecated:: 0.17.0
Use :py:func:`~salt.states.file.replace` instead. Use :py:func:`~salt.states.file.replace` instead.
Maintain a simple edit to a file Maintain a simple edit to a file
@ -1808,7 +1808,7 @@ def sed(name,
negate_match : False negate_match : False
Negate the search command (``!``) Negate the search command (``!``)
.. versionadded:: 0.17 .. versionadded:: 0.17.0
Usage:: Usage::

View File

@ -18,6 +18,7 @@ import yaml
# Import salt libs # Import salt libs
import salt import salt
import salt.fileclient import salt.fileclient
from salt.utils.odict import OrderedDict
log = logging.getLogger(__name__) 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): class SaltCacheLoader(BaseLoader):
''' '''
A special jinja Template Loader for salt. A special jinja Template Loader for salt.
@ -223,7 +233,8 @@ class SerializerExtension(Extension, object):
return Markup(json.dumps(value, sort_keys=True).strip()) return Markup(json.dumps(value, sort_keys=True).strip())
def format_yaml(self, value): 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): def load_yaml(self, value):
if isinstance(value, TemplateModule): if isinstance(value, TemplateModule):

View File

@ -393,6 +393,21 @@ class CkMinions(object):
log.error('Invalid regular expression: {0}'.format(regex)) log.error('Invalid regular expression: {0}'.format(regex))
return all(vals) 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'): def auth_check(self, auth_list, funs, tgt, tgt_type='glob'):
''' '''
Returns a bool which defines if the requested function is authorized. Returns a bool which defines if the requested function is authorized.
@ -490,3 +505,34 @@ class CkMinions(object):
if self.match_check(regex, fun): if self.match_check(regex, fun):
return True return True
return False 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

View File

@ -1599,6 +1599,10 @@ class SaltSSHOptionParser(OptionParser, ConfigDirMixIn, MergeConfigMixIn,
'initial deployment of keys very fast and easy') 'initial deployment of keys very fast and easy')
def _mixin_after_parsed(self): def _mixin_after_parsed(self):
if not self.args:
self.print_help()
self.exit(1)
if self.options.list: if self.options.list:
if ',' in self.args[0]: if ',' in self.args[0]:
self.config['tgt'] = self.args[0].split(',') self.config['tgt'] = self.args[0].split(',')

View File

@ -121,6 +121,31 @@ class FileModuleTestCase(TestCase):
newfile.read() 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__': if __name__ == '__main__':
from integration import run_tests from integration import run_tests