mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge branch 'develop' of github.com:saltstack/salt into develop
This commit is contained in:
commit
d6d8e22b68
52 changed files with 2186 additions and 477 deletions
|
@ -13,8 +13,8 @@
|
|||
#publish_port: 4505
|
||||
|
||||
# Refresh the publisher connections when sending out commands, this is a fix
|
||||
# for zeromq losing some minion connections. Default: True
|
||||
#pub_refresh: True
|
||||
# for zeromq losing some minion connections. Default: False
|
||||
#pub_refresh: False
|
||||
|
||||
# The user to run the salt-master as. Salt will update all permissions to
|
||||
# allow the specified user to run the master. If the modified files cause
|
||||
|
@ -256,6 +256,10 @@
|
|||
# - hiera: /etc/hiera.yaml
|
||||
# - cmd_yaml: cat /etc/salt/yaml
|
||||
#
|
||||
# The pillar_opts option adds the master configuration file data to a dict in
|
||||
# the pillar called "master". This is used to set simple configurations in the
|
||||
# master config file that can then be used on minions.
|
||||
#pillar_opts: True
|
||||
|
||||
##### Syndic settings #####
|
||||
##########################################
|
||||
|
|
|
@ -162,12 +162,6 @@
|
|||
# failure detected in the state execution, defaults to False
|
||||
#failhard: False
|
||||
#
|
||||
# state_verbose allows for the data returned from the minion to be more
|
||||
# verbose. Normally only states that fail or states that have changes are
|
||||
# returned, but setting state_verbose to True will return all states that
|
||||
# were checked
|
||||
#state_verbose: False
|
||||
#
|
||||
# autoload_dynamic_modules Turns on automatic loading of modules found in the
|
||||
# environments on the master. This is turned on by default, to turn of
|
||||
# autoloading modules when states run set this value to False
|
||||
|
|
2
debian/rules
vendored
2
debian/rules
vendored
|
@ -4,8 +4,6 @@
|
|||
dh $@ #--with python2
|
||||
dh_override_auto_build:
|
||||
python setup.py build #--install-layout=deb build
|
||||
override_dh_installinit:
|
||||
dh_installinit --no-start
|
||||
#get-orig-source:
|
||||
# git clone https://github.com/saltstack/salt.git
|
||||
# mv salt salt-0.9.8
|
||||
|
|
41
debian/salt-common.postrm
vendored
Normal file
41
debian/salt-common.postrm
vendored
Normal 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
41
debian/salt-master.postrm
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Purge config files, logs, and directories created after package install.
|
||||
# Note that user-specified alternate locations for these are not affected.
|
||||
|
||||
clean_common() {
|
||||
# remove shared job cache and other runtime directories
|
||||
rm -rf /var/cache/salt /var/run/salt /etc/salt /var/log/salt 2> /dev/null
|
||||
}
|
||||
|
||||
clean_conf() {
|
||||
# remove config and log file for master, minion, or syndic
|
||||
rm -f /etc/salt/$1 /var/log/salt/$1 2> /dev/null
|
||||
# XXX add more specific files to purge here XXX #
|
||||
}
|
||||
|
||||
purgefiles() {
|
||||
case "$pkg" in
|
||||
master|minion|syndic)
|
||||
clean_conf $pkg ;;
|
||||
common)
|
||||
clean_common ;;
|
||||
*)
|
||||
echo "$0 unknown package \`$1'" 1>&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
pkg=`echo $0 | cut -f1 -d. | cut -f2 -d-`
|
||||
|
||||
case "$1" in
|
||||
remove)
|
||||
;;
|
||||
purge)
|
||||
purgefiles ;;
|
||||
upgrade|failed-upgrade|disappear|abort-install|abort-upgrade)
|
||||
;;
|
||||
*)
|
||||
echo "$0 unknown action \`$1'" 1>&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
|
||||
exit 0
|
41
debian/salt-minion.postrm
vendored
Normal file
41
debian/salt-minion.postrm
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Purge config files, logs, and directories created after package install.
|
||||
# Note that user-specified alternate locations for these are not affected.
|
||||
|
||||
clean_common() {
|
||||
# remove shared job cache and other runtime directories
|
||||
rm -rf /var/cache/salt /var/run/salt /etc/salt /var/log/salt 2> /dev/null
|
||||
}
|
||||
|
||||
clean_conf() {
|
||||
# remove config and log file for master, minion, or syndic
|
||||
rm -f /etc/salt/$1 /var/log/salt/$1 2> /dev/null
|
||||
# XXX add more specific files to purge here XXX #
|
||||
}
|
||||
|
||||
purgefiles() {
|
||||
case "$pkg" in
|
||||
master|minion|syndic)
|
||||
clean_conf $pkg ;;
|
||||
common)
|
||||
clean_common ;;
|
||||
*)
|
||||
echo "$0 unknown package \`$1'" 1>&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
pkg=`echo $0 | cut -f1 -d. | cut -f2 -d-`
|
||||
|
||||
case "$1" in
|
||||
remove)
|
||||
;;
|
||||
purge)
|
||||
purgefiles ;;
|
||||
upgrade|failed-upgrade|disappear|abort-install|abort-upgrade)
|
||||
;;
|
||||
*)
|
||||
echo "$0 unknown action \`$1'" 1>&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
|
||||
exit 0
|
41
debian/salt-syndic.postrm
vendored
Normal file
41
debian/salt-syndic.postrm
vendored
Normal 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
|
|
@ -145,7 +145,7 @@ html_context = {
|
|||
'github_downloads': 'https://github.com/saltstack/salt/downloads',
|
||||
}
|
||||
|
||||
html_use_index = False
|
||||
html_use_index = True
|
||||
html_last_updated_fmt = '%b %d, %Y'
|
||||
html_show_sourcelink = False
|
||||
html_show_sphinx = True
|
||||
|
|
|
@ -14,6 +14,7 @@ Full Table of Contents
|
|||
topics/tutorials/modules
|
||||
topics/tutorials/starting_states
|
||||
topics/tutorials/states*
|
||||
topics/eauth*
|
||||
topics/tutorials/firewall
|
||||
topics/tutorials/bootstrap_ec2
|
||||
topics/tutorials/esky
|
||||
|
|
|
@ -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``
|
||||
|
|
|
@ -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
|
||||
-------------------------
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -339,7 +339,7 @@ components.
|
|||
|
||||
<ID Declaration>:
|
||||
<State Declaration>:
|
||||
- <Function>:
|
||||
- <Function>
|
||||
- <Function Arg>
|
||||
- <Function Arg>
|
||||
- <Function Arg>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
51
doc/topics/eauth/access_control.rst
Normal file
51
doc/topics/eauth/access_control.rst
Normal file
|
@ -0,0 +1,51 @@
|
|||
=====================
|
||||
Access Control System
|
||||
=====================
|
||||
|
||||
.. versionadded:: 0.10.4
|
||||
|
||||
Salt maintains a standard system used to open granular control to non
|
||||
administrative users to execute Salt commands. The access control system
|
||||
has been applied to all systems used to configure access to non administrative
|
||||
control interfaces in Salt.These interfaces include, the ``peer`` system, the
|
||||
``external auth`` system and the ``client acl`` system.
|
||||
|
||||
The access control system mandated a standard configuration syntax used in
|
||||
all of the three aforementioned systems. While this adds functionality to the
|
||||
configuration in 0.10.4, it does not negate the old configuration.
|
||||
|
||||
Now specific functions can be opened up to specific minions from specific users
|
||||
in the case of external auth and client acls, and for specific minions in the
|
||||
case of the peer system.
|
||||
|
||||
The access controls are manifest using matchers in these configurations:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
client_acl:
|
||||
fred:
|
||||
- web\*:
|
||||
- pkg.list_pkgs
|
||||
- test.*
|
||||
- apache.*
|
||||
|
||||
In the above example, fred is able to send commands only to minions which match
|
||||
the specifieed glb target. This can be expanded to include other functions for
|
||||
other minions based on standard targets.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
external_auth:
|
||||
pam:
|
||||
dave:
|
||||
- mongo\*:
|
||||
- network.*
|
||||
- log\*:
|
||||
- network.*
|
||||
- pkg.*
|
||||
- 'G@os:RedHat':
|
||||
- kmod.*
|
||||
- test.ping
|
||||
|
||||
The above allows for all minions to be hit by test.ping by dave, and adds a
|
||||
few functions for hitting other minions.
|
52
doc/topics/eauth/index.rst
Normal file
52
doc/topics/eauth/index.rst
Normal 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.
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -42,6 +42,7 @@ class Master(parsers.MasterOptionParser):
|
|||
os.path.join(self.config['cachedir'], 'jobs'),
|
||||
os.path.dirname(self.config['log_file']),
|
||||
self.config['sock_dir'],
|
||||
self.config['token_dir'],
|
||||
],
|
||||
self.config['user'],
|
||||
permissive=self.config['permissive_pki_access'],
|
||||
|
|
|
@ -13,10 +13,11 @@ so that any external authentication system can be used inside of Salt
|
|||
# 6. Interface to verify tokens
|
||||
|
||||
# Import Python libs
|
||||
import os
|
||||
import hashlib
|
||||
import time
|
||||
import logging
|
||||
import random
|
||||
import inspect
|
||||
import getpass
|
||||
|
||||
# Import Salt libs
|
||||
|
@ -53,9 +54,12 @@ class LoadAuth(object):
|
|||
except IndexError:
|
||||
return ''
|
||||
|
||||
def auth_call(self, load):
|
||||
def __auth_call(self, load):
|
||||
'''
|
||||
Return the token and set the cache data for use
|
||||
|
||||
Do not call this directly! Use the time_auth method to overcome timing
|
||||
attacks
|
||||
'''
|
||||
if not 'eauth' in load:
|
||||
return False
|
||||
|
@ -79,7 +83,7 @@ class LoadAuth(object):
|
|||
Make sure that all failures happen in the same amount of time
|
||||
'''
|
||||
start = time.time()
|
||||
ret = self.auth_call(load)
|
||||
ret = self.__auth_call(load)
|
||||
if ret:
|
||||
return ret
|
||||
f_time = time.time() - start
|
||||
|
@ -98,33 +102,48 @@ class LoadAuth(object):
|
|||
'''
|
||||
Run time_auth and create a token. Return False or the token
|
||||
'''
|
||||
ret = time_auth(load)
|
||||
ret = self.time_auth(load)
|
||||
if ret is False:
|
||||
return ret
|
||||
tok = hashlib.md5(os.urandom(512)).hexdigest()
|
||||
t_path = os.path.join(opts['token_dir'], tok)
|
||||
fstr = '{0}.auth'.format(load['eauth'])
|
||||
tok = str(hashlib.md5(os.urandom(512)).hexdigest())
|
||||
t_path = os.path.join(self.opts['token_dir'], tok)
|
||||
while os.path.isfile(t_path):
|
||||
tok = hashlib.md5(os.urandom(512)).hexdigest()
|
||||
t_path = os.path.join(opts['token_dir'], tok)
|
||||
t_path = os.path.join(self.opts['token_dir'], tok)
|
||||
fcall = salt.utils.format_call(self.auth[fstr], load)
|
||||
tdata = {'start': time.time(),
|
||||
'expire': time.time() + self.opts['token_expire'],
|
||||
'name': fcall['args'][0],}
|
||||
'name': fcall['args'][0],
|
||||
'eauth': load['eauth'],
|
||||
'token': tok}
|
||||
with open(t_path, 'w+') as fp_:
|
||||
fp_.write(self.serial.dumps(tdata))
|
||||
return tok
|
||||
return tdata
|
||||
|
||||
def get_tok(self, tok):
|
||||
'''
|
||||
Return the name associate with the token, or False if the token is
|
||||
not valid
|
||||
'''
|
||||
t_path = os.path.join(opts['token_dir'], tok)
|
||||
t_path = os.path.join(self.opts['token_dir'], tok)
|
||||
if not os.path.isfile:
|
||||
return False
|
||||
return {}
|
||||
with open(t_path, 'r') as fp_:
|
||||
return self.serial.loads(fp_.read())
|
||||
return False
|
||||
tdata = self.serial.loads(fp_.read())
|
||||
rm_tok = False
|
||||
if not 'expire' in tdata:
|
||||
# invalid token, delete it!
|
||||
rm_tok = True
|
||||
if tdata.get('expire', '0') < time.time():
|
||||
rm_tok = True
|
||||
if rm_tok:
|
||||
try:
|
||||
os.remove(t_path)
|
||||
return {}
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
return tdata
|
||||
|
||||
|
||||
class Resolver(object):
|
||||
|
@ -153,11 +172,34 @@ class Resolver(object):
|
|||
|
||||
args = salt.utils.arg_lookup(self.auth[fstr])
|
||||
for arg in args['args']:
|
||||
if arg.startswith('pass'):
|
||||
if arg in self.opts:
|
||||
ret[arg] = self.opts[arg]
|
||||
elif arg.startswith('pass'):
|
||||
ret[arg] = getpass.getpass('{0}: '.format(arg))
|
||||
else:
|
||||
ret[arg] = raw_input('{0}: '.format(arg))
|
||||
for kwarg, default in args['kwargs'].items():
|
||||
ret[kwarg] = raw_input('{0} [{1}]: '.format(kwarg, default))
|
||||
if kwarg in self.opts:
|
||||
ret['kwarg'] = self.opts[kwarg]
|
||||
else:
|
||||
ret[kwarg] = raw_input('{0} [{1}]: '.format(kwarg, default))
|
||||
|
||||
return ret
|
||||
|
||||
def token_cli(self, eauth, load):
|
||||
'''
|
||||
Create the token from the cli and request the correct data to
|
||||
authenticate via the passed authentication mechanism
|
||||
'''
|
||||
load['cmd'] = 'mk_token'
|
||||
load['eauth'] = eauth
|
||||
sreq = salt.payload.SREQ(
|
||||
'tcp://{0[interface]}:{0[ret_port]}'.format(self.opts),
|
||||
)
|
||||
tdata = sreq.send('clear', load)
|
||||
try:
|
||||
with open(self.opts['token_file'], 'w+') as fp_:
|
||||
fp_.write(tdata['token'])
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
return tdata
|
||||
|
|
|
@ -58,6 +58,9 @@ class SaltCMD(parsers.SaltCMDOptionParser):
|
|||
'arg': self.config['arg'],
|
||||
'timeout': self.options.timeout}
|
||||
|
||||
if 'token' in self.config:
|
||||
kwargs['token'] = self.config['token']
|
||||
|
||||
if self.selected_target_option:
|
||||
kwargs['expr_form'] = self.selected_target_option
|
||||
else:
|
||||
|
@ -66,9 +69,15 @@ class SaltCMD(parsers.SaltCMDOptionParser):
|
|||
if getattr(self.options, 'return'):
|
||||
kwargs['ret'] = getattr(self.options, 'return')
|
||||
|
||||
print self.options.eauth
|
||||
if self.options.eauth:
|
||||
resolver = salt.auth.Resolver(self.config)
|
||||
res = resolver.cli(self.options.eauth)
|
||||
if self.options.mktoken:
|
||||
kwargs['token'] = resolver.token_cli(
|
||||
self.options.eauth,
|
||||
res
|
||||
)['token']
|
||||
if not res:
|
||||
sys.exit(2)
|
||||
kwargs.update(res)
|
||||
|
|
198
salt/client.py
198
salt/client.py
|
@ -29,12 +29,10 @@ The data structure needs to be:
|
|||
# This means that the primary client to build is, the LocalClient
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import glob
|
||||
import time
|
||||
import getpass
|
||||
import fnmatch
|
||||
|
||||
# Import salt modules
|
||||
import salt.config
|
||||
|
@ -42,7 +40,7 @@ import salt.payload
|
|||
import salt.utils
|
||||
import salt.utils.verify
|
||||
import salt.utils.event
|
||||
from salt.exceptions import SaltClientError, SaltInvocationError
|
||||
from salt.exceptions import SaltInvocationError
|
||||
|
||||
# Try to import range from https://github.com/ytoolshed/range
|
||||
RANGE = False
|
||||
|
@ -69,8 +67,8 @@ class LocalClient(object):
|
|||
'''
|
||||
Connect to the salt master via the local server and via root
|
||||
'''
|
||||
def __init__(self, c_path='/etc/salt/master'):
|
||||
self.opts = salt.config.master_config(c_path)
|
||||
def __init__(self, c_path='/etc/salt'):
|
||||
self.opts = salt.config.client_config(c_path)
|
||||
self.serial = salt.payload.Serial(self.opts)
|
||||
self.salt_user = self.__get_user()
|
||||
self.key = self.__read_master_key()
|
||||
|
@ -115,129 +113,6 @@ class LocalClient(object):
|
|||
return user
|
||||
return user
|
||||
|
||||
def _check_glob_minions(self, expr):
|
||||
'''
|
||||
Return the minions found by looking via globs
|
||||
'''
|
||||
cwd = os.getcwd()
|
||||
os.chdir(os.path.join(self.opts['pki_dir'], 'minions'))
|
||||
ret = set(glob.glob(expr))
|
||||
os.chdir(cwd)
|
||||
return ret
|
||||
|
||||
def _check_list_minions(self, expr):
|
||||
'''
|
||||
Return the minions found by looking via a list
|
||||
'''
|
||||
ret = []
|
||||
for fn_ in os.listdir(os.path.join(self.opts['pki_dir'], 'minions')):
|
||||
if fn_ in expr:
|
||||
if fn_ not in ret:
|
||||
ret.append(fn_)
|
||||
return ret
|
||||
|
||||
def _check_pcre_minions(self, expr):
|
||||
'''
|
||||
Return the minions found by looking via regular expressions
|
||||
'''
|
||||
ret = set()
|
||||
cwd = os.getcwd()
|
||||
os.chdir(os.path.join(self.opts['pki_dir'], 'minions'))
|
||||
reg = re.compile(expr)
|
||||
for fn_ in os.listdir('.'):
|
||||
if reg.match(fn_):
|
||||
ret.add(fn_)
|
||||
os.chdir(cwd)
|
||||
return ret
|
||||
|
||||
def _check_grain_minions(self, expr):
|
||||
'''
|
||||
Return the minions found by looking via a list
|
||||
'''
|
||||
minions = set(os.listdir(os.path.join(self.opts['pki_dir'], 'minions')))
|
||||
if self.opts.get('minion_data_cache', False):
|
||||
cdir = os.path.join(self.opts['cachedir'], 'minions')
|
||||
if not os.path.isdir(cdir):
|
||||
return list(minions)
|
||||
for id_ in os.listdir(cdir):
|
||||
if not id_ in minions:
|
||||
continue
|
||||
datap = os.path.join(cdir, id_, 'data.p')
|
||||
if not os.path.isfile(datap):
|
||||
continue
|
||||
grains = self.serial.load(open(datap)).get('grains')
|
||||
comps = expr.split(':')
|
||||
if len(comps) < 2:
|
||||
continue
|
||||
if comps[0] not in grains:
|
||||
minions.remove(id_)
|
||||
continue
|
||||
if isinstance(grains.get(comps[0]), list):
|
||||
# We are matching a single component to a single list member
|
||||
found = False
|
||||
for member in grains[comps[0]]:
|
||||
if fnmatch.fnmatch(str(member).lower(), comps[1].lower()):
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
continue
|
||||
minions.remove(id_)
|
||||
continue
|
||||
if fnmatch.fnmatch(
|
||||
str(grains.get(comps[0], '').lower()),
|
||||
comps[1].lower(),
|
||||
):
|
||||
continue
|
||||
else:
|
||||
minions.remove(id_)
|
||||
return list(minions)
|
||||
|
||||
def _check_grain_pcre_minions(self, expr):
|
||||
'''
|
||||
Return the minions found by looking via a list
|
||||
'''
|
||||
minions = set(os.listdir(os.path.join(self.opts['pki_dir'], 'minions')))
|
||||
if self.opts.get('minion_data_cache', False):
|
||||
cdir = os.path.join(self.opts['cachedir'], 'minions')
|
||||
if not os.path.isdir(cdir):
|
||||
return list(minions)
|
||||
for id_ in os.listdir(cdir):
|
||||
if not id_ in minions:
|
||||
continue
|
||||
datap = os.path.join(cdir, id_, 'data.p')
|
||||
if not os.path.isfile(datap):
|
||||
continue
|
||||
grains = self.serial.load(open(datap)).get('grains')
|
||||
comps = expr.split(':')
|
||||
if len(comps) < 2:
|
||||
continue
|
||||
if comps[0] not in grains:
|
||||
minions.remove(id_)
|
||||
if isinstance(grains[comps[0]], list):
|
||||
# We are matching a single component to a single list member
|
||||
found = False
|
||||
for member in grains[comps[0]]:
|
||||
if re.match(comps[1].lower(), str(member).lower()):
|
||||
found = True
|
||||
if found:
|
||||
continue
|
||||
minions.remove(id_)
|
||||
continue
|
||||
if re.match(
|
||||
comps[1].lower(),
|
||||
str(grains[comps[0]]).lower()
|
||||
):
|
||||
continue
|
||||
else:
|
||||
minions.remove(id_)
|
||||
return list(minions)
|
||||
|
||||
def _all_minions(self, expr=None):
|
||||
'''
|
||||
Return a list of all minions that have auth'd
|
||||
'''
|
||||
return os.listdir(os.path.join(self.opts['pki_dir'], 'minions'))
|
||||
|
||||
def _convert_range_to_list(self, tgt):
|
||||
range = seco.range.Range(self.opts['range_server'])
|
||||
try:
|
||||
|
@ -246,7 +121,7 @@ class LocalClient(object):
|
|||
print(("Range server exception: {0}".format(e)))
|
||||
return []
|
||||
|
||||
def gather_job_info(self, jid, tgt, tgt_type):
|
||||
def gather_job_info(self, jid, tgt, tgt_type, **kwargs):
|
||||
'''
|
||||
Return the information about a given job
|
||||
'''
|
||||
|
@ -255,7 +130,8 @@ class LocalClient(object):
|
|||
'saltutil.find_job',
|
||||
[jid],
|
||||
2,
|
||||
tgt_type)
|
||||
tgt_type,
|
||||
**kwargs)
|
||||
|
||||
def _check_pub_data(self, pub_data):
|
||||
'''
|
||||
|
@ -367,7 +243,8 @@ class LocalClient(object):
|
|||
timeout or self.opts['timeout'],
|
||||
tgt,
|
||||
expr_form,
|
||||
verbose):
|
||||
verbose,
|
||||
**kwargs):
|
||||
|
||||
if not fn_ret:
|
||||
continue
|
||||
|
@ -480,7 +357,8 @@ class LocalClient(object):
|
|||
timeout=None,
|
||||
tgt='*',
|
||||
tgt_type='glob',
|
||||
verbose=False):
|
||||
verbose=False,
|
||||
**kwargs):
|
||||
'''
|
||||
This method starts off a watcher looking at the return data for
|
||||
a specified jid, it returns all of the information for the jid
|
||||
|
@ -543,7 +421,7 @@ class LocalClient(object):
|
|||
if int(time.time()) > start + timeout:
|
||||
# The timeout has been reached, check the jid to see if the
|
||||
# timeout needs to be increased
|
||||
jinfo = self.gather_job_info(jid, tgt, tgt_type)
|
||||
jinfo = self.gather_job_info(jid, tgt, tgt_type, **kwargs)
|
||||
more_time = False
|
||||
for id_ in jinfo:
|
||||
if jinfo[id_]:
|
||||
|
@ -738,6 +616,7 @@ class LocalClient(object):
|
|||
'''
|
||||
Get the returns for the command line interface via the event system
|
||||
'''
|
||||
minions = set(minions)
|
||||
if verbose:
|
||||
msg = 'Executing job with jid {0}'.format(jid)
|
||||
print(msg)
|
||||
|
@ -795,7 +674,8 @@ class LocalClient(object):
|
|||
timeout=None,
|
||||
tgt='*',
|
||||
tgt_type='glob',
|
||||
verbose=False):
|
||||
verbose=False,
|
||||
**kwargs):
|
||||
'''
|
||||
Get the returns for the command line interface via the event system
|
||||
'''
|
||||
|
@ -850,7 +730,7 @@ class LocalClient(object):
|
|||
if int(time.time()) > start + timeout:
|
||||
# The timeout has been reached, check the jid to see if the
|
||||
# timeout needs to be increased
|
||||
jinfo = self.gather_job_info(jid, tgt, tgt_type)
|
||||
jinfo = self.gather_job_info(jid, tgt, tgt_type, **kwargs)
|
||||
more_time = False
|
||||
for id_ in jinfo:
|
||||
if jinfo[id_]:
|
||||
|
@ -932,27 +812,6 @@ class LocalClient(object):
|
|||
continue
|
||||
return ret
|
||||
|
||||
def check_minions(self, expr, expr_form='glob'):
|
||||
'''
|
||||
Check the passed regex against the available minions' public keys
|
||||
stored for authentication. This should return a set of ids which
|
||||
match the regex, this will then be used to parse the returns to
|
||||
make sure everyone has checked back in.
|
||||
'''
|
||||
try:
|
||||
minions = {'glob': self._check_glob_minions,
|
||||
'pcre': self._check_pcre_minions,
|
||||
'list': self._check_list_minions,
|
||||
'grain': self._check_grain_minions,
|
||||
'grain_pcre': self._check_grain_pcre_minions,
|
||||
'exsel': self._all_minions,
|
||||
'pillar': self._all_minions,
|
||||
'compound': self._all_minions,
|
||||
}[expr_form](expr)
|
||||
except Exception:
|
||||
minions = expr
|
||||
return minions
|
||||
|
||||
def pub(self,
|
||||
tgt,
|
||||
fun,
|
||||
|
@ -997,7 +856,10 @@ class LocalClient(object):
|
|||
conf_file = self.opts.get('conf_file', 'the master config file')
|
||||
err = 'Node group {0} unavailable in {1}'.format(tgt, conf_file)
|
||||
raise SaltInvocationError(err)
|
||||
tgt = self.opts['nodegroups'][tgt]
|
||||
tgt = salt.utils.minions.nodegroup_comp(
|
||||
tgt,
|
||||
self.opts['nodegroups']
|
||||
)
|
||||
expr_form = 'compound'
|
||||
|
||||
# Convert a range expression to a list of nodes and change expression
|
||||
|
@ -1006,28 +868,22 @@ class LocalClient(object):
|
|||
tgt = self._convert_range_to_list(tgt)
|
||||
expr_form = 'list'
|
||||
|
||||
# Run a check_minions, if no minions match return False
|
||||
# format the payload - make a function that does this in the payload
|
||||
# module
|
||||
# make the zmq client
|
||||
# connect to the req server
|
||||
# send!
|
||||
# return what we get back
|
||||
minions = self.check_minions(tgt, expr_form)
|
||||
|
||||
if not minions:
|
||||
return {'jid': None,
|
||||
'minions': minions}
|
||||
|
||||
# Generate the standard keyword args to feed to format_payload
|
||||
payload_kwargs = {'cmd': 'publish',
|
||||
'tgt': tgt,
|
||||
'fun': fun,
|
||||
'arg': arg,
|
||||
'key': self.key,
|
||||
'tgt_type': expr_form,
|
||||
'ret': ret,
|
||||
'jid': jid}
|
||||
'tgt': tgt,
|
||||
'fun': fun,
|
||||
'arg': arg,
|
||||
'key': self.key,
|
||||
'tgt_type': expr_form,
|
||||
'ret': ret,
|
||||
'jid': jid}
|
||||
|
||||
# if kwargs are passed, pack them.
|
||||
if kwargs:
|
||||
|
@ -1048,7 +904,7 @@ class LocalClient(object):
|
|||
if not payload:
|
||||
return payload
|
||||
return {'jid': payload['load']['jid'],
|
||||
'minions': minions}
|
||||
'minions': payload['load']['minions']}
|
||||
|
||||
|
||||
class FunctionWrapper(dict):
|
||||
|
|
|
@ -104,7 +104,6 @@ def include_config(include, opts, orig_path, verbose):
|
|||
Parses extra configuration file(s) specified in an include list in the
|
||||
main config file.
|
||||
'''
|
||||
|
||||
# Protect against empty option
|
||||
if not include:
|
||||
return opts
|
||||
|
@ -276,6 +275,7 @@ def master_config(path):
|
|||
'runner_dirs': [],
|
||||
'client_acl': {},
|
||||
'external_auth': {},
|
||||
'token_expire': 720,
|
||||
'file_buffer_size': 1048576,
|
||||
'max_open_files': 100000,
|
||||
'hash_type': 'md5',
|
||||
|
@ -339,3 +339,21 @@ def master_config(path):
|
|||
opts['auto_accept'] = opts['auto_accept'] is True
|
||||
opts['file_roots'] = _validate_file_roots(opts['file_roots'])
|
||||
return opts
|
||||
|
||||
|
||||
def client_config(path):
|
||||
'''
|
||||
Load in the configuration data needed for the LocalClient. This function
|
||||
searches for client specific configurations and adds them to the data from
|
||||
the master configuration.
|
||||
'''
|
||||
opts = {'token_file': os.path.expanduser('~/.salt_token')}
|
||||
opts.update(master_config(path))
|
||||
cpath = os.path.expanduser('~/.salt')
|
||||
load_config(opts, cpath, 'SALT_CLIENT_CONFIG')
|
||||
if 'token_file' in opts:
|
||||
opts['token_file'] = os.path.expanduser(opts['token_file'])
|
||||
if os.path.isfile(opts['token_file']):
|
||||
with open(opts['token_file']) as fp_:
|
||||
opts['token'] = fp_.read().strip()
|
||||
return opts
|
||||
|
|
|
@ -127,10 +127,13 @@ def _sunos_cpudata(osdata):
|
|||
grains = {'num_cpus': 0}
|
||||
|
||||
grains['cpuarch'] = __salt__['cmd.run']('uname -p').strip()
|
||||
for line in __salt__['cmd.run']('/usr/sbin/psrinfo 2>/dev/null').split('\n'):
|
||||
for line in __salt__['cmd.run'](
|
||||
'/usr/sbin/psrinfo 2>/dev/null'
|
||||
).split('\n'):
|
||||
grains['num_cpus'] += 1
|
||||
grains['cpu_model'] = __salt__['cmd.run']('kstat -p cpu_info:0:cpu_info0:implementation').split()[1].strip()
|
||||
|
||||
grains['cpu_model'] = __salt__['cmd.run'](
|
||||
'kstat -p cpu_info:*:*:implementation'
|
||||
).split()[1].strip()
|
||||
return grains
|
||||
|
||||
|
||||
|
@ -162,14 +165,14 @@ def _memdata(osdata):
|
|||
if comps[0].strip() == 'Memory' and comps[1].strip() == 'size:':
|
||||
grains['mem_total'] = int(comps[2].strip())
|
||||
elif osdata['kernel'] == 'Windows':
|
||||
for line in __salt__['cmd.run']('SYSTEMINFO /FO LIST').split('\n'):
|
||||
comps = line.split(':')
|
||||
if not len(comps) > 1:
|
||||
continue
|
||||
if comps[0].strip() == 'Total Physical Memory':
|
||||
# Windows XP use '.' as separator and Windows 2008 Server R2 use ','
|
||||
grains['mem_total'] = int(comps[1].split()[0].replace('.', '').replace(',', ''))
|
||||
break
|
||||
import wmi
|
||||
wmi_c = wmi.WMI()
|
||||
# this is a list of each stick of ram in a system
|
||||
# WMI returns it as the string value of the number of bytes
|
||||
tot_bytes = sum(map(lambda x: int(x.Capacity),
|
||||
wmi_c.Win32_PhysicalMemory()), 0)
|
||||
# return memory info in gigabytes
|
||||
grains['mem_total'] = int(tot_bytes / (1024 ** 2))
|
||||
return grains
|
||||
|
||||
|
||||
|
@ -421,8 +424,14 @@ def os_data():
|
|||
Return grains pertaining to the operating system
|
||||
'''
|
||||
grains = {}
|
||||
(grains['defaultlanguage'],
|
||||
grains['defaultencoding']) = locale.getdefaultlocale()
|
||||
try:
|
||||
(grains['defaultlanguage'],
|
||||
grains['defaultencoding']) = locale.getdefaultlocale()
|
||||
except Exception:
|
||||
# locale.getdefaultlocale can ValueError!! Catch anything else it
|
||||
# might do, per #2205
|
||||
grains['defaultlanguage'] = 'unknown'
|
||||
grains['defaultencoding'] = 'unknown'
|
||||
# Windows Server 2008 64-bit
|
||||
# ('Windows', 'MINIONNAME', '2008ServerR2', '6.1.7601', 'AMD64', 'Intel64 Fam ily 6 Model 23 Stepping 6, GenuineIntel')
|
||||
# Ubuntu 10.04
|
||||
|
|
|
@ -534,17 +534,17 @@ class Loader(object):
|
|||
Pass in a function object returned from get_functions to load in
|
||||
introspection functions.
|
||||
'''
|
||||
funcs['sys.list_functions'] = lambda: self.list_funcs(funcs)
|
||||
funcs['sys.list_functions'] = lambda module='': self.list_funcs(funcs, module)
|
||||
funcs['sys.list_modules'] = lambda: self.list_modules(funcs)
|
||||
funcs['sys.doc'] = lambda module = '': self.get_docs(funcs, module)
|
||||
funcs['sys.reload_modules'] = lambda: True
|
||||
return funcs
|
||||
|
||||
def list_funcs(self, funcs):
|
||||
def list_funcs(self, funcs, module=''):
|
||||
'''
|
||||
List the functions
|
||||
List the functions. Optionally, specify a module to list from.
|
||||
'''
|
||||
return sorted(funcs)
|
||||
return sorted(f for f in funcs if f.startswith(module + '.')) if module else sorted(funcs)
|
||||
|
||||
def list_modules(self, funcs):
|
||||
'''
|
||||
|
|
|
@ -43,6 +43,7 @@ import salt.auth
|
|||
import salt.utils.atomicfile
|
||||
import salt.utils.event
|
||||
import salt.utils.verify
|
||||
import salt.utils.minions
|
||||
from salt.utils.debug import enable_sigusr1_handler
|
||||
|
||||
|
||||
|
@ -525,6 +526,7 @@ class AESFuncs(object):
|
|||
self.event = salt.utils.event.MasterEvent(self.opts['sock_dir'])
|
||||
self.serial = salt.payload.Serial(opts)
|
||||
self.crypticle = crypticle
|
||||
self.ckminions = salt.utils.minions.CkMinions(opts)
|
||||
# Create the tops dict for loading external top data
|
||||
self.tops = salt.loader.tops(self.opts)
|
||||
# Make a client
|
||||
|
@ -953,13 +955,12 @@ class AESFuncs(object):
|
|||
clear_load['id'])
|
||||
log.warn(msg)
|
||||
return {}
|
||||
perms = set()
|
||||
perms = []
|
||||
for match in self.opts['peer']:
|
||||
if re.match(match, clear_load['id']):
|
||||
# This is the list of funcs/modules!
|
||||
if isinstance(self.opts['peer'][match], list):
|
||||
perms.update(self.opts['peer'][match])
|
||||
good = False
|
||||
perms.extend(self.opts['peer'][match])
|
||||
if ',' in clear_load['fun']:
|
||||
# 'arg': [['cat', '/proc/cpuinfo'], [], ['foo']]
|
||||
clear_load['fun'] = clear_load['fun'].split(',')
|
||||
|
@ -967,15 +968,11 @@ class AESFuncs(object):
|
|||
for arg in clear_load['arg']:
|
||||
arg_.append(arg.split())
|
||||
clear_load['arg'] = arg_
|
||||
for perm in perms:
|
||||
if isinstance(clear_load['fun'], list):
|
||||
good = True
|
||||
for fun in clear_load['fun']:
|
||||
if not re.match(perm, fun):
|
||||
good = False
|
||||
else:
|
||||
if re.match(perm, clear_load['fun']):
|
||||
good = True
|
||||
good = self.ckminions.auth_check(
|
||||
perms,
|
||||
clear_load['fun'],
|
||||
clear_load['tgt'],
|
||||
clear_load.get('tgt_type', 'glob'))
|
||||
if not good:
|
||||
return {}
|
||||
# Set up the publication payload
|
||||
|
@ -1039,7 +1036,7 @@ class AESFuncs(object):
|
|||
if ret_form == 'clean':
|
||||
return self.local.get_returns(
|
||||
jid,
|
||||
self.local.check_minions(
|
||||
self.ckminions.check_minions(
|
||||
clear_load['tgt'],
|
||||
expr_form
|
||||
),
|
||||
|
@ -1048,7 +1045,7 @@ class AESFuncs(object):
|
|||
elif ret_form == 'full':
|
||||
ret = self.local.get_full_returns(
|
||||
jid,
|
||||
self.local.check_minions(
|
||||
self.ckminions.check_minions(
|
||||
clear_load['tgt'],
|
||||
expr_form
|
||||
),
|
||||
|
@ -1098,6 +1095,8 @@ class ClearFuncs(object):
|
|||
self.event = salt.utils.event.MasterEvent(self.opts['sock_dir'])
|
||||
# Make a client
|
||||
self.local = salt.client.LocalClient(self.opts['conf_file'])
|
||||
# Make an minion checker object
|
||||
self.ckminions = salt.utils.minions.CkMinions(opts)
|
||||
# Make an Auth object
|
||||
self.loadauth = salt.auth.LoadAuth(opts)
|
||||
|
||||
|
@ -1379,6 +1378,19 @@ class ClearFuncs(object):
|
|||
self.event.fire_event(eload, 'auth')
|
||||
return ret
|
||||
|
||||
def mk_token(self, clear_load):
|
||||
if not 'eauth' in clear_load:
|
||||
return ''
|
||||
if not clear_load['eauth'] in self.opts['external_auth']:
|
||||
# The eauth system is not enabled, fail
|
||||
return ''
|
||||
name = self.loadauth.load_name(clear_load)
|
||||
if not name in self.opts['external_auth'][clear_load['eauth']]:
|
||||
return ''
|
||||
if not self.loadauth.time_auth(clear_load):
|
||||
return ''
|
||||
return self.loadauth.mk_token(clear_load)
|
||||
|
||||
def publish(self, clear_load):
|
||||
'''
|
||||
This method sends out publications to the minions, it can only be used
|
||||
|
@ -1386,7 +1398,25 @@ class ClearFuncs(object):
|
|||
'''
|
||||
extra = clear_load.get('kwargs', {})
|
||||
# Check for external auth calls
|
||||
if 'eauth' in extra:
|
||||
if extra.get('token', False):
|
||||
# A token was passwd, check it
|
||||
token = self.loadauth.get_tok(extra['token'])
|
||||
if not token:
|
||||
return ''
|
||||
if not token['eauth'] in self.opts['external_auth']:
|
||||
return ''
|
||||
if not token['name'] in self.opts['external_auth'][token['eauth']]:
|
||||
return ''
|
||||
good = self.ckminions.auth_check(
|
||||
self.opts['external_auth'][token['eauth']][token['name']],
|
||||
clear_load['fun'],
|
||||
clear_load['tgt'],
|
||||
clear_load.get('tgt_type', 'glob'))
|
||||
if not good:
|
||||
# Accept find_job so the cli will function cleanly
|
||||
if not clear_load['fun'] == 'saltutil.find_job':
|
||||
return ''
|
||||
elif 'eauth' in extra:
|
||||
if not extra['eauth'] in self.opts['external_auth']:
|
||||
# The eauth system is not enabled, fail
|
||||
return ''
|
||||
|
@ -1395,10 +1425,11 @@ class ClearFuncs(object):
|
|||
return ''
|
||||
if not self.loadauth.time_auth(extra):
|
||||
return ''
|
||||
good = False
|
||||
for regex in self.opts['external_auth'][extra['eauth']][name]:
|
||||
if re.match(regex, clear_load['fun']):
|
||||
good = True
|
||||
good = self.ckminions.auth_check(
|
||||
self.opts['external_auth'][extra['eauth']][name],
|
||||
clear_load['fun'],
|
||||
clear_load['tgt'],
|
||||
clear_load.get('tgt_type', 'glob'))
|
||||
if not good:
|
||||
# Accept find_job so the cli will function cleanly
|
||||
if not clear_load['fun'] == 'saltutil.find_job':
|
||||
|
@ -1420,14 +1451,15 @@ class ClearFuncs(object):
|
|||
if not clear_load.pop('key') == self.key[clear_load['user']]:
|
||||
return ''
|
||||
good = False
|
||||
for user in self.opts['client_acl']:
|
||||
if clear_load['user'] != user:
|
||||
continue
|
||||
for regex in self.opts['client_acl'][user]:
|
||||
if re.match(regex, clear_load['fun']):
|
||||
good = True
|
||||
good = self.ckminions.auth_check(
|
||||
self.opts['client_acl'],
|
||||
clear_load['fun'],
|
||||
clear_load['tgt'],
|
||||
clear_load.get('tgt_type', 'glob'))
|
||||
if not good:
|
||||
return ''
|
||||
# Accept find_job so the cli will function cleanly
|
||||
if not clear_load['fun'] == 'saltutil.find_job':
|
||||
return ''
|
||||
else:
|
||||
return ''
|
||||
else:
|
||||
|
@ -1492,5 +1524,7 @@ class ClearFuncs(object):
|
|||
)
|
||||
pub_sock.connect(pull_uri)
|
||||
pub_sock.send(self.serial.dumps(payload))
|
||||
minions = self.ckminions.check_minions(load['tgt'], load.get('tgt_type', 'glob'))
|
||||
return {'enc': 'clear',
|
||||
'load': {'jid': clear_load['jid']}}
|
||||
'load': {'jid': clear_load['jid'],
|
||||
'minions': minions}}
|
||||
|
|
|
@ -921,5 +921,7 @@ class Matcher(object):
|
|||
matcher is used in states
|
||||
'''
|
||||
if tgt in nodegroups:
|
||||
return self.compound_match(nodegroups[tgt])
|
||||
return self.compound_match(
|
||||
salt.utils.nodegroup_comp(tgt, nodegroups)
|
||||
)
|
||||
return False
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -162,23 +162,14 @@ def _run(cmd,
|
|||
kwargs['executable'] = shell
|
||||
kwargs['close_fds'] = True
|
||||
|
||||
# If all we want is the return code then don't block on gathering input.
|
||||
if retcode:
|
||||
kwargs['stdout'] = None
|
||||
kwargs['stderr'] = None
|
||||
|
||||
# This is where the magic happens
|
||||
proc = subprocess.Popen(cmd, **kwargs)
|
||||
|
||||
# If all we want is the return code then don't block on gathering input,
|
||||
# this is used to bypass ampersand issues with background processes in
|
||||
# scripts
|
||||
if retcode:
|
||||
while True:
|
||||
retcode = proc.poll()
|
||||
if retcode is None:
|
||||
continue
|
||||
else:
|
||||
out = ''
|
||||
err = ''
|
||||
break
|
||||
else:
|
||||
out, err = proc.communicate()
|
||||
out, err = proc.communicate()
|
||||
|
||||
if rstrip:
|
||||
if out is not None:
|
||||
|
|
|
@ -14,7 +14,6 @@ might look like::
|
|||
This data can also be passed into pillar. Options passed into opts will
|
||||
overwrite options passed into pillar
|
||||
'''
|
||||
import pipes
|
||||
import logging
|
||||
from salt.utils import check_or_die
|
||||
from salt.exceptions import CommandNotFoundError
|
||||
|
@ -62,6 +61,11 @@ def _connection_defaults(user=None, host=None, port=None):
|
|||
|
||||
return (user, host, port)
|
||||
|
||||
def _quote(s):
|
||||
r = s.replace("'", r"\'").replace('"', r'\"')
|
||||
if ' ' in s:
|
||||
r = "'%s'" % r
|
||||
return r
|
||||
|
||||
def _psql_cmd(*args, **kwargs):
|
||||
'''
|
||||
|
@ -81,7 +85,7 @@ def _psql_cmd(*args, **kwargs):
|
|||
if port is not None:
|
||||
cmd += ['--port', port]
|
||||
cmd += args
|
||||
cmdstr = ' '.join(map(pipes.quote, cmd))
|
||||
cmdstr = ' '.join(map(_quote, cmd))
|
||||
return cmdstr
|
||||
|
||||
|
||||
|
|
|
@ -8,9 +8,13 @@ except ImportError:
|
|||
pass
|
||||
|
||||
import os
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
|
||||
from salt._compat import string_types
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
|
@ -19,13 +23,45 @@ def __virtual__():
|
|||
return 'user' if __grains__['kernel'] == 'FreeBSD' else False
|
||||
|
||||
|
||||
def _get_gecos(name):
|
||||
'''
|
||||
Retrieve GECOS field info and return it in dictionary form
|
||||
'''
|
||||
gecos_field = pwd.getpwnam(name).pw_gecos.split(',', 3)
|
||||
if not gecos_field:
|
||||
return {}
|
||||
else:
|
||||
# Assign empty strings for any unspecified trailing GECOS fields
|
||||
while len(gecos_field) < 4: gecos_field.append('')
|
||||
return {'fullname': str(gecos_field[0]),
|
||||
'roomnumber': str(gecos_field[1]),
|
||||
'workphone': str(gecos_field[2]),
|
||||
'homephone': str(gecos_field[3])}
|
||||
|
||||
|
||||
def _build_gecos(gecos_dict):
|
||||
'''
|
||||
Accepts a dictionary entry containing GECOS field names and their values,
|
||||
and returns a full GECOS comment string, to be used with pw usermod.
|
||||
'''
|
||||
return '{0},{1},{2},{3}'.format(gecos_dict.get('fullname',''),
|
||||
gecos_dict.get('roomnumber',''),
|
||||
gecos_dict.get('workphone',''),
|
||||
gecos_dict.get('homephone',''))
|
||||
|
||||
|
||||
def add(name,
|
||||
uid=None,
|
||||
gid=None,
|
||||
groups=None,
|
||||
home=True,
|
||||
shell=None,
|
||||
unique=True,
|
||||
system=False,
|
||||
fullname='',
|
||||
roomnumber='',
|
||||
workphone='',
|
||||
homephone='',
|
||||
**kwargs):
|
||||
'''
|
||||
Add a user to the minion
|
||||
|
@ -50,6 +86,11 @@ def add(name,
|
|||
cmd += '-m '
|
||||
else:
|
||||
cmd += '-m -b {0} '.format(os.path.dirname(home))
|
||||
gecos_field = '{0},{1},{2},{3}'.format(fullname,
|
||||
roomnumber,
|
||||
workphone,
|
||||
homephone)
|
||||
cmd += '-c "{0}" '.format(gecos_field)
|
||||
cmd += '-n {0}'.format(name)
|
||||
ret = __salt__['cmd.run_all'](cmd)
|
||||
|
||||
|
@ -190,6 +231,98 @@ def chgroups(name, groups, append=False):
|
|||
return len(ugrps - agrps) == 0
|
||||
|
||||
|
||||
def chfullname(name, fullname):
|
||||
'''
|
||||
Change the user's Full Name
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chfullname foo "Foo Bar"
|
||||
'''
|
||||
fullname = str(fullname)
|
||||
pre_info = _get_gecos(name)
|
||||
if not pre_info: return False
|
||||
if fullname == pre_info['fullname']:
|
||||
return True
|
||||
gecos_field = deepcopy(pre_info)
|
||||
gecos_field['fullname'] = fullname
|
||||
cmd = 'pw usermod {0} -c "{1}"'.format(name, _build_gecos(gecos_field))
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['fullname'] != pre_info['fullname']:
|
||||
return post_info['fullname'] == fullname
|
||||
return False
|
||||
|
||||
|
||||
def chroomnumber(name, roomnumber):
|
||||
'''
|
||||
Change the user's Room Number
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chroomnumber foo 123
|
||||
'''
|
||||
roomnumber = str(roomnumber)
|
||||
pre_info = _get_gecos(name)
|
||||
if not pre_info: return False
|
||||
if roomnumber == pre_info['roomnumber']:
|
||||
return True
|
||||
gecos_field = deepcopy(pre_info)
|
||||
gecos_field['roomnumber'] = roomnumber
|
||||
cmd = 'pw usermod {0} -c "{1}"'.format(name, _build_gecos(gecos_field))
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['roomnumber'] != pre_info['roomnumber']:
|
||||
return post_info['roomnumber'] == roomnumber
|
||||
return False
|
||||
|
||||
|
||||
def chworkphone(name, workphone):
|
||||
'''
|
||||
Change the user's Work Phone
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chworkphone foo "7735550123"
|
||||
'''
|
||||
workphone = str(workphone)
|
||||
pre_info = _get_gecos(name)
|
||||
if not pre_info: return False
|
||||
if workphone == pre_info['workphone']:
|
||||
return True
|
||||
gecos_field = deepcopy(pre_info)
|
||||
gecos_field['workphone'] = workphone
|
||||
cmd = 'pw usermod {0} -c "{1}"'.format(name, _build_gecos(gecos_field))
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['workphone'] != pre_info['workphone']:
|
||||
return post_info['workphone'] == workphone
|
||||
return False
|
||||
|
||||
|
||||
def chhomephone(name, homephone):
|
||||
'''
|
||||
Change the user's Home Phone
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chhomephone foo "7735551234"
|
||||
'''
|
||||
homephone = str(homephone)
|
||||
pre_info = _get_gecos(name)
|
||||
if not pre_info: return False
|
||||
if homephone == pre_info['homephone']:
|
||||
return True
|
||||
gecos_field = deepcopy(pre_info)
|
||||
gecos_field['homephone'] = homephone
|
||||
cmd = 'pw usermod {0} -c "{1}"'.format(name, _build_gecos(gecos_field))
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['homephone'] != pre_info['homephone']:
|
||||
return post_info['homephone'] == homephone
|
||||
return False
|
||||
|
||||
|
||||
def info(name):
|
||||
'''
|
||||
Return user information
|
||||
|
@ -199,14 +332,35 @@ def info(name):
|
|||
salt '*' user.info root
|
||||
'''
|
||||
ret = {}
|
||||
data = pwd.getpwnam(name)
|
||||
ret['name'] = data.pw_name
|
||||
ret['passwd'] = data.pw_passwd
|
||||
ret['uid'] = data.pw_uid
|
||||
ret['gid'] = data.pw_gid
|
||||
ret['home'] = data.pw_dir
|
||||
ret['shell'] = data.pw_shell
|
||||
ret['groups'] = list_groups(name)
|
||||
try:
|
||||
data = pwd.getpwnam(name)
|
||||
ret['gid'] = data.pw_gid
|
||||
ret['groups'] = list_groups(name)
|
||||
ret['home'] = data.pw_dir
|
||||
ret['name'] = data.pw_name
|
||||
ret['passwd'] = data.pw_passwd
|
||||
ret['shell'] = data.pw_shell
|
||||
ret['uid'] = data.pw_uid
|
||||
# Put GECOS info into a list
|
||||
gecos_field = data.pw_gecos.split(',', 3)
|
||||
# Assign empty strings for any unspecified GECOS fields
|
||||
while len(gecos_field) < 4: gecos_field.append('')
|
||||
ret['fullname'] = gecos_field[0]
|
||||
ret['roomnumber'] = gecos_field[1]
|
||||
ret['workphone'] = gecos_field[2]
|
||||
ret['homephone'] = gecos_field[3]
|
||||
except KeyError:
|
||||
ret['gid'] = ''
|
||||
ret['groups'] = ''
|
||||
ret['home'] = ''
|
||||
ret['name'] = ''
|
||||
ret['passwd'] = ''
|
||||
ret['shell'] = ''
|
||||
ret['uid'] = ''
|
||||
ret['fullname'] = ''
|
||||
ret['roomnumber'] = ''
|
||||
ret['workphone'] = ''
|
||||
ret['homephone'] = ''
|
||||
return ret
|
||||
|
||||
|
||||
|
|
97
salt/modules/solaris_group.py
Normal file
97
salt/modules/solaris_group.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
'''
|
||||
Manage groups on Solaris
|
||||
'''
|
||||
try:
|
||||
import grp
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Set the group module if the kernel is SunOS
|
||||
'''
|
||||
return 'group' if __grains__['kernel'] == 'SunOS' else False
|
||||
|
||||
|
||||
def add(name, gid=None, system=False):
|
||||
'''
|
||||
Add the specified group
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' group.add foo 3456
|
||||
'''
|
||||
cmd = 'groupadd '
|
||||
if gid:
|
||||
cmd += '-g {0} '.format(gid)
|
||||
cmd += name
|
||||
|
||||
ret = __salt__['cmd.run_all'](cmd)
|
||||
|
||||
return not ret['retcode']
|
||||
|
||||
|
||||
def delete(name):
|
||||
'''
|
||||
Remove the named group
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' group.delete foo
|
||||
'''
|
||||
ret = __salt__['cmd.run_all']('groupdel {0}'.format(name))
|
||||
|
||||
return not ret['retcode']
|
||||
|
||||
|
||||
def info(name):
|
||||
'''
|
||||
Return information about a group
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' group.info foo
|
||||
'''
|
||||
try:
|
||||
grinfo = grp.getgrnam(name)
|
||||
except KeyError:
|
||||
return {}
|
||||
else:
|
||||
return {'name': grinfo.gr_name,
|
||||
'passwd': grinfo.gr_passwd,
|
||||
'gid': grinfo.gr_gid,
|
||||
'members': grinfo.gr_mem}
|
||||
|
||||
|
||||
def getent():
|
||||
'''
|
||||
Return info on all groups
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' group.getent
|
||||
'''
|
||||
ret = []
|
||||
for grinfo in grp.getgrall():
|
||||
ret.append(info(grinfo.gr_name))
|
||||
return ret
|
||||
|
||||
|
||||
def chgid(name, gid):
|
||||
'''
|
||||
Change the gid for a named group
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' group.chgid foo 4376
|
||||
'''
|
||||
pre_gid = __salt__['file.group_to_gid'](name)
|
||||
if gid == pre_gid:
|
||||
return True
|
||||
cmd = 'groupmod -g {0} {1}'.format(gid, name)
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_gid = __salt__['file.group_to_gid'](name)
|
||||
if post_gid != pre_gid:
|
||||
return post_gid == gid
|
||||
return False
|
399
salt/modules/solaris_user.py
Normal file
399
salt/modules/solaris_user.py
Normal file
|
@ -0,0 +1,399 @@
|
|||
'''
|
||||
Manage users with the useradd command
|
||||
'''
|
||||
try:
|
||||
import grp
|
||||
import pwd
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
|
||||
from salt._compat import string_types, callable
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Set the user module if the kernel is SunOS
|
||||
'''
|
||||
|
||||
return 'user' if __grains__['kernel'] == 'SunOS' else False
|
||||
|
||||
|
||||
def _get_gecos(name):
|
||||
'''
|
||||
Retrieve GECOS field info and return it in dictionary form
|
||||
'''
|
||||
gecos_field = pwd.getpwnam(name).pw_gecos.split(',', 3)
|
||||
if not gecos_field:
|
||||
return {}
|
||||
else:
|
||||
# Assign empty strings for any unspecified trailing GECOS fields
|
||||
while len(gecos_field) < 4: gecos_field.append('')
|
||||
return {'fullname': str(gecos_field[0]),
|
||||
'roomnumber': str(gecos_field[1]),
|
||||
'workphone': str(gecos_field[2]),
|
||||
'homephone': str(gecos_field[3])}
|
||||
|
||||
|
||||
def _build_gecos(gecos_dict):
|
||||
'''
|
||||
Accepts a dictionary entry containing GECOS field names and their values,
|
||||
and returns a full GECOS comment string, to be used with usermod.
|
||||
'''
|
||||
return '{0},{1},{2},{3}'.format(gecos_dict.get('fullname',''),
|
||||
gecos_dict.get('roomnumber',''),
|
||||
gecos_dict.get('workphone',''),
|
||||
gecos_dict.get('homephone',''))
|
||||
|
||||
|
||||
def add(name,
|
||||
uid=None,
|
||||
gid=None,
|
||||
groups=None,
|
||||
home=True,
|
||||
shell=None,
|
||||
unique=True,
|
||||
system=False,
|
||||
fullname='',
|
||||
roomnumber='',
|
||||
workphone='',
|
||||
homephone=''):
|
||||
'''
|
||||
Add a user to the minion
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.add name <uid> <gid> <groups> <home> <shell>
|
||||
'''
|
||||
if isinstance(groups, string_types):
|
||||
groups = groups.split(',')
|
||||
cmd = 'useradd '
|
||||
if shell:
|
||||
cmd += '-s {0} '.format(shell)
|
||||
if uid:
|
||||
cmd += '-u {0} '.format(uid)
|
||||
if gid:
|
||||
cmd += '-g {0} '.format(gid)
|
||||
if groups:
|
||||
cmd += '-G {0} '.format(','.join(groups))
|
||||
if home:
|
||||
if home is not True:
|
||||
if system:
|
||||
cmd += '-d {0} '.format(home)
|
||||
else:
|
||||
cmd += '-m -d {0} '.format(home)
|
||||
else:
|
||||
if not system:
|
||||
cmd += '-m '
|
||||
if not unique:
|
||||
cmd += '-o '
|
||||
cmd += name
|
||||
ret = __salt__['cmd.retcode'](cmd)
|
||||
if ret != 0:
|
||||
return False
|
||||
else:
|
||||
# At this point, the user was successfully created, so return true
|
||||
# regardless of the outcome of the below functions. If there is a
|
||||
# problem wth changing any of the user's info below, it will be raised
|
||||
# in a future highstate call. If anyone has a better idea on how to do
|
||||
# this, feel free to change it, but I didn't think it was a good idea
|
||||
# to return False when the user was successfully created since A) the
|
||||
# user does exist, and B) running useradd again would result in a
|
||||
# nonzero exit status and be interpreted as a False result.
|
||||
if fullname:
|
||||
chfullname(name, fullname)
|
||||
if roomnumber:
|
||||
chroomnumber(name, roomnumber)
|
||||
if workphone:
|
||||
chworkphone(name, workphone)
|
||||
if homephone:
|
||||
chhomephone(name, homephone)
|
||||
return True
|
||||
|
||||
|
||||
def delete(name, remove=False, force=False):
|
||||
'''
|
||||
Remove a user from the minion
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.delete name remove=True force=True
|
||||
'''
|
||||
cmd = 'userdel '
|
||||
if remove:
|
||||
cmd += '-r '
|
||||
cmd += name
|
||||
|
||||
ret = __salt__['cmd.run_all'](cmd)
|
||||
|
||||
return not ret['retcode']
|
||||
|
||||
|
||||
def getent():
|
||||
'''
|
||||
Return the list of all info for all users
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.getent
|
||||
'''
|
||||
ret = []
|
||||
for data in pwd.getpwall():
|
||||
ret.append(info(data.pw_name))
|
||||
return ret
|
||||
|
||||
|
||||
def chuid(name, uid):
|
||||
'''
|
||||
Change the uid for a named user
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chuid foo 4376
|
||||
'''
|
||||
pre_info = info(name)
|
||||
if uid == pre_info['uid']:
|
||||
return True
|
||||
cmd = 'usermod -u {0} {1}'.format(uid, name)
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['uid'] != pre_info['uid']:
|
||||
return post_info['uid'] == uid
|
||||
return False
|
||||
|
||||
|
||||
def chgid(name, gid):
|
||||
'''
|
||||
Change the default group of the user
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chgid foo 4376
|
||||
'''
|
||||
pre_info = info(name)
|
||||
if gid == pre_info['gid']:
|
||||
return True
|
||||
cmd = 'usermod -g {0} {1}'.format(gid, name)
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['gid'] != pre_info['gid']:
|
||||
return post_info['gid'] == gid
|
||||
return False
|
||||
|
||||
|
||||
def chshell(name, shell):
|
||||
'''
|
||||
Change the default shell of the user
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chshell foo /bin/zsh
|
||||
'''
|
||||
pre_info = info(name)
|
||||
if shell == pre_info['shell']:
|
||||
return True
|
||||
cmd = 'usermod -s {0} {1}'.format(shell, name)
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['shell'] != pre_info['shell']:
|
||||
return post_info['shell'] == shell
|
||||
return False
|
||||
|
||||
|
||||
def chhome(name, home, persist=False):
|
||||
'''
|
||||
Change the home directory of the user, pass true for persist to copy files
|
||||
to the new home dir
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chhome foo /home/users/foo True
|
||||
'''
|
||||
pre_info = info(name)
|
||||
if home == pre_info['home']:
|
||||
return True
|
||||
cmd = 'usermod -d {0} '.format(home)
|
||||
if persist:
|
||||
cmd += ' -m '
|
||||
cmd += name
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['home'] != pre_info['home']:
|
||||
return post_info['home'] == home
|
||||
return False
|
||||
|
||||
|
||||
def chgroups(name, groups, append=False):
|
||||
'''
|
||||
Change the groups this user belongs to, add append to append the specified
|
||||
groups
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chgroups foo wheel,root True
|
||||
'''
|
||||
if isinstance(groups, string_types):
|
||||
groups = groups.split(',')
|
||||
ugrps = set(list_groups(name))
|
||||
if ugrps == set(groups):
|
||||
return True
|
||||
if append:
|
||||
groups += ugrps
|
||||
cmd = 'usermod -G {0} {1} '.format(','.join(groups), name)
|
||||
return not __salt__['cmd.retcode'](cmd)
|
||||
|
||||
|
||||
def chfullname(name, fullname):
|
||||
'''
|
||||
Change the user's Full Name
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chfullname foo "Foo Bar"
|
||||
'''
|
||||
fullname = str(fullname)
|
||||
pre_info = _get_gecos(name)
|
||||
if not pre_info: return False
|
||||
if fullname == pre_info['fullname']:
|
||||
return True
|
||||
gecos_field = deepcopy(pre_info)
|
||||
gecos_field['fullname'] = fullname
|
||||
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['fullname'] != pre_info['fullname']:
|
||||
return post_info['fullname'] == fullname
|
||||
return False
|
||||
|
||||
|
||||
def chroomnumber(name, roomnumber):
|
||||
'''
|
||||
Change the user's Room Number
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chroomnumber foo 123
|
||||
'''
|
||||
roomnumber = str(roomnumber)
|
||||
pre_info = _get_gecos(name)
|
||||
if not pre_info: return False
|
||||
if roomnumber == pre_info['roomnumber']:
|
||||
return True
|
||||
gecos_field = deepcopy(pre_info)
|
||||
gecos_field['roomnumber'] = roomnumber
|
||||
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['roomnumber'] != pre_info['roomnumber']:
|
||||
return post_info['roomnumber'] == roomnumber
|
||||
return False
|
||||
|
||||
|
||||
def chworkphone(name, workphone):
|
||||
'''
|
||||
Change the user's Work Phone
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chworkphone foo "7735550123"
|
||||
'''
|
||||
workphone = str(workphone)
|
||||
pre_info = _get_gecos(name)
|
||||
if not pre_info: return False
|
||||
if workphone == pre_info['workphone']:
|
||||
return True
|
||||
gecos_field = deepcopy(pre_info)
|
||||
gecos_field['workphone'] = workphone
|
||||
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['workphone'] != pre_info['workphone']:
|
||||
return post_info['workphone'] == workphone
|
||||
return False
|
||||
|
||||
|
||||
def chhomephone(name, homephone):
|
||||
'''
|
||||
Change the user's Home Phone
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chhomephone foo "7735551234"
|
||||
'''
|
||||
homephone = str(homephone)
|
||||
pre_info = _get_gecos(name)
|
||||
if not pre_info: return False
|
||||
if homephone == pre_info['homephone']:
|
||||
return True
|
||||
gecos_field = deepcopy(pre_info)
|
||||
gecos_field['homephone'] = homephone
|
||||
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['homephone'] != pre_info['homephone']:
|
||||
return post_info['homephone'] == homephone
|
||||
return False
|
||||
|
||||
|
||||
def info(name):
|
||||
'''
|
||||
Return user information
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.info root
|
||||
'''
|
||||
ret = {}
|
||||
try:
|
||||
data = pwd.getpwnam(name)
|
||||
ret['gid'] = data.pw_gid
|
||||
ret['groups'] = list_groups(name)
|
||||
ret['home'] = data.pw_dir
|
||||
ret['name'] = data.pw_name
|
||||
ret['passwd'] = data.pw_passwd
|
||||
ret['shell'] = data.pw_shell
|
||||
ret['uid'] = data.pw_uid
|
||||
# Put GECOS info into a list
|
||||
gecos_field = data.pw_gecos.split(',', 3)
|
||||
# Assign empty strings for any unspecified GECOS fields
|
||||
while len(gecos_field) < 4: gecos_field.append('')
|
||||
ret['fullname'] = gecos_field[0]
|
||||
ret['roomnumber'] = gecos_field[1]
|
||||
ret['workphone'] = gecos_field[2]
|
||||
ret['homephone'] = gecos_field[3]
|
||||
except KeyError:
|
||||
ret['gid'] = ''
|
||||
ret['groups'] = ''
|
||||
ret['home'] = ''
|
||||
ret['name'] = ''
|
||||
ret['passwd'] = ''
|
||||
ret['shell'] = ''
|
||||
ret['uid'] = ''
|
||||
ret['fullname'] = ''
|
||||
ret['roomnumber'] = ''
|
||||
ret['workphone'] = ''
|
||||
ret['homephone'] = ''
|
||||
return ret
|
||||
|
||||
|
||||
def list_groups(name):
|
||||
'''
|
||||
Return a list of groups the named user belongs to
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.list_groups foo
|
||||
'''
|
||||
ugrp = set()
|
||||
# Add the primary user's group
|
||||
ugrp.add(grp.getgrgid(pwd.getpwnam(name).pw_gid).gr_name)
|
||||
# Now, all other groups the user belongs to
|
||||
for group in grp.getgrall():
|
||||
if name in group.gr_mem:
|
||||
ugrp.add(group.gr_name)
|
||||
|
||||
return sorted(list(ugrp))
|
342
salt/modules/solarispkg.py
Normal file
342
salt/modules/solarispkg.py
Normal file
|
@ -0,0 +1,342 @@
|
|||
'''
|
||||
Package support for Solaris
|
||||
'''
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import shutil
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Set the virtual pkg module if the os is Solaris
|
||||
'''
|
||||
if __grains__['os'] == 'Solaris':
|
||||
return 'pkg'
|
||||
return False
|
||||
|
||||
|
||||
def _list_removed(old, new):
|
||||
'''
|
||||
List the packages which have been removed between the two package objects
|
||||
'''
|
||||
pkgs = []
|
||||
for pkg in old:
|
||||
if pkg not in new:
|
||||
pkgs.append(pkg)
|
||||
return pkgs
|
||||
|
||||
|
||||
def _compare_versions(old, new):
|
||||
'''
|
||||
Returns a dict that that displays old and new versions for a package after
|
||||
install/upgrade of package.
|
||||
'''
|
||||
pkgs = {}
|
||||
for npkg in new:
|
||||
if npkg in old:
|
||||
if old[npkg] == new[npkg]:
|
||||
# no change in the package
|
||||
continue
|
||||
else:
|
||||
# the package was here before and the version has changed
|
||||
pkgs[npkg] = {'old': old[npkg],
|
||||
'new': new[npkg]}
|
||||
else:
|
||||
# the package is freshly installed
|
||||
pkgs[npkg] = {'old': '',
|
||||
'new': new[npkg]}
|
||||
return pkgs
|
||||
|
||||
|
||||
def _get_pkgs():
|
||||
'''
|
||||
Get a full list of the package installed on the machine
|
||||
'''
|
||||
pkg = {}
|
||||
cmd = '/usr/bin/pkginfo -x'
|
||||
|
||||
line_count = 0
|
||||
for line in __salt__['cmd.run'](cmd).split('\n'):
|
||||
if line_count % 2 == 0:
|
||||
namever = line.split()[0].strip()
|
||||
if line_count % 2 == 1:
|
||||
pkg[namever] = line.split()[1].strip()
|
||||
line_count = line_count + 1
|
||||
return pkg
|
||||
|
||||
|
||||
def list_pkgs():
|
||||
'''
|
||||
List the packages currently installed as a dict::
|
||||
|
||||
{'<package_name>': '<version>'}
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' pkg.list_pkgs
|
||||
'''
|
||||
return _get_pkgs()
|
||||
|
||||
|
||||
def version(name):
|
||||
'''
|
||||
Returns a version if the package is installed, else returns an empty string
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' pkg.version <package name>
|
||||
'''
|
||||
cmd = '/usr/bin/pkgparam {0} VERSION 2> /dev/null'.format(name)
|
||||
namever = __salt__['cmd.run'](cmd)
|
||||
if namever:
|
||||
return namever
|
||||
return ''
|
||||
|
||||
|
||||
def available_version(name):
|
||||
'''
|
||||
The available version of the package in the repository
|
||||
On Solaris with the pkg module this always returns the
|
||||
version that is installed since pkgadd does not have
|
||||
the concept of a repository.
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' pkg.available_version <package name>
|
||||
'''
|
||||
return version(name)
|
||||
|
||||
|
||||
def install(name, refresh=False, **kwargs):
|
||||
'''
|
||||
Install the passed package. Can install packages from the following sources::
|
||||
|
||||
* Locally (package already exists on the minion
|
||||
* HTTP/HTTPS server
|
||||
* FTP server
|
||||
* Salt master
|
||||
|
||||
Returns a dict containing the new package names and versions::
|
||||
|
||||
{'<package>': {'old': '<old-version>',
|
||||
'new': '<new-version>']}
|
||||
|
||||
CLI Example, installing a datastream pkg that already exists on the minion::
|
||||
|
||||
salt '*' pkg.install <package name once installed> source=/dir/on/minion/<package filename>
|
||||
salt '*' pkg.install SMClgcc346 source=/var/spool/pkg/gcc-3.4.6-sol10-sparc-local.pkg
|
||||
|
||||
CLI Example, installing a datastream pkg that exists on the salt master::
|
||||
|
||||
salt '*' pkg.install <package name once installed> source='salt://srv/salt/pkgs/<package filename>'
|
||||
salt '*' pkg.install SMClgcc346 source='salt://srv/salt/pkgs/gcc-3.4.6-sol10-sparc-local.pkg'
|
||||
|
||||
CLI Example, installing a datastream pkg that exists on a HTTP server::
|
||||
|
||||
salt '*' pkg.install <package name once installed> source='http://packages.server.com/<package filename>'
|
||||
salt '*' pkg.install SMClgcc346 source='http://packages.server.com/gcc-3.4.6-sol10-sparc-local.pkg'
|
||||
|
||||
By default salt automatically provides an adminfile, to automate package installation, with these options set:
|
||||
|
||||
email=
|
||||
instance=quit
|
||||
partial=nocheck
|
||||
runlevel=nocheck
|
||||
idepend=nocheck
|
||||
rdepend=nocheck
|
||||
space=nocheck
|
||||
setuid=nocheck
|
||||
conflict=nocheck
|
||||
action=nocheck
|
||||
basedir=default
|
||||
|
||||
You can override any of these options in two ways. First you can optionally pass any of
|
||||
the options as a kwarg to the module/state to override the default value or you can
|
||||
optionally pass the 'admin_source' option providing your own adminfile to the minions.
|
||||
|
||||
Note: You can find all of the possible options to provide to the adminfile by reading the admin man page:
|
||||
|
||||
man -s 4 admin
|
||||
|
||||
CLI Example - Overriding the 'instance' adminfile option when calling the module directly:
|
||||
|
||||
salt '*' pkg.install <package name once installed> source='salt://srv/salt/pkgs/<package filename>' instance="overwrite"
|
||||
|
||||
CLI Example - Overriding the 'instance' adminfile option when used in a state:
|
||||
|
||||
SMClgcc346:
|
||||
pkg.installed:
|
||||
- source: salt://srv/salt/pkgs/gcc-3.4.6-sol10-sparc-local.pkg
|
||||
- instance: overwrite
|
||||
|
||||
CLI Example - Providing your own adminfile when calling the module directly:
|
||||
|
||||
salt '*' pkg.install <package name once installed> source='salt://srv/salt/pkgs/<package filename>' admin_source='salt://srv/salt/pkgs/<adminfile filename>'
|
||||
|
||||
CLI Example - Providing your own adminfile when using states:
|
||||
|
||||
<package name once installed>:
|
||||
pkg.installed:
|
||||
- source: salt://srv/salt/pkgs/<package filename>
|
||||
- admin_source: salt://srv/salt/pkgs/<adminfile filename>
|
||||
'''
|
||||
|
||||
if not 'source' in kwargs:
|
||||
return 'source option required with solaris pkg installs'
|
||||
else:
|
||||
if (kwargs['source']).startswith('salt://') or (kwargs['source']).startswith('http://') or (kwargs['source']).startswith('https://') or (kwargs['source']).startswith('ftp://'):
|
||||
pkgname = __salt__['cp.cache_file'](kwargs['source'])
|
||||
else:
|
||||
pkgname = (kwargs['source'])
|
||||
|
||||
if 'admin_source' in kwargs:
|
||||
adminfile = __salt__['cp.cache_file'](kwargs['admin_source'])
|
||||
else:
|
||||
# Set the adminfile default variables
|
||||
email=kwargs.get('email', '')
|
||||
instance=kwargs.get('instance', 'quit')
|
||||
partial=kwargs.get('partial', 'nocheck')
|
||||
runlevel=kwargs.get('runlevel', 'nocheck')
|
||||
idepend=kwargs.get('idepend', 'nocheck')
|
||||
rdepend=kwargs.get('rdepend', 'nocheck')
|
||||
space=kwargs.get('space', 'nocheck')
|
||||
setuid=kwargs.get('setuid', 'nocheck')
|
||||
conflict=kwargs.get('conflict', 'nocheck')
|
||||
action=kwargs.get('action', 'nocheck')
|
||||
basedir=kwargs.get('basedir', 'default')
|
||||
|
||||
# Make tempfile to hold the adminfile contents.
|
||||
fd, adminfile = tempfile.mkstemp(prefix="salt-")
|
||||
|
||||
# Write to file then close it.
|
||||
os.write(fd, "email=%s\n" % email)
|
||||
os.write(fd, "instance=%s\n" % instance)
|
||||
os.write(fd, "partial=%s\n" % partial)
|
||||
os.write(fd, "runlevel=%s\n" % runlevel)
|
||||
os.write(fd, "idepend=%s\n" % idepend)
|
||||
os.write(fd, "rdepend=%s\n" % rdepend)
|
||||
os.write(fd, "space=%s\n" % space)
|
||||
os.write(fd, "setuid=%s\n" % setuid)
|
||||
os.write(fd, "conflict=%s\n" % conflict)
|
||||
os.write(fd, "action=%s\n" % action)
|
||||
os.write(fd, "basedir=%s\n" % basedir)
|
||||
os.close(fd)
|
||||
|
||||
# Get a list of the packages before install so we can diff after to see
|
||||
# what got installed.
|
||||
old = _get_pkgs()
|
||||
|
||||
# Install the package
|
||||
cmd = '/usr/sbin/pkgadd -n -a {0} -d {1} \'all\''.format(adminfile, pkgname)
|
||||
__salt__['cmd.retcode'](cmd)
|
||||
|
||||
# Get a list of the packages again, including newly installed ones.
|
||||
new = _get_pkgs()
|
||||
|
||||
# Remove the temp adminfile
|
||||
if not 'admin_source' in kwargs:
|
||||
os.unlink(adminfile)
|
||||
|
||||
# Return a list of the new package installed.
|
||||
return _compare_versions(old, new)
|
||||
|
||||
|
||||
def remove(name, **kwargs):
|
||||
'''
|
||||
Remove a single package with pkgrm
|
||||
|
||||
By default salt automatically provides an adminfile, to automate package removal, with these options set:
|
||||
|
||||
email=
|
||||
instance=quit
|
||||
partial=nocheck
|
||||
runlevel=nocheck
|
||||
idepend=nocheck
|
||||
rdepend=nocheck
|
||||
space=nocheck
|
||||
setuid=nocheck
|
||||
conflict=nocheck
|
||||
action=nocheck
|
||||
basedir=default
|
||||
|
||||
You can override any of these options in two ways. First you can optionally pass any of
|
||||
the options as a kwarg to the module/state to override the default value or you can
|
||||
optionally pass the 'admin_source' option providing your own adminfile to the minions.
|
||||
|
||||
Note: You can find all of the possible options to provide to the adminfile by reading the admin man page:
|
||||
|
||||
man -s 4 admin
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' pkg.remove <package name>
|
||||
salt '*' pkg.remove SUNWgit
|
||||
'''
|
||||
|
||||
# Check to see if the package is installed before we proceed
|
||||
if version(name) == '':
|
||||
return ''
|
||||
|
||||
if 'admin_source' in kwargs:
|
||||
adminfile = __salt__['cp.cache_file'](kwargs['admin_source'])
|
||||
else:
|
||||
# Set the adminfile default variables
|
||||
email=kwargs.get('email', '')
|
||||
instance=kwargs.get('instance', 'quit')
|
||||
partial=kwargs.get('partial', 'nocheck')
|
||||
runlevel=kwargs.get('runlevel', 'nocheck')
|
||||
idepend=kwargs.get('idepend', 'nocheck')
|
||||
rdepend=kwargs.get('rdepend', 'nocheck')
|
||||
space=kwargs.get('space', 'nocheck')
|
||||
setuid=kwargs.get('setuid', 'nocheck')
|
||||
conflict=kwargs.get('conflict', 'nocheck')
|
||||
action=kwargs.get('action', 'nocheck')
|
||||
basedir=kwargs.get('basedir', 'default')
|
||||
|
||||
# Make tempfile to hold the adminfile contents.
|
||||
fd, adminfile = tempfile.mkstemp(prefix="salt-")
|
||||
|
||||
# Write to file then close it.
|
||||
os.write(fd, "email=%s\n" % email)
|
||||
os.write(fd, "instance=%s\n" % instance)
|
||||
os.write(fd, "partial=%s\n" % partial)
|
||||
os.write(fd, "runlevel=%s\n" % runlevel)
|
||||
os.write(fd, "idepend=%s\n" % idepend)
|
||||
os.write(fd, "rdepend=%s\n" % rdepend)
|
||||
os.write(fd, "space=%s\n" % space)
|
||||
os.write(fd, "setuid=%s\n" % setuid)
|
||||
os.write(fd, "conflict=%s\n" % conflict)
|
||||
os.write(fd, "action=%s\n" % action)
|
||||
os.write(fd, "basedir=%s\n" % basedir)
|
||||
os.close(fd)
|
||||
|
||||
# Get a list of the currently installed pkgs.
|
||||
old = _get_pkgs()
|
||||
|
||||
# Remove the package
|
||||
cmd = '/usr/sbin/pkgrm -n -a {0} {1}'.format(adminfile, name)
|
||||
__salt__['cmd.retcode'](cmd)
|
||||
|
||||
# Remove the temp adminfile
|
||||
if not 'admin_source' in kwargs:
|
||||
os.unlink(adminfile)
|
||||
|
||||
# Get a list of the packages after the uninstall
|
||||
new = _get_pkgs()
|
||||
|
||||
# Compare the pre and post remove package objects and report the uninstalled pkgs.
|
||||
return _list_removed(old, new)
|
||||
|
||||
|
||||
def purge(name, **kwargs):
|
||||
'''
|
||||
Remove a single package with pkgrm
|
||||
|
||||
Returns a list containing the removed packages.
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' pkg.purge <package name>
|
||||
'''
|
||||
return remove(name, **kwargs)
|
|
@ -6,6 +6,7 @@ import re
|
|||
# Import Salt libs
|
||||
import salt.utils
|
||||
|
||||
LOCAL_CONFIG_PATH = '/etc/systemd/system'
|
||||
VALID_UNIT_TYPES = ['service','socket', 'device', 'mount', 'automount',
|
||||
'swap', 'target', 'path', 'timer']
|
||||
|
||||
|
@ -20,14 +21,29 @@ def __virtual__():
|
|||
return False
|
||||
|
||||
|
||||
def _canonical_unit_name(name):
|
||||
'''
|
||||
Build a canonical unit name treating unit names without one
|
||||
of the valid suffixes as a service.
|
||||
'''
|
||||
if any(name.endswith(suffix) for suffix in VALID_UNIT_TYPES):
|
||||
return name
|
||||
return '{0}.service'.format(name)
|
||||
|
||||
|
||||
def _canonical_template_unit_name(name):
|
||||
'''
|
||||
Build a canonical unit name for unit instances based on templates.
|
||||
'''
|
||||
return re.sub(r'@.+?(\.|$)', r'@\1', name)
|
||||
|
||||
|
||||
def _systemctl_cmd(action, name):
|
||||
'''
|
||||
Build a systemctl command line. Treat unit names without one
|
||||
of the valid suffixes as a service.
|
||||
'''
|
||||
if not any(name.endswith(suffix) for suffix in VALID_UNIT_TYPES):
|
||||
name += '.service'
|
||||
return 'systemctl {0} {1}'.format(action, name)
|
||||
return 'systemctl {0} {1}'.format(action, _canonical_unit_name(name))
|
||||
|
||||
def _get_all_unit_files():
|
||||
'''
|
||||
|
@ -38,7 +54,9 @@ def _get_all_unit_files():
|
|||
'|'.join(VALID_UNIT_TYPES) +
|
||||
')\s+(?P<state>.+)$')
|
||||
|
||||
out = __salt__['cmd.run_stdout']('systemctl --no-legend list-unit-files | col -b')
|
||||
out = __salt__['cmd.run_stdout'](
|
||||
'systemctl --full list-unit-files | col -b'
|
||||
)
|
||||
|
||||
ret = {}
|
||||
for match in rexp.finditer(out):
|
||||
|
@ -90,6 +108,14 @@ def get_all():
|
|||
return sorted(_get_all_unit_files().keys())
|
||||
|
||||
|
||||
def available(name):
|
||||
'''
|
||||
Check that the given service is available taking into account
|
||||
template units.
|
||||
'''
|
||||
return _canonical_template_unit_name(name) in get_all()
|
||||
|
||||
|
||||
def start(name):
|
||||
'''
|
||||
Start the specified service with systemd
|
||||
|
@ -178,6 +204,24 @@ def disable(name):
|
|||
return not __salt__['cmd.retcode'](_systemctl_cmd('disable', name))
|
||||
|
||||
|
||||
def _templated_instance_enabled(name):
|
||||
'''
|
||||
Services instantiated based on templates can not be checked with
|
||||
systemctl is-enabled. Presence of the actual symlinks is checked
|
||||
as a fall-back.
|
||||
'''
|
||||
if '@' not in name:
|
||||
return False
|
||||
find_unit_by_name = 'find {0} -name {1} -type l -print -quit'
|
||||
return len(__salt__['cmd.run'](find_unit_by_name.format(LOCAL_CONFIG_PATH,
|
||||
_canonical_unit_name(name))))
|
||||
|
||||
|
||||
def _enabled(name):
|
||||
is_enabled = not bool(__salt__['cmd.retcode'](_systemctl_cmd('is-enabled', name)))
|
||||
return is_enabled or _templated_instance_enabled(name)
|
||||
|
||||
|
||||
def enabled(name):
|
||||
'''
|
||||
Return if the named service is enabled to start on boot
|
||||
|
@ -186,7 +230,7 @@ def enabled(name):
|
|||
|
||||
salt '*' service.enabled <service name>
|
||||
'''
|
||||
return not __salt__['cmd.retcode'](_systemctl_cmd('is-enabled', name))
|
||||
return _enabled(name)
|
||||
|
||||
|
||||
def disabled(name):
|
||||
|
@ -197,4 +241,4 @@ def disabled(name):
|
|||
|
||||
salt '*' service.disabled <service name>
|
||||
'''
|
||||
return bool(__salt__['cmd.retcode'](_systemctl_cmd('is-enabled', name)))
|
||||
return not _enabled(name)
|
||||
|
|
|
@ -7,8 +7,13 @@ try:
|
|||
except ImportError:
|
||||
pass
|
||||
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
|
||||
from salt._compat import string_types, callable
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
|
@ -25,19 +30,45 @@ def __virtual__():
|
|||
return 'user' if __grains__['kernel'] in ('Linux', 'Darwin') else False
|
||||
|
||||
|
||||
def _get_gecos(name):
|
||||
'''
|
||||
Retrieve GECOS field info and return it in dictionary form
|
||||
'''
|
||||
gecos_field = pwd.getpwnam(name).pw_gecos.split(',', 3)
|
||||
if not gecos_field:
|
||||
return {}
|
||||
else:
|
||||
# Assign empty strings for any unspecified trailing GECOS fields
|
||||
while len(gecos_field) < 4: gecos_field.append('')
|
||||
return {'fullname': str(gecos_field[0]),
|
||||
'roomnumber': str(gecos_field[1]),
|
||||
'workphone': str(gecos_field[2]),
|
||||
'homephone': str(gecos_field[3])}
|
||||
|
||||
|
||||
def _build_gecos(gecos_dict):
|
||||
'''
|
||||
Accepts a dictionary entry containing GECOS field names and their values,
|
||||
and returns a full GECOS comment string, to be used with usermod.
|
||||
'''
|
||||
return '{0},{1},{2},{3}'.format(gecos_dict.get('fullname',''),
|
||||
gecos_dict.get('roomnumber',''),
|
||||
gecos_dict.get('workphone',''),
|
||||
gecos_dict.get('homephone',''))
|
||||
|
||||
|
||||
def add(name,
|
||||
uid=None,
|
||||
gid=None,
|
||||
groups=None,
|
||||
home=True,
|
||||
shell=None,
|
||||
fullname=None,
|
||||
roomnumber=None,
|
||||
workphone=None,
|
||||
homephone=None,
|
||||
other=None,
|
||||
unique=True,
|
||||
system=False):
|
||||
system=False,
|
||||
fullname='',
|
||||
roomnumber='',
|
||||
workphone='',
|
||||
homephone=''):
|
||||
'''
|
||||
Add a user to the minion
|
||||
|
||||
|
@ -90,8 +121,6 @@ def add(name,
|
|||
chworkphone(name, workphone)
|
||||
if homephone:
|
||||
chhomephone(name, homephone)
|
||||
if other:
|
||||
chother(name, other)
|
||||
return True
|
||||
|
||||
|
||||
|
@ -233,16 +262,20 @@ def chgroups(name, groups, append=False):
|
|||
|
||||
def chfullname(name, fullname):
|
||||
'''
|
||||
Change the users Full Name
|
||||
Change the user's Full Name
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chfullname foo "Foo Bar"
|
||||
'''
|
||||
pre_info = info(name)
|
||||
fullname = str(fullname)
|
||||
pre_info = _get_gecos(name)
|
||||
if not pre_info: return False
|
||||
if fullname == pre_info['fullname']:
|
||||
return True
|
||||
cmd = 'chfn -f "{0}" {1}'.format(fullname, name)
|
||||
gecos_field = deepcopy(pre_info)
|
||||
gecos_field['fullname'] = fullname
|
||||
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['fullname'] != pre_info['fullname']:
|
||||
|
@ -258,10 +291,14 @@ def chroomnumber(name, roomnumber):
|
|||
|
||||
salt '*' user.chroomnumber foo 123
|
||||
'''
|
||||
pre_info = info(name)
|
||||
roomnumber = str(roomnumber)
|
||||
pre_info = _get_gecos(name)
|
||||
if not pre_info: return False
|
||||
if roomnumber == pre_info['roomnumber']:
|
||||
return True
|
||||
cmd = 'chfn -r "{0}" {1}'.format(roomnumber, name)
|
||||
gecos_field = deepcopy(pre_info)
|
||||
gecos_field['roomnumber'] = roomnumber
|
||||
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['roomnumber'] != pre_info['roomnumber']:
|
||||
|
@ -277,10 +314,14 @@ def chworkphone(name, workphone):
|
|||
|
||||
salt '*' user.chworkphone foo "7735550123"
|
||||
'''
|
||||
pre_info = info(name)
|
||||
workphone = str(workphone)
|
||||
pre_info = _get_gecos(name)
|
||||
if not pre_info: return False
|
||||
if workphone == pre_info['workphone']:
|
||||
return True
|
||||
cmd = 'chfn -w "{0}" {1}'.format(workphone, name)
|
||||
gecos_field = deepcopy(pre_info)
|
||||
gecos_field['workphone'] = workphone
|
||||
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['workphone'] != pre_info['workphone']:
|
||||
|
@ -296,10 +337,14 @@ def chhomephone(name, homephone):
|
|||
|
||||
salt '*' user.chhomephone foo "7735551234"
|
||||
'''
|
||||
pre_info = info(name)
|
||||
homephone = str(homephone)
|
||||
pre_info = _get_gecos(name)
|
||||
if not pre_info: return False
|
||||
if homephone == pre_info['homephone']:
|
||||
return True
|
||||
cmd = 'chfn -h "{0}" {1}'.format(homephone, name)
|
||||
gecos_field = deepcopy(pre_info)
|
||||
gecos_field['homephone'] = homephone
|
||||
cmd = 'usermod -c "{0}" {1}'.format(_build_gecos(gecos_field), name)
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['homephone'] != pre_info['homephone']:
|
||||
|
@ -307,25 +352,6 @@ def chhomephone(name, homephone):
|
|||
return False
|
||||
|
||||
|
||||
def chother(name, other):
|
||||
'''
|
||||
Change the user's "Other" GECOS field
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' user.chother foo "fax=7735555678"
|
||||
'''
|
||||
pre_info = info(name)
|
||||
if other == pre_info['other']:
|
||||
return True
|
||||
cmd = 'chfn -o "{0}" {1}'.format(other, name)
|
||||
__salt__['cmd.run'](cmd)
|
||||
post_info = info(name)
|
||||
if post_info['other'] != pre_info['other']:
|
||||
return post_info['other'] == other
|
||||
return False
|
||||
|
||||
|
||||
def info(name):
|
||||
'''
|
||||
Return user information
|
||||
|
@ -345,15 +371,13 @@ def info(name):
|
|||
ret['shell'] = data.pw_shell
|
||||
ret['uid'] = data.pw_uid
|
||||
# Put GECOS info into a list
|
||||
gecos_field = data.pw_gecos.split(',', 4)
|
||||
gecos_field = data.pw_gecos.split(',', 3)
|
||||
# Assign empty strings for any unspecified GECOS fields
|
||||
while len(gecos_field) < 5:
|
||||
gecos_field.append('')
|
||||
while len(gecos_field) < 4: gecos_field.append('')
|
||||
ret['fullname'] = gecos_field[0]
|
||||
ret['roomnumber'] = gecos_field[1]
|
||||
ret['workphone'] = gecos_field[2]
|
||||
ret['homephone'] = gecos_field[3]
|
||||
ret['other'] = gecos_field[4]
|
||||
except KeyError:
|
||||
ret['gid'] = ''
|
||||
ret['groups'] = ''
|
||||
|
@ -366,7 +390,6 @@ def info(name):
|
|||
ret['roomnumber'] = ''
|
||||
ret['workphone'] = ''
|
||||
ret['homephone'] = ''
|
||||
ret['other'] = ''
|
||||
return ret
|
||||
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ def list_upgrades(*args):
|
|||
for pkg in pkgs:
|
||||
exactmatch, matched, unmatched = yum.packages.parsePackages(pl, [pkg])
|
||||
for pkg in exactmatch:
|
||||
if pkg.arch == getBaseArch():
|
||||
if pkg.arch == getBaseArch() or pkg.arch == 'noarch':
|
||||
versions_list[pkg['name']] = '-'.join([pkg['version'],pkg['release']])
|
||||
return versions_list
|
||||
|
||||
|
@ -114,7 +114,7 @@ def available_version(name):
|
|||
# installed. Maybe we should just return the value if we get a hit
|
||||
# on available, and only iterate though updates if we don't..
|
||||
for pkg in exactmatch:
|
||||
if pkg.arch == getBaseArch():
|
||||
if pkg.arch == getBaseArch() or pkg.arch == 'noarch':
|
||||
versions_list.append('-'.join([pkg.version, pkg.release]))
|
||||
|
||||
if len(versions_list) == 0:
|
||||
|
|
|
@ -339,6 +339,8 @@ class Pillar(object):
|
|||
pillar, errors = self.render_pillar(matches)
|
||||
pillar.update(self.ext_pillar())
|
||||
errors.extend(terrors)
|
||||
if self.opts.get('pillar_opts', False):
|
||||
pillar['master'] = self.opts
|
||||
if errors:
|
||||
for error in errors:
|
||||
log.critical('Pillar render error: {0}'.format(error))
|
||||
|
|
|
@ -15,7 +15,7 @@ except ImportError:
|
|||
has_redis = False
|
||||
|
||||
__opts__ = {'redis.db': '0',
|
||||
'redis.host': 'mcp',
|
||||
'redis.host': 'redis',
|
||||
'redis.port': 6379}
|
||||
|
||||
|
||||
|
@ -33,7 +33,7 @@ def returner(ret):
|
|||
port=__opts__['redis.port'],
|
||||
db=__opts__['redis.db'])
|
||||
|
||||
serv.sadd("%(id)s:jobs" % ret, ret['jid'])
|
||||
serv.set("%(jid)s:%(id)s" % ret, json.dumps(ret['return']))
|
||||
serv.sadd('{0}:jobs'.format(ret['id']))
|
||||
serv.set('{0}:{1}'.format(ret['jid'], json.dumps(ret['return'])))
|
||||
serv.sadd('jobs', ret['jid'])
|
||||
serv.sadd(ret['jid'], ret['id'])
|
||||
|
|
|
@ -6,38 +6,55 @@ import sys
|
|||
|
||||
# Import salt modules
|
||||
import salt.loader
|
||||
import salt.exceptions
|
||||
|
||||
|
||||
class Runner(object):
|
||||
class RunnerClient(object):
|
||||
'''
|
||||
Execute the salt runner interface
|
||||
A client for accessing runners
|
||||
'''
|
||||
def __init__(self, opts):
|
||||
self.opts = opts
|
||||
self.functions = salt.loader.runner(opts)
|
||||
|
||||
def _verify_fun(self):
|
||||
def _verify_fun(self, fun):
|
||||
'''
|
||||
Verify an environmental issues
|
||||
Check that the function passed really exists
|
||||
'''
|
||||
if not self.opts['fun']:
|
||||
err = 'Must pass a runner function'
|
||||
sys.stderr.write('{0}\n'.format(err))
|
||||
sys.exit(1)
|
||||
if self.opts['fun'] not in self.functions:
|
||||
err = 'Passed function is unavailable'
|
||||
sys.stderr.write('{0}\n'.format(err))
|
||||
sys.exit(1)
|
||||
if fun not in self.functions:
|
||||
err = "Function '{0}' is unavailable".format(fun)
|
||||
raise salt.exceptions.CommandExecutionError(err)
|
||||
|
||||
def get_docs(self):
|
||||
'''
|
||||
Return a dictionary of functions and the inline documentation for each
|
||||
'''
|
||||
ret = [(fun, self.functions[fun].__doc__)
|
||||
for fun in sorted(self.functions)
|
||||
if fun.startswith(self.opts['fun'])]
|
||||
|
||||
return dict(ret)
|
||||
|
||||
def cmd(self, fun, arg):
|
||||
'''
|
||||
Execute a runner with the given arguments
|
||||
'''
|
||||
self._verify_fun(fun)
|
||||
# pylint: disable-msg=W0142
|
||||
return self.functions[fun](*arg)
|
||||
|
||||
class Runner(RunnerClient):
|
||||
'''
|
||||
Execute the salt runner interface
|
||||
'''
|
||||
def _print_docs(self):
|
||||
'''
|
||||
Print out the documentation!
|
||||
'''
|
||||
for fun in sorted(self.functions):
|
||||
if fun.startswith(self.opts['fun']):
|
||||
print('{0}:'.format(fun))
|
||||
print(self.functions[fun].__doc__)
|
||||
print('')
|
||||
ret = super(Runner, self).get_docs()
|
||||
|
||||
for fun, doc in ret.items():
|
||||
print("{0}:\n{1}\n".format(fun, doc))
|
||||
|
||||
def run(self):
|
||||
'''
|
||||
|
@ -46,5 +63,9 @@ class Runner(object):
|
|||
if self.opts.get('doc', False):
|
||||
self._print_docs()
|
||||
else:
|
||||
self._verify_fun()
|
||||
return self.functions[self.opts['fun']](*self.opts['arg'])
|
||||
try:
|
||||
return super(Runner, self).cmd(
|
||||
self.opts['fun'], self.opts['arg'])
|
||||
except salt.exceptions.SaltException as exc:
|
||||
sys.stderr.write('{0}\n'.format(exc))
|
||||
sys.exit(1)
|
||||
|
|
|
@ -58,41 +58,14 @@ def lookup_jid(jid):
|
|||
'''
|
||||
Return the printout from a previously executed job
|
||||
'''
|
||||
def _format_ret(full_ret):
|
||||
'''
|
||||
Take the full return data and format it to simple output
|
||||
'''
|
||||
out = None
|
||||
ret = {}
|
||||
for key, data in full_ret.items():
|
||||
ret[key] = data['ret']
|
||||
if 'out' in data:
|
||||
out = data['out']
|
||||
return ret, out
|
||||
|
||||
client = salt.client.LocalClient(__opts__['conf_file'])
|
||||
full_ret = client.get_full_returns(jid, [], 0)
|
||||
formatted = _format_ret(full_ret)
|
||||
|
||||
if all(formatted):
|
||||
ret = formatted[0]
|
||||
out = formatted[1]
|
||||
else:
|
||||
ret = SaltException(
|
||||
'Job {0} hasn\'t finished. No data yet :('.format(jid)
|
||||
)
|
||||
out = ''
|
||||
ret = {}
|
||||
for mid, data in client.get_full_returns(jid, [], 0).items():
|
||||
printout = salt.output.get_outputter(data.get('out', None))
|
||||
ret[mid] = data.get('ret')
|
||||
printout({mid: ret[mid]})
|
||||
|
||||
# Determine the proper output method and run it
|
||||
get_outputter = salt.output.get_outputter
|
||||
if isinstance(ret, (list, dict, string_types)) and out:
|
||||
printout = get_outputter(out)
|
||||
# Pretty print any salt exceptions
|
||||
elif isinstance(ret, SaltException):
|
||||
printout = get_outputter("txt")
|
||||
else:
|
||||
printout = get_outputter(None)
|
||||
printout(ret)
|
||||
return ret
|
||||
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import salt.fileclient
|
|||
from salt._compat import string_types, callable
|
||||
|
||||
from salt.template import compile_template, compile_template_str
|
||||
from salt.exceptions import SaltReqTimeoutError
|
||||
from salt.exceptions import SaltReqTimeoutError, SaltException
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -264,6 +264,24 @@ class State(object):
|
|||
elif data['state'] == 'pkg':
|
||||
_refresh()
|
||||
|
||||
def verify_ret(self, ret):
|
||||
'''
|
||||
Verify the state return data
|
||||
'''
|
||||
if not isinstance(ret, dict):
|
||||
raise SaltException(
|
||||
'Malformed state return, return must be a dict'
|
||||
)
|
||||
bad = []
|
||||
for val in ['name', 'result', 'changes', 'comment']:
|
||||
if not val in ret:
|
||||
bad.append(val)
|
||||
if bad:
|
||||
raise SaltException(
|
||||
('The following keys were not present in the state '
|
||||
'return: {0}'
|
||||
).format(','.join(bad)))
|
||||
|
||||
def verify_data(self, data):
|
||||
'''
|
||||
Verify the data, return an error statement if something is wrong
|
||||
|
@ -860,6 +878,7 @@ class State(object):
|
|||
*cdata['args'], **cdata['kwargs'])
|
||||
else:
|
||||
ret = self.states[cdata['full']](*cdata['args'])
|
||||
self.verify_ret(ret)
|
||||
except Exception:
|
||||
trb = traceback.format_exc()
|
||||
ret = {
|
||||
|
|
|
@ -245,7 +245,14 @@ def purged(name):
|
|||
|
||||
def mod_init(low):
|
||||
'''
|
||||
Refresh the package database here so that it only needs to happen once
|
||||
Set a flag to tell the install functions to refresh the package database.
|
||||
This ensures that the package database is refreshed only once durring
|
||||
a state run significaltly improving the speed of package management
|
||||
durring a state run.
|
||||
|
||||
It sets a flag for a number of reasons, primarily due to timeline logic.
|
||||
When originally setting up the mod_init for pkg a number of corner cases
|
||||
arose with different package managers and how they refresh package data.
|
||||
'''
|
||||
if low['fun'] == 'installed' or low['fun'] == 'latest':
|
||||
rtag = __gen_rtag()
|
||||
|
|
|
@ -187,6 +187,18 @@ def _disable(name, started):
|
|||
return ret
|
||||
|
||||
|
||||
def _available(name, ret):
|
||||
# Check if the service is available
|
||||
if 'service.available' in __salt__:
|
||||
ret['available'] = __salt__['service.available'](name)
|
||||
elif 'service.get_all' in __salt__:
|
||||
ret['available'] = name in __salt__['service.get_all']()
|
||||
if not ret.get('available', True):
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'The named service {0} is not available'.format(name)
|
||||
return ret
|
||||
|
||||
|
||||
def running(name, enable=None, sig=None):
|
||||
'''
|
||||
Verify that the service is running
|
||||
|
@ -216,15 +228,10 @@ def running(name, enable=None, sig=None):
|
|||
else:
|
||||
return ret
|
||||
|
||||
# Check if the service is available
|
||||
if 'service.get_all' in __salt__:
|
||||
# get_all is available, we can reliable check for the service
|
||||
services = __salt__['service.get_all']()
|
||||
if not name in services:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'The named service {0} is not available'.format(
|
||||
name)
|
||||
return ret
|
||||
# Check if the service is available:
|
||||
ret = _available(name, ret)
|
||||
if not ret.pop('available', True):
|
||||
return ret
|
||||
|
||||
# Run the tests
|
||||
if __opts__['test']:
|
||||
|
@ -282,15 +289,10 @@ def dead(name, enable=None, sig=None):
|
|||
else:
|
||||
return ret
|
||||
|
||||
# Check if the service is available
|
||||
if 'service.get_all' in __salt__:
|
||||
# get_all is available, we can reliable check for the service
|
||||
services = __salt__['service.get_all']()
|
||||
if not name in services:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'The named service {0} is not available'.format(
|
||||
name)
|
||||
return ret
|
||||
# Check if the service is available:
|
||||
ret = _available(name, ret)
|
||||
if not ret.pop('available', True):
|
||||
return ret
|
||||
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
|
|
|
@ -37,13 +37,11 @@ def _changes(
|
|||
password=None,
|
||||
enforce_password=True,
|
||||
shell=None,
|
||||
fullname=None,
|
||||
roomnumber=None,
|
||||
workphone=None,
|
||||
homephone=None,
|
||||
other=None,
|
||||
unique=True,
|
||||
):
|
||||
fullname='',
|
||||
roomnumber='',
|
||||
workphone='',
|
||||
homephone=''):
|
||||
'''
|
||||
Return a dict of the changes required for a user if the user is present,
|
||||
otherwise return False.
|
||||
|
@ -83,21 +81,16 @@ def _changes(
|
|||
lshad['pwd'] != '!' and enforce_password:
|
||||
if lshad['pwd'] != password:
|
||||
change['passwd'] = password
|
||||
if fullname:
|
||||
if lusr['fullname'] != fullname:
|
||||
change['fullname'] = fullname
|
||||
if roomnumber:
|
||||
if lusr['roomnumber'] != roomnumber:
|
||||
change['roomnumber'] = roomnumber
|
||||
if workphone:
|
||||
if lusr['workphone'] != workphone:
|
||||
change['workphone'] = workphone
|
||||
if homephone:
|
||||
if lusr['homephone'] != homephone:
|
||||
change['homephone'] = homephone
|
||||
if other:
|
||||
if lusr['other'] != other:
|
||||
change['other'] = other
|
||||
# GECOS fields
|
||||
if lusr['fullname'] != fullname:
|
||||
change['fullname'] = fullname
|
||||
if lusr['roomnumber'] != roomnumber:
|
||||
change['roomnumber'] = roomnumber
|
||||
if lusr['workphone'] != workphone:
|
||||
change['workphone'] = workphone
|
||||
if lusr['homephone'] != homephone:
|
||||
change['homephone'] = homephone
|
||||
|
||||
if not found:
|
||||
return False
|
||||
return change
|
||||
|
@ -114,14 +107,12 @@ def present(
|
|||
password=None,
|
||||
enforce_password=True,
|
||||
shell=None,
|
||||
fullname=None,
|
||||
roomnumber=None,
|
||||
workphone=None,
|
||||
homephone=None,
|
||||
other=None,
|
||||
unique=True,
|
||||
system=False,
|
||||
):
|
||||
fullname='',
|
||||
roomnumber='',
|
||||
workphone='',
|
||||
homephone=''):
|
||||
'''
|
||||
Ensure that the named user is present with the specified properties
|
||||
|
||||
|
@ -166,8 +157,14 @@ def present(
|
|||
shell
|
||||
The login shell, defaults to the system default shell
|
||||
|
||||
unique
|
||||
Require a unique UID, True by default
|
||||
|
||||
User comment field (GECOS) support (currently Linux-only):
|
||||
system
|
||||
Choose UID in the range of FIRST_SYSTEM_UID and LAST_SYSTEM_UID.
|
||||
|
||||
|
||||
User comment field (GECOS) support (currently Linux and FreeBSD only):
|
||||
|
||||
The below values should be specified as strings to avoid ambiguities when
|
||||
the values are loaded. (Especially the phone and room number fields which
|
||||
|
@ -184,15 +181,6 @@ def present(
|
|||
|
||||
homephone
|
||||
The user's home phone number
|
||||
|
||||
other
|
||||
The user's "other" GECOS field
|
||||
|
||||
unique
|
||||
Require a unique UID, True by default
|
||||
|
||||
system
|
||||
Choose UID in the range of FIRST_SYSTEM_UID and LAST_SYSTEM_UID.
|
||||
'''
|
||||
ret = {'name': name,
|
||||
'changes': {},
|
||||
|
@ -225,6 +213,11 @@ def present(
|
|||
log.warning('Group "{0}" specified in both groups and '
|
||||
'optional_groups for user {1}'.format(x,name))
|
||||
|
||||
if fullname is None: fullname = ''
|
||||
if roomnumber is None: roomnumber = ''
|
||||
if workphone is None: workphone = ''
|
||||
if homephone is None: homephone = ''
|
||||
|
||||
if gid_from_name:
|
||||
gid = __salt__['file.group_to_gid'](name)
|
||||
changes = _changes(
|
||||
|
@ -237,12 +230,11 @@ def present(
|
|||
password,
|
||||
enforce_password,
|
||||
shell,
|
||||
unique,
|
||||
fullname,
|
||||
roomnumber,
|
||||
workphone,
|
||||
homephone,
|
||||
other,
|
||||
unique)
|
||||
homephone)
|
||||
if changes:
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
|
@ -290,13 +282,12 @@ def present(
|
|||
groups=groups,
|
||||
home=home,
|
||||
shell=shell,
|
||||
unique=unique,
|
||||
system=system,
|
||||
fullname=fullname,
|
||||
roomnumber=roomnumber,
|
||||
workphone=workphone,
|
||||
homephone=homephone,
|
||||
other=other,
|
||||
unique=unique,
|
||||
system=system):
|
||||
homephone=homephone):
|
||||
ret['comment'] = 'New user {0} created'.format(name)
|
||||
ret['changes'] = __salt__['user.info'](name)
|
||||
if password:
|
||||
|
|
277
salt/utils/minions.py
Normal file
277
salt/utils/minions.py
Normal file
|
@ -0,0 +1,277 @@
|
|||
'''
|
||||
This module contains routines used to verify the matcher against the minions
|
||||
expected to return
|
||||
'''
|
||||
# Import Python libs
|
||||
import os
|
||||
import glob
|
||||
import fnmatch
|
||||
import re
|
||||
|
||||
# Import Salt libs
|
||||
import salt.payload
|
||||
|
||||
|
||||
def nodegroup_comp(group, nodegroups, skip=None):
|
||||
'''
|
||||
Take the nodegroup and the nodegroups and fill in nodegroup refs
|
||||
'''
|
||||
if skip is None:
|
||||
skip = set([group])
|
||||
if not group in nodegroups:
|
||||
return ''
|
||||
gstr = nodegroups[group]
|
||||
ret = ''
|
||||
for comp in gstr.split():
|
||||
if not comp.startswith('N@'):
|
||||
ret += '{0} '.format(comp)
|
||||
continue
|
||||
ngroup = comp[2:]
|
||||
if ngroup in skip:
|
||||
continue
|
||||
skip.add(ngroup)
|
||||
ret += nodegroup_comp(ngroup, nodegroups, skip)
|
||||
return ret
|
||||
|
||||
|
||||
class CkMinions(object):
|
||||
'''
|
||||
Used to check what minions should respond from a target
|
||||
'''
|
||||
def __init__(self, opts):
|
||||
self.opts = opts
|
||||
self.serial = salt.payload.Serial(opts)
|
||||
|
||||
def _check_glob_minions(self, expr):
|
||||
'''
|
||||
Return the minions found by looking via globs
|
||||
'''
|
||||
cwd = os.getcwd()
|
||||
os.chdir(os.path.join(self.opts['pki_dir'], 'minions'))
|
||||
ret = set(glob.glob(expr))
|
||||
os.chdir(cwd)
|
||||
return list(ret)
|
||||
|
||||
def _check_list_minions(self, expr):
|
||||
'''
|
||||
Return the minions found by looking via a list
|
||||
'''
|
||||
ret = []
|
||||
for fn_ in os.listdir(os.path.join(self.opts['pki_dir'], 'minions')):
|
||||
if fn_ in expr:
|
||||
if fn_ not in ret:
|
||||
ret.append(fn_)
|
||||
return ret
|
||||
|
||||
def _check_pcre_minions(self, expr):
|
||||
'''
|
||||
Return the minions found by looking via regular expressions
|
||||
'''
|
||||
ret = set()
|
||||
cwd = os.getcwd()
|
||||
os.chdir(os.path.join(self.opts['pki_dir'], 'minions'))
|
||||
reg = re.compile(expr)
|
||||
for fn_ in os.listdir('.'):
|
||||
if reg.match(fn_):
|
||||
ret.add(fn_)
|
||||
os.chdir(cwd)
|
||||
return list(ret)
|
||||
|
||||
def _check_grain_minions(self, expr):
|
||||
'''
|
||||
Return the minions found by looking via a list
|
||||
'''
|
||||
minions = set(os.listdir(os.path.join(self.opts['pki_dir'], 'minions')))
|
||||
if self.opts.get('minion_data_cache', False):
|
||||
cdir = os.path.join(self.opts['cachedir'], 'minions')
|
||||
if not os.path.isdir(cdir):
|
||||
return list(minions)
|
||||
for id_ in os.listdir(cdir):
|
||||
if not id_ in minions:
|
||||
continue
|
||||
datap = os.path.join(cdir, id_, 'data.p')
|
||||
if not os.path.isfile(datap):
|
||||
continue
|
||||
grains = self.serial.load(open(datap)).get('grains')
|
||||
comps = expr.split(':')
|
||||
if len(comps) < 2:
|
||||
continue
|
||||
if comps[0] not in grains:
|
||||
minions.remove(id_)
|
||||
continue
|
||||
if isinstance(grains.get(comps[0]), list):
|
||||
# We are matching a single component to a single list member
|
||||
found = False
|
||||
for member in grains[comps[0]]:
|
||||
if fnmatch.fnmatch(str(member).lower(), comps[1].lower()):
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
continue
|
||||
minions.remove(id_)
|
||||
continue
|
||||
if fnmatch.fnmatch(
|
||||
str(grains.get(comps[0], '').lower()),
|
||||
comps[1].lower(),
|
||||
):
|
||||
continue
|
||||
else:
|
||||
minions.remove(id_)
|
||||
return list(minions)
|
||||
|
||||
def _check_grain_pcre_minions(self, expr):
|
||||
'''
|
||||
Return the minions found by looking via a list
|
||||
'''
|
||||
minions = set(os.listdir(os.path.join(self.opts['pki_dir'], 'minions')))
|
||||
if self.opts.get('minion_data_cache', False):
|
||||
cdir = os.path.join(self.opts['cachedir'], 'minions')
|
||||
if not os.path.isdir(cdir):
|
||||
return list(minions)
|
||||
for id_ in os.listdir(cdir):
|
||||
if not id_ in minions:
|
||||
continue
|
||||
datap = os.path.join(cdir, id_, 'data.p')
|
||||
if not os.path.isfile(datap):
|
||||
continue
|
||||
grains = self.serial.load(open(datap)).get('grains')
|
||||
comps = expr.split(':')
|
||||
if len(comps) < 2:
|
||||
continue
|
||||
if comps[0] not in grains:
|
||||
minions.remove(id_)
|
||||
if isinstance(grains[comps[0]], list):
|
||||
# We are matching a single component to a single list member
|
||||
found = False
|
||||
for member in grains[comps[0]]:
|
||||
if re.match(comps[1].lower(), str(member).lower()):
|
||||
found = True
|
||||
if found:
|
||||
continue
|
||||
minions.remove(id_)
|
||||
continue
|
||||
if re.match(
|
||||
comps[1].lower(),
|
||||
str(grains[comps[0]]).lower()
|
||||
):
|
||||
continue
|
||||
else:
|
||||
minions.remove(id_)
|
||||
return list(minions)
|
||||
|
||||
def _all_minions(self, expr=None):
|
||||
'''
|
||||
Return a list of all minions that have auth'd
|
||||
'''
|
||||
return os.listdir(os.path.join(self.opts['pki_dir'], 'minions'))
|
||||
|
||||
def check_minions(self, expr, expr_form='glob'):
|
||||
'''
|
||||
Check the passed regex against the available minions' public keys
|
||||
stored for authentication. This should return a set of ids which
|
||||
match the regex, this will then be used to parse the returns to
|
||||
make sure everyone has checked back in.
|
||||
'''
|
||||
try:
|
||||
minions = {'glob': self._check_glob_minions,
|
||||
'pcre': self._check_pcre_minions,
|
||||
'list': self._check_list_minions,
|
||||
'grain': self._check_grain_minions,
|
||||
'grain_pcre': self._check_grain_pcre_minions,
|
||||
'exsel': self._all_minions,
|
||||
'pillar': self._all_minions,
|
||||
'compound': self._all_minions,
|
||||
}[expr_form](expr)
|
||||
except Exception:
|
||||
minions = expr
|
||||
return minions
|
||||
|
||||
def validate_tgt(self, valid, expr, expr_form):
|
||||
'''
|
||||
Return a Bool. This function returns if the expresion sent in is within
|
||||
the scope of the valid expression
|
||||
'''
|
||||
ref = {'G': 'grain',
|
||||
'P': 'grain_pcre',
|
||||
'X': 'exsel',
|
||||
'I': 'pillar',
|
||||
'L': 'list',
|
||||
'S': 'ipcidr',
|
||||
'E': 'pcre',
|
||||
'N': 'node'}
|
||||
infinate = [
|
||||
'node',
|
||||
'ipcidr',
|
||||
'exsel',
|
||||
'pillar',
|
||||
]
|
||||
if not self.opts.get('minion_data_cache', False):
|
||||
infinate.append('grain')
|
||||
infinate.append('grain_pcre')
|
||||
|
||||
if '@' in valid and valid[1] == '@':
|
||||
comps = valid.split('@')
|
||||
v_matcher = ref.get(comps[0])
|
||||
v_expr = comps[1]
|
||||
else:
|
||||
v_matcher = 'glob'
|
||||
v_expr = valid
|
||||
if v_matcher in infinate:
|
||||
# We can't be sure what the subset is, only match the identical
|
||||
# target
|
||||
if not v_matcher == expr_form:
|
||||
return False
|
||||
return v_expr == expr
|
||||
v_minions = set(self.check_minions(v_expr, v_matcher))
|
||||
minions = set(self.check_minions(expr, expr_form))
|
||||
d_bool = bool(minions.difference(v_minions))
|
||||
if len(v_minions) == len(minions) and not d_bool:
|
||||
return True
|
||||
return d_bool
|
||||
|
||||
def match_check(self, regex, fun):
|
||||
'''
|
||||
Validate a single regex to function comparison, the function argument
|
||||
can be a list of functions. It is all or nothing for a list of
|
||||
functions
|
||||
'''
|
||||
vals = []
|
||||
if isinstance(fun, str):
|
||||
fun = [fun]
|
||||
for func in fun:
|
||||
if re.match(regex, func):
|
||||
vals.append(True)
|
||||
else:
|
||||
vals.append(False)
|
||||
return all(vals)
|
||||
|
||||
def auth_check(self, auth_list, fun, tgt, tgt_type='glob'):
|
||||
'''
|
||||
Returns a bool which defines if the requested function is authorized.
|
||||
Used to evaluate the standard structure under external master
|
||||
authentication interfaces, like eauth, peer, peer_run, etc.
|
||||
'''
|
||||
for ind in auth_list:
|
||||
if isinstance(ind, str):
|
||||
# Allowed for all minions
|
||||
if self.match_check(ind, fun):
|
||||
return True
|
||||
elif isinstance(ind, dict):
|
||||
if len(ind) != 1:
|
||||
# Invalid argument
|
||||
continue
|
||||
valid = ind.keys()[0]
|
||||
# Check if minions are allowed
|
||||
if self.validate_tgt(
|
||||
valid,
|
||||
tgt,
|
||||
tgt_type):
|
||||
# Minions are allowed, verify function in allowed list
|
||||
if isinstance(ind[valid], str):
|
||||
if self.match_check(ind[valid], fun):
|
||||
return True
|
||||
elif isinstance(ind[valid], list):
|
||||
for regex in ind[valid]:
|
||||
if self.match_check(regex, fun):
|
||||
return True
|
||||
return False
|
|
@ -70,17 +70,19 @@ class OptionParserMeta(MixInMeta):
|
|||
|
||||
|
||||
class OptionParser(optparse.OptionParser):
|
||||
VERSION = version.__version__
|
||||
|
||||
usage = '%prog'
|
||||
|
||||
epilog = ('You can find additional help about %prog issuing "man %prog" '
|
||||
'or on http://docs.saltstack.org/en/latest/index.html')
|
||||
'or on http://docs.saltstack.org')
|
||||
description = None
|
||||
|
||||
# Private attributes
|
||||
_mixin_prio_ = 100
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('version', '%prog {0}'.format(version.__version__))
|
||||
kwargs.setdefault('version', '%prog {0}'.format(self.VERSION))
|
||||
kwargs.setdefault('usage', self.usage)
|
||||
if self.description:
|
||||
kwargs.setdefault('description', self.description)
|
||||
|
@ -688,7 +690,17 @@ class SaltCMDOptionParser(OptionParser, ConfigDirMixIn, TimeoutMixIn,
|
|||
'-a', '--auth', '--eauth', '--extended-auth',
|
||||
default='',
|
||||
dest='eauth',
|
||||
help=('Specify an extended authentication system to use.'))
|
||||
help=('Specify an extended authentication system to use.')
|
||||
)
|
||||
self.add_option(
|
||||
'-T', '--make-token',
|
||||
default=False,
|
||||
dest='mktoken',
|
||||
action='store_true',
|
||||
help=('Generate and save an authentication token for re-use. The'
|
||||
'token is generated and made available for the period '
|
||||
'defined in the Salt Master.')
|
||||
)
|
||||
self.add_option(
|
||||
'--return',
|
||||
default='',
|
||||
|
@ -753,7 +765,7 @@ class SaltCMDOptionParser(OptionParser, ConfigDirMixIn, TimeoutMixIn,
|
|||
self.config['arg'] = self.args[2:]
|
||||
|
||||
def setup_config(self):
|
||||
return config.master_config(self.get_config_file_path('master'))
|
||||
return config.client_config(self.get_config_file_path('master'))
|
||||
|
||||
|
||||
class SaltCPOptionParser(OptionParser, ConfigDirMixIn, TimeoutMixIn,
|
||||
|
|
|
@ -27,8 +27,7 @@ class ManageTest(integration.ShellCase):
|
|||
jobs.lookup_jid
|
||||
'''
|
||||
ret = self.run_run_plus('jobs.lookup_jid', '', '23974239742394')
|
||||
self.assertFalse(ret['fun'])
|
||||
self.assertFalse(ret['out'][1])
|
||||
self.assertFalse(ret['out'][0])
|
||||
|
||||
def test_list_jobs(self):
|
||||
'''
|
||||
|
|
|
@ -52,6 +52,10 @@ def parse():
|
|||
action='store_true',
|
||||
default=False,
|
||||
help='Don\'t cleanup temporary files/directories')
|
||||
parser.add_option('--root-dir',
|
||||
dest='root_dir',
|
||||
default=None,
|
||||
help='Override the minion root_dir config')
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
|
@ -69,7 +73,16 @@ class Swarm(object):
|
|||
'''
|
||||
def __init__(self, opts):
|
||||
self.opts = opts
|
||||
self.swarm_root = tempfile.mkdtemp(prefix='mswarm-root', suffix='.d')
|
||||
|
||||
# If given a root_dir, keep the tmp files there as well
|
||||
if opts['root_dir']:
|
||||
tmpdir = os.path.join(opts['root_dir'], 'tmp')
|
||||
else:
|
||||
tmpdir = opts['root_dir']
|
||||
|
||||
self.swarm_root = tempfile.mkdtemp(prefix='mswarm-root', suffix='.d',
|
||||
dir=tmpdir)
|
||||
|
||||
self.pki = self._pki_dir()
|
||||
self.__zfill = len(str(self.opts['minions']))
|
||||
|
||||
|
@ -110,6 +123,9 @@ class Swarm(object):
|
|||
'log_file': os.path.join(dpath, 'minion.log')
|
||||
}
|
||||
|
||||
if self.opts['root_dir']:
|
||||
data['root_dir'] = self.opts['root_dir']
|
||||
|
||||
path = os.path.join(dpath, 'minion')
|
||||
|
||||
if self.opts['keep']:
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
'''
|
||||
Test the verification routines
|
||||
'''
|
||||
|
||||
# Import Python libs
|
||||
import getpass
|
||||
import os
|
||||
import sys
|
||||
|
@ -6,6 +11,7 @@ import shutil
|
|||
import resource
|
||||
import tempfile
|
||||
|
||||
# Import Salt libs
|
||||
from saltunittest import skipIf, TestCase, TestsLoggingHandler
|
||||
|
||||
from salt.utils.verify import (
|
||||
|
@ -18,6 +24,9 @@ from salt.utils.verify import (
|
|||
|
||||
|
||||
class TestVerify(TestCase):
|
||||
'''
|
||||
Verify module tests
|
||||
'''
|
||||
|
||||
def test_zmq_verify(self):
|
||||
self.assertTrue(zmq_version())
|
||||
|
@ -60,8 +69,8 @@ class TestVerify(TestCase):
|
|||
self.assertEqual(dir_stat.st_uid, os.getuid())
|
||||
self.assertEqual(dir_stat.st_gid, os.getgid())
|
||||
self.assertEqual(dir_stat.st_mode & stat.S_IRWXU, stat.S_IRWXU)
|
||||
self.assertEqual(dir_stat.st_mode & stat.S_IRWXG, 0)
|
||||
self.assertEqual(dir_stat.st_mode & stat.S_IRWXO, 0)
|
||||
self.assertEqual(dir_stat.st_mode & stat.S_IRWXG, 40)
|
||||
self.assertEqual(dir_stat.st_mode & stat.S_IRWXO, 5)
|
||||
|
||||
def test_verify_socket(self):
|
||||
self.assertTrue(verify_socket('', 18000, 18001))
|
||||
|
|
Loading…
Add table
Reference in a new issue