Merge branch 'develop' of git://github.com/saltstack/salt into develop

This commit is contained in:
Eric Poelke 2012-01-25 13:56:32 -08:00
commit c23c5f902a
16 changed files with 390 additions and 119 deletions

View File

@ -28,8 +28,8 @@ Options
.. option:: -L, --list-all .. option:: -L, --list-all
List all public keys on this salt master, both accepted and pending List all public keys on this salt master: accepted, pending,
acceptance. and rejected.
.. option:: -a ACCEPT, --accept=ACCEPT .. option:: -a ACCEPT, --accept=ACCEPT
@ -39,6 +39,14 @@ Options
Accepts all pending public keys. Accepts all pending public keys.
.. option:: -r REJECT, --reject=REJECT
Reject the named minion public key.
.. option:: -R, --reject-all
Rejects all pending public keys.
.. option:: -c CONFIG, --config=CONFIG .. option:: -c CONFIG, --config=CONFIG
The master configuration file needs to be read to determine where the salt The master configuration file needs to be read to determine where the salt

View File

@ -16,6 +16,7 @@ running and the Salt :term:`minions <minion>` point to the master.
* `pyzmq`_ >= 2.1.9 — ZeroMQ Python bindings * `pyzmq`_ >= 2.1.9 — ZeroMQ Python bindings
* `M2Crypto`_ — Python OpenSSL wrapper * `M2Crypto`_ — Python OpenSSL wrapper
* `PyCrypto`_ — The Python cryptography toolkit * `PyCrypto`_ — The Python cryptography toolkit
* `msgpack-python`_ — High-performance message interchange format
* `YAML`_ — Python YAML bindings * `YAML`_ — Python YAML bindings
Optional Dependencies: Optional Dependencies:

11
pkg/rpm/README.fedora Normal file
View File

@ -0,0 +1,11 @@
These packages are *optional* dependencies for salt. By default, they are not included in the salt RPMs.
Install any of these packages to enable the functionality within salt.
MySQL-python
libvirt-python
python-mako
pymongo
python-redis / redis
A semi-canonical list of the optional salt modules can be found at
https://github.com/saltstack/salt/blob/develop/doc/conf.py#L30

View File

@ -9,8 +9,8 @@
%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")} %{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
Name: salt Name: salt
Version: 0.9.4 Version: 0.9.6
Release: 6%{?dist} Release: 2%{?dist}
Summary: A parallel remote execution system Summary: A parallel remote execution system
Group: System Environment/Daemons Group: System Environment/Daemons
@ -23,6 +23,7 @@ Source3: %{name}-minion
Source4: %{name}-master.service Source4: %{name}-master.service
Source5: %{name}-syndic.service Source5: %{name}-syndic.service
Source6: %{name}-minion.service Source6: %{name}-minion.service
Source7: README.fedora
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildArch: noarch BuildArch: noarch
@ -33,6 +34,7 @@ BuildRequires: python26-crypto
BuildRequires: python26-devel BuildRequires: python26-devel
BuildRequires: python26-PyYAML BuildRequires: python26-PyYAML
BuildRequires: python26-m2crypto BuildRequires: python26-m2crypto
BuildRequires: python26-msgpack
Requires: python26-crypto Requires: python26-crypto
Requires: python26-zmq Requires: python26-zmq
@ -40,6 +42,7 @@ Requires: python26-jinja2
Requires: python26-PyYAML Requires: python26-PyYAML
Requires: python26-m2crypto Requires: python26-m2crypto
Requires: python26-PyXML Requires: python26-PyXML
Requires: python26-msgpack
%else %else
@ -48,6 +51,7 @@ BuildRequires: python-crypto
BuildRequires: python-devel BuildRequires: python-devel
BuildRequires: PyYAML BuildRequires: PyYAML
BuildRequires: m2crypto BuildRequires: m2crypto
BuildRequires: python-msgpack
Requires: python-crypto Requires: python-crypto
Requires: python-zmq Requires: python-zmq
@ -55,6 +59,7 @@ Requires: python-jinja2
Requires: PyYAML Requires: PyYAML
Requires: m2crypto Requires: m2crypto
Requires: PyXML Requires: PyXML
Requires: python-msgpack
%endif %endif
@ -71,7 +76,7 @@ BuildRequires: systemd-units
%endif %endif
Requires: MySQL-python libvirt-python yum #Requires: MySQL-python libvirt-python yum
%description %description
Salt is a distributed remote execution system used to execute commands and Salt is a distributed remote execution system used to execute commands and
@ -120,6 +125,11 @@ install -p -m 0644 %{SOURCE5} $RPM_BUILD_ROOT%{_unitdir}/
install -p -m 0644 %{SOURCE6} $RPM_BUILD_ROOT%{_unitdir}/ install -p -m 0644 %{SOURCE6} $RPM_BUILD_ROOT%{_unitdir}/
%endif %endif
install -p %{SOURCE7} .
install -p -m 0640 $RPM_BUILD_ROOT%{_sysconfdir}/salt/minion.template $RPM_BUILD_ROOT%{_sysconfdir}/salt/minion
install -p -m 0640 $RPM_BUILD_ROOT%{_sysconfdir}/salt/master.template $RPM_BUILD_ROOT%{_sysconfdir}/salt/master
%clean %clean
rm -rf $RPM_BUILD_ROOT rm -rf $RPM_BUILD_ROOT
@ -129,6 +139,7 @@ rm -rf $RPM_BUILD_ROOT
%{python_sitelib}/%{name}/* %{python_sitelib}/%{name}/*
%{python_sitelib}/%{name}-%{version}-py?.?.egg-info %{python_sitelib}/%{name}-%{version}-py?.?.egg-info
%doc %{_mandir}/man7/salt.7.* %doc %{_mandir}/man7/salt.7.*
%doc README.fedora
%files -n salt-minion %files -n salt-minion
%defattr(-,root,root) %defattr(-,root,root)
@ -143,7 +154,8 @@ rm -rf $RPM_BUILD_ROOT
%{_unitdir}/salt-minion.service %{_unitdir}/salt-minion.service
%endif %endif
%config(noreplace) /etc/salt/minion %config(noreplace) %{_sysconfdir}/salt/minion
%config %{_sysconfdir}/salt/minion.template
%files -n salt-master %files -n salt-master
%defattr(-,root,root) %defattr(-,root,root)
@ -166,7 +178,8 @@ rm -rf $RPM_BUILD_ROOT
%{_unitdir}/salt-master.service %{_unitdir}/salt-master.service
%{_unitdir}/salt-syndic.service %{_unitdir}/salt-syndic.service
%endif %endif
%config(noreplace) /etc/salt/master %config(noreplace) %{_sysconfdir}/salt/master
%config %{_sysconfdir}/salt/master.template
%if ! (0%{?rhel} >= 7 || 0%{?fedora} >= 15) %if ! (0%{?rhel} >= 7 || 0%{?fedora} >= 15)
@ -242,6 +255,12 @@ fi
%endif %endif
%changelog %changelog
* Tue Jan 24 2012 Clint Savage <herlo1@gmail.com> - 0.9.6-2
- Added README.fedora and removed deps for optional modules
* Sat Jan 21 2012 Clint Savage <herlo1@gmail.com> - 0.9.6-1
- New upstream release
* Sun Jan 8 2012 Clint Savage <herlo1@gmail.com> - 0.9.4-6 * Sun Jan 8 2012 Clint Savage <herlo1@gmail.com> - 0.9.4-6
- Missed some critical elements for SysV and rpmlint cleanup - Missed some critical elements for SysV and rpmlint cleanup

View File

@ -1,6 +1,7 @@
# pip requirements file for Salt
Jinja2 Jinja2
pyzmq
msgpack-python
M2Crypto M2Crypto
pycrypto msgpack-python
PyCrypto
PyYAML PyYAML
pyzmq >= 2.1.9

View File

@ -19,6 +19,22 @@ except ImportError as e:
if e.message != 'No module named _msgpack': if e.message != 'No module named _msgpack':
raise raise
def set_pidfile(pidfile):
'''
Save the pidfile
'''
pdir = os.path.dirname(pidfile)
if not os.path.isdir(pdir):
os.makedirs(pdir)
try:
open(pidfile, 'w+').write(str(os.getpid()))
except IOError:
err = ('Failed to commit the pid file to location {0}, please verify'
' that the location is available').format(pidfile)
log.error(err)
def verify_env(dirs): def verify_env(dirs):
''' '''
Verify that the named directories are in place and that the environment Verify that the named directories are in place and that the environment
@ -103,6 +119,11 @@ class Master(object):
'--user', '--user',
dest='user', dest='user',
help='Specify user to run minion') help='Specify user to run minion')
parser.add_option('--pid-file',
dest='pidfile',
default='/var/run/salt-master.pid',
help=('Specify the location of the pidfile. Default'
' %default'))
parser.add_option('-l', parser.add_option('-l',
'--log-level', '--log-level',
dest='log_level', dest='log_level',
@ -118,7 +139,8 @@ class Master(object):
cli = {'daemon': options.daemon, cli = {'daemon': options.daemon,
'config': options.config, 'config': options.config,
'user': options.user} 'user': options.user,
'pidfile': options.pidfile}
return cli return cli
@ -128,6 +150,7 @@ class Master(object):
''' '''
verify_env([os.path.join(self.opts['pki_dir'], 'minions'), verify_env([os.path.join(self.opts['pki_dir'], 'minions'),
os.path.join(self.opts['pki_dir'], 'minions_pre'), os.path.join(self.opts['pki_dir'], 'minions_pre'),
os.path.join(self.opts['pki_dir'], 'minions_rejected'),
os.path.join(self.opts['cachedir'], 'jobs'), os.path.join(self.opts['cachedir'], 'jobs'),
os.path.dirname(self.opts['log_file']), os.path.dirname(self.opts['log_file']),
self.opts['sock_dir'], self.opts['sock_dir'],
@ -143,6 +166,7 @@ class Master(object):
# Late import so logging works correctly # Late import so logging works correctly
if check_user(self.opts['user'], log): if check_user(self.opts['user'], log):
import salt.master import salt.master
set_pidfile(self.cli['pidfile'])
master = salt.master.Master(self.opts) master = salt.master.Master(self.opts)
if self.cli['daemon']: if self.cli['daemon']:
# Late import so logging works correctly # Late import so logging works correctly

View File

@ -447,6 +447,19 @@ class SaltKey(object):
action='store_true', action='store_true',
help='Accept all pending keys') help='Accept all pending keys')
parser.add_option('-r',
'--reject',
dest='reject',
default='',
help='Reject the specified public key')
parser.add_option('-R',
'--reject-all',
dest='reject_all',
default=False,
action='store_true',
help='Reject all pending keys')
parser.add_option('-p', parser.add_option('-p',
'--print', '--print',
dest='print_', dest='print_',
@ -500,6 +513,8 @@ class SaltKey(object):
opts['list_all'] = options.list_all opts['list_all'] = options.list_all
opts['accept'] = options.accept opts['accept'] = options.accept
opts['accept_all'] = options.accept_all opts['accept_all'] = options.accept_all
opts['reject'] = options.reject
opts['reject_all'] = options.reject_all
opts['print'] = options.print_ opts['print'] = options.print_
opts['print_all'] = options.print_all opts['print_all'] = options.print_all
opts['delete'] = options.delete opts['delete'] = options.delete

View File

@ -43,7 +43,7 @@ class Caller(object):
) )
except (TypeError, CommandExecutionError) as exc: except (TypeError, CommandExecutionError) as exc:
msg = 'Error running \'{0}\': {1}\n' msg = 'Error running \'{0}\': {1}\n'
sys.stderr.write(msg.format(yellow, end, fun, str(exc))) sys.stderr.write(msg.format(fun, str(exc)))
sys.exit(1) sys.exit(1)
except CommandNotFoundError as exc: except CommandNotFoundError as exc:
msg = 'Command not found in \'{0}\': {1}\n' msg = 'Command not found in \'{0}\': {1}\n'

View File

@ -28,6 +28,8 @@ class Key(object):
subdir = '' subdir = ''
if key_type == 'pre': if key_type == 'pre':
subdir = 'minions_pre' subdir = 'minions_pre'
elif key_type == 'rej':
subdir = 'minions_rejected'
elif key_type == 'acc': elif key_type == 'acc':
subdir = 'minions' subdir = 'minions'
dir_ = os.path.join(self.opts['pki_dir'], subdir) dir_ = os.path.join(self.opts['pki_dir'], subdir)
@ -60,12 +62,21 @@ class Key(object):
for key in sorted(self._keys('acc')): for key in sorted(self._keys('acc')):
print utils.GREEN + key + utils.ENDC print utils.GREEN + key + utils.ENDC
def _list_rejected(self):
'''
List the unaccepted keys
'''
print utils.LIGHT_BLUE + 'Rejected:' + utils.ENDC
for key in sorted(self._keys('rej')):
print utils.BLUE + key + utils.ENDC
def _list_all(self): def _list_all(self):
''' '''
List all keys List all keys
''' '''
self._list_pre() self._list_pre()
self._list_accepted() self._list_accepted()
self._list_rejected()
def _print_key(self, name): def _print_key(self, name):
''' '''
@ -88,74 +99,94 @@ class Key(object):
for key in sorted(self._keys('acc', True)): for key in sorted(self._keys('acc', True)):
print ' ' + utils.GREEN + os.path.basename(key) + utils.ENDC print ' ' + utils.GREEN + os.path.basename(key) + utils.ENDC
print open(key, 'r').read() print open(key, 'r').read()
print utils.LIGHT_BLUE + 'Rejected keys:' + utils.ENDC
for key in sorted(self._keys('pre', True)):
print ' ' + utils.BLUE + os.path.basename(key) + utils.ENDC
print open(key, 'r').read()
def _accept(self, key): def _accept(self, key):
''' '''
Accept a specified host's public key Accept a specified host's public key
''' '''
pre_dir = os.path.join(self.opts['pki_dir'], 'minions_pre') (minions_accepted,
minions = os.path.join(self.opts['pki_dir'], 'minions') minions_pre,
if not os.path.isdir(minions): minions_rejected) = self._check_minions_directories()
err = ('The minions directory is not present, ensure that the ' pre = os.listdir(minions_pre)
'master server has been started')
sys.stderr.write(err + '\n')
sys.exit(42)
if not os.path.isdir(pre_dir):
err = ('The minions_pre directory is not present, ensure '
'that the master server has been started')
sys.stderr.write(err + '\n')
sys.exit(42)
pre = os.listdir(pre_dir)
if not pre.count(key): if not pre.count(key):
err = ('The named host is unavailable, please accept an ' err = ('The named host is unavailable, please accept an '
'available key') 'available key')
sys.stderr.write(err + '\n') sys.stderr.write(err + '\n')
sys.exit(43) sys.exit(43)
shutil.move(os.path.join(pre_dir, key), os.path.join(minions, key)) shutil.move(os.path.join(minions_pre, key),
os.path.join(minions_accepted, key))
def _accept_all(self): def _accept_all(self):
''' '''
Accept all keys in pre Accept all keys in pre
''' '''
pre_dir = os.path.join(self.opts['pki_dir'], 'minions_pre') (minions_accepted,
minions = os.path.join(self.opts['pki_dir'], 'minions') minions_pre,
if not os.path.isdir(minions): minions_rejected) = self._check_minions_directories()
err = ('The minions directory is not present, ensure that the ' for key in os.listdir(minions_pre):
'master server has been started')
sys.stderr.write(err + '\n')
sys.exit(42)
if not os.path.isdir(pre_dir):
err = ('The minions_pre directory is not present, ensure that the '
'master server has been started')
sys.stderr.write(err + '\n')
sys.exit(42)
for key in os.listdir(pre_dir):
self._accept(key) self._accept(key)
def _delete_key(self): def _delete_key(self):
''' '''
Delete a key Delete a key
''' '''
pre_dir = os.path.join(self.opts['pki_dir'], 'minions_pre') (minions_accepted,
minions = os.path.join(self.opts['pki_dir'], 'minions') minions_pre,
if not os.path.isdir(minions): minions_rejected) = self._check_minions_directories()
err = ('The minions directory is not present, ensure that the ' pre = os.path.join(minions_pre, self.opts['delete'])
'master server has been started') acc = os.path.join(minions_accepted, self.opts['delete'])
sys.stderr.write(err + '\n') rej= os.path.join(minions_rejected, self.opts['delete'])
sys.exit(42)
if not os.path.isdir(pre_dir):
err = ('The minions_pre directory is not present, ensure that the '
'master server has been started')
sys.stderr.write(err + '\n')
sys.exit(42)
pre = os.path.join(pre_dir, self.opts['delete'])
acc = os.path.join(minions, self.opts['delete'])
if os.path.exists(pre): if os.path.exists(pre):
os.remove(pre) os.remove(pre)
print 'Removed pending key %s' % self.opts['delete'] print 'Removed pending key %s' % self.opts['delete']
if os.path.exists(acc): if os.path.exists(acc):
os.remove(acc) os.remove(acc)
print 'Removed accepted key %s' % self.opts['delete'] print 'Removed accepted key %s' % self.opts['delete']
if os.path.exists(rej):
os.remove(rej)
print 'Removed rejected key %s' % self.opts['delete']
def _reject(self, key):
'''
Reject a specified host's public key
'''
(minions_accepted,
minions_pre,
minions_rejected) = self._check_minions_directories()
pre = os.listdir(minions_pre)
if not pre.count(key):
err = ('The named host is unavailable, please accept an '
'available key')
sys.stderr.write(err + '\n')
sys.exit(43)
shutil.move(os.path.join(minions_pre, key),
os.path.join(minions_rejected, key))
def _reject_all(self):
'''
Reject all keys in pre
'''
(minions_accepted,
minions_pre,
minions_rejected) = self._check_minions_directories()
for key in os.listdir(minions_pre):
self._reject(key)
def _check_minions_directories(self):
minions_accepted = os.path.join(self.opts['pki_dir'], 'minions')
minions_pre = os.path.join(self.opts['pki_dir'], 'minions_pre')
minions_rejected = os.path.join(self.opts['pki_dir'], 'minions_rejected')
for dir in [minions_accepted, minions_pre, minions_rejected]:
if not os.path.isdir(dir):
err = ('The minions directory {0} is not present, ensure '
'that the master server has been started'.format(dir))
sys.stderr.write(err + '\n')
sys.exit(42)
return minions_accepted, minions_pre, minions_rejected
def run(self): def run(self):
''' '''
@ -179,6 +210,10 @@ class Key(object):
self._accept(self.opts['accept']) self._accept(self.opts['accept'])
elif self.opts['accept_all']: elif self.opts['accept_all']:
self._accept_all() self._accept_all()
elif self.opts['reject']:
self._reject(self.opts['reject'])
elif self.opts['reject_all']:
self._reject_all()
elif self.opts['delete']: elif self.opts['delete']:
self._delete_key() self._delete_key()
else: else:

View File

@ -42,7 +42,7 @@ def prep_jid(opts, load):
os.makedirs(jid_dir) os.makedirs(jid_dir)
serial.dump(load, open(os.path.join(jid_dir, '.load.p'), 'w+')) serial.dump(load, open(os.path.join(jid_dir, '.load.p'), 'w+'))
else: else:
return prep_jid(cachedir, load) return prep_jid(opts['cachedir'], load)
return jid return jid
@ -646,6 +646,9 @@ class ClearFuncs(object):
pubfn_pend = os.path.join(self.opts['pki_dir'], pubfn_pend = os.path.join(self.opts['pki_dir'],
'minions_pre', 'minions_pre',
load['id']) load['id'])
pubfn_rejected = os.path.join(self.opts['pki_dir'],
'minions_rejected',
load['id'])
if self.opts['open_mode']: if self.opts['open_mode']:
# open mode is turned on, nuts to checks and overwrite whatever # open mode is turned on, nuts to checks and overwrite whatever
# is there # is there
@ -661,6 +664,12 @@ class ClearFuncs(object):
ret = {'enc': 'clear', ret = {'enc': 'clear',
'load': {'ret': False}} 'load': {'ret': False}}
return ret return ret
elif os.path.isfile(pubfn_rejected):
# The key has been rejected, don't place it in pending
log.info('Public key rejected for %(id)s', load)
ret = {'enc': 'clear',
'load': {'ret': False}}
return ret
elif not os.path.isfile(pubfn_pend)\ elif not os.path.isfile(pubfn_pend)\
and not self.opts['auto_accept']: and not self.opts['auto_accept']:
# This is a new key, stick it in pre # This is a new key, stick it in pre

View File

@ -208,8 +208,10 @@ class Minion(object):
minion side execution. minion side execution.
''' '''
if self.opts['multiprocessing']: if self.opts['multiprocessing']:
fn_ = os.path.join(self.proc_dir, str(os.getpid())) fn_ = os.path.join(self.proc_dir, data['jid'])
open(fn_, 'w+').write(self.serial.dumps(data)) sdata = {'pid': os.getpid()}
sdata.update(data)
open(fn_, 'w+').write(self.serial.dumps(sdata))
ret = {} ret = {}
for ind in range(0, len(data['arg'])): for ind in range(0, len(data['arg'])):
try: try:
@ -313,7 +315,7 @@ class Minion(object):
Return the data from the executed command to the master server Return the data from the executed command to the master server
''' '''
if self.opts['multiprocessing']: if self.opts['multiprocessing']:
fn_ = os.path.join(self.proc_dir, str(os.getpid())) fn_ = os.path.join(self.proc_dir, ret['jid'])
if os.path.isfile(fn_): if os.path.isfile(fn_):
os.remove(fn_) os.remove(fn_)
log.info('Returning information for job: {0}'.format(ret['jid'])) log.info('Returning information for job: {0}'.format(ret['jid']))
@ -383,19 +385,10 @@ class Minion(object):
Check to see if the salt refresh file has been laid down, if it has, Check to see if the salt refresh file has been laid down, if it has,
refresh the functions and returners. refresh the functions and returners.
''' '''
if os.path.isfile( fn_ = os.path.join(self.opts['cachedir'], 'module_refresh')
os.path.join( if os.path.isfile(fn_):
self.opts['cachedir'], os.remove(fn_)
'.module_refresh'
)
):
self.functions, self.returners = self.__load_modules() self.functions, self.returners = self.__load_modules()
os.remove(
os.path.join(
self.opts['cachedir'],
'.module_refresh'
)
)
def tune_in(self): def tune_in(self):
''' '''

View File

@ -10,6 +10,7 @@ import os
import subprocess import subprocess
import tempfile import tempfile
import salt.utils import salt.utils
from salt.exceptions import CommandExecutionError
try: try:
import pwd import pwd
except: except:
@ -19,16 +20,12 @@ except:
# Set up logging # Set up logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# Set the default working directory to the home directory
# of the user salt-minion is running as. Default: /root
DEFAULT_CWD = os.path.expanduser('~')
# Set up the default outputters # Set up the default outputters
__outputter__ = { __outputter__ = {
'run': 'txt', 'run': 'txt',
} }
def _run(cmd, def _run(cmd,
cwd=DEFAULT_CWD, cwd=None,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
quiet=False, quiet=False,
@ -37,38 +34,49 @@ def _run(cmd,
''' '''
Do the DRY thing and only call subprocess.Popen() once Do the DRY thing and only call subprocess.Popen() once
''' '''
# Don't use runas in Windows # Set the default working directory to the home directory
# of the user salt-minion is running as. Default: /root
if not cwd:
cwd = os.path.expanduser('~{0}'.format('' if not runas else runas))
# TODO: Figure out the proper way to do this in windows
disable_runas = [ disable_runas = [
'Windows', 'Windows',
] ]
ret = {} ret = {}
if runas and __grains__['os'] not in disable_runas: if runas and __grains__['os'] in disable_runas:
msg = 'Sorry, {0} does not support runas functionality'
raise CommandExecutionError(msg.format(__grains__['os']))
if runas:
# Save the original command before munging it
orig_cmd = cmd
try: try:
p = pwd.getpwnam(runas) p = pwd.getpwnam(runas)
except KeyError: except KeyError:
stderr_str = 'The user {0} is not available'.format(runas) msg = 'User \'{0}\' is not available'.format(runas)
if stderr == subprocess.STDOUT: raise CommandExecutionError(msg)
ret['stdout'] = stderr_str
else: cmd_prefix = 'su'
ret['stdout'] = ''
ret['stderr'] = stderr_str # Load the 'nix environment
ret['retcode'] = 1
return ret
cmd_prefix = 'su '
if with_env: if with_env:
cmd_prefix += '- ' cmd_prefix += ' - '
cmd_prefix += runas + ' -c' cmd_prefix += runas + ' -c'
cmd = '%s "%s"' % (cmd_prefix, cmd) cmd = '{0} "{1}"'.format(cmd_prefix, cmd)
if not quiet: if not quiet:
if runas and __grains__['os'] not in disable_runas: # Put the most common case first
log.info('Executing command {0} as user {1} in directory {2}'.format( if not runas:
cmd, runas, cwd))
else:
log.info('Executing command {0} in directory {1}'.format(cmd, cwd)) log.info('Executing command {0} in directory {1}'.format(cmd, cwd))
else:
log.info('Executing command {0} as user {1} in directory {2}'.format(
orig_cmd, runas, cwd))
# This is where the magic happens
proc = subprocess.Popen(cmd, proc = subprocess.Popen(cmd,
cwd=cwd, cwd=cwd,
shell=True, shell=True,
@ -79,19 +87,19 @@ def _run(cmd,
out = proc.communicate() out = proc.communicate()
ret['stdout'] = out[0] ret['stdout'] = out[0]
ret['stderr'] = out[1] ret['stderr'] = out[1]
ret['retcode'] = proc.returncode
ret['pid'] = proc.pid ret['pid'] = proc.pid
ret['retcode'] = proc.returncode
return ret return ret
def _run_quiet(cmd, cwd=DEFAULT_CWD, runas=None): def _run_quiet(cmd, cwd=None, runas=None):
''' '''
Helper for running commands quietly for minion startup Helper for running commands quietly for minion startup
''' '''
return _run(cmd, runas=runas, cwd=cwd, stderr=subprocess.STDOUT, quiet=True)['stdout'] return _run(cmd, runas=runas, cwd=cwd, stderr=subprocess.STDOUT, quiet=True)['stdout']
def run(cmd, cwd=DEFAULT_CWD, runas=None): def run(cmd, cwd=None, runas=None):
''' '''
Execute the passed command and return the output as a string Execute the passed command and return the output as a string
@ -104,7 +112,7 @@ def run(cmd, cwd=DEFAULT_CWD, runas=None):
return out return out
def run_stdout(cmd, cwd=DEFAULT_CWD, runas=None): def run_stdout(cmd, cwd=None, runas=None):
''' '''
Execute a command, and only return the standard out Execute a command, and only return the standard out
@ -117,7 +125,7 @@ def run_stdout(cmd, cwd=DEFAULT_CWD, runas=None):
return stdout return stdout
def run_stderr(cmd, cwd=DEFAULT_CWD, runas=None): def run_stderr(cmd, cwd=None, runas=None):
''' '''
Execute a command and only return the standard error Execute a command and only return the standard error
@ -130,7 +138,7 @@ def run_stderr(cmd, cwd=DEFAULT_CWD, runas=None):
return stderr return stderr
def run_all(cmd, cwd=DEFAULT_CWD, runas=None): def run_all(cmd, cwd=None, runas=None):
''' '''
Execute the passed command and return a dict of return data Execute the passed command and return a dict of return data
@ -150,7 +158,7 @@ def run_all(cmd, cwd=DEFAULT_CWD, runas=None):
return ret return ret
def retcode(cmd, cwd=DEFAULT_CWD, runas=None): def retcode(cmd, cwd=None, runas=None):
''' '''
Execute a shell command and return the command's return code. Execute a shell command and return the command's return code.
@ -181,7 +189,7 @@ def which(cmd):
''' '''
return salt.utils.which(cmd) return salt.utils.which(cmd)
def exec_code(lang, code, cwd=DEFAULT_CWD): def exec_code(lang, code, cwd=None):
''' '''
Pass in two strings, the first naming the executable language, aka - Pass in two strings, the first naming the executable language, aka -
python2, python3, ruby, perl, lua, etc. the second string containing python2, python3, ruby, perl, lua, etc. the second string containing

View File

@ -19,30 +19,47 @@ def _check_puppet():
return __salt__['cmd.has_exec']('puppetd') return __salt__['cmd.has_exec']('puppetd')
def run(): def run(tags=None):
''' '''
Execute a puppet run and return a dict with the stderr,stdout,return code Execute a puppet run and return a dict with the stderr, stdout,
etc. return code, etc. If an argument is specified, it is treated as
a comma separated list of tags passed to puppetd --test --tags:
http://projects.puppetlabs.com/projects/1/wiki/Using_Tags
CLI Example:: CLI Examples::
salt '*' puppet.run salt '*' puppet.run
'''
if _check_puppet():
return __salt__['cmd.run_all']('puppetd --test')
else:
raise CommandNotFoundError("puppetd not available")
def noop(): salt '*' puppet.run basefiles::edit,apache::server
''' '''
Execute a puppet noop run and return a dict with the stderr,stdout,return code if not tags:
etc. cmd = 'puppetd --test'
else:
cmd = 'puppetd --test --tags "{0}"'.format(tags)
if _check_puppet():
return __salt__['cmd.run_all'](cmd)
else:
raise CommandNotFoundError('puppetd not available')
def noop(tags=None):
'''
Execute a puppet noop run and return a dict with the stderr, stdout,
return code, etc. If an argument is specified, it is treated as a
comma separated list of tags passed to puppetd --test --noop --tags
CLI Example:: CLI Example::
salt '*' puppet.noop salt '*' puppet.noop
salt '*' puppet.noop web::server,django::base
''' '''
if _check_puppet(): if not tags:
return __salt__['cmd.run_all']('puppetd --test --noop') cmd = 'puppetd --test --noop'
else: else:
raise CommandNotFoundError("puppetd not available") cmd = 'puppetd --test --tags "{0}" --noop'.format(tags)
if _check_puppet():
return __salt__['cmd.run_all'](cmd)
else:
raise CommandNotFoundError('puppetd not available')

View File

@ -3,11 +3,16 @@ The Saltutil module is used to manage the state of the salt minion itself. It is
used to manage minion modules as well as automate updates to the salt minion used to manage minion modules as well as automate updates to the salt minion
''' '''
# Import Python libs
import os import os
import hashlib import hashlib
import shutil import shutil
import signal
import logging import logging
# Import Salt libs
import salt.payload
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def _sync(form, env): def _sync(form, env):
@ -42,7 +47,7 @@ def _sync(form, env):
shutil.copyfile(fn_, dest) shutil.copyfile(fn_, dest)
ret.append('{0}.{1}'.format(form, os.path.basename(fn_))) ret.append('{0}.{1}'.format(form, os.path.basename(fn_)))
if ret: if ret:
open(os.path.join(__opts__['cachedir'], '.module_refresh'), 'w+').write('') open(os.path.join(__opts__['cachedir'], 'module_refresh'), 'w+').write('')
if __opts__.get('clean_dynamic_modules', True): if __opts__.get('clean_dynamic_modules', True):
current = set(os.listdir(mod_dir)) current = set(os.listdir(mod_dir))
for fn_ in current.difference(remote): for fn_ in current.difference(remote):
@ -138,3 +143,94 @@ def sync_all(env='base'):
ret.append(sync_renderers(env)) ret.append(sync_renderers(env))
ret.append(sync_returners(env)) ret.append(sync_returners(env))
return ret return ret
def running():
'''
Return the data on all running processes salt on the minion
CLI Example::
salt '*' saltutil.running
'''
procs = __salt__['status.procs']()
ret = []
serial = salt.payload.Serial(__opts__)
pid = os.getpid()
proc_dir = os.path.join(__opts__['cachedir'], 'proc')
if not os.path.isdir(proc_dir):
return []
for fn_ in os.listdir(proc_dir):
path = os.path.join(proc_dir, fn_)
data = serial.loads(open(path, 'rb').read())
if not procs.get(str(data['pid'])):
# The process is no longer running, clear out the file and
# continue
os.remove(path)
continue
if data.get('pid') == pid:
continue
ret.append(data)
return ret
def find_job(jid):
'''
Return the data for a specific job id
CLI Example::
salt '*' saltutil.find_job <job id>
'''
for data in running():
if data['jid'] == jid:
return data
return {}
def signal_job(jid, sig):
'''
Sends a signal to the named salt job's process
CLI Example::
salt '*' saltutil.signal_job <job id> 15
'''
for data in running():
if data['jid'] == jid:
try:
os.kill(int(data['pid']), sig)
return 'Signal {0} sent to job {1} at pid {2}'.format(
int(sig),
jid,
data['pid']
)
except OSError:
path = os.path.join(__opts__['cachedir'], 'proc', str(jid))
if os.path.isfile(path):
os.remove(path)
return ('Job {0} was not running and job data has been '
' cleaned up').format(jid)
return ''
def term_job(jid):
'''
Sends a termination signal (SIGTERM 15) to the named salt job's process
CLI Example::
salt '*' saltutil.term_job <job id>
'''
return signal_job(jid, signal.SIGTERM)
def kill_job(jid):
'''
Sends a termination signal (SIGTERM 15) to the named salt job's process
CLI Example::
salt '*' saltutil.kill_job <job id>
'''
return signal_job(jid, signal.SIGKILL)

View File

@ -26,6 +26,40 @@ def _number(text):
return text return text
def procs():
'''
Return the process data
CLI Example::
salt '*' status.procs
'''
# Get the user, pid and cmd
ret = {}
uind = 0
pind = 0
cind = 0
plines = __salt__['cmd.run'](__grains__['ps']).split('\n')
guide = plines.pop(0).split()
if 'USER' in guide:
uind = guide.index('USER')
elif 'UID' in guide:
uind = guide.index('UID')
if 'PID' in guide:
pind = guide.index('PID')
if 'COMMAND' in guide:
cind = guide.index('COMMAND')
elif 'CMD' in guide:
cind = guide.index('CMD')
for line in plines:
if not line:
continue
comps = line.split()
ret[comps[pind]] = {'user': comps[uind],
'cmd': ' '.join(comps[cind:])}
return ret
def custom(): def custom():
''' '''
Return a custom composite of status data and info for this minon, Return a custom composite of status data and info for this minon,

View File

@ -125,13 +125,13 @@ class State(object):
self.load_modules() self.load_modules()
open(os.path.join( open(os.path.join(
self.opts['cachedir'], self.opts['cachedir'],
'.module_refresh'), 'module_refresh'),
'w+').write('') 'w+').write('')
elif data['fun'] == 'recurse': elif data['fun'] == 'recurse':
self.load_modules() self.load_modules()
open(os.path.join( open(os.path.join(
self.opts['cachedir'], self.opts['cachedir'],
'.module_refresh'), 'module_refresh'),
'w+').write('') 'w+').write('')
def format_verbosity(self, returns): def format_verbosity(self, returns):