mirror of
https://github.com/valitydev/salt.git
synced 2024-11-08 01:18:58 +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
|
# 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 #####
|
||||||
###########################################
|
###########################################
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
'''
|
'''
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
@ -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():
|
||||||
|
@ -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()
|
||||||
|
@ -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:
|
||||||
|
@ -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::
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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(',')
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user