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

resolution of documentation merge with upstream develop branch
This commit is contained in:
G. Clifford Williams 2012-10-20 13:13:59 -05:00
commit 607dab0e71
175 changed files with 6911 additions and 1837 deletions

View File

@ -14,7 +14,7 @@ before_install:
- "if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi"
install: pip install -r requirements.txt --use-mirrors
script: sudo -E python setup.py test --runtests-opts='--run-destructive'
script: sudo -E python setup.py test --runtests-opts='--run-destructive -v'
notifications:
irc:

View File

@ -154,6 +154,10 @@ If it is less than 2047, you should increase it with::
Running the tests
~~~~~~~~~~~~~~~~~
You will need a recent version of ``virtualenv``::
pip install "virtualenv>=1.8.2"
You will need ``mock`` to run the tests::
pip install mock

View File

@ -7,7 +7,6 @@ include tests/*.py
recursive-include tests *.py
include tests/integration/modules/files/*
include tests/integration/files/*
include tests/integration/tmp/_README
include tests/unit/templates/files/*
recursive-include doc *
recursive-include scripts *

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
@ -144,7 +144,14 @@
# larry:
# - test.ping
# - network.*
#
# The external auth system uses the Salt auth modules to authenticate and
# validate users to access areas of the Salt system
#
# external_auth:
# pam:
# fred:
# - test.*
##### Master Module Management #####
##########################################
@ -164,6 +171,17 @@
# root of the base environment as defined in "File Server settings" below.
#state_top: top.sls
#
# The master_tops option replaces the external_nodes option by creating
# a plugable system for the generation of external top data. The external_nodes
# option is deprecated by the master_tops option.
# To gain the capabilities of the classic external_nodes system use the
# following configuration
#
# master_tops:
# ext_nodes: <Shell command which returns yaml>
#
#master_tops: {}
#
# The external_nodes option allows Salt to gather data that would normally be
# placed in a top file. The external_nodes option is the executable that will
# return the ENC data. Remember that Salt will look for external nodes AND top
@ -238,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 #####
##########################################
@ -342,6 +364,6 @@
##### Range Cluster settings #####
##########################################
# The range server (and optional port) that
# serves your cluster information
# The range server (and optional port) that serves your cluster information
# https://github.com/grierj/range/wiki/Introduction-to-Range-with-YAML-files
#range_server: range:80

View File

@ -164,12 +164,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/changelog vendored
View File

@ -2,7 +2,7 @@ salt (0.10.3) precise; urgency=low
* New upstream version
-- Thomas S Hatch <thatch@saltstack.com> Sun, 30 Aug 2012 13:34:10 -0700
-- Tom Vaughan <thomas.david.vaughan@gmail.com> Sun, 30 Aug 2012 13:34:10 -0700
salt (0.10.2) precise; urgency=low

2
debian/copyright vendored
View File

@ -1,7 +1,7 @@
Format: http://dep.debian.net/deps/dep5
Upstream-Name: salt
Upstream-Contact: salt-users@googlegroups.com
Source: https://github.com/downloads/saltstack/salt/salt-0.9.9.tar.gz
Source: https://github.com/downloads/saltstack/salt/salt-0.10.3.tar.gz
Files: *
Copyright: 2012 Thomas S Hatch <thatch45@gmail.com>

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

View File

@ -5,7 +5,4 @@ start on (net-device-up
and runlevel [2345])
stop on runlevel [!2345]
respawn limit 10 5
respawn
exec /usr/bin/salt-master >/dev/null 2>&1

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

View File

@ -5,7 +5,4 @@ start on (net-device-up
and runlevel [2345])
stop on runlevel [!2345]
respawn limit 10 5
respawn
exec /usr/bin/salt-minion >/dev/null 2>&1

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

@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-
# pylint: disable=C0103,W0622
'''
Sphinx documentation for Salt
'''
import sys
import os
import types
@ -7,6 +10,7 @@ import types
from sphinx.directives import TocTree
# pylint: disable=R0903
class Mock(object):
'''
Mock out specified imports
@ -30,34 +34,36 @@ class Mock(object):
return type(name, (), {})
else:
return Mock()
# pylint: enable=R0903
MOCK_MODULES = [
# salt core
'yaml',
'yaml.nodes',
'yaml.constructor',
'msgpack',
'zmq',
'Crypto',
'Crypto.Cipher',
'Crypto.Hash',
'Crypto.PublicKey',
'Crypto.Random',
'M2Crypto',
'msgpack',
'yaml',
'yaml.constructor',
'yaml.nodes',
'zmq',
# modules, renderers, states, returners, et al
'django',
'libvirt',
'mako',
'mako.template',
'MySQLdb',
'MySQLdb.cursors',
'psutil',
'libvirt',
'yum',
'mako',
'mako.template',
'pycassa',
'pymongo',
'redis',
'rpm',
'rpmUtils',
'rpmUtils.arch',
'pycassa',
'yum',
]
for mod_name in MOCK_MODULES:
@ -92,8 +98,12 @@ master_doc = 'contents'
templates_path = ['_templates']
exclude_patterns = ['_build']
extensions = ['saltdocs', 'sphinx.ext.autodoc', 'sphinx.ext.extlinks',
'sphinx.ext.autosummary']
extensions = [
'saltdocs',
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.extlinks',
]
modindex_common_prefix = ['salt.']
@ -144,7 +154,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,11 +14,14 @@ 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
topics/tutorials/preseed_key
topics/tutorials/standalone_minion
topics/pillar/index
topics/master_tops/index
topics/jobs/index
topics/nonroot
topics/troubleshooting/index
@ -26,6 +29,7 @@ Full Table of Contents
topics/community
topics/projects/index
topics/event/index
topics/tests/*
ref/index
ref/modules/*
@ -38,7 +42,9 @@ Full Table of Contents
ref/renderers/all/index
ref/pillar/*
ref/pillar/all/index
ref/runners
ref/tops/*
ref/topstops/all/index
ref/runners/index
ref/peer
ref/clientacl
ref/syndic

View File

@ -70,7 +70,6 @@ truly make it work for you.
.. sidebar:: More tutorials!
* :doc:`Bootstraping Salt on EC2 <topics/tutorials/bootstrap_ec2>`
* :doc:`Installing Salt on FreeBSD <topics/installation/freebsd>`
* :doc:`Preseeding Minions with Accepted Keys <topics/tutorials/preseed_key>`
.. contents:: The components of Salt
@ -197,11 +196,14 @@ Salt is many splendid things.
management. The possibilities are endless and Salt's future looks
bright.
:doc:`Testing Salt <topics/tests/index>`
A howto for writing unit tests and integration tests.
:doc:`Python API interface <ref/python-api>`
Use Salt programmatically from your own scripts and programs easily and
simply via ``import salt``.
:doc:`Automatic Updates and Frozen Deployments <ref/esky>`
:doc:`Automatic Updates and Frozen Binary Deployments <topics/tutorials/esky>`
Use a frozen install to make deployments easier (Even on Windows!). Or
take advantage of automatic updates to keep your minions running your
latest builds.

View File

@ -135,6 +135,22 @@ Calling the Function
The function to call on the specified target is placed after the target
specification.
.. versionadded:: 0.9.8
Functions may also accept arguments, space-delimited:
.. code-block:: bash
salt '*' cmd.exec_code python 'import sys; print sys.version'
Optional, keyword arguments are also supported:
.. code-block:: bash
salt '*' pip.install salt timeout=5 upgrade=True
They are always in the form of ``kwarg=argument``.
Finding available minion functions
``````````````````````````````````

View File

@ -49,7 +49,8 @@ Options
.. option:: -d DELETE, --delete=DELETE
Delete the named minion key for command execution.
Delete the named minion key or minion keys matching a glob for command
execution.
.. option:: -D, --delete-all

View File

@ -94,7 +94,11 @@ Options
.. option:: -N, --nodegroup
Use a predefined compound target defined in the Salt master configuration
file
file.
.. option:: -S, --ipcidr
Match based on Subnet (CIDR notation) or IPv4 address.
.. option:: -R, --range

View File

@ -36,5 +36,5 @@ existing user keys and re-start the Salt master:
.. code-block:: bash
rm /var/cache/salt/.*keys
rm /var/cache/salt/.*key
service salt-master restart

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

@ -112,7 +112,7 @@ The location for minion cache data.
cachedir: /var/cache/salt
.. conf_minion:: cachedir
.. conf_minion:: backup_mode
``backup_mode``
---------------
@ -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

@ -21,6 +21,7 @@ Full list of builtin modules
apache
apt
archive
at
augeas_cfg
bluez
brew
@ -29,13 +30,14 @@ Full list of builtin modules
cassandra
cluster
cmdmod
config
cp
cron
data
debconfmod
debian_service
disk
django
djangomod
ebuild
event
file
@ -54,8 +56,10 @@ Full list of builtin modules
kmod
kvm_hyper
launchctl
ldap
linux_sysctl
mdadm
mongodb
monit
moosefs
mount
@ -67,6 +71,7 @@ Full list of builtin modules
openbsdservice
osxdesktop
pacman
pecl
pillar
pip
pkgng
@ -86,11 +91,13 @@ Full list of builtin modules
selinux
service
shadow
smf
solr
sqlite3
ssh
state
status
supervisord
systemd
test
tomcat
@ -106,7 +113,7 @@ Full list of builtin modules
win_service
win_shadow
win_useradd
yumpkg5
yumpkg
zfs
yumpkg5
zpool
zypper

View File

@ -0,0 +1,6 @@
===============
salt.modules.at
===============
.. automodule:: salt.modules.at
:members:

View File

@ -0,0 +1,6 @@
===================
salt.modules.config
===================
.. automodule:: salt.modules.config
:members:

View File

@ -1,6 +0,0 @@
===================
salt.modules.django
===================
.. automodule:: salt.modules.django
:members:

View File

@ -0,0 +1,6 @@
======================
salt.modules.djangomod
======================
.. automodule:: salt.modules.djangomod
:members:

View File

@ -0,0 +1,6 @@
=================
salt.modules.ldap
=================
.. automodule:: salt.modules.ldap
:members:

View File

@ -0,0 +1,6 @@
====================
salt.modules.mongodb
====================
.. automodule:: salt.modules.mongodb
:members:

View File

@ -0,0 +1,6 @@
=================
salt.modules.pecl
=================
.. automodule:: salt.modules.pecl
:members:

View File

@ -0,0 +1,6 @@
================
salt.modules.smf
================
.. automodule:: salt.modules.smf
:members:

View File

@ -0,0 +1,6 @@
========================
salt.modules.supervisord
========================
.. automodule:: salt.modules.supervisord
:members:

View File

@ -1,6 +0,0 @@
================
salt.modules.zfs
================
.. automodule:: salt.modules.zfs
:members:

View File

@ -0,0 +1,6 @@
==================
salt.modules.zpool
==================
.. automodule:: salt.modules.zpool
:members:

View File

@ -13,3 +13,4 @@ Full list of builtin pillars
cmd_yaml
hiera
mongo
pillar_ldap

View File

@ -0,0 +1,6 @@
=================
salt.pillar.pillar_ldap
=================
.. automodule:: salt.pillar.pillar_ldap
:members:

View File

@ -11,5 +11,5 @@ Salt includes a number of built-in external pillars, listed at
You may also wish to look at the standard pillar documentation, at
:ref:`pillar-configuration`
The source for the built-in Salt returners can be found here:
The source for the built-in Salt pillars can be found here:
:blob:`salt/pillar`

View File

@ -0,0 +1,16 @@
.. _all-salt.runners:
=========================
Full list of Salt runners
=========================
.. currentmodule:: salt.runners
.. autosummary::
:toctree:
:template: autosummary.rst.tmpl
jobs
launchd
manage
network

View File

@ -0,0 +1,6 @@
=================
salt.runners.jobs
=================
.. automodule:: salt.runners.jobs
:members:

View File

@ -0,0 +1,6 @@
====================
salt.runners.launchd
====================
.. automodule:: salt.runners.launchd
:members:

View File

@ -0,0 +1,6 @@
===================
salt.runners.manage
===================
.. automodule:: salt.runners.manage
:members:

View File

@ -0,0 +1,6 @@
====================
salt.runners.network
====================
.. automodule:: salt.runners.network
:members:

View File

@ -2,6 +2,8 @@
Salt Runners
============
.. seealso:: :ref:`The full list of runners <all-salt.runners>`
Salt runners are convenience applications executed with the salt-run command.
A Salt runner can be a simple client call, or a complex application.

View File

@ -17,17 +17,21 @@ Full list of builtin states
gem
git
group
hg
host
kmod
module
mongodb_database
mongodb_user
mount
mysql_database
mysql_grants
mysql_user
network
pecl
pip
pkgng
pkg
pkgng
postgres_database
postgres_user
rvm
@ -35,6 +39,7 @@ Full list of builtin states
service
ssh_auth
ssh_known_hosts
supervisord
sysctl
user
virtualenv

View File

@ -0,0 +1,6 @@
==============
salt.states.hg
==============
.. automodule:: salt.states.hg
:members:

View File

@ -0,0 +1,6 @@
============================
salt.states.mongodb_database
============================
.. automodule:: salt.states.mongodb_database
:members:

View File

@ -0,0 +1,6 @@
========================
salt.states.mongodb_user
========================
.. automodule:: salt.states.mongodb_user
:members:

View File

@ -0,0 +1,6 @@
================
salt.states.pecl
================
.. automodule:: salt.states.pecl
:members:

View File

@ -0,0 +1,6 @@
=======================
salt.states.supervisord
=======================
.. automodule:: salt.states.supervisord
:members:

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

@ -24,7 +24,7 @@ The commands sent out via the salt client are broadcast out to the minions via
ZeroMQ PUB/SUB. This is done by allowing the minions to maintain a connection
back to the Salt Master and then all connecions are informed to download the
command data at once. The command data is kept extreamly small (usually less
than 1K) so it is bnto a burden on the network.
than 1K) so it is not a burden on the network.
Return
======

View File

@ -0,0 +1,13 @@
.. _all-salt.tops:
================================
Full list of builtin master tops
================================
.. currentmodule:: salt.tops
.. autosummary::
:toctree:
:template: autosummary.rst.tmpl
ext_nodes

View File

@ -0,0 +1,6 @@
===================
salt.tops.ext_nodes
===================
.. automodule:: salt.tops.ext_nodes
:members:

13
doc/ref/tops/index.rst Normal file
View File

@ -0,0 +1,13 @@
.. _salt-top:
===========
Master Tops
===========
Salt includes a number of built-in subsystems to generate top file data, they
are listed listed at
:ref:`all-salt.tops`.
The source for the built-in Salt master tops can be found here:
:blob:`salt/tops`

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

@ -1 +0,0 @@
../../HACKING.rst

171
doc/topics/hacking.rst Normal file
View File

@ -0,0 +1,171 @@
Developing Salt
===============
If you want to help develop Salt there is a great need and your patches are
welcome!
To assist in Salt development, you can help in a number of ways.
Setting a Github pull request
-----------------------------
This is the preferred method for contributions, simply create a Github
fork, commit your changes to the fork, and then open up a pull request.
Posting patches to the mailing list
-----------------------------------
If you have a patch for Salt, please format it via :command:`git format-patch`
and send it to the Salt users mailing list. This allows the patch to give you
the contributor the credit for your patch, and gives the Salt community an
archive of the patch and a place for discussion.
Contributions Welcome!
----------------------
The goal here is to make contributions clear, make sure there is a trail for
where the code has come from, but most importantly, to give credit where credit
is due!
The `Open Comparison Contributing Docs`__ explains the workflow for forking,
cloning, branching, committing, and sending a pull request for the git
repository.
``git pull upstream develop`` is a shorter way to update your local repository
to the latest version.
.. __: http://opencomparison.readthedocs.org/en/latest/contributing.html
Editing and Previewing the Docs
-------------------------------
You need ``sphinx-build`` to build the docs. In Debian/Ubuntu this is provided
in the ``python-sphinx`` package.
Then::
cd doc; make html
- The docs then are built in the ``docs/_build/html/`` folder. If you make
changes and want to see the results, ``make html`` again.
- The docs use ``reStructuredText`` for markup. See a live demo at
http://rst.ninjs.org/
- The help information on each module or state is culled from the python code
that runs for that piece. Find them in ``salt/modules/`` or ``salt/states/``
Installing Salt for development
-------------------------------
Clone the repository using::
git clone https://github.com/saltstack/salt
Create a new `virtualenv`_::
virtualenv /path/to/your/virtualenv
.. note:: site packages
If you wish to use installed packages rather than have pip download and
compile new ones into this environment, add "--system-site-packages".
.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
Activate the virtualenv::
source /path/to/your/virtualenv/bin/activate
Install Salt (and dependencies) into the virtualenv::
pip install -e ./salt # the path to the salt git clone from above
.. note:: Installing M2Crypto
You may need ``swig`` and ``libssl-dev`` to build M2Crypto. If you
encounter the error ``command 'swig' failed with exit status 1``
while installing M2Crypto, try installing it with the following command::
env SWIG_FEATURES="-cpperraswarn -includeall -D__`uname -m`__ -I/usr/include/openssl" pip install M2Crypto
Debian and Ubuntu systems have modified openssl libraries and mandate that
a patched version of M2Crypto be installed. This means that M2Crypto
needs to be installed via apt:
apt-get install python-m2crypto
Running a self-contained development version
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
During development it is easiest to be able to run the Salt master and minion
that are installed in the virtualenv you created above, and also to have all
the configuration, log, and cache files contained in the virtualenv as well.
Copy the master and minion config files into your virtualenv::
mkdir -p /path/to/your/virtualenv/etc/salt
cp ./salt/conf/master.template /path/to/your/virtualenv/etc/salt/master
cp ./salt/conf/minion.template /path/to/your/virtualenv/etc/salt/minion
Edit the master config file:
1. Uncomment and change the ``user: root`` value to your own user.
2. Uncomment and change the ``root_dir: /`` value to point to
``/path/to/your/virtualenv``.
3. If you are also running a non-development version of Salt you will have to
change the ``publish_port`` and ``ret_port`` values as well.
Edit the minion config file:
1. Repeat the edits you made in the master config for the ``user`` and
``root_dir`` values as well as any port changes.
2. Uncomment and change the ``master: salt`` value to point at ``localhost``.
3. Uncomment and change the ``id:`` value to something descriptive like
"saltdev". This isn't strictly necessary but it will serve as a reminder of
which Salt installation you are working with.
.. note:: Using `salt-call` with a :doc:`Standalone Minion </topics/tutorials/standalone_minion>`
If you plan to run `salt-call` with this self-contained development
environment in a masterless setup, you should invoke `salt-call` with
``-c /path/to/your/virtualenv/etc/salt`` so that salt can find the minion
config file. Without the ``-c`` option, Salt finds its config files in `/etc/salt`.
Start the master and minion, accept the minon's key, and verify your local Salt
installation is working::
salt-master -c ./etc/salt -d
salt-minion -c ./etc/salt -d
salt-key -c ./etc/salt -L
salt-key -c ./etc/salt -A
salt -c ./etc/salt '*' test.ping
File descriptor limit
~~~~~~~~~~~~~~~~~~~~~
Check your file descriptor limit with::
ulimit -n
If it is less than 2047, you should increase it with::
ulimit -n 2047
(or "limit descriptors 2047" for c-shell)
Running the tests
~~~~~~~~~~~~~~~~~
You will need ``mock`` to run the tests::
pip install mock
If you are on Python < 2.7 then you will also need unittest2::
pip install unittest2
Finally you use setup.py to run the tests with the following command::
./setup.py test
For greater control while running the tests, please try::
./tests/runtests.py -h

View File

@ -1,3 +1,5 @@
.. _installation:
============
Installation
============

View File

@ -2,9 +2,15 @@
Solaris
=======
Salt was added to the OpenCSW package repository in September of 2012 by Romeo Theriault <romeot@hawaii.edu> at version 0.10.2 of Salt. It has mainly been tested on Solaris 10 (sparc), though it is built for, and should work fine on Solaris 10 (x86), Solaris 9 (sparc/x86) and 11 (sparc/x86) also. Most of the testing has also just focused on the minion, though it has verified that the master starts up successfully on Solaris 10.
Salt was added to the OpenCSW package repository in September of 2012 by Romeo Theriault <romeot@hawaii.edu> at version 0.10.2 of Salt. It has mainly been tested on Solaris 10 (sparc), though it is built for and has been tested minimally on Solaris 10 (x86), Solaris 9 (sparc/x86) and 11 (sparc/x86). (Please let me know if you're using it on these platforms!) Most of the testing has also just focused on the minion, though it has verified that the master starts up successfully on Solaris 10.
Comments and patches for better support on these platforms is very welcome. Currently at version 0.10.2 of salt, grain detection is weak but patches that very much improve the grain detection will be released in 0.10.3. Work is also underway to include support for services and packages in Solaris.
Comments and patches for better support on these platforms is very welcome. Currently at version 0.10.3 of salt, remote execution works good, grain detection is good and service control with SMF is supported. Work is underway to fill in the remaining gaps by adding support for the following:
1. 'pkg' states with pkgadd and pkgutil modules
2. support for solaris cron
3. support for user and group management
We hope to have all of the above included in v0.10.4 of Salt.
Salt is dependent on the following additional packages. These will automatically be installed as
dependencies of the ``py_salt`` package. ::
@ -52,7 +58,7 @@ Ok, time to install salt.
root> /opt/csw/bin/pkgutil -i -y py_salt
Minion Configuration
=============
====================
Now that salt is installed you can find it's configuration files in:
@ -99,7 +105,7 @@ Run a simple test against the minion:
salt '<your-salt-minion>' test.ping
Troubleshooting
=============
===============
Logs are in ``/var/log/salt``

View File

@ -32,8 +32,7 @@ Install on Windows XP 32bit
git clone git://github.com/saltstack/salt.git
2. Install `Microsoft Visual Studio 2008 Express`_ with the web installer.
Or `download a full iso with the installer`_ .
2. Install `Microsoft Visual Studio 2008 Express`_.
You must use Visual Studio 2008 Express, **not** Visual Studio 2010 Express.
3. Install `Python 2.7.x`_
@ -130,8 +129,7 @@ Install on Windows XP 32bit
.. _msysgit: http://code.google.com/p/msysgit/downloads/list?can=3
.. _Microsoft Visual Studio 2008 Express: http://www.microsoft.com/visualstudio/en-us/products/2008-editions/express
.. _download a full iso with the installer: http://www.microsoft.com/download/en/details.aspx?id=20682
.. _Microsoft Visual Studio 2008 Express: http://www.microsoft.com/en-gb/download/details.aspx?id=20682
.. _Python 2.7.x: http://www.python.org
.. _vcredist_x86: http://www.microsoft.com/download/en/details.aspx?id=5582
.. _Win32OpenSSL-1_0_0e.exe: http://www.slproweb.com/products/Win32OpenSSL.html

View File

@ -0,0 +1,20 @@
==================
Master Tops System
==================
In 0.10.4 the `external_nodes` system was upgraded to allow for modular
subsystems to be used to generate the top file data for a highstate run on
the master.
The old `external_nodes` option still works, but will be removed in the
future in favor of the new `master_tops` option which uses the modular
system instead. The master tops system contains a number of subsystems that
are loaded via the Salt loader interfaces like modules, states, returners,
runners, etc.
Using the new `master_tops` option is simple:
.. code-block:: yaml
master_tops:
ext_nodes: cobbler-external-nodes

View File

@ -49,7 +49,7 @@ The ability to generate fingerprints of keys used by Salt has been added to
``salt-key``. The new option `finger` accepts the name of the key to generate
and display a fingerprint for.
.. code-block:: base
.. code-block:: bash
salt-key -F master

View File

@ -0,0 +1,40 @@
=========================
Salt 0.10.4 Release Notes
=========================
The latest taste of Salt has come, this release has many fixes and feature
additions. Modifications have been made to make ZeroMQ connections more
reliable, the begining of the ACL system is in place, a new command line
parsing system has been added, dynamic module distribution has become more
environment aware, the new `master_finger` option and many more!
Major Features
==============
External Authentication System
------------------------------
Access Control System
---------------------
Master Tops System
------------------
Next Level Solaris Support
--------------------------
Security
========
A vulnerability in the handshake was found and has been repaired, old minions
should be able to connect to a new master, so as usual the master should be
updated first and then the minions.
Pillar Updates
--------------
The pillar communication has been updated to add some extra levels of
verification that the intended minion is the only one allowed to gather the
data. Once all minions and the master are updated to salt 0.10.4 please
activate pillar 2 by changing the `pillar_version` in the master config to
`2`. This will be set to `2` by default in a future release.

View File

@ -29,7 +29,7 @@ Here s the md5sum:
7d5aca4633bc22f59045f59e82f43b56
For instructions on how to set up Salt please see the `installation`_
For instructions on how to set up Salt please see the :ref:`installation`
instructions.
New Features

View File

@ -24,7 +24,7 @@ Here is the md5sum:
9a925da04981e65a0f237f2e77ddab37
For instructions on how to set up Salt please see the `installation`_
For instructions on how to set up Salt please see the :ref:`installation`
instructions.
New Features

View File

@ -23,7 +23,7 @@ Or from PiPy:
http://pypi.python.org/packages/source/s/salt/salt-0.9.2.tar.gz
For instructions on how to set up Salt please see the `installation`_
For instructions on how to set up Salt please see the :ref:`installation`
instructions.
New Features

View File

@ -22,7 +22,7 @@ Or from PiPy:
http://pypi.python.org/packages/source/s/salt/salt-0.9.3.tar.gz
For instructions on how to set up Salt please see the `installation`_
For instructions on how to set up Salt please see the :ref:`installation`
instructions.
New Features

View File

@ -24,7 +24,7 @@ Or from PiPy:
http://pypi.python.org/packages/source/s/salt/salt-0.9.4.tar.gz
For instructions on how to set up Salt please see the `installation`_
For instructions on how to set up Salt please see the :ref:`installation`
instructions.
New Features

View File

@ -21,6 +21,7 @@ E PCRE Minion id match ``E@web\d+\.(dev|qa|prod)\.loc``
P Grains PCRE match ``P@os:(RedHat|Fedora|CentOS)``
L List of minions ``L@minion1.example.com,minion3.domain.com and bl*.domain.com``
I Pillar glob match ``I@pdata:foobar``
S Subnet/IP addr match ``S@192.168.1.0/24`` or ``S@192.168.1.100``
====== ==================== ===============================================================
Matchers can be joined using boolean ``and``, ``or``, and ``not`` operators.

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

@ -11,7 +11,7 @@ Node groups
Nodegroups are declared using a compound target specification. The compount
target documentation can be found here:
:doc:`Compound Matchers <topics/targeting/compound>`
:doc:`Compound Matchers <compound>`
For example, in the master config file :conf_master:`nodegroups` setting::

View File

@ -23,12 +23,13 @@ Here is a sample script::
# Install saltstack
add-apt-repository ppa:saltstack/salt -y
apt-get update -y
apt-get install salt -y
apt-get install salt-minion -y
apt-get install salt-master -y
apt-get upgrade -y
# Set salt master location and start minion
cp /etc/salt/minion.template /etc/salt/minion
sed -i '' -e 's/#master: salt/master: [salt_master_fqdn]' /etc/salt/minion
sed -i 's/#master: salt/master: [salt_master_fqdn]/' /etc/salt/minion
salt-minion -d
First the script adds the saltstack ppa and installs the package. Then

View File

@ -57,7 +57,7 @@ Gotchas
My Windows minion isn't responding
----------------------------------
The process dispatch on Windows is slower than it is on *nix. You may need to
The process dispatch on Windows is slower than it is on \*nix. You may need to
add '-t 15' to your salt calls to give them plenty of time to return.
Windows and the Visual Studio Redist

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

@ -65,3 +65,9 @@ arguments
Space-delimited arguments to the function::
salt '*' cmd.exec_code python 'import sys; print sys.version'
Optional, keyword arguments are also supported::
salt '*' pip.install salt timeout=5 upgrade=True
They are always in the form of ``kwarg=argument``.

View File

@ -21,7 +21,7 @@ value in the master config.
.. _`Jinja2`: http://jinja.pocoo.org/
All states are passed through a templating system when they are initially read.
To make use of the templating system, simple add some templating markup.
To make use of the templating system, simply add some templating markup.
An example of an sls module with templating markup may look like this:
.. code-block:: yaml

View File

@ -9,8 +9,8 @@
%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
Name: salt
Version: 0.10.2
Release: 2%{?dist}
Version: 0.10.3
Release: 1%{?dist}
Summary: A parallel remote execution system
Group: System Environment/Daemons
@ -24,7 +24,6 @@ Source4: %{name}-master.service
Source5: %{name}-syndic.service
Source6: %{name}-minion.service
Source7: README.fedora
Patch0: 0001-Only-expect-args-if-they-are-actually-passed-in.patch
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildArch: noarch
@ -74,6 +73,11 @@ Requires(preun): initscripts
Requires(postun): initscripts
%else
%if 0%{?systemd_preun:1}
Requires(post): systemd-units
Requires(preun): systemd-units
Requires(postun): systemd-units
%endif
BuildRequires: systemd-units
@ -107,7 +111,6 @@ Salt minion is queried and controlled from the master.
%prep
%setup -q
%patch0 -p1
%build
@ -187,80 +190,109 @@ rm -rf $RPM_BUILD_ROOT
%config(noreplace) %{_sysconfdir}/salt/master
%config %{_sysconfdir}/salt/master.template
# less than RHEL 8 / Fedora 16
# not sure if RHEL 7 will use systemd yet
%if ! (0%{?rhel} >= 7 || 0%{?fedora} >= 15)
%preun -n salt-master
if [ $1 -eq 0 ] ; then
/sbin/service salt-master stop >/dev/null 2>&1
/sbin/service salt-syndic stop >/dev/null 2>&1
/sbin/chkconfig --del salt-master
/sbin/chkconfig --del salt-syndic
fi
if [ $1 -eq 0 ] ; then
/sbin/service salt-master stop >/dev/null 2>&1
/sbin/service salt-syndic stop >/dev/null 2>&1
/sbin/chkconfig --del salt-master
/sbin/chkconfig --del salt-syndic
fi
%preun -n salt-minion
if [ $1 -eq 0 ] ; then
/sbin/service salt-minion stop >/dev/null 2>&1
/sbin/chkconfig --del salt-minion
fi
if [ $1 -eq 0 ] ; then
/sbin/service salt-minion stop >/dev/null 2>&1
/sbin/chkconfig --del salt-minion
fi
%post -n salt-master
/sbin/chkconfig --add salt-master
/sbin/chkconfig --add salt-syndic
/sbin/chkconfig --add salt-master
/sbin/chkconfig --add salt-syndic
%post -n salt-minion
/sbin/chkconfig --add salt-minion
/sbin/chkconfig --add salt-minion
%postun -n salt-master
if [ "$1" -ge "1" ] ; then
/sbin/service salt-master condrestart >/dev/null 2>&1 || :
/sbin/service salt-syndic condrestart >/dev/null 2>&1 || :
fi
if [ "$1" -ge "1" ] ; then
/sbin/service salt-master condrestart >/dev/null 2>&1 || :
/sbin/service salt-syndic condrestart >/dev/null 2>&1 || :
fi
%postun -n salt-minion
if [ "$1" -ge "1" ] ; then
/sbin/service salt-master condrestart >/dev/null 2>&1 || :
/sbin/service salt-syndic condrestart >/dev/null 2>&1 || :
fi
if [ "$1" -ge "1" ] ; then
/sbin/service salt-master condrestart >/dev/null 2>&1 || :
/sbin/service salt-syndic condrestart >/dev/null 2>&1 || :
fi
%else
%preun -n salt-master
if [ $1 -eq 0 ] ; then
%if 0%{?systemd_preun:1}
%systemd_preun salt-master.service
%else
if [ $1 -eq 0 ] ; then
# Package removal, not upgrade
/bin/systemctl --no-reload disable salt-master.service > /dev/null 2>&1 || :
/bin/systemctl stop salt-master.service > /dev/null 2>&1 || :
/bin/systemctl --no-reload disable salt-syndic.service > /dev/null 2>&1 || :
/bin/systemctl stop salt-syndic.service > /dev/null 2>&1 || :
fi
fi
%endif
%preun -n salt-minion
if [ $1 -eq 0 ] ; then
# Package removal, not upgrade
/bin/systemctl --no-reload disable salt-master.service > /dev/null 2>&1 || :
/bin/systemctl stop salt-master.service > /dev/null 2>&1 || :
fi
%if 0%{?systemd_preun:1}
%systemd_preun salt-minion.service
%else
if [ $1 -eq 0 ] ; then
# Package removal, not upgrade
/bin/systemctl --no-reload disable salt-master.service > /dev/null 2>&1 || :
/bin/systemctl stop salt-master.service > /dev/null 2>&1 || :
fi
%endif
%post -n salt-master
/bin/systemctl daemon-reload &>/dev/null || :
%if 0%{?systemd_post:1}
%systemd_post salt-master.service
%else
/bin/systemctl daemon-reload &>/dev/null || :
%endif
%post -n salt-minion
/bin/systemctl daemon-reload &>/dev/null || :
%if 0%{?systemd_post:1}
%systemd_post salt-minion.service
%else
/bin/systemctl daemon-reload &>/dev/null || :
%endif
%postun -n salt-master
/bin/systemctl daemon-reload &>/dev/null
[ $1 -gt 0 ] && /bin/systemctl try-restart salt-master.service &>/dev/null || :
[ $1 -gt 0 ] && /bin/systemctl try-restart salt-syndic.service &>/dev/null || :
%if 0%{?systemd_post:1}
%systemd_postun salt-master.service
%else
/bin/systemctl daemon-reload &>/dev/null
[ $1 -gt 0 ] && /bin/systemctl try-restart salt-master.service &>/dev/null || :
[ $1 -gt 0 ] && /bin/systemctl try-restart salt-syndic.service &>/dev/null || :
%endif
%postun -n salt-minion
/bin/systemctl daemon-reload &>/dev/null
[ $1 -gt 0 ] && /bin/systemctl try-restart salt-master.service &>/dev/null || :
[ $1 -gt 0 ] && /bin/systemctl try-restart salt-syndic.service &>/dev/null || :
%if 0%{?systemd_post:1}
%systemd_postun salt-minion.service
%else
/bin/systemctl daemon-reload &>/dev/null
[ $1 -gt 0 ] && /bin/systemctl try-restart salt-master.service &>/dev/null || :
[ $1 -gt 0 ] && /bin/systemctl try-restart salt-syndic.service &>/dev/null || :
%endif
%endif
%changelog
* Tue Oct 2 2012 Clint Savage <herlo1@gmail.com> - 0.10.3-1
- Moved to upstream release 0.10.3
- Added systemd scriplets (RHBZ#850408)
* Thu Aug 2 2012 Clint Savage <herlo1@gmail.com> - 0.10.2-2
- Fix upstream bug #1730 per RHBZ#845295

View File

@ -13,7 +13,6 @@ import getpass
from salt.version import __version__
try:
import salt.config
from salt.utils import parsers
from salt.utils.verify import check_user, verify_env, verify_socket
except ImportError as e:
@ -43,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'],
@ -99,7 +99,8 @@ class Minion(parsers.MinionOptionParser):
sys.exit(err.errno)
self.setup_logfile_logger()
logging.getLogger(__name__).warn(
log = logging.getLogger(__name__)
log.warn(
'Setting up the Salt Minion "{0}"'.format(
self.config['id']
)
@ -146,7 +147,8 @@ class Syndic(parsers.SyndicOptionParser):
sys.exit(err.errno)
self.setup_logfile_logger()
logging.getLogger(__name__).warn(
log = logging.getLogger(__name__)
log.warn(
'Setting up the Salt Syndic Minion "{0}"'.format(
self.config['id']
)

205
salt/auth/__init__.py Normal file
View File

@ -0,0 +1,205 @@
'''
Salt's pluggable authentication system
This sysetm allows for authentication to be managed in a module pluggable way
so that any external authentication system can be used inside of Salt
'''
# 1. Create auth loader instance
# 2. Accept arguments as a dict
# 3. Verify with function introspection
# 4. Execute auth function
# 5. Cache auth token with relative data opts['token_dir']
# 6. Interface to verify tokens
# Import Python libs
import os
import hashlib
import time
import logging
import random
import getpass
# Import Salt libs
import salt.loader
import salt.utils
import salt.payload
log = logging.getLogger(__name__)
class LoadAuth(object):
'''
Wrap the authentication system to handle periphrial components
'''
def __init__(self, opts):
self.opts = opts
self.max_fail = 1.0
self.serial = salt.payload.Serial(opts)
self.auth = salt.loader.auth(opts)
def load_name(self, load):
'''
Return the primary name associate with the load, if an empty string
is returned then the load does not match the function
'''
if not 'eauth' in load:
return ''
fstr = '{0}.auth'.format(load['eauth'])
if not fstr in self.auth:
return ''
fcall = salt.utils.format_call(self.auth[fstr], load)
try:
return fcall['args'][0]
except IndexError:
return ''
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
fstr = '{0}.auth'.format(load['eauth'])
if not fstr in self.auth:
return False
fcall = salt.utils.format_call(self.auth[fstr], load)
try:
if 'kwargs' in fcall:
return self.auth[fstr](*fcall['args'], **fcall['kwargs'])
else:
return self.auth[fstr](*fcall['args'])
except Exception as exc:
err = 'Authentication module threw an exception: {0}'.format(exc)
log.critical(err)
return False
return False
def time_auth(self, load):
'''
Make sure that all failures happen in the same amount of time
'''
start = time.time()
ret = self.__auth_call(load)
if ret:
return ret
f_time = time.time() - start
if f_time > self.max_fail:
self.max_fail = f_time
deviation = self.max_fail / 4
r_time = random.uniform(
self.max_fail - deviation,
self.max_fail + deviation
)
while start + r_time > time.time():
time.sleep(0.001)
return False
def mk_token(self, load):
'''
Run time_auth and create a token. Return False or the token
'''
ret = self.time_auth(load)
if ret is False:
return ret
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(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],
'eauth': load['eauth'],
'token': tok}
with open(t_path, 'w+') as fp_:
fp_.write(self.serial.dumps(tdata))
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(self.opts['token_dir'], tok)
if not os.path.isfile:
return {}
with open(t_path, 'r') as fp_:
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):
'''
The class used to resolve options for the command line and for genric
interactive interfaces
'''
def __init__(self, opts):
self.opts = opts
self.auth = salt.loader.auth(opts)
def cli(self, eauth):
'''
Execute the cli options to fill in the extra data needed for the
defined eauth system
'''
ret = {}
if not eauth:
print 'External authentication system has not been specified'
return ret
fstr = '{0}.auth'.format(eauth)
if not fstr in self.auth:
print ('The specified external authentication system "{0}" is '
'not available').format(eauth)
return ret
args = salt.utils.arg_lookup(self.auth[fstr])
for arg in args['args']:
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():
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

146
salt/auth/pam.py Normal file
View File

@ -0,0 +1,146 @@
# The pam components have been modified to be salty and have been taken from
# the pam module under this licence:
# (c) 2007 Chris AtLee <chris@atlee.ca>
# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license.php
'''
PAM module for python
Provides an authenticate function that will allow the caller to authenticate
a user against the Pluggable Authentication Modules (PAM) on the system.
Implemented using ctypes, so no compilation is necessary.
'''
# Import python libs
from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, pointer, sizeof
from ctypes import c_void_p, c_uint, c_char_p, c_char, c_int
from ctypes.util import find_library
LIBPAM = CDLL(find_library('pam'))
LIBC = CDLL(find_library('c'))
CALLOC = LIBC.calloc
CALLOC.restype = c_void_p
CALLOC.argtypes = [c_uint, c_uint]
STRDUP = LIBC.strdup
STRDUP.argstypes = [c_char_p]
STRDUP.restype = POINTER(c_char) # NOT c_char_p !!!!
# Various constants
PAM_PROMPT_ECHO_OFF = 1
PAM_PROMPT_ECHO_ON = 2
PAM_ERROR_MSG = 3
PAM_TEXT_INFO = 4
class PamHandle(Structure):
'''
Wrapper class for pam_handle_t
'''
_fields_ = [
('handle', c_void_p)
]
def __init__(self):
Structure.__init__(self)
self.handle = 0
class PamMessage(Structure):
'''
Wrapper class for pam_message structure
'''
_fields_ = [
("msg_style", c_int),
("msg", c_char_p),
]
def __repr__(self):
return "<PamMessage %i '%s'>" % (self.msg_style, self.msg)
class PamResponse(Structure):
'''
Wrapper class for pam_response structure
'''
_fields_ = [
('resp', c_char_p),
('resp_retcode', c_int),
]
def __repr__(self):
return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp)
CONV_FUNC = CFUNCTYPE(c_int,
c_int, POINTER(POINTER(PamMessage)),
POINTER(POINTER(PamResponse)), c_void_p)
class PamConv(Structure):
'''
Wrapper class for pam_conv structure
'''
_fields_ = [
('conv', CONV_FUNC),
('appdata_ptr', c_void_p)
]
PAM_START = LIBPAM.pam_start
PAM_START.restype = c_int
PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv),
POINTER(PamHandle)]
PAM_AUTHENTICATE = LIBPAM.pam_authenticate
PAM_AUTHENTICATE.restype = c_int
PAM_AUTHENTICATE.argtypes = [PamHandle, c_int]
def authenticate(username, password, service='login'):
'''
Returns True if the given username and password authenticate for the
given service. Returns False otherwise
``username``: the username to authenticate
``password``: the password in plain text
``service``: the PAM service to authenticate against.
Defaults to 'login'
'''
@CONV_FUNC
def my_conv(n_messages, messages, p_response, app_data):
'''
Simple conversation function that responds to any
prompt where the echo is off with the supplied password
'''
# Create an array of n_messages response objects
addr = CALLOC(n_messages, sizeof(PamResponse))
p_response[0] = cast(addr, POINTER(PamResponse))
for i in range(n_messages):
if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
pw_copy = STRDUP(str(password))
p_response.contents[i].resp = cast(pw_copy, c_char_p)
p_response.contents[i].resp_retcode = 0
return 0
handle = PamHandle()
conv = PamConv(my_conv, 0)
retval = PAM_START(service, username, pointer(conv), pointer(handle))
if retval != 0:
# TODO: This is not an authentication error, something
# has gone wrong starting up PAM
return False
retval = PAM_AUTHENTICATE(handle, 0)
return retval == 0
def auth(username, password, **kwargs):
'''
Authenticate via pam
'''
return authenticate(username, password, kwargs.get('service', 'login'))

View File

@ -4,6 +4,7 @@ The management of salt command line utilities are stored in here
# Import python libs
import os
import sys
# Import salt components
import salt.cli.caller
@ -13,6 +14,7 @@ import salt.cli.batch
import salt.client
import salt.output
import salt.runner
import salt.auth
from salt.utils import parsers
from salt.utils.verify import verify_env
@ -50,42 +52,57 @@ class SaltCMD(parsers.SaltCMDOptionParser):
if self.options.timeout <= 0:
self.options.timeout = local.opts['timeout']
args = [
self.config['tgt'],
self.config['fun'],
self.config['arg'],
self.options.timeout,
]
kwargs = {
'tgt': self.config['tgt'],
'fun': self.config['fun'],
'arg': self.config['arg'],
'timeout': self.options.timeout}
if 'token' in self.config:
kwargs['token'] = self.config['token']
if self.selected_target_option:
args.append(self.selected_target_option)
kwargs['expr_form'] = self.selected_target_option
else:
args.append('glob')
kwargs['expr_form'] = 'glob'
if getattr(self.options, 'return'):
args.append(getattr(self.options, 'return'))
else:
args.append('')
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)
kwargs['eauth'] = self.options.eauth
try:
# local will be None when there was an error
if local:
if self.options.static:
if self.options.verbose:
args.append(True)
full_ret = local.cmd_full_return(*args)
kwargs['verbose'] = True
full_ret = local.cmd_full_return(**kwargs)
ret, out = self._format_ret(full_ret)
self._output_ret(ret, out)
elif self.config['fun'] == 'sys.doc':
ret = {}
out = ''
for full_ret in local.cmd_cli(*args):
for full_ret in local.cmd_cli(**kwargs):
ret_, out = self._format_ret(full_ret)
ret.update(ret_)
self._output_ret(ret, out)
else:
if self.options.verbose:
args.append(True)
for full_ret in local.cmd_cli(*args):
kwargs['verbose'] = True
for full_ret in local.cmd_cli(**kwargs):
ret, out = self._format_ret(full_ret)
self._output_ret(ret, out)
except SaltInvocationError as exc:

View File

@ -7,6 +7,7 @@ import os
import shutil
import sys
import logging
import glob
# Import salt modules
import salt.crypt
import salt.utils
@ -278,9 +279,10 @@ class Key(object):
def _delete_key(self, delete=None):
'''
Delete a key
Delete a key or keys by glob
'''
# Don't ask for verification if yes is not set
del_ = []
yes = self.opts.get('yes', True)
(minions_accepted,
minions_pre,
@ -290,48 +292,25 @@ class Key(object):
else:
# If a key is explicitly passed then don't ask for verification
yes = True
pre = os.path.join(minions_pre, delete)
acc = os.path.join(minions_accepted, delete)
rej = os.path.join(minions_rejected, delete)
if os.path.exists(pre):
del_.extend(glob.glob(os.path.join(minions_pre, delete)))
del_.extend(glob.glob(os.path.join(minions_accepted, delete)))
del_.extend(glob.glob(os.path.join(minions_rejected, delete)))
if del_:
rm_ = True
if not yes:
msg = ('The following pending key is set to be removed: {0}'
'\n[n/Y]').format(delete)
veri = raw_input(msg)
# Default to Yes
msg = 'The following keys are going to be deleted:\n'
#for key in sorted(del_):
for key in sorted(set(del_)):
msg += '{0}\n'.format(key)
veri = raw_input('{0}[n/Y]'.format(msg))
if veri.lower().startswith('n'):
rm_ = False
if rm_:
os.remove(pre)
self._log('Removed pending key {0}'.format(delete),
level='info')
if os.path.exists(acc):
rm_ = True
if not yes:
msg = ('The following accepted key is set to be removed: {0}'
'\n[n/Y]').format(delete)
veri = raw_input(msg)
# Default to Yes
if veri.lower().startswith('n'):
rm_ = False
if rm_:
os.remove(acc)
self._log('Removed accepted key {0}'.format(delete),
level='info')
if os.path.exists(rej):
rm_ = True
if not yes:
msg = ('The following rejected key is set to be removed: {0}'
'\n[n/Y]').format(delete)
veri = raw_input(msg)
# Default to Yes
if veri.lower().startswith('n'):
rm_ = False
if rm_:
os.remove(rej)
self._log('Removed rejected key {0}'.format(delete),
level='info')
for key in del_:
os.remove(key)
filepath, filename = os.path.split(key)
self._log('Removed pending key {0}'.format(filename),
level='info')
def _delete_all(self):
'''

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()
@ -93,9 +91,8 @@ class LocalClient(object):
with open(keyfile, 'r') as KEY:
return KEY.read()
except (OSError, IOError):
# In theory, this should never get hit. Belt & suspenders baby!
raise SaltClientError(('Problem reading the salt root key. Are'
' you root?'))
# Fall back to eauth
return ''
def __get_user(self):
'''
@ -104,7 +101,7 @@ class LocalClient(object):
user = getpass.getuser()
# if our user is root, look for other ways to figure out
# who we are
if user == 'root':
if user == 'root' or 'SUDO_USER' in os.environ:
env_vars = ['SUDO_USER', 'USER', 'USERNAME']
for evar in env_vars:
if evar in os.environ:
@ -116,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:
@ -247,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
'''
@ -256,7 +130,56 @@ class LocalClient(object):
'saltutil.find_job',
[jid],
2,
tgt_type)
tgt_type,
**kwargs)
def _check_pub_data(self, pub_data):
'''
Common checks on the pub_data data structure returned from running pub
'''
if not pub_data:
err = ('Failed to authenticate, is this user permitted to execute '
'commands?\n')
sys.stderr.write(err)
sys.exit(4)
# Failed to connect to the master and send the pub
if not 'jid' in pub_data or pub_data['jid'] == '0':
return {}
return pub_data
def run_job(self,
tgt,
fun,
arg=(),
expr_form='glob',
ret='',
timeout=None,
**kwargs):
'''
Prep the job dir and send minions the pub.
Returns a dict of (checked) pub_data or an empty dict.
'''
try:
jid = salt.utils.prep_jid(
self.opts['cachedir'],
self.opts['hash_type']
)
except Exception:
jid = ''
pub_data = self.pub(
tgt,
fun,
arg,
expr_form,
ret,
jid=jid,
timeout=timeout or self.opts['timeout'],
**kwargs)
return self._check_pub_data(pub_data)
def cmd(
self,
@ -266,39 +189,26 @@ class LocalClient(object):
timeout=None,
expr_form='glob',
ret='',
kwarg=None):
kwarg=None,
**kwargs):
'''
Execute a salt command and return.
'''
arg = condition_kwarg(arg, kwarg)
if timeout is None:
timeout = self.opts['timeout']
try:
jid = salt.utils.prep_jid(
self.opts['cachedir'],
self.opts['hash_type']
)
except Exception:
jid = ''
pub_data = self.pub(
pub_data = self.run_job(
tgt,
fun,
arg,
expr_form,
ret,
jid=jid,
timeout=timeout)
timeout,
**kwargs)
if not pub_data:
err = ('Failed to authenticate, is this user permitted to execute '
'commands?\n')
sys.stderr.write(err)
sys.exit(4)
if pub_data['jid'] == '0':
# Failed to connect to the master and send the pub
return {}
elif not pub_data['jid']:
return {}
return self.get_returns(pub_data['jid'], pub_data['minions'], timeout)
return pub_data
return self.get_returns(pub_data['jid'], pub_data['minions'],
timeout or self.opts['timeout'])
def cmd_cli(
self,
@ -309,49 +219,36 @@ class LocalClient(object):
expr_form='glob',
ret='',
verbose=False,
kwarg=None):
kwarg=None,
**kwargs):
'''
Execute a salt command and return data conditioned for command line
output
'''
arg = condition_kwarg(arg, kwarg)
if timeout is None:
timeout = self.opts['timeout']
try:
jid = salt.utils.prep_jid(
self.opts['cachedir'],
self.opts['hash_type']
)
except Exception:
jid = ''
pub_data = self.pub(
pub_data = self.run_job(
tgt,
fun,
arg,
expr_form,
ret,
jid=jid,
timeout=timeout)
timeout,
**kwargs)
if not pub_data:
err = ('Failed to authenticate, is this user permitted to execute '
'commands?\n')
sys.stderr.write(err)
sys.exit(4)
if pub_data['jid'] == '0':
print('Failed to connect to the Master, is the Salt Master running?')
yield {}
elif not pub_data['jid']:
print('No minions match the target')
yield {}
yield pub_data
else:
for fn_ret in self.get_cli_event_returns(pub_data['jid'],
pub_data['minions'],
timeout,
timeout or self.opts['timeout'],
tgt,
expr_form,
verbose):
verbose,
**kwargs):
if not fn_ret:
continue
yield fn_ret
def cmd_iter(
@ -362,36 +259,24 @@ class LocalClient(object):
timeout=None,
expr_form='glob',
ret='',
kwarg=None):
kwarg=None,
**kwargs):
'''
Execute a salt command and return an iterator to return data as it is
received
'''
arg = condition_kwarg(arg, kwarg)
if timeout is None:
timeout = self.opts['timeout']
jid = salt.utils.prep_jid(
self.opts['cachedir'],
self.opts['hash_type']
)
pub_data = self.pub(
pub_data = self.run_job(
tgt,
fun,
arg,
expr_form,
ret,
jid=jid,
timeout=timeout)
timeout,
**kwargs)
if not pub_data:
err = ('Failed to authenticate, is this user permitted to execute '
'commands?\n')
sys.stderr.write(err)
sys.exit(4)
if pub_data['jid'] == '0':
# Failed to connect to the master and send the pub
yield {}
elif not pub_data['jid']:
yield {}
yield pub_data
else:
for fn_ret in self.get_iter_returns(pub_data['jid'],
pub_data['minions'],
@ -408,35 +293,23 @@ class LocalClient(object):
timeout=None,
expr_form='glob',
ret='',
kwarg=None):
kwarg=None,
**kwargs):
'''
Execute a salt command and return
'''
arg = condition_kwarg(arg, kwarg)
if timeout is None:
timeout = self.opts['timeout']
jid = salt.utils.prep_jid(
self.opts['cachedir'],
self.opts['hash_type']
)
pub_data = self.pub(
pub_data = self.run_job(
tgt,
fun,
arg,
expr_form,
ret,
jid=jid,
timeout=timeout)
timeout,
**kwargs)
if not pub_data:
err = ('Failed to authenticate, is this user permitted to execute '
'commands?\n')
sys.stderr.write(err)
sys.exit(4)
if pub_data['jid'] == '0':
# Failed to connect to the master and send the pub
yield {}
elif not pub_data['jid']:
yield {}
yield pub_data
else:
for fn_ret in self.get_iter_returns(pub_data['jid'],
pub_data['minions'],
@ -452,35 +325,24 @@ class LocalClient(object):
expr_form='glob',
ret='',
verbose=False,
kwarg=None):
kwarg=None,
**kwargs):
'''
Execute a salt command and return
'''
arg = condition_kwarg(arg, kwarg)
if timeout is None:
timeout = self.opts['timeout']
jid = salt.utils.prep_jid(
self.opts['cachedir'],
self.opts['hash_type']
)
pub_data = self.pub(
pub_data = self.run_job(
tgt,
fun,
arg,
expr_form,
ret,
jid=jid,
timeout=timeout)
timeout,
**kwargs)
if not pub_data:
err = ('Failed to authenticate, is this user permitted to execute '
'commands?\n')
sys.stderr.write(err)
sys.exit(4)
if pub_data['jid'] == '0':
# Failed to connect to the master and send the pub
return {}
elif not pub_data['jid']:
return {}
return pub_data
return (self.get_cli_static_event_returns(pub_data['jid'],
pub_data['minions'],
timeout,
@ -495,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
@ -558,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_]:
@ -753,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)
@ -810,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
'''
@ -865,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_]:
@ -947,29 +812,15 @@ 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, arg=(), expr_form='glob',
ret='', jid='', timeout=5):
def pub(self,
tgt,
fun,
arg=(),
expr_form='glob',
ret='',
jid='',
timeout=5,
**kwargs):
'''
Take the required arguments and publish the given command.
Arguments:
@ -1005,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
@ -1014,28 +868,26 @@ 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:
payload_kwargs['kwargs'] = kwargs
# If we have a salt user, add it to the payload
if self.salt_user:
@ -1052,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
@ -239,14 +238,14 @@ def minion_config(path):
opts['open_mode'] = opts['open_mode'] is True
# set up the extension_modules location from the cachedir
opts['extension_modules'] = os.path.join(opts['cachedir'], 'extmods')
opts['extension_modules'] = (
opts.get('extension_modules') or
os.path.join(opts['cachedir'], 'extmods')
)
# Prepend root_dir to other paths
prepend_root_dir(opts, ['pki_dir', 'cachedir', 'log_file', 'sock_dir',
'key_logfile', 'extension_modules'])
opts['grains'] = salt.loader.grains(opts)
return opts
@ -274,20 +273,25 @@ def master_config(path):
'pillar_roots': {
'base': ['/srv/pillar'],
},
'ext_pillar': {},
'ext_pillar': [],
# TODO - Set this to 2 by default in 0.10.5
'pillar_version': 1,
'syndic_master': '',
'runner_dirs': [],
'client_acl': {},
'external_auth': {},
'token_expire': 720,
'file_buffer_size': 1048576,
'max_open_files': 100000,
'hash_type': 'md5',
'conf_file': path,
'pub_refresh': True,
'pub_refresh': False,
'open_mode': False,
'auto_accept': False,
'renderer': 'yaml_jinja',
'failhard': False,
'state_top': 'top.sls',
'master_tops': {},
'external_nodes': '',
'order_masters': False,
'job_cache': True,
@ -327,10 +331,15 @@ def master_config(path):
opts['aes'] = salt.crypt.Crypticle.generate_key_string()
opts['extension_modules'] = os.path.join(opts['cachedir'], 'extmods')
opts['extension_modules'] = (
opts.get('extension_modules') or
os.path.join(opts['cachedir'], 'extmods')
)
opts['token_dir'] = os.path.join(opts['cachedir'], 'tokens')
# Prepend root_dir to other paths
prepend_root_dir(opts, ['pki_dir', 'cachedir', 'log_file',
'sock_dir', 'key_logfile', 'extension_modules', 'autosign_file'])
'sock_dir', 'key_logfile', 'extension_modules',
'autosign_file', 'token_dir'])
# Enabling open mode requires that the value be set to True, and
# nothing else!
@ -338,3 +347,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

@ -129,6 +129,7 @@ class Auth(object):
'''
def __init__(self, opts):
self.opts = opts
self.token = Crypticle.generate_key_string()
self.serial = salt.payload.Serial(self.opts)
self.pub_path = os.path.join(self.opts['pki_dir'], 'minion.pub')
self.rsa_path = os.path.join(self.opts['pki_dir'], 'minion.pem')
@ -177,6 +178,11 @@ class Auth(object):
payload['load'] = {}
payload['load']['cmd'] = '_auth'
payload['load']['id'] = self.opts['id']
try:
pub = RSA.load_pub_key(os.path.join(self.opts['pki_dir'], self.mpub))
payload['load']['token'] = pub.public_encrypt(self.token, 4)
except Exception:
pass
with open(tmp_pub, 'r') as fp_:
payload['load']['pub'] = fp_.read()
os.remove(tmp_pub)
@ -218,12 +224,16 @@ class Auth(object):
'have been subverted, verify salt master\'s public '
'key')
return False
try:
if token and not self.decrypt_aes(token) == self.token:
log.error('The master failed to decrypt the random minion token')
return False
except Exception:
log.error('The master failed to decrypt the random minion token')
return False
return True
else:
open(m_pub_fn, 'w+').write(master_pub)
pub = RSA.load_pub_key(tmp_pub)
plaintext = pub.public_decrypt(token, 5)
os.remove(tmp_pub)
if plaintext == 'salty bacon':
return True
log.error('The salt master has failed verification for an unknown '
'reason, verify your salt keys')
@ -275,6 +285,8 @@ class Auth(object):
if not self.verify_master(payload['pub_key'], payload['token']):
log.critical(
'The Salt Master server\'s public key did not authenticate!\n'
'The master may need to be updated if it is a version of Salt '
'lower than 0.10.4, or\n'
'If you are confident that you are connecting to a valid Salt '
'Master, then remove the master public key and restart the '
'Salt Minion.\nThe master public key can be found '

View File

@ -729,7 +729,8 @@ class RemoteClient(Client):
master.
'''
load = {'cmd': '_ext_nodes',
'id': self.opts['id']}
'id': self.opts['id'],
'opts': self.opts}
try:
return self.auth.crypticle.loads(
self.sreq.send(

View File

@ -18,6 +18,8 @@ import socket
import sys
import re
import platform
import logging
import locale
# Extend the default list of supported distros. This will be used for the
# /etc/DISTRO-release checking that is part of platform.linux_distribution()
@ -25,12 +27,29 @@ from platform import _supported_dists
_supported_dists += ('arch', 'mageia', 'meego', 'vmware', 'bluewhite64',
'slamd64', 'enterprise', 'ovs', 'system')
import salt.log
import salt.utils
# Solve the Chicken and egg problem where grains need to run before any
# of the modules are loaded and are generally available for any usage.
import salt.modules.cmdmod
__salt__ = {'cmd.run': salt.modules.cmdmod._run_quiet}
__salt__ = {
'cmd.run': salt.modules.cmdmod._run_quiet,
'cmd.run_all': salt.modules.cmdmod._run_all_quiet
}
log = logging.getLogger(__name__)
has_wmi = False
if sys.platform.startswith('win'):
# attempt to import the python wmi module
# the Windows minion uses WMI for some of its grains
try:
import wmi
has_wmi = True
except ImportError:
log.exception("Unable to import Python wmi module, some core grains "
"will be missing")
def _windows_cpudata():
@ -119,10 +138,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
@ -150,18 +172,17 @@ def _memdata(osdata):
grains['mem_total'] = str(int(mem) / 1024 / 1024)
elif osdata['kernel'] == 'SunOS':
for line in __salt__['cmd.run']('/usr/sbin/prtconf 2>/dev/null').split('\n'):
comps = line.split(' ')
comps = line.split(' ')
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
grains['mem_total'] = int(comps[2].strip())
elif osdata['kernel'] == 'Windows' and has_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
@ -177,38 +198,69 @@ def _virtual(osdata):
lspci = salt.utils.which('lspci')
dmidecode = salt.utils.which('dmidecode')
if dmidecode:
output = __salt__['cmd.run']('dmidecode')
# Product Name: VirtualBox
if 'Vendor: QEMU' in output:
# FIXME: Make this detect between kvm or qemu
grains['virtual'] = 'kvm'
if 'Vendor: Bochs' in output:
grains['virtual'] = 'kvm'
elif 'VirtualBox' in output:
grains['virtual'] = 'VirtualBox'
# Product Name: VMware Virtual Platform
elif 'VMware' in output:
grains['virtual'] = 'VMware'
# Manufacturer: Microsoft Corporation
# Product Name: Virtual Machine
elif 'Manufacturer: Microsoft' in output and 'Virtual Machine' in output:
grains['virtual'] = 'VirtualPC'
# Manufacturer: Parallels Software International Inc.
elif 'Parallels Software' in output:
grains['virtual'] = 'Parallels'
# Fall back to lspci if dmidecode isn't available
elif lspci:
model = __salt__['cmd.run']('lspci').lower()
if 'vmware' in model:
grains['virtual'] = 'VMware'
# 00:04.0 System peripheral: InnoTek Systemberatung GmbH VirtualBox Guest Service
elif 'virtualbox' in model:
grains['virtual'] = 'VirtualBox'
elif 'qemu' in model:
grains['virtual'] = 'kvm'
elif 'virtio' in model:
grains['virtual'] = 'kvm'
for command in ('dmidecode', 'lspci'):
which = salt.utils.which(command)
if which is None:
continue
ret = __salt__['cmd.run_all'](which)
if ret['retcode'] > 0:
if salt.log.is_logging_configured():
log.warn(
'Although \'{0}\' was found in path, the current user '
'cannot execute it. Grains output might not be '
'accurate.'.format(command)
)
continue
output = ret['stdout']
if command == 'dmidecode':
# Product Name: VirtualBox
if 'Vendor: QEMU' in output:
# FIXME: Make this detect between kvm or qemu
grains['virtual'] = 'kvm'
if 'Vendor: Bochs' in output:
grains['virtual'] = 'kvm'
elif 'VirtualBox' in output:
grains['virtual'] = 'VirtualBox'
# Product Name: VMware Virtual Platform
elif 'VMware' in output:
grains['virtual'] = 'VMware'
# Manufacturer: Microsoft Corporation
# Product Name: Virtual Machine
elif 'Manufacturer: Microsoft' in output and 'Virtual Machine' in output:
grains['virtual'] = 'VirtualPC'
# Manufacturer: Parallels Software International Inc.
elif 'Parallels Software' in output:
grains['virtual'] = 'Parallels'
# Break out of the loop, lspci parsing is not necessary
break
elif command == 'lspci':
# dmidecode not available or the user does not have the necessary
# permissions
model = output.lower()
if 'vmware' in model:
grains['virtual'] = 'VMware'
# 00:04.0 System peripheral: InnoTek Systemberatung GmbH VirtualBox Guest Service
elif 'virtualbox' in model:
grains['virtual'] = 'VirtualBox'
elif 'qemu' in model:
grains['virtual'] = 'kvm'
elif 'virtio' in model:
grains['virtual'] = 'kvm'
# Break out of the loop so the next log message is not issued
break
else:
log.warn(
'Both \'dmidecode\' and \'lspci\' failed to execute, either '
'because they do not exist on the system of the user running '
'this instance does not have the necessary permissions to '
'execute them. Grains output might not be accurate.'
)
choices = ('Linux', 'OpenBSD', 'HP-UX')
isdir = os.path.isdir
if osdata['kernel'] in choices:
@ -268,7 +320,7 @@ def _virtual(osdata):
# Check if it's a "regular" zone. (i.e. Solaris 10/11 zone)
zonename = salt.utils.which('zonename')
if zonename:
zone = __salt__['cmd.run']('{0}'.format(zonename)).strip()
zone = __salt__['cmd.run']('{0}'.format(zonename)).strip()
if zone != "global":
grains['virtual'] = 'zone'
# Check if it's a branded zone (i.e. Solaris 8/9 zone)
@ -285,7 +337,7 @@ def _ps(osdata):
bsd_choices = ('FreeBSD', 'NetBSD', 'OpenBSD', 'MacOS')
if osdata['os'] in bsd_choices:
grains['ps'] = 'ps auxwww'
if osdata['os'] == 'Solaris':
elif osdata['os_family'] == 'Solaris':
grains['ps'] = '/usr/ucb/ps auxwww'
elif osdata['os'] == 'Windows':
grains['ps'] = 'tasklist.exe'
@ -305,30 +357,39 @@ def _windows_platform_data(osdata):
# productname
# biosversion
# osfullname
# inputlocale
# timezone
# windowsdomain
grains = {}
get_these_grains = {
'OS Manufacturer': 'osmanufacturer',
'System Manufacturer': 'manufacturer',
'System Model': 'productname',
'BIOS Version': 'biosversion',
'OS Name': 'osfullname',
'Input Locale': 'inputlocale',
'Time Zone': 'timezone',
'Domain': 'windowsdomain',
}
systeminfo = __salt__['cmd.run']('SYSTEMINFO')
for line in systeminfo.split('\n'):
comps = line.split(':', 1)
if not len(comps) > 1:
continue
item = comps[0].strip()
value = comps[1].strip()
if item in get_these_grains:
grains[get_these_grains[item]] = value
if not has_wmi:
return {}
wmi_c = wmi.WMI()
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394102%28v=vs.85%29.aspx
systeminfo = wmi_c.Win32_ComputerSystem()[0]
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394239%28v=vs.85%29.aspx
osinfo = wmi_c.Win32_OperatingSystem()[0]
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394077(v=vs.85).aspx
biosinfo = wmi_c.Win32_BIOS()[0]
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394498(v=vs.85).aspx
timeinfo = wmi_c.Win32_TimeZone()[0]
# the name of the OS comes with a bunch of other data about the install
# location. For example:
# 'Microsoft Windows Server 2008 R2 Standard |C:\\Windows|\\Device\\Harddisk0\\Partition2'
(osfullname, _) = osinfo.Name.split('|', 1)
osfullname = osfullname.strip()
grains = {
'osmanufacturer': osinfo.Manufacturer,
'manufacturer': systeminfo.Manufacturer,
'productname': systeminfo.Model,
# bios name had a bunch of whitespace appended to it in my testing
# 'PhoenixBIOS 4.0 Release 6.0 '
'biosversion': biosinfo.Name.strip(),
'osfullname': osfullname,
'timezone': timeinfo.Description,
'windowsdomain': systeminfo.Domain,
}
return grains
@ -338,12 +399,14 @@ def id_():
'''
return {'id': __opts__['id']}
# This maps (at most) the first ten characters (no spaces, lowercased) of
# This maps (at most) the first ten characters (no spaces, lowercased) of
# 'osfullname' to the 'os' grain that Salt traditionally uses.
_os_name_map = {
'redhatente': 'RedHat',
'debian': 'Debian',
'arch': 'Arch',
'amazonlinu': 'Amazon',
'centoslinu': 'CentOS',
}
# Map the 'os' grain to the 'os_family' grain
@ -355,17 +418,19 @@ _os_family_map = {
'Scientific': 'RedHat',
'Amazon': 'RedHat',
'CloudLinux': 'RedHat',
'OVS': 'RedHat',
'OEL': 'RedHat',
'Mandrake': 'Mandriva',
'ESXi': 'VMWare',
'VMWareESX': 'VMWare',
'Bluewhite64': 'Bluewhite',
'Slamd64': 'Slackware',
'OVS': 'Oracle',
'OEL': 'Oracle',
'SLES': 'Suse',
'SLED': 'Suse',
'openSUSE': 'Suse',
'SUSE': 'Suse'
'SUSE': 'Suse',
'Solaris': 'Solaris',
'SmartOS': 'Solaris',
}
@ -374,10 +439,18 @@ def os_data():
Return grains pertaining to the operating system
'''
grains = {}
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
# ('Linux', 'FIRE66VMA01', '2.6.32-38-server', '#83-Ubuntu SMP Wed Jan 4 11:26:59 UTC 2012', 'x86_64', '')
# ('Linux', 'MINIONNAME', '2.6.32-38-server', '#83-Ubuntu SMP Wed Jan 4 11:26:59 UTC 2012', 'x86_64', '')
(grains['kernel'], grains['host'],
grains['kernelrelease'], version, grains['cpuarch'], _) = platform.uname()
if grains['kernel'] == 'Windows':
@ -432,6 +505,11 @@ def os_data():
grains.update(_linux_cpudata())
elif grains['kernel'] == 'SunOS':
grains['os'] = 'Solaris'
if os.path.isfile('/etc/release'):
with open('/etc/release', 'r') as fp_:
rel_data = fp_.read()
if 'SmartOS' in rel_data:
grains['os'] = 'SmartOS'
grains.update(_sunos_cpudata(grains))
elif grains['kernel'] == 'VMkernel':
grains['os'] = 'ESXi'

View File

@ -22,7 +22,13 @@ salt_base_path = os.path.dirname(salt.__file__)
loaded_base_name = 'salt.loaded'
def _create_loader(opts, ext_type, tag, ext_dirs=True, ext_type_dirs=None):
def _create_loader(
opts,
ext_type,
tag,
ext_dirs=True,
ext_type_dirs=None,
base_path=None):
'''
Creates Loader instance
@ -31,8 +37,11 @@ def _create_loader(opts, ext_type, tag, ext_dirs=True, ext_type_dirs=None):
extension types,
base types.
'''
if base_path:
sys_types = os.path.join(base_path, ext_type)
else:
sys_types = os.path.join(salt_base_path, ext_type)
ext_types = os.path.join(opts['extension_modules'], ext_type)
sys_types = os.path.join(salt_base_path, ext_type)
ext_type_types = []
if ext_dirs:
@ -94,6 +103,22 @@ def pillars(opts, functions):
return load.filter_func('ext_pillar', pack)
def tops(opts):
'''
Returns the returner modules
'''
load = _create_loader(opts, 'tops', 'top')
return load.filter_func('top')
def auth(opts):
'''
Returns the returner modules
'''
load = _create_loader(opts, 'auth', 'auth')
return load.gen_functions()
def states(opts, functions):
'''
Returns the state modules
@ -132,7 +157,7 @@ def grains(opts):
salt.config.load_config(
pre_opts, opts['conf_file'], 'SALT_MINION_CONFIG'
)
default_include = pre_opts.get('default_include', [])
default_include = pre_opts.get('default_include', opts['default_include'])
include = pre_opts.get('include', [])
pre_opts = salt.config.include_config(
default_include, pre_opts, opts['conf_file'], verbose=False
@ -509,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

@ -52,6 +52,11 @@ def is_logfile_configured():
return __LOGFILE_CONFIGURED
def is_logging_configured():
global __CONSOLE_CONFIGURED, __LOGFILE_CONFIGURED
return __CONSOLE_CONFIGURED or __LOGFILE_CONFIGURED
class Logging(LoggingLoggerClass):
def __new__(cls, logger_name, *args, **kwargs):
global MAX_LOGGER_MODNAME_LENGTH

View File

@ -39,8 +39,11 @@ import salt.payload
import salt.pillar
import salt.state
import salt.runner
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
@ -398,7 +401,6 @@ class ReqServer(object):
self.publisher = Publisher(self.opts)
self.publisher.start()
def start_event_publisher(self):
'''
Start the salt publisher interface
@ -524,6 +526,9 @@ 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
self.local = salt.client.LocalClient(self.opts['conf_file'])
@ -533,6 +538,8 @@ class AESFuncs(object):
'''
fnd = {'path': '',
'rel': ''}
if os.path.isabs(path):
return fnd
if env not in self.opts['file_roots']:
return fnd
for root in self.opts['file_roots'][env]:
@ -582,33 +589,53 @@ class AESFuncs(object):
if not 'id' in load:
log.error('Received call for external nodes without an id')
return {}
if not self.opts['external_nodes']:
return {}
if not salt.utils.which(self.opts['external_nodes']):
log.error(('Specified external nodes controller {0} is not'
' available, please verify that it is installed'
'').format(self.opts['external_nodes']))
return {}
cmd = '{0} {1}'.format(self.opts['external_nodes'], load['id'])
ndata = yaml.safe_load(
subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE
).communicate()[0])
ret = {}
if 'environment' in ndata:
env = ndata['environment']
else:
env = 'base'
if 'classes' in ndata:
if isinstance(ndata['classes'], dict):
ret[env] = list(ndata['classes'])
elif isinstance(ndata['classes'], list):
ret[env] = ndata['classes']
# The old ext_nodes method is set to be deprecated in 0.10.4
# and should be removed within 3-5 releases in favor of the
# "master_tops" system
if self.opts['external_nodes']:
if not salt.utils.which(self.opts['external_nodes']):
log.error(('Specified external nodes controller {0} is not'
' available, please verify that it is installed'
'').format(self.opts['external_nodes']))
return {}
cmd = '{0} {1}'.format(self.opts['external_nodes'], load['id'])
ndata = yaml.safe_load(
subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE
).communicate()[0])
if 'environment' in ndata:
env = ndata['environment']
else:
return ret
env = 'base'
if 'classes' in ndata:
if isinstance(ndata['classes'], dict):
ret[env] = list(ndata['classes'])
elif isinstance(ndata['classes'], list):
ret[env] = ndata['classes']
else:
return ret
# Evaluate all configured master_tops interfaces
opts = {}
grains = {}
if 'opts' in load:
opts = load['opts']
if 'grains' in load['opts']:
grains = load['opts']['grains']
for fun in self.tops:
try:
ret.update(self.tops[fun](opts=opts, grains=grains))
except Exception as exc:
log.error(
('Top function {0} failed with error {1} for minion '
'{2}').format(fun, exc, load['id'])
)
# If anything happens in the top generation, log it and move on
pass
return ret
def _serve_file(self, load):
@ -742,7 +769,7 @@ class AESFuncs(object):
'''
if 'id' not in load or 'tag' not in load or 'data' not in load:
return False
tag = '{0}_{1}'.format(load['tag'], load['id'])
tag = load['tag']
return self.event.fire_event(load, tag)
def _return(self, load):
@ -784,11 +811,24 @@ class AESFuncs(object):
' attack').format(load['id'])
)
return False
self.serial.dump(load['return'],
open(os.path.join(hn_dir, 'return.p'), 'w+'))
self.serial.dump(
load['return'],
# Use atomic open here to avoid the file being read before it's
# completely written to. Refs #1935
salt.utils.atomicfile.atomic_open(
os.path.join(hn_dir, 'return.p'), 'w+'
)
)
if 'out' in load:
self.serial.dump(load['out'],
open(os.path.join(hn_dir, 'out.p'), 'w+'))
self.serial.dump(
load['out'],
# Use atomic open here to avoid the file being read before
# it's completely written to. Refs #1935
salt.utils.atomicfile.atomic_open(
os.path.join(hn_dir, 'out.p'), 'w+'
)
)
def _syndic_return(self, load):
'''
@ -873,7 +913,6 @@ class AESFuncs(object):
runner = salt.runner.Runner(opts)
return runner.run()
def minion_publish(self, clear_load):
'''
Publish a command initiated from a minion, this method executes minion
@ -915,13 +954,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(',')
@ -929,15 +967,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
@ -1001,7 +1035,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
),
@ -1010,7 +1044,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
),
@ -1037,6 +1071,27 @@ class AESFuncs(object):
# (we don't care about the return value, so why encrypt it?)
if func == '_return':
return ret
if func == '_pillar' and 'id' in load:
if not load.get('ver') == '2' and self.opts['pillar_version'] == 1:
# Authorized to return old pillar proto
return self.crypticle.dumps(ret)
# encrypt with a specific aes key
pubfn = os.path.join(self.opts['pki_dir'],
'minions',
load['id'])
key = salt.crypt.Crypticle.generate_key_string()
pcrypt = salt.crypt.Crypticle(
self.opts,
key)
try:
pub = RSA.load_pub_key(pubfn)
except RSA.RSAError, e:
return self.crypticle.dumps({})
pret = {}
pret['key'] = pub.public_encrypt(key, 4)
pret['pillar'] = pcrypt.dumps(ret)
return pret
# AES Encrypt the return
return self.crypticle.dumps(ret)
@ -1060,6 +1115,10 @@ 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)
def _send_cluster(self):
'''
@ -1214,6 +1273,16 @@ class ClearFuncs(object):
# open mode is turned on, nuts to checks and overwrite whatever
# is there
pass
elif os.path.isfile(pubfn_rejected):
# The key has been rejected, don't place it in pending
log.info('Public key rejected for {id}'.format(**load))
ret = {'enc': 'clear',
'load': {'ret': False}}
eload = {'result': False,
'id': load['id'],
'pub': load['pub']}
self.event.fire_event(eload, 'auth')
return ret
elif os.path.isfile(pubfn):
# The key has been accepted check it
if not open(pubfn, 'r').read() == load['pub']:
@ -1229,16 +1298,6 @@ class ClearFuncs(object):
'pub': load['pub']}
self.event.fire_event(eload, 'auth')
return ret
elif os.path.isfile(pubfn_rejected):
# The key has been rejected, don't place it in pending
log.info('Public key rejected for {id}'.format(**load))
ret = {'enc': 'clear',
'load': {'ret': False}}
eload = {'result': False,
'id': load['id'],
'pub': load['pub']}
self.event.fire_event(eload, 'auth')
return ret
elif not os.path.isfile(pubfn_pend)\
and not self._check_autosign(load['id']):
# This is a new key, stick it in pre
@ -1331,6 +1390,15 @@ class ClearFuncs(object):
'token': self.master_key.token,
'publish_port': self.opts['publish_port'],
}
if 'token' in load:
try:
mtoken = self.master_key.key.private_decrypt(load['token'], 4)
ret['token'] = pub.public_encrypt(mtoken, 4)
except Exception:
# Token failed to decrypt, send back the salty bacon to
# support older minions
pass
ret['aes'] = pub.public_encrypt(self.opts['aes'], 4)
eload = {'result': True,
'act': 'accept',
@ -1339,13 +1407,64 @@ 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
by the LocalClient.
'''
extra = clear_load.get('kwargs', {})
# Check for external auth calls
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 ''
name = self.loadauth.load_name(extra)
if not name in self.opts['external_auth'][extra['eauth']]:
return ''
if not self.loadauth.time_auth(extra):
return ''
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':
return ''
# Verify that the caller has root on master
if 'user' in clear_load:
elif 'user' in clear_load:
if clear_load['user'].startswith('sudo_'):
if not clear_load.pop('key') == self.key.get(getpass.getuser(), ''):
return ''
@ -1361,14 +1480,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:
@ -1433,5 +1553,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

@ -99,6 +99,10 @@ class SMinion(object):
def __init__(self, opts):
# Generate all of the minion side components
self.opts = opts
# Late setup the of the opts grains, so we can log from the grains
# module
opts['grains'] = salt.loader.grains(opts)
self.opts = opts
self.gen_modules()
def gen_modules(self):
@ -128,6 +132,9 @@ class Minion(object):
'''
Pass in the options dict
'''
# Late setup the of the opts grains, so we can log from the grains
# module
opts['grains'] = salt.loader.grains(opts)
self.opts = opts
self.serial = salt.payload.Serial(self.opts)
self.mod_opts = self.__prep_mod_opts()
@ -163,6 +170,20 @@ class Minion(object):
returners = salt.loader.returners(self.opts, functions)
return functions, returners
def _fire_master(self, data, tag):
'''
Fire an event on the master
'''
load = {'id': self.opts['id'],
'tag': tag,
'data': data,
'cmd': '_minion_event'}
sreq = salt.payload.SREQ(self.opts['master_uri'])
try:
sreq.send('aes', self.crypticle.dumps(load))
except:
pass
def _handle_payload(self, payload):
'''
Takes a payload from the master publisher and does whatever the
@ -570,6 +591,14 @@ class Minion(object):
socket.connect(self.master_pub)
poller.register(socket, zmq.POLLIN)
epoller.register(epull_sock, zmq.POLLIN)
# Send an event to the master that the minion is live
self._fire_master(
'Minion {0} started at {1}'.format(
self.opts['id'],
time.asctime()
),
'minion_start'
)
# Make sure to gracefully handle SIGUSR1
enable_sigusr1_handler()
@ -724,10 +753,9 @@ class Matcher(object):
'''
def __init__(self, opts, functions=None):
self.opts = opts
if not functions:
if functions is None:
functions = salt.loader.minion_mods(self.opts)
else:
self.functions = functions
self.functions = functions
def confirm_top(self, match, data, nodegroups=None):
'''
@ -850,6 +878,25 @@ class Matcher(object):
comps[1].lower(),
))
def ipcidr_match(self, tgt):
'''
Matches based on ip address or CIDR notation
'''
num_parts = len(tgt.split('/'))
if num_parts > 2:
return False
elif num_parts == 2:
return self.functions['network.in_subnet'](tgt)
else:
import socket
try:
socket.inet_aton(tgt)
except socket.error:
# Not a valid IPv4 address
return False
else:
return tgt in self.functions['network.ip_addrs']()
def compound_match(self, tgt):
'''
Runs the compound target check
@ -862,12 +909,13 @@ class Matcher(object):
'X': 'exsel',
'I': 'pillar',
'L': 'list',
'S': 'ipcidr',
'E': 'pcre'}
results = []
opers = ['and', 'or', 'not']
for match in tgt.split():
# Try to match tokens from the compound target, first by using
# the 'G, X, I, L, E' matcher types, then by hostname glob.
# the 'G, X, I, L, S, E' matcher types, then by hostname glob.
if '@' in match and match[1] == '@':
comps = match.split('@')
matcher = ref.get(comps[0])
@ -895,5 +943,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
@ -283,7 +282,7 @@ def list_pkgs(regex_string=""):
for line in out.split('\n'):
cols = line.split()
if len(cols) and 'install' in cols[0] and 'installed' in cols[2]:
if len(cols) and ('install' in cols[0] or 'hold' in cols[0]) and 'installed' in cols[2]:
ret[cols[3]] = cols[4]
# If ret is empty at this point, check to see if the package is virtual.

View File

@ -11,7 +11,7 @@ REQUIREMENT 2:
Add the following values in /etc/salt/minion for the
CA module to function properly::
ca.cert_base_path: '/etc/pki/koji'
ca.cert_base_path: '/etc/pki'
'''
# Import Python libs
@ -167,7 +167,7 @@ def create_ca(
organization, default is "Salt Stack"
OU
organizational unit, default is None
email
emailAddress
email address for the CA owner, default is 'xyz@pdq.net'
Writes out a CA certificate based upon defined config values. If the file
@ -205,7 +205,7 @@ def create_ca(
ca.get_subject().emailAddress = emailAddress
ca.gmtime_adj_notBefore(0)
ca.gmtime_adj_notAfter(days * 24 * 60 * 60)
ca.gmtime_adj_notAfter(int(days) * 24 * 60 * 60)
ca.set_issuer(ca.get_subject())
ca.set_pubkey(key)
@ -441,7 +441,7 @@ def create_ca_signed_cert(ca_name, CN, days=365):
cert = OpenSSL.crypto.X509()
cert.set_subject(req.get_subject())
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(days * 24 * 60 * 60)
cert.gmtime_adj_notAfter(int(days) * 24 * 60 * 60)
cert.set_serial_number(_new_serial(ca_name, CN))
cert.set_issuer(ca_cert.get_subject())
cert.set_pubkey(req.get_pubkey())

View File

@ -12,6 +12,7 @@ import shutil
import subprocess
import tempfile
import sys
from functools import partial
# Import Salt libs
import salt.utils
@ -35,6 +36,7 @@ __outputter__ = {
DEFAULT_SHELL = shell_grain()['shell']
def __virtual__():
'''
Overwriting the cmd python module makes debugging modules
@ -43,6 +45,48 @@ def __virtual__():
return 'cmd'
def _chugid(runas):
uinfo = pwd.getpwnam(runas)
if os.getuid() == uinfo.pw_uid and os.getgid() == uinfo.pw_gid:
# No need to change user or group
return
# No logging can happen on this function
#
# 08:46:32,161 [salt.loaded.int.module.cmdmod:276 ][DEBUG ] stderr: Traceback (most recent call last):
# File "/usr/lib/python2.7/logging/__init__.py", line 870, in emit
# self.flush()
# File "/usr/lib/python2.7/logging/__init__.py", line 832, in flush
# self.stream.flush()
# IOError: [Errno 9] Bad file descriptor
# Logged from file cmdmod.py, line 59
# 08:46:17,481 [salt.loaded.int.module.cmdmod:59 ][DEBUG ] Switching user 0 -> 1008 and group 0 -> 1012 if needed
#
# apparently because we closed fd's on Popen, though if not closed, output
# would also go to it's stderr
if os.getgid() != uinfo.pw_gid:
try:
os.setgid(uinfo.pw_gid)
except OSError, err:
raise CommandExecutionError(
'Failed to change from gid {0} to {1}. Error: {2}'.format(
os.getgid(), uinfo.pw_gid, err
)
)
if os.getuid() != uinfo.pw_uid:
try:
os.setuid(uinfo.pw_uid)
except OSError, err:
raise CommandExecutionError(
'Failed to change from uid {0} to {1}. Error: {2}'.format(
os.getuid(), uinfo.pw_uid, err
)
)
def _run(cmd,
cwd=None,
stdout=subprocess.PIPE,
@ -87,30 +131,19 @@ def _run(cmd,
if runas:
# Save the original command before munging it
orig_cmd = cmd
try:
pwd.getpwnam(runas)
except KeyError:
msg = 'User \'{0}\' is not available'.format(runas)
raise CommandExecutionError(msg)
cmd_prefix = 'su -s {0}'.format(shell)
# Load the 'nix environment
if with_env:
cmd_prefix += ' -'
cmd = 'cd {0} && {1}'.format(cwd, cmd)
cmd_prefix += ' {0} -c'.format(runas)
cmd = '{0} {1}'.format(cmd_prefix, pipes.quote(cmd))
if not quiet:
# Put the most common case first
if not runas:
log.info('Executing command {0} in directory {1}'.format(cmd, cwd))
else:
log.info('Executing command {0} as user {1} in directory {2}'.format(
orig_cmd, runas, cwd))
log.info(
'Executing command {0!r} {1}in directory {2!r}'.format(
cmd, 'as user {0!r} '.format(runas) if runas else '', cwd
)
)
run_env = os.environ
run_env.update(env)
@ -118,29 +151,25 @@ def _run(cmd,
'shell': True,
'env': run_env,
'stdout': stdout,
'stderr':stderr}
'stderr': stderr}
if runas:
kwargs['preexec_fn'] = partial(_chugid, runas)
if not sys.platform.startswith('win'):
# close_fds is not supported on Windows platforms if you redirect
# stdin/stdout/stderr
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:
@ -164,6 +193,14 @@ def _run_quiet(cmd, cwd=None, runas=None, shell=DEFAULT_SHELL, env=()):
quiet=True, shell=shell, env=env)['stdout']
def _run_all_quiet(cmd, cwd=None, runas=None, shell=DEFAULT_SHELL, env=()):
'''
Helper for running commands quietly for minion startup.
Returns a dict of return data
'''
return _run(cmd, runas=runas, cwd=cwd, shell=shell, env=env, quiet=True)
def run(cmd, cwd=None, runas=None, shell=DEFAULT_SHELL, env=()):
'''
Execute the passed command and return the output as a string
@ -213,6 +250,7 @@ def run_all(cmd, cwd=None, runas=None, shell=DEFAULT_SHELL, env=()):
salt '*' cmd.run_all "ls -l | awk '/foo/{print $2}'"
'''
ret = _run(cmd, runas=runas, cwd=cwd, shell=shell, env=env)
if ret['retcode'] != 0:
rcode = ret['retcode']
msg = 'Command \'{0}\' failed with return code: {1}'

32
salt/modules/config.py Normal file
View File

@ -0,0 +1,32 @@
'''
Return config information
'''
def backup_mode(backup=''):
'''
Return the backup mode
'''
if backup:
return backup
if 'backup_mode' in __opts__:
return __opts__['backup_mode']
if 'master.backup_mode' in __pillar__:
return __pillar__['master.backup_mode']
id_conf = 'master.{0}.backup_mode'.format(__grains__['id'])
if id_conf in __pillar__:
return __pillar__[id_conf]
def manage_mode(mode):
'''
Return a mode value, normalized to a string
'''
if mode:
mode = str(mode).lstrip('0')
if not mode:
return '0'
else:
return mode
return mode

Some files were not shown because too many files have changed in this diff Show More