mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #42363 from rallytime/merge-2017.7
[2017.7] Merge forward from 2016.11 to 2017.7
This commit is contained in:
commit
587138d771
22 changed files with 213 additions and 57 deletions
26
doc/faq.rst
26
doc/faq.rst
|
@ -321,7 +321,27 @@ Restart using states
|
|||
********************
|
||||
|
||||
Now we can apply the workaround to restart the Minion in reliable way.
|
||||
The following example works on both UNIX-like and Windows operating systems:
|
||||
The following example works on UNIX-like operating systems:
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{%- if grains['os'] != 'Windows' %
|
||||
Restart Salt Minion:
|
||||
cmd.run:
|
||||
- name: 'salt-call --local service.restart salt-minion'
|
||||
- bg: True
|
||||
- onchanges:
|
||||
- pkg: Upgrade Salt Minion
|
||||
{%- endif %}
|
||||
|
||||
Note that restarting the ``salt-minion`` service on Windows operating systems is
|
||||
not always necessary when performing an upgrade. The installer stops the
|
||||
``salt-minion`` service, removes it, deletes the contents of the ``\salt\bin``
|
||||
directory, installs the new code, re-creates the ``salt-minion`` service, and
|
||||
starts it (by default). The restart step **would** be necessary during the
|
||||
upgrade process, however, if the minion config was edited after the upgrade or
|
||||
installation. If a minion restart is necessary, the state above can be edited
|
||||
as follows:
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
|
@ -337,8 +357,8 @@ The following example works on both UNIX-like and Windows operating systems:
|
|||
- pkg: Upgrade Salt Minion
|
||||
|
||||
However, it requires more advanced tricks to upgrade from legacy version of
|
||||
Salt (before ``2016.3.0``), where executing commands in the background is not
|
||||
supported:
|
||||
Salt (before ``2016.3.0``) on UNIX-like operating systems, where executing
|
||||
commands in the background is not supported:
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
|
|
|
@ -1272,6 +1272,20 @@ A list of extra directories to search for Salt renderers
|
|||
render_dirs:
|
||||
- /var/lib/salt/renderers
|
||||
|
||||
.. conf_minion:: utils_dirs
|
||||
|
||||
``utils_dirs``
|
||||
---------------
|
||||
|
||||
Default: ``[]``
|
||||
|
||||
A list of extra directories to search for Salt utilities
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
utils_dirs:
|
||||
- /var/lib/salt/utils
|
||||
|
||||
.. conf_minion:: cython_enable
|
||||
|
||||
``cython_enable``
|
||||
|
|
|
@ -78,6 +78,7 @@ parameters are discussed in more detail below.
|
|||
# RHEL -> ec2-user
|
||||
# CentOS -> ec2-user
|
||||
# Ubuntu -> ubuntu
|
||||
# Debian -> admin
|
||||
#
|
||||
ssh_username: ec2-user
|
||||
|
||||
|
|
|
@ -371,7 +371,6 @@ both.
|
|||
compute_name: cloudServersOpenStack
|
||||
protocol: ipv4
|
||||
compute_region: DFW
|
||||
protocol: ipv4
|
||||
user: myuser
|
||||
tenant: 5555555
|
||||
password: mypass
|
||||
|
|
|
@ -32,6 +32,8 @@ Builds for a few platforms are available as part of the RC at https://repo.salts
|
|||
|
||||
Available builds:
|
||||
|
||||
- Ubuntu16
|
||||
- Redhat7
|
||||
- Windows
|
||||
|
||||
.. FreeBSD
|
||||
|
|
|
@ -64,7 +64,8 @@ Deploy ssh key for salt-ssh
|
|||
===========================
|
||||
|
||||
By default, salt-ssh will generate key pairs for ssh, the default path will be
|
||||
/etc/salt/pki/master/ssh/salt-ssh.rsa
|
||||
``/etc/salt/pki/master/ssh/salt-ssh.rsa``. The key generation happens when you run
|
||||
``salt-ssh`` for the first time.
|
||||
|
||||
You can use ssh-copy-id, (the OpenSSH key deployment tool) to deploy keys to your servers.
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ hit `Enter`. Also, you can convert tabs to 2 spaces by these commands in Vim:
|
|||
|
||||
Indentation
|
||||
===========
|
||||
|
||||
The suggested syntax for YAML files is to use 2 spaces for indentation,
|
||||
but YAML will follow whatever indentation system that the individual file
|
||||
uses. Indentation of two spaces works very well for SLS files given the
|
||||
|
@ -112,8 +113,24 @@ PyYAML will load these values as boolean ``True`` or ``False``. Un-capitalized
|
|||
versions will also be loaded as booleans (``true``, ``false``, ``yes``, ``no``,
|
||||
``on``, and ``off``). This can be especially problematic when constructing
|
||||
Pillar data. Make sure that your Pillars which need to use the string versions
|
||||
of these values are enclosed in quotes. Pillars will be parsed twice by salt,
|
||||
so you'll need to wrap your values in multiple quotes, for example '"false"'.
|
||||
of these values are enclosed in quotes. Pillars will be parsed twice by salt,
|
||||
so you'll need to wrap your values in multiple quotes, including double quotation
|
||||
marks (``" "``) and single quotation marks (``' '``). Note that spaces are included
|
||||
in the quotation type examples for clarity.
|
||||
|
||||
Multiple quoting examples looks like this:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- '"false"'
|
||||
- "'True'"
|
||||
- "'YES'"
|
||||
- '"No"'
|
||||
|
||||
.. note::
|
||||
|
||||
When using multiple quotes in this manner, they must be different. Using ``"" ""``
|
||||
or ``'' ''`` won't work in this case (spaces are included in examples for clarity).
|
||||
|
||||
The '%' Sign
|
||||
============
|
||||
|
|
|
@ -54,7 +54,7 @@ types like so:
|
|||
|
||||
salt '*' mymodule.observe_the_awesomeness
|
||||
'''
|
||||
print __utils__['foo.bar']()
|
||||
return __utils__['foo.bar']()
|
||||
|
||||
Utility modules, like any other kind of Salt extension, support using a
|
||||
:ref:`__virtual__ function <modules-virtual-name>` to conditionally load them,
|
||||
|
@ -81,11 +81,56 @@ the ``foo`` utility module with a ``__virtual__`` function.
|
|||
def bar():
|
||||
return 'baz'
|
||||
|
||||
Also you could even write your utility modules in object oriented fashion:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
My utils module
|
||||
---------------
|
||||
|
||||
This module contains common functions for use in my other custom types.
|
||||
'''
|
||||
|
||||
class Foo(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def bar(self):
|
||||
return 'baz'
|
||||
|
||||
And import them into other custom modules:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
My awesome execution module
|
||||
---------------------------
|
||||
'''
|
||||
|
||||
import mymodule
|
||||
|
||||
def observe_the_awesomeness():
|
||||
'''
|
||||
Prints information from my utility module
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' mymodule.observe_the_awesomeness
|
||||
'''
|
||||
foo = mymodule.Foo()
|
||||
return foo.bar()
|
||||
|
||||
These are, of course, contrived examples, but they should serve to show some of
|
||||
the possibilities opened up by writing utility modules. Keep in mind though
|
||||
that States still have access to all of the execution modules, so it is not
|
||||
that states still have access to all of the execution modules, so it is not
|
||||
necessary to write a utility module to make a function available to both a
|
||||
state and an execution module. One good use case for utililty modules is one
|
||||
state and an execution module. One good use case for utility modules is one
|
||||
where it is necessary to invoke the same function from a custom :ref:`outputter
|
||||
<all-salt.output>`/returner, as well as an execution module.
|
||||
|
||||
|
|
|
@ -135,6 +135,14 @@ Alternatively, one could use the private IP to connect by specifying:
|
|||
ssh_interface: private_ips
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
When using floating ips from networks, if the OpenStack driver is unable to
|
||||
allocate a new ip address for the server, it will check that for
|
||||
unassociated ip addresses in the floating ip pool. If SaltCloud is running
|
||||
in parallel mode, it is possible that more than one server will attempt to
|
||||
use the same ip address.
|
||||
|
||||
'''
|
||||
|
||||
# Import python libs
|
||||
|
@ -855,40 +863,43 @@ def _assign_floating_ips(vm_, conn, kwargs):
|
|||
pool = OpenStack_1_1_FloatingIpPool(
|
||||
net['floating'], conn.connection
|
||||
)
|
||||
for idx in pool.list_floating_ips():
|
||||
if idx.node_id is None:
|
||||
floating.append(idx)
|
||||
try:
|
||||
floating.append(pool.create_floating_ip())
|
||||
except Exception as e:
|
||||
log.debug('Cannot allocate IP from floating pool \'%s\'. Checking for unassociated ips.',
|
||||
net['floating'])
|
||||
for idx in pool.list_floating_ips():
|
||||
if idx.node_id is None:
|
||||
floating.append(idx)
|
||||
break
|
||||
if not floating:
|
||||
try:
|
||||
floating.append(pool.create_floating_ip())
|
||||
except Exception as e:
|
||||
raise SaltCloudSystemExit(
|
||||
'Floating pool \'{0}\' does not have any more '
|
||||
'please create some more or use a different '
|
||||
'pool.'.format(net['floating'])
|
||||
)
|
||||
raise SaltCloudSystemExit(
|
||||
'There are no more floating IP addresses '
|
||||
'available, please create some more'
|
||||
)
|
||||
# otherwise, attempt to obtain list without specifying pool
|
||||
# this is the same as 'nova floating-ip-list'
|
||||
elif ssh_interface(vm_) != 'private_ips':
|
||||
try:
|
||||
# This try/except is here because it appears some
|
||||
# *cough* Rackspace *cough*
|
||||
# OpenStack providers return a 404 Not Found for the
|
||||
# floating ip pool URL if there are no pools setup
|
||||
pool = OpenStack_1_1_FloatingIpPool(
|
||||
'', conn.connection
|
||||
)
|
||||
for idx in pool.list_floating_ips():
|
||||
if idx.node_id is None:
|
||||
floating.append(idx)
|
||||
try:
|
||||
floating.append(pool.create_floating_ip())
|
||||
except Exception as e:
|
||||
log.debug('Cannot allocate IP from the default floating pool. Checking for unassociated ips.')
|
||||
for idx in pool.list_floating_ips():
|
||||
if idx.node_id is None:
|
||||
floating.append(idx)
|
||||
break
|
||||
if not floating:
|
||||
try:
|
||||
floating.append(pool.create_floating_ip())
|
||||
except Exception as e:
|
||||
raise SaltCloudSystemExit(
|
||||
'There are no more floating IP addresses '
|
||||
'available, please create some more'
|
||||
)
|
||||
raise SaltCloudSystemExit(
|
||||
'There are no more floating IP addresses '
|
||||
'available, please create some more'
|
||||
)
|
||||
except Exception as e:
|
||||
if str(e).startswith('404'):
|
||||
pass
|
||||
|
|
|
@ -150,7 +150,7 @@ def avail_locations(conn=None, call=None):
|
|||
|
||||
ret[img_name] = {}
|
||||
for attr in dir(img):
|
||||
if attr.startswith('_'):
|
||||
if attr.startswith('_') or attr == 'driver':
|
||||
continue
|
||||
|
||||
attr_value = getattr(img, attr)
|
||||
|
@ -187,7 +187,7 @@ def avail_images(conn=None, call=None):
|
|||
|
||||
ret[img_name] = {}
|
||||
for attr in dir(img):
|
||||
if attr.startswith('_'):
|
||||
if attr.startswith('_') or attr in ('driver', 'get_uuid'):
|
||||
continue
|
||||
attr_value = getattr(img, attr)
|
||||
if isinstance(attr_value, string_types) and not six.PY3:
|
||||
|
@ -222,7 +222,7 @@ def avail_sizes(conn=None, call=None):
|
|||
|
||||
ret[size_name] = {}
|
||||
for attr in dir(size):
|
||||
if attr.startswith('_'):
|
||||
if attr.startswith('_') or attr in ('driver', 'get_uuid'):
|
||||
continue
|
||||
|
||||
try:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
'''
|
||||
Subversion Fileserver Backend
|
||||
|
||||
After enabling this backend, branches, and tags in a remote subversion
|
||||
After enabling this backend, branches and tags in a remote subversion
|
||||
repository are exposed to salt as different environments. To enable this
|
||||
backend, add ``svn`` to the :conf_master:`fileserver_backend` option in the
|
||||
Master config file.
|
||||
|
@ -697,7 +697,7 @@ def file_hash(load, fnd):
|
|||
|
||||
def _file_lists(load, form):
|
||||
'''
|
||||
Return a dict containing the file lists for files, dirs, emtydirs and symlinks
|
||||
Return a dict containing the file lists for files, dirs, emptydirs and symlinks
|
||||
'''
|
||||
if 'env' in load:
|
||||
salt.utils.warn_until(
|
||||
|
|
|
@ -194,7 +194,7 @@ def minion_mods(
|
|||
generated modules in __context__
|
||||
|
||||
:param dict utils: Utility functions which should be made available to
|
||||
Salt modules in __utils__. See `utils_dir` in
|
||||
Salt modules in __utils__. See `utils_dirs` in
|
||||
salt.config for additional information about
|
||||
configuration.
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Compendium of generic DNS utilities
|
||||
Compendium of generic DNS utilities.
|
||||
|
||||
.. note::
|
||||
|
||||
Some functions in the ``dnsutil`` execution module depend on ``dig``.
|
||||
'''
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
@ -232,7 +236,7 @@ def check_ip(ip_addr):
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
salt ns1 dig.check_ip 127.0.0.1
|
||||
salt ns1 dnsutil.check_ip 127.0.0.1
|
||||
'''
|
||||
if _has_dig():
|
||||
return __salt__['dig.check_ip'](ip_addr)
|
||||
|
@ -242,7 +246,7 @@ def check_ip(ip_addr):
|
|||
|
||||
def A(host, nameserver=None):
|
||||
'''
|
||||
Return the A record(s) for `host`.
|
||||
Return the A record(s) for ``host``.
|
||||
|
||||
Always returns a list.
|
||||
|
||||
|
@ -267,7 +271,7 @@ def A(host, nameserver=None):
|
|||
|
||||
def AAAA(host, nameserver=None):
|
||||
'''
|
||||
Return the AAAA record(s) for `host`.
|
||||
Return the AAAA record(s) for ``host``.
|
||||
|
||||
Always returns a list.
|
||||
|
||||
|
@ -302,7 +306,7 @@ def NS(domain, resolve=True, nameserver=None):
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
salt ns1 dig.NS google.com
|
||||
salt ns1 dnsutil.NS google.com
|
||||
|
||||
'''
|
||||
if _has_dig():
|
||||
|
@ -323,7 +327,7 @@ def SPF(domain, record='SPF', nameserver=None):
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
salt ns1 dig.SPF google.com
|
||||
salt ns1 dnsutil.SPF google.com
|
||||
'''
|
||||
if _has_dig():
|
||||
return __salt__['dig.SPF'](domain, record, nameserver)
|
||||
|
@ -346,7 +350,7 @@ def MX(domain, resolve=False, nameserver=None):
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
salt ns1 dig.MX google.com
|
||||
salt ns1 dnsutil.MX google.com
|
||||
'''
|
||||
if _has_dig():
|
||||
return __salt__['dig.MX'](domain, resolve, nameserver)
|
||||
|
|
|
@ -978,6 +978,10 @@ def login(*registries):
|
|||
cmd = ['docker', 'login', '-u', username, '-p', password]
|
||||
if registry.lower() != 'hub':
|
||||
cmd.append(registry)
|
||||
log.debug(
|
||||
'Attempting to login to docker registry \'%s\' as user \'%s\'',
|
||||
registry, username
|
||||
)
|
||||
login_cmd = __salt__['cmd.run_all'](
|
||||
cmd,
|
||||
python_shell=False,
|
||||
|
|
|
@ -185,15 +185,24 @@ def _git_run(command, cwd=None, user=None, password=None, identity=None,
|
|||
identity = [identity]
|
||||
|
||||
# try each of the identities, independently
|
||||
tmp_identity_file = None
|
||||
for id_file in identity:
|
||||
if 'salt://' in id_file:
|
||||
_id_file = id_file
|
||||
id_file = __salt__['cp.cache_file'](id_file, saltenv)
|
||||
with salt.utils.files.set_umask(0o077):
|
||||
tmp_identity_file = salt.utils.mkstemp()
|
||||
_id_file = id_file
|
||||
id_file = __salt__['cp.get_file'](id_file,
|
||||
tmp_identity_file,
|
||||
saltenv)
|
||||
if not id_file:
|
||||
log.error('identity {0} does not exist.'.format(_id_file))
|
||||
__salt__['file.remove'](tmp_identity_file)
|
||||
continue
|
||||
else:
|
||||
__salt__['file.set_mode'](id_file, '0600')
|
||||
if user:
|
||||
os.chown(id_file,
|
||||
__salt__['file.user_to_uid'](user),
|
||||
-1)
|
||||
else:
|
||||
if not __salt__['file.file_exists'](id_file):
|
||||
missing_keys.append(id_file)
|
||||
|
@ -264,6 +273,11 @@ def _git_run(command, cwd=None, user=None, password=None, identity=None,
|
|||
if not salt.utils.is_windows() and 'GIT_SSH' in env:
|
||||
os.remove(env['GIT_SSH'])
|
||||
|
||||
# Cleanup the temporary identify file
|
||||
if tmp_identity_file and os.path.exists(tmp_identity_file):
|
||||
log.debug('Removing identify file {0}'.format(tmp_identity_file))
|
||||
#__salt__['file.remove'](tmp_identity_file)
|
||||
|
||||
# If the command was successful, no need to try additional IDs
|
||||
if result['retcode'] == 0:
|
||||
return result
|
||||
|
|
|
@ -262,7 +262,7 @@ def create_keytab(name, keytab, enctypes=None):
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
salt 'kdc.example.com' host/host1.example.com host1.example.com.keytab
|
||||
salt 'kdc.example.com' kerberos.create_keytab host/host1.example.com host1.example.com.keytab
|
||||
'''
|
||||
ret = {}
|
||||
|
||||
|
|
|
@ -345,9 +345,10 @@ def summary():
|
|||
|
||||
def plugin_sync():
|
||||
'''
|
||||
Runs a plugin synch between the puppet master and agent
|
||||
Runs a plugin sync between the puppet master and agent
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' puppet.plugin_sync
|
||||
|
|
|
@ -1433,7 +1433,10 @@ def absent(name):
|
|||
ret['comment'] = 'File {0} is set for removal'.format(name)
|
||||
return ret
|
||||
try:
|
||||
__salt__['file.remove'](name)
|
||||
if salt.utils.is_windows():
|
||||
__salt__['file.remove'](name, force=True)
|
||||
else:
|
||||
__salt__['file.remove'](name)
|
||||
ret['comment'] = 'Removed file {0}'.format(name)
|
||||
ret['changes']['removed'] = name
|
||||
return ret
|
||||
|
|
|
@ -1441,6 +1441,15 @@ def installed(
|
|||
'result': True,
|
||||
'comment': 'No packages to install provided'}
|
||||
|
||||
# If just a name (and optionally a version) is passed, just pack them into
|
||||
# the pkgs argument.
|
||||
if name and not any((pkgs, sources)):
|
||||
if version:
|
||||
pkgs = [{name: version}]
|
||||
version = None
|
||||
else:
|
||||
pkgs = [name]
|
||||
|
||||
kwargs['saltenv'] = __env__
|
||||
refresh = salt.utils.pkg.check_refresh(__opts__, refresh)
|
||||
if not isinstance(pkg_verify, list):
|
||||
|
@ -1607,7 +1616,7 @@ def installed(
|
|||
if salt.utils.is_freebsd():
|
||||
force = True # Downgrades need to be forced.
|
||||
try:
|
||||
pkg_ret = __salt__['pkg.install'](name,
|
||||
pkg_ret = __salt__['pkg.install'](name=None,
|
||||
refresh=refresh,
|
||||
version=version,
|
||||
force=force,
|
||||
|
|
|
@ -126,12 +126,14 @@ def _changes(name,
|
|||
if shell and lusr['shell'] != shell:
|
||||
change['shell'] = shell
|
||||
if 'shadow.info' in __salt__ and 'shadow.default_hash' in __salt__:
|
||||
if password:
|
||||
if password and not empty_password:
|
||||
default_hash = __salt__['shadow.default_hash']()
|
||||
if lshad['passwd'] == default_hash \
|
||||
or lshad['passwd'] != default_hash and enforce_password:
|
||||
if lshad['passwd'] != password:
|
||||
change['passwd'] = password
|
||||
if empty_password and lshad['passwd'] != '':
|
||||
change['empty_password'] = True
|
||||
if date and date is not 0 and lshad['lstchg'] != date:
|
||||
change['date'] = date
|
||||
if mindays and mindays is not 0 and lshad['min'] != mindays:
|
||||
|
@ -454,9 +456,6 @@ def present(name,
|
|||
if gid_from_name:
|
||||
gid = __salt__['file.group_to_gid'](name)
|
||||
|
||||
if empty_password:
|
||||
__salt__['shadow.del_password'](name)
|
||||
|
||||
changes = _changes(name,
|
||||
uid,
|
||||
gid,
|
||||
|
@ -510,6 +509,9 @@ def present(name,
|
|||
if key == 'passwd' and empty_password:
|
||||
log.warning("No password will be set when empty_password=True")
|
||||
continue
|
||||
if key == 'empty_password' and val:
|
||||
__salt__['shadow.del_password'](name)
|
||||
continue
|
||||
if key == 'date':
|
||||
__salt__['shadow.set_date'](name, date)
|
||||
continue
|
||||
|
@ -676,6 +678,14 @@ def present(name,
|
|||
' {1}'.format(name, 'XXX-REDACTED-XXX')
|
||||
ret['result'] = False
|
||||
ret['changes']['password'] = 'XXX-REDACTED-XXX'
|
||||
if empty_password and not password:
|
||||
__salt__['shadow.del_password'](name)
|
||||
spost = __salt__['shadow.info'](name)
|
||||
if spost['passwd'] != '':
|
||||
ret['comment'] = 'User {0} created but failed to ' \
|
||||
'empty password'.format(name)
|
||||
ret['result'] = False
|
||||
ret['changes']['password'] = ''
|
||||
if date:
|
||||
__salt__['shadow.set_date'](name, date)
|
||||
spost = __salt__['shadow.info'](name)
|
||||
|
|
|
@ -495,6 +495,7 @@ def container_setting(name, container, settings=None):
|
|||
processModel.maxProcesses: 1
|
||||
processModel.userName: TestUser
|
||||
processModel.password: TestPassword
|
||||
processModel.identityType: SpecificUser
|
||||
|
||||
Example of usage for the ``Sites`` container:
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ def _git_version():
|
|||
git_version = subprocess.Popen(
|
||||
['git', '--version'],
|
||||
shell=False,
|
||||
close_fds=False if salt.utils.is_windows else True,
|
||||
close_fds=False if salt.utils.is_windows() else True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE).communicate()[0]
|
||||
except OSError:
|
||||
|
|
Loading…
Add table
Reference in a new issue