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

This commit is contained in:
elfixit 2013-04-16 20:25:01 +02:00
commit 37158aa1ab
9 changed files with 269 additions and 90 deletions

View File

@ -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

View File

@ -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):
'''

View File

@ -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

View File

@ -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),

View File

@ -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))

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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