mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #33711 from rallytime/merge-develop-3
[develop] Merge forward from 2016.3 to develop
This commit is contained in:
commit
08a584b3e5
63 changed files with 1144 additions and 508 deletions
|
@ -386,6 +386,10 @@
|
|||
#include:
|
||||
# - /etc/salt/extra_config
|
||||
# - /etc/roles/webserver
|
||||
|
||||
# The syndic minion can verify that it is talking to the correct master via the
|
||||
# key fingerprint of the higher-level master with the "syndic_finger" config.
|
||||
#syndic_finger: ''
|
||||
#
|
||||
#
|
||||
#
|
||||
|
|
|
@ -74,7 +74,7 @@ $( document ).ready(function() {
|
|||
var $target = $(target);
|
||||
|
||||
$('html, body').stop().animate({
|
||||
'scrollTop': $target.offset().top
|
||||
'scrollTop': $target.offset().top + 200
|
||||
}, 900, 'swing', function () {
|
||||
window.location.hash = target;
|
||||
});
|
||||
|
@ -89,7 +89,7 @@ $( document ).ready(function() {
|
|||
var $target = $('dt[id="' + target + '"]');
|
||||
|
||||
$('html, body').stop().animate({
|
||||
'scrollTop': $target.offset().top
|
||||
'scrollTop': $target.offset().top + 200
|
||||
}, 900, 'swing', function () {
|
||||
window.location.hash = target;
|
||||
});
|
||||
|
@ -207,3 +207,70 @@ function getMetaStatus() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
(function(document, history, location) {
|
||||
var HISTORY_SUPPORT = !!(history && history.pushState);
|
||||
|
||||
var anchorScrolls = {
|
||||
ANCHOR_REGEX: /^#[^ ]+$/,
|
||||
OFFSET_HEIGHT_PX: 60,
|
||||
|
||||
/**
|
||||
* Establish events, and fix initial scroll position if a hash is provided.
|
||||
*/
|
||||
init: function() {
|
||||
this.scrollIfAnchor(location.hash);
|
||||
$('body').on('click', 'a', $.proxy(this, 'delegateAnchors'));
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the offset amount to deduct from the normal scroll position.
|
||||
* Modify as appropriate to allow for dynamic calculations
|
||||
*/
|
||||
getFixedOffset: function() {
|
||||
return this.OFFSET_HEIGHT_PX;
|
||||
},
|
||||
|
||||
/**
|
||||
* If the provided href is an anchor which resolves to an element on the
|
||||
* page, scroll to it.
|
||||
* @param {String} href
|
||||
* @return {Boolean} - Was the href an anchor.
|
||||
*/
|
||||
scrollIfAnchor: function(href, pushToHistory) {
|
||||
var match, anchorOffset;
|
||||
|
||||
if(!this.ANCHOR_REGEX.test(href)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match = document.getElementById(href.slice(1));
|
||||
|
||||
if(match) {
|
||||
anchorOffset = $(match).offset().top - this.getFixedOffset();
|
||||
$('html, body').animate({ scrollTop: anchorOffset});
|
||||
|
||||
// Add the state to history as-per normal anchor links
|
||||
if(HISTORY_SUPPORT && pushToHistory) {
|
||||
history.pushState({}, document.title, location.pathname + href);
|
||||
}
|
||||
}
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
/**
|
||||
* If the click event's target was an anchor, fix the scroll position.
|
||||
*/
|
||||
delegateAnchors: function(e) {
|
||||
var elem = e.target;
|
||||
|
||||
if(this.scrollIfAnchor(elem.getAttribute('href'), true)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready($.proxy(anchorScrolls, 'init'));
|
||||
})(window.document, window.history, window.location);
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ returners
|
|||
roster
|
||||
runners
|
||||
sdb
|
||||
thorium
|
||||
serializers
|
||||
states
|
||||
tops
|
||||
|
|
|
@ -279,11 +279,11 @@ rst_prolog = """\
|
|||
.. _`salt-packagers`: https://groups.google.com/forum/#!forum/salt-packagers
|
||||
.. |windownload| raw:: html
|
||||
|
||||
<p>x86: <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-2-x86-Setup.exe"><strong>Salt-Minion-{release}-2-x86-Setup.exe</strong></a>
|
||||
| <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-2-x86-Setup.exe.md5"><strong>md5</strong></a></p>
|
||||
<p>x86: <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-x86-Setup.exe"><strong>Salt-Minion-{release}-x86-Setup.exe</strong></a>
|
||||
| <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-x86-Setup.exe.md5"><strong>md5</strong></a></p>
|
||||
|
||||
<p>AMD64: <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-2-AMD64-Setup.exe"><strong>Salt-Minion-{release}-2-AMD64-Setup.exe</strong></a>
|
||||
| <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-2-AMD64-Setup.exe.md5"><strong>md5</strong></a></p>
|
||||
<p>AMD64: <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-AMD64-Setup.exe"><strong>Salt-Minion-{release}-AMD64-Setup.exe</strong></a>
|
||||
| <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-AMD64-Setup.exe.md5"><strong>md5</strong></a></p>
|
||||
|
||||
""".format(release=release)
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ Salt Table of Contents
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
index
|
||||
topics/installation/index
|
||||
topics/configuration/index
|
||||
topics/using_salt
|
||||
|
|
114
doc/index.rst
114
doc/index.rst
|
@ -1,114 +0,0 @@
|
|||
.. _get-started:
|
||||
|
||||
=========
|
||||
SaltStack
|
||||
=========
|
||||
Salt, a new approach to infrastructure management, is easy enough to get
|
||||
running in minutes, scalable enough to manage tens of thousands of servers,
|
||||
and fast enough to communicate with those servers in *seconds*.
|
||||
|
||||
Salt delivers a dynamic communication bus for infrastructures that can be used
|
||||
for orchestration, remote execution, configuration management and much more.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
topics/index
|
||||
|
||||
Get Started
|
||||
===========
|
||||
The Get Started Guide shows you how to:
|
||||
|
||||
* Install and configure SaltStack
|
||||
* Remotely execute commands across all managed systems
|
||||
* Design, develop, and deploy system configurations
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
Get Started Guide <https://docs.saltstack.com/en/getstarted/>
|
||||
|
||||
If you just want to get Salt installed and start using it, *Salt in 10 minutes*
|
||||
gets you up and running quickly.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
topics/tutorials/walkthrough
|
||||
|
||||
Install Salt
|
||||
============
|
||||
**Latest Stable Release**: |current_release_doc|
|
||||
|
||||
The installation document, found in the following link, outlines where to
|
||||
obtain packages and installation specifics for platforms:
|
||||
|
||||
* :ref:`Installation <installation>`
|
||||
|
||||
The Salt Bootstrap project, found in the following repository, is a single
|
||||
shell script, which automates the install correctly on multiple platforms:
|
||||
|
||||
* https://github.com/saltstack/salt-bootstrap
|
||||
|
||||
Demo Environments
|
||||
=================
|
||||
You can download one of the following `Vagrant <http://vagrantup.com>`_
|
||||
projects to quickly set up a Salt demo environment:
|
||||
|
||||
- https://github.com/UtahDave/salt-vagrant-demo
|
||||
- https://github.com/UtahDave/salt-vagrant-lxc
|
||||
|
||||
Example Formulas
|
||||
================
|
||||
A Github repo that contains a number of community-maintained formulas is
|
||||
available at https://github.com/saltstack-formulas. Contributions are welcome!
|
||||
|
||||
A Github repo that contains formulas to install a number of Windows
|
||||
applications is available at https://github.com/saltstack/salt-winrepo-ng. Note
|
||||
that Salt makes this repo :ref:`available <windows-package-manager>` to your
|
||||
Windows minions, and contributions are welcome!
|
||||
|
||||
Mailing List
|
||||
============
|
||||
Join the `salt-users mailing list`_. It is the best place to ask questions
|
||||
about Salt and see whats going on with Salt development! The Salt mailing list
|
||||
is hosted by Google Groups. It is open to new members.
|
||||
|
||||
https://groups.google.com/forum/#!forum/salt-users
|
||||
|
||||
.. _`salt-users mailing list`: https://groups.google.com/forum/#!forum/salt-users
|
||||
|
||||
There is also a low-traffic list used to announce new releases
|
||||
called `salt-announce`_
|
||||
|
||||
https://groups.google.com/forum/#!forum/salt-announce
|
||||
|
||||
.. _`salt-announce`: https://groups.google.com/forum/#!forum/salt-announce
|
||||
|
||||
IRC
|
||||
===
|
||||
The ``#salt`` IRC channel is hosted on the popular `Freenode`__ network. You
|
||||
can use the `Freenode webchat client`__ right from your browser.
|
||||
|
||||
`Logs of the IRC channel activity`__ are being collected courtesy of Moritz Lenz.
|
||||
|
||||
.. __: http://freenode.net/irc_servers.shtml
|
||||
.. __: http://webchat.freenode.net/?channels=salt&uio=Mj10cnVlJjk9dHJ1ZSYxMD10cnVl83
|
||||
.. __: http://irclog.perlgeek.de/salt/
|
||||
|
||||
If you wish to discuss the development of Salt itself join us in
|
||||
``#salt-devel``.
|
||||
|
||||
Follow on GitHub
|
||||
================
|
||||
The Salt code is developed via GitHub. Follow Salt for constant updates on what
|
||||
is happening in Salt development:
|
||||
|
||||
|saltrepo|
|
||||
|
||||
Hack the Source
|
||||
===============
|
||||
If you want to get involved with the development of source code or the
|
||||
documentation efforts, please review the :ref:`Developing Salt Tutorial
|
||||
<developing-tutorial>`.
|
||||
|
|
@ -2296,6 +2296,26 @@ configuration is the same as :conf_master:`file_roots`:
|
|||
prod:
|
||||
- /srv/pillar/prod
|
||||
|
||||
.. conf_master:: pillar_opts
|
||||
|
||||
``pillar_opts``
|
||||
---------------
|
||||
|
||||
Default: ``False``
|
||||
|
||||
The ``pillar_opts`` option adds the master configuration file data to a dict in
|
||||
the pillar called ``master``. This can be used to set simple configurations in
|
||||
the master config file that can then be used on minions.
|
||||
|
||||
Note that setting this option to ``True`` means the master config file will be
|
||||
included in all minion's pillars. While this makes global configuration of services
|
||||
and systems easy, it may not be desired if sensitive data is stored in the master
|
||||
configuration.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
pillar_opts: False
|
||||
|
||||
.. _master-configuration-ext-pillar:
|
||||
|
||||
.. conf_master:: ext_pillar
|
||||
|
|
|
@ -942,6 +942,20 @@ what you are doing! Transports are explained in :ref:`Salt Transports
|
|||
|
||||
transport: zeromq
|
||||
|
||||
.. conf_minion:: syndic_finger
|
||||
|
||||
``syndic_finger``
|
||||
-----------------
|
||||
|
||||
Default: ``''``
|
||||
|
||||
The key fingerprint of the higher-level master for the syndic to verify it is
|
||||
talking to the intended master.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
syndic_finger: 'ab:30:65:2a:d6:9e:20:4f:d8:b2:f3:a7:d4:65:50:10'
|
||||
|
||||
|
||||
Minion Module Management
|
||||
========================
|
||||
|
|
|
@ -341,3 +341,13 @@ string with quotes:
|
|||
ValueError: month must be in 1..12
|
||||
>>> yaml.safe_load('"4017-16-20"')
|
||||
'4017-16-20'
|
||||
|
||||
|
||||
Keys Limited to 1024 Characters
|
||||
===============================
|
||||
|
||||
Simple keys are limited to a single line and cannot be longer that 1024 characters.
|
||||
This is a limitation from PyYaml, as seen in a comment in `PyYAML's code`_, and
|
||||
applies to anything parsed by YAML in Salt.
|
||||
|
||||
.. _PyYAML's code: http://pyyaml.org/browser/pyyaml/trunk/lib/yaml/scanner.py#L91
|
||||
|
|
|
@ -59,7 +59,7 @@ to set up the libvirt pki keys.
|
|||
- contents: 'LIBVIRTD_ARGS="--listen"'
|
||||
- require:
|
||||
- pkg: libvirt
|
||||
libvirt.keys:
|
||||
virt.keys:
|
||||
- require:
|
||||
- pkg: libvirt
|
||||
service.running:
|
||||
|
@ -139,7 +139,7 @@ date:
|
|||
.. code-block:: yaml
|
||||
|
||||
libvirt_keys:
|
||||
libvirt.keys
|
||||
virt.keys
|
||||
|
||||
Getting Virtual Machine Images Ready
|
||||
====================================
|
||||
|
|
|
@ -17,6 +17,20 @@ things:
|
|||
Otherwise, it will attempt to connect to a master and fail. The salt-call
|
||||
command stands on its own and does not need the salt-minion daemon.
|
||||
|
||||
|
||||
Minion Configuration
|
||||
--------------------
|
||||
|
||||
Throughout this document there are several references to setting different
|
||||
options to configure a masterless Minion. Salt Minions are easy to configure
|
||||
via a configuration file that is located, by default, in ``/etc/salt/minion``.
|
||||
Note, however, that on FreeBSD systems, the minion configuration file is located
|
||||
in ``/usr/local/etc/salt/minion``.
|
||||
|
||||
You can learn more about minion configuration options in the
|
||||
:ref:`Configuring the Salt Minion <configuration-salt-minion>` docs.
|
||||
|
||||
|
||||
Telling Salt Call to Run Masterless
|
||||
===================================
|
||||
|
||||
|
@ -39,7 +53,6 @@ Now the salt-call command will not look for a master and will assume that the
|
|||
local system has all of the file and pillar resources.
|
||||
|
||||
|
||||
|
||||
Running States Masterless
|
||||
=========================
|
||||
|
||||
|
@ -81,6 +94,7 @@ it unnecessary to change the configuration file:
|
|||
|
||||
salt-call state.apply --local
|
||||
|
||||
|
||||
External Pillars
|
||||
================
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@ else
|
|||
PKGDIR=$2
|
||||
fi
|
||||
|
||||
CPUARCH=`uname -m`
|
||||
|
||||
############################################################################
|
||||
# Additional Parameters Required for the script to function properly
|
||||
############################################################################
|
||||
|
@ -140,7 +142,7 @@ cp $SRCDIR/conf/minion $PKGDIR/etc/salt/minion.dist
|
|||
cp $SRCDIR/conf/master $PKGDIR/etc/salt/master.dist
|
||||
|
||||
############################################################################
|
||||
# Add Version to distribution.xml
|
||||
# Add Version and CPU Arch to distribution.xml
|
||||
############################################################################
|
||||
echo -n -e "\033]0;Build_Pkg: Add Version to .xml\007"
|
||||
|
||||
|
@ -150,6 +152,10 @@ SEDSTR="s/@VERSION@/$VERSION/"
|
|||
echo $SEDSTR
|
||||
sed -i '' $SEDSTR distribution.xml
|
||||
|
||||
SEDSTR="s/@CPUARCH@/$CPUARCH/"
|
||||
echo $SEDSTR
|
||||
sed -i '' $SEDSTR distribution.xml
|
||||
|
||||
############################################################################
|
||||
# Build the Package
|
||||
############################################################################
|
||||
|
@ -159,10 +165,10 @@ pkgbuild --root $PKGDIR \
|
|||
--scripts $PKGDIR/scripts \
|
||||
--identifier=com.saltstack.salt \
|
||||
--version=$VERSION \
|
||||
--ownership=recommended salt-src-$VERSION.pkg
|
||||
--ownership=recommended salt-src-$VERSION-$CPUARCH.pkg
|
||||
|
||||
productbuild --resources=$PKGDIR/resources \
|
||||
--distribution=distribution.xml \
|
||||
--package-path=salt-src-$VERSION.pkg \
|
||||
--version=$VERSION salt-$VERSION.pkg
|
||||
--package-path=salt-src-$VERSION-$CPUARCH.pkg \
|
||||
--version=$VERSION salt-$VERSION-$CPUARCH.pkg
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</allowed-os-versions>
|
||||
</volume-check>
|
||||
<options rootVolumeOnly="true"
|
||||
hostArchitectures="x86_64" />
|
||||
hostArchitectures="@CPUARCH@" />
|
||||
<domains enable_localSystem="true" />
|
||||
<!-- Define background image -->
|
||||
<background file="saltstack.png"
|
||||
|
@ -25,7 +25,7 @@
|
|||
<!-- List all component packages -->
|
||||
<pkg-ref id="com.saltstack.salt"
|
||||
version="@VERSION@"
|
||||
auth="root">salt-src-@VERSION@.pkg</pkg-ref>
|
||||
auth="root">salt-src-@VERSION@-@CPUARCH@.pkg</pkg-ref>
|
||||
<!-- List them again here. They can now be organized
|
||||
as a hierarchy if you want. -->
|
||||
<choices-outline>
|
||||
|
|
|
@ -6,58 +6,50 @@ However it does document pretty thoroughly how I initially created a build envir
|
|||
for packaging up esky builds for SmartOS
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
set -ux
|
||||
|
||||
export PATH=$PATH:/opt/local/gcc47/bin/
|
||||
HERE=$(pwd)
|
||||
## environment
|
||||
export PATH=$PATH:/opt/local/gcc49/bin/
|
||||
BLDPATH=/tmp/bbfreeze_loader
|
||||
SALTBASE=/data
|
||||
|
||||
mv /opt/local /opt/local.backup ; hash -r
|
||||
cd /
|
||||
curl http://pkgsrc.joyent.com/packages/SmartOS/bootstrap/bootstrap-2014Q4-x86_64.tar.gz | gtar xz
|
||||
hash -r
|
||||
|
||||
rm -rf /var/db/pkgin/
|
||||
pkgin -y up
|
||||
## packages
|
||||
pkgin -y in build-essential salt swig py27-pip unzip py27-mysqldb libsodium mysql-client patchelf
|
||||
pkgin -y rm salt py27-zmq
|
||||
|
||||
pip install --egg esky bbfreeze
|
||||
pip install --no-use-wheel --egg esky bbfreeze
|
||||
|
||||
cd $HERE
|
||||
## bzfreeze-loader
|
||||
COMPILE="gcc -fno-strict-aliasing -O2 -pipe -O2 -DHAVE_DB_185_H -I/usr/include -I/opt/local/include -I/opt/local/include/db4 -I/opt/local/include/gettext -I/opt/local/include/ncurses -DNDEBUG -O2 -pipe -O2 -DHAVE_DB_185_H -I/usr/include -I/opt/local/include -I/opt/local/include/db4 -I/opt/local/include/gettext -I/opt/local/include/ncurses -fPIC -I/opt/local/include/python2.7 -static-libgcc"
|
||||
LINK_OPTS="-L/opt/local/lib -L/opt/local/lib/python2.7/config -L/opt/local/lib -lsocket -lnsl -ldl -lrt -lm -static-libgcc"
|
||||
mkdir -p ${BLDPATH}
|
||||
cd ${BLDPATH}
|
||||
curl -kO 'https://pypi.python.org/packages/source/b/bbfreeze-loader/bbfreeze-loader-1.1.0.zip'
|
||||
unzip bbfreeze-loader-1.1.0.zip
|
||||
${COMPILE} -c bbfreeze-loader-1.1.0/_bbfreeze_loader/console.c -o ${BLDPATH}/console.o
|
||||
${COMPILE} -c bbfreeze-loader-1.1.0/_bbfreeze_loader/getpath.c -o ${BLDPATH}/getpath.o
|
||||
gcc ${BLDPATH}/console.o ${BLDPATH}/getpath.o /opt/local/lib/python2.7/config/libpython2.7.a ${LINK_OPTS} -o ${BLDPATH}/console.exe
|
||||
patchelf --set-rpath '$ORIGIN:$ORIGIN/../lib' ${BLDPATH}/console.exe
|
||||
find /opt/local -name console.exe -exec cp ${BLDPATH}/console.exe {} \;
|
||||
|
||||
COMPILE="gcc -fno-strict-aliasing -O2 -pipe -O2 -DHAVE_DB_185_H -I/usr/include -I/opt/local/include -I/opt/local/include/db4 -I/opt/local/include/gettext -I/opt/local/include/ncurses -DNDEBUG -O2 -pipe -O2 -DHAVE_DB_185_H -I/usr/include -I/opt/local/include -I/opt/local/include/db4 -I/opt/local/include/gettext -I/opt/local/include/ncurses -fPIC -I/opt/local/include/python2.7 -static-libgcc"
|
||||
$COMPILE -c bbfreeze-loader-1.1.0/_bbfreeze_loader/console.c -o $HERE/console.o
|
||||
$COMPILE -c bbfreeze-loader-1.1.0/_bbfreeze_loader/getpath.c -o $HERE/getpath.o
|
||||
gcc $HERE/console.o $HERE/getpath.o /opt/local/lib/python2.7/config/libpython2.7.a -L/opt/local/lib -L/opt/local/lib/python2.7/config -L/opt/local/lib -lsocket -lnsl -ldl -lrt -lm -static-libgcc -o $HERE/console.exe
|
||||
patchelf --set-rpath '$ORIGIN:$ORIGIN/../lib' $HERE/console.exe
|
||||
## clone saltstack repo
|
||||
cd ${SALTBASE}
|
||||
git clone git://github.com/saltstack/salt -b 2016.3
|
||||
|
||||
find /opt/local -name console.exe -exec mv $HERE/console.exe {} \;
|
||||
## salt requirements
|
||||
cd ${SALTBASE}/salt
|
||||
until pip install --no-use-wheel --egg -r pkg/smartos/esky/zeromq_requirements.txt ; do sleep 1 ; done ;
|
||||
until pip install --no-use-wheel --egg -r pkg/smartos/esky/raet_requirements.txt ; do sleep 1 ; done ;
|
||||
|
||||
git clone git://github.com/saltstack/salt -b 2014.7
|
||||
cd $HERE/salt
|
||||
|
||||
# install all requirements
|
||||
# (installing them as eggs seems to trigger esky pulling in the whole egg)
|
||||
# this step is buggy... I had to run them repeatedly until they succeeded...
|
||||
until pip install --egg -r pkg/smartos/esky/zeromq_requirements.txt ; do sleep 1 ; done ;
|
||||
until pip install --egg -r pkg/smartos/esky/raet_requirements.txt ; do sleep 1 ; done ;
|
||||
|
||||
# install the sodium_grabber library
|
||||
## sodium grabber
|
||||
cd ${SALTBASE}/salt
|
||||
python2.7 pkg/smartos/esky/sodium_grabber_installer.py install
|
||||
|
||||
# ugly workaround for odd zeromq linking breakage
|
||||
cp /opt/local/lib/libzmq.so.4 /opt/local/lib/python2.7/site-packages/pyzmq-13.1.0-py2.7-solaris-2.11-i86pc.64bit.egg/zmq/
|
||||
patchelf --set-rpath '$ORIGIN:$ORIGIN/../lib' /opt/local/lib/python2.7/site-packages/pyzmq-13.1.0-py2.7-solaris-2.11-i86pc.64bit.egg/zmq/libzmq.so.4
|
||||
cp /opt/local/lib/libsodium.so.13 /opt/local/lib/python2.7/site-packages/pyzmq-13.1.0-py2.7-solaris-2.11-i86pc.64bit.egg/zmq/
|
||||
patchelf --set-rpath '$ORIGIN:$ORIGIN/../lib' /opt/local/lib/python2.7/site-packages/pyzmq-13.1.0-py2.7-solaris-2.11-i86pc.64bit.egg/zmq/libsodium.so.13
|
||||
## cleanup
|
||||
rm -r ${BLDPATH}
|
||||
|
||||
# at this point you have a build environment that you could set aside and reuse to run further builds.
|
||||
|
||||
bash pkg/smartos/esky/build-tarball.sh
|
||||
|
||||
# Upload packages into Manta
|
||||
#mmkdir -p /$MANTA_USER/public/salt
|
||||
#for file in dist/salt*; do mput -m /$MANTA_USER/public/salt -f $file; done;
|
||||
## build esky package
|
||||
cd ${SALTBASE}/salt
|
||||
pkg/smartos/esky/build-tarball.sh
|
||||
```
|
||||
|
|
|
@ -24,5 +24,9 @@ chmod +x $BUILD_DIR/install/install.sh
|
|||
unzip -d $BUILD_DIR/bin dist/*.zip
|
||||
cp $BUILD_DIR/bin/*/libgcc_s.so.1 $BUILD_DIR/bin/
|
||||
find build/output/salt/bin/ -mindepth 1 -maxdepth 1 -type d -not -name appdata -exec mv {} $BUILD_DIR/bin/appdata/ \;
|
||||
PYZMQ=$(find ${BUILD_DIR}/bin/ -mindepth 1 -type d -name 'pyzmq-*.egg')/zmq
|
||||
find /opt/local/lib/ -maxdepth 1 -type l -regextype sed -regex '.*/libzmq.so.[0-9]\+$' -exec cp {} ${PYZMQ}/ \;
|
||||
find /opt/local/lib/ -maxdepth 1 -type l -regextype sed -regex '.*/libsodium.so.[0-9]\+$' -exec cp {} ${PYZMQ}/ \;
|
||||
find ${PYZMQ}/ -maxdepth 1 -type f -name '*.so.*' -exec patchelf --set-rpath '$ORIGIN:$ORIGIN/../../:$ORIGIN/../lib' {} \;
|
||||
gtar -C $BUILD_DIR/.. -czvf dist/salt-$(awk '/^Version:/{print $2}' < PKG-INFO)-esky-smartos.tar.gz salt
|
||||
echo "tarball built"
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
#-r ../../../requirements/zeromq.txt
|
||||
-r ../../../requirements/base.txt
|
||||
pycrypto>=2.6.1
|
||||
pyzmq == 13.1.0
|
||||
pyzmq
|
||||
-r requirements.txt
|
||||
|
|
|
@ -55,7 +55,7 @@ RETVAL=0
|
|||
start() {
|
||||
echo -n $"Starting salt-minion daemon: "
|
||||
if [ -f $SUSE_RELEASE ]; then
|
||||
startproc -f -p /var/run/$SERVICE.pid $SALTMINION -d $MINION_ARGS
|
||||
startproc -p /var/run/$SERVICE.pid $SALTMINION -d $MINION_ARGS
|
||||
rc_status -v
|
||||
elif [ -e $DEBIAN_VERSION ]; then
|
||||
if [ -f $LOCKFILE ]; then
|
||||
|
|
|
@ -26,10 +26,10 @@ if [%1]==[] (
|
|||
)
|
||||
|
||||
:: Create Build Environment
|
||||
cmd /c powershell -ExecutionPolicy RemoteSigned -File "%CurDir%build_env.ps1" -Silent
|
||||
PowerShell.exe -ExecutionPolicy RemoteSigned -File "%CurDir%build_env.ps1" -Silent
|
||||
|
||||
:: Install Current Version of salt
|
||||
cmd /c "%PyDir%\python.exe %SrcDir%\setup.py" install --force
|
||||
"%PyDir%\python.exe" "%SrcDir%\setup.py" install --force
|
||||
|
||||
:: Build the Salt Package
|
||||
call "%CurDir%build_pkg.bat" "%Version%"
|
||||
|
|
|
@ -257,16 +257,6 @@ $p = Start-Process "$($ini['Settings']['ScriptsDir'])\pip.exe" -ArgumentList "in
|
|||
Write-Output " ----------------------------------------------------------------"
|
||||
Write-Output " - Copying DLLs . . ."
|
||||
Write-Output " ----------------------------------------------------------------"
|
||||
ForEach ($key in $ini['CommonDLLs'].Keys) {
|
||||
If ($arrInstalled -notcontains $key) {
|
||||
Write-Output " - $key . . ."
|
||||
$file = "$($ini['CommonDLLs'][$key])"
|
||||
$url = "$($ini['Settings']['SaltRepo'])/$file"
|
||||
$file = "$($ini['Settings']['PythonDir'])\$file"
|
||||
DownloadFileWithProgress $url $file
|
||||
}
|
||||
}
|
||||
|
||||
# Architecture Specific DLL's
|
||||
ForEach($key in $ini[$bitDLLs].Keys) {
|
||||
If ($arrInstalled -notcontains $key) {
|
||||
|
|
|
@ -58,17 +58,12 @@ Function Get-Settings {
|
|||
}
|
||||
$ini.Add("32bitPrograms", $32bitPrograms)
|
||||
|
||||
# CPU Architecture Independent DLL's
|
||||
$CommonDLLs = @{
|
||||
"libsodium" = "libsodium-13.dll"
|
||||
}
|
||||
$ini.Add("CommonDLLs", $CommonDLLs)
|
||||
|
||||
# DLL's for 64 bit Windows
|
||||
$64bitDLLs = @{
|
||||
"Libeay" = "libeay32.dll"
|
||||
"SSLeay" = "ssleay32.dll"
|
||||
"OpenSSLLic" = "OpenSSL_License.txt"
|
||||
"libsodium" = "libsodium.dll"
|
||||
}
|
||||
$ini.Add("64bitDLLs", $64bitDLLs)
|
||||
|
||||
|
@ -77,6 +72,7 @@ Function Get-Settings {
|
|||
"Libeay" = "libeay32.dll"
|
||||
"SSLeay" = "ssleay32.dll"
|
||||
"OpenSSLLic" = "OpenSSL_License.txt"
|
||||
"libsodium" = "libsodium.dll"
|
||||
}
|
||||
$ini.Add("32bitDLLs", $32bitDLLs)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ ioflo==1.5.3
|
|||
ioloop==0.1a0
|
||||
ipaddress==1.0.16
|
||||
Jinja2==2.8
|
||||
libnacl==1.4.4
|
||||
libnacl==1.4.5
|
||||
Mako==1.0.4
|
||||
MarkupSafe==0.23
|
||||
msgpack-python==0.4.7
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
pip==8.1.1
|
||||
setuptools==21.0.0
|
||||
pip==8.1.2
|
||||
setuptools==21.2.1
|
||||
|
|
|
@ -117,6 +117,10 @@ try:
|
|||
PAM_AUTHENTICATE.restype = c_int
|
||||
PAM_AUTHENTICATE.argtypes = [PamHandle, c_int]
|
||||
|
||||
PAM_ACCT_MGMT = LIBPAM.pam_acct_mgmt
|
||||
PAM_ACCT_MGMT.restype = c_int
|
||||
PAM_ACCT_MGMT.argtypes = [PamHandle, c_int]
|
||||
|
||||
PAM_END = LIBPAM.pam_end
|
||||
PAM_END.restype = c_int
|
||||
PAM_END.argtypes = [PamHandle, c_int]
|
||||
|
@ -171,6 +175,8 @@ def authenticate(username, password):
|
|||
return False
|
||||
|
||||
retval = PAM_AUTHENTICATE(handle, 0)
|
||||
if retval == 0:
|
||||
PAM_ACCT_MGMT(handle, 0)
|
||||
PAM_END(handle, 0)
|
||||
return retval == 0
|
||||
|
||||
|
|
|
@ -68,8 +68,7 @@ def beacon(config):
|
|||
|
||||
'''
|
||||
ret = []
|
||||
for diskusage in config:
|
||||
mount = diskusage.keys()[0]
|
||||
for mount in config:
|
||||
|
||||
try:
|
||||
_current_usage = psutil.disk_usage(mount)
|
||||
|
@ -79,7 +78,7 @@ def beacon(config):
|
|||
continue
|
||||
|
||||
current_usage = _current_usage.percent
|
||||
monitor_usage = diskusage[mount]
|
||||
monitor_usage = config[mount]
|
||||
if '%' in monitor_usage:
|
||||
monitor_usage = re.sub('%', '', monitor_usage)
|
||||
monitor_usage = float(monitor_usage)
|
||||
|
|
|
@ -2957,7 +2957,12 @@ def apply_minion_config(overrides=None,
|
|||
# Enabling open mode requires that the value be set to True, and
|
||||
# nothing else!
|
||||
opts['open_mode'] = opts['open_mode'] is True
|
||||
|
||||
# Make sure ext_mods gets set if it is an untrue value
|
||||
# (here to catch older bad configs)
|
||||
opts['extension_modules'] = (
|
||||
opts.get('extension_modules') or
|
||||
os.path.join(opts['cachedir'], 'extmods')
|
||||
)
|
||||
# Set up the utils_dirs location from the extension_modules location
|
||||
opts['utils_dirs'] = (
|
||||
opts.get('utils_dirs') or
|
||||
|
|
|
@ -11,7 +11,6 @@ import re
|
|||
|
||||
# Import salt libs
|
||||
import salt.utils
|
||||
import salt.utils.decorators as decorators
|
||||
|
||||
# Solve the Chicken and egg problem where grains need to run before any
|
||||
# of the modules are loaded and are generally available for any usage.
|
||||
|
@ -29,91 +28,94 @@ def disks():
|
|||
Return list of disk devices
|
||||
'''
|
||||
if salt.utils.is_freebsd():
|
||||
return _freebsd_disks()
|
||||
return _freebsd_geom()
|
||||
elif salt.utils.is_linux():
|
||||
return _linux_disks()
|
||||
else:
|
||||
log.trace('Disk grain does not support OS')
|
||||
|
||||
|
||||
def _clean_keys(key):
|
||||
key = key.replace(' ', '_')
|
||||
key = key.replace('(', '')
|
||||
key = key.replace(')', '')
|
||||
return key
|
||||
class _geomconsts(object):
|
||||
GEOMNAME = 'Geom name'
|
||||
MEDIASIZE = 'Mediasize'
|
||||
SECTORSIZE = 'Sectorsize'
|
||||
STRIPESIZE = 'Stripesize'
|
||||
STRIPEOFFSET = 'Stripeoffset'
|
||||
DESCR = 'descr' # model
|
||||
LUNID = 'lunid'
|
||||
LUNNAME = 'lunname'
|
||||
IDENT = 'ident' # serial
|
||||
ROTATIONRATE = 'rotationrate' # RPM or 0 for non-rotating
|
||||
|
||||
# Preserve the API where possible with Salt < 2016.3
|
||||
_aliases = {
|
||||
DESCR: 'device_model',
|
||||
IDENT: 'serial_number',
|
||||
ROTATIONRATE: 'media_RPM',
|
||||
LUNID: 'WWN',
|
||||
}
|
||||
|
||||
_datatypes = {
|
||||
MEDIASIZE: ('re_int', r'(\d+)'),
|
||||
SECTORSIZE: 'try_int',
|
||||
STRIPESIZE: 'try_int',
|
||||
STRIPEOFFSET: 'try_int',
|
||||
ROTATIONRATE: 'try_int',
|
||||
}
|
||||
|
||||
|
||||
class _camconsts(object):
|
||||
PROTOCOL = 'protocol'
|
||||
DEVICE_MODEL = 'device model'
|
||||
FIRMWARE_REVISION = 'firmware revision'
|
||||
SERIAL_NUMBER = 'serial number'
|
||||
WWN = 'WWN'
|
||||
SECTOR_SIZE = 'sector size'
|
||||
MEDIA_RPM = 'media RPM'
|
||||
|
||||
_identify_attribs = [_camconsts.__dict__[key] for key in
|
||||
_camconsts.__dict__ if not key.startswith('__')]
|
||||
def _datavalue(datatype, data):
|
||||
if datatype == 'try_int':
|
||||
try:
|
||||
return int(data)
|
||||
except ValueError:
|
||||
return None
|
||||
elif datatype is tuple and datatype[0] == 're_int':
|
||||
search = re.search(datatype[1], data)
|
||||
if search:
|
||||
try:
|
||||
return int(search.group(1))
|
||||
except ValueError:
|
||||
return None
|
||||
return None
|
||||
else:
|
||||
return data
|
||||
|
||||
|
||||
@decorators.memoize
|
||||
def _freebsd_vbox():
|
||||
# Don't tickle VirtualBox storage emulation bugs
|
||||
camcontrol = salt.utils.which('camcontrol')
|
||||
devlist = __salt__['cmd.run']('{0} devlist'.format(camcontrol))
|
||||
if 'VBOX' in devlist:
|
||||
return True
|
||||
return False
|
||||
_geom_attribs = [_geomconsts.__dict__[key] for key in
|
||||
_geomconsts.__dict__ if not key.startswith('_')]
|
||||
|
||||
|
||||
def _freebsd_disks():
|
||||
def _freebsd_geom():
|
||||
geom = salt.utils.which('geom')
|
||||
ret = {'disks': {}, 'SSDs': []}
|
||||
sysctl = salt.utils.which('sysctl')
|
||||
devices = __salt__['cmd.run']('{0} -n kern.disks'.format(sysctl))
|
||||
SSD_TOKEN = 'non-rotating'
|
||||
|
||||
for device in devices.split(' '):
|
||||
if device.startswith('cd'):
|
||||
log.debug('Disk grain skipping cd')
|
||||
elif _freebsd_vbox():
|
||||
log.debug('Disk grain skipping CAM identify/inquirty on VBOX')
|
||||
ret['disks'][device] = {}
|
||||
else:
|
||||
cam = _freebsd_camcontrol(device)
|
||||
ret['disks'][device] = cam
|
||||
if cam.get(_clean_keys(_camconsts.MEDIA_RPM)) == SSD_TOKEN:
|
||||
ret['SSDs'].append(device)
|
||||
devices = __salt__['cmd.run']('{0} disk list'.format(geom))
|
||||
devices = devices.split('\n\n')
|
||||
|
||||
return ret
|
||||
def parse_geom_attribs(device):
|
||||
tmp = {}
|
||||
for line in device.split('\n'):
|
||||
for attrib in _geom_attribs:
|
||||
search = re.search(r'{0}:\s(.*)'.format(attrib), line)
|
||||
if search:
|
||||
value = _datavalue(_geomconsts._datatypes.get(attrib),
|
||||
search.group(1))
|
||||
tmp[attrib] = value
|
||||
if attrib in _geomconsts._aliases:
|
||||
tmp[_geomconsts._aliases[attrib]] = value
|
||||
|
||||
name = tmp.pop(_geomconsts.GEOMNAME)
|
||||
if name.startswith('cd'):
|
||||
return
|
||||
|
||||
def _freebsd_camcontrol(device):
|
||||
camcontrol = salt.utils.which('camcontrol')
|
||||
ret = {}
|
||||
ret['disks'][name] = tmp
|
||||
if tmp[_geomconsts.ROTATIONRATE] == 0:
|
||||
log.trace('Device {0} reports itself as an SSD'.format(device))
|
||||
ret['SSDs'].append(name)
|
||||
|
||||
def parse_identify_attribs(line):
|
||||
for attrib in _identify_attribs:
|
||||
search = re.search(r'^{0}\s+(.*)'.format(attrib), line)
|
||||
if search:
|
||||
ret[_clean_keys(attrib)] = search.group(1)
|
||||
|
||||
identify = __salt__['cmd.run']('{0} identify {1}'.format(camcontrol,
|
||||
device))
|
||||
for line in identify.splitlines():
|
||||
parse_identify_attribs(line)
|
||||
|
||||
def parse_inquiry(inquiry):
|
||||
if not ret.get(_clean_keys(_camconsts.DEVICE_MODEL)):
|
||||
model = re.search(r'\s<(.+?)>', inquiry)
|
||||
if model:
|
||||
ret[_clean_keys(_camconsts.DEVICE_MODEL)] = model.group(1)
|
||||
if not ret.get(_clean_keys(_camconsts.SERIAL_NUMBER)):
|
||||
sn = re.search(r'\sSerial Number\s+(\w+)\s', inquiry)
|
||||
if sn:
|
||||
ret[_clean_keys(_camconsts.SERIAL_NUMBER)] = sn.group(1)
|
||||
|
||||
inquiry = __salt__['cmd.run']('{0} inquiry {1}'.format(camcontrol, device))
|
||||
parse_inquiry(inquiry)
|
||||
for device in devices:
|
||||
parse_geom_attribs(device)
|
||||
|
||||
return ret
|
||||
|
||||
|
|
|
@ -1063,7 +1063,7 @@ class Minion(MinionBase):
|
|||
mod_opts[key] = val
|
||||
return mod_opts
|
||||
|
||||
def _load_modules(self, force_refresh=False, notify=False):
|
||||
def _load_modules(self, force_refresh=False, notify=False, grains=None):
|
||||
'''
|
||||
Return the functions and the returners loaded up from the loader
|
||||
module
|
||||
|
@ -1091,7 +1091,8 @@ class Minion(MinionBase):
|
|||
else:
|
||||
proxy = None
|
||||
|
||||
self.opts['grains'] = salt.loader.grains(self.opts, force_refresh, proxy=proxy)
|
||||
if grains is None:
|
||||
self.opts['grains'] = salt.loader.grains(self.opts, force_refresh, proxy=proxy)
|
||||
self.utils = salt.loader.utils(self.opts)
|
||||
|
||||
if self.opts.get('multimaster', False):
|
||||
|
@ -1241,7 +1242,7 @@ class Minion(MinionBase):
|
|||
minion_instance.connected = connected
|
||||
if not hasattr(minion_instance, 'functions'):
|
||||
functions, returners, function_errors, executors = (
|
||||
minion_instance._load_modules()
|
||||
minion_instance._load_modules(grains=opts['grains'])
|
||||
)
|
||||
minion_instance.functions = functions
|
||||
minion_instance.returners = returners
|
||||
|
|
|
@ -4,8 +4,11 @@ Execution module to work with etcd
|
|||
|
||||
:depends: - python-etcd
|
||||
|
||||
In order to use an etcd server, a profile should be created in the master
|
||||
configuration file:
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
To work with an etcd server you must configure an etcd profile. The etcd config
|
||||
can be set in either the Salt Minion configuration file or in pillar:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
|
@ -21,6 +24,17 @@ or clusters are available.
|
|||
|
||||
etcd.host: 127.0.0.1
|
||||
etcd.port: 4001
|
||||
|
||||
.. note::
|
||||
|
||||
The etcd configuration can also be set in the Salt Master config file,
|
||||
but in order to use any etcd configurations defined in the Salt Master
|
||||
config, the :conf_master:`pillar_opts` must be set to ``True``.
|
||||
|
||||
Be aware that setting ``pillar_opts`` to ``True`` has security implications
|
||||
as this makes all master configuration settings available in all minion's
|
||||
pillars.
|
||||
|
||||
'''
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
|
|
@ -448,6 +448,12 @@ def image_schema(profile=None):
|
|||
'''
|
||||
Returns names and descriptions of the schema "image"'s
|
||||
properties for this profile's instance of glance
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' glance.image_schema
|
||||
'''
|
||||
return schema_get('image', profile)
|
||||
|
||||
|
@ -459,6 +465,13 @@ def image_update(id=None, name=None, profile=None, **kwargs): # pylint: disable
|
|||
- min_ram (in MB)
|
||||
- protected (bool)
|
||||
- visibility ('public' or 'private')
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' glance.image_update id=c2eb2eb0-53e1-4a80-b990-8ec887eae7df
|
||||
salt '*' glance.image_update name=f16-jeos
|
||||
'''
|
||||
if id:
|
||||
image = image_show(id=id, profile=profile)
|
||||
|
@ -512,6 +525,12 @@ def schema_get(name, profile=None):
|
|||
- images
|
||||
- member
|
||||
- members
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' glance.schema_get name=f16-jeos
|
||||
'''
|
||||
g_client = _auth(profile)
|
||||
pformat = pprint.PrettyPrinter(indent=4).pformat
|
||||
|
|
|
@ -21,6 +21,9 @@ from salt.exceptions import CommandExecutionError, MinionError
|
|||
import salt.ext.six as six
|
||||
from salt.ext.six.moves import zip
|
||||
|
||||
# Import third party libs
|
||||
import json
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Define the module's virtual name
|
||||
|
@ -54,7 +57,9 @@ def _tap(tap, runas=None):
|
|||
return True
|
||||
|
||||
cmd = 'brew tap {0}'.format(tap)
|
||||
if _call_brew(cmd)['retcode']:
|
||||
try:
|
||||
_call_brew(cmd)
|
||||
except CommandExecutionError:
|
||||
log.error('Failed to tap "{0}"'.format(tap))
|
||||
return False
|
||||
|
||||
|
@ -76,11 +81,18 @@ def _call_brew(cmd, redirect_stderr=False):
|
|||
'''
|
||||
user = __salt__['file.get_user'](_homebrew_bin())
|
||||
runas = user if user != __opts__['user'] else None
|
||||
return __salt__['cmd.run_all'](cmd,
|
||||
runas=runas,
|
||||
output_loglevel='trace',
|
||||
python_shell=False,
|
||||
redirect_stderr=redirect_stderr)
|
||||
ret = __salt__['cmd.run_all'](cmd,
|
||||
runas=runas,
|
||||
output_loglevel='trace',
|
||||
python_shell=False,
|
||||
redirect_stderr=redirect_stderr)
|
||||
if ret['retcode'] != 0:
|
||||
raise CommandExecutionError(
|
||||
'stdout: {stdout}\n'
|
||||
'stderr: {stderr}\n'
|
||||
'retcode: {retcode}\n'.format(**ret)
|
||||
)
|
||||
return ret
|
||||
|
||||
|
||||
def list_pkgs(versions_as_list=False, **kwargs):
|
||||
|
@ -161,7 +173,6 @@ def latest_version(*names, **kwargs):
|
|||
salt '*' pkg.latest_version <package1> <package2> <package3>
|
||||
'''
|
||||
refresh = salt.utils.is_true(kwargs.pop('refresh', True))
|
||||
|
||||
if refresh:
|
||||
refresh_db()
|
||||
|
||||
|
@ -403,20 +414,20 @@ def list_upgrades(refresh=True):
|
|||
if refresh:
|
||||
refresh_db()
|
||||
|
||||
cmd = 'brew outdated'
|
||||
call = _call_brew(cmd)
|
||||
if call['retcode'] != 0:
|
||||
comment = ''
|
||||
if 'stderr' in call:
|
||||
comment += call['stderr']
|
||||
if 'stdout' in call:
|
||||
comment += call['stdout']
|
||||
raise CommandExecutionError(
|
||||
'{0}'.format(comment)
|
||||
)
|
||||
else:
|
||||
out = call['stdout']
|
||||
return out.splitlines()
|
||||
res = _call_brew(['brew', 'outdated', '--json=v1'])
|
||||
ret = {}
|
||||
|
||||
try:
|
||||
data = json.loads(res['stdout'])
|
||||
except ValueError as err:
|
||||
msg = 'unable to interpret output from "brew outdated": {0}'.format(err)
|
||||
log.error(msg)
|
||||
raise CommandExecutionError(msg)
|
||||
|
||||
for pkg in data:
|
||||
# current means latest available to brew
|
||||
ret[pkg['name']] = pkg['current_version']
|
||||
return ret
|
||||
|
||||
|
||||
def upgrade_available(pkg):
|
||||
|
@ -473,3 +484,22 @@ def upgrade(refresh=True):
|
|||
ret['changes'] = salt.utils.compare_dicts(old, new)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def info_installed(*names):
|
||||
'''
|
||||
Return the information of the named package(s) installed on the system.
|
||||
|
||||
.. versionadded:: 2016.3.1
|
||||
|
||||
names
|
||||
The names of the packages for which to return information.
|
||||
|
||||
CLI example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' pkg.info_installed <package1>
|
||||
salt '*' pkg.info_installed <package1> <package2> <package3> ...
|
||||
'''
|
||||
return _info(*names)
|
||||
|
|
|
@ -562,6 +562,12 @@ def list_(profile=None):
|
|||
'''
|
||||
To maintain the feel of the nova command line, this function simply calls
|
||||
the server_list function.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' nova.list
|
||||
'''
|
||||
return server_list(profile=profile)
|
||||
|
||||
|
|
|
@ -99,7 +99,8 @@ def _get_pkgng_version(jail=None, chroot=None, root=None):
|
|||
'''
|
||||
return the version of 'pkg'
|
||||
'''
|
||||
return __salt__['cmd.run']([_pkg(jail, chroot, root), '--version']).strip()
|
||||
cmd = _pkg(jail, chroot, root) + ['--version']
|
||||
return __salt__['cmd.run'](cmd).strip()
|
||||
|
||||
|
||||
def _get_version(name, results):
|
||||
|
@ -300,9 +301,9 @@ def latest_version(*names, **kwargs):
|
|||
for name in names:
|
||||
# FreeBSD supports packages in format java/openjdk7
|
||||
if '/' in name:
|
||||
cmd = [_pkg(jail, chroot, root), 'search']
|
||||
cmd = _pkg(jail, chroot, root) + ['search']
|
||||
else:
|
||||
cmd = [_pkg(jail, chroot, root), 'search', '-S', 'name', '-Q', 'version', '-e']
|
||||
cmd = _pkg(jail, chroot, root) + ['search', '-S', 'name', '-Q', 'version', '-e']
|
||||
if quiet:
|
||||
cmd.append('-q')
|
||||
cmd.append(name)
|
||||
|
|
|
@ -346,6 +346,27 @@ def psql_query(query, user=None, host=None, port=None, maintenance_db=None,
|
|||
WITH updated AS (UPDATE pg_authid SET rolconnlimit = 2000 WHERE
|
||||
rolname = 'rolename' RETURNING rolconnlimit) SELECT * FROM updated;
|
||||
|
||||
query
|
||||
The query string.
|
||||
|
||||
user
|
||||
Database username, if different from config or default.
|
||||
|
||||
host
|
||||
Database host, if different from config or default.
|
||||
|
||||
port
|
||||
Database port, if different from the config or default.
|
||||
|
||||
maintenance_db
|
||||
The database to run the query against.
|
||||
|
||||
password
|
||||
User password, if different from the config or default.
|
||||
|
||||
runas
|
||||
User to run the command as.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
|
|
@ -639,3 +639,29 @@ def version_cmp(ver1, ver2):
|
|||
log.warning("Failed to compare version '{0}' to '{1}' using RPM: {2}".format(ver1, ver2, exc))
|
||||
|
||||
return salt.utils.version_cmp(ver1, ver2)
|
||||
|
||||
|
||||
def checksum(*paths):
|
||||
'''
|
||||
Return if the signature of a RPM file is valid.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' lowpkg.checksum /path/to/package1.rpm
|
||||
salt '*' lowpkg.checksum /path/to/package1.rpm /path/to/package2.rpm
|
||||
'''
|
||||
ret = dict()
|
||||
|
||||
if not paths:
|
||||
raise CommandExecutionError("No package files has been specified.")
|
||||
|
||||
for package_file in paths:
|
||||
ret[package_file] = (bool(__salt__['file.file_exists'](package_file)) and
|
||||
not __salt__['cmd.retcode'](["rpm", "-K", "--quiet", package_file],
|
||||
ignore_retcode=True,
|
||||
output_loglevel='trace',
|
||||
python_shell=False))
|
||||
|
||||
return ret
|
||||
|
|
|
@ -52,7 +52,7 @@ import salt.utils.process
|
|||
import salt.utils.url
|
||||
import salt.wheel
|
||||
from salt.exceptions import (
|
||||
SaltReqTimeoutError, SaltRenderError, CommandExecutionError
|
||||
SaltReqTimeoutError, SaltRenderError, CommandExecutionError, SaltInvocationError
|
||||
)
|
||||
|
||||
__proxyenabled__ = ['*']
|
||||
|
@ -1085,7 +1085,7 @@ def runner(_fun, **kwargs):
|
|||
return rclient.cmd(_fun, kwarg=kwargs)
|
||||
|
||||
|
||||
def wheel(_fun, **kwargs):
|
||||
def wheel(_fun, *args, **kwargs):
|
||||
'''
|
||||
Execute a wheel module (this function must be run on the master)
|
||||
|
||||
|
@ -1093,6 +1093,13 @@ def wheel(_fun, **kwargs):
|
|||
|
||||
name
|
||||
The name of the function to run
|
||||
|
||||
args
|
||||
Any positional arguments to pass to the wheel function. A common example
|
||||
of this would be the ``match`` arg needed for key functions.
|
||||
|
||||
.. versionadded:: v2015.8.11
|
||||
|
||||
kwargs
|
||||
Any keyword arguments to pass to the wheel function
|
||||
|
||||
|
@ -1100,10 +1107,33 @@ def wheel(_fun, **kwargs):
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' saltutil.wheel key.accept match=jerry
|
||||
salt '*' saltutil.wheel key.accept jerry
|
||||
'''
|
||||
wclient = salt.wheel.WheelClient(__opts__)
|
||||
return wclient.cmd(_fun, kwarg=kwargs)
|
||||
if __opts__['__role'] == 'minion':
|
||||
master_config = os.path.join(os.path.dirname(__opts__['conf_file']),
|
||||
'master')
|
||||
master_opts = salt.config.client_config(master_config)
|
||||
wheel_client = salt.wheel.WheelClient(master_opts)
|
||||
else:
|
||||
wheel_client = salt.wheel.WheelClient(__opts__)
|
||||
|
||||
# The WheelClient cmd needs args, kwargs, and pub_data separated out from
|
||||
# the "normal" kwargs structure, which at this point contains __pub_x keys.
|
||||
pub_data = {}
|
||||
valid_kwargs = {}
|
||||
for key, val in six.iteritems(kwargs):
|
||||
if key.startswith('__'):
|
||||
pub_data[key] = val
|
||||
else:
|
||||
valid_kwargs[key] = val
|
||||
|
||||
try:
|
||||
ret = wheel_client.cmd(_fun, arg=args, pub_data=pub_data, kwarg=valid_kwargs)
|
||||
except SaltInvocationError:
|
||||
raise CommandExecutionError('This command can only be executed on a minion '
|
||||
'that is located on the master.')
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
# this is the only way I could figure out how to get the REAL file_roots
|
||||
|
|
|
@ -207,7 +207,22 @@ def low(data, queue=False, **kwargs):
|
|||
return ret
|
||||
|
||||
|
||||
def high(data, test=False, queue=False, **kwargs):
|
||||
def _get_test_value(test=None, **kwargs):
|
||||
'''
|
||||
Determine the correct value for the test flag.
|
||||
'''
|
||||
ret = True
|
||||
if test is None:
|
||||
if salt.utils.test_mode(test=test, **kwargs):
|
||||
ret = True
|
||||
else:
|
||||
ret = __opts__.get('test', None)
|
||||
else:
|
||||
ret = test
|
||||
return ret
|
||||
|
||||
|
||||
def high(data, test=None, queue=False, **kwargs):
|
||||
'''
|
||||
Execute the compound calls stored in a single set of high data
|
||||
|
||||
|
@ -225,12 +240,7 @@ def high(data, test=False, queue=False, **kwargs):
|
|||
return conflict
|
||||
opts = _get_opts(kwargs.get('localconfig'))
|
||||
|
||||
if salt.utils.test_mode(test=test, **kwargs):
|
||||
opts['test'] = True
|
||||
elif test is not None:
|
||||
opts['test'] = test
|
||||
else:
|
||||
opts['test'] = __opts__.get('test', None)
|
||||
opts['test'] = _get_test_value(test, **kwargs)
|
||||
|
||||
pillar = kwargs.get('pillar')
|
||||
pillar_enc = kwargs.get('pillar_enc')
|
||||
|
@ -664,13 +674,7 @@ def highstate(test=None,
|
|||
|
||||
opts = _get_opts(kwargs.get('localconfig'))
|
||||
|
||||
if test is None:
|
||||
if salt.utils.test_mode(test=test, **kwargs):
|
||||
opts['test'] = True
|
||||
else:
|
||||
opts['test'] = __opts__.get('test', None)
|
||||
else:
|
||||
opts['test'] = test
|
||||
opts['test'] = _get_test_value(test, **kwargs)
|
||||
|
||||
if 'env' in kwargs:
|
||||
salt.utils.warn_until(
|
||||
|
@ -882,12 +886,7 @@ def sls(mods,
|
|||
orig_test = __opts__.get('test', None)
|
||||
opts = _get_opts(kwargs.get('localconfig'))
|
||||
|
||||
if salt.utils.test_mode(test=test, **kwargs):
|
||||
opts['test'] = True
|
||||
elif test is not None:
|
||||
opts['test'] = test
|
||||
else:
|
||||
opts['test'] = __opts__.get('test', None)
|
||||
opts['test'] = _get_test_value(test, **kwargs)
|
||||
|
||||
pillar = kwargs.get('pillar')
|
||||
pillar_enc = kwargs.get('pillar_enc')
|
||||
|
@ -1010,10 +1009,7 @@ def top(topfn,
|
|||
return err
|
||||
orig_test = __opts__.get('test', None)
|
||||
opts = _get_opts(kwargs.get('localconfig'))
|
||||
if salt.utils.test_mode(test=test, **kwargs):
|
||||
opts['test'] = True
|
||||
else:
|
||||
opts['test'] = __opts__.get('test', None)
|
||||
opts['test'] = _get_test_value(test, **kwargs)
|
||||
|
||||
pillar = kwargs.get('pillar')
|
||||
pillar_enc = kwargs.get('pillar_enc')
|
||||
|
@ -1130,10 +1126,7 @@ def sls_id(
|
|||
return conflict
|
||||
orig_test = __opts__.get('test', None)
|
||||
opts = _get_opts(kwargs.get('localconfig'))
|
||||
if salt.utils.test_mode(test=test, **kwargs):
|
||||
opts['test'] = True
|
||||
else:
|
||||
opts['test'] = __opts__.get('test', None)
|
||||
opts['test'] = _get_test_value(test, **kwargs)
|
||||
if 'pillarenv' in kwargs:
|
||||
opts['pillarenv'] = kwargs['pillarenv']
|
||||
st_ = salt.state.HighState(opts)
|
||||
|
@ -1195,10 +1188,7 @@ def show_low_sls(mods,
|
|||
return conflict
|
||||
orig_test = __opts__.get('test', None)
|
||||
opts = _get_opts(kwargs.get('localconfig'))
|
||||
if salt.utils.test_mode(test=test, **kwargs):
|
||||
opts['test'] = True
|
||||
else:
|
||||
opts['test'] = __opts__.get('test', None)
|
||||
opts['test'] = _get_test_value(test, **kwargs)
|
||||
if 'pillarenv' in kwargs:
|
||||
opts['pillarenv'] = kwargs['pillarenv']
|
||||
st_ = salt.state.HighState(opts)
|
||||
|
@ -1252,10 +1242,7 @@ def show_sls(mods, saltenv='base', test=None, queue=False, **kwargs):
|
|||
orig_test = __opts__.get('test', None)
|
||||
opts = _get_opts(kwargs.get('localconfig'))
|
||||
|
||||
if salt.utils.test_mode(test=test, **kwargs):
|
||||
opts['test'] = True
|
||||
else:
|
||||
opts['test'] = __opts__.get('test', None)
|
||||
opts['test'] = _get_test_value(test, **kwargs)
|
||||
|
||||
pillar = kwargs.get('pillar')
|
||||
pillar_enc = kwargs.get('pillar_enc')
|
||||
|
@ -1355,10 +1342,7 @@ def single(fun, name, test=None, queue=False, **kwargs):
|
|||
'name': name})
|
||||
orig_test = __opts__.get('test', None)
|
||||
opts = _get_opts(kwargs.get('localconfig'))
|
||||
if salt.utils.test_mode(test=test, **kwargs):
|
||||
opts['test'] = True
|
||||
else:
|
||||
opts['test'] = __opts__.get('test', None)
|
||||
opts['test'] = _get_test_value(test, **kwargs)
|
||||
|
||||
pillar = kwargs.get('pillar')
|
||||
pillar_enc = kwargs.get('pillar_enc')
|
||||
|
@ -1414,7 +1398,7 @@ def clear_cache():
|
|||
return ret
|
||||
|
||||
|
||||
def pkg(pkg_path, pkg_sum, hash_type, test=False, **kwargs):
|
||||
def pkg(pkg_path, pkg_sum, hash_type, test=None, **kwargs):
|
||||
'''
|
||||
Execute a packaged state run, the packaged state run will exist in a
|
||||
tarball available locally. This packaged state
|
||||
|
@ -1458,10 +1442,7 @@ def pkg(pkg_path, pkg_sum, hash_type, test=False, **kwargs):
|
|||
popts = _get_opts(kwargs.get('localconfig'))
|
||||
popts['fileclient'] = 'local'
|
||||
popts['file_roots'] = {}
|
||||
if salt.utils.test_mode(test=test, **kwargs):
|
||||
popts['test'] = True
|
||||
else:
|
||||
popts['test'] = __opts__.get('test', None)
|
||||
popts['test'] = _get_test_value(test, **kwargs)
|
||||
envs = os.listdir(root)
|
||||
for fn_ in envs:
|
||||
full = os.path.join(root, fn_)
|
||||
|
|
|
@ -171,12 +171,16 @@ def install(feature, recurse=False, restart=False):
|
|||
management_tools)
|
||||
out = _pshell_json(cmd)
|
||||
|
||||
ret = {'ExitCode': out['ExitCode'],
|
||||
'DisplayName': out['FeatureResult'][0]['DisplayName'],
|
||||
'RestartNeeded': out['FeatureResult'][0]['RestartNeeded'],
|
||||
'Success': out['Success']}
|
||||
|
||||
return ret
|
||||
if out['FeatureResult']:
|
||||
return {'ExitCode': out['ExitCode'],
|
||||
'DisplayName': out['FeatureResult'][0]['DisplayName'],
|
||||
'RestartNeeded': out['FeatureResult'][0]['RestartNeeded'],
|
||||
'Success': out['Success']}
|
||||
else:
|
||||
return {'ExitCode': out['ExitCode'],
|
||||
'DisplayName': '{0} (already installed)'.format(feature),
|
||||
'RestartNeeded': False,
|
||||
'Success': out['Success']}
|
||||
|
||||
|
||||
def remove(feature):
|
||||
|
@ -206,9 +210,13 @@ def remove(feature):
|
|||
'-WarningAction SilentlyContinue'.format(_cmd_quote(feature))
|
||||
out = _pshell_json(cmd)
|
||||
|
||||
ret = {'ExitCode': out['ExitCode'],
|
||||
'DisplayName': out['FeatureResult'][0]['DisplayName'],
|
||||
'RestartNeeded': out['FeatureResult'][0]['RestartNeeded'],
|
||||
'Success': out['Success']}
|
||||
|
||||
return ret
|
||||
if out['FeatureResult']:
|
||||
return {'ExitCode': out['ExitCode'],
|
||||
'DisplayName': out['FeatureResult'][0]['DisplayName'],
|
||||
'RestartNeeded': out['FeatureResult'][0]['RestartNeeded'],
|
||||
'Success': out['Success']}
|
||||
else:
|
||||
return {'ExitCode': out['ExitCode'],
|
||||
'DisplayName': '{0} (not installed)'.format(feature),
|
||||
'RestartNeeded': False,
|
||||
'Success': out['Success']}
|
||||
|
|
|
@ -1333,7 +1333,10 @@ def upgrade(refresh=True,
|
|||
except MinionError as exc:
|
||||
raise CommandExecutionError(exc)
|
||||
|
||||
targets = [x for x in pkg_params]
|
||||
if pkg_params:
|
||||
targets = [x for x in pkg_params]
|
||||
else:
|
||||
targets = None
|
||||
|
||||
cmd = [_yum(), '--quiet', '-y']
|
||||
for args in (repo_arg, exclude_arg, branch_arg):
|
||||
|
@ -1342,7 +1345,8 @@ def upgrade(refresh=True,
|
|||
if skip_verify:
|
||||
cmd.append('--nogpgcheck')
|
||||
cmd.append('upgrade')
|
||||
cmd.extend(targets)
|
||||
if targets:
|
||||
cmd.extend(targets)
|
||||
|
||||
__salt__['cmd.run'](cmd, output_loglevel='trace', python_shell=False)
|
||||
__context__.pop('pkg.list_pkgs', None)
|
||||
|
|
|
@ -769,6 +769,7 @@ def mod_repo(repo, **kwargs):
|
|||
|
||||
# Modify added or existing repo according to the options
|
||||
cmd_opt = []
|
||||
global_cmd_opt = []
|
||||
|
||||
if 'enabled' in kwargs:
|
||||
cmd_opt.append(kwargs['enabled'] and '--enable' or '--disable')
|
||||
|
@ -784,18 +785,18 @@ def mod_repo(repo, **kwargs):
|
|||
if 'gpgcheck' in kwargs:
|
||||
cmd_opt.append(kwargs['gpgcheck'] and '--gpgcheck' or '--no-gpgcheck')
|
||||
|
||||
if kwargs.get('gpgautoimport') is True:
|
||||
cmd_opt.append('--gpg-auto-import-keys')
|
||||
|
||||
if 'priority' in kwargs:
|
||||
cmd_opt.append("--priority={0}".format(kwargs.get('priority', DEFAULT_PRIORITY)))
|
||||
|
||||
if 'humanname' in kwargs:
|
||||
cmd_opt.append("--name='{0}'".format(kwargs.get('humanname')))
|
||||
|
||||
if kwargs.get('gpgautoimport') is True:
|
||||
global_cmd_opt.append('--gpg-auto-import-keys')
|
||||
|
||||
if cmd_opt:
|
||||
cmd_opt.append(repo)
|
||||
__zypper__.refreshable.xml.call('mr', *cmd_opt)
|
||||
cmd_opt = global_cmd_opt + ['mr'] + cmd_opt + [repo]
|
||||
__zypper__.refreshable.xml.call(*cmd_opt)
|
||||
|
||||
# If repo nor added neither modified, error should be thrown
|
||||
if not added and not cmd_opt:
|
||||
|
@ -1583,6 +1584,9 @@ def download(*packages, **kwargs):
|
|||
pkg_ret[key] = pkg_info
|
||||
|
||||
if pkg_ret:
|
||||
failed = [pkg for pkg in packages if pkg not in pkg_ret]
|
||||
if failed:
|
||||
pkg_ret['_error'] = ('The following package(s) failed to download: {0}'.format(', '.join(failed)))
|
||||
return pkg_ret
|
||||
|
||||
raise CommandExecutionError(
|
||||
|
|
|
@ -242,6 +242,54 @@ external template file.
|
|||
following tags: `macro`, `set`, `load_yaml`, `load_json`, `import_yaml` and
|
||||
`import_json`.
|
||||
|
||||
Escaping Jinja
|
||||
==============
|
||||
|
||||
Occasionally, it may be necessary to escape Jinja syntax. There are two ways to
|
||||
to do this in Jinja. One is escaping individual variables or strings and the
|
||||
other is to escape entire blocks.
|
||||
|
||||
To escape a string commonly used in Jinja syntax such as ``{{``, you can use the
|
||||
following syntax:
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{{ '{{' }}
|
||||
|
||||
For larger blocks that contain Jinja syntax that needs to be escaped, you can use
|
||||
raw blocks:
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{% raw %]
|
||||
some text that contains jinja characters that need to be escaped
|
||||
{% endraw %}
|
||||
|
||||
See the `Escaping`_ section of Jinja's documentation to learn more.
|
||||
|
||||
A real-word example of needing to use raw tags to escape a larger block of code
|
||||
is when using ``file.managed`` with the ``contents_pillar`` option to manage
|
||||
files that contain something like consul-template, which shares a syntax subset
|
||||
with Jinja. Raw blocks are necessary here because the Jinja in the pillar would
|
||||
be rendered before the file.managed is ever called, so the Jinja syntax must be
|
||||
escaped:
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{% raw %}
|
||||
- contents_pillar: |
|
||||
job "example-job" {
|
||||
<snipped>
|
||||
task "example" {
|
||||
driver = "docker"
|
||||
|
||||
config {
|
||||
image = "docker-registry.service.consul:5000/example-job:{{key "nomad/jobs/example-job/version"}}"
|
||||
<snipped>
|
||||
{% endraw %}
|
||||
|
||||
.. _`Escaping`: http://jinja.pocoo.org/docs/dev/templates/#escaping
|
||||
|
||||
Calling Salt Functions
|
||||
======================
|
||||
|
||||
|
|
|
@ -419,14 +419,14 @@ def clean_old_jobs():
|
|||
for final in t_path_dirs:
|
||||
f_path = os.path.join(t_path, final)
|
||||
jid_file = os.path.join(f_path, 'jid')
|
||||
if not os.path.isfile(jid_file):
|
||||
if not os.path.isfile(jid_file) and os.path.exists(t_path):
|
||||
# No jid file means corrupted cache entry, scrub it
|
||||
# by removing the entire t_path directory
|
||||
shutil.rmtree(t_path)
|
||||
else:
|
||||
elif os.path.isfile(jid_file):
|
||||
jid_ctime = os.stat(jid_file).st_ctime
|
||||
hours_difference = (cur - jid_ctime) / 3600.0
|
||||
if hours_difference > __opts__['keep_jobs']:
|
||||
if hours_difference > __opts__['keep_jobs'] and os.path.exists(t_path):
|
||||
# Remove the entire t_path from the original JID dir
|
||||
shutil.rmtree(t_path)
|
||||
|
||||
|
|
|
@ -349,9 +349,13 @@ def list_jobs(ext_source=None,
|
|||
if search_target and _match:
|
||||
_match = False
|
||||
if 'Target' in ret[item]:
|
||||
for key in salt.utils.split_input(search_target):
|
||||
if fnmatch.fnmatch(ret[item]['Target'], key):
|
||||
_match = True
|
||||
targets = ret[item]['Target']
|
||||
if isinstance(targets, six.string_types):
|
||||
targets = [targets]
|
||||
for target in targets:
|
||||
for key in salt.utils.split_input(search_target):
|
||||
if fnmatch.fnmatch(target, key):
|
||||
_match = True
|
||||
|
||||
if search_function and _match:
|
||||
_match = False
|
||||
|
@ -488,6 +492,33 @@ def print_job(jid, ext_source=None, outputter=None):
|
|||
return ret
|
||||
|
||||
|
||||
def exit_success(jid, ext_source=None):
|
||||
'''
|
||||
Check if a job has been executed and exit successfully
|
||||
|
||||
jid
|
||||
The jid to look up.
|
||||
ext_source
|
||||
The external job cache to use. Default: `None`.
|
||||
|
||||
CLI Example:
|
||||
.. code-block:: bash
|
||||
salt-run jobs.exit_success 20160520145827701627
|
||||
'''
|
||||
ret = dict()
|
||||
|
||||
data = lookup_jid(
|
||||
jid,
|
||||
ext_source=ext_source
|
||||
)
|
||||
|
||||
for minion in data:
|
||||
if "retcode" in data[minion]:
|
||||
ret[minion] = True if not data[minion]['retcode'] else False
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def last_run(ext_source=None,
|
||||
outputter=None,
|
||||
metadata=None,
|
||||
|
|
|
@ -72,7 +72,7 @@ def enable(name):
|
|||
Name of the Apache module
|
||||
'''
|
||||
salt.utils.warn_until(
|
||||
'Carbon',
|
||||
'Nitrogen',
|
||||
'This functionality has been deprecated; use "apache_module.enabled" '
|
||||
'instead.'
|
||||
)
|
||||
|
@ -122,7 +122,7 @@ def disable(name):
|
|||
Name of the Apache module
|
||||
'''
|
||||
salt.utils.warn_until(
|
||||
'Carbon',
|
||||
'Nitrogen',
|
||||
'This functionality has been deprecated; use "apache_module.disabled" '
|
||||
'instead.'
|
||||
)
|
||||
|
|
|
@ -498,7 +498,7 @@ def wait(name,
|
|||
'Replace them with runas. '
|
||||
'These arguments will be removed in Salt Oxygen.'
|
||||
)
|
||||
if kwargs['user'] is not None and runas is None:
|
||||
if 'user' in kwargs and kwargs['user'] is not None and runas is None:
|
||||
runas = kwargs.pop('user')
|
||||
|
||||
# Ignoring our arguments is intentional.
|
||||
|
@ -629,7 +629,7 @@ def wait_script(name,
|
|||
'Replace them with runas. '
|
||||
'These arguments will be removed in Salt Oxygen.'
|
||||
)
|
||||
if kwargs['user'] is not None and runas is None:
|
||||
if 'user' in kwargs and kwargs['user'] is not None and runas is None:
|
||||
runas = kwargs.pop('user')
|
||||
|
||||
# Ignoring our arguments is intentional.
|
||||
|
@ -812,17 +812,18 @@ def run(name,
|
|||
'Replace them with runas. '
|
||||
'These arguments will be removed in Salt Oxygen.'
|
||||
)
|
||||
if kwargs['user'] is not None and runas is None:
|
||||
if 'user' in kwargs and kwargs['user'] is not None and runas is None:
|
||||
runas = kwargs.pop('user')
|
||||
|
||||
cmd_kwargs = {'cwd': cwd,
|
||||
'runas': runas,
|
||||
'use_vt': use_vt,
|
||||
'shell': shell or __grains__['shell'],
|
||||
'env': env,
|
||||
'umask': umask,
|
||||
'output_loglevel': output_loglevel,
|
||||
'quiet': quiet}
|
||||
cmd_kwargs = copy.deepcopy(kwargs)
|
||||
cmd_kwargs.update({'cwd': cwd,
|
||||
'runas': runas,
|
||||
'use_vt': use_vt,
|
||||
'shell': shell or __grains__['shell'],
|
||||
'env': env,
|
||||
'umask': umask,
|
||||
'output_loglevel': output_loglevel,
|
||||
'quiet': quiet})
|
||||
|
||||
cret = mod_run_check(cmd_kwargs, onlyif, unless, creates)
|
||||
if isinstance(cret, dict):
|
||||
|
@ -1048,7 +1049,7 @@ def script(name,
|
|||
'Replace them with runas. '
|
||||
'These arguments will be removed in Salt Oxygen.'
|
||||
)
|
||||
if kwargs['user'] is not None and runas is None:
|
||||
if 'user' in kwargs and kwargs['user'] is not None and runas is None:
|
||||
runas = kwargs.pop('user')
|
||||
|
||||
cmd_kwargs = copy.deepcopy(kwargs)
|
||||
|
|
|
@ -10,11 +10,11 @@ Manage etcd Keys
|
|||
|
||||
This state module supports setting and removing keys from etcd.
|
||||
|
||||
Salt Master Configuration
|
||||
-------------------------
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
To work with an etcd server you must configure an etcd profile in the Salt
|
||||
Master configuration, for example:
|
||||
To work with an etcd server you must configure an etcd profile. The etcd config
|
||||
can be set in either the Salt Minion configuration file or in pillar:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
|
@ -22,14 +22,25 @@ Master configuration, for example:
|
|||
etcd.host: 127.0.0.1
|
||||
etcd.port: 4001
|
||||
|
||||
You can also configure etcd without a profile however it is recommended that
|
||||
you use profiles:
|
||||
It is technically possible to configure etcd without using a profile, but this
|
||||
is not considered to be a best practice, especially when multiple etcd servers
|
||||
or clusters are available.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
etcd.host: 127.0.0.1
|
||||
etcd.port: 4001
|
||||
|
||||
.. note::
|
||||
|
||||
The etcd configuration can also be set in the Salt Master config file,
|
||||
but in order to use any etcd configurations defined in the Salt Master
|
||||
config, the :conf_master:`pillar_opts` must be set to ``True``.
|
||||
|
||||
Be aware that setting ``pillar_opts`` to ``True`` has security implications
|
||||
as this makes all master configuration settings available in all minion's
|
||||
pillars.
|
||||
|
||||
Available Functions
|
||||
-------------------
|
||||
|
||||
|
|
|
@ -10,16 +10,19 @@ file on the minions, by default at: /etc/salt/grains
|
|||
Note: This does NOT override any grains set in the minion file.
|
||||
'''
|
||||
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import
|
||||
from salt.defaults import DEFAULT_TARGET_DELIM
|
||||
import re
|
||||
|
||||
# Import Salt libs
|
||||
from salt.defaults import DEFAULT_TARGET_DELIM
|
||||
|
||||
|
||||
def present(name, value, delimiter=DEFAULT_TARGET_DELIM, force=False):
|
||||
'''
|
||||
Ensure that a grain is set
|
||||
|
||||
.. versionchanged:: 2016.3.0
|
||||
.. versionchanged:: v2015.8.2
|
||||
|
||||
name
|
||||
The grain name
|
||||
|
@ -27,14 +30,16 @@ def present(name, value, delimiter=DEFAULT_TARGET_DELIM, force=False):
|
|||
value
|
||||
The value to set on the grain
|
||||
|
||||
:param force: If force is True, the existing grain will be overwritten
|
||||
force
|
||||
If force is True, the existing grain will be overwritten
|
||||
regardless of its existing or provided value type. Defaults to False
|
||||
|
||||
.. versionadded:: 2016.3.0
|
||||
.. versionadded:: v2015.8.2
|
||||
|
||||
:param delimiter: A delimiter different from the default can be provided.
|
||||
delimiter
|
||||
A delimiter different from the default can be provided.
|
||||
|
||||
.. versionadded:: 2016.3.0
|
||||
.. versionadded:: v2015.8.2
|
||||
|
||||
It is now capable to set a grain to a complex value (ie. lists and dicts)
|
||||
and supports nested grains as well.
|
||||
|
@ -59,11 +64,11 @@ def present(name, value, delimiter=DEFAULT_TARGET_DELIM, force=False):
|
|||
- name: icinga:Apache SSL
|
||||
- value:
|
||||
- command: check_https
|
||||
- params: -H localhost -p 443 -S
|
||||
- params: -H localhost -p 443 -S
|
||||
|
||||
with,a,custom,delimiter:
|
||||
grains.present:
|
||||
- value: yay
|
||||
- value: yay
|
||||
- delimiter: ,
|
||||
'''
|
||||
name = re.sub(delimiter, DEFAULT_TARGET_DELIM, name)
|
||||
|
@ -106,9 +111,10 @@ def list_present(name, value, delimiter=DEFAULT_TARGET_DELIM):
|
|||
value
|
||||
The value is present in the list type grain.
|
||||
|
||||
:param delimiter: A delimiter different from the default ``:`` can be provided.
|
||||
delimiter
|
||||
A delimiter different from the default ``:`` can be provided.
|
||||
|
||||
.. versionadded:: 2016.3.0
|
||||
.. versionadded:: v2015.8.2
|
||||
|
||||
The grain should be `list type <http://docs.python.org/2/tutorial/datastructures.html#data-structures>`_
|
||||
|
||||
|
@ -198,9 +204,10 @@ def list_absent(name, value, delimiter=DEFAULT_TARGET_DELIM):
|
|||
value
|
||||
The value to delete from the grain list.
|
||||
|
||||
:param delimiter: A delimiter different from the default ``:`` can be provided.
|
||||
delimiter
|
||||
A delimiter different from the default ``:`` can be provided.
|
||||
|
||||
.. versionadded:: 2016.3.0
|
||||
.. versionadded:: v2015.8.2
|
||||
|
||||
The grain should be `list type <http://docs.python.org/2/tutorial/datastructures.html#data-structures>`_
|
||||
|
||||
|
@ -273,19 +280,23 @@ def absent(name,
|
|||
name
|
||||
The grain name
|
||||
|
||||
:param destructive: If destructive is True, delete the entire grain. If
|
||||
destructive
|
||||
If destructive is True, delete the entire grain. If
|
||||
destructive is False, set the grain's value to None. Defaults to False.
|
||||
|
||||
:param force: If force is True, the existing grain will be overwritten
|
||||
force
|
||||
If force is True, the existing grain will be overwritten
|
||||
regardless of its existing or provided value type. Defaults to False
|
||||
|
||||
.. versionadded:: 2016.3.0
|
||||
.. versionadded:: v2015.8.2
|
||||
|
||||
:param delimiter: A delimiter different from the default can be provided.
|
||||
delimiter
|
||||
A delimiter different from the default can be provided.
|
||||
|
||||
.. versionadded:: 2016.3.0
|
||||
.. versionadded:: v2015.8.2
|
||||
|
||||
.. versionchanged:: v2015.8.2
|
||||
|
||||
.. versionchanged:: 2016.3.0
|
||||
This state now support nested grains and complex values. It is also more
|
||||
conservative: if a grain has a value that is a list or a dict, it will
|
||||
not be removed unless the `force` parameter is True.
|
||||
|
@ -367,13 +378,15 @@ def append(name, value, convert=False,
|
|||
value
|
||||
The value to append
|
||||
|
||||
:param convert: If convert is True, convert non-list contents into a list.
|
||||
convert
|
||||
If convert is True, convert non-list contents into a list.
|
||||
If convert is False and the grain contains non-list contents, an error
|
||||
is given. Defaults to False.
|
||||
|
||||
:param delimiter: A delimiter different from the default can be provided.
|
||||
delimiter
|
||||
A delimiter different from the default can be provided.
|
||||
|
||||
.. versionadded:: 2016.3.0
|
||||
.. versionadded:: v2015.8.2
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ def present(name,
|
|||
password_hash=None,
|
||||
allow_passwordless=False,
|
||||
unix_socket=False,
|
||||
password_column='Password',
|
||||
password_column=None,
|
||||
**connection_args):
|
||||
'''
|
||||
Ensure that the named user is present with the specified properties. A
|
||||
|
|
|
@ -274,6 +274,11 @@ def managed(name, ppa=None, **kwargs):
|
|||
ret['result'] = False
|
||||
ret['comment'] = 'You may not use both "keyid"/"keyserver" and ' \
|
||||
'"key_url" argument.'
|
||||
if 'repo' in kwargs:
|
||||
ret['result'] = False
|
||||
ret['comment'] = ('\'repo\' is not a supported argument for this '
|
||||
'state. The \'name\' argument is probably what was '
|
||||
'intended.')
|
||||
return ret
|
||||
|
||||
repo = name
|
||||
|
|
|
@ -66,13 +66,21 @@ import time
|
|||
from salt.exceptions import CommandExecutionError
|
||||
import salt.utils
|
||||
|
||||
__virtualname__ = 'service'
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Only make these states available if a service provider has been detected or
|
||||
assigned for this minion
|
||||
'''
|
||||
return 'service.start' in __salt__
|
||||
if 'service.start' in __salt__:
|
||||
return __virtualname__
|
||||
else:
|
||||
return (False, 'No service execution module loaded: '
|
||||
'check support for service management on {0} '
|
||||
''.format(__grains__.get('osfinger', __grains__['os']))
|
||||
)
|
||||
|
||||
|
||||
def _enabled_used_error(ret):
|
||||
|
@ -284,7 +292,7 @@ def _available(name, ret):
|
|||
|
||||
def running(name, enable=None, sig=None, init_delay=None, **kwargs):
|
||||
'''
|
||||
Verify that the service is running
|
||||
Ensure that the service is running
|
||||
|
||||
name
|
||||
The name of the init or rc script used to manage the service
|
||||
|
@ -478,7 +486,7 @@ def dead(name, enable=None, sig=None, **kwargs):
|
|||
|
||||
def enabled(name, **kwargs):
|
||||
'''
|
||||
Verify that the service is enabled on boot, only use this state if you
|
||||
Ensure that the service is enabled on boot, only use this state if you
|
||||
don't want to manage the running process, remember that if you want to
|
||||
enable a running service to use the enable: True option for the running
|
||||
or dead function.
|
||||
|
@ -497,7 +505,7 @@ def enabled(name, **kwargs):
|
|||
|
||||
def disabled(name, **kwargs):
|
||||
'''
|
||||
Verify that the service is disabled on boot, only use this state if you
|
||||
Ensure that the service is disabled on boot, only use this state if you
|
||||
don't want to manage the running process, remember that if you want to
|
||||
disable a service to use the enable: False option for the running or dead
|
||||
function.
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
Manage Windows features via the ServerManager powershell module
|
||||
'''
|
||||
|
||||
# Import salt modules
|
||||
import salt.utils
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
|
@ -52,10 +55,13 @@ def installed(name, recurse=False, force=False, restart=False):
|
|||
'comment': ''}
|
||||
|
||||
# Determine if the feature is installed
|
||||
if name not in __salt__['win_servermanager.list_installed']():
|
||||
ret['changes'] = {'feature': '{0} will be installed recurse={1}'.format(name, recurse)}
|
||||
old = __salt__['win_servermanager.list_installed']()
|
||||
if name not in old:
|
||||
ret['changes']['feature'] = \
|
||||
'{0} will be installed recurse={1}'.format(name, recurse)
|
||||
elif force and recurse:
|
||||
ret['changes'] = {'feature': '{0} already installed but might install sub-features'.format(name)}
|
||||
ret['changes']['feature'] = \
|
||||
'{0} already installed but might install sub-features'.format(name)
|
||||
else:
|
||||
ret['comment'] = 'The feature {0} is already installed'.format(name)
|
||||
return ret
|
||||
|
@ -64,19 +70,26 @@ def installed(name, recurse=False, force=False, restart=False):
|
|||
ret['result'] = None
|
||||
return ret
|
||||
|
||||
# Install the features
|
||||
ret['changes'] = {'feature': __salt__['win_servermanager.install'](name, recurse, restart)}
|
||||
if ret['changes']['feature']:
|
||||
ret['comment'] = ret['changes']['feature']
|
||||
|
||||
if 'Success' in ret['changes']['feature']:
|
||||
ret['result'] = ret['changes']['feature']['Success']
|
||||
if not ret['result']:
|
||||
ret['comment'] = 'Failed to install {0}: {1}'.format(name, ret['changes']['feature']['ExitCode'])
|
||||
else:
|
||||
ret['comment'] = 'Installed {0}'.format(name)
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Failed to install {0}.\nError Message:\n{1}'.format(name, ret['changes']['feature'])
|
||||
ret['changes'] = {}
|
||||
ret['changes'] = {}
|
||||
|
||||
# Install the features
|
||||
status = __salt__['win_servermanager.install'](name, recurse, restart)
|
||||
|
||||
ret['result'] = status['Success']
|
||||
if not ret['result']:
|
||||
ret['comment'] = 'Failed to install {0}: {1}'\
|
||||
.format(name, status['ExitCode'])
|
||||
|
||||
new = __salt__['win_servermanager.list_installed']()
|
||||
changes = salt.utils.compare_dicts(old, new)
|
||||
|
||||
if changes:
|
||||
ret['comment'] = 'Installed {0}'.format(name)
|
||||
ret['changes'] = status
|
||||
ret['changes']['feature'] = changes
|
||||
|
||||
return ret
|
||||
|
||||
|
@ -110,8 +123,9 @@ def removed(name):
|
|||
'changes': {},
|
||||
'comment': ''}
|
||||
# Determine if the feature is installed
|
||||
if name in __salt__['win_servermanager.list_installed']():
|
||||
ret['changes'] = {'feature': '{0} will be removed'.format(name)}
|
||||
old = __salt__['win_servermanager.list_installed']()
|
||||
if name in old:
|
||||
ret['changes']['feature'] = '{0} will be removed'.format(name)
|
||||
else:
|
||||
ret['comment'] = 'The feature {0} is not installed'.format(name)
|
||||
return ret
|
||||
|
@ -120,10 +134,22 @@ def removed(name):
|
|||
ret['result'] = None
|
||||
return ret
|
||||
|
||||
ret['changes'] = {}
|
||||
|
||||
# Remove the features
|
||||
ret['changes'] = {'feature': __salt__['win_servermanager.remove'](name)}
|
||||
ret['result'] = ret['changes']['feature']['Success']
|
||||
status = __salt__['win_servermanager.remove'](name)
|
||||
|
||||
ret['result'] = status['Success']
|
||||
if not ret['result']:
|
||||
ret['comment'] = 'Failed to uninstall the feature {0}'.format(ret['changes']['feature']['ExitCode'])
|
||||
ret['comment'] = 'Failed to uninstall the feature {0}'\
|
||||
.format(status['ExitCode'])
|
||||
|
||||
new = __salt__['win_servermanager.list_installed']()
|
||||
changes = salt.utils.compare_dicts(old, new)
|
||||
|
||||
if changes:
|
||||
ret['comment'] = 'Removed {0}'.format(name)
|
||||
ret['changes'] = status
|
||||
ret['changes']['feature'] = changes
|
||||
|
||||
return ret
|
||||
|
|
|
@ -63,7 +63,7 @@ class SaltCacheLoader(BaseLoader):
|
|||
self.searchpath = opts['file_roots'][saltenv]
|
||||
else:
|
||||
self.searchpath = [path.join(opts['cachedir'], 'files', saltenv)]
|
||||
log.debug('Jinja search path: \'{0}\''.format(self.searchpath))
|
||||
log.debug('Jinja search path: %s', self.searchpath)
|
||||
self._file_client = None
|
||||
self.cached = []
|
||||
self.pillar_rend = pillar_rend
|
||||
|
@ -154,10 +154,10 @@ class PrintableDict(OrderedDict):
|
|||
for key, value in six.iteritems(self):
|
||||
if isinstance(value, six.string_types):
|
||||
# keeps quotes around strings
|
||||
output.append('\'{0}\': \'{1}\''.format(key, value))
|
||||
output.append('{0!r}: {1!r}'.format(key, value))
|
||||
else:
|
||||
# let default output
|
||||
output.append('\'{0}\': {1!s}'.format(key, value))
|
||||
output.append('{0!r}: {1!s}'.format(key, value))
|
||||
return '{' + ', '.join(output) + '}'
|
||||
|
||||
def __repr__(self): # pylint: disable=W0221
|
||||
|
@ -165,7 +165,7 @@ class PrintableDict(OrderedDict):
|
|||
for key, value in six.iteritems(self):
|
||||
# Raw string formatter required here because this is a repr
|
||||
# function.
|
||||
output.append('\'{0}\': {1!r}'.format(key, value))
|
||||
output.append('{0!r}: {1!r}'.format(key, value))
|
||||
return '{' + ', '.join(output) + '}'
|
||||
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ SYS_TMP_DIR = os.path.realpath(
|
|||
# Avoid ${TMPDIR} and gettempdir() on MacOS as they yield a base path too long
|
||||
# for unix sockets: ``error: AF_UNIX path too long``
|
||||
# Gentoo Portage prefers ebuild tests are rooted in ${TMPDIR}
|
||||
os.environ.get('TMPDIR', tempfile.gettempdir()) if salt.utils.is_darwin() else '/tmp'
|
||||
os.environ.get('TMPDIR', tempfile.gettempdir()) if not salt.utils.is_darwin() else '/tmp'
|
||||
)
|
||||
TMP = os.path.join(SYS_TMP_DIR, 'salt-tests-tmpdir')
|
||||
FILES = os.path.join(INTEGRATION_TEST_DIR, 'files')
|
||||
|
|
|
@ -20,7 +20,7 @@ SYS_TMP_DIR = os.path.realpath(
|
|||
# Avoid ${TMPDIR} and gettempdir() on MacOS as they yield a base path too long
|
||||
# for unix sockets: ``error: AF_UNIX path too long``
|
||||
# Gentoo Portage prefers ebuild tests are rooted in ${TMPDIR}
|
||||
os.environ.get('TMPDIR', tempfile.gettempdir()) if salt.utils.is_darwin() else '/tmp'
|
||||
os.environ.get('TMPDIR', tempfile.gettempdir()) if not salt.utils.is_darwin() else '/tmp'
|
||||
)
|
||||
# This tempdir path is defined on tests.integration.__init__
|
||||
TMP = os.path.join(SYS_TMP_DIR, 'salt-tests-tmpdir')
|
||||
|
|
|
@ -12,7 +12,6 @@ from salttesting import skipIf
|
|||
from salttesting.helpers import (
|
||||
destructiveTest,
|
||||
ensure_in_syspath,
|
||||
requires_system_grains
|
||||
)
|
||||
ensure_in_syspath('../../')
|
||||
|
||||
|
@ -21,12 +20,17 @@ import integration
|
|||
import salt.utils
|
||||
from salt.exceptions import CommandExecutionError
|
||||
|
||||
# Import third party libs
|
||||
import salt.ext.six as six
|
||||
|
||||
# Brew doesn't support local package installation - So, let's
|
||||
# Grab some small packages available online for brew
|
||||
ADD_PKG = 'algol68g'
|
||||
DEL_PKG = 'acme'
|
||||
|
||||
|
||||
@destructiveTest
|
||||
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
|
||||
class BrewModuleTest(integration.ModuleCase):
|
||||
'''
|
||||
Integration tests for the brew module
|
||||
|
@ -51,10 +55,7 @@ class BrewModuleTest(integration.ModuleCase):
|
|||
'You must have brew installed to run these tests'
|
||||
)
|
||||
|
||||
@destructiveTest
|
||||
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
|
||||
@requires_system_grains
|
||||
def test_brew_install(self, grains=None):
|
||||
def test_brew_install(self):
|
||||
'''
|
||||
Tests the installation of packages
|
||||
'''
|
||||
|
@ -70,10 +71,7 @@ class BrewModuleTest(integration.ModuleCase):
|
|||
self.run_function('pkg.remove', [ADD_PKG])
|
||||
raise
|
||||
|
||||
@destructiveTest
|
||||
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
|
||||
@requires_system_grains
|
||||
def test_remove(self, grains=None):
|
||||
def test_remove(self):
|
||||
'''
|
||||
Tests the removal of packages
|
||||
'''
|
||||
|
@ -96,10 +94,7 @@ class BrewModuleTest(integration.ModuleCase):
|
|||
self.run_function('pkg.remove', [DEL_PKG])
|
||||
raise
|
||||
|
||||
@destructiveTest
|
||||
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
|
||||
@requires_system_grains
|
||||
def test_mac_brew_pkg_version(self, grains=None):
|
||||
def test_version(self):
|
||||
'''
|
||||
Test pkg.version for mac. Installs
|
||||
a package and then checks we can get
|
||||
|
@ -129,20 +124,79 @@ class BrewModuleTest(integration.ModuleCase):
|
|||
self.run_function('pkg.remove', [ADD_PKG])
|
||||
raise
|
||||
|
||||
@destructiveTest
|
||||
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
|
||||
@requires_system_grains
|
||||
def test_mac_brew_refresh_db(self, grains=None):
|
||||
def test_latest_version(self):
|
||||
'''
|
||||
Test pkg.latest_version:
|
||||
- get the latest version available
|
||||
- install the package
|
||||
- get the latest version available
|
||||
- check that the latest version is empty after installing it
|
||||
'''
|
||||
try:
|
||||
self.run_function('pkg.remove', [ADD_PKG])
|
||||
uninstalled_latest = self.run_function('pkg.latest_version', [ADD_PKG])
|
||||
|
||||
self.run_function('pkg.install', [ADD_PKG])
|
||||
installed_latest = self.run_function('pkg.latest_version', [ADD_PKG])
|
||||
version = self.run_function('pkg.version', [ADD_PKG])
|
||||
try:
|
||||
self.assertTrue(isinstance(uninstalled_latest, six.string_types))
|
||||
self.assertEqual(installed_latest, '')
|
||||
except AssertionError:
|
||||
self.run_function('pkg.remove', [ADD_PKG])
|
||||
raise
|
||||
except CommandExecutionError:
|
||||
self.run_function('pkg.remove', [ADD_PKG])
|
||||
raise
|
||||
|
||||
def test_refresh_db(self):
|
||||
'''
|
||||
Integration test to ensure pkg.refresh_db works with brew
|
||||
'''
|
||||
refresh_brew = self.run_function('pkg.refresh_db')
|
||||
self.assertTrue(refresh_brew)
|
||||
|
||||
@destructiveTest
|
||||
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
|
||||
@requires_system_grains
|
||||
def tearDown(self, grains=None):
|
||||
def test_list_upgrades(self):
|
||||
'''
|
||||
Test pkg.list_upgrades: data is in the form {'name1': 'version1',
|
||||
'name2': 'version2', ... }
|
||||
'''
|
||||
try:
|
||||
upgrades = self.run_function('pkg.list_upgrades')
|
||||
try:
|
||||
self.assertTrue(isinstance(upgrades, dict))
|
||||
if len(upgrades):
|
||||
for name in upgrades:
|
||||
self.assertTrue(isinstance(name, six.string_types))
|
||||
self.assertTrue(isinstance(upgrades[name], six.string_types))
|
||||
except AssertionError:
|
||||
self.run_function('pkg.remove', [ADD_PKG])
|
||||
raise
|
||||
except CommandExecutionError:
|
||||
self.run_function('pkg.remove', [ADD_PKG])
|
||||
raise
|
||||
|
||||
def test_info_installed(self):
|
||||
'''
|
||||
Test pkg.info_installed: info returned has certain fields used by
|
||||
mac_brew.latest_version
|
||||
'''
|
||||
try:
|
||||
self.run_function('pkg.install', [ADD_PKG])
|
||||
info = self.run_function('pkg.info_installed', [ADD_PKG])
|
||||
try:
|
||||
self.assertTrue(ADD_PKG in info)
|
||||
self.assertTrue('versions' in info[ADD_PKG])
|
||||
self.assertTrue('revision' in info[ADD_PKG])
|
||||
self.assertTrue('stable' in info[ADD_PKG]['versions'])
|
||||
except AssertionError:
|
||||
self.run_function('pkg.remove', [ADD_PKG])
|
||||
raise
|
||||
except CommandExecutionError:
|
||||
self.run_function('pkg.remove', [ADD_PKG])
|
||||
raise
|
||||
|
||||
def tearDown(self):
|
||||
'''
|
||||
Clean up after tests
|
||||
'''
|
||||
|
|
59
tests/integration/modules/saltutil.py
Normal file
59
tests/integration/modules/saltutil.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Integration tests for the saltutil module.
|
||||
'''
|
||||
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import
|
||||
|
||||
# Import Salt Testing libs
|
||||
from salttesting.helpers import ensure_in_syspath
|
||||
|
||||
ensure_in_syspath('../../')
|
||||
|
||||
# Import Salt libs
|
||||
import integration
|
||||
|
||||
|
||||
class SaltUtilModuleTest(integration.ModuleCase):
|
||||
'''
|
||||
Testcase for the saltutil execution module
|
||||
'''
|
||||
|
||||
# Tests for the wheel function
|
||||
|
||||
def test_wheel_just_function(self):
|
||||
'''
|
||||
Tests using the saltutil.wheel function when passing only a function.
|
||||
'''
|
||||
ret = self.run_function('saltutil.wheel', ['minions.connected'])
|
||||
self.assertEqual(ret, ['minion', 'sub_minion'])
|
||||
|
||||
def test_wheel_with_arg(self):
|
||||
'''
|
||||
Tests using the saltutil.wheel function when passing a function and an arg.
|
||||
'''
|
||||
ret = self.run_function('saltutil.wheel', ['key.list', 'minion'])
|
||||
self.assertEqual(ret, {})
|
||||
|
||||
def test_wheel_no_arg_raise_error(self):
|
||||
'''
|
||||
Tests using the saltutil.wheel function when passing a function that requires
|
||||
an arg, but one isn't supplied.
|
||||
'''
|
||||
self.assertRaises(TypeError, 'saltutil.wheel', ['key.list'])
|
||||
|
||||
def test_wheel_with_kwarg(self):
|
||||
'''
|
||||
Tests using the saltutil.wheel function when passing a function and a kwarg.
|
||||
This function just generates a key pair, but doesn't do anything with it. We
|
||||
just need this for testing purposes.
|
||||
'''
|
||||
ret = self.run_function('saltutil.wheel', ['key.gen'], keysize=1024)
|
||||
self.assertIn('pub', ret)
|
||||
self.assertIn('priv', ret)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
run_tests(SaltUtilModuleTest)
|
|
@ -87,6 +87,7 @@ class SysModuleTest(integration.ModuleCase):
|
|||
'runtests_decorators.depends_will_fallback',
|
||||
'runtests_decorators.missing_depends',
|
||||
'runtests_decorators.missing_depends_will_fallback',
|
||||
'swift.head',
|
||||
'yumpkg.expand_repo_def',
|
||||
'yumpkg5.expand_repo_def',
|
||||
'container_resource.run',
|
||||
|
|
|
@ -8,12 +8,11 @@ Discover all instances of unittest.TestCase in this directory.
|
|||
# Import python libs
|
||||
from __future__ import absolute_import, print_function
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
# Import salt libs
|
||||
from integration import TestDaemon, TMP # pylint: disable=W0403
|
||||
from integration import INTEGRATION_TEST_DIR
|
||||
from integration import SYS_TMP_DIR, INTEGRATION_TEST_DIR
|
||||
from integration import CODE_DIR as SALT_ROOT
|
||||
import salt.utils
|
||||
|
||||
|
@ -563,7 +562,7 @@ def main():
|
|||
parser = SaltTestsuiteParser(
|
||||
TEST_DIR,
|
||||
xml_output_dir=XML_OUTPUT_DIR,
|
||||
tests_logfile=os.path.join(tempfile.gettempdir(), 'salt-runtests.log')
|
||||
tests_logfile=os.path.join(SYS_TMP_DIR, 'salt-runtests.log')
|
||||
)
|
||||
parser.parse_args()
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ from __future__ import absolute_import
|
|||
|
||||
# Import Salt Libs
|
||||
from salt.modules import mac_brew
|
||||
from salt.exceptions import CommandExecutionError
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from salttesting import skipIf, TestCase
|
||||
|
@ -38,7 +39,8 @@ class BrewTestCase(TestCase):
|
|||
'''
|
||||
Tests the return of the list of taps
|
||||
'''
|
||||
mock_taps = MagicMock(return_value={'stdout': TAPS_STRING})
|
||||
mock_taps = MagicMock(return_value={'stdout': TAPS_STRING,
|
||||
'retcode': 0})
|
||||
mock_user = MagicMock(return_value='foo')
|
||||
mock_cmd = MagicMock(return_value='')
|
||||
with patch.dict(mac_brew.__salt__, {'file.get_user': mock_user,
|
||||
|
@ -60,7 +62,9 @@ class BrewTestCase(TestCase):
|
|||
'''
|
||||
Tests if the tap installation failed
|
||||
'''
|
||||
mock_failure = MagicMock(return_value={'retcode': 1})
|
||||
mock_failure = MagicMock(return_value={'stdout': '',
|
||||
'stderr': '',
|
||||
'retcode': 1})
|
||||
mock_user = MagicMock(return_value='foo')
|
||||
mock_cmd = MagicMock(return_value='')
|
||||
with patch.dict(mac_brew.__salt__, {'cmd.run_all': mock_failure,
|
||||
|
@ -147,10 +151,12 @@ class BrewTestCase(TestCase):
|
|||
Tests an update of homebrew package repository failure
|
||||
'''
|
||||
mock_user = MagicMock(return_value='foo')
|
||||
mock_failure = MagicMock(return_value={'retcode': 1})
|
||||
mock_failure = MagicMock(return_value={'stdout': '',
|
||||
'stderr': '',
|
||||
'retcode': 1})
|
||||
with patch.dict(mac_brew.__salt__, {'file.get_user': mock_user,
|
||||
'cmd.run_all': mock_failure}):
|
||||
self.assertFalse(mac_brew.refresh_db())
|
||||
self.assertRaises(CommandExecutionError, mac_brew.refresh_db)
|
||||
|
||||
@patch('salt.modules.mac_brew._homebrew_bin',
|
||||
MagicMock(return_value=HOMEBREW_BIN))
|
||||
|
|
|
@ -98,6 +98,22 @@ class RpmTestCase(TestCase):
|
|||
self.assertDictEqual(rpm.owner('/usr/bin/python', '/usr/bin/vim'),
|
||||
ret)
|
||||
|
||||
# 'checksum' function tests: 1
|
||||
|
||||
def test_checksum(self):
|
||||
'''
|
||||
Test if checksum validate as expected
|
||||
'''
|
||||
ret = {
|
||||
"file1.rpm": True,
|
||||
"file2.rpm": False,
|
||||
"file3.rpm": False,
|
||||
}
|
||||
|
||||
mock = MagicMock(side_effect=[True, 0, True, 1, False, 0])
|
||||
with patch.dict(rpm.__salt__, {'file.file_exists': mock, 'cmd.retcode': mock}):
|
||||
self.assertDictEqual(rpm.checksum("file1.rpm", "file2.rpm", "file3.rpm"), ret)
|
||||
|
||||
@patch('salt.modules.rpm.HAS_RPM', True)
|
||||
def test_version_cmp_rpm(self):
|
||||
'''
|
||||
|
|
19
tests/unit/modules/zypp/zypper-download.xml
Normal file
19
tests/unit/modules/zypp/zypper-download.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version='1.0'?>
|
||||
<stream>
|
||||
<message type="info">Loading repository data...</message>
|
||||
<message type="info">Reading installed packages...</message>
|
||||
<message type="warning">Argument resolves to no package: foo</message>
|
||||
<progress id="" name="(1/1) /var/cache/zypp/packages/SLE-12-x86_64-Pool/x86_64/nmap-6.46-1.72.x86_64.rpm"/>
|
||||
<download-result>
|
||||
<solvable>
|
||||
<kind>package</kind>
|
||||
<name>nmap</name>
|
||||
<edition epoch="0" version="6.46" release="1.72"/>
|
||||
<arch>x86_64</arch>
|
||||
<repository name="SLE-12-x86_64-Pool" alias="SLE-12-x86_64-Pool"/>
|
||||
</solvable>
|
||||
<localfile path="/var/cache/zypp/packages/SLE-12-x86_64-Pool/x86_64/nmap-6.46-1.72.x86_64.rpm"/>
|
||||
</download-result>
|
||||
<progress id="" name="(1/1) /var/cache/zypp/packages/SLE-12-x86_64-Pool/x86_64/nmap-6.46-1.72.x86_64.rpm" done="0"/>
|
||||
<message type="info">download: Done.</message>
|
||||
</stream>
|
|
@ -9,6 +9,7 @@ from __future__ import absolute_import
|
|||
# Import Salt Testing Libs
|
||||
from salttesting import TestCase, skipIf
|
||||
from salttesting.mock import (
|
||||
Mock,
|
||||
MagicMock,
|
||||
patch,
|
||||
NO_MOCK,
|
||||
|
@ -354,6 +355,30 @@ class ZypperTestCase(TestCase):
|
|||
self.assertTrue(pkgs.get(pkg_name))
|
||||
self.assertEqual(pkgs[pkg_name], pkg_version)
|
||||
|
||||
def test_download(self):
|
||||
'''
|
||||
Test package download
|
||||
:return:
|
||||
'''
|
||||
download_out = {
|
||||
'stdout': get_test_data('zypper-download.xml'),
|
||||
'stderr': None,
|
||||
'retcode': 0
|
||||
}
|
||||
|
||||
test_out = {
|
||||
'nmap': {
|
||||
'repository-alias': u'SLE-12-x86_64-Pool',
|
||||
'repository-name': u'SLE-12-x86_64-Pool'
|
||||
}
|
||||
}
|
||||
|
||||
with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=download_out)}):
|
||||
with patch.dict(zypper.__salt__, {'lowpkg.checksum': MagicMock(return_value=True)}):
|
||||
self.assertEqual(zypper.download("nmap"), test_out)
|
||||
test_out['_error'] = "The following package(s) failed to download: foo"
|
||||
self.assertEqual(zypper.download("nmap", "foo"), test_out)
|
||||
|
||||
def test_remove_purge(self):
|
||||
'''
|
||||
Test package removal
|
||||
|
@ -413,6 +438,39 @@ class ZypperTestCase(TestCase):
|
|||
self.assertEqual(r_info['enabled'], alias == 'SLE12-SP1-x86_64-Update')
|
||||
self.assertEqual(r_info['autorefresh'], alias == 'SLE12-SP1-x86_64-Update')
|
||||
|
||||
def test_modify_repo_gpg_auto_import_keys_parameter_position(self):
|
||||
'''
|
||||
Tests if when modifying a repo, --gpg-auto-import-keys is a global option
|
||||
|
||||
:return:
|
||||
'''
|
||||
zypper_patcher = patch.multiple(
|
||||
'salt.modules.zypper',
|
||||
**{
|
||||
'_get_configured_repos': Mock(
|
||||
**{
|
||||
'return_value.sections.return_value': ['mock-repo-name']
|
||||
}
|
||||
),
|
||||
'__zypper__': Mock(),
|
||||
'get_repo': Mock()
|
||||
}
|
||||
)
|
||||
with zypper_patcher:
|
||||
zypper.mod_repo(
|
||||
'mock-repo-name',
|
||||
**{
|
||||
'disabled': False,
|
||||
'url': 'http://repo.url/some/path',
|
||||
'gpgkey': 'http://repo.key',
|
||||
'refresh': True,
|
||||
'gpgautoimport': True
|
||||
}
|
||||
)
|
||||
zypper.__zypper__.refreshable.xml.call.assert_called_once_with(
|
||||
'--gpg-auto-import-keys', 'mr', '--refresh', 'mock-repo-name'
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
run_tests(ZypperTestCase, needs_daemon=False)
|
||||
|
|
87
tests/unit/runners/jobs_test.py
Normal file
87
tests/unit/runners/jobs_test.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
unit tests for the jobs runner
|
||||
'''
|
||||
|
||||
# Import Python Libs
|
||||
from __future__ import absolute_import
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from salttesting import skipIf, TestCase
|
||||
from salttesting.helpers import ensure_in_syspath
|
||||
from salttesting.mock import (
|
||||
NO_MOCK,
|
||||
NO_MOCK_REASON,
|
||||
patch
|
||||
)
|
||||
|
||||
ensure_in_syspath('../../')
|
||||
|
||||
# Import Salt Libs
|
||||
from salt.runners import jobs
|
||||
import salt.minion
|
||||
|
||||
jobs.__opts__ = {'ext_job_cache': None, 'master_job_cache': 'local_cache'}
|
||||
jobs.__salt__ = {}
|
||||
|
||||
|
||||
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
||||
class JobsTest(TestCase):
|
||||
'''
|
||||
Validate the jobs runner
|
||||
'''
|
||||
def test_list_jobs_with_search_target(self):
|
||||
'''
|
||||
test jobs.list_jobs runner with search_target args
|
||||
'''
|
||||
mock_jobs_cache = {
|
||||
'20160524035503086853': {'Arguments': [],
|
||||
'Function': 'test.ping',
|
||||
'StartTime': '2016, May 24 03:55:03.086853',
|
||||
'Target': 'node-1-1.com',
|
||||
'Target-type': 'glob',
|
||||
'User': 'root'},
|
||||
'20160524035524895387': {'Arguments': [],
|
||||
'Function': 'test.ping',
|
||||
'StartTime': '2016, May 24 03:55:24.895387',
|
||||
'Target': ['node-1-2.com', 'node-1-1.com'],
|
||||
'Target-type': 'list',
|
||||
'User': 'sudo_ubuntu'}
|
||||
}
|
||||
|
||||
def return_mock_jobs():
|
||||
return mock_jobs_cache
|
||||
|
||||
class MockMasterMinion(object):
|
||||
|
||||
returners = {'local_cache.get_jids': return_mock_jobs}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
returns = {'all': mock_jobs_cache,
|
||||
'node-1-1.com': mock_jobs_cache,
|
||||
'node-1-2.com': {'20160524035524895387':
|
||||
mock_jobs_cache['20160524035524895387']},
|
||||
'non-existant': {}}
|
||||
|
||||
with patch.object(salt.minion, 'MasterMinion', MockMasterMinion):
|
||||
self.assertEqual(jobs.list_jobs(), returns['all'])
|
||||
|
||||
self.assertEqual(jobs.list_jobs(search_target=['node-1-1*',
|
||||
'node-1-2*']),
|
||||
returns['all'])
|
||||
|
||||
self.assertEqual(jobs.list_jobs(search_target='node-1-1.com'),
|
||||
returns['node-1-1.com'])
|
||||
|
||||
self.assertEqual(jobs.list_jobs(search_target='node-1-2.com'),
|
||||
returns['node-1-2.com'])
|
||||
|
||||
self.assertEqual(jobs.list_jobs(search_target='non-existant'),
|
||||
returns['non-existant'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
run_tests(JobsTest, needs_daemon=False)
|
|
@ -35,58 +35,92 @@ class WinServermanagerTestCase(TestCase):
|
|||
'''
|
||||
Test to install the windows feature
|
||||
'''
|
||||
ret = {'name': 'salt',
|
||||
'changes': {},
|
||||
'result': True,
|
||||
'comment': ''}
|
||||
mock = MagicMock(side_effect=['salt', 'stack', 'stack'])
|
||||
mock1 = MagicMock(return_value={'Success': True})
|
||||
mock_list = MagicMock(
|
||||
side_effect=[{'spongebob': 'squarepants'},
|
||||
{'squidward': 'patrick'},
|
||||
{'spongebob': 'squarepants'},
|
||||
{'spongebob': 'squarepants',
|
||||
'squidward': 'patrick'}])
|
||||
mock_install = MagicMock(
|
||||
return_value={'Success': True,
|
||||
'RestartNeeded': False,
|
||||
'ExitCode': 1234})
|
||||
with patch.dict(win_servermanager.__salt__,
|
||||
{"win_servermanager.list_installed": mock,
|
||||
"win_servermanager.install": mock1}):
|
||||
ret.update({'comment': 'The feature salt is already installed'})
|
||||
self.assertDictEqual(win_servermanager.installed('salt'), ret)
|
||||
{"win_servermanager.list_installed": mock_list,
|
||||
"win_servermanager.install": mock_install}):
|
||||
ret = {'name': 'spongebob',
|
||||
'changes': {},
|
||||
'result': True,
|
||||
'comment': 'The feature spongebob is already installed'}
|
||||
self.assertDictEqual(win_servermanager.installed('spongebob'), ret)
|
||||
|
||||
with patch.dict(win_servermanager.__opts__, {"test": True}):
|
||||
ret.update({'changes': {'feature':
|
||||
'salt will be installed'
|
||||
' recurse=False'}, 'result': None,
|
||||
'comment': ''})
|
||||
self.assertDictEqual(win_servermanager.installed('salt'), ret)
|
||||
ret = {'name': 'spongebob',
|
||||
'result': None,
|
||||
'comment': '',
|
||||
'changes': {
|
||||
'feature': 'spongebob will be installed '
|
||||
'recurse=False'}}
|
||||
self.assertDictEqual(
|
||||
win_servermanager.installed('spongebob'), ret)
|
||||
|
||||
with patch.dict(win_servermanager.__opts__, {"test": False}):
|
||||
ret.update({'changes': {'feature': {'Success': True}},
|
||||
'result': True, 'comment': 'Installed salt'})
|
||||
self.assertDictEqual(win_servermanager.installed('salt'),
|
||||
ret)
|
||||
with patch.dict(win_servermanager.__opts__, {"test": False}):
|
||||
ret = {'name': 'squidward',
|
||||
'result': True,
|
||||
'comment': 'Installed squidward',
|
||||
'changes': {
|
||||
'Success': True,
|
||||
'RestartNeeded': False,
|
||||
'ExitCode': 1234,
|
||||
'feature': {'squidward': {'new': 'patrick',
|
||||
'old': ''}}}}
|
||||
self.assertDictEqual(
|
||||
win_servermanager.installed('squidward'), ret)
|
||||
|
||||
def test_removed(self):
|
||||
'''
|
||||
Test to remove the windows feature
|
||||
'''
|
||||
ret = {'name': 'salt',
|
||||
'changes': {},
|
||||
'result': True,
|
||||
'comment': ''}
|
||||
mock = MagicMock(side_effect=['stack', 'salt', 'salt'])
|
||||
mock1 = MagicMock(return_value={'Success': True})
|
||||
mock_list = MagicMock(
|
||||
side_effect=[{'spongebob': 'squarepants'},
|
||||
{'squidward': 'patrick'},
|
||||
{'spongebob': 'squarepants',
|
||||
'squidward': 'patrick'},
|
||||
{'spongebob': 'squarepants'}])
|
||||
mock_remove = MagicMock(
|
||||
return_value={'Success': True,
|
||||
'RestartNeeded': False,
|
||||
'ExitCode': 1234})
|
||||
with patch.dict(win_servermanager.__salt__,
|
||||
{"win_servermanager.list_installed": mock,
|
||||
"win_servermanager.remove": mock1}):
|
||||
ret.update({'comment': 'The feature salt is not installed'})
|
||||
self.assertDictEqual(win_servermanager.removed('salt'), ret)
|
||||
{"win_servermanager.list_installed": mock_list,
|
||||
"win_servermanager.remove": mock_remove}):
|
||||
ret = {'name': 'squidward',
|
||||
'changes': {},
|
||||
'result': True,
|
||||
'comment': 'The feature squidward is not installed'}
|
||||
self.assertDictEqual(
|
||||
win_servermanager.removed('squidward'), ret)
|
||||
|
||||
with patch.dict(win_servermanager.__opts__, {"test": True}):
|
||||
ret.update({'changes': {'feature':
|
||||
'salt will be removed'},
|
||||
'result': None, 'comment': ''})
|
||||
self.assertDictEqual(win_servermanager.removed('salt'), ret)
|
||||
ret = {'name': 'squidward',
|
||||
'result': None,
|
||||
'comment': '',
|
||||
'changes': {'feature': 'squidward will be removed'}}
|
||||
self.assertDictEqual(
|
||||
win_servermanager.removed('squidward'), ret)
|
||||
|
||||
with patch.dict(win_servermanager.__opts__, {"test": False}):
|
||||
ret.update({'changes': {'feature': {'Success': True}},
|
||||
'result': True})
|
||||
self.assertDictEqual(win_servermanager.removed('salt'),
|
||||
ret)
|
||||
with patch.dict(win_servermanager.__opts__, {"test": False}):
|
||||
ret = {'name': 'squidward',
|
||||
'result': True,
|
||||
'comment': 'Removed squidward',
|
||||
'changes': {
|
||||
'Success': True,
|
||||
'RestartNeeded': False,
|
||||
'ExitCode': 1234,
|
||||
'feature': {'squidward': {'new': '',
|
||||
'old': 'patrick'}}}}
|
||||
self.assertDictEqual(
|
||||
win_servermanager.removed('squidward'), ret)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
Loading…
Add table
Reference in a new issue