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

This commit is contained in:
Pedro Algarvio 2012-10-12 10:57:37 +01:00
commit d6d8e22b68
52 changed files with 2186 additions and 477 deletions

View file

@ -13,8 +13,8 @@
#publish_port: 4505
# Refresh the publisher connections when sending out commands, this is a fix
# for zeromq losing some minion connections. Default: True
#pub_refresh: True
# for zeromq losing some minion connections. Default: False
#pub_refresh: False
# The user to run the salt-master as. Salt will update all permissions to
# allow the specified user to run the master. If the modified files cause
@ -256,6 +256,10 @@
# - hiera: /etc/hiera.yaml
# - cmd_yaml: cat /etc/salt/yaml
#
# The pillar_opts option adds the master configuration file data to a dict in
# the pillar called "master". This is used to set simple configurations in the
# master config file that can then be used on minions.
#pillar_opts: True
##### Syndic settings #####
##########################################

View file

@ -162,12 +162,6 @@
# failure detected in the state execution, defaults to False
#failhard: False
#
# state_verbose allows for the data returned from the minion to be more
# verbose. Normally only states that fail or states that have changes are
# returned, but setting state_verbose to True will return all states that
# were checked
#state_verbose: False
#
# autoload_dynamic_modules Turns on automatic loading of modules found in the
# environments on the master. This is turned on by default, to turn of
# autoloading modules when states run set this value to False

2
debian/rules vendored
View file

@ -4,8 +4,6 @@
dh $@ #--with python2
dh_override_auto_build:
python setup.py build #--install-layout=deb build
override_dh_installinit:
dh_installinit --no-start
#get-orig-source:
# git clone https://github.com/saltstack/salt.git
# mv salt salt-0.9.8

41
debian/salt-common.postrm vendored Normal file
View file

@ -0,0 +1,41 @@
# Purge config files, logs, and directories created after package install.
# Note that user-specified alternate locations for these are not affected.
clean_common() {
# remove shared job cache and other runtime directories
rm -rf /var/cache/salt /var/run/salt /etc/salt /var/log/salt 2> /dev/null
}
clean_conf() {
# remove config and log file for master, minion, or syndic
rm -f /etc/salt/$1 /var/log/salt/$1 2> /dev/null
# XXX add more specific files to purge here XXX #
}
purgefiles() {
case "$pkg" in
master|minion|syndic)
clean_conf $pkg ;;
common)
clean_common ;;
*)
echo "$0 unknown package \`$1'" 1>&2
exit 1 ;;
esac
}
pkg=`echo $0 | cut -f1 -d. | cut -f2 -d-`
case "$1" in
remove)
;;
purge)
purgefiles ;;
upgrade|failed-upgrade|disappear|abort-install|abort-upgrade)
;;
*)
echo "$0 unknown action \`$1'" 1>&2
exit 1 ;;
esac
exit 0

41
debian/salt-master.postrm vendored Normal file
View file

@ -0,0 +1,41 @@
# Purge config files, logs, and directories created after package install.
# Note that user-specified alternate locations for these are not affected.
clean_common() {
# remove shared job cache and other runtime directories
rm -rf /var/cache/salt /var/run/salt /etc/salt /var/log/salt 2> /dev/null
}
clean_conf() {
# remove config and log file for master, minion, or syndic
rm -f /etc/salt/$1 /var/log/salt/$1 2> /dev/null
# XXX add more specific files to purge here XXX #
}
purgefiles() {
case "$pkg" in
master|minion|syndic)
clean_conf $pkg ;;
common)
clean_common ;;
*)
echo "$0 unknown package \`$1'" 1>&2
exit 1 ;;
esac
}
pkg=`echo $0 | cut -f1 -d. | cut -f2 -d-`
case "$1" in
remove)
;;
purge)
purgefiles ;;
upgrade|failed-upgrade|disappear|abort-install|abort-upgrade)
;;
*)
echo "$0 unknown action \`$1'" 1>&2
exit 1 ;;
esac
exit 0

41
debian/salt-minion.postrm vendored Normal file
View file

@ -0,0 +1,41 @@
# Purge config files, logs, and directories created after package install.
# Note that user-specified alternate locations for these are not affected.
clean_common() {
# remove shared job cache and other runtime directories
rm -rf /var/cache/salt /var/run/salt /etc/salt /var/log/salt 2> /dev/null
}
clean_conf() {
# remove config and log file for master, minion, or syndic
rm -f /etc/salt/$1 /var/log/salt/$1 2> /dev/null
# XXX add more specific files to purge here XXX #
}
purgefiles() {
case "$pkg" in
master|minion|syndic)
clean_conf $pkg ;;
common)
clean_common ;;
*)
echo "$0 unknown package \`$1'" 1>&2
exit 1 ;;
esac
}
pkg=`echo $0 | cut -f1 -d. | cut -f2 -d-`
case "$1" in
remove)
;;
purge)
purgefiles ;;
upgrade|failed-upgrade|disappear|abort-install|abort-upgrade)
;;
*)
echo "$0 unknown action \`$1'" 1>&2
exit 1 ;;
esac
exit 0

41
debian/salt-syndic.postrm vendored Normal file
View file

@ -0,0 +1,41 @@
# Purge config files, logs, and directories created after package install.
# Note that user-specified alternate locations for these are not affected.
clean_common() {
# remove shared job cache and other runtime directories
rm -rf /var/cache/salt /var/run/salt /etc/salt /var/log/salt 2> /dev/null
}
clean_conf() {
# remove config and log file for master, minion, or syndic
rm -f /etc/salt/$1 /var/log/salt/$1 2> /dev/null
# XXX add more specific files to purge here XXX #
}
purgefiles() {
case "$pkg" in
master|minion|syndic)
clean_conf $pkg ;;
common)
clean_common ;;
*)
echo "$0 unknown package \`$1'" 1>&2
exit 1 ;;
esac
}
pkg=`echo $0 | cut -f1 -d. | cut -f2 -d-`
case "$1" in
remove)
;;
purge)
purgefiles ;;
upgrade|failed-upgrade|disappear|abort-install|abort-upgrade)
;;
*)
echo "$0 unknown action \`$1'" 1>&2
exit 1 ;;
esac
exit 0

View file

@ -145,7 +145,7 @@ html_context = {
'github_downloads': 'https://github.com/saltstack/salt/downloads',
}
html_use_index = False
html_use_index = True
html_last_updated_fmt = '%b %d, %Y'
html_show_sourcelink = False
html_show_sphinx = True

View file

@ -14,6 +14,7 @@ Full Table of Contents
topics/tutorials/modules
topics/tutorials/starting_states
topics/tutorials/states*
topics/eauth*
topics/tutorials/firewall
topics/tutorials/bootstrap_ec2
topics/tutorials/esky

View file

@ -42,6 +42,22 @@ The network port to set up the publication interface
publish_port: 4505
.. conf_master:: pub_refresh
``pub_refresh``
---------------
Default: ``False``
The pub_refresh system manually refreshed the master ZeroMQ publisher. It is
used in some cases where the minions loose connection to the master and it
is solved by restarting the master.
.. code-block:: yaml
pub_refresh: False
.. conf_master:: user
``user``
@ -55,6 +71,32 @@ The user to run the Salt processes
user: root
.. conf_master:: max_open_files
``max_open_files``
------------------
Default: ``max_open_files``
Each minion connecting to the master uses AT LEAST one file descriptor, the
master subscription connection. If enough minions connect you might start
seeing on the console(and then salt-master crashes):
Too many open files (tcp_listener.cpp:335)
Aborted (core dumped)
By default this value will be the one of `ulimit -Hn`, ie, the hard limit for
max open files.
If you wish to set a different value than the default one, uncomment and
configure this setting. Remember that this value CANNOT be higher than the
hard limit. Raising the hard limit depends on your OS and/or distribution,
a good way to find the limit is to search the internet for(for example):
raise max open files hard limit debian
.. code-block:: yaml
max_open_files: 100000
.. conf_master:: worker_threads
``worker_threads``
@ -84,6 +126,19 @@ execution returns and command executions.
ret_port: 4506
.. conf_master:: pidfile
``pidfile``
-----------
Default: ``/var/run/salt-master.pid``
Specify the location of the master pidfile
.. code-block:: yaml
pidfile: /var/run/salt-master.pid
.. conf_master:: root_dir
``root_dir``

View file

@ -263,6 +263,17 @@ This setting requires that ``gcc`` and ``cython`` are installed on the minion
cython_enable: False
.. conf_minion:: providers
``providers``
-------------
Default: (empty)
A module provider can be statically overwritten or extended for the minion via
the providers option. This can be done on an individual basis in an SLS file or
globally here in the minion config.
State Management Settings
-------------------------

View file

@ -5,16 +5,23 @@ Dynamic Module Distribution
.. versionadded:: 0.9.5
Salt Python modules can be distributed automatically via the Salt file server.
Under the root of any environment defined via the file_roots option on the
master server directories corresponding to the type of module can be used.
Under the root of any environment defined via the :conf_master:`file_roots`
option on the master server directories corresponding to the type of module can
be used.
.. glossary::
Module sync
Automatically transfer and load modules, grains, renderers, returners,
states, etc from the master to the minions.
The directories are prepended with an underscore:
1. _modules
2. _grains
3. _renderers
4. _returners
5. _states
1. :file:`_modules`
2. :file:`_grains`
3. :file:`_renderers`
4. :file:`_returners`
5. :file:`_states`
The contents of these directories need to be synced over to the minions after
Python modules have been created in them. There are a number of ways to sync

View file

@ -339,7 +339,7 @@ components.
<ID Declaration>:
<State Declaration>:
- <Function>:
- <Function>
- <Function Arg>
- <Function Arg>
- <Function Arg>

View file

@ -20,9 +20,15 @@ module detected for Arch Linux, the systemd module can be used:
- enable: True
- provider: systemd
In this instance the systemd module will replace the service virtual module
which is used by default on Arch Linux, and the httpd service will be set up
using systemd.
In this instance the :py:mod:`~salt.modules.systemd` module will replace the
:py:mod:`~salt.modules.service` basic module which is used by default on Arch
Linux, and the :program:`httpd` service will be set up using
:program:`systemd`.
.. note::
You can also set a provider globally in the minion config
:conf_minion:`providers`.
Arbitrary Module Redirects
==========================
@ -39,7 +45,8 @@ module can be used to provide certain functionality.
- pkg: yumpkg5
- cmd: customcmd
In this example the default pkg module is being redirected to use the *yumpkg5*
module (*yum* via shelling out instead of via the yum API), but is also using
a custom module to invoke commands. This could be used to dramatically change
the behavior of a given state.
In this example the default :py:mod:`~salt.modules.pkg` module is being
redirected to use the :py:mod:`~salt.modules.yumpkg5` module (:program:`yum`
via shelling out instead of via the :program:`yum` Python API), but is also
using a custom module to invoke commands. This could be used to dramatically
change the behavior of a given state.

View file

@ -10,10 +10,17 @@ matches systems should draw from.
Environments
============
.. glossary::
Environment
A configuration that allows conceptually organizing state tree
directories. Environments can be made to be self-contained or state
trees can be made to bleed through environments.
The environments in the top file corresponds with the environments defined in
the file_roots variable. In a simple, single environment setup you only have
the base environment, and therefore only one state tree. Here is a simple
example of file_roots in the master configuration:
the :conf_master:`file_roots` variable. In a simple, single environment setup
you only have the ``base`` environment, and therefore only one state tree. Here
is a simple example of :conf_master:`file_roots` in the master configuration:
.. code-block:: yaml
@ -31,9 +38,9 @@ here is a simple, single environment top file:
- core
- edit
This also means that /srv/salt has a state tree. But if you want to use
This also means that :file:`/srv/salt` has a state tree. But if you want to use
multiple environments, or partition the file server to serve more than
just the state tree, then the file_roots option can be expanded:
just the state tree, then the :conf_master:`file_roots` option can be expanded:
.. code-block:: yaml
@ -67,12 +74,12 @@ Then our top file could reference the environments:
'db*prod*':
- db
In this setup we have state trees in 3 of the 4 environments, and no state
tree in the base environment. Notice that the targets for the minions
In this setup we have state trees in three of the four environments, and no
state tree in the ``base`` environment. Notice that the targets for the minions
specify environment data. In Salt the master determines who is in what
environment, and many environments can be crossed together. For instance,
a separate global state tree could be added to the base environment if
it suits your deployment:
environment, and many environments can be crossed together. For instance, a
separate global state tree could be added to the ``base`` environment if it
suits your deployment:
.. code-block:: yaml

View file

@ -58,7 +58,7 @@ Running Salt
There is also a full :doc:`troubleshooting guide</topics/troubleshooting/index>`
available.
Manage Salt public keys
Manage Salt Public Keys
=======================
Salt manages authentication with RSA public keys. The keys are managed on the

View file

@ -0,0 +1,51 @@
=====================
Access Control System
=====================
.. versionadded:: 0.10.4
Salt maintains a standard system used to open granular control to non
administrative users to execute Salt commands. The access control system
has been applied to all systems used to configure access to non administrative
control interfaces in Salt.These interfaces include, the ``peer`` system, the
``external auth`` system and the ``client acl`` system.
The access control system mandated a standard configuration syntax used in
all of the three aforementioned systems. While this adds functionality to the
configuration in 0.10.4, it does not negate the old configuration.
Now specific functions can be opened up to specific minions from specific users
in the case of external auth and client acls, and for specific minions in the
case of the peer system.
The access controls are manifest using matchers in these configurations:
.. code-block:: yaml
client_acl:
fred:
- web\*:
- pkg.list_pkgs
- test.*
- apache.*
In the above example, fred is able to send commands only to minions which match
the specifieed glb target. This can be expanded to include other functions for
other minions based on standard targets.
.. code-block:: yaml
external_auth:
pam:
dave:
- mongo\*:
- network.*
- log\*:
- network.*
- pkg.*
- 'G@os:RedHat':
- kmod.*
- test.ping
The above allows for all minions to be hit by test.ping by dave, and adds a
few functions for hitting other minions.

View file

@ -0,0 +1,52 @@
==============================
External Authentication System
==============================
Salt 0.10.4 comes with a fantastic new way to open up running Salt commands
to users. This system allows for Salt itself to pass through authentication to
any authentication system (The Unix PAM system was the first) to determine
if a user has permission to execute a Salt command.
The external authentication system allows for specific users to be granted
access to execute specific functions on specific minions. Access is configured
in the master configuration file, and uses the new access control system:
.. code-block:: yaml
external_auth:
pam:
thatch:
- 'web*':
- test.*
- network.*
So, the above allows the user thatch to execute functions in the test and
network modules on the minions that match the web* target.
The external authentication system can then be used from the command line by
any user on the same system as the master with the `-a` option:
.. code-block:: bash
$ salt -a pam web\* test.ping
The system will ask the user for the credentials required buy the
authentication system and then publish the command.
Tokens
------
With external authentication alone the authentication credentials will be
required with every call to Salt. This can be alleviated with Salt tokens.
The tokens are short term authorizations and can be easily created by just
adding a capital T option when authenticating:
.. code-block:: bash
$ salt -T -a pam web\* test.ping
Now a token will be created that has a expiration of, by default, 12 hours.
This token is stored in the active user's home directory and is now sent with
all subsequent communications, so the authentication does not need to be
declared again until the token expires.

View file

@ -6,6 +6,12 @@ Salt comes with an interface to derive information about the underlying system.
This is called the grains interface, because it presents salt with grains of
information.
.. glossary::
Grains
Static bits of information that a minion collects about the system when
the minion first starts.
The grains interface is made available to Salt modules and components so that
the right salt minion commands are automatically available on the right
systems.

View file

@ -54,10 +54,10 @@ Follow these instructions: http://wiki.debian.org/iptables
Once you've found your firewall rules, you'll need to add the two lines below
to allow traffic on ``tcp/4505`` and ``tcp/4506``:
.. code-block:: diff
::
+ -A INPUT -m state --state new -m tcp -p tcp --dport 4505 -j ACCEPT
+ -A INPUT -m state --state new -m tcp -p tcp --dport 4506 -j ACCEPT
-A INPUT -m state --state new -m tcp -p tcp --dport 4505 -j ACCEPT
-A INPUT -m state --state new -m tcp -p tcp --dport 4506 -j ACCEPT
**Ubuntu**
@ -77,10 +77,10 @@ The BSD-family of operating systems uses `packet filter (pf)`_. The following
example describes the additions to ``pf.conf`` needed to access the Salt
master.
.. code-block:: diff
::
+ pass in on $int_if proto tcp from any to $int_if port 4505
+ pass in on $int_if proto tcp from any to $int_if port 4506
pass in on $int_if proto tcp from any to $int_if port 4505
pass in on $int_if proto tcp from any to $int_if port 4506
Once these additions have been made to the ``pf.conf`` the rules will need to
be reloaded. This can be done using the ``pfctl`` command.

View file

@ -42,6 +42,7 @@ class Master(parsers.MasterOptionParser):
os.path.join(self.config['cachedir'], 'jobs'),
os.path.dirname(self.config['log_file']),
self.config['sock_dir'],
self.config['token_dir'],
],
self.config['user'],
permissive=self.config['permissive_pki_access'],

View file

@ -13,10 +13,11 @@ so that any external authentication system can be used inside of Salt
# 6. Interface to verify tokens
# Import Python libs
import os
import hashlib
import time
import logging
import random
import inspect
import getpass
# Import Salt libs
@ -53,9 +54,12 @@ class LoadAuth(object):
except IndexError:
return ''
def auth_call(self, load):
def __auth_call(self, load):
'''
Return the token and set the cache data for use
Do not call this directly! Use the time_auth method to overcome timing
attacks
'''
if not 'eauth' in load:
return False
@ -79,7 +83,7 @@ class LoadAuth(object):
Make sure that all failures happen in the same amount of time
'''
start = time.time()
ret = self.auth_call(load)
ret = self.__auth_call(load)
if ret:
return ret
f_time = time.time() - start
@ -98,33 +102,48 @@ class LoadAuth(object):
'''
Run time_auth and create a token. Return False or the token
'''
ret = time_auth(load)
ret = self.time_auth(load)
if ret is False:
return ret
tok = hashlib.md5(os.urandom(512)).hexdigest()
t_path = os.path.join(opts['token_dir'], tok)
fstr = '{0}.auth'.format(load['eauth'])
tok = str(hashlib.md5(os.urandom(512)).hexdigest())
t_path = os.path.join(self.opts['token_dir'], tok)
while os.path.isfile(t_path):
tok = hashlib.md5(os.urandom(512)).hexdigest()
t_path = os.path.join(opts['token_dir'], tok)
t_path = os.path.join(self.opts['token_dir'], tok)
fcall = salt.utils.format_call(self.auth[fstr], load)
tdata = {'start': time.time(),
'expire': time.time() + self.opts['token_expire'],
'name': fcall['args'][0],}
'name': fcall['args'][0],
'eauth': load['eauth'],
'token': tok}
with open(t_path, 'w+') as fp_:
fp_.write(self.serial.dumps(tdata))
return tok
return tdata
def get_tok(self, tok):
'''
Return the name associate with the token, or False if the token is
not valid
'''
t_path = os.path.join(opts['token_dir'], tok)
t_path = os.path.join(self.opts['token_dir'], tok)
if not os.path.isfile:
return False
return {}
with open(t_path, 'r') as fp_:
return self.serial.loads(fp_.read())
return False
tdata = self.serial.loads(fp_.read())
rm_tok = False
if not 'expire' in tdata:
# invalid token, delete it!
rm_tok = True
if tdata.get('expire', '0') < time.time():
rm_tok = True
if rm_tok:
try:
os.remove(t_path)
return {}
except (IOError, OSError):
pass
return tdata
class Resolver(object):
@ -153,11 +172,34 @@ class Resolver(object):
args = salt.utils.arg_lookup(self.auth[fstr])
for arg in args['args']:
if arg.startswith('pass'):
if arg in self.opts:
ret[arg] = self.opts[arg]
elif arg.startswith('pass'):
ret[arg] = getpass.getpass('{0}: '.format(arg))
else:
ret[arg] = raw_input('{0}: '.format(arg))
for kwarg, default in args['kwargs'].items():
ret[kwarg] = raw_input('{0} [{1}]: '.format(kwarg, default))
if kwarg in self.opts:
ret['kwarg'] = self.opts[kwarg]
else:
ret[kwarg] = raw_input('{0} [{1}]: '.format(kwarg, default))
return ret
def token_cli(self, eauth, load):
'''
Create the token from the cli and request the correct data to
authenticate via the passed authentication mechanism
'''
load['cmd'] = 'mk_token'
load['eauth'] = eauth
sreq = salt.payload.SREQ(
'tcp://{0[interface]}:{0[ret_port]}'.format(self.opts),
)
tdata = sreq.send('clear', load)
try:
with open(self.opts['token_file'], 'w+') as fp_:
fp_.write(tdata['token'])
except (IOError, OSError):
pass
return tdata

View file

@ -58,6 +58,9 @@ class SaltCMD(parsers.SaltCMDOptionParser):
'arg': self.config['arg'],
'timeout': self.options.timeout}
if 'token' in self.config:
kwargs['token'] = self.config['token']
if self.selected_target_option:
kwargs['expr_form'] = self.selected_target_option
else:
@ -66,9 +69,15 @@ class SaltCMD(parsers.SaltCMDOptionParser):
if getattr(self.options, 'return'):
kwargs['ret'] = getattr(self.options, 'return')
print self.options.eauth
if self.options.eauth:
resolver = salt.auth.Resolver(self.config)
res = resolver.cli(self.options.eauth)
if self.options.mktoken:
kwargs['token'] = resolver.token_cli(
self.options.eauth,
res
)['token']
if not res:
sys.exit(2)
kwargs.update(res)

View file

@ -29,12 +29,10 @@ The data structure needs to be:
# This means that the primary client to build is, the LocalClient
import os
import re
import sys
import glob
import time
import getpass
import fnmatch
# Import salt modules
import salt.config
@ -42,7 +40,7 @@ import salt.payload
import salt.utils
import salt.utils.verify
import salt.utils.event
from salt.exceptions import SaltClientError, SaltInvocationError
from salt.exceptions import SaltInvocationError
# Try to import range from https://github.com/ytoolshed/range
RANGE = False
@ -69,8 +67,8 @@ class LocalClient(object):
'''
Connect to the salt master via the local server and via root
'''
def __init__(self, c_path='/etc/salt/master'):
self.opts = salt.config.master_config(c_path)
def __init__(self, c_path='/etc/salt'):
self.opts = salt.config.client_config(c_path)
self.serial = salt.payload.Serial(self.opts)
self.salt_user = self.__get_user()
self.key = self.__read_master_key()
@ -115,129 +113,6 @@ class LocalClient(object):
return user
return user
def _check_glob_minions(self, expr):
'''
Return the minions found by looking via globs
'''
cwd = os.getcwd()
os.chdir(os.path.join(self.opts['pki_dir'], 'minions'))
ret = set(glob.glob(expr))
os.chdir(cwd)
return ret
def _check_list_minions(self, expr):
'''
Return the minions found by looking via a list
'''
ret = []
for fn_ in os.listdir(os.path.join(self.opts['pki_dir'], 'minions')):
if fn_ in expr:
if fn_ not in ret:
ret.append(fn_)
return ret
def _check_pcre_minions(self, expr):
'''
Return the minions found by looking via regular expressions
'''
ret = set()
cwd = os.getcwd()
os.chdir(os.path.join(self.opts['pki_dir'], 'minions'))
reg = re.compile(expr)
for fn_ in os.listdir('.'):
if reg.match(fn_):
ret.add(fn_)
os.chdir(cwd)
return ret
def _check_grain_minions(self, expr):
'''
Return the minions found by looking via a list
'''
minions = set(os.listdir(os.path.join(self.opts['pki_dir'], 'minions')))
if self.opts.get('minion_data_cache', False):
cdir = os.path.join(self.opts['cachedir'], 'minions')
if not os.path.isdir(cdir):
return list(minions)
for id_ in os.listdir(cdir):
if not id_ in minions:
continue
datap = os.path.join(cdir, id_, 'data.p')
if not os.path.isfile(datap):
continue
grains = self.serial.load(open(datap)).get('grains')
comps = expr.split(':')
if len(comps) < 2:
continue
if comps[0] not in grains:
minions.remove(id_)
continue
if isinstance(grains.get(comps[0]), list):
# We are matching a single component to a single list member
found = False
for member in grains[comps[0]]:
if fnmatch.fnmatch(str(member).lower(), comps[1].lower()):
found = True
break
if found:
continue
minions.remove(id_)
continue
if fnmatch.fnmatch(
str(grains.get(comps[0], '').lower()),
comps[1].lower(),
):
continue
else:
minions.remove(id_)
return list(minions)
def _check_grain_pcre_minions(self, expr):
'''
Return the minions found by looking via a list
'''
minions = set(os.listdir(os.path.join(self.opts['pki_dir'], 'minions')))
if self.opts.get('minion_data_cache', False):
cdir = os.path.join(self.opts['cachedir'], 'minions')
if not os.path.isdir(cdir):
return list(minions)
for id_ in os.listdir(cdir):
if not id_ in minions:
continue
datap = os.path.join(cdir, id_, 'data.p')
if not os.path.isfile(datap):
continue
grains = self.serial.load(open(datap)).get('grains')
comps = expr.split(':')
if len(comps) < 2:
continue
if comps[0] not in grains:
minions.remove(id_)
if isinstance(grains[comps[0]], list):
# We are matching a single component to a single list member
found = False
for member in grains[comps[0]]:
if re.match(comps[1].lower(), str(member).lower()):
found = True
if found:
continue
minions.remove(id_)
continue
if re.match(
comps[1].lower(),
str(grains[comps[0]]).lower()
):
continue
else:
minions.remove(id_)
return list(minions)
def _all_minions(self, expr=None):
'''
Return a list of all minions that have auth'd
'''
return os.listdir(os.path.join(self.opts['pki_dir'], 'minions'))
def _convert_range_to_list(self, tgt):
range = seco.range.Range(self.opts['range_server'])
try:
@ -246,7 +121,7 @@ class LocalClient(object):
print(("Range server exception: {0}".format(e)))
return []
def gather_job_info(self, jid, tgt, tgt_type):
def gather_job_info(self, jid, tgt, tgt_type, **kwargs):
'''
Return the information about a given job
'''
@ -255,7 +130,8 @@ class LocalClient(object):
'saltutil.find_job',
[jid],
2,
tgt_type)
tgt_type,
**kwargs)
def _check_pub_data(self, pub_data):
'''
@ -367,7 +243,8 @@ class LocalClient(object):
timeout or self.opts['timeout'],
tgt,
expr_form,
verbose):
verbose,
**kwargs):
if not fn_ret:
continue
@ -480,7 +357,8 @@ class LocalClient(object):
timeout=None,
tgt='*',
tgt_type='glob',
verbose=False):
verbose=False,
**kwargs):
'''
This method starts off a watcher looking at the return data for
a specified jid, it returns all of the information for the jid
@ -543,7 +421,7 @@ class LocalClient(object):
if int(time.time()) > start + timeout:
# The timeout has been reached, check the jid to see if the
# timeout needs to be increased
jinfo = self.gather_job_info(jid, tgt, tgt_type)
jinfo = self.gather_job_info(jid, tgt, tgt_type, **kwargs)
more_time = False
for id_ in jinfo:
if jinfo[id_]:
@ -738,6 +616,7 @@ class LocalClient(object):
'''
Get the returns for the command line interface via the event system
'''
minions = set(minions)
if verbose:
msg = 'Executing job with jid {0}'.format(jid)
print(msg)
@ -795,7 +674,8 @@ class LocalClient(object):
timeout=None,
tgt='*',
tgt_type='glob',
verbose=False):
verbose=False,
**kwargs):
'''
Get the returns for the command line interface via the event system
'''
@ -850,7 +730,7 @@ class LocalClient(object):
if int(time.time()) > start + timeout:
# The timeout has been reached, check the jid to see if the
# timeout needs to be increased
jinfo = self.gather_job_info(jid, tgt, tgt_type)
jinfo = self.gather_job_info(jid, tgt, tgt_type, **kwargs)
more_time = False
for id_ in jinfo:
if jinfo[id_]:
@ -932,27 +812,6 @@ class LocalClient(object):
continue
return ret
def check_minions(self, expr, expr_form='glob'):
'''
Check the passed regex against the available minions' public keys
stored for authentication. This should return a set of ids which
match the regex, this will then be used to parse the returns to
make sure everyone has checked back in.
'''
try:
minions = {'glob': self._check_glob_minions,
'pcre': self._check_pcre_minions,
'list': self._check_list_minions,
'grain': self._check_grain_minions,
'grain_pcre': self._check_grain_pcre_minions,
'exsel': self._all_minions,
'pillar': self._all_minions,
'compound': self._all_minions,
}[expr_form](expr)
except Exception:
minions = expr
return minions
def pub(self,
tgt,
fun,
@ -997,7 +856,10 @@ class LocalClient(object):
conf_file = self.opts.get('conf_file', 'the master config file')
err = 'Node group {0} unavailable in {1}'.format(tgt, conf_file)
raise SaltInvocationError(err)
tgt = self.opts['nodegroups'][tgt]
tgt = salt.utils.minions.nodegroup_comp(
tgt,
self.opts['nodegroups']
)
expr_form = 'compound'
# Convert a range expression to a list of nodes and change expression
@ -1006,28 +868,22 @@ class LocalClient(object):
tgt = self._convert_range_to_list(tgt)
expr_form = 'list'
# Run a check_minions, if no minions match return False
# format the payload - make a function that does this in the payload
# module
# make the zmq client
# connect to the req server
# send!
# return what we get back
minions = self.check_minions(tgt, expr_form)
if not minions:
return {'jid': None,
'minions': minions}
# Generate the standard keyword args to feed to format_payload
payload_kwargs = {'cmd': 'publish',
'tgt': tgt,
'fun': fun,
'arg': arg,
'key': self.key,
'tgt_type': expr_form,
'ret': ret,
'jid': jid}
'tgt': tgt,
'fun': fun,
'arg': arg,
'key': self.key,
'tgt_type': expr_form,
'ret': ret,
'jid': jid}
# if kwargs are passed, pack them.
if kwargs:
@ -1048,7 +904,7 @@ class LocalClient(object):
if not payload:
return payload
return {'jid': payload['load']['jid'],
'minions': minions}
'minions': payload['load']['minions']}
class FunctionWrapper(dict):

View file

@ -104,7 +104,6 @@ def include_config(include, opts, orig_path, verbose):
Parses extra configuration file(s) specified in an include list in the
main config file.
'''
# Protect against empty option
if not include:
return opts
@ -276,6 +275,7 @@ def master_config(path):
'runner_dirs': [],
'client_acl': {},
'external_auth': {},
'token_expire': 720,
'file_buffer_size': 1048576,
'max_open_files': 100000,
'hash_type': 'md5',
@ -339,3 +339,21 @@ def master_config(path):
opts['auto_accept'] = opts['auto_accept'] is True
opts['file_roots'] = _validate_file_roots(opts['file_roots'])
return opts
def client_config(path):
'''
Load in the configuration data needed for the LocalClient. This function
searches for client specific configurations and adds them to the data from
the master configuration.
'''
opts = {'token_file': os.path.expanduser('~/.salt_token')}
opts.update(master_config(path))
cpath = os.path.expanduser('~/.salt')
load_config(opts, cpath, 'SALT_CLIENT_CONFIG')
if 'token_file' in opts:
opts['token_file'] = os.path.expanduser(opts['token_file'])
if os.path.isfile(opts['token_file']):
with open(opts['token_file']) as fp_:
opts['token'] = fp_.read().strip()
return opts

View file

@ -127,10 +127,13 @@ def _sunos_cpudata(osdata):
grains = {'num_cpus': 0}
grains['cpuarch'] = __salt__['cmd.run']('uname -p').strip()
for line in __salt__['cmd.run']('/usr/sbin/psrinfo 2>/dev/null').split('\n'):
for line in __salt__['cmd.run'](
'/usr/sbin/psrinfo 2>/dev/null'
).split('\n'):
grains['num_cpus'] += 1
grains['cpu_model'] = __salt__['cmd.run']('kstat -p cpu_info:0:cpu_info0:implementation').split()[1].strip()
grains['cpu_model'] = __salt__['cmd.run'](
'kstat -p cpu_info:*:*:implementation'
).split()[1].strip()
return grains
@ -162,14 +165,14 @@ def _memdata(osdata):
if comps[0].strip() == 'Memory' and comps[1].strip() == 'size:':
grains['mem_total'] = int(comps[2].strip())
elif osdata['kernel'] == 'Windows':
for line in __salt__['cmd.run']('SYSTEMINFO /FO LIST').split('\n'):
comps = line.split(':')
if not len(comps) > 1:
continue
if comps[0].strip() == 'Total Physical Memory':
# Windows XP use '.' as separator and Windows 2008 Server R2 use ','
grains['mem_total'] = int(comps[1].split()[0].replace('.', '').replace(',', ''))
break
import wmi
wmi_c = wmi.WMI()
# this is a list of each stick of ram in a system
# WMI returns it as the string value of the number of bytes
tot_bytes = sum(map(lambda x: int(x.Capacity),
wmi_c.Win32_PhysicalMemory()), 0)
# return memory info in gigabytes
grains['mem_total'] = int(tot_bytes / (1024 ** 2))
return grains
@ -421,8 +424,14 @@ def os_data():
Return grains pertaining to the operating system
'''
grains = {}
(grains['defaultlanguage'],
grains['defaultencoding']) = locale.getdefaultlocale()
try:
(grains['defaultlanguage'],
grains['defaultencoding']) = locale.getdefaultlocale()
except Exception:
# locale.getdefaultlocale can ValueError!! Catch anything else it
# might do, per #2205
grains['defaultlanguage'] = 'unknown'
grains['defaultencoding'] = 'unknown'
# Windows Server 2008 64-bit
# ('Windows', 'MINIONNAME', '2008ServerR2', '6.1.7601', 'AMD64', 'Intel64 Fam ily 6 Model 23 Stepping 6, GenuineIntel')
# Ubuntu 10.04

View file

@ -534,17 +534,17 @@ class Loader(object):
Pass in a function object returned from get_functions to load in
introspection functions.
'''
funcs['sys.list_functions'] = lambda: self.list_funcs(funcs)
funcs['sys.list_functions'] = lambda module='': self.list_funcs(funcs, module)
funcs['sys.list_modules'] = lambda: self.list_modules(funcs)
funcs['sys.doc'] = lambda module = '': self.get_docs(funcs, module)
funcs['sys.reload_modules'] = lambda: True
return funcs
def list_funcs(self, funcs):
def list_funcs(self, funcs, module=''):
'''
List the functions
List the functions. Optionally, specify a module to list from.
'''
return sorted(funcs)
return sorted(f for f in funcs if f.startswith(module + '.')) if module else sorted(funcs)
def list_modules(self, funcs):
'''

View file

@ -43,6 +43,7 @@ import salt.auth
import salt.utils.atomicfile
import salt.utils.event
import salt.utils.verify
import salt.utils.minions
from salt.utils.debug import enable_sigusr1_handler
@ -525,6 +526,7 @@ class AESFuncs(object):
self.event = salt.utils.event.MasterEvent(self.opts['sock_dir'])
self.serial = salt.payload.Serial(opts)
self.crypticle = crypticle
self.ckminions = salt.utils.minions.CkMinions(opts)
# Create the tops dict for loading external top data
self.tops = salt.loader.tops(self.opts)
# Make a client
@ -953,13 +955,12 @@ class AESFuncs(object):
clear_load['id'])
log.warn(msg)
return {}
perms = set()
perms = []
for match in self.opts['peer']:
if re.match(match, clear_load['id']):
# This is the list of funcs/modules!
if isinstance(self.opts['peer'][match], list):
perms.update(self.opts['peer'][match])
good = False
perms.extend(self.opts['peer'][match])
if ',' in clear_load['fun']:
# 'arg': [['cat', '/proc/cpuinfo'], [], ['foo']]
clear_load['fun'] = clear_load['fun'].split(',')
@ -967,15 +968,11 @@ class AESFuncs(object):
for arg in clear_load['arg']:
arg_.append(arg.split())
clear_load['arg'] = arg_
for perm in perms:
if isinstance(clear_load['fun'], list):
good = True
for fun in clear_load['fun']:
if not re.match(perm, fun):
good = False
else:
if re.match(perm, clear_load['fun']):
good = True
good = self.ckminions.auth_check(
perms,
clear_load['fun'],
clear_load['tgt'],
clear_load.get('tgt_type', 'glob'))
if not good:
return {}
# Set up the publication payload
@ -1039,7 +1036,7 @@ class AESFuncs(object):
if ret_form == 'clean':
return self.local.get_returns(
jid,
self.local.check_minions(
self.ckminions.check_minions(
clear_load['tgt'],
expr_form
),
@ -1048,7 +1045,7 @@ class AESFuncs(object):
elif ret_form == 'full':
ret = self.local.get_full_returns(
jid,
self.local.check_minions(
self.ckminions.check_minions(
clear_load['tgt'],
expr_form
),
@ -1098,6 +1095,8 @@ class ClearFuncs(object):
self.event = salt.utils.event.MasterEvent(self.opts['sock_dir'])
# Make a client
self.local = salt.client.LocalClient(self.opts['conf_file'])
# Make an minion checker object
self.ckminions = salt.utils.minions.CkMinions(opts)
# Make an Auth object
self.loadauth = salt.auth.LoadAuth(opts)
@ -1379,6 +1378,19 @@ class ClearFuncs(object):
self.event.fire_event(eload, 'auth')
return ret
def mk_token(self, clear_load):
if not 'eauth' in clear_load:
return ''
if not clear_load['eauth'] in self.opts['external_auth']:
# The eauth system is not enabled, fail
return ''
name = self.loadauth.load_name(clear_load)
if not name in self.opts['external_auth'][clear_load['eauth']]:
return ''
if not self.loadauth.time_auth(clear_load):
return ''
return self.loadauth.mk_token(clear_load)
def publish(self, clear_load):
'''
This method sends out publications to the minions, it can only be used
@ -1386,7 +1398,25 @@ class ClearFuncs(object):
'''
extra = clear_load.get('kwargs', {})
# Check for external auth calls
if 'eauth' in extra:
if extra.get('token', False):
# A token was passwd, check it
token = self.loadauth.get_tok(extra['token'])
if not token:
return ''
if not token['eauth'] in self.opts['external_auth']:
return ''
if not token['name'] in self.opts['external_auth'][token['eauth']]:
return ''
good = self.ckminions.auth_check(
self.opts['external_auth'][token['eauth']][token['name']],
clear_load['fun'],
clear_load['tgt'],
clear_load.get('tgt_type', 'glob'))
if not good:
# Accept find_job so the cli will function cleanly
if not clear_load['fun'] == 'saltutil.find_job':
return ''
elif 'eauth' in extra:
if not extra['eauth'] in self.opts['external_auth']:
# The eauth system is not enabled, fail
return ''
@ -1395,10 +1425,11 @@ class ClearFuncs(object):
return ''
if not self.loadauth.time_auth(extra):
return ''
good = False
for regex in self.opts['external_auth'][extra['eauth']][name]:
if re.match(regex, clear_load['fun']):
good = True
good = self.ckminions.auth_check(
self.opts['external_auth'][extra['eauth']][name],
clear_load['fun'],
clear_load['tgt'],
clear_load.get('tgt_type', 'glob'))
if not good:
# Accept find_job so the cli will function cleanly
if not clear_load['fun'] == 'saltutil.find_job':
@ -1420,14 +1451,15 @@ class ClearFuncs(object):
if not clear_load.pop('key') == self.key[clear_load['user']]:
return ''
good = False
for user in self.opts['client_acl']:
if clear_load['user'] != user:
continue
for regex in self.opts['client_acl'][user]:
if re.match(regex, clear_load['fun']):
good = True
good = self.ckminions.auth_check(
self.opts['client_acl'],
clear_load['fun'],
clear_load['tgt'],
clear_load.get('tgt_type', 'glob'))
if not good:
return ''
# Accept find_job so the cli will function cleanly
if not clear_load['fun'] == 'saltutil.find_job':
return ''
else:
return ''
else:
@ -1492,5 +1524,7 @@ class ClearFuncs(object):
)
pub_sock.connect(pull_uri)
pub_sock.send(self.serial.dumps(payload))
minions = self.ckminions.check_minions(load['tgt'], load.get('tgt_type', 'glob'))
return {'enc': 'clear',
'load': {'jid': clear_load['jid']}}
'load': {'jid': clear_load['jid'],
'minions': minions}}

View file

@ -921,5 +921,7 @@ class Matcher(object):
matcher is used in states
'''
if tgt in nodegroups:
return self.compound_match(nodegroups[tgt])
return self.compound_match(
salt.utils.nodegroup_comp(tgt, nodegroups)
)
return False

View file

@ -13,7 +13,6 @@ def __virtual__():
'''
Confirm this module is on a Debian based system
'''
return 'pkg' if __grains__['os'] in ('Debian', 'Ubuntu') else False

View file

@ -162,23 +162,14 @@ def _run(cmd,
kwargs['executable'] = shell
kwargs['close_fds'] = True
# If all we want is the return code then don't block on gathering input.
if retcode:
kwargs['stdout'] = None
kwargs['stderr'] = None
# This is where the magic happens
proc = subprocess.Popen(cmd, **kwargs)
# If all we want is the return code then don't block on gathering input,
# this is used to bypass ampersand issues with background processes in
# scripts
if retcode:
while True:
retcode = proc.poll()
if retcode is None:
continue
else:
out = ''
err = ''
break
else:
out, err = proc.communicate()
out, err = proc.communicate()
if rstrip:
if out is not None:

View file

@ -14,7 +14,6 @@ might look like::
This data can also be passed into pillar. Options passed into opts will
overwrite options passed into pillar
'''
import pipes
import logging
from salt.utils import check_or_die
from salt.exceptions import CommandNotFoundError
@ -62,6 +61,11 @@ def _connection_defaults(user=None, host=None, port=None):
return (user, host, port)
def _quote(s):
r = s.replace("'", r"\'").replace('"', r'\"')
if ' ' in s:
r = "'%s'" % r
return r
def _psql_cmd(*args, **kwargs):
'''
@ -81,7 +85,7 @@ def _psql_cmd(*args, **kwargs):
if port is not None:
cmd += ['--port', port]
cmd += args
cmdstr = ' '.join(map(pipes.quote, cmd))
cmdstr = ' '.join(map(_quote, cmd))
return cmdstr

View file

@ -8,9 +8,13 @@ except ImportError:
pass
import os
import logging
from copy import deepcopy
from salt._compat import string_types
log = logging.getLogger(__name__)
def __virtual__():
'''
@ -19,13 +23,45 @@ def __virtual__():
return 'user' if __grains__['kernel'] == 'FreeBSD' else False
def _get_gecos(name):
'''
Retrieve GECOS field info and return it in dictionary form
'''
gecos_field = pwd.getpwnam(name).pw_gecos.split(',', 3)
if not gecos_field:
return {}
else:
# Assign empty strings for any unspecified trailing GECOS fields
while len(gecos_field) < 4: gecos_field.append('')
return {'fullname': str(gecos_field[0]),
'roomnumber': str(gecos_field[1]),
'workphone': str(gecos_field[2]),
'homephone': str(gecos_field[3])}
def _build_gecos(gecos_dict):
'''
Accepts a dictionary entry containing GECOS field names and their values,
and returns a full GECOS comment string, to be used with pw usermod.
'''
return '{0},{1},{2},{3}'.format(gecos_dict.get('fullname',''),
gecos_dict.get('roomnumber',''),
gecos_dict.get('workphone',''),
gecos_dict.get('homephone',''))
def add(name,
uid=None,
gid=None,
groups=None,
home=True,
shell=None,
unique=True,
system=False,
fullname='',
roomnumber='',
workphone='',
homephone='',
**kwargs):
'''
Add a user to the minion
@ -50,6 +86,11 @@ def add(name,
cmd += '-m '
else:
cmd += '-m -b {0} '.format(os.path.dirname(home))
gecos_field = '{0},{1},{2},{3}'.format(fullname,
roomnumber,
workphone,
homephone)
cmd += '-c "{0}" '.format(gecos_field)
cmd += '-n {0}'.format(name)
ret = __salt__['cmd.run_all'](cmd)
@ -190,6 +231,98 @@ def chgroups(name, groups, append=False):
return len(ugrps - agrps) == 0
def chfullname(name, fullname):
'''
Change the user's Full Name
CLI Example::
salt '*' user.chfullname foo "Foo Bar"
'''
fullname = str(fullname)
pre_info = _get_gecos(name)
if not pre_info: return False
if fullname == pre_info['fullname']:
return True
gecos_field = deepcopy(pre_info)
gecos_field['fullname'] = fullname
cmd = 'pw usermod {0} -c "{1}"'.format(name, _build_gecos(gecos_field))
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['fullname'] != pre_info['fullname']:
return post_info['fullname'] == fullname
return False
def chroomnumber(name, roomnumber):
'''
Change the user's Room Number
CLI Example::
salt '*' user.chroomnumber foo 123
'''
roomnumber = str(roomnumber)
pre_info = _get_gecos(name)
if not pre_info: return False
if roomnumber == pre_info['roomnumber']:
return True
gecos_field = deepcopy(pre_info)
gecos_field['roomnumber'] = roomnumber
cmd = 'pw usermod {0} -c "{1}"'.format(name, _build_gecos(gecos_field))
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['roomnumber'] != pre_info['roomnumber']:
return post_info['roomnumber'] == roomnumber
return False
def chworkphone(name, workphone):
'''
Change the user's Work Phone
CLI Example::
salt '*' user.chworkphone foo "7735550123"
'''
workphone = str(workphone)
pre_info = _get_gecos(name)
if not pre_info: return False
if workphone == pre_info['workphone']:
return True
gecos_field = deepcopy(pre_info)
gecos_field['workphone'] = workphone
cmd = 'pw usermod {0} -c "{1}"'.format(name, _build_gecos(gecos_field))
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['workphone'] != pre_info['workphone']:
return post_info['workphone'] == workphone
return False
def chhomephone(name, homephone):
'''
Change the user's Home Phone
CLI Example::
salt '*' user.chhomephone foo "7735551234"
'''
homephone = str(homephone)
pre_info = _get_gecos(name)
if not pre_info: return False
if homephone == pre_info['homephone']:
return True
gecos_field = deepcopy(pre_info)
gecos_field['homephone'] = homephone
cmd = 'pw usermod {0} -c "{1}"'.format(name, _build_gecos(gecos_field))
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['homephone'] != pre_info['homephone']:
return post_info['homephone'] == homephone
return False
def info(name):
'''
Return user information
@ -199,14 +332,35 @@ def info(name):
salt '*' user.info root
'''
ret = {}
data = pwd.getpwnam(name)
ret['name'] = data.pw_name
ret['passwd'] = data.pw_passwd
ret['uid'] = data.pw_uid
ret['gid'] = data.pw_gid
ret['home'] = data.pw_dir
ret['shell'] = data.pw_shell
ret['groups'] = list_groups(name)
try:
data = pwd.getpwnam(name)
ret['gid'] = data.pw_gid
ret['groups'] = list_groups(name)
ret['home'] = data.pw_dir
ret['name'] = data.pw_name
ret['passwd'] = data.pw_passwd
ret['shell'] = data.pw_shell
ret['uid'] = data.pw_uid
# Put GECOS info into a list
gecos_field = data.pw_gecos.split(',', 3)
# Assign empty strings for any unspecified GECOS fields
while len(gecos_field) < 4: gecos_field.append('')
ret['fullname'] = gecos_field[0]
ret['roomnumber'] = gecos_field[1]
ret['workphone'] = gecos_field[2]
ret['homephone'] = gecos_field[3]
except KeyError:
ret['gid'] = ''
ret['groups'] = ''
ret['home'] = ''
ret['name'] = ''
ret['passwd'] = ''
ret['shell'] = ''
ret['uid'] = ''
ret['fullname'] = ''
ret['roomnumber'] = ''
ret['workphone'] = ''
ret['homephone'] = ''
return ret

View file

@ -0,0 +1,97 @@
'''
Manage groups on Solaris
'''
try:
import grp
except ImportError:
pass
def __virtual__():
'''
Set the group module if the kernel is SunOS
'''
return 'group' if __grains__['kernel'] == 'SunOS' else False
def add(name, gid=None, system=False):
'''
Add the specified group
CLI Example::
salt '*' group.add foo 3456
'''
cmd = 'groupadd '
if gid:
cmd += '-g {0} '.format(gid)
cmd += name
ret = __salt__['cmd.run_all'](cmd)
return not ret['retcode']
def delete(name):
'''
Remove the named group
CLI Example::
salt '*' group.delete foo
'''
ret = __salt__['cmd.run_all']('groupdel {0}'.format(name))
return not ret['retcode']
def info(name):
'''
Return information about a group
CLI Example::
salt '*' group.info foo
'''
try:
grinfo = grp.getgrnam(name)
except KeyError:
return {}
else:
return {'name': grinfo.gr_name,
'passwd': grinfo.gr_passwd,
'gid': grinfo.gr_gid,
'members': grinfo.gr_mem}
def getent():
'''
Return info on all groups
CLI Example::
salt '*' group.getent
'''
ret = []
for grinfo in grp.getgrall():
ret.append(info(grinfo.gr_name))
return ret
def chgid(name, gid):
'''
Change the gid for a named group
CLI Example::
salt '*' group.chgid foo 4376
'''
pre_gid = __salt__['file.group_to_gid'](name)
if gid == pre_gid:
return True
cmd = 'groupmod -g {0} {1}'.format(gid, name)
__salt__['cmd.run'](cmd)
post_gid = __salt__['file.group_to_gid'](name)
if post_gid != pre_gid:
return post_gid == gid
return False

View file

@ -0,0 +1,399 @@
'''
Manage users with the useradd command
'''
try:
import grp
import pwd
except ImportError:
pass
import logging
from copy import deepcopy
from salt._compat import string_types, callable
log = logging.getLogger(__name__)
def __virtual__():
'''
Set the user module if the kernel is SunOS
'''
return 'user' if __grains__['kernel'] == 'SunOS' else False
def _get_gecos(name):
'''
Retrieve GECOS field info and return it in dictionary form
'''
gecos_field = pwd.getpwnam(name).pw_gecos.split(',', 3)
if not gecos_field:
return {}
else:
# Assign empty strings for any unspecified trailing GECOS fields
while len(gecos_field) < 4: gecos_field.append('')
return {'fullname': str(gecos_field[0]),
'roomnumber': str(gecos_field[1]),
'workphone': str(gecos_field[2]),
'homephone': str(gecos_field[3])}
def _build_gecos(gecos_dict):
'''
Accepts a dictionary entry containing GECOS field names and their values,
and returns a full GECOS comment string, to be used with usermod.
'''
return '{0},{1},{2},{3}'.format(gecos_dict.get('fullname',''),
gecos_dict.get('roomnumber',''),
gecos_dict.get('workphone',''),
gecos_dict.get('homephone',''))
def add(name,
uid=None,
gid=None,
groups=None,
home=True,
shell=None,
unique=True,
system=False,
fullname='',
roomnumber='',
workphone='',
homephone=''):
'''
Add a user to the minion
CLI Example::
salt '*' user.add name <uid> <gid> <groups> <home> <shell>
'''
if isinstance(groups, string_types):
groups = groups.split(',')
cmd = 'useradd '
if shell:
cmd += '-s {0} '.format(shell)
if uid:
cmd += '-u {0} '.format(uid)
if gid:
cmd += '-g {0} '.format(gid)
if groups:
cmd += '-G {0} '.format(','.join(groups))
if home:
if home is not True:
if system:
cmd += '-d {0} '.format(home)
else:
cmd += '-m -d {0} '.format(home)
else:
if not system:
cmd += '-m '
if not unique:
cmd += '-o '
cmd += name
ret = __salt__['cmd.retcode'](cmd)
if ret != 0:
return False
else:
# At this point, the user was successfully created, so return true
# regardless of the outcome of the below functions. If there is a
# problem wth changing any of the user's info below, it will be raised
# in a future highstate call. If anyone has a better idea on how to do
# this, feel free to change it, but I didn't think it was a good idea
# to return False when the user was successfully created since A) the
# user does exist, and B) running useradd again would result in a
# nonzero exit status and be interpreted as a False result.
if fullname:
chfullname(name, fullname)
if roomnumber:
chroomnumber(name, roomnumber)
if workphone:
chworkphone(name, workphone)
if homephone:
chhomephone(name, homephone)
return True
def delete(name, remove=False, force=False):
'''
Remove a user from the minion
CLI Example::
salt '*' user.delete name remove=True force=True
'''
cmd = 'userdel '
if remove:
cmd += '-r '
cmd += name
ret = __salt__['cmd.run_all'](cmd)
return not ret['retcode']
def getent():
'''
Return the list of all info for all users
CLI Example::
salt '*' user.getent
'''
ret = []
for data in pwd.getpwall():
ret.append(info(data.pw_name))
return ret
def chuid(name, uid):
'''
Change the uid for a named user
CLI Example::
salt '*' user.chuid foo 4376
'''
pre_info = info(name)
if uid == pre_info['uid']:
return True
cmd = 'usermod -u {0} {1}'.format(uid, name)
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['uid'] != pre_info['uid']:
return post_info['uid'] == uid
return False
def chgid(name, gid):
'''
Change the default group of the user
CLI Example::
salt '*' user.chgid foo 4376
'''
pre_info = info(name)
if gid == pre_info['gid']:
return True
cmd = 'usermod -g {0} {1}'.format(gid, name)
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['gid'] != pre_info['gid']:
return post_info['gid'] == gid
return False
def chshell(name, shell):
'''
Change the default shell of the user
CLI Example::
salt '*' user.chshell foo /bin/zsh
'''
pre_info = info(name)
if shell == pre_info['shell']:
return True
cmd = 'usermod -s {0} {1}'.format(shell, name)
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['shell'] != pre_info['shell']:
return post_info['shell'] == shell
return False
def chhome(name, home, persist=False):
'''
Change the home directory of the user, pass true for persist to copy files
to the new home dir
CLI Example::
salt '*' user.chhome foo /home/users/foo True
'''
pre_info = info(name)
if home == pre_info['home']:
return True
cmd = 'usermod -d {0} '.format(home)
if persist:
cmd += ' -m '
cmd += name
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['home'] != pre_info['home']:
return post_info['home'] == home
return False
def chgroups(name, groups, append=False):
'''
Change the groups this user belongs to, add append to append the specified
groups
CLI Example::
salt '*' user.chgroups foo wheel,root True
'''
if isinstance(groups, string_types):
groups = groups.split(',')
ugrps = set(list_groups(name))
if ugrps == set(groups):
return True
if append:
groups += ugrps
cmd = 'usermod -G {0} {1} '.format(','.join(groups), name)
return not __salt__['cmd.retcode'](cmd)
def chfullname(name, fullname):
'''
Change the user's Full Name
CLI Example::
salt '*' user.chfullname foo "Foo Bar"
'''
fullname = str(fullname)
pre_info = _get_gecos(name)
if not pre_info: return False
if fullname == pre_info['fullname']:
return True
gecos_field = deepcopy(pre_info)
gecos_field['fullname'] = fullname
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['fullname'] != pre_info['fullname']:
return post_info['fullname'] == fullname
return False
def chroomnumber(name, roomnumber):
'''
Change the user's Room Number
CLI Example::
salt '*' user.chroomnumber foo 123
'''
roomnumber = str(roomnumber)
pre_info = _get_gecos(name)
if not pre_info: return False
if roomnumber == pre_info['roomnumber']:
return True
gecos_field = deepcopy(pre_info)
gecos_field['roomnumber'] = roomnumber
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['roomnumber'] != pre_info['roomnumber']:
return post_info['roomnumber'] == roomnumber
return False
def chworkphone(name, workphone):
'''
Change the user's Work Phone
CLI Example::
salt '*' user.chworkphone foo "7735550123"
'''
workphone = str(workphone)
pre_info = _get_gecos(name)
if not pre_info: return False
if workphone == pre_info['workphone']:
return True
gecos_field = deepcopy(pre_info)
gecos_field['workphone'] = workphone
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['workphone'] != pre_info['workphone']:
return post_info['workphone'] == workphone
return False
def chhomephone(name, homephone):
'''
Change the user's Home Phone
CLI Example::
salt '*' user.chhomephone foo "7735551234"
'''
homephone = str(homephone)
pre_info = _get_gecos(name)
if not pre_info: return False
if homephone == pre_info['homephone']:
return True
gecos_field = deepcopy(pre_info)
gecos_field['homephone'] = homephone
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['homephone'] != pre_info['homephone']:
return post_info['homephone'] == homephone
return False
def info(name):
'''
Return user information
CLI Example::
salt '*' user.info root
'''
ret = {}
try:
data = pwd.getpwnam(name)
ret['gid'] = data.pw_gid
ret['groups'] = list_groups(name)
ret['home'] = data.pw_dir
ret['name'] = data.pw_name
ret['passwd'] = data.pw_passwd
ret['shell'] = data.pw_shell
ret['uid'] = data.pw_uid
# Put GECOS info into a list
gecos_field = data.pw_gecos.split(',', 3)
# Assign empty strings for any unspecified GECOS fields
while len(gecos_field) < 4: gecos_field.append('')
ret['fullname'] = gecos_field[0]
ret['roomnumber'] = gecos_field[1]
ret['workphone'] = gecos_field[2]
ret['homephone'] = gecos_field[3]
except KeyError:
ret['gid'] = ''
ret['groups'] = ''
ret['home'] = ''
ret['name'] = ''
ret['passwd'] = ''
ret['shell'] = ''
ret['uid'] = ''
ret['fullname'] = ''
ret['roomnumber'] = ''
ret['workphone'] = ''
ret['homephone'] = ''
return ret
def list_groups(name):
'''
Return a list of groups the named user belongs to
CLI Example::
salt '*' user.list_groups foo
'''
ugrp = set()
# Add the primary user's group
ugrp.add(grp.getgrgid(pwd.getpwnam(name).pw_gid).gr_name)
# Now, all other groups the user belongs to
for group in grp.getgrall():
if name in group.gr_mem:
ugrp.add(group.gr_name)
return sorted(list(ugrp))

342
salt/modules/solarispkg.py Normal file
View file

@ -0,0 +1,342 @@
'''
Package support for Solaris
'''
import tempfile
import os
import shutil
def __virtual__():
'''
Set the virtual pkg module if the os is Solaris
'''
if __grains__['os'] == 'Solaris':
return 'pkg'
return False
def _list_removed(old, new):
'''
List the packages which have been removed between the two package objects
'''
pkgs = []
for pkg in old:
if pkg not in new:
pkgs.append(pkg)
return pkgs
def _compare_versions(old, new):
'''
Returns a dict that that displays old and new versions for a package after
install/upgrade of package.
'''
pkgs = {}
for npkg in new:
if npkg in old:
if old[npkg] == new[npkg]:
# no change in the package
continue
else:
# the package was here before and the version has changed
pkgs[npkg] = {'old': old[npkg],
'new': new[npkg]}
else:
# the package is freshly installed
pkgs[npkg] = {'old': '',
'new': new[npkg]}
return pkgs
def _get_pkgs():
'''
Get a full list of the package installed on the machine
'''
pkg = {}
cmd = '/usr/bin/pkginfo -x'
line_count = 0
for line in __salt__['cmd.run'](cmd).split('\n'):
if line_count % 2 == 0:
namever = line.split()[0].strip()
if line_count % 2 == 1:
pkg[namever] = line.split()[1].strip()
line_count = line_count + 1
return pkg
def list_pkgs():
'''
List the packages currently installed as a dict::
{'<package_name>': '<version>'}
CLI Example::
salt '*' pkg.list_pkgs
'''
return _get_pkgs()
def version(name):
'''
Returns a version if the package is installed, else returns an empty string
CLI Example::
salt '*' pkg.version <package name>
'''
cmd = '/usr/bin/pkgparam {0} VERSION 2> /dev/null'.format(name)
namever = __salt__['cmd.run'](cmd)
if namever:
return namever
return ''
def available_version(name):
'''
The available version of the package in the repository
On Solaris with the pkg module this always returns the
version that is installed since pkgadd does not have
the concept of a repository.
CLI Example::
salt '*' pkg.available_version <package name>
'''
return version(name)
def install(name, refresh=False, **kwargs):
'''
Install the passed package. Can install packages from the following sources::
* Locally (package already exists on the minion
* HTTP/HTTPS server
* FTP server
* Salt master
Returns a dict containing the new package names and versions::
{'<package>': {'old': '<old-version>',
'new': '<new-version>']}
CLI Example, installing a datastream pkg that already exists on the minion::
salt '*' pkg.install <package name once installed> source=/dir/on/minion/<package filename>
salt '*' pkg.install SMClgcc346 source=/var/spool/pkg/gcc-3.4.6-sol10-sparc-local.pkg
CLI Example, installing a datastream pkg that exists on the salt master::
salt '*' pkg.install <package name once installed> source='salt://srv/salt/pkgs/<package filename>'
salt '*' pkg.install SMClgcc346 source='salt://srv/salt/pkgs/gcc-3.4.6-sol10-sparc-local.pkg'
CLI Example, installing a datastream pkg that exists on a HTTP server::
salt '*' pkg.install <package name once installed> source='http://packages.server.com/<package filename>'
salt '*' pkg.install SMClgcc346 source='http://packages.server.com/gcc-3.4.6-sol10-sparc-local.pkg'
By default salt automatically provides an adminfile, to automate package installation, with these options set:
email=
instance=quit
partial=nocheck
runlevel=nocheck
idepend=nocheck
rdepend=nocheck
space=nocheck
setuid=nocheck
conflict=nocheck
action=nocheck
basedir=default
You can override any of these options in two ways. First you can optionally pass any of
the options as a kwarg to the module/state to override the default value or you can
optionally pass the 'admin_source' option providing your own adminfile to the minions.
Note: You can find all of the possible options to provide to the adminfile by reading the admin man page:
man -s 4 admin
CLI Example - Overriding the 'instance' adminfile option when calling the module directly:
salt '*' pkg.install <package name once installed> source='salt://srv/salt/pkgs/<package filename>' instance="overwrite"
CLI Example - Overriding the 'instance' adminfile option when used in a state:
SMClgcc346:
pkg.installed:
- source: salt://srv/salt/pkgs/gcc-3.4.6-sol10-sparc-local.pkg
- instance: overwrite
CLI Example - Providing your own adminfile when calling the module directly:
salt '*' pkg.install <package name once installed> source='salt://srv/salt/pkgs/<package filename>' admin_source='salt://srv/salt/pkgs/<adminfile filename>'
CLI Example - Providing your own adminfile when using states:
<package name once installed>:
pkg.installed:
- source: salt://srv/salt/pkgs/<package filename>
- admin_source: salt://srv/salt/pkgs/<adminfile filename>
'''
if not 'source' in kwargs:
return 'source option required with solaris pkg installs'
else:
if (kwargs['source']).startswith('salt://') or (kwargs['source']).startswith('http://') or (kwargs['source']).startswith('https://') or (kwargs['source']).startswith('ftp://'):
pkgname = __salt__['cp.cache_file'](kwargs['source'])
else:
pkgname = (kwargs['source'])
if 'admin_source' in kwargs:
adminfile = __salt__['cp.cache_file'](kwargs['admin_source'])
else:
# Set the adminfile default variables
email=kwargs.get('email', '')
instance=kwargs.get('instance', 'quit')
partial=kwargs.get('partial', 'nocheck')
runlevel=kwargs.get('runlevel', 'nocheck')
idepend=kwargs.get('idepend', 'nocheck')
rdepend=kwargs.get('rdepend', 'nocheck')
space=kwargs.get('space', 'nocheck')
setuid=kwargs.get('setuid', 'nocheck')
conflict=kwargs.get('conflict', 'nocheck')
action=kwargs.get('action', 'nocheck')
basedir=kwargs.get('basedir', 'default')
# Make tempfile to hold the adminfile contents.
fd, adminfile = tempfile.mkstemp(prefix="salt-")
# Write to file then close it.
os.write(fd, "email=%s\n" % email)
os.write(fd, "instance=%s\n" % instance)
os.write(fd, "partial=%s\n" % partial)
os.write(fd, "runlevel=%s\n" % runlevel)
os.write(fd, "idepend=%s\n" % idepend)
os.write(fd, "rdepend=%s\n" % rdepend)
os.write(fd, "space=%s\n" % space)
os.write(fd, "setuid=%s\n" % setuid)
os.write(fd, "conflict=%s\n" % conflict)
os.write(fd, "action=%s\n" % action)
os.write(fd, "basedir=%s\n" % basedir)
os.close(fd)
# Get a list of the packages before install so we can diff after to see
# what got installed.
old = _get_pkgs()
# Install the package
cmd = '/usr/sbin/pkgadd -n -a {0} -d {1} \'all\''.format(adminfile, pkgname)
__salt__['cmd.retcode'](cmd)
# Get a list of the packages again, including newly installed ones.
new = _get_pkgs()
# Remove the temp adminfile
if not 'admin_source' in kwargs:
os.unlink(adminfile)
# Return a list of the new package installed.
return _compare_versions(old, new)
def remove(name, **kwargs):
'''
Remove a single package with pkgrm
By default salt automatically provides an adminfile, to automate package removal, with these options set:
email=
instance=quit
partial=nocheck
runlevel=nocheck
idepend=nocheck
rdepend=nocheck
space=nocheck
setuid=nocheck
conflict=nocheck
action=nocheck
basedir=default
You can override any of these options in two ways. First you can optionally pass any of
the options as a kwarg to the module/state to override the default value or you can
optionally pass the 'admin_source' option providing your own adminfile to the minions.
Note: You can find all of the possible options to provide to the adminfile by reading the admin man page:
man -s 4 admin
CLI Example::
salt '*' pkg.remove <package name>
salt '*' pkg.remove SUNWgit
'''
# Check to see if the package is installed before we proceed
if version(name) == '':
return ''
if 'admin_source' in kwargs:
adminfile = __salt__['cp.cache_file'](kwargs['admin_source'])
else:
# Set the adminfile default variables
email=kwargs.get('email', '')
instance=kwargs.get('instance', 'quit')
partial=kwargs.get('partial', 'nocheck')
runlevel=kwargs.get('runlevel', 'nocheck')
idepend=kwargs.get('idepend', 'nocheck')
rdepend=kwargs.get('rdepend', 'nocheck')
space=kwargs.get('space', 'nocheck')
setuid=kwargs.get('setuid', 'nocheck')
conflict=kwargs.get('conflict', 'nocheck')
action=kwargs.get('action', 'nocheck')
basedir=kwargs.get('basedir', 'default')
# Make tempfile to hold the adminfile contents.
fd, adminfile = tempfile.mkstemp(prefix="salt-")
# Write to file then close it.
os.write(fd, "email=%s\n" % email)
os.write(fd, "instance=%s\n" % instance)
os.write(fd, "partial=%s\n" % partial)
os.write(fd, "runlevel=%s\n" % runlevel)
os.write(fd, "idepend=%s\n" % idepend)
os.write(fd, "rdepend=%s\n" % rdepend)
os.write(fd, "space=%s\n" % space)
os.write(fd, "setuid=%s\n" % setuid)
os.write(fd, "conflict=%s\n" % conflict)
os.write(fd, "action=%s\n" % action)
os.write(fd, "basedir=%s\n" % basedir)
os.close(fd)
# Get a list of the currently installed pkgs.
old = _get_pkgs()
# Remove the package
cmd = '/usr/sbin/pkgrm -n -a {0} {1}'.format(adminfile, name)
__salt__['cmd.retcode'](cmd)
# Remove the temp adminfile
if not 'admin_source' in kwargs:
os.unlink(adminfile)
# Get a list of the packages after the uninstall
new = _get_pkgs()
# Compare the pre and post remove package objects and report the uninstalled pkgs.
return _list_removed(old, new)
def purge(name, **kwargs):
'''
Remove a single package with pkgrm
Returns a list containing the removed packages.
CLI Example::
salt '*' pkg.purge <package name>
'''
return remove(name, **kwargs)

View file

@ -6,6 +6,7 @@ import re
# Import Salt libs
import salt.utils
LOCAL_CONFIG_PATH = '/etc/systemd/system'
VALID_UNIT_TYPES = ['service','socket', 'device', 'mount', 'automount',
'swap', 'target', 'path', 'timer']
@ -20,14 +21,29 @@ def __virtual__():
return False
def _canonical_unit_name(name):
'''
Build a canonical unit name treating unit names without one
of the valid suffixes as a service.
'''
if any(name.endswith(suffix) for suffix in VALID_UNIT_TYPES):
return name
return '{0}.service'.format(name)
def _canonical_template_unit_name(name):
'''
Build a canonical unit name for unit instances based on templates.
'''
return re.sub(r'@.+?(\.|$)', r'@\1', name)
def _systemctl_cmd(action, name):
'''
Build a systemctl command line. Treat unit names without one
of the valid suffixes as a service.
'''
if not any(name.endswith(suffix) for suffix in VALID_UNIT_TYPES):
name += '.service'
return 'systemctl {0} {1}'.format(action, name)
return 'systemctl {0} {1}'.format(action, _canonical_unit_name(name))
def _get_all_unit_files():
'''
@ -38,7 +54,9 @@ def _get_all_unit_files():
'|'.join(VALID_UNIT_TYPES) +
')\s+(?P<state>.+)$')
out = __salt__['cmd.run_stdout']('systemctl --no-legend list-unit-files | col -b')
out = __salt__['cmd.run_stdout'](
'systemctl --full list-unit-files | col -b'
)
ret = {}
for match in rexp.finditer(out):
@ -90,6 +108,14 @@ def get_all():
return sorted(_get_all_unit_files().keys())
def available(name):
'''
Check that the given service is available taking into account
template units.
'''
return _canonical_template_unit_name(name) in get_all()
def start(name):
'''
Start the specified service with systemd
@ -178,6 +204,24 @@ def disable(name):
return not __salt__['cmd.retcode'](_systemctl_cmd('disable', name))
def _templated_instance_enabled(name):
'''
Services instantiated based on templates can not be checked with
systemctl is-enabled. Presence of the actual symlinks is checked
as a fall-back.
'''
if '@' not in name:
return False
find_unit_by_name = 'find {0} -name {1} -type l -print -quit'
return len(__salt__['cmd.run'](find_unit_by_name.format(LOCAL_CONFIG_PATH,
_canonical_unit_name(name))))
def _enabled(name):
is_enabled = not bool(__salt__['cmd.retcode'](_systemctl_cmd('is-enabled', name)))
return is_enabled or _templated_instance_enabled(name)
def enabled(name):
'''
Return if the named service is enabled to start on boot
@ -186,7 +230,7 @@ def enabled(name):
salt '*' service.enabled <service name>
'''
return not __salt__['cmd.retcode'](_systemctl_cmd('is-enabled', name))
return _enabled(name)
def disabled(name):
@ -197,4 +241,4 @@ def disabled(name):
salt '*' service.disabled <service name>
'''
return bool(__salt__['cmd.retcode'](_systemctl_cmd('is-enabled', name)))
return not _enabled(name)

View file

@ -7,8 +7,13 @@ try:
except ImportError:
pass
import logging
from copy import deepcopy
from salt._compat import string_types, callable
log = logging.getLogger(__name__)
def __virtual__():
'''
@ -25,19 +30,45 @@ def __virtual__():
return 'user' if __grains__['kernel'] in ('Linux', 'Darwin') else False
def _get_gecos(name):
'''
Retrieve GECOS field info and return it in dictionary form
'''
gecos_field = pwd.getpwnam(name).pw_gecos.split(',', 3)
if not gecos_field:
return {}
else:
# Assign empty strings for any unspecified trailing GECOS fields
while len(gecos_field) < 4: gecos_field.append('')
return {'fullname': str(gecos_field[0]),
'roomnumber': str(gecos_field[1]),
'workphone': str(gecos_field[2]),
'homephone': str(gecos_field[3])}
def _build_gecos(gecos_dict):
'''
Accepts a dictionary entry containing GECOS field names and their values,
and returns a full GECOS comment string, to be used with usermod.
'''
return '{0},{1},{2},{3}'.format(gecos_dict.get('fullname',''),
gecos_dict.get('roomnumber',''),
gecos_dict.get('workphone',''),
gecos_dict.get('homephone',''))
def add(name,
uid=None,
gid=None,
groups=None,
home=True,
shell=None,
fullname=None,
roomnumber=None,
workphone=None,
homephone=None,
other=None,
unique=True,
system=False):
system=False,
fullname='',
roomnumber='',
workphone='',
homephone=''):
'''
Add a user to the minion
@ -90,8 +121,6 @@ def add(name,
chworkphone(name, workphone)
if homephone:
chhomephone(name, homephone)
if other:
chother(name, other)
return True
@ -233,16 +262,20 @@ def chgroups(name, groups, append=False):
def chfullname(name, fullname):
'''
Change the users Full Name
Change the user's Full Name
CLI Example::
salt '*' user.chfullname foo "Foo Bar"
'''
pre_info = info(name)
fullname = str(fullname)
pre_info = _get_gecos(name)
if not pre_info: return False
if fullname == pre_info['fullname']:
return True
cmd = 'chfn -f "{0}" {1}'.format(fullname, name)
gecos_field = deepcopy(pre_info)
gecos_field['fullname'] = fullname
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['fullname'] != pre_info['fullname']:
@ -258,10 +291,14 @@ def chroomnumber(name, roomnumber):
salt '*' user.chroomnumber foo 123
'''
pre_info = info(name)
roomnumber = str(roomnumber)
pre_info = _get_gecos(name)
if not pre_info: return False
if roomnumber == pre_info['roomnumber']:
return True
cmd = 'chfn -r "{0}" {1}'.format(roomnumber, name)
gecos_field = deepcopy(pre_info)
gecos_field['roomnumber'] = roomnumber
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['roomnumber'] != pre_info['roomnumber']:
@ -277,10 +314,14 @@ def chworkphone(name, workphone):
salt '*' user.chworkphone foo "7735550123"
'''
pre_info = info(name)
workphone = str(workphone)
pre_info = _get_gecos(name)
if not pre_info: return False
if workphone == pre_info['workphone']:
return True
cmd = 'chfn -w "{0}" {1}'.format(workphone, name)
gecos_field = deepcopy(pre_info)
gecos_field['workphone'] = workphone
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['workphone'] != pre_info['workphone']:
@ -296,10 +337,14 @@ def chhomephone(name, homephone):
salt '*' user.chhomephone foo "7735551234"
'''
pre_info = info(name)
homephone = str(homephone)
pre_info = _get_gecos(name)
if not pre_info: return False
if homephone == pre_info['homephone']:
return True
cmd = 'chfn -h "{0}" {1}'.format(homephone, name)
gecos_field = deepcopy(pre_info)
gecos_field['homephone'] = homephone
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['homephone'] != pre_info['homephone']:
@ -307,25 +352,6 @@ def chhomephone(name, homephone):
return False
def chother(name, other):
'''
Change the user's "Other" GECOS field
CLI Example::
salt '*' user.chother foo "fax=7735555678"
'''
pre_info = info(name)
if other == pre_info['other']:
return True
cmd = 'chfn -o "{0}" {1}'.format(other, name)
__salt__['cmd.run'](cmd)
post_info = info(name)
if post_info['other'] != pre_info['other']:
return post_info['other'] == other
return False
def info(name):
'''
Return user information
@ -345,15 +371,13 @@ def info(name):
ret['shell'] = data.pw_shell
ret['uid'] = data.pw_uid
# Put GECOS info into a list
gecos_field = data.pw_gecos.split(',', 4)
gecos_field = data.pw_gecos.split(',', 3)
# Assign empty strings for any unspecified GECOS fields
while len(gecos_field) < 5:
gecos_field.append('')
while len(gecos_field) < 4: gecos_field.append('')
ret['fullname'] = gecos_field[0]
ret['roomnumber'] = gecos_field[1]
ret['workphone'] = gecos_field[2]
ret['homephone'] = gecos_field[3]
ret['other'] = gecos_field[4]
except KeyError:
ret['gid'] = ''
ret['groups'] = ''
@ -366,7 +390,6 @@ def info(name):
ret['roomnumber'] = ''
ret['workphone'] = ''
ret['homephone'] = ''
ret['other'] = ''
return ret

View file

@ -88,7 +88,7 @@ def list_upgrades(*args):
for pkg in pkgs:
exactmatch, matched, unmatched = yum.packages.parsePackages(pl, [pkg])
for pkg in exactmatch:
if pkg.arch == getBaseArch():
if pkg.arch == getBaseArch() or pkg.arch == 'noarch':
versions_list[pkg['name']] = '-'.join([pkg['version'],pkg['release']])
return versions_list
@ -114,7 +114,7 @@ def available_version(name):
# installed. Maybe we should just return the value if we get a hit
# on available, and only iterate though updates if we don't..
for pkg in exactmatch:
if pkg.arch == getBaseArch():
if pkg.arch == getBaseArch() or pkg.arch == 'noarch':
versions_list.append('-'.join([pkg.version, pkg.release]))
if len(versions_list) == 0:

View file

@ -339,6 +339,8 @@ class Pillar(object):
pillar, errors = self.render_pillar(matches)
pillar.update(self.ext_pillar())
errors.extend(terrors)
if self.opts.get('pillar_opts', False):
pillar['master'] = self.opts
if errors:
for error in errors:
log.critical('Pillar render error: {0}'.format(error))

View file

@ -15,7 +15,7 @@ except ImportError:
has_redis = False
__opts__ = {'redis.db': '0',
'redis.host': 'mcp',
'redis.host': 'redis',
'redis.port': 6379}
@ -33,7 +33,7 @@ def returner(ret):
port=__opts__['redis.port'],
db=__opts__['redis.db'])
serv.sadd("%(id)s:jobs" % ret, ret['jid'])
serv.set("%(jid)s:%(id)s" % ret, json.dumps(ret['return']))
serv.sadd('{0}:jobs'.format(ret['id']))
serv.set('{0}:{1}'.format(ret['jid'], json.dumps(ret['return'])))
serv.sadd('jobs', ret['jid'])
serv.sadd(ret['jid'], ret['id'])

View file

@ -6,38 +6,55 @@ import sys
# Import salt modules
import salt.loader
import salt.exceptions
class Runner(object):
class RunnerClient(object):
'''
Execute the salt runner interface
A client for accessing runners
'''
def __init__(self, opts):
self.opts = opts
self.functions = salt.loader.runner(opts)
def _verify_fun(self):
def _verify_fun(self, fun):
'''
Verify an environmental issues
Check that the function passed really exists
'''
if not self.opts['fun']:
err = 'Must pass a runner function'
sys.stderr.write('{0}\n'.format(err))
sys.exit(1)
if self.opts['fun'] not in self.functions:
err = 'Passed function is unavailable'
sys.stderr.write('{0}\n'.format(err))
sys.exit(1)
if fun not in self.functions:
err = "Function '{0}' is unavailable".format(fun)
raise salt.exceptions.CommandExecutionError(err)
def get_docs(self):
'''
Return a dictionary of functions and the inline documentation for each
'''
ret = [(fun, self.functions[fun].__doc__)
for fun in sorted(self.functions)
if fun.startswith(self.opts['fun'])]
return dict(ret)
def cmd(self, fun, arg):
'''
Execute a runner with the given arguments
'''
self._verify_fun(fun)
# pylint: disable-msg=W0142
return self.functions[fun](*arg)
class Runner(RunnerClient):
'''
Execute the salt runner interface
'''
def _print_docs(self):
'''
Print out the documentation!
'''
for fun in sorted(self.functions):
if fun.startswith(self.opts['fun']):
print('{0}:'.format(fun))
print(self.functions[fun].__doc__)
print('')
ret = super(Runner, self).get_docs()
for fun, doc in ret.items():
print("{0}:\n{1}\n".format(fun, doc))
def run(self):
'''
@ -46,5 +63,9 @@ class Runner(object):
if self.opts.get('doc', False):
self._print_docs()
else:
self._verify_fun()
return self.functions[self.opts['fun']](*self.opts['arg'])
try:
return super(Runner, self).cmd(
self.opts['fun'], self.opts['arg'])
except salt.exceptions.SaltException as exc:
sys.stderr.write('{0}\n'.format(exc))
sys.exit(1)

View file

@ -58,41 +58,14 @@ def lookup_jid(jid):
'''
Return the printout from a previously executed job
'''
def _format_ret(full_ret):
'''
Take the full return data and format it to simple output
'''
out = None
ret = {}
for key, data in full_ret.items():
ret[key] = data['ret']
if 'out' in data:
out = data['out']
return ret, out
client = salt.client.LocalClient(__opts__['conf_file'])
full_ret = client.get_full_returns(jid, [], 0)
formatted = _format_ret(full_ret)
if all(formatted):
ret = formatted[0]
out = formatted[1]
else:
ret = SaltException(
'Job {0} hasn\'t finished. No data yet :('.format(jid)
)
out = ''
ret = {}
for mid, data in client.get_full_returns(jid, [], 0).items():
printout = salt.output.get_outputter(data.get('out', None))
ret[mid] = data.get('ret')
printout({mid: ret[mid]})
# Determine the proper output method and run it
get_outputter = salt.output.get_outputter
if isinstance(ret, (list, dict, string_types)) and out:
printout = get_outputter(out)
# Pretty print any salt exceptions
elif isinstance(ret, SaltException):
printout = get_outputter("txt")
else:
printout = get_outputter(None)
printout(ret)
return ret

View file

@ -29,7 +29,7 @@ import salt.fileclient
from salt._compat import string_types, callable
from salt.template import compile_template, compile_template_str
from salt.exceptions import SaltReqTimeoutError
from salt.exceptions import SaltReqTimeoutError, SaltException
log = logging.getLogger(__name__)
@ -264,6 +264,24 @@ class State(object):
elif data['state'] == 'pkg':
_refresh()
def verify_ret(self, ret):
'''
Verify the state return data
'''
if not isinstance(ret, dict):
raise SaltException(
'Malformed state return, return must be a dict'
)
bad = []
for val in ['name', 'result', 'changes', 'comment']:
if not val in ret:
bad.append(val)
if bad:
raise SaltException(
('The following keys were not present in the state '
'return: {0}'
).format(','.join(bad)))
def verify_data(self, data):
'''
Verify the data, return an error statement if something is wrong
@ -860,6 +878,7 @@ class State(object):
*cdata['args'], **cdata['kwargs'])
else:
ret = self.states[cdata['full']](*cdata['args'])
self.verify_ret(ret)
except Exception:
trb = traceback.format_exc()
ret = {

View file

@ -245,7 +245,14 @@ def purged(name):
def mod_init(low):
'''
Refresh the package database here so that it only needs to happen once
Set a flag to tell the install functions to refresh the package database.
This ensures that the package database is refreshed only once durring
a state run significaltly improving the speed of package management
durring a state run.
It sets a flag for a number of reasons, primarily due to timeline logic.
When originally setting up the mod_init for pkg a number of corner cases
arose with different package managers and how they refresh package data.
'''
if low['fun'] == 'installed' or low['fun'] == 'latest':
rtag = __gen_rtag()

View file

@ -187,6 +187,18 @@ def _disable(name, started):
return ret
def _available(name, ret):
# Check if the service is available
if 'service.available' in __salt__:
ret['available'] = __salt__['service.available'](name)
elif 'service.get_all' in __salt__:
ret['available'] = name in __salt__['service.get_all']()
if not ret.get('available', True):
ret['result'] = False
ret['comment'] = 'The named service {0} is not available'.format(name)
return ret
def running(name, enable=None, sig=None):
'''
Verify that the service is running
@ -216,15 +228,10 @@ def running(name, enable=None, sig=None):
else:
return ret
# Check if the service is available
if 'service.get_all' in __salt__:
# get_all is available, we can reliable check for the service
services = __salt__['service.get_all']()
if not name in services:
ret['result'] = False
ret['comment'] = 'The named service {0} is not available'.format(
name)
return ret
# Check if the service is available:
ret = _available(name, ret)
if not ret.pop('available', True):
return ret
# Run the tests
if __opts__['test']:
@ -282,15 +289,10 @@ def dead(name, enable=None, sig=None):
else:
return ret
# Check if the service is available
if 'service.get_all' in __salt__:
# get_all is available, we can reliable check for the service
services = __salt__['service.get_all']()
if not name in services:
ret['result'] = False
ret['comment'] = 'The named service {0} is not available'.format(
name)
return ret
# Check if the service is available:
ret = _available(name, ret)
if not ret.pop('available', True):
return ret
if __opts__['test']:
ret['result'] = None

View file

@ -37,13 +37,11 @@ def _changes(
password=None,
enforce_password=True,
shell=None,
fullname=None,
roomnumber=None,
workphone=None,
homephone=None,
other=None,
unique=True,
):
fullname='',
roomnumber='',
workphone='',
homephone=''):
'''
Return a dict of the changes required for a user if the user is present,
otherwise return False.
@ -83,21 +81,16 @@ def _changes(
lshad['pwd'] != '!' and enforce_password:
if lshad['pwd'] != password:
change['passwd'] = password
if fullname:
if lusr['fullname'] != fullname:
change['fullname'] = fullname
if roomnumber:
if lusr['roomnumber'] != roomnumber:
change['roomnumber'] = roomnumber
if workphone:
if lusr['workphone'] != workphone:
change['workphone'] = workphone
if homephone:
if lusr['homephone'] != homephone:
change['homephone'] = homephone
if other:
if lusr['other'] != other:
change['other'] = other
# GECOS fields
if lusr['fullname'] != fullname:
change['fullname'] = fullname
if lusr['roomnumber'] != roomnumber:
change['roomnumber'] = roomnumber
if lusr['workphone'] != workphone:
change['workphone'] = workphone
if lusr['homephone'] != homephone:
change['homephone'] = homephone
if not found:
return False
return change
@ -114,14 +107,12 @@ def present(
password=None,
enforce_password=True,
shell=None,
fullname=None,
roomnumber=None,
workphone=None,
homephone=None,
other=None,
unique=True,
system=False,
):
fullname='',
roomnumber='',
workphone='',
homephone=''):
'''
Ensure that the named user is present with the specified properties
@ -166,8 +157,14 @@ def present(
shell
The login shell, defaults to the system default shell
unique
Require a unique UID, True by default
User comment field (GECOS) support (currently Linux-only):
system
Choose UID in the range of FIRST_SYSTEM_UID and LAST_SYSTEM_UID.
User comment field (GECOS) support (currently Linux and FreeBSD only):
The below values should be specified as strings to avoid ambiguities when
the values are loaded. (Especially the phone and room number fields which
@ -184,15 +181,6 @@ def present(
homephone
The user's home phone number
other
The user's "other" GECOS field
unique
Require a unique UID, True by default
system
Choose UID in the range of FIRST_SYSTEM_UID and LAST_SYSTEM_UID.
'''
ret = {'name': name,
'changes': {},
@ -225,6 +213,11 @@ def present(
log.warning('Group "{0}" specified in both groups and '
'optional_groups for user {1}'.format(x,name))
if fullname is None: fullname = ''
if roomnumber is None: roomnumber = ''
if workphone is None: workphone = ''
if homephone is None: homephone = ''
if gid_from_name:
gid = __salt__['file.group_to_gid'](name)
changes = _changes(
@ -237,12 +230,11 @@ def present(
password,
enforce_password,
shell,
unique,
fullname,
roomnumber,
workphone,
homephone,
other,
unique)
homephone)
if changes:
if __opts__['test']:
ret['result'] = None
@ -290,13 +282,12 @@ def present(
groups=groups,
home=home,
shell=shell,
unique=unique,
system=system,
fullname=fullname,
roomnumber=roomnumber,
workphone=workphone,
homephone=homephone,
other=other,
unique=unique,
system=system):
homephone=homephone):
ret['comment'] = 'New user {0} created'.format(name)
ret['changes'] = __salt__['user.info'](name)
if password:

277
salt/utils/minions.py Normal file
View file

@ -0,0 +1,277 @@
'''
This module contains routines used to verify the matcher against the minions
expected to return
'''
# Import Python libs
import os
import glob
import fnmatch
import re
# Import Salt libs
import salt.payload
def nodegroup_comp(group, nodegroups, skip=None):
'''
Take the nodegroup and the nodegroups and fill in nodegroup refs
'''
if skip is None:
skip = set([group])
if not group in nodegroups:
return ''
gstr = nodegroups[group]
ret = ''
for comp in gstr.split():
if not comp.startswith('N@'):
ret += '{0} '.format(comp)
continue
ngroup = comp[2:]
if ngroup in skip:
continue
skip.add(ngroup)
ret += nodegroup_comp(ngroup, nodegroups, skip)
return ret
class CkMinions(object):
'''
Used to check what minions should respond from a target
'''
def __init__(self, opts):
self.opts = opts
self.serial = salt.payload.Serial(opts)
def _check_glob_minions(self, expr):
'''
Return the minions found by looking via globs
'''
cwd = os.getcwd()
os.chdir(os.path.join(self.opts['pki_dir'], 'minions'))
ret = set(glob.glob(expr))
os.chdir(cwd)
return list(ret)
def _check_list_minions(self, expr):
'''
Return the minions found by looking via a list
'''
ret = []
for fn_ in os.listdir(os.path.join(self.opts['pki_dir'], 'minions')):
if fn_ in expr:
if fn_ not in ret:
ret.append(fn_)
return ret
def _check_pcre_minions(self, expr):
'''
Return the minions found by looking via regular expressions
'''
ret = set()
cwd = os.getcwd()
os.chdir(os.path.join(self.opts['pki_dir'], 'minions'))
reg = re.compile(expr)
for fn_ in os.listdir('.'):
if reg.match(fn_):
ret.add(fn_)
os.chdir(cwd)
return list(ret)
def _check_grain_minions(self, expr):
'''
Return the minions found by looking via a list
'''
minions = set(os.listdir(os.path.join(self.opts['pki_dir'], 'minions')))
if self.opts.get('minion_data_cache', False):
cdir = os.path.join(self.opts['cachedir'], 'minions')
if not os.path.isdir(cdir):
return list(minions)
for id_ in os.listdir(cdir):
if not id_ in minions:
continue
datap = os.path.join(cdir, id_, 'data.p')
if not os.path.isfile(datap):
continue
grains = self.serial.load(open(datap)).get('grains')
comps = expr.split(':')
if len(comps) < 2:
continue
if comps[0] not in grains:
minions.remove(id_)
continue
if isinstance(grains.get(comps[0]), list):
# We are matching a single component to a single list member
found = False
for member in grains[comps[0]]:
if fnmatch.fnmatch(str(member).lower(), comps[1].lower()):
found = True
break
if found:
continue
minions.remove(id_)
continue
if fnmatch.fnmatch(
str(grains.get(comps[0], '').lower()),
comps[1].lower(),
):
continue
else:
minions.remove(id_)
return list(minions)
def _check_grain_pcre_minions(self, expr):
'''
Return the minions found by looking via a list
'''
minions = set(os.listdir(os.path.join(self.opts['pki_dir'], 'minions')))
if self.opts.get('minion_data_cache', False):
cdir = os.path.join(self.opts['cachedir'], 'minions')
if not os.path.isdir(cdir):
return list(minions)
for id_ in os.listdir(cdir):
if not id_ in minions:
continue
datap = os.path.join(cdir, id_, 'data.p')
if not os.path.isfile(datap):
continue
grains = self.serial.load(open(datap)).get('grains')
comps = expr.split(':')
if len(comps) < 2:
continue
if comps[0] not in grains:
minions.remove(id_)
if isinstance(grains[comps[0]], list):
# We are matching a single component to a single list member
found = False
for member in grains[comps[0]]:
if re.match(comps[1].lower(), str(member).lower()):
found = True
if found:
continue
minions.remove(id_)
continue
if re.match(
comps[1].lower(),
str(grains[comps[0]]).lower()
):
continue
else:
minions.remove(id_)
return list(minions)
def _all_minions(self, expr=None):
'''
Return a list of all minions that have auth'd
'''
return os.listdir(os.path.join(self.opts['pki_dir'], 'minions'))
def check_minions(self, expr, expr_form='glob'):
'''
Check the passed regex against the available minions' public keys
stored for authentication. This should return a set of ids which
match the regex, this will then be used to parse the returns to
make sure everyone has checked back in.
'''
try:
minions = {'glob': self._check_glob_minions,
'pcre': self._check_pcre_minions,
'list': self._check_list_minions,
'grain': self._check_grain_minions,
'grain_pcre': self._check_grain_pcre_minions,
'exsel': self._all_minions,
'pillar': self._all_minions,
'compound': self._all_minions,
}[expr_form](expr)
except Exception:
minions = expr
return minions
def validate_tgt(self, valid, expr, expr_form):
'''
Return a Bool. This function returns if the expresion sent in is within
the scope of the valid expression
'''
ref = {'G': 'grain',
'P': 'grain_pcre',
'X': 'exsel',
'I': 'pillar',
'L': 'list',
'S': 'ipcidr',
'E': 'pcre',
'N': 'node'}
infinate = [
'node',
'ipcidr',
'exsel',
'pillar',
]
if not self.opts.get('minion_data_cache', False):
infinate.append('grain')
infinate.append('grain_pcre')
if '@' in valid and valid[1] == '@':
comps = valid.split('@')
v_matcher = ref.get(comps[0])
v_expr = comps[1]
else:
v_matcher = 'glob'
v_expr = valid
if v_matcher in infinate:
# We can't be sure what the subset is, only match the identical
# target
if not v_matcher == expr_form:
return False
return v_expr == expr
v_minions = set(self.check_minions(v_expr, v_matcher))
minions = set(self.check_minions(expr, expr_form))
d_bool = bool(minions.difference(v_minions))
if len(v_minions) == len(minions) and not d_bool:
return True
return d_bool
def match_check(self, regex, fun):
'''
Validate a single regex to function comparison, the function argument
can be a list of functions. It is all or nothing for a list of
functions
'''
vals = []
if isinstance(fun, str):
fun = [fun]
for func in fun:
if re.match(regex, func):
vals.append(True)
else:
vals.append(False)
return all(vals)
def auth_check(self, auth_list, fun, tgt, tgt_type='glob'):
'''
Returns a bool which defines if the requested function is authorized.
Used to evaluate the standard structure under external master
authentication interfaces, like eauth, peer, peer_run, etc.
'''
for ind in auth_list:
if isinstance(ind, str):
# Allowed for all minions
if self.match_check(ind, fun):
return True
elif isinstance(ind, dict):
if len(ind) != 1:
# Invalid argument
continue
valid = ind.keys()[0]
# Check if minions are allowed
if self.validate_tgt(
valid,
tgt,
tgt_type):
# Minions are allowed, verify function in allowed list
if isinstance(ind[valid], str):
if self.match_check(ind[valid], fun):
return True
elif isinstance(ind[valid], list):
for regex in ind[valid]:
if self.match_check(regex, fun):
return True
return False

View file

@ -70,17 +70,19 @@ class OptionParserMeta(MixInMeta):
class OptionParser(optparse.OptionParser):
VERSION = version.__version__
usage = '%prog'
epilog = ('You can find additional help about %prog issuing "man %prog" '
'or on http://docs.saltstack.org/en/latest/index.html')
'or on http://docs.saltstack.org')
description = None
# Private attributes
_mixin_prio_ = 100
def __init__(self, *args, **kwargs):
kwargs.setdefault('version', '%prog {0}'.format(version.__version__))
kwargs.setdefault('version', '%prog {0}'.format(self.VERSION))
kwargs.setdefault('usage', self.usage)
if self.description:
kwargs.setdefault('description', self.description)
@ -688,7 +690,17 @@ class SaltCMDOptionParser(OptionParser, ConfigDirMixIn, TimeoutMixIn,
'-a', '--auth', '--eauth', '--extended-auth',
default='',
dest='eauth',
help=('Specify an extended authentication system to use.'))
help=('Specify an extended authentication system to use.')
)
self.add_option(
'-T', '--make-token',
default=False,
dest='mktoken',
action='store_true',
help=('Generate and save an authentication token for re-use. The'
'token is generated and made available for the period '
'defined in the Salt Master.')
)
self.add_option(
'--return',
default='',
@ -753,7 +765,7 @@ class SaltCMDOptionParser(OptionParser, ConfigDirMixIn, TimeoutMixIn,
self.config['arg'] = self.args[2:]
def setup_config(self):
return config.master_config(self.get_config_file_path('master'))
return config.client_config(self.get_config_file_path('master'))
class SaltCPOptionParser(OptionParser, ConfigDirMixIn, TimeoutMixIn,

View file

@ -27,8 +27,7 @@ class ManageTest(integration.ShellCase):
jobs.lookup_jid
'''
ret = self.run_run_plus('jobs.lookup_jid', '', '23974239742394')
self.assertFalse(ret['fun'])
self.assertFalse(ret['out'][1])
self.assertFalse(ret['out'][0])
def test_list_jobs(self):
'''

View file

@ -52,6 +52,10 @@ def parse():
action='store_true',
default=False,
help='Don\'t cleanup temporary files/directories')
parser.add_option('--root-dir',
dest='root_dir',
default=None,
help='Override the minion root_dir config')
options, args = parser.parse_args()
@ -69,7 +73,16 @@ class Swarm(object):
'''
def __init__(self, opts):
self.opts = opts
self.swarm_root = tempfile.mkdtemp(prefix='mswarm-root', suffix='.d')
# If given a root_dir, keep the tmp files there as well
if opts['root_dir']:
tmpdir = os.path.join(opts['root_dir'], 'tmp')
else:
tmpdir = opts['root_dir']
self.swarm_root = tempfile.mkdtemp(prefix='mswarm-root', suffix='.d',
dir=tmpdir)
self.pki = self._pki_dir()
self.__zfill = len(str(self.opts['minions']))
@ -110,6 +123,9 @@ class Swarm(object):
'log_file': os.path.join(dpath, 'minion.log')
}
if self.opts['root_dir']:
data['root_dir'] = self.opts['root_dir']
path = os.path.join(dpath, 'minion')
if self.opts['keep']:

View file

@ -1,3 +1,8 @@
'''
Test the verification routines
'''
# Import Python libs
import getpass
import os
import sys
@ -6,6 +11,7 @@ import shutil
import resource
import tempfile
# Import Salt libs
from saltunittest import skipIf, TestCase, TestsLoggingHandler
from salt.utils.verify import (
@ -18,6 +24,9 @@ from salt.utils.verify import (
class TestVerify(TestCase):
'''
Verify module tests
'''
def test_zmq_verify(self):
self.assertTrue(zmq_version())
@ -60,8 +69,8 @@ class TestVerify(TestCase):
self.assertEqual(dir_stat.st_uid, os.getuid())
self.assertEqual(dir_stat.st_gid, os.getgid())
self.assertEqual(dir_stat.st_mode & stat.S_IRWXU, stat.S_IRWXU)
self.assertEqual(dir_stat.st_mode & stat.S_IRWXG, 0)
self.assertEqual(dir_stat.st_mode & stat.S_IRWXO, 0)
self.assertEqual(dir_stat.st_mode & stat.S_IRWXG, 40)
self.assertEqual(dir_stat.st_mode & stat.S_IRWXO, 5)
def test_verify_socket(self):
self.assertTrue(verify_socket('', 18000, 18001))