mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge branch 'master' of github.com:thatch45/salt
This commit is contained in:
commit
878e7e5846
10 changed files with 254 additions and 27 deletions
24
conf/master
24
conf/master
|
@ -90,6 +90,30 @@
|
|||
# The buffer size in the file server can be adjusted here:
|
||||
#file_buffer_size: 1048576
|
||||
|
||||
##### Peer Publish settings #####
|
||||
##########################################
|
||||
# Salt minions can send commands to other minions, but only if the minion is
|
||||
# allowed to. By default "Peer Publication" is disabled, and when enabled it
|
||||
# is enabled for specific minions and specific commands. This allows secure
|
||||
# compartmentalization of commands based on individual minions.
|
||||
#
|
||||
# The configuration uses regular expressions to match minions and then a list
|
||||
# of regular expressions to match functions, the following will allow the
|
||||
# minion authenticated as foo.example.com to execute functions from the test
|
||||
# and pkg modules
|
||||
# peer:
|
||||
# foo.example.com:
|
||||
# - test.*
|
||||
# - pkg.*
|
||||
#
|
||||
# This will allow all minions to execute all commands:
|
||||
# peer:
|
||||
# .*:
|
||||
# - .*
|
||||
# This is not recomanded, since it would allow anyone who gets root on any
|
||||
# single minion to instantly have root on all of the minions!
|
||||
#
|
||||
|
||||
##### Cluster settings #####
|
||||
##########################################
|
||||
# Salt supports automatic clustering, salt creates a single ip address which
|
||||
|
|
14
conf/minion
14
conf/minion
|
@ -69,17 +69,9 @@
|
|||
|
||||
###### Thread settings #####
|
||||
###########################################
|
||||
# Enable multiprocessing support, by default when a minion receives a
|
||||
# publication a new thread is spawned and the command is executed therein. This
|
||||
# is the optimal behavior for the use case where salt is used for data queries
|
||||
# and distributed system management, but not the optimal use case when salt is
|
||||
# used for distributed computation. Since python threads are bad at cpu bound
|
||||
# tasks salt allows for a multiprocessing process to be used for the execution
|
||||
# instead. This adds more initial overhead to publications, but cpu bound
|
||||
# executions will be faster. This feature requires python 2.6 or higher on the
|
||||
# minion, if set to True and python 2.6 or higher is not present then it will
|
||||
# fall back to python threads
|
||||
#multiprocessing: False
|
||||
# Disable multiprocessing support, by default when a minion receives a
|
||||
# publication a new process is spawned and the command is executed therein.
|
||||
#multiprocessing: True
|
||||
|
||||
###### Logging settings #####
|
||||
###########################################
|
||||
|
|
|
@ -4,6 +4,7 @@ Make me some salt!
|
|||
# Import python libs
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
# Import salt libs
|
||||
import salt.config
|
||||
|
||||
|
@ -155,6 +156,98 @@ class Minion(object):
|
|||
salt.utils.daemonize()
|
||||
minion.tune_in()
|
||||
|
||||
|
||||
class Syndic(object):
|
||||
'''
|
||||
Create a syndic server
|
||||
'''
|
||||
def __init__(self):
|
||||
self.cli = self.__parse_cli()
|
||||
self.opts = self.__prep_opts()
|
||||
|
||||
def __prep_opts(self):
|
||||
'''
|
||||
Generate the opts used by the syndic
|
||||
'''
|
||||
opts = salt.config.master_config(self.cli['master_config'])
|
||||
opts['_minion_conf_file'] = opts['conf_file']
|
||||
opts.update(salt.config.minion_config(self.cli['minion_config']))
|
||||
if opts.has_key('syndic_master'):
|
||||
opts['master'] = opts['syndic_master']
|
||||
opts['_master_conf_file'] = opts['conf_file']
|
||||
opts.pop('conf_file')
|
||||
return opts
|
||||
err = 'The syndic_master needs to be configured in the salt master'\
|
||||
+ ' config, EXITING!\n'
|
||||
sys.stderr.write(err)
|
||||
sys.exit(2)
|
||||
|
||||
def __parse_cli(self):
|
||||
'''
|
||||
Parse the cli for options passed to a master daemon
|
||||
'''
|
||||
import salt.log
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option('-d',
|
||||
'--daemon',
|
||||
dest='daemon',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Run the master in a daemon')
|
||||
parser.add_option('--master-config',
|
||||
dest='master_config',
|
||||
default='/etc/salt/master',
|
||||
help='Pass in an alternative master configuration file')
|
||||
parser.add_option('--minion-config',
|
||||
dest='minion_config',
|
||||
default='/etc/salt/minion',
|
||||
help='Pass in an alternative minion configuration file')
|
||||
parser.add_option('-l',
|
||||
'--log-level',
|
||||
dest='log_level',
|
||||
default='warning',
|
||||
choices=salt.log.LOG_LEVELS.keys(),
|
||||
help='Console log level. One of %s. For the logfile settings '
|
||||
'see the config file. Default: \'%%default\'.' %
|
||||
', '.join([repr(l) for l in salt.log.LOG_LEVELS.keys()])
|
||||
)
|
||||
|
||||
options, args = parser.parse_args()
|
||||
salt.log.setup_console_logger(options.log_level)
|
||||
|
||||
cli = {'daemon': options.daemon,
|
||||
'minion_config': options.minion_config,
|
||||
'master_config': options.master_config,
|
||||
}
|
||||
|
||||
return cli
|
||||
|
||||
def start(self):
|
||||
'''
|
||||
Execute this method to start up a syndic.
|
||||
'''
|
||||
verify_env([self.opts['pki_dir'], self.opts['cachedir'],
|
||||
os.path.dirname(self.opts['log_file']),
|
||||
])
|
||||
import salt.log
|
||||
salt.log.setup_logfile_logger(
|
||||
self.opts['log_file'], self.opts['log_level']
|
||||
)
|
||||
for name, level in self.opts['log_granular_levels'].iteritems():
|
||||
salt.log.set_logger_level(name, level)
|
||||
|
||||
import logging
|
||||
|
||||
# Late import so logging works correctly
|
||||
import salt.minion
|
||||
syndic = salt.minion.Syndic(self.opts)
|
||||
if self.cli['daemon']:
|
||||
# Late import so logging works correctly
|
||||
import salt.utils
|
||||
salt.utils.daemonize()
|
||||
syndic.tune_in()
|
||||
|
||||
|
||||
class Monitor(object):
|
||||
'''
|
||||
Create a monitor server
|
||||
|
|
|
@ -112,7 +112,7 @@ class LocalClient(object):
|
|||
'''
|
||||
Execute a salt command and return
|
||||
'''
|
||||
pub_data = self.pub(tgt, fun, arg, expr_form, ret)
|
||||
pub_data = self.pub(tgt, fun, arg, expr_form, ret, timeout)
|
||||
return self.get_full_returns(pub_data['jid'], pub_data['minions'], timeout)
|
||||
|
||||
def get_returns(self, jid, minions, timeout=5):
|
||||
|
@ -231,7 +231,7 @@ class LocalClient(object):
|
|||
'exsel': self._check_grain_minions,
|
||||
}[expr_form](expr)
|
||||
|
||||
def pub(self, tgt, fun, arg=(), expr_form='glob', ret=''):
|
||||
def pub(self, tgt, fun, arg=(), expr_form='glob', ret='', jid='', timeout=5):
|
||||
'''
|
||||
Take the required arguments and publish the given command.
|
||||
Arguments:
|
||||
|
@ -264,14 +264,28 @@ class LocalClient(object):
|
|||
if not minions:
|
||||
return {'jid': '',
|
||||
'minions': minions}
|
||||
package = salt.payload.format_payload('clear',
|
||||
cmd='publish',
|
||||
tgt=tgt,
|
||||
fun=fun,
|
||||
arg=arg,
|
||||
key=self.key,
|
||||
tgt_type=expr_form,
|
||||
ret=ret)
|
||||
if self.opts['order_masters']:
|
||||
package = salt.payload.format_payload(
|
||||
'clear',
|
||||
cmd='publish',
|
||||
tgt=tgt,
|
||||
fun=fun,
|
||||
arg=arg,
|
||||
key=self.key,
|
||||
tgt_type=expr_form,
|
||||
ret=ret,
|
||||
jid=jid,
|
||||
to=timeout)
|
||||
else:
|
||||
package = salt.payload.format_payload(
|
||||
'clear',
|
||||
cmd='publish',
|
||||
tgt=tgt,
|
||||
fun=fun,
|
||||
arg=arg,
|
||||
key=self.key,
|
||||
tgt_type=expr_form,
|
||||
ret=ret)
|
||||
# Prep zmq
|
||||
context = zmq.Context()
|
||||
socket = context.socket(zmq.REQ)
|
||||
|
@ -280,4 +294,3 @@ class LocalClient(object):
|
|||
payload = salt.payload.unpackage(socket.recv())
|
||||
return {'jid': payload['load']['jid'],
|
||||
'minions': minions}
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ def minion_config(path):
|
|||
'states_dirs': [],
|
||||
'render_dirs': [],
|
||||
'open_mode': False,
|
||||
'multiprocessing': False,
|
||||
'multiprocessing': True,
|
||||
'log_file': '/var/log/salt/minion',
|
||||
'log_level': 'warning',
|
||||
'log_granular_levels': {},
|
||||
|
@ -141,6 +141,7 @@ def master_config(path):
|
|||
'auto_accept': False,
|
||||
'renderer': 'yaml_jinja',
|
||||
'state_top': 'top.yml',
|
||||
'order_masters': False,
|
||||
'log_file': '/var/log/salt/master',
|
||||
'log_level': 'warning',
|
||||
'log_granular_levels': {},
|
||||
|
|
|
@ -379,6 +379,18 @@ class AESFuncs(object):
|
|||
Publish a command initiated from a minion, this method executes minion
|
||||
restrictions so that the minion publication will only work if it is
|
||||
enabled in the config.
|
||||
The configuration on the master allows minions to be matched to
|
||||
salt functions, so the minions can only publish allowed salt functions
|
||||
The config will look like this:
|
||||
peer:
|
||||
.*:
|
||||
- .*
|
||||
This configuration will enable all minions to execute all commands.
|
||||
peer:
|
||||
foo.example.com:
|
||||
- test.*
|
||||
This configuration will only allow the minion foo.example.com to
|
||||
execute commands from the test module
|
||||
'''
|
||||
# Verify that the load is valid
|
||||
if not self.opts.has_key('peer'):
|
||||
|
@ -423,7 +435,7 @@ class AESFuncs(object):
|
|||
'ret': clear_load['ret'],
|
||||
}
|
||||
expr_form = 'glob'
|
||||
timeout = 5
|
||||
timeout = 0
|
||||
if clear_load.has_key('tgt_type'):
|
||||
load['tgt_type'] = clear_load['tgt_type']
|
||||
expr_form = load['tgt_type']
|
||||
|
@ -435,11 +447,11 @@ class AESFuncs(object):
|
|||
context = zmq.Context(1)
|
||||
pub_sock = context.socket(zmq.PUSH)
|
||||
pub_sock.connect(
|
||||
'tcp://127.0.0.1:{0}publish_pull_port]'.format(self.opts)
|
||||
'tcp://127.0.0.1:{0[publish_pull_port]}'.format(self.opts)
|
||||
)
|
||||
pub_sock.send(salt.payload.package(payload))
|
||||
# Run the client get_returns method
|
||||
return self.local._get_returns(
|
||||
return self.local.get_returns(
|
||||
jid,
|
||||
self.local.check_minions(
|
||||
clear_load['tgt'],
|
||||
|
@ -602,7 +614,10 @@ class ClearFuncs(object):
|
|||
'''
|
||||
if not clear_load.pop('key') == self.key:
|
||||
return ''
|
||||
jid = prep_jid(self.opts['cachedir'], clear_load)
|
||||
if clear_load.has_key('jid'):
|
||||
jid = clear_load['jid']
|
||||
else:
|
||||
jid = prep_jid(self.opts['cachedir'], clear_load)
|
||||
payload = {'enc': 'aes'}
|
||||
load = {
|
||||
'fun': clear_load['fun'],
|
||||
|
|
|
@ -23,6 +23,7 @@ import salt.utils
|
|||
import salt.modules
|
||||
import salt.returners
|
||||
import salt.loader
|
||||
import salt.client
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -291,6 +292,72 @@ class Minion(object):
|
|||
self._handle_payload(payload)
|
||||
|
||||
|
||||
class Syndic(salt.client.LocalClient, Minion):
|
||||
'''
|
||||
Make a Syndic minion, this minion wil use the minion keys on the master to
|
||||
authenticate with a higher level master.
|
||||
'''
|
||||
def __init__(self, opts):
|
||||
salt.client.LocalClient.__init__(self, opts['_master_conf_file'])
|
||||
Minion.__init__(self, opts)
|
||||
|
||||
def _handle_aes(self, load):
|
||||
'''
|
||||
Takes the aes encrypted load, decrypts is and runs the encapsulated
|
||||
instructions
|
||||
'''
|
||||
data = None
|
||||
# If the AES authentication has changed, re-authenticate
|
||||
try:
|
||||
data = self.crypticle.loads(load)
|
||||
except AuthenticationError:
|
||||
self.authenticate()
|
||||
data = self.crypticle.loads(load)
|
||||
# Verify that the publication is valid
|
||||
if not data.has_key('tgt')\
|
||||
or not data.has_key('jid')\
|
||||
or not data.has_key('fun')\
|
||||
or not data.has_key('to')\
|
||||
or not data.has_key('expr_form')\
|
||||
or not data.has_key('arg'):
|
||||
return
|
||||
self._handle_decoded_payload(data)
|
||||
|
||||
def _handle_decoded_payload(self, data):
|
||||
'''
|
||||
Override this method if you wish to handle the decoded data differently.
|
||||
'''
|
||||
if self.opts['multiprocessing']:
|
||||
multiprocessing.Process(
|
||||
target=lambda: self.syndic_cmd(data)
|
||||
).start()
|
||||
else:
|
||||
threading.Thread(
|
||||
target=lambda: self.syndic_cmd(data)
|
||||
).start()
|
||||
|
||||
def syndic_cmd(self, data):
|
||||
'''
|
||||
Take the now clear load and forward it on to the client cmd
|
||||
'''
|
||||
# Send out the publication
|
||||
pub_data = self.pub(
|
||||
data['tgt'],
|
||||
data['fun'],
|
||||
data['arg'],
|
||||
data['expr_form']
|
||||
)
|
||||
# Gather the return data
|
||||
ret = self.get_returns(
|
||||
pub_data['jid'],
|
||||
pub_data['minions'],
|
||||
data['to']
|
||||
)
|
||||
ret['jid'] = data['jid']
|
||||
ret['fun'] = data['fun']
|
||||
# Return the publication data up the pipe
|
||||
self._return_pub(ret)
|
||||
|
||||
class Matcher(object):
|
||||
'''
|
||||
Use to return the value for matching calls from the master
|
||||
|
|
20
scripts/salt-syndic
Normal file
20
scripts/salt-syndic
Normal file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/python2
|
||||
'''
|
||||
This script is used to kick off a salt syndic daemon
|
||||
'''
|
||||
import salt
|
||||
import os
|
||||
|
||||
def main():
|
||||
'''
|
||||
The main function
|
||||
'''
|
||||
pid = os.getpid()
|
||||
try:
|
||||
syndic = salt.Syndic()
|
||||
syndic.start()
|
||||
except KeyboardInterrupt:
|
||||
os.kill(pid, 15)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
2
setup.py
2
setup.py
|
@ -98,10 +98,12 @@ setup(name=NAME,
|
|||
'salt.runners',
|
||||
'salt.grains',
|
||||
'salt.states',
|
||||
'salt.utils',
|
||||
],
|
||||
scripts=['scripts/salt-master',
|
||||
'scripts/salt-minion',
|
||||
'scripts/salt-monitor',
|
||||
'scripts/salt-syndic',
|
||||
'scripts/salt-key',
|
||||
'scripts/salt-cp',
|
||||
'scripts/salt-call',
|
||||
|
|
Loading…
Add table
Reference in a new issue