mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
37158aa1ab
@ -18,6 +18,10 @@ A Salt Minion Windows installer can be found here:
|
||||
|
||||
.. admonition:: Download here
|
||||
|
||||
* 0.14.1
|
||||
* http://saltstack.com/downloads/Salt-Minion-0.14.1-win32-Setup.exe
|
||||
* http://saltstack.com/downloads/Salt-Minion-0.14.1-AMD64-Setup.exe
|
||||
|
||||
* 0.14.0
|
||||
* http://saltstack.com/downloads/Salt-Minion-0.14.0-win32-Setup.exe
|
||||
* http://saltstack.com/downloads/Salt-Minion-0.14.0-AMD64-Setup.exe
|
||||
|
107
salt/master.py
107
salt/master.py
@ -992,7 +992,8 @@ class AESFuncs(object):
|
||||
# The minion is not who it says it is!
|
||||
# We don't want to listen to it!
|
||||
log.warn(
|
||||
'Minion id {0} is not who it says it is!'.format(
|
||||
('Minion id {0} is not who it says it is and is attempting '
|
||||
'to issue a peer command').format(
|
||||
clear_load['id']
|
||||
)
|
||||
)
|
||||
@ -1018,50 +1019,25 @@ class AESFuncs(object):
|
||||
if not good:
|
||||
return {}
|
||||
# Set up the publication payload
|
||||
jid = salt.utils.prep_jid(
|
||||
self.opts['cachedir'],
|
||||
self.opts['hash_type'],
|
||||
clear_load.get('nocache', False)
|
||||
)
|
||||
load = {
|
||||
'fun': clear_load['fun'],
|
||||
'arg': clear_load['arg'],
|
||||
'tgt_type': clear_load.get('tgt_type', 'glob'),
|
||||
'expr_form': clear_load.get('tgt_type', 'glob'),
|
||||
'tgt': clear_load['tgt'],
|
||||
'jid': jid,
|
||||
'ret': clear_load['ret'],
|
||||
'id': clear_load['id'],
|
||||
}
|
||||
self.serial.dump(
|
||||
load, salt.utils.fopen(
|
||||
os.path.join(
|
||||
salt.utils.jid_dir(
|
||||
jid,
|
||||
self.opts['cachedir'],
|
||||
self.opts['hash_type']
|
||||
),
|
||||
'.load.p'
|
||||
),
|
||||
'w+')
|
||||
)
|
||||
# Save the load to the ext_job_cace if it is turned on
|
||||
if self.opts['ext_job_cache']:
|
||||
try:
|
||||
fstr = '{0}.save_load'.format(self.opts['ext_job_cache'])
|
||||
self.mminion.returners[fstr](clear_load['jid'], clear_load)
|
||||
except KeyError:
|
||||
log.critical(
|
||||
'The specified returner used for the external job cache '
|
||||
'"{0}" does not have a save_load function!'.format(
|
||||
self.opts['ext_job_cache']
|
||||
)
|
||||
)
|
||||
payload = {'enc': 'aes'}
|
||||
expr_form = 'glob'
|
||||
timeout = 5
|
||||
if 'tmo' in clear_load:
|
||||
try:
|
||||
timeout = int(clear_load['tmo'])
|
||||
load['timeout'] = int(clear_load['tmo'])
|
||||
except ValueError:
|
||||
msg = 'Failed to parse timeout value: {0}'.format(
|
||||
clear_load['tmo'])
|
||||
log.warn(msg)
|
||||
return {}
|
||||
if 'timeout' in clear_load:
|
||||
try:
|
||||
load['timeout'] = int(clear_load['timeout'])
|
||||
except ValueError:
|
||||
msg = 'Failed to parse timeout value: {0}'.format(
|
||||
clear_load['tmo'])
|
||||
@ -1071,51 +1047,26 @@ class AESFuncs(object):
|
||||
if clear_load['tgt_type'].startswith('node'):
|
||||
if clear_load['tgt'] in self.opts['nodegroups']:
|
||||
load['tgt'] = self.opts['nodegroups'][clear_load['tgt']]
|
||||
load['tgt_type'] = 'compound'
|
||||
expr_form = load['tgt_type']
|
||||
load['expr_form_type'] = 'compound'
|
||||
else:
|
||||
return {}
|
||||
else:
|
||||
load['tgt_type'] = clear_load['tgt_type']
|
||||
expr_form = load['tgt_type']
|
||||
if 'timeout' in clear_load:
|
||||
timeout = clear_load['timeout']
|
||||
# Encrypt!
|
||||
payload['load'] = self.crypticle.dumps(load)
|
||||
# Set the subscriber to the the jid before publishing the command
|
||||
self.local.event.subscribe(load['jid'])
|
||||
# Connect to the publisher
|
||||
context = zmq.Context(1)
|
||||
pub_sock = context.socket(zmq.PUSH)
|
||||
pull_uri = 'ipc://{0}'.format(
|
||||
os.path.join(self.opts['sock_dir'], 'publish_pull.ipc')
|
||||
)
|
||||
pub_sock.connect(pull_uri)
|
||||
log.info(('Publishing minion job: #{jid}, func: "{fun}", args:'
|
||||
' "{arg}", target: "{tgt}"').format(**load))
|
||||
pub_sock.send(self.serial.dumps(payload))
|
||||
# Run the client get_returns method based on the form data sent
|
||||
ret_form = clear_load.get('form', 'clean')
|
||||
if ret_form == 'clean':
|
||||
return self.local.get_returns(
|
||||
jid,
|
||||
self.ckminions.check_minions(
|
||||
clear_load['tgt'],
|
||||
expr_form
|
||||
),
|
||||
timeout
|
||||
)
|
||||
elif ret_form == 'full':
|
||||
ret = self.local.get_full_returns(
|
||||
jid,
|
||||
self.ckminions.check_minions(
|
||||
clear_load['tgt'],
|
||||
expr_form
|
||||
),
|
||||
timeout
|
||||
)
|
||||
ret['__jid__'] = jid
|
||||
return ret
|
||||
load['expr_form'] = clear_load['tgt_type']
|
||||
if clear_load.get('form', '') == 'full':
|
||||
load['raw'] = True
|
||||
ret = {}
|
||||
for minion in self.local.cmd_iter(**load):
|
||||
if load.get('raw', False):
|
||||
data = minion
|
||||
if 'jid' in minion:
|
||||
ret['__jid__'] = minion['jid']
|
||||
data['ret'] = data.pop('return')
|
||||
ret[minion['id']] = data
|
||||
else:
|
||||
id_ = minion.keys()[0]
|
||||
ret[id_] = minion[id_].get('ret', None)
|
||||
return ret
|
||||
|
||||
|
||||
def run_func(self, func, load):
|
||||
'''
|
||||
|
@ -7,14 +7,19 @@ import salt.utils
|
||||
|
||||
# Import python libs
|
||||
import logging
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
no_dig = bool(subprocess.call([ 'which', 'dig' ]))
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Generic, should work on any platform
|
||||
'''
|
||||
if no_dig:
|
||||
log.warning("'dig' does not appear to be in PATH. Will return empty lists.")
|
||||
return 'dnsutil'
|
||||
|
||||
|
||||
@ -116,3 +121,140 @@ def _to_seconds(time):
|
||||
return time
|
||||
|
||||
|
||||
def check_IP(x):
|
||||
'''
|
||||
Check that string x is a valid IP
|
||||
'''
|
||||
# This is probably validating. Tacked on the CIDR bit myself.
|
||||
ip_regex = r'(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(/([0-9]|[12][0-9]|3[0-2]))?$'
|
||||
return bool(re.match(ip_regex, x))
|
||||
|
||||
def A(host, nameserver=None):
|
||||
'''
|
||||
Return the A record for 'host'.
|
||||
|
||||
Always returns a list.
|
||||
'''
|
||||
if no_dig:
|
||||
return []
|
||||
dig = [ 'dig', '+short', str(host), 'A' ]
|
||||
if nameserver is not None:
|
||||
dig.append('@'+str(nameserver))
|
||||
cmd = subprocess.Popen(dig, stdout=subprocess.PIPE)
|
||||
if cmd.returncode is not None:
|
||||
log.warning("dig returned exit code '{0}'. Returning empty list as fallback.".format(cmd.returncode))
|
||||
return []
|
||||
else:
|
||||
t = cmd.communicate()[0].split("\n")
|
||||
# last element is always a ''
|
||||
t.pop()
|
||||
# now that we have a list, make sure they are all IPs
|
||||
return [ x for x in t if check_IP(x) ]
|
||||
|
||||
|
||||
def NS(domain, resolve=True, nameserver=None):
|
||||
'''
|
||||
Return a list of IPs of the nameservers for 'domain'
|
||||
|
||||
If 'resolve' is False, don't resolve names.
|
||||
'''
|
||||
if no_dig:
|
||||
return []
|
||||
dig = [ 'dig', '+short', str(domain), 'NS' ]
|
||||
if nameserver is not None:
|
||||
dig.append('@'+str(nameserver))
|
||||
cmd = subprocess.Popen(dig, stdout=subprocess.PIPE)
|
||||
if cmd.returncode is not None:
|
||||
log.warning("dig returned exit code '{0}'. Returning empty list as fallback.".format(cmd.returncode))
|
||||
return []
|
||||
else:
|
||||
t = cmd.communicate()[0].split("\n")
|
||||
# last element is always a ''
|
||||
t.pop()
|
||||
|
||||
if resolve:
|
||||
ret = []
|
||||
for ns in t:
|
||||
for a in A(ns, nameserver):
|
||||
ret.append(a)
|
||||
else:
|
||||
ret = t
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def SPF(domain, record='SPF', nameserver=None):
|
||||
'''
|
||||
Return the allowed IPv4 ranges in the SPF record for 'domain'.
|
||||
|
||||
If record is 'SPF' and the SPF record is empty, the TXT record will be
|
||||
searched automatically. If you know the domain uses TXT and not SPF, specifying
|
||||
that will save a lookup.
|
||||
'''
|
||||
if no_dig:
|
||||
return []
|
||||
def _process(x):
|
||||
'''
|
||||
Parse out valid IP bits of an spf record.
|
||||
'''
|
||||
m = re.match(r"(\+|~)?ip4:([0-9./]+)", x)
|
||||
if m:
|
||||
if check_IP(m.group(2)):
|
||||
return m.group(2)
|
||||
return None
|
||||
|
||||
dig = [ 'dig', '+short', str(domain), record ]
|
||||
if nameserver is not None:
|
||||
dig.append('@'+str(nameserver))
|
||||
cmd = subprocess.Popen(dig, stdout=subprocess.PIPE)
|
||||
if cmd.returncode is not None:
|
||||
log.warning("dig returned exit code '{0}'. Returning empty list as fallback.".format(cmd.returncode))
|
||||
return []
|
||||
else:
|
||||
t = cmd.communicate()[0]
|
||||
if t == "" and record == 'SPF':
|
||||
# empty string is sucessful query, but nothing to return. So, try TXT record.
|
||||
return SPF(domain, 'TXT', nameserver)
|
||||
|
||||
t = re.sub('"', '', t).split()
|
||||
if len(t) == 0 or t[0] != 'v=spf1':
|
||||
return []
|
||||
|
||||
return [ x for x in map(_process, t) if x is not None ]
|
||||
|
||||
|
||||
def MX(domain, ip=False, nameserver=None):
|
||||
'''
|
||||
Return a list of lists for the MX of 'domain'. Example:
|
||||
|
||||
>>> dnsutil.MX('saltstack.org')
|
||||
[ [10, 'mx01.1and1.com.'], [10, 'mx00.1and1.com.'] ]
|
||||
|
||||
If the 'ip' argument is True, resolve IPs for the servers.
|
||||
|
||||
It's limited to one IP, because although in practice it's very rarely a round
|
||||
robin, it is an acceptable configuration and pulling just one IP lets the data
|
||||
be similar to the non-resolved version. If you think an MX has multiple IPs,
|
||||
don't use the resolver here, resolve them in a separate step.
|
||||
'''
|
||||
if no_dig:
|
||||
return []
|
||||
dig = [ 'dig', '+short', str(domain), 'MX' ]
|
||||
if nameserver is not None:
|
||||
dig.append('@'+str(nameserver))
|
||||
cmd = subprocess.Popen(dig, stdout=subprocess.PIPE)
|
||||
if cmd.returncode is not None:
|
||||
log.warning("dig returned exit code '{0}'. Returning empty list as fallback.".format(cmd.returncode))
|
||||
return []
|
||||
else:
|
||||
t = cmd.communicate()[0].split("\n")
|
||||
# last element is always a ''
|
||||
t.pop()
|
||||
t = [ x.split() for x in t ]
|
||||
|
||||
if ip:
|
||||
t = [ (lambda x: [ x[0], A(x[1], nameserver)[0] ])(x) for x in t ]
|
||||
|
||||
return t
|
||||
|
||||
|
||||
|
@ -517,7 +517,7 @@ def sed(path, before, after, limit='', backup='.bak', options='-r -e',
|
||||
flags=flags,
|
||||
path=path)
|
||||
|
||||
return __salt__['cmd.run'](cmd)
|
||||
return __salt__['cmd.run_all'](cmd)
|
||||
|
||||
|
||||
def sed_contains(path, text, limit='', flags='g'):
|
||||
@ -543,7 +543,7 @@ def sed_contains(path, text, limit='', flags='g'):
|
||||
options = options.replace('-r', '-E')
|
||||
|
||||
cmd = r"sed {options} '{limit}s/{before}/$/{flags}' {path}".format(
|
||||
options='-n -r -e',
|
||||
options=options,
|
||||
limit='/{0}/ '.format(limit) if limit else '',
|
||||
before=before,
|
||||
flags='p{0}'.format(flags),
|
||||
|
@ -57,7 +57,7 @@ def install(pkg=None,
|
||||
if not _valid_version():
|
||||
return '"{0}" is not available.'.format('npm.install')
|
||||
|
||||
cmd = 'npm install --json'
|
||||
cmd = 'npm install --silent --json'
|
||||
|
||||
if dir is None:
|
||||
cmd += ' --global'
|
||||
@ -74,6 +74,13 @@ def install(pkg=None,
|
||||
|
||||
while ' -> ' in lines[0]:
|
||||
lines = lines[1:]
|
||||
|
||||
''' Strip all lines until JSON output starts '''
|
||||
for i in lines:
|
||||
if i.startswith("{"):
|
||||
break
|
||||
else:
|
||||
lines = lines[1:]
|
||||
|
||||
return json.loads(''.join(lines))
|
||||
|
||||
|
@ -188,3 +188,14 @@ def off(device):
|
||||
__salt__['cmd.run'](cmd)
|
||||
|
||||
|
||||
def get_mode(device):
|
||||
'''
|
||||
Report whether the quota system for this device is on or off
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' quota.get_mode
|
||||
'''
|
||||
cmd = 'quotaon -p {0}'.format(device)
|
||||
ret = __salt__['cmd.run'](cmd)
|
||||
return ret
|
||||
|
@ -29,7 +29,7 @@ import salt.utils
|
||||
from salt.exceptions import SaltInvocationError
|
||||
from file import check_hash, check_managed, check_perms, contains_regex,\
|
||||
directory_exists, get_managed, makedirs, makedirs_perms, manage_file,\
|
||||
patch, remove, source_list
|
||||
patch, remove, source_list, sed_contains
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -374,11 +374,16 @@ def find(path, **kwargs):
|
||||
return ret
|
||||
|
||||
|
||||
def _sed_esc(string):
|
||||
def _sed_esc(string, escape_all=False):
|
||||
'''
|
||||
Escape single quotes and forward slashes
|
||||
'''
|
||||
return '{0}'.format(string).replace("'", "'\"'\"'").replace("/", "\/")
|
||||
special_chars = "^.[$()|*+?{"
|
||||
string = string.replace("'", "'\"'\"'").replace("/", "\/")
|
||||
if escape_all is True:
|
||||
for char in special_chars:
|
||||
string = string.replace(char, "\\" + char)
|
||||
return string
|
||||
|
||||
|
||||
def sed(path, before, after, limit='', backup='.bak', options='-r -e',
|
||||
@ -521,13 +526,21 @@ def contains(path, text, limit=''):
|
||||
|
||||
.. versionadded:: 0.9.5
|
||||
'''
|
||||
# Largely inspired by Fabric's contrib.files.contains()
|
||||
|
||||
if not os.path.exists(path):
|
||||
return False
|
||||
|
||||
result = __salt__['file.sed'](path, text, '&', limit=limit, backup='',
|
||||
options='-n -r -e', flags='gp')
|
||||
before = _sed_esc(str(text), False)
|
||||
limit = _sed_esc(str(limit), False)
|
||||
options = '-n -r -e'
|
||||
|
||||
cmd = r"sed {options} '{limit}s/{before}/$/{flags}' {path}".format(
|
||||
options=options,
|
||||
limit='/{0}/ '.format(limit) if limit else '',
|
||||
before=before,
|
||||
flags='p{0}'.format(flags),
|
||||
path=path)
|
||||
|
||||
result = __salt__['cmd.run'](cmd)
|
||||
|
||||
return bool(result)
|
||||
|
||||
|
@ -1376,7 +1376,19 @@ def sed(name, before, after, limit='', backup='.bak', options='-r -e',
|
||||
slines = fp_.readlines()
|
||||
|
||||
# should be ok now; perform the edit
|
||||
__salt__['file.sed'](name, before, after, limit, backup, options, flags)
|
||||
retcode = __salt__['file.sed'](name,
|
||||
before,
|
||||
after,
|
||||
limit,
|
||||
backup,
|
||||
options,
|
||||
flags)['retcode']
|
||||
|
||||
if retcode != 0:
|
||||
ret['result'] = False
|
||||
ret['comment'] = ('There was an error running sed. '
|
||||
'Return code {0}').format(retcode)
|
||||
return ret
|
||||
|
||||
with salt.utils.fopen(name, 'rb') as fp_:
|
||||
nlines = fp_.readlines()
|
||||
@ -1390,7 +1402,7 @@ def sed(name, before, after, limit='', backup='.bak', options='-r -e',
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'sed ran without error'
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'sed ran without error, but no changes were made'
|
||||
|
||||
return ret
|
||||
|
@ -112,3 +112,42 @@ def removed(name,
|
||||
ret["comment"] = "Package was successfully removed."
|
||||
|
||||
return ret
|
||||
|
||||
def bootstrap(
|
||||
name,
|
||||
runas=None):
|
||||
'''
|
||||
Bootstraps a node.js application.
|
||||
|
||||
will execute npm install --json on the specified directory
|
||||
|
||||
|
||||
runas
|
||||
The user to run NPM with
|
||||
|
||||
|
||||
'''
|
||||
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
|
||||
|
||||
try:
|
||||
call = __salt__['npm.install'](
|
||||
dir=name,
|
||||
runas=runas,
|
||||
pkg=None
|
||||
)
|
||||
|
||||
except (CommandNotFoundError, CommandExecutionError) as err:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Error Bootstrapping \'{0}\': {1}'.format(name, err)
|
||||
return ret
|
||||
|
||||
if call:
|
||||
ret['result'] = True
|
||||
ret['changes'] = name,'Bootstrapped'
|
||||
ret['comment'] = 'Directory was successfully bootstrapped'
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Could not bootstrap directory'
|
||||
|
||||
return ret
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user