mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge branch '2016.3' into 2016_3_develop
This commit is contained in:
commit
4956d7d5a3
16 changed files with 405 additions and 285 deletions
|
@ -2315,9 +2315,9 @@ exposed.
|
|||
.. code-block:: yaml
|
||||
|
||||
minionfs_whitelist:
|
||||
- base
|
||||
- v1.*
|
||||
- 'mybranch\d+'
|
||||
- server01
|
||||
- dev*
|
||||
- 'mail\d+.mydomain.tld'
|
||||
|
||||
.. conf_master:: minionfs_blacklist
|
||||
|
||||
|
@ -2341,9 +2341,9 @@ exposed.
|
|||
.. code-block:: yaml
|
||||
|
||||
minionfs_blacklist:
|
||||
- base
|
||||
- v1.*
|
||||
- 'mybranch\d+'
|
||||
- server01
|
||||
- dev*
|
||||
- 'mail\d+.mydomain.tld'
|
||||
|
||||
|
||||
.. _pillar-configuration:
|
||||
|
|
|
@ -4,155 +4,149 @@
|
|||
MinionFS Backend Walkthrough
|
||||
============================
|
||||
|
||||
Propagating Files
|
||||
=================
|
||||
|
||||
.. versionadded:: 2014.1.0
|
||||
|
||||
Sometimes, one might need to propagate files that are generated on a minion.
|
||||
Salt already has a feature to send files from a minion to the master.
|
||||
|
||||
Enabling File Propagation
|
||||
=========================
|
||||
|
||||
To enable propagation, the :conf_master:`file_recv` option needs to be set to ``True``.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
file_recv: True
|
||||
|
||||
These changes require a restart of the master, then new requests for the
|
||||
``salt://minion-id/`` protocol will send files that are pushed by ``cp.push``
|
||||
from ``minion-id`` to the master.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt 'minion-id' cp.push /path/to/the/file
|
||||
|
||||
This command will store the file, including its full path, under
|
||||
:conf_master:`cachedir` ``/master/minions/minion-id/files``. With the default
|
||||
:conf_master:`cachedir` the example file above would be stored as
|
||||
`/var/cache/salt/master/minions/minion-id/files/path/to/the/file`.
|
||||
|
||||
.. note::
|
||||
|
||||
This walkthrough assumes basic knowledge of Salt and :mod:`cp.push
|
||||
<salt.modules.cp.push>`. To get up to speed, check out the
|
||||
:doc:`walkthrough </topics/tutorials/walkthrough>`.
|
||||
|
||||
MinionFS Backend
|
||||
================
|
||||
Sometimes it is desirable to deploy a file located on one minion to one or more
|
||||
other minions. This is supported in Salt, and can be accomplished in two parts:
|
||||
|
||||
Since it is not a good idea to expose the whole :conf_master:`cachedir`, MinionFS
|
||||
should be used to send these files to other minions.
|
||||
#. Minion support for pushing files to the master (using :py:func:`cp.push
|
||||
<salt.modules.cp.push>`)
|
||||
|
||||
Simple Configuration
|
||||
====================
|
||||
#. The :mod:`minionfs <salt.fileserver.minionfs>` fileserver backend
|
||||
|
||||
To use the minionfs backend only two configuration changes are required on the
|
||||
master. The :conf_master:`fileserver_backend` option needs to contain a value of
|
||||
``minion`` and :conf_master:`file_recv` needs to be set to true:
|
||||
|
||||
This walkthrough will show how to use both of these features.
|
||||
|
||||
|
||||
Enabling File Push
|
||||
==================
|
||||
|
||||
To set the master to accept files pushed from minions, the
|
||||
:conf_master:`file_recv` option in the master config file must be set to
|
||||
``True`` (the default is ``False``).
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
file_recv: True
|
||||
|
||||
.. note::
|
||||
This change requires a restart of the salt-master service.
|
||||
|
||||
Pushing Files
|
||||
=============
|
||||
|
||||
Once this has been done, files can be pushed to the master using the
|
||||
:py:func:`cp.push <salt.modules.cp.push>` function:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt 'minion-id' cp.push /path/to/the/file
|
||||
|
||||
This command will store the file in a subdirectory named ``minions`` under the
|
||||
master's :conf_master:`cachedir`. On most masters, this path will be
|
||||
``/var/cache/salt/master/minions``. Within this directory will be one directory
|
||||
for each minion which has pushed a file to the master, and underneath that the
|
||||
full path to the file on the minion. So, for example, if a minion with an ID of
|
||||
``dev1`` pushed a file ``/var/log/myapp.log`` to the master, it would be saved
|
||||
to ``/var/cache/salt/master/minions/dev1/var/log/myapp.log``.
|
||||
|
||||
Serving Pushed Files Using MinionFS
|
||||
===================================
|
||||
|
||||
While it is certainly possible to add ``/var/cache/salt/master/minions`` to the
|
||||
master's :conf_master:`file_roots` and serve these files, it may only be
|
||||
desirable to expose files pushed from certain minions. Adding
|
||||
``/var/cache/salt/master/minions/<minion-id>`` for each minion that needs to be
|
||||
exposed can be cumbersome and prone to errors.
|
||||
|
||||
Enter :mod:`minionfs <salt.fileserver.minionfs>`. This fileserver backend will
|
||||
make files pushed using :py:func:`cp.push <salt.modules.cp.push>` available to
|
||||
the Salt fileserver, and provides an easy mechanism to restrict which minions'
|
||||
pushed files are made available.
|
||||
|
||||
Simple Configuration
|
||||
--------------------
|
||||
|
||||
To use the :mod:`minionfs <salt.fileserver.minionfs>` backend, add ``minion``
|
||||
to the list of backends in the :conf_master:`fileserver_backend` configuration
|
||||
option on the master:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
file_recv: True
|
||||
|
||||
fileserver_backend:
|
||||
- roots
|
||||
- minion
|
||||
|
||||
.. note::
|
||||
As described earlier, ``file_recv: True`` is also needed to enable the
|
||||
master to receive files pushed from minions. As always, changes to the
|
||||
master configuration require a restart of the ``salt-master`` service.
|
||||
|
||||
Files made available via :mod:`minionfs <salt.fileserver.minionfs>` are by
|
||||
default located at ``salt://<minion-id>/path/to/file``. Think back to the
|
||||
earlier example, in which ``dev1`` pushed a file ``/var/log/myapp.log`` to the
|
||||
master. With :mod:`minionfs <salt.fileserver.minionfs>` enabled, this file
|
||||
would be addressable in Salt at ``salt://dev1/var/log/myapp.log``.
|
||||
|
||||
If many minions have pushed to the master, this will result in many directories
|
||||
in the root of the Salt fileserver. For this reason, it is recommended to use
|
||||
the :conf_master:`minionfs_mountpoint` config option to organize these files
|
||||
underneath a subdirectory:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
minionfs_mountpoint: salt://minionfs
|
||||
|
||||
Using the above mountpoint, the file in the example would be located at
|
||||
``salt://minionfs/dev1/var/log/myapp.log``.
|
||||
|
||||
|
||||
Restricting Certain Minions' Files from Being Available Via MinionFS
|
||||
--------------------------------------------------------------------
|
||||
|
||||
A whitelist and blacklist can be used to restrict the minions whose pushed
|
||||
files are available via :mod:`minionfs <salt.fileserver.minionfs>`. These lists
|
||||
can be managed using the :conf_master:`minionfs_whitelist` and
|
||||
:conf_master:`minionfs_blacklist` config options. Click the links for both of
|
||||
them for a detailed explanation of how to use them.
|
||||
|
||||
A more complex configuration example, which uses both a whitelist and
|
||||
blacklist, can be found below:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
file_recv: True
|
||||
|
||||
These changes require a restart of the master, then new requests for the
|
||||
``salt://minion-id/`` protocol will send files that are pushed by ``cp.push``
|
||||
from ``minion-id`` to the master.
|
||||
fileserver_backend:
|
||||
- roots
|
||||
- minion
|
||||
|
||||
.. note::
|
||||
minionfs_mountpoint: salt://minionfs
|
||||
|
||||
All of the files that are pushed to the master are going to be available to
|
||||
all of the minions. If this is not what you want, please remove ``minion``
|
||||
from :conf_master:`fileserver_backend` in the master config file.
|
||||
minionfs_whitelist:
|
||||
- host04
|
||||
- web*
|
||||
- 'mail\d+\.domain\.tld'
|
||||
|
||||
.. note::
|
||||
minionfs_whitelist:
|
||||
- web21
|
||||
|
||||
Having directories with the same name as your minions in the root
|
||||
that can be accessed like ``salt://minion-id/`` might cause confusion.
|
||||
Potential Concerns
|
||||
------------------
|
||||
|
||||
Commandline Example
|
||||
===================
|
||||
* There is no access control in place to restrict which minions have access to
|
||||
files served up by :mod:`minionfs <salt.fileserver.minionfs>`. All minions
|
||||
will have access to these files.
|
||||
|
||||
Lets assume that we are going to generate SSH keys on a minion called
|
||||
``minion-source`` and put the public part in ``~/.ssh/authorized_keys`` of root
|
||||
user of a minion called ``minion-destination``.
|
||||
|
||||
First, lets make sure that ``/root/.ssh`` exists and has the right permissions:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
[root@salt-master file]# salt '*' file.mkdir dir_path=/root/.ssh user=root group=root mode=700
|
||||
minion-source:
|
||||
None
|
||||
minion-destination:
|
||||
None
|
||||
|
||||
We create an RSA key pair without a passphrase [*]_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
[root@salt-master file]# salt 'minion-source' cmd.run 'ssh-keygen -N "" -f /root/.ssh/id_rsa'
|
||||
minion-source:
|
||||
Generating public/private rsa key pair.
|
||||
Your identification has been saved in /root/.ssh/id_rsa.
|
||||
Your public key has been saved in /root/.ssh/id_rsa.pub.
|
||||
The key fingerprint is:
|
||||
9b:cd:1c:b9:c2:93:8e:ad:a3:52:a0:8b:0a:cc:d4:9b root@minion-source
|
||||
The key's randomart image is:
|
||||
+--[ RSA 2048]----+
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| o . |
|
||||
| o o S o |
|
||||
|= + . B o |
|
||||
|o+ E B = |
|
||||
|+ . .+ o |
|
||||
|o ...ooo |
|
||||
+-----------------+
|
||||
|
||||
and we send the public part to the master to be available to all minions:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
[root@salt-master file]# salt 'minion-source' cp.push /root/.ssh/id_rsa.pub
|
||||
minion-source:
|
||||
True
|
||||
|
||||
now it can be seen by everyone:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
[root@salt-master file]# salt 'minion-destination' cp.list_master_dirs
|
||||
minion-destination:
|
||||
- .
|
||||
- etc
|
||||
- minion-source/root
|
||||
- minion-source/root/.ssh
|
||||
|
||||
Lets copy that as the only authorized key to ``minion-destination``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
[root@salt-master file]# salt 'minion-destination' cp.get_file salt://minion-source/root/.ssh/id_rsa.pub /root/.ssh/authorized_keys
|
||||
minion-destination:
|
||||
/root/.ssh/authorized_keys
|
||||
|
||||
Or we can use a more elegant and salty way to add an SSH key:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
[root@salt-master file]# salt 'minion-destination' ssh.set_auth_key_from_file user=root source=salt://minion-source/root/.ssh/id_rsa.pub
|
||||
minion-destination:
|
||||
new
|
||||
|
||||
|
||||
|
||||
|
||||
.. [*] Yes, that was the actual key on my server, but the server is already destroyed.
|
||||
* Unless the :conf_master:`minionfs_whitelist` and/or
|
||||
:conf_master:`minionfs_blacklist` config options are used, all minions which
|
||||
push files to the master will have their files made available via
|
||||
:mod:`minionfs <salt.fileserver.minionfs>`.
|
||||
|
|
|
@ -798,7 +798,6 @@ def setup_multiprocessing_logging_listener(opts, queue=None):
|
|||
target=__process_multiprocessing_logging_queue,
|
||||
args=(opts, queue or get_multiprocessing_logging_queue(),)
|
||||
)
|
||||
__MP_LOGGING_QUEUE_PROCESS.daemon = True
|
||||
__MP_LOGGING_QUEUE_PROCESS.start()
|
||||
__MP_LOGGING_LISTENER_CONFIGURED = True
|
||||
|
||||
|
|
|
@ -539,7 +539,8 @@ class Master(SMaster):
|
|||
'reload': salt.crypt.Crypticle.generate_key_string
|
||||
}
|
||||
log.info('Creating master process manager')
|
||||
self.process_manager = salt.utils.process.ProcessManager(name='Master_ProcessManager')
|
||||
# Since there are children having their own ProcessManager we should wait for kill more time.
|
||||
self.process_manager = salt.utils.process.ProcessManager(wait_for_kill=5)
|
||||
pub_channels = []
|
||||
log.info('Creating master publisher process')
|
||||
for transport, opts in iter_transport_opts(self.opts):
|
||||
|
@ -726,7 +727,9 @@ class ReqServer(SignalHandlingMultiprocessingProcess):
|
|||
except os.error:
|
||||
pass
|
||||
|
||||
self.process_manager = salt.utils.process.ProcessManager(name='ReqServer_ProcessManager')
|
||||
# Wait for kill should be less then parent's ProcessManager.
|
||||
self.process_manager = salt.utils.process.ProcessManager(name='ReqServer_ProcessManager',
|
||||
wait_for_kill=1)
|
||||
|
||||
req_channels = []
|
||||
tcp_only = True
|
||||
|
|
|
@ -6,6 +6,7 @@ A module to wrap (non-Windows) archive calls
|
|||
'''
|
||||
from __future__ import absolute_import
|
||||
import os
|
||||
import logging
|
||||
import contextlib # For < 2.7 compat
|
||||
import logging
|
||||
|
||||
|
@ -21,6 +22,8 @@ __func_alias__ = {
|
|||
'zip_': 'zip'
|
||||
}
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
HAS_ZIPFILE = False
|
||||
try:
|
||||
|
@ -535,8 +538,12 @@ def unzip(zip_file, dest, excludes=None, options=None, template=None,
|
|||
|
||||
salt '*' archive.unzip /tmp/zipfile.zip /home/strongbad/ password='BadPassword'
|
||||
'''
|
||||
if options:
|
||||
log.warning("Options '{0}' ignored, only works with unzip binary.".format(options))
|
||||
# https://bugs.python.org/issue15795
|
||||
log.warning('Due to bug 15795 in python\'s zip lib, the permissions of the'
|
||||
' extracted files may not be preserved when using archive.unzip')
|
||||
log.warning('To preserve the permissions of extracted files, use'
|
||||
' archive.cmd_unzip')
|
||||
|
||||
if not excludes:
|
||||
excludes = []
|
||||
if runas:
|
||||
|
|
|
@ -36,9 +36,13 @@ def __virtual__():
|
|||
salt-minion running as SYSTEM.
|
||||
'''
|
||||
if not salt.utils.is_windows():
|
||||
return (False, 'Cannot load module chocolatey: Chocolatey requires Windows')
|
||||
elif __grains__['osrelease'] in ('XP', '2003Server'):
|
||||
return (False, 'Cannot load module chocolatey: Chocolatey requires Windows Vista or later')
|
||||
return (False, 'Cannot load module chocolatey: Chocolatey requires '
|
||||
'Windows')
|
||||
|
||||
if __grains__['osrelease'] in ('XP', '2003Server'):
|
||||
return (False, 'Cannot load module chocolatey: Chocolatey requires '
|
||||
'Windows Vista or later')
|
||||
|
||||
return 'chocolatey'
|
||||
|
||||
|
||||
|
@ -72,7 +76,7 @@ def _find_chocolatey(context, salt):
|
|||
if 'chocolatey._path' in context:
|
||||
return context['chocolatey._path']
|
||||
choc_defaults = ['C:\\Chocolatey\\bin\\chocolatey.bat',
|
||||
'C:\\ProgramData\\Chocolatey\\bin\\chocolatey.exe', ]
|
||||
'C:\\ProgramData\\Chocolatey\\bin\\chocolatey.exe', ]
|
||||
|
||||
choc_path = salt['cmd.which']('chocolatey.exe')
|
||||
if not choc_path:
|
||||
|
@ -244,11 +248,11 @@ def list_(narrow=None,
|
|||
if narrow:
|
||||
cmd.append(narrow)
|
||||
if salt.utils.is_true(all_versions):
|
||||
cmd.append('-AllVersions')
|
||||
cmd.append('-allversions')
|
||||
if salt.utils.is_true(pre_versions):
|
||||
cmd.append('-Prerelease')
|
||||
cmd.append('-prerelease')
|
||||
if source:
|
||||
cmd.extend(['-Source', source])
|
||||
cmd.extend(['-source', source])
|
||||
if local_only:
|
||||
cmd.extend(['-localonly'])
|
||||
|
||||
|
@ -277,6 +281,9 @@ def list_webpi():
|
|||
Instructs Chocolatey to pull a full package list from the Microsoft Web PI
|
||||
repository.
|
||||
|
||||
Returns:
|
||||
str: List of webpi packages
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
@ -284,7 +291,7 @@ def list_webpi():
|
|||
salt '*' chocolatey.list_webpi
|
||||
'''
|
||||
choc_path = _find_chocolatey(__context__, __salt__)
|
||||
cmd = [choc_path, 'list', '-Source', 'webpi']
|
||||
cmd = [choc_path, 'list', '-source', 'webpi']
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
if result['retcode'] != 0:
|
||||
|
@ -300,6 +307,9 @@ def list_windowsfeatures():
|
|||
Instructs Chocolatey to pull a full package list from the Windows Features
|
||||
list, via the Deployment Image Servicing and Management tool.
|
||||
|
||||
Returns:
|
||||
str: List of Windows Features
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
@ -307,7 +317,7 @@ def list_windowsfeatures():
|
|||
salt '*' chocolatey.list_windowsfeatures
|
||||
'''
|
||||
choc_path = _find_chocolatey(__context__, __salt__)
|
||||
cmd = [choc_path, 'list', '-Source', 'windowsfeatures']
|
||||
cmd = [choc_path, 'list', '-source', 'windowsfeatures']
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
if result['retcode'] != 0:
|
||||
|
@ -330,36 +340,52 @@ def install(name,
|
|||
'''
|
||||
Instructs Chocolatey to install a package.
|
||||
|
||||
name
|
||||
The name of the package to be installed. Only accepts a single argument.
|
||||
Args:
|
||||
|
||||
version
|
||||
Install a specific version of the package. Defaults to latest version.
|
||||
name (str):
|
||||
The name of the package to be installed. Only accepts a single
|
||||
argument.
|
||||
|
||||
source
|
||||
Chocolatey repository (directory, share or remote URL feed) the package
|
||||
comes from. Defaults to the official Chocolatey feed.
|
||||
version (str):
|
||||
Install a specific version of the package. Defaults to latest
|
||||
version.
|
||||
|
||||
force
|
||||
Reinstall the current version of an existing package.
|
||||
source (str):
|
||||
Chocolatey repository (directory, share or remote URL feed) the
|
||||
package comes from. Defaults to the official Chocolatey feed.
|
||||
|
||||
pre_versions
|
||||
Include pre-release packages. Defaults to False.
|
||||
Alternative Sources:
|
||||
|
||||
install_args
|
||||
A list of install arguments you want to pass to the installation process
|
||||
i.e product key or feature list
|
||||
- cygwin
|
||||
- python
|
||||
- ruby
|
||||
- webpi
|
||||
- windowsfeatures
|
||||
|
||||
override_args
|
||||
Set to true if you want to override the original install arguments (for the native installer)
|
||||
in the package and use your own. When this is set to False install_args will be appended to the end of the
|
||||
default arguments
|
||||
force (bool):
|
||||
Reinstall the current version of an existing package.
|
||||
|
||||
force_x86
|
||||
Force x86 (32bit) installation on 64 bit systems. Defaults to false.
|
||||
pre_versions (bool):
|
||||
Include pre-release packages. Defaults to False.
|
||||
|
||||
package_args
|
||||
A list of arguments you want to pass to the package
|
||||
install_args (str):
|
||||
A list of install arguments you want to pass to the installation
|
||||
process i.e product key or feature list
|
||||
|
||||
override_args (bool):
|
||||
Set to true if you want to override the original install arguments
|
||||
(for the native installer) in the package and use your own. When
|
||||
this is set to False install_args will be appended to the end of the
|
||||
default arguments
|
||||
|
||||
force_x86 (str):
|
||||
Force x86 (32bit) installation on 64 bit systems. Defaults to false.
|
||||
|
||||
package_args (str):
|
||||
A list of arguments you want to pass to the package
|
||||
|
||||
Returns:
|
||||
str: The output of the ``chocolatey`` command
|
||||
|
||||
CLI Example:
|
||||
|
||||
|
@ -373,29 +399,30 @@ def install(name,
|
|||
# chocolatey helpfully only supports a single package argument
|
||||
cmd = [choc_path, 'install', name]
|
||||
if version:
|
||||
cmd.extend(['-Version', version])
|
||||
cmd.extend(['-version', version])
|
||||
if source:
|
||||
cmd.extend(['-Source', source])
|
||||
cmd.extend(['-source', source])
|
||||
if salt.utils.is_true(force):
|
||||
cmd.extend(['-Force'])
|
||||
cmd.append('-force')
|
||||
if salt.utils.is_true(pre_versions):
|
||||
cmd.extend(['-PreRelease'])
|
||||
cmd.append('-prerelease')
|
||||
if install_args:
|
||||
cmd.extend(['-InstallArguments', install_args])
|
||||
cmd.extend(['-installarguments', install_args])
|
||||
if override_args:
|
||||
cmd.extend(['-OverrideArguments'])
|
||||
cmd.append('-overridearguments')
|
||||
if force_x86:
|
||||
cmd.extend(['-forcex86'])
|
||||
cmd.append('-forcex86')
|
||||
if package_args:
|
||||
cmd.extend(['-PackageParameters', package_args])
|
||||
cmd.extend(['-packageparameters', package_args])
|
||||
cmd.extend(_yes(__context__))
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
if result['retcode'] != 0:
|
||||
if result['retcode'] not in [0, 1641, 3010]:
|
||||
err = 'Running chocolatey failed: {0}'.format(result['stderr'])
|
||||
log.error(err)
|
||||
raise CommandExecutionError(err)
|
||||
elif name == 'chocolatey':
|
||||
|
||||
if name == 'chocolatey':
|
||||
_clear_context(__context__)
|
||||
|
||||
return result['stdout']
|
||||
|
@ -424,21 +451,10 @@ def install_cygwin(name, install_args=None, override_args=False):
|
|||
salt '*' chocolatey.install_cygwin <package name>
|
||||
salt '*' chocolatey.install_cygwin <package name> install_args=<args> override_args=True
|
||||
'''
|
||||
choc_path = _find_chocolatey(__context__, __salt__)
|
||||
cmd = [choc_path, 'cygwin', name]
|
||||
if install_args:
|
||||
cmd.extend(['-InstallArguments', install_args])
|
||||
if override_args:
|
||||
cmd.extend(['-OverrideArguments'])
|
||||
cmd.extend(_yes(__context__))
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
if result['retcode'] != 0:
|
||||
err = 'Running chocolatey failed: {0}'.format(result['stderr'])
|
||||
log.error(err)
|
||||
raise CommandExecutionError(err)
|
||||
|
||||
return result['stdout']
|
||||
return install(name,
|
||||
source='cygwin',
|
||||
install_args=install_args,
|
||||
override_args=override_args)
|
||||
|
||||
|
||||
def install_gem(name, version=None, install_args=None, override_args=False):
|
||||
|
@ -470,23 +486,11 @@ def install_gem(name, version=None, install_args=None, override_args=False):
|
|||
salt '*' chocolatey.install_gem <package name> version=<package version>
|
||||
salt '*' chocolatey.install_gem <package name> install_args=<args> override_args=True
|
||||
'''
|
||||
choc_path = _find_chocolatey(__context__, __salt__)
|
||||
cmd = [choc_path, 'gem', name]
|
||||
if version:
|
||||
cmd.extend(['-Version', version])
|
||||
if install_args:
|
||||
cmd.extend(['-InstallArguments', install_args])
|
||||
if override_args:
|
||||
cmd.extend(['-OverrideArguments'])
|
||||
cmd.extend(_yes(__context__))
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
if result['retcode'] != 0:
|
||||
err = 'Running chocolatey failed: {0}'.format(result['stderr'])
|
||||
log.error(err)
|
||||
raise CommandExecutionError(err)
|
||||
|
||||
return result['stdout']
|
||||
return install(name,
|
||||
version=version,
|
||||
source='ruby',
|
||||
install_args=install_args,
|
||||
override_args=override_args)
|
||||
|
||||
|
||||
def install_missing(name, version=None, source=None):
|
||||
|
@ -525,9 +529,9 @@ def install_missing(name, version=None, source=None):
|
|||
# chocolatey helpfully only supports a single package argument
|
||||
cmd = [choc_path, 'installmissing', name]
|
||||
if version:
|
||||
cmd.extend(['-Version', version])
|
||||
cmd.extend(['-version', version])
|
||||
if source:
|
||||
cmd.extend(['-Source', source])
|
||||
cmd.extend(['-source', source])
|
||||
# Shouldn't need this as this code should never run on v0.9.9 and newer
|
||||
cmd.extend(_yes(__context__))
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
@ -568,23 +572,11 @@ def install_python(name, version=None, install_args=None, override_args=False):
|
|||
salt '*' chocolatey.install_python <package name> version=<package version>
|
||||
salt '*' chocolatey.install_python <package name> install_args=<args> override_args=True
|
||||
'''
|
||||
choc_path = _find_chocolatey(__context__, __salt__)
|
||||
cmd = [choc_path, 'python', name]
|
||||
if version:
|
||||
cmd.extend(['-Version', version])
|
||||
if install_args:
|
||||
cmd.extend(['-InstallArguments', install_args])
|
||||
if override_args:
|
||||
cmd.extend(['-OverrideArguments'])
|
||||
cmd.extend(_yes(__context__))
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
if result['retcode'] != 0:
|
||||
err = 'Running chocolatey failed: {0}'.format(result['stderr'])
|
||||
log.error(err)
|
||||
raise CommandExecutionError(err)
|
||||
|
||||
return result['stdout']
|
||||
return install(name,
|
||||
version=version,
|
||||
source='python',
|
||||
install_args=install_args,
|
||||
override_args=override_args)
|
||||
|
||||
|
||||
def install_windowsfeatures(name):
|
||||
|
@ -601,17 +593,7 @@ def install_windowsfeatures(name):
|
|||
|
||||
salt '*' chocolatey.install_windowsfeatures <package name>
|
||||
'''
|
||||
choc_path = _find_chocolatey(__context__, __salt__)
|
||||
cmd = [choc_path, 'windowsfeatures', name]
|
||||
cmd.extend(_yes(__context__))
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
if result['retcode'] != 0:
|
||||
err = 'Running chocolatey failed: {0}'.format(result['stderr'])
|
||||
log.error(err)
|
||||
raise CommandExecutionError(err)
|
||||
|
||||
return result['stdout']
|
||||
return install(name, source='windowsfeatures')
|
||||
|
||||
|
||||
def install_webpi(name, install_args=None, override_args=False):
|
||||
|
@ -637,21 +619,10 @@ def install_webpi(name, install_args=None, override_args=False):
|
|||
salt '*' chocolatey.install_webpi <package name>
|
||||
salt '*' chocolatey.install_webpi <package name> install_args=<args> override_args=True
|
||||
'''
|
||||
choc_path = _find_chocolatey(__context__, __salt__)
|
||||
cmd = [choc_path, 'webpi', name]
|
||||
if install_args:
|
||||
cmd.extend(['-InstallArguments', install_args])
|
||||
if override_args:
|
||||
cmd.extend(['-OverrideArguments'])
|
||||
cmd.extend(_yes(__context__))
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
if result['retcode'] != 0:
|
||||
err = 'Running chocolatey failed: {0}'.format(result['stderr'])
|
||||
log.error(err)
|
||||
raise CommandExecutionError(err)
|
||||
|
||||
return result['stdout']
|
||||
return install(name,
|
||||
source='webpi',
|
||||
install_args=install_args,
|
||||
override_args=override_args)
|
||||
|
||||
|
||||
def uninstall(name, version=None, uninstall_args=None, override_args=False):
|
||||
|
@ -686,15 +657,107 @@ def uninstall(name, version=None, uninstall_args=None, override_args=False):
|
|||
# chocolatey helpfully only supports a single package argument
|
||||
cmd = [choc_path, 'uninstall', name]
|
||||
if version:
|
||||
cmd.extend(['-Version', version])
|
||||
cmd.extend(['-version', version])
|
||||
if uninstall_args:
|
||||
cmd.extend(['-UninstallArguments', uninstall_args])
|
||||
cmd.extend(['-uninstallarguments', uninstall_args])
|
||||
if override_args:
|
||||
cmd.extend(['-OverrideArguments'])
|
||||
cmd.extend(['-overridearguments'])
|
||||
cmd.extend(_yes(__context__))
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
if result['retcode'] != 0:
|
||||
if result['retcode'] not in [0, 1605, 1614, 1641]:
|
||||
err = 'Running chocolatey failed: {0}'.format(result['stderr'])
|
||||
log.error(err)
|
||||
raise CommandExecutionError(err)
|
||||
|
||||
return result['stdout']
|
||||
|
||||
|
||||
def upgrade(name,
|
||||
version=None,
|
||||
source=None,
|
||||
force=False,
|
||||
pre_versions=False,
|
||||
install_args=None,
|
||||
override_args=False,
|
||||
force_x86=False,
|
||||
package_args=None):
|
||||
'''
|
||||
.. version-added:: 2016.3.4
|
||||
|
||||
Instructs Chocolatey to upgrade packages on the system. (update is being
|
||||
deprecated)
|
||||
|
||||
Args:
|
||||
|
||||
name (str):
|
||||
The name of the package to update, or "all" to update everything
|
||||
installed on the system.
|
||||
|
||||
version (str):
|
||||
Install a specific version of the package. Defaults to latest
|
||||
version.
|
||||
|
||||
source (str):
|
||||
Chocolatey repository (directory, share or remote URL feed) the
|
||||
package comes from. Defaults to the official Chocolatey feed.
|
||||
|
||||
force (bool):
|
||||
Reinstall the **same** version already installed
|
||||
|
||||
pre_versions (bool):
|
||||
Include pre-release packages in comparison. Defaults to False.
|
||||
|
||||
install_args (str):
|
||||
A list of install arguments you want to pass to the installation
|
||||
process i.e product key or feature list
|
||||
|
||||
override_args (str):
|
||||
Set to true if you want to override the original install arguments
|
||||
(for the native installer) in the package and use your own. When
|
||||
this is set to False install_args will be appended to the end of the
|
||||
default arguments
|
||||
|
||||
force_x86
|
||||
Force x86 (32bit) installation on 64 bit systems. Defaults to false.
|
||||
|
||||
package_args
|
||||
A list of arguments you want to pass to the package
|
||||
|
||||
Returns:
|
||||
str: Results of the ``chocolatey`` command
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt "*" chocolatey.upgrade all
|
||||
salt "*" chocolatey.upgrade <package name> pre_versions=True
|
||||
'''
|
||||
# chocolatey helpfully only supports a single package argument
|
||||
choc_path = _find_chocolatey(__context__, __salt__)
|
||||
cmd = [choc_path, 'upgrade', name]
|
||||
if version:
|
||||
cmd.extend(['-version', version])
|
||||
if source:
|
||||
cmd.extend(['-source', source])
|
||||
if salt.utils.is_true(force):
|
||||
cmd.append('-force')
|
||||
if salt.utils.is_true(pre_versions):
|
||||
cmd.append('-prerelease')
|
||||
if install_args:
|
||||
cmd.extend(['-installarguments', install_args])
|
||||
if override_args:
|
||||
cmd.append('-overridearguments')
|
||||
if force_x86:
|
||||
cmd.append('-forcex86')
|
||||
if package_args:
|
||||
cmd.extend(['-packageparameters', package_args])
|
||||
cmd.extend(_yes(__context__))
|
||||
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
if result['retcode'] not in [0, 1641, 3010]:
|
||||
err = 'Running chocolatey failed: {0}'.format(result['stderr'])
|
||||
log.error(err)
|
||||
raise CommandExecutionError(err)
|
||||
|
@ -726,15 +789,19 @@ def update(name, source=None, pre_versions=False):
|
|||
'''
|
||||
# chocolatey helpfully only supports a single package argument
|
||||
choc_path = _find_chocolatey(__context__, __salt__)
|
||||
if _LooseVersion(chocolatey_version()) >= _LooseVersion('0.9.8.24'):
|
||||
log.warning('update is deprecated, using upgrade')
|
||||
return upgrade(name, source=source, pre_versions=pre_versions)
|
||||
|
||||
cmd = [choc_path, 'update', name]
|
||||
if source:
|
||||
cmd.extend(['-Source', source])
|
||||
cmd.extend(['-source', source])
|
||||
if salt.utils.is_true(pre_versions):
|
||||
cmd.append('-PreRelease')
|
||||
cmd.append('-prerelease')
|
||||
cmd.extend(_yes(__context__))
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
if result['retcode'] != 0:
|
||||
if result['retcode'] not in [0, 1641, 3010]:
|
||||
err = 'Running chocolatey failed: {0}'.format(result['stderr'])
|
||||
log.error(err)
|
||||
raise CommandExecutionError(err)
|
||||
|
@ -776,11 +843,11 @@ def version(name, check_remote=False, source=None, pre_versions=False):
|
|||
|
||||
cmd = [choc_path, 'list', name]
|
||||
if not salt.utils.is_true(check_remote):
|
||||
cmd.append('-LocalOnly')
|
||||
cmd.append('-localonly')
|
||||
if salt.utils.is_true(pre_versions):
|
||||
cmd.append('-Prerelease')
|
||||
cmd.append('-prerelease')
|
||||
if source:
|
||||
cmd.extend(['-Source', source])
|
||||
cmd.extend(['-source', source])
|
||||
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
|
@ -797,7 +864,8 @@ def version(name, check_remote=False, source=None, pre_versions=False):
|
|||
for line in res:
|
||||
if 'packages found' not in line and 'packages installed' not in line:
|
||||
for name, ver in ver_re.findall(line):
|
||||
ret[name] = ver
|
||||
if name not in ['Did', 'Features?', 'Chocolatey']:
|
||||
ret[name] = ver
|
||||
|
||||
return ret
|
||||
|
||||
|
@ -827,7 +895,7 @@ def add_source(name, source_location, username=None, password=None):
|
|||
|
||||
'''
|
||||
choc_path = _find_chocolatey(__context__, __salt__)
|
||||
cmd = [choc_path, 'sources', 'Add', '-Name', name, "-Source", source_location]
|
||||
cmd = [choc_path, 'sources', 'add', '-name', name, "-source", source_location]
|
||||
if username:
|
||||
cmd.extend(['-u', username])
|
||||
if password:
|
||||
|
@ -854,7 +922,7 @@ def _change_source_state(name, state):
|
|||
|
||||
'''
|
||||
choc_path = _find_chocolatey(__context__, __salt__)
|
||||
cmd = [choc_path, 'source', state, "-Name", name]
|
||||
cmd = [choc_path, 'source', state, "-name", name]
|
||||
result = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
if result['retcode'] != 0:
|
||||
|
|
|
@ -46,10 +46,11 @@ except ImportError:
|
|||
|
||||
# Import salt libs
|
||||
import salt.utils
|
||||
import salt.utils.atomicfile
|
||||
import salt.utils.find
|
||||
import salt.utils.filebuffer
|
||||
import salt.utils.files
|
||||
import salt.utils.atomicfile
|
||||
import salt.utils.templates
|
||||
import salt.utils.url
|
||||
from salt.exceptions import CommandExecutionError, SaltInvocationError, get_error_message as _get_error_message
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ def __virtual__():
|
|||
|
||||
|
||||
def _ssh_flag(identity_path):
|
||||
return '--ssh "ssh -i {0}"'.format(identity_path)
|
||||
return ['--ssh', 'ssh -i {0}'.format(identity_path)]
|
||||
|
||||
|
||||
def revision(cwd, rev='tip', short=False, user=None):
|
||||
|
@ -181,7 +181,7 @@ def pull(cwd, opts=None, user=None, identity=None, repository=None):
|
|||
'''
|
||||
cmd = ['hg', 'pull']
|
||||
if identity:
|
||||
cmd.append(_ssh_flag(identity))
|
||||
cmd.extend(_ssh_flag(identity))
|
||||
if opts:
|
||||
for opt in opts.split():
|
||||
cmd.append(opt)
|
||||
|
@ -250,7 +250,7 @@ def clone(cwd, repository, opts=None, user=None, identity=None):
|
|||
for opt in opts.split():
|
||||
cmd.append('{0}'.format(opt))
|
||||
if identity:
|
||||
cmd.append(_ssh_flag(identity))
|
||||
cmd.extend(_ssh_flag(identity))
|
||||
return __salt__['cmd.run'](cmd, runas=user, python_shell=False)
|
||||
|
||||
|
||||
|
|
|
@ -2908,12 +2908,21 @@ def privileges_grant(name,
|
|||
if object_type == 'group':
|
||||
query = 'GRANT {0} TO "{1}" WITH ADMIN OPTION'.format(
|
||||
object_name, name)
|
||||
elif (object_type in ('table', 'sequence') and
|
||||
object_name.upper() == 'ALL'):
|
||||
query = 'GRANT {0} ON ALL {1}S IN SCHEMA {2} TO ' \
|
||||
'"{3}" WITH GRANT OPTION'.format(
|
||||
_grants, object_type.upper(), prepend, name)
|
||||
else:
|
||||
query = 'GRANT {0} ON {1} {2} TO "{3}" WITH GRANT OPTION'.format(
|
||||
_grants, object_type.upper(), on_part, name)
|
||||
else:
|
||||
if object_type == 'group':
|
||||
query = 'GRANT {0} TO "{1}"'.format(object_name, name)
|
||||
elif (object_type in ('table', 'sequence') and
|
||||
object_name.upper() == 'ALL'):
|
||||
query = 'GRANT {0} ON ALL {1}S IN SCHEMA {2} TO "{3}"'.format(
|
||||
_grants, object_type.upper(), prepend, name)
|
||||
else:
|
||||
query = 'GRANT {0} ON {1} {2} TO "{3}"'.format(
|
||||
_grants, object_type.upper(), on_part, name)
|
||||
|
|
|
@ -1126,10 +1126,11 @@ def upgrade(refresh=True, skip_verify=False):
|
|||
refresh_db()
|
||||
old = list_pkgs()
|
||||
|
||||
to_append = ''
|
||||
if skip_verify:
|
||||
to_append = '--no-gpg-checks'
|
||||
__zypper__(systemd_scope=_systemd_scope()).noraise.call('update', '--auto-agree-with-licenses', to_append)
|
||||
__zypper__(systemd_scope=_systemd_scope()).noraise.call('update', '--auto-agree-with-licenses', '--no-gpg-checks')
|
||||
else:
|
||||
__zypper__(systemd_scope=_systemd_scope()).noraise.call('update', '--auto-agree-with-licenses')
|
||||
|
||||
if __zypper__.exit_code not in __zypper__.SUCCESS_EXIT_CODES:
|
||||
ret['result'] = False
|
||||
ret['comment'] = (__zypper__.stdout() + os.linesep + __zypper__.stderr()).strip()
|
||||
|
|
|
@ -375,10 +375,16 @@ def extracted(name,
|
|||
|
||||
log.debug('Extracting {0} to {1}'.format(filename, name))
|
||||
if archive_format == 'zip':
|
||||
if use_cmd_unzip:
|
||||
files = __salt__['archive.cmd_unzip'](filename, name, options=zip_options, trim_output=trim_output, **kwargs)
|
||||
if password is None and salt.utils.which('unzip'):
|
||||
files = __salt__['archive.cmd_unzip'](filename, name, trim_output=trim_output)
|
||||
else:
|
||||
files = __salt__['archive.unzip'](filename, name, options=zip_options, trim_output=trim_output, password=password, **kwargs)
|
||||
# https://bugs.python.org/issue15795
|
||||
if password is not None:
|
||||
log.warning('Password supplied: using archive.unzip')
|
||||
if not salt.utils.which('unzip'):
|
||||
log.warning('Cannot find unzip command for archive.cmd_unzip:'
|
||||
' using archive.unzip instead')
|
||||
files = __salt__['archive.unzip'](filename, name, trim_output=trim_output, password=password)
|
||||
elif archive_format == 'rar':
|
||||
files = __salt__['archive.unrar'](filename, name, trim_output=trim_output, **kwargs)
|
||||
else:
|
||||
|
|
|
@ -93,7 +93,8 @@ def present(name,
|
|||
Name of the role to which privileges should be granted
|
||||
|
||||
object_name
|
||||
Name of the object on which the grant is to be performed
|
||||
Name of the object on which the grant is to be performed.
|
||||
'ALL' may be used for objects of type 'table' or 'sequence'.
|
||||
|
||||
object_type
|
||||
The object type, which can be one of the following:
|
||||
|
|
|
@ -467,7 +467,10 @@ def _interfaces_ifconfig(out):
|
|||
if not salt.utils.is_sunos():
|
||||
ipv6scope = mmask6.group(3) or mmask6.group(4)
|
||||
addr_obj['scope'] = ipv6scope.lower() if ipv6scope is not None else ipv6scope
|
||||
if addr_obj['address'] != '::' and addr_obj['prefixlen'] != 0: # SunOS sometimes has ::/0 as inet6 addr when using addrconf
|
||||
# SunOS sometimes has ::/0 as inet6 addr when using addrconf
|
||||
if not salt.utils.is_sunos() \
|
||||
or addr_obj['address'] != '::' \
|
||||
and addr_obj['prefixlen'] != 0:
|
||||
data['inet6'].append(addr_obj)
|
||||
data['up'] = updown
|
||||
if iface in ret:
|
||||
|
|
4
setup.py
4
setup.py
|
@ -29,6 +29,7 @@ from distutils.command.build import build
|
|||
from distutils.command.clean import clean
|
||||
from distutils.command.sdist import sdist
|
||||
from distutils.command.install_lib import install_lib
|
||||
from ctypes.util import find_library
|
||||
# pylint: enable=E0611
|
||||
|
||||
try:
|
||||
|
@ -447,6 +448,9 @@ class DownloadWindowsDlls(Command):
|
|||
dest = os.path.join(os.path.dirname(sys.executable), '{fname}.dll')
|
||||
with indent_log():
|
||||
for fname in ('libeay32', 'ssleay32', 'libsodium', 'msvcr120'):
|
||||
# See if the library is already on the system
|
||||
if find_library(fname):
|
||||
continue
|
||||
furl = url.format(bits=platform_bits[:2], fname=fname)
|
||||
fdest = dest.format(fname=fname)
|
||||
if not os.path.exists(fdest):
|
||||
|
|
|
@ -143,6 +143,7 @@ class TestModulesGrains(integration.ModuleCase):
|
|||
get_grain, int, msg='grain: {0} is not an int or empty'.format(grain))
|
||||
|
||||
|
||||
@destructiveTest
|
||||
class GrainsAppendTestCase(integration.ModuleCase):
|
||||
'''
|
||||
Tests written specifically for the grains.append function.
|
||||
|
@ -150,13 +151,11 @@ class GrainsAppendTestCase(integration.ModuleCase):
|
|||
GRAIN_KEY = 'salttesting-grain-key'
|
||||
GRAIN_VAL = 'my-grain-val'
|
||||
|
||||
@destructiveTest
|
||||
def tearDown(self):
|
||||
test_grain = self.run_function('grains.get', [self.GRAIN_KEY])
|
||||
if test_grain and test_grain == [self.GRAIN_VAL]:
|
||||
self.run_function('grains.remove', [self.GRAIN_KEY, self.GRAIN_VAL])
|
||||
|
||||
@destructiveTest
|
||||
def test_grains_append(self):
|
||||
'''
|
||||
Tests the return of a simple grains.append call.
|
||||
|
@ -164,7 +163,6 @@ class GrainsAppendTestCase(integration.ModuleCase):
|
|||
ret = self.run_function('grains.append', [self.GRAIN_KEY, self.GRAIN_VAL])
|
||||
self.assertEqual(ret[self.GRAIN_KEY], [self.GRAIN_VAL])
|
||||
|
||||
@destructiveTest
|
||||
def test_grains_append_val_already_present(self):
|
||||
'''
|
||||
Tests the return of a grains.append call when the value is already present in the grains list.
|
||||
|
@ -178,7 +176,6 @@ class GrainsAppendTestCase(integration.ModuleCase):
|
|||
ret = self.run_function('grains.append', [self.GRAIN_KEY, self.GRAIN_VAL])
|
||||
self.assertEqual(messaging, ret)
|
||||
|
||||
@destructiveTest
|
||||
def test_grains_append_val_is_list(self):
|
||||
'''
|
||||
Tests the return of a grains.append call when val is passed in as a list.
|
||||
|
@ -187,7 +184,6 @@ class GrainsAppendTestCase(integration.ModuleCase):
|
|||
ret = self.run_function('grains.append', [self.GRAIN_KEY, [self.GRAIN_VAL, second_grain]])
|
||||
self.assertEqual(ret[self.GRAIN_KEY], [self.GRAIN_VAL, second_grain])
|
||||
|
||||
@destructiveTest
|
||||
def test_grains_append_call_twice(self):
|
||||
'''
|
||||
Tests the return of a grains.append call when the value is already present
|
||||
|
|
|
@ -1301,6 +1301,34 @@ class PostgresTestCase(TestCase):
|
|||
host='testhost', port='testport',
|
||||
password='testpassword', user='testuser', runas='user')
|
||||
|
||||
# Test grant on all tables
|
||||
with patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': 0})):
|
||||
with patch('salt.modules.postgres.has_privileges',
|
||||
Mock(return_value=False)):
|
||||
ret = postgres.privileges_grant(
|
||||
'baruwa',
|
||||
'ALL',
|
||||
'table',
|
||||
'SELECT',
|
||||
maintenance_db='db_name',
|
||||
runas='user',
|
||||
host='testhost',
|
||||
port='testport',
|
||||
user='testuser',
|
||||
password='testpassword'
|
||||
)
|
||||
|
||||
query = 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "baruwa"'
|
||||
|
||||
postgres._run_psql.assert_called_once_with(
|
||||
['/usr/bin/pgsql', '--no-align', '--no-readline',
|
||||
'--no-password', '--username', 'testuser', '--host',
|
||||
'testhost', '--port', 'testport', '--dbname', 'db_name',
|
||||
'-c', query],
|
||||
host='testhost', port='testport',
|
||||
password='testpassword', user='testuser', runas='user')
|
||||
|
||||
def test_privileges_grant_group(self):
|
||||
'''
|
||||
Test granting privileges on group
|
||||
|
|
Loading…
Add table
Reference in a new issue