Large diffs are not rendered by default.

@@ -60,3 +60,10 @@ Options
.. option:: --no-color

Disable all colored output

See also
========

:manpage:`salt(1)`
:manpage:`salt-master(1)`
:manpage:`salt-minion(1)`
@@ -78,3 +78,10 @@ Options
The location of the salt master configuration file, the salt master
settings are required to know where the connections are;
default=/etc/salt/master

See also
========

:manpage:`salt(1)`
:manpage:`salt-master(1)`
:manpage:`salt-minion(1)`
@@ -44,3 +44,10 @@ Options
Console log level. One of ``info``, ``none``, ``garbage``,
``trace``, ``warning``, ``error``, ``debug``. For the logfile
settings see the config file. Default: ``warning``.

See also
========

:manpage:`salt(1)`
:manpage:`salt(7)`
:manpage:`salt-minion(1)`
@@ -45,3 +45,10 @@ Options
Console log level. One of ``info``, ``none``, ``garbage``,
``trace``, ``warning``, ``error``, ``debug``. For the logfile
settings see the config file. Default: ``warning``.

See also
========

:manpage:`salt(1)`
:manpage:`salt(7)`
:manpage:`salt-master(1)`
@@ -32,3 +32,10 @@ Options
The location of the salt master configuration file, the salt master
settings are required to know where the connections are;
default=/etc/salt/master

See also
========

:manpage:`salt(1)`
:manpage:`salt-master(1)`
:manpage:`salt-minion(1)`
@@ -40,3 +40,10 @@ Options
.. option:: --minion-config=MINION_CONFIG

The minion configuration file to use, the default is /etc/salt/minion

See also
========

:manpage:`salt(1)`
:manpage:`salt-master(1)`
:manpage:`salt-minion(1)`
File renamed without changes.
@@ -523,6 +523,17 @@ def saltversion():
from salt import __version__
return {'saltversion': __version__}

def shell():
'''
Return the default shell to use on this system
'''
# Provides:
# shell
ret = {'shell': '/bin/sh'}
if 'SHELL' in os.environ:
ret['shell'] = os.environ['SHELL']
return ret

# Relatively complex mini-algorithm to iterate over the various
# sections of dmidecode output and return matches for specific
# lines containing data we want, but only in the right section.
@@ -7,10 +7,11 @@


# Import python libs
import imp
import logging
import os
import imp
import salt
import logging
import tempfile
from salt.exceptions import LoaderError

log = logging.getLogger(__name__)
@@ -246,7 +247,7 @@ def gen_module(self, name, functions, pack=None):
return None
try:
if full.endswith('.pyx') and self.opts['cython_enable']:
mod = pyximport.load_module(name, full, '/tmp')
mod = pyximport.load_module(name, full, tempfile.gettempdir())
else:
fn_, path, desc = imp.find_module(name, self.module_dirs)
mod = imp.load_module(
@@ -339,7 +340,7 @@ def gen_functions(self, pack=None, virtual_enable=True):
mod = pyximport.load_module(
'{0}_{1}'.format(name, self.tag),
names[name],
'/tmp')
tempfile.gettempdir())
else:
fn_, path, desc = imp.find_module(name, self.module_dirs)
mod = imp.load_module(
@@ -7,14 +7,15 @@
import os
import re
import time
import errno
import signal
import shutil
import logging
import hashlib
import tempfile
import datetime
import signal
import multiprocessing
import subprocess
import multiprocessing

# Import zeromq
import zmq
@@ -32,6 +33,7 @@
import salt.payload
import salt.pillar
import salt.state
from salt.utils.debug import enable_sigusr1_handler


log = logging.getLogger(__name__)
@@ -148,6 +150,8 @@ def start(self):
'''
Turn on the master server components
'''
enable_sigusr1_handler()

log.warn('Starting the Salt Master')
clear_old_jobs_proc = multiprocessing.Process(
target=self._clear_old_jobs)
@@ -219,8 +223,15 @@ def run(self):

try:
while True:
package = pull_sock.recv()
pub_sock.send(package)
# Catch and handle EINTR from when this process is sent
# SIGUSR1 gracefully so we don't choke and die horribly
try:
package = pull_sock.recv()
pub_sock.send(package)
except zmq.ZMQError as exc:
if exc.errno == errno.EINTR:
continue
raise exc
except KeyboardInterrupt:
pub_sock.close()
pull_sock.close()
@@ -270,7 +281,13 @@ def __bind(self):

self.workers.bind(self.w_uri)

zmq.device(zmq.QUEUE, self.clients, self.workers)
while True:
try:
zmq.device(zmq.QUEUE, self.clients, self.workers)
except zmq.ZMQError as exc:
if exc.errno == errno.EINTR:
continue
raise exc

def start_publisher(self):
'''
@@ -320,10 +337,16 @@ def __bind(self):
socket.connect(w_uri)

while True:
package = socket.recv()
payload = self.serial.loads(package)
ret = self.serial.dumps(self._handle_payload(payload))
socket.send(ret)
try:
package = socket.recv()
payload = self.serial.loads(package)
ret = self.serial.dumps(self._handle_payload(payload))
socket.send(ret)
# Properly handle EINTR from SIGUSR1
except zmq.ZMQError as exc:
if exc.errno == errno.EINTR:
continue
raise exc
except KeyboardInterrupt:
socket.close()

@@ -976,7 +999,7 @@ def publish(self, clear_load):
# Set up the payload
payload = {'enc': 'aes'}
# Altering the contents of the publish load is serious!! Changes here
# break compatibility with minion/master versions and even tiny
# break compatibility with minion/master versions and even tiny
# additions can have serious implications on the performance of the
# publish commands.
#
@@ -26,6 +26,7 @@
import salt.loader
import salt.utils
import salt.payload
from salt.utils.debug import enable_sigusr1_handler

log = logging.getLogger(__name__)

@@ -475,6 +476,10 @@ def tune_in(self):
socket.setsockopt(zmq.SUBSCRIBE, '')
socket.setsockopt(zmq.IDENTITY, self.opts['id'])
socket.connect(self.master_pub)

# Make sure to gracefully handle SIGUSR1
enable_sigusr1_handler()

if self.opts['sub_timeout']:
last = time.time()
while True:
@@ -216,7 +216,7 @@ def purge(pkg):
return ret_pkgs


def upgrade(refresh=True):
def upgrade(refresh=True, **kwargs):
'''
Upgrades all packages via ``apt-get dist-upgrade``
@@ -56,6 +56,10 @@ def _run(cmd,
if not cwd:
cwd = os.path.expanduser('~{0}'.format('' if not runas else runas))

if not os.path.isfile(shell) or not os.access(shell, os.X_OK):
msg = 'The shell {0} is not available'.format(shell)
raise CommandExecutionError(msg)

# TODO: Figure out the proper way to do this in windows
disable_runas = [
'Windows',
@@ -146,7 +146,7 @@ def _interfaces_ifconfig():
ret = {}

piface = re.compile('^(\w+)')
pmac = re.compile('.*?(?:HWaddr|ether) (.*?)\s')
pmac = re.compile('.*?(?:HWaddr|ether) ([0-9a-fA-F:]+)')
pip = re.compile('.*?(?:inet addr:|inet )(.*?)\s')
pip6 = re.compile('.*?(?:inet6 addr: (.*?)/|inet6 )([0-9a-fA-F:]+)')
pmask = re.compile('.*?(?:Mask:|netmask )(?:(0x[0-9a-fA-F]{8})|([\d\.]+))')
@@ -393,7 +393,7 @@ def hwaddr(interface):
salt '*' network.hwaddr eth0
'''
data = interfaces().get(interface)
if data:
if data and 'hwaddr' in data:
return data['hwaddr']
else:
return None
@@ -30,14 +30,6 @@
cmd:
- run
- unless: echo 'foo' > /tmp/.test
.. warning::
Please be advised that on Unix systems the shell being used by python
to run executions is /bin/sh, this requires that commands are formatted
to execute under /bin/sh. Some capabilities of newer shells such as bash,
zsh and ksh will not always be available on minions.
'''

import grp
@@ -49,7 +41,8 @@ def wait(name,
unless=None,
cwd='/root',
user=None,
group=None):
group=None,
shell=None):
'''
Run the given command only if the watch statement calls it
@@ -74,6 +67,9 @@ def wait(name,
group
The group context to run the command as
shell
The shell to use for execution, defaults to /bin/sh
'''
return {'name': name,
'changes': {},
@@ -86,7 +82,7 @@ def run(name,
cwd='/root',
user=None,
group=None,
shell='/bin/sh',
shell=None,
env=()):
'''
Run a command if certain circumstances are met
@@ -112,6 +108,9 @@ def run(name,
group
The group context to run the command as
shell
The shell to use for execution, defaults to the shell grain
'''
ret = {'name': name,
'changes': {},
@@ -147,7 +146,7 @@ def run(name,

cmd_kwargs = {'cwd': cwd,
'runas': user,
'shell': shell,
'shell': shell or __grains__['shell'],
'env': env}

if onlyif:
@@ -167,7 +166,7 @@ def run(name,
try:
cmd_all = __salt__['cmd.run_all'](name, **cmd_kwargs)
except CommandExecutionError as e:
ret['comment'] = e
ret['comment'] = str(e)
return ret

ret['changes'] = cmd_all
@@ -772,6 +772,7 @@ def managed(name,
template=None,
makedirs=False,
context=None,
replace=True,
defaults=None,
env=None,
**kwargs):
@@ -821,6 +822,10 @@ def managed(name,
directories will be created to facilitate the creation of the named
file.
replace
If this file should be replaced, if false then this command will
be ignored if the file exists already. Default is true.
context
Overrides default context variables passed to the template.
@@ -845,6 +850,13 @@ def managed(name,
ret['result'] = False
return ret

if not replace:
if os.path.exists(name):
ret['comment'] = 'File {0} exists. No changes made'.format(name)
return ret
if not source:
return touch(name, makedirs=makedirs)

if __opts__['test']:
ret['result'], ret['comment'] = _check_managed(
name,
@@ -63,7 +63,8 @@ def _changes(
change['groups'] = groups
if home:
if lusr['home'] != home:
change['home'] = home
if not home is True:
change['home'] = home
if shell:
if lusr['shell'] != shell:
change['shell'] = shell
@@ -0,0 +1,43 @@
'''
Print a stacktrace when sent a SIGUSR1 for debugging
'''

import os
import sys
import time
import signal
import datetime
import tempfile
import traceback

def _makepretty(printout, stack):
'''
Pretty print the stack trace and environment information
for debugging those hard to reproduce user problems. :)
'''
printout.write('======== Salt Debug Stack Trace =========\n')
traceback.print_stack(stack, file=printout)
printout.write('=========================================\n')


def _handle_sigusr1(sig, stack):
'''
Signal handler for SIGUSR1, only available on Unix-like systems
'''
# When running in the foreground, do the right thing
# and spit out the debug info straight to the console
if sys.stderr.isatty():
output = sys.stderr
_makepretty(output, stack)
else:
filename = 'salt-debug-{0}.log'.format(int(time.time()))
destfile = os.path.join(tempfile.gettempdir(), filename)
with open(destfile, 'w') as output:
_makepretty(output, stack)

def enable_sigusr1_handler():
'''
Pretty print a stack trace to the console or a debug log under /tmp
when any of the salt daemons such as salt-master are sent a SIGUSR1
'''
signal.signal(signal.SIGUSR1, _handle_sigusr1)
@@ -495,6 +495,9 @@ def __init__(self, options):
_REQUIRES_STAT: list(),
_REQUIRES_CONTENTS: list()}
for key, value in options.iteritems():
if key.startswith('_'):
# this is a passthrough object, continue
continue
if value is None or len(value) == 0:
raise ValueError('missing value for "{0}" option'.format(key))
try:
@@ -36,23 +36,23 @@ def __enter__(self):
Start a master and minion
'''
self.master_opts = salt.config.master_config(
os.path.join(INTEGRATION_TEST_DIR, 'files/conf/master'))
os.path.join(INTEGRATION_TEST_DIR, 'files', 'conf', 'master'))
self.minion_opts = salt.config.minion_config(
os.path.join(INTEGRATION_TEST_DIR, 'files/conf/minion'))
os.path.join(INTEGRATION_TEST_DIR, 'files', 'conf', 'minion'))
self.smaster_opts = salt.config.master_config(
os.path.join(INTEGRATION_TEST_DIR, 'files/conf/syndic_master'))
os.path.join(INTEGRATION_TEST_DIR, 'files', 'conf', 'syndic_master'))
self.syndic_opts = salt.config.minion_config(
os.path.join(INTEGRATION_TEST_DIR, 'files/conf/syndic'))
os.path.join(INTEGRATION_TEST_DIR, 'files', 'conf', 'syndic'))
self.syndic_opts['_master_conf_file'] = os.path.join(
INTEGRATION_TEST_DIR,
'files/conf/master'
)
# Set up config options that require internal data
self.master_opts['pillar_roots'] = {
'base': [os.path.join(FILES, 'pillar/base')]
'base': [os.path.join(FILES, 'pillar', 'base')]
}
self.master_opts['file_roots'] = {
'base': [os.path.join(FILES, 'file/base')]
'base': [os.path.join(FILES, 'file', 'base')]
}
self.master_opts['ext_pillar'] = [
{'cmd_yaml': 'cat {0}'.format(
@@ -135,7 +135,7 @@ def setUp(self):
self.client = salt.client.LocalClient(
os.path.join(
INTEGRATION_TEST_DIR,
'files/conf/master'
'files', 'conf', 'master'
)
)

@@ -154,7 +154,7 @@ def minion_opts(self):
return salt.config.minion_config(
os.path.join(
INTEGRATION_TEST_DIR,
'files/conf/minion'
'files', 'conf', 'minion'
)
)

@@ -165,7 +165,7 @@ def master_opts(self):
return salt.config.minion_config(
os.path.join(
INTEGRATION_TEST_DIR,
'files/conf/master'
'files', 'conf', 'master'
)
)

@@ -180,7 +180,7 @@ def setUp(self):
self.client = salt.client.LocalClient(
os.path.join(
INTEGRATION_TEST_DIR,
'files/conf/syndic_master'
'files', 'conf', 'syndic_master'
)
)

@@ -216,22 +216,22 @@ def run_salt(self, arg_str):
'''
Execute salt-key
'''
mconf = os.path.join(INTEGRATION_TEST_DIR, 'files/conf/master')
mconf = os.path.join(INTEGRATION_TEST_DIR, 'files', 'conf', 'master')
arg_str = '-c {0} {1}'.format(mconf, arg_str)
return self.run_script('salt', arg_str)

def run_run(self, arg_str):
'''
Execute salt-key
'''
mconf = os.path.join(INTEGRATION_TEST_DIR, 'files/conf/master')
mconf = os.path.join(INTEGRATION_TEST_DIR, 'files', 'conf', 'master')
arg_str = '-c {0} {1}'.format(mconf, arg_str)
return self.run_script('salt-run', arg_str)

def run_key(self, arg_str):
'''
Execute salt-key
'''
mconf = os.path.join(INTEGRATION_TEST_DIR, 'files/conf/master')
mconf = os.path.join(INTEGRATION_TEST_DIR, 'files', 'conf', 'master')
arg_str = '-c {0} {1}'.format(mconf, arg_str)
return self.run_script('salt-key', arg_str)
@@ -32,26 +32,30 @@ def test_ls(self):
'''
grains.ls
'''
check_for = (
'cpuarch',
'cpu_flags',
'cpu_model',
'domain',
'fqdn',
'host',
'kernel',
'kernelrelease',
'localhost',
'mem_total',
'num_cpus',
'os',
'path',
'ps',
'pythonpath',
'pythonversion',
'saltpath',
'saltversion',
'virtual',
)
lsgrains = self.run_function('grains.ls')
self.assertTrue('cpu_model' in lsgrains)
self.assertTrue('cpu_flags' in lsgrains)
self.assertTrue('cpuarch' in lsgrains)
self.assertTrue('domain' in lsgrains)
self.assertTrue('fqdn' in lsgrains)
self.assertTrue('host' in lsgrains)
self.assertTrue('kernel' in lsgrains)
self.assertTrue('kernelrelease' in lsgrains)
self.assertTrue('localhost' in lsgrains)
self.assertTrue('mem_total' in lsgrains)
self.assertTrue('num_cpus' in lsgrains)
self.assertTrue('os' in lsgrains)
self.assertTrue('path' in lsgrains)
self.assertTrue('ps' in lsgrains)
self.assertTrue('pythonpath' in lsgrains)
self.assertTrue('pythonversion' in lsgrains)
self.assertTrue('saltpath' in lsgrains)
self.assertTrue('saltversion' in lsgrains)
self.assertTrue('virtual' in lsgrains)
for grain_name in check_for:
self.assertTrue(grain_name in lsgrains)

if __name__ == "__main__":
loader = TestLoader()
@@ -44,17 +44,22 @@ def test_kwarg(self):
'cheese=spam',
]
)['minion']['ret']
self.assertTrue('__pub_arg' in ret)
self.assertTrue('__pub_id' in ret)
self.assertTrue('__pub_fun' in ret)
self.assertTrue('__pub_jid' in ret)
self.assertTrue('__pub_tgt' in ret)
self.assertTrue('__pub_tgt_type' in ret)
self.assertTrue('__pub_ret' in ret)
self.assertTrue('cheese' in ret)
self.assertEqual(ret['cheese'], 'spam')
check_true = (
'cheese',
'__pub_arg',
'__pub_fun',
'__pub_id',
'__pub_jid',
'__pub_ret',
'__pub_tgt',
'__pub_tgt_type',
)
for name in check_true:
self.assertTrue(name in ret)

self.assertEqual(ret['cheese'], 'spam')
self.assertEqual(ret['__pub_arg'], ['cheese=spam'])
self.assertEqual(ret['__pub_id'], 'minion')
self.assertEqual(ret['__pub_id'], 'minion')
self.assertEqual(ret['__pub_fun'], 'test.kwarg')

def test_reject_minion(self):
@@ -1,10 +1,11 @@
from os import path
from salt.utils.jinja import SaltCacheLoader, get_template
import os
import tempfile
from jinja2 import Environment
from salt.utils.jinja import SaltCacheLoader, get_template

from saltunittest import TestCase

TEMPLATES_DIR = path.dirname(path.abspath(__file__))
TEMPLATES_DIR = os.path.dirname(os.path.abspath(__file__))

class MockFileClient(object):
'''
@@ -15,7 +16,7 @@ def __init__(self, loader=None):
self.requests = []
def get_file(self, template, dest='', makedirs=False, env='base'):
self.requests.append({
'path': template,
'path': template,
'dest': dest,
'makedirs': makedirs,
'env': env
@@ -26,8 +27,9 @@ def test_searchpath(self):
'''
The searchpath is based on the cachedir option and the env parameter
'''
loader = SaltCacheLoader({'cachedir': '/tmp'}, env='test')
assert loader.searchpath == '/tmp/files/test'
tmp = tempfile.gettempdir()
loader = SaltCacheLoader({'cachedir': tmp}, env='test')
assert loader.searchpath == os.path.join(tmp, 'files', 'test')
def test_mockclient(self):
'''
A MockFileClient is used that records all file request normally send to the master.
@@ -37,7 +39,8 @@ def test_mockclient(self):
res = loader.get_source(None, 'hello_simple')
assert len(res) == 3
self.assertEqual(res[0], 'world\n')
self.assertEqual(res[1], '%s/files/test/hello_simple' % TEMPLATES_DIR)
tmpl_dir = os.path.join(TEMPLATES_DIR, 'files', 'test', 'hello_simple')
self.assertEqual(res[1], tmpl_dir)
assert res[2](), "Template up to date?"
assert len(fc.requests)
self.assertEqual(fc.requests[0]['path'], 'salt://hello_simple')
@@ -81,18 +84,18 @@ def test_include_context(self):
class TestGetTemplate(TestCase):
def test_fallback(self):
'''
A Template without loader is returned as fallback
A Template without loader is returned as fallback
if the file is not contained in the searchpath
'''
filename = '%s/files/test/hello_simple' % TEMPLATES_DIR
filename = os.path.join(TEMPLATES_DIR, 'files', 'test', 'hello_simple')
tmpl = get_template(filename, {'cachedir': TEMPLATES_DIR}, env='other')
self.assertEqual(tmpl.render(), 'world')
def test_fallback_noloader(self):
'''
If the fallback is used any attempt to load other templates
will raise a TypeError.
'''
filename = '%s/files/test/hello_import' % TEMPLATES_DIR
filename = os.path.join(TEMPLATES_DIR, 'files', 'test', 'hello_import')
tmpl = get_template(filename, {'cachedir': TEMPLATES_DIR}, env='other')
self.assertRaises(TypeError, tmpl.render)
def test_env(self):
@@ -105,8 +108,8 @@ def test_env(self):
fc = MockFileClient()
# monkey patch file client
_fc = SaltCacheLoader.file_client
SaltCacheLoader.file_client = lambda loader: fc
filename = '%s/files/test/hello_import' % TEMPLATES_DIR
SaltCacheLoader.file_client = lambda loader: fc
filename = os.path.join(TEMPLATES_DIR, 'files', 'test', 'hello_import')
tmpl = get_template(filename, {'cachedir': TEMPLATES_DIR}, env='test')
self.assertEqual(tmpl.render(a='Hi', b='Salt'), 'Hey world !Hi Salt !')
self.assertEqual(fc.requests[0]['path'], 'salt://macro')