mirror of
https://github.com/valitydev/salt.git
synced 2024-11-06 16:45:27 +00:00
Merge branch 'develop' into proxycaller
This commit is contained in:
commit
ed3d85be98
4
.github/stale.yml
vendored
4
.github/stale.yml
vendored
@ -1,8 +1,8 @@
|
||||
# Probot Stale configuration file
|
||||
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
# 620 is approximately 1 year and 8 months
|
||||
daysUntilStale: 620
|
||||
# 610 is approximately 1 year and 8 months
|
||||
daysUntilStale: 610
|
||||
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
|
12
conf/minion
12
conf/minion
@ -26,14 +26,14 @@
|
||||
#no_proxy: []
|
||||
|
||||
# If multiple masters are specified in the 'master' setting, the default behavior
|
||||
# is to always try to connect to them in the order they are listed. If random_master is
|
||||
# set to True, the order will be randomized instead. This can be helpful in distributing
|
||||
# the load of many minions executing salt-call requests, for example, from a cron job.
|
||||
# If only one master is listed, this setting is ignored and a warning will be logged.
|
||||
# NOTE: If master_type is set to failover, use master_shuffle instead.
|
||||
# is to always try to connect to them in the order they are listed. If random_master
|
||||
# is set to True, the order will be randomized upon Minion startup instead. This can
|
||||
# be helpful in distributing the load of many minions executing salt-call requests,
|
||||
# for example, from a cron job. If only one master is listed, this setting is ignored
|
||||
# and a warning will be logged.
|
||||
#random_master: False
|
||||
|
||||
# Use if master_type is set to failover.
|
||||
# NOTE: Deprecated in Salt Fluorine. Use 'random_master' instead.
|
||||
#master_shuffle: False
|
||||
|
||||
# Minions can connect to multiple masters simultaneously (all masters
|
||||
|
@ -286,13 +286,14 @@ to the next master in the list if it finds the existing one is dead.
|
||||
------------------
|
||||
|
||||
.. versionadded:: 2014.7.0
|
||||
.. deprecated:: Fluorine
|
||||
|
||||
Default: ``False``
|
||||
|
||||
If :conf_minion:`master` is a list of addresses and :conf_minion`master_type`
|
||||
is ``failover``, shuffle them before trying to connect to distribute the
|
||||
minions over all available masters. This uses Python's :func:`random.shuffle
|
||||
<python2:random.shuffle>` method.
|
||||
.. warning::
|
||||
|
||||
This option has been deprecated in Salt ``Fluorine``. Please use
|
||||
:conf_minion:`random_master` instead.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
@ -303,17 +304,38 @@ minions over all available masters. This uses Python's :func:`random.shuffle
|
||||
``random_master``
|
||||
-----------------
|
||||
|
||||
.. versionadded:: 2014.7.0
|
||||
.. versionchanged:: Fluorine
|
||||
The :conf_minion:`master_failback` option can be used in conjunction with
|
||||
``random_master`` to force the minion to fail back to the first master in the
|
||||
list if the first master is back online. Note that :conf_minion:`master_type`
|
||||
must be set to ``failover`` in order for the ``master_failback`` setting to
|
||||
work.
|
||||
|
||||
Default: ``False``
|
||||
|
||||
If :conf_minion:`master` is a list of addresses, and :conf_minion`master_type`
|
||||
is set to ``failover`` shuffle them before trying to connect to distribute the
|
||||
minions over all available masters. This uses Python's :func:`random.shuffle
|
||||
<python2:random.shuffle>` method.
|
||||
If :conf_minion:`master` is a list of addresses, shuffle them before trying to
|
||||
connect to distribute the minions over all available masters. This uses Python's
|
||||
:func:`random.shuffle <python2:random.shuffle>` method.
|
||||
|
||||
If multiple masters are specified in the 'master' setting as a list, the default
|
||||
behavior is to always try to connect to them in the order they are listed. If
|
||||
``random_master`` is set to True, the order will be randomized instead upon Minion
|
||||
startup. This can be helpful in distributing the load of many minions executing
|
||||
``salt-call`` requests, for example, from a cron job. If only one master is listed,
|
||||
this setting is ignored and a warning is logged.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
random_master: True
|
||||
|
||||
.. note::
|
||||
|
||||
When the ``failover``, ``master_failback``, and ``random_master`` options are
|
||||
used together, only the "secondary masters" will be shuffled. The first master
|
||||
in the list is ignored in the :func:`random.shuffle <python2:random.shuffle>`
|
||||
call. See :conf_minion:`master_failback` for more information.
|
||||
|
||||
.. conf_minion:: retry_dns
|
||||
|
||||
``retry_dns``
|
||||
|
@ -87,6 +87,7 @@ execution modules
|
||||
chocolatey
|
||||
chronos
|
||||
cimc
|
||||
ciscoconfparse_mod
|
||||
cisconso
|
||||
cloud
|
||||
cmdmod
|
||||
@ -271,9 +272,9 @@ execution modules
|
||||
namecheap_ns
|
||||
namecheap_ssl
|
||||
namecheap_users
|
||||
napalm
|
||||
napalm_acl
|
||||
napalm_bgp
|
||||
napalm_mod
|
||||
napalm_network
|
||||
napalm_ntp
|
||||
napalm_probes
|
||||
|
7
doc/ref/modules/all/salt.modules.ciscoconfparse_mod.rst
Normal file
7
doc/ref/modules/all/salt.modules.ciscoconfparse_mod.rst
Normal file
@ -0,0 +1,7 @@
|
||||
======================================
|
||||
salt.modules.ciscoconfparse_mod module
|
||||
======================================
|
||||
|
||||
.. automodule:: salt.modules.ciscoconfparse_mod
|
||||
:members:
|
||||
|
@ -1,7 +0,0 @@
|
||||
==========================
|
||||
salt.modules.napalm module
|
||||
==========================
|
||||
|
||||
.. automodule:: salt.modules.napalm
|
||||
:members:
|
||||
|
7
doc/ref/modules/all/salt.modules.napalm_mod.rst
Normal file
7
doc/ref/modules/all/salt.modules.napalm_mod.rst
Normal file
@ -0,0 +1,7 @@
|
||||
==============================
|
||||
salt.modules.napalm_mod module
|
||||
==============================
|
||||
|
||||
.. automodule:: salt.modules.napalm_mod
|
||||
:members:
|
||||
|
@ -12,7 +12,7 @@ Requirements
|
||||
|
||||
.. note::
|
||||
Support ``winexe`` and ``impacket`` has been deprecated and will be removed in
|
||||
Fluorine. These dependencies are replaced by ``pypsexec`` and ``smbprotocol``
|
||||
Sodium. These dependencies are replaced by ``pypsexec`` and ``smbprotocol``
|
||||
respectivly. These are pure python alternatives that are compatible with all
|
||||
supported python versions.
|
||||
|
||||
|
@ -521,6 +521,12 @@ their code to use ``tgt_type``.
|
||||
>>> local.cmd('*', 'cmd.run', ['whoami'], tgt_type='glob')
|
||||
{'jerry': 'root'}
|
||||
|
||||
Minion Configuration Deprecations
|
||||
---------------------------------
|
||||
|
||||
The :conf_minion:`master_shuffle` configuration option is deprecated as of the
|
||||
``Fluorine`` release. Please use the :conf_minion:`random_master` option instead.
|
||||
|
||||
Module Deprecations
|
||||
-------------------
|
||||
|
||||
@ -677,6 +683,11 @@ State Deprecations
|
||||
Utils Deprecations
|
||||
------------------
|
||||
|
||||
The ``cloud`` utils module had the following changes:
|
||||
|
||||
- Support for the ``cache_nodes_ip`` function in :mod:`salt utils module <salt.utils.cloud>`
|
||||
has been removed. The function was incomplete and non-functional.
|
||||
|
||||
The ``vault`` utils module had the following changes:
|
||||
|
||||
- Support for specifying Vault connection data within a 'profile' has been removed.
|
||||
|
@ -64,7 +64,7 @@ To match a nodegroup on the CLI, use the ``-N`` command-line option:
|
||||
|
||||
The ``N@`` classifier historically could not be used in compound matches
|
||||
within the CLI or :term:`top file`, it was only recognized in the
|
||||
:conf_master:`nodegroups` master config file parameter. As of Fluorine
|
||||
:conf_master:`nodegroups` master config file parameter. As of the Fluorine
|
||||
release, this limitation no longer exists.
|
||||
|
||||
To match a nodegroup in your :term:`top file`, make sure to put ``- match:
|
||||
|
37
rfcs/0001-adding-package-logic-to-loader.md
Normal file
37
rfcs/0001-adding-package-logic-to-loader.md
Normal file
@ -0,0 +1,37 @@
|
||||
- Feature Name: adding_python_package_logic_to_loader
|
||||
- Start Date: 2018-06-29
|
||||
- RFC PR:
|
||||
- Salt Issue:
|
||||
|
||||
# Summary
|
||||
[summary]: #summary
|
||||
|
||||
Add support for python packages to the loader. Instead of just having
|
||||
apache.py, we would have `salt/modules/apache/__init__.py`, and all the other
|
||||
modules under that directory loaded in the `apache.<function>` namespace that
|
||||
we already have.
|
||||
|
||||
# Motivation
|
||||
[motivation]: #motivation
|
||||
|
||||
Split modules out into different git repositories, and allow the community more control to help maintain them.
|
||||
|
||||
# Design
|
||||
[design]: #detailed-design
|
||||
|
||||
This should be pretty straight forward, we should be able to use the same
|
||||
_module_dirs that we are using now, and iterate over the files, and if they are
|
||||
a directory, then we load all of the modules underneath it into the namespace
|
||||
of the directory.
|
||||
|
||||
Heirarchy would be alphabetical, and we would need to log messages if the
|
||||
function exists in an earlier file that has been loaded.
|
||||
|
||||
## Alternatives
|
||||
[alternatives]: #alternatives
|
||||
|
||||
## Unresolved questions
|
||||
[unresolved]: #unresolved-questions
|
||||
|
||||
# Drawbacks
|
||||
[drawbacks]: #drawbacks
|
217
salt/beacons/watchdog.py
Normal file
217
salt/beacons/watchdog.py
Normal file
@ -0,0 +1,217 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
watchdog beacon
|
||||
|
||||
.. versionadded:: Fluorine
|
||||
|
||||
Watch files and translate the changes into salt events
|
||||
|
||||
:depends: - watchdog Python module >= 0.8.3
|
||||
|
||||
'''
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
import collections
|
||||
import logging
|
||||
|
||||
from salt.ext.six.moves import map
|
||||
|
||||
# Import third party libs
|
||||
try:
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
HAS_WATCHDOG = True
|
||||
except ImportError:
|
||||
HAS_WATCHDOG = False
|
||||
|
||||
class FileSystemEventHandler(object):
|
||||
""" A dummy class to make the import work """
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
__virtualname__ = 'watchdog'
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_MASK = [
|
||||
'create',
|
||||
'delete',
|
||||
'modify',
|
||||
'move',
|
||||
]
|
||||
|
||||
|
||||
class Handler(FileSystemEventHandler):
|
||||
def __init__(self, queue, masks=None):
|
||||
super(Handler, self).__init__()
|
||||
self.masks = masks or DEFAULT_MASK
|
||||
self.queue = queue
|
||||
|
||||
def on_created(self, event):
|
||||
self._append_if_mask(event, 'create')
|
||||
|
||||
def on_modified(self, event):
|
||||
self._append_if_mask(event, 'modify')
|
||||
|
||||
def on_deleted(self, event):
|
||||
self._append_if_mask(event, 'delete')
|
||||
|
||||
def on_moved(self, event):
|
||||
self._append_if_mask(event, 'move')
|
||||
|
||||
def _append_if_mask(self, event, mask):
|
||||
logging.debug(event)
|
||||
|
||||
self._append_path_if_mask(event, mask)
|
||||
|
||||
def _append_path_if_mask(self, event, mask):
|
||||
if mask in self.masks:
|
||||
self.queue.append(event)
|
||||
|
||||
|
||||
def __virtual__():
|
||||
if HAS_WATCHDOG:
|
||||
return __virtualname__
|
||||
return False
|
||||
|
||||
|
||||
def _get_queue(config):
|
||||
'''
|
||||
Check the context for the notifier and construct it if not present
|
||||
'''
|
||||
|
||||
if 'watchdog.observer' not in __context__:
|
||||
queue = collections.deque()
|
||||
observer = Observer()
|
||||
for path in config.get('directories', {}):
|
||||
path_params = config.get('directories').get(path)
|
||||
masks = path_params.get('mask', DEFAULT_MASK)
|
||||
event_handler = Handler(queue, masks)
|
||||
observer.schedule(event_handler, path)
|
||||
|
||||
observer.start()
|
||||
|
||||
__context__['watchdog.observer'] = observer
|
||||
__context__['watchdog.queue'] = queue
|
||||
|
||||
return __context__['watchdog.queue']
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def validate(config):
|
||||
'''
|
||||
Validate the beacon configuration
|
||||
'''
|
||||
|
||||
try:
|
||||
_validate(config)
|
||||
return True, 'Valid beacon configuration'
|
||||
except ValidationError as error:
|
||||
return False, str(error)
|
||||
|
||||
|
||||
def _validate(config):
|
||||
if not isinstance(config, list):
|
||||
raise ValidationError(
|
||||
'Configuration for watchdog beacon must be a list.')
|
||||
|
||||
_config = {}
|
||||
for part in config:
|
||||
_config.update(part)
|
||||
|
||||
if 'directories' not in _config:
|
||||
raise ValidationError(
|
||||
'Configuration for watchdog beacon must include directories.')
|
||||
|
||||
if not isinstance(_config['directories'], dict):
|
||||
raise ValidationError(
|
||||
'Configuration for watchdog beacon directories must be a '
|
||||
'dictionary.')
|
||||
|
||||
for path in _config['directories']:
|
||||
_validate_path(_config['directories'][path])
|
||||
|
||||
|
||||
def _validate_path(path_config):
|
||||
if not isinstance(path_config, dict):
|
||||
raise ValidationError(
|
||||
'Configuration for watchdog beacon directory path must be '
|
||||
'a dictionary.')
|
||||
|
||||
if 'mask' in path_config:
|
||||
_validate_mask(path_config['mask'])
|
||||
|
||||
|
||||
def _validate_mask(mask_config):
|
||||
valid_mask = [
|
||||
'create',
|
||||
'modify',
|
||||
'delete',
|
||||
'move',
|
||||
]
|
||||
|
||||
if not isinstance(mask_config, list):
|
||||
raise ValidationError(
|
||||
'Configuration for watchdog beacon mask must be list.')
|
||||
|
||||
if any(mask not in valid_mask for mask in mask_config):
|
||||
raise ValidationError(
|
||||
'Configuration for watchdog beacon contains invalid mask')
|
||||
|
||||
|
||||
def to_salt_event(event):
|
||||
return {
|
||||
'tag': __virtualname__,
|
||||
'path': event.src_path,
|
||||
'change': event.event_type,
|
||||
}
|
||||
|
||||
|
||||
def beacon(config):
|
||||
'''
|
||||
Watch the configured directories
|
||||
|
||||
Example Config
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
beacons:
|
||||
watchdog:
|
||||
- directories:
|
||||
/path/to/dir:
|
||||
mask:
|
||||
- create
|
||||
- modify
|
||||
- delete
|
||||
- move
|
||||
|
||||
The mask list can contain the following events (the default mask is create,
|
||||
modify delete, and move):
|
||||
* create - File or directory is created in watched directory
|
||||
* modify - The watched directory is modified
|
||||
* delete - File or directory is deleted from watched directory
|
||||
* move - File or directory is moved or renamed in the watched directory
|
||||
'''
|
||||
|
||||
_config = {}
|
||||
list(map(_config.update, config))
|
||||
|
||||
queue = _get_queue(_config)
|
||||
|
||||
ret = []
|
||||
while queue:
|
||||
ret.append(to_salt_event(queue.popleft()))
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def close(config):
|
||||
observer = __context__.pop('watchdog.observer', None)
|
||||
|
||||
if observer:
|
||||
observer.stop()
|
@ -351,7 +351,9 @@ class SSH(object):
|
||||
return
|
||||
|
||||
hostname = self.opts['tgt'].split('@')[-1]
|
||||
needs_expansion = '*' not in hostname and salt.utils.network.is_reachable_host(hostname)
|
||||
needs_expansion = '*' not in hostname and \
|
||||
salt.utils.network.is_reachable_host(hostname) and \
|
||||
salt.utils.network.is_ip(hostname)
|
||||
if needs_expansion:
|
||||
hostname = salt.utils.network.ip_to_host(hostname)
|
||||
if hostname is None:
|
||||
|
@ -134,7 +134,8 @@ VALID_OPTS = {
|
||||
# a master fingerprint with `salt-key -F master`
|
||||
'master_finger': six.string_types,
|
||||
|
||||
# Selects a random master when starting a minion up in multi-master mode
|
||||
# Deprecated in Fluorine. Use 'random_master' instead.
|
||||
# Do not remove! Keep as an alias for usability.
|
||||
'master_shuffle': bool,
|
||||
|
||||
# When in multi-master mode, temporarily remove a master from the list if a conenction
|
||||
@ -967,6 +968,8 @@ VALID_OPTS = {
|
||||
# Never give up when trying to authenticate to a master
|
||||
'auth_safemode': bool,
|
||||
|
||||
# Selects a random master when starting a minion up in multi-master mode or
|
||||
# when starting a minion with salt-call. ``master`` must be a list.
|
||||
'random_master': bool,
|
||||
|
||||
# An upper bound for the amount of time for a minion to sleep before attempting to
|
||||
|
@ -90,6 +90,10 @@ if salt.utils.platform.is_windows():
|
||||
'will be missing'
|
||||
)
|
||||
|
||||
HAS_UNAME = True
|
||||
if not hasattr(os, 'uname'):
|
||||
HAS_UNAME = False
|
||||
|
||||
_INTERFACES = {}
|
||||
|
||||
|
||||
@ -693,7 +697,7 @@ def _virtual(osdata):
|
||||
|
||||
# Quick backout for BrandZ (Solaris LX Branded zones)
|
||||
# Don't waste time trying other commands to detect the virtual grain
|
||||
if osdata['kernel'] == 'Linux' and 'BrandZ virtual linux' in os.uname():
|
||||
if HAS_UNAME and osdata['kernel'] == 'Linux' and 'BrandZ virtual linux' in os.uname():
|
||||
grains['virtual'] = 'zone'
|
||||
return grains
|
||||
|
||||
@ -1846,7 +1850,10 @@ def os_data():
|
||||
elif grains['kernel'] == 'SunOS':
|
||||
if salt.utils.platform.is_smartos():
|
||||
# See https://github.com/joyent/smartos-live/issues/224
|
||||
uname_v = os.uname()[3] # format: joyent_20161101T004406Z
|
||||
if HAS_UNAME:
|
||||
uname_v = os.uname()[3] # format: joyent_20161101T004406Z
|
||||
else:
|
||||
uname_v = os.name
|
||||
uname_v = uname_v[uname_v.index('_')+1:]
|
||||
grains['os'] = grains['osfullname'] = 'SmartOS'
|
||||
# store a parsed version of YYYY.MM.DD as osrelease
|
||||
@ -1875,7 +1882,10 @@ def os_data():
|
||||
else:
|
||||
if development is not None:
|
||||
osname = ' '.join((osname, development))
|
||||
uname_v = os.uname()[3]
|
||||
if HAS_UNAME:
|
||||
uname_v = os.uname()[3]
|
||||
else:
|
||||
uname_v = os.name
|
||||
grains['os'] = grains['osfullname'] = osname
|
||||
if osname in ['Oracle Solaris'] and uname_v.startswith(osmajorrelease):
|
||||
# Oracla Solars 11 and up have minor version in uname
|
||||
|
@ -507,12 +507,12 @@ class MinionBase(object):
|
||||
log.warning('master_type = distributed needs more than 1 master.')
|
||||
|
||||
if opts['master_shuffle']:
|
||||
if opts['master_failback']:
|
||||
secondary_masters = opts['master'][1:]
|
||||
shuffle(secondary_masters)
|
||||
opts['master'][1:] = secondary_masters
|
||||
else:
|
||||
shuffle(opts['master'])
|
||||
log.warning(
|
||||
'Use of \'master_shuffle\' detected. \'master_shuffle\' is deprecated in favor '
|
||||
'of \'random_master\'. Please update your minion config file.'
|
||||
)
|
||||
opts['random_master'] = opts['master_shuffle']
|
||||
|
||||
opts['auth_tries'] = 0
|
||||
if opts['master_failback'] and opts['master_failback_interval'] == 0:
|
||||
opts['master_failback_interval'] = opts['master_alive_interval']
|
||||
@ -578,12 +578,19 @@ class MinionBase(object):
|
||||
# happy with the first one that allows us to connect
|
||||
if isinstance(opts['master'], list):
|
||||
conn = False
|
||||
# shuffle the masters and then loop through them
|
||||
opts['local_masters'] = copy.copy(opts['master'])
|
||||
if opts['random_master']:
|
||||
shuffle(opts['local_masters'])
|
||||
last_exc = None
|
||||
opts['master_uri_list'] = list()
|
||||
opts['master_uri_list'] = []
|
||||
opts['local_masters'] = copy.copy(opts['master'])
|
||||
|
||||
# shuffle the masters and then loop through them
|
||||
if opts['random_master']:
|
||||
# master_failback is only used when master_type is set to failover
|
||||
if opts['master_type'] == 'failover' and opts['master_failback']:
|
||||
secondary_masters = opts['local_masters'][1:]
|
||||
shuffle(secondary_masters)
|
||||
opts['local_masters'][1:] = secondary_masters
|
||||
else:
|
||||
shuffle(opts['local_masters'])
|
||||
|
||||
# This sits outside of the connection loop below because it needs to set
|
||||
# up a list of master URIs regardless of which masters are available
|
||||
|
443
salt/modules/ciscoconfparse_mod.py
Normal file
443
salt/modules/ciscoconfparse_mod.py
Normal file
@ -0,0 +1,443 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Execution module for `ciscoconfparse <http://www.pennington.net/py/ciscoconfparse/index.html>`_
|
||||
|
||||
.. versionadded:: Fluorine
|
||||
|
||||
This module can be used for basic configuration parsing, audit or validation
|
||||
for a variety of network platforms having Cisco IOS style configuration (one
|
||||
space indentation), including: Cisco IOS, Cisco Nexus, Cisco IOS-XR,
|
||||
Cisco IOS-XR, Cisco ASA, Arista EOS, Brocade, HP Switches, Dell PowerConnect
|
||||
Switches, or Extreme Networks devices. In newer versions, ``ciscoconfparse``
|
||||
provides support for brace-delimited configuration style as well, for platforms
|
||||
such as: Juniper Junos, Palo Alto, or F5 Networks.
|
||||
|
||||
See http://www.pennington.net/py/ciscoconfparse/index.html for further details.
|
||||
|
||||
:depends: ciscoconfparse
|
||||
|
||||
This module depends on the Python library with the same name,
|
||||
``ciscoconfparse`` - to install execute: ``pip install ciscoconfparse``.
|
||||
'''
|
||||
# Import Python Libs
|
||||
from __future__ import absolute_import, unicode_literals, print_function
|
||||
|
||||
# Import Salt modules
|
||||
from salt.ext import six
|
||||
from salt.exceptions import SaltException
|
||||
|
||||
try:
|
||||
import ciscoconfparse
|
||||
HAS_CISCOCONFPARSE = True
|
||||
except ImportError:
|
||||
HAS_CISCOCONFPARSE = False
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# module properties
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
__virtualname__ = 'ciscoconfparse'
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# property functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def __virtual__():
|
||||
return HAS_CISCOCONFPARSE
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# helper functions -- will not be exported
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _get_ccp(config=None, config_path=None, saltenv='base'):
|
||||
'''
|
||||
'''
|
||||
if config_path:
|
||||
config = __salt__['cp.get_file_str'](config_path, saltenv=saltenv)
|
||||
if config is False:
|
||||
raise SaltException('{} is not available'.format(config_path))
|
||||
if isinstance(config, six.string_types):
|
||||
config = config.splitlines()
|
||||
ccp = ciscoconfparse.CiscoConfParse(config)
|
||||
return ccp
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# callable functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def find_objects(config=None, config_path=None, regex=None, saltenv='base'):
|
||||
'''
|
||||
Return all the line objects that match the expression in the ``regex``
|
||||
argument.
|
||||
|
||||
.. warning::
|
||||
This function is mostly valuable when invoked from other Salt
|
||||
components (i.e., execution modules, states, templates etc.). For CLI
|
||||
usage, please consider using
|
||||
:py:func:`ciscoconfparse.find_lines <salt.ciscoconfparse_mod.find_lines>`
|
||||
|
||||
config
|
||||
The configuration sent as text.
|
||||
|
||||
.. note::
|
||||
This argument is ignored when ``config_path`` is specified.
|
||||
|
||||
config_path
|
||||
The absolute or remote path to the file with the configuration to be
|
||||
parsed. This argument supports the usual Salt filesystem URIs, e.g.,
|
||||
``salt://``, ``https://``, ``ftp://``, ``s3://``, etc.
|
||||
|
||||
regex
|
||||
The regular expression to match the lines against.
|
||||
|
||||
saltenv: ``base``
|
||||
Salt fileserver environment from which to retrieve the file. This
|
||||
argument is ignored when ``config_path`` is not a ``salt://`` URL.
|
||||
|
||||
Usage example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
objects = __salt__['ciscoconfparse.find_objects'](config_path='salt://path/to/config.txt',
|
||||
regex='Gigabit')
|
||||
for obj in objects:
|
||||
print(obj.text)
|
||||
'''
|
||||
ccp = _get_ccp(config=config, config_path=config_path, saltenv=saltenv)
|
||||
lines = ccp.find_objects(regex)
|
||||
return lines
|
||||
|
||||
|
||||
def find_lines(config=None, config_path=None, regex=None, saltenv='base'):
|
||||
'''
|
||||
Return all the lines (as text) that match the expression in the ``regex``
|
||||
argument.
|
||||
|
||||
config
|
||||
The configuration sent as text.
|
||||
|
||||
.. note::
|
||||
This argument is ignored when ``config_path`` is specified.
|
||||
|
||||
config_path
|
||||
The absolute or remote path to the file with the configuration to be
|
||||
parsed. This argument supports the usual Salt filesystem URIs, e.g.,
|
||||
``salt://``, ``https://``, ``ftp://``, ``s3://``, etc.
|
||||
|
||||
regex
|
||||
The regular expression to match the lines against.
|
||||
|
||||
saltenv: ``base``
|
||||
Salt fileserver environment from which to retrieve the file. This
|
||||
argument is ignored when ``config_path`` is not a ``salt://`` URL.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' ciscoconfparse.find_lines config_path=https://bit.ly/2mAdq7z regex='ip address'
|
||||
|
||||
Output example:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
cisco-ios-router:
|
||||
- ip address dhcp
|
||||
- ip address 172.20.0.1 255.255.255.0
|
||||
- no ip address
|
||||
'''
|
||||
lines = find_objects(config=config,
|
||||
config_path=config_path,
|
||||
regex=regex,
|
||||
saltenv=saltenv)
|
||||
return [line.text for line in lines]
|
||||
|
||||
|
||||
def find_objects_w_child(config=None,
|
||||
config_path=None,
|
||||
parent_regex=None,
|
||||
child_regex=None,
|
||||
ignore_ws=False,
|
||||
saltenv='base'):
|
||||
'''
|
||||
Parse through the children of all parent lines matching ``parent_regex``,
|
||||
and return a list of child objects, which matched the ``child_regex``.
|
||||
|
||||
.. warning::
|
||||
This function is mostly valuable when invoked from other Salt
|
||||
components (i.e., execution modules, states, templates etc.). For CLI
|
||||
usage, please consider using
|
||||
:py:func:`ciscoconfparse.find_lines_w_child <salt.ciscoconfparse_mod.find_lines_w_child>`
|
||||
|
||||
config
|
||||
The configuration sent as text.
|
||||
|
||||
.. note::
|
||||
This argument is ignored when ``config_path`` is specified.
|
||||
|
||||
config_path
|
||||
The absolute or remote path to the file with the configuration to be
|
||||
parsed. This argument supports the usual Salt filesystem URIs, e.g.,
|
||||
``salt://``, ``https://``, ``ftp://``, ``s3://``, etc.
|
||||
|
||||
parent_regex
|
||||
The regular expression to match the parent lines against.
|
||||
|
||||
child_regex
|
||||
The regular expression to match the child lines against.
|
||||
|
||||
ignore_ws: ``False``
|
||||
Whether to ignore the white spaces.
|
||||
|
||||
saltenv: ``base``
|
||||
Salt fileserver environment from which to retrieve the file. This
|
||||
argument is ignored when ``config_path`` is not a ``salt://`` URL.
|
||||
|
||||
Usage example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
objects = __salt__['ciscoconfparse.find_objects_w_child'](config_path='https://bit.ly/2mAdq7z',
|
||||
parent_regex='line con',
|
||||
child_regex='stopbits')
|
||||
for obj in objects:
|
||||
print(obj.text)
|
||||
'''
|
||||
ccp = _get_ccp(config=config, config_path=config_path, saltenv=saltenv)
|
||||
lines = ccp.find_objects_w_child(parent_regex, child_regex, ignore_ws=ignore_ws)
|
||||
return lines
|
||||
|
||||
|
||||
def find_lines_w_child(config=None,
|
||||
config_path=None,
|
||||
parent_regex=None,
|
||||
child_regex=None,
|
||||
ignore_ws=False,
|
||||
saltenv='base'):
|
||||
r'''
|
||||
Return a list of parent lines (as text) matching the regular expression
|
||||
``parent_regex`` that have children lines matching ``child_regex``.
|
||||
|
||||
config
|
||||
The configuration sent as text.
|
||||
|
||||
.. note::
|
||||
This argument is ignored when ``config_path`` is specified.
|
||||
|
||||
config_path
|
||||
The absolute or remote path to the file with the configuration to be
|
||||
parsed. This argument supports the usual Salt filesystem URIs, e.g.,
|
||||
``salt://``, ``https://``, ``ftp://``, ``s3://``, etc.
|
||||
|
||||
parent_regex
|
||||
The regular expression to match the parent lines against.
|
||||
|
||||
child_regex
|
||||
The regular expression to match the child lines against.
|
||||
|
||||
ignore_ws: ``False``
|
||||
Whether to ignore the white spaces.
|
||||
|
||||
saltenv: ``base``
|
||||
Salt fileserver environment from which to retrieve the file. This
|
||||
argument is ignored when ``config_path`` is not a ``salt://`` URL.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' ciscoconfparse.find_lines_w_child config_path=https://bit.ly/2mAdq7z parent_line='line con' child_line='stopbits'
|
||||
salt '*' ciscoconfparse.find_lines_w_child config_path=https://bit.ly/2uIRxau parent_regex='ge-(.*)' child_regex='unit \d+'
|
||||
'''
|
||||
lines = find_objects_w_child(config=config,
|
||||
config_path=config_path,
|
||||
parent_regex=parent_regex,
|
||||
child_regex=child_regex,
|
||||
ignore_ws=ignore_ws,
|
||||
saltenv=saltenv)
|
||||
return [line.text for line in lines]
|
||||
|
||||
|
||||
def find_objects_wo_child(config=None,
|
||||
config_path=None,
|
||||
parent_regex=None,
|
||||
child_regex=None,
|
||||
ignore_ws=False,
|
||||
saltenv='base'):
|
||||
'''
|
||||
Return a list of parent ``ciscoconfparse.IOSCfgLine`` objects, which matched
|
||||
the ``parent_regex`` and whose children did *not* match ``child_regex``.
|
||||
Only the parent ``ciscoconfparse.IOSCfgLine`` objects will be returned. For
|
||||
simplicity, this method only finds oldest ancestors without immediate
|
||||
children that match.
|
||||
|
||||
.. warning::
|
||||
This function is mostly valuable when invoked from other Salt
|
||||
components (i.e., execution modules, states, templates etc.). For CLI
|
||||
usage, please consider using
|
||||
:py:func:`ciscoconfparse.find_lines_wo_child <salt.ciscoconfparse_mod.find_lines_wo_child>`
|
||||
|
||||
config
|
||||
The configuration sent as text.
|
||||
|
||||
.. note::
|
||||
This argument is ignored when ``config_path`` is specified.
|
||||
|
||||
config_path
|
||||
The absolute or remote path to the file with the configuration to be
|
||||
parsed. This argument supports the usual Salt filesystem URIs, e.g.,
|
||||
``salt://``, ``https://``, ``ftp://``, ``s3://``, etc.
|
||||
|
||||
parent_regex
|
||||
The regular expression to match the parent lines against.
|
||||
|
||||
child_regex
|
||||
The regular expression to match the child lines against.
|
||||
|
||||
ignore_ws: ``False``
|
||||
Whether to ignore the white spaces.
|
||||
|
||||
saltenv: ``base``
|
||||
Salt fileserver environment from which to retrieve the file. This
|
||||
argument is ignored when ``config_path`` is not a ``salt://`` URL.
|
||||
|
||||
Usage example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
objects = __salt__['ciscoconfparse.find_objects_wo_child'](config_path='https://bit.ly/2mAdq7z',
|
||||
parent_regex='line con',
|
||||
child_regex='stopbits')
|
||||
for obj in objects:
|
||||
print(obj.text)
|
||||
'''
|
||||
ccp = _get_ccp(config=config, config_path=config_path, saltenv=saltenv)
|
||||
lines = ccp.find_objects_wo_child(parent_regex, child_regex, ignore_ws=ignore_ws)
|
||||
return lines
|
||||
|
||||
|
||||
def find_lines_wo_child(config=None,
|
||||
config_path=None,
|
||||
parent_regex=None,
|
||||
child_regex=None,
|
||||
ignore_ws=False,
|
||||
saltenv='base'):
|
||||
'''
|
||||
Return a list of parent ``ciscoconfparse.IOSCfgLine`` lines as text, which
|
||||
matched the ``parent_regex`` and whose children did *not* match ``child_regex``.
|
||||
Only the parent ``ciscoconfparse.IOSCfgLine`` text lines will be returned.
|
||||
For simplicity, this method only finds oldest ancestors without immediate
|
||||
children that match.
|
||||
|
||||
config
|
||||
The configuration sent as text.
|
||||
|
||||
.. note::
|
||||
This argument is ignored when ``config_path`` is specified.
|
||||
|
||||
config_path
|
||||
The absolute or remote path to the file with the configuration to be
|
||||
parsed. This argument supports the usual Salt filesystem URIs, e.g.,
|
||||
``salt://``, ``https://``, ``ftp://``, ``s3://``, etc.
|
||||
|
||||
parent_regex
|
||||
The regular expression to match the parent lines against.
|
||||
|
||||
child_regex
|
||||
The regular expression to match the child lines against.
|
||||
|
||||
ignore_ws: ``False``
|
||||
Whether to ignore the white spaces.
|
||||
|
||||
saltenv: ``base``
|
||||
Salt fileserver environment from which to retrieve the file. This
|
||||
argument is ignored when ``config_path`` is not a ``salt://`` URL.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' ciscoconfparse.find_lines_wo_child config_path=https://bit.ly/2mAdq7z parent_line='line con' child_line='stopbits'
|
||||
'''
|
||||
lines = find_objects_wo_child(config=config,
|
||||
config_path=config_path,
|
||||
parent_regex=parent_regex,
|
||||
child_regex=child_regex,
|
||||
ignore_ws=ignore_ws,
|
||||
saltenv=saltenv)
|
||||
return [line.text for line in lines]
|
||||
|
||||
|
||||
def filter_lines(config=None,
|
||||
config_path=None,
|
||||
parent_regex=None,
|
||||
child_regex=None,
|
||||
saltenv='base'):
|
||||
'''
|
||||
Return a list of detailed matches, for the configuration blocks (parent-child
|
||||
relationship) whose parent respects the regular expressions configured via
|
||||
the ``parent_regex`` argument, and the child matches the ``child_regex``
|
||||
regular expression. The result is a list of dictionaries with the following
|
||||
keys:
|
||||
|
||||
- ``match``: a boolean value that tells whether ``child_regex`` matched any
|
||||
children lines.
|
||||
- ``parent``: the parent line (as text).
|
||||
- ``child``: the child line (as text). If no child line matched, this field
|
||||
will be ``None``.
|
||||
|
||||
Note that the return list contains the elements that matched the parent
|
||||
condition, the ``parent_regex`` regular expression. Therefore, the ``parent``
|
||||
field will always have a valid value, while ``match`` and ``child`` may
|
||||
default to ``False`` and ``None`` respectively when there is not child match.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' ciscoconfparse.filter_lines config_path=https://bit.ly/2mAdq7z parent_regex='Gigabit' child_regex='shutdown'
|
||||
|
||||
Example output (for the example above):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
{
|
||||
'parent': 'interface GigabitEthernet1',
|
||||
'match': False,
|
||||
'child': None
|
||||
},
|
||||
{
|
||||
'parent': 'interface GigabitEthernet2',
|
||||
'match': True,
|
||||
'child': ' shutdown'
|
||||
},
|
||||
{
|
||||
'parent': 'interface GigabitEthernet3',
|
||||
'match': True,
|
||||
'child': ' shutdown'
|
||||
}
|
||||
]
|
||||
'''
|
||||
ret = []
|
||||
ccp = _get_ccp(config=config, config_path=config_path, saltenv=saltenv)
|
||||
parent_lines = ccp.find_objects(parent_regex)
|
||||
for parent_line in parent_lines:
|
||||
child_lines = parent_line.re_search_children(child_regex)
|
||||
if child_lines:
|
||||
for child_line in child_lines:
|
||||
ret.append({
|
||||
'match': True,
|
||||
'parent': parent_line.text,
|
||||
'child': child_line.text
|
||||
})
|
||||
else:
|
||||
ret.append({
|
||||
'match': False,
|
||||
'parent': parent_line.text,
|
||||
'child': None
|
||||
})
|
||||
return ret
|
@ -462,6 +462,7 @@ def gather_bootstrap_script(bootstrap=None):
|
||||
if 'Success' in ret and len(ret['Success']['Files updated']) > 0:
|
||||
return ret['Success']['Files updated'][0]
|
||||
|
||||
|
||||
def items():
|
||||
'''
|
||||
Return the complete config from the currently running minion process.
|
||||
|
@ -85,6 +85,10 @@ def usage(args=None):
|
||||
'''
|
||||
Return usage information for volumes mounted on this minion
|
||||
|
||||
.. versionchanged:: Fluorine
|
||||
|
||||
Default for SunOS changed to 1 kilobyte blocks
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
@ -103,6 +107,8 @@ def usage(args=None):
|
||||
cmd = 'df -P'
|
||||
elif __grains__['kernel'] == 'OpenBSD' or __grains__['kernel'] == 'AIX':
|
||||
cmd = 'df -kP'
|
||||
elif __grains__['kernel'] == 'SunOS':
|
||||
cmd = 'df -k'
|
||||
else:
|
||||
cmd = 'df'
|
||||
if flags:
|
||||
|
@ -20,6 +20,7 @@ from salt.utils.napalm import proxy_napalm_wrap
|
||||
|
||||
# Import Salt modules
|
||||
from salt.ext import six
|
||||
from salt.utils.decorators import depends
|
||||
from salt.exceptions import CommandExecutionError
|
||||
try:
|
||||
from netmiko import BaseConnection
|
||||
@ -39,6 +40,12 @@ try:
|
||||
except ImportError:
|
||||
HAS_JXMLEASE = False
|
||||
|
||||
try:
|
||||
import ciscoconfparse # pylint: disable=unused-import
|
||||
HAS_CISCOCONFPARSE = True
|
||||
except ImportError:
|
||||
HAS_CISCOCONFPARSE = False
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
# module properties
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
@ -1286,3 +1293,156 @@ def rpc(command, **kwargs):
|
||||
napalm_map.update(default_map)
|
||||
fun = napalm_map.get(__grains__['os'], 'napalm.netmiko_commands')
|
||||
return __salt__[fun](command, **kwargs)
|
||||
|
||||
|
||||
@depends(HAS_CISCOCONFPARSE)
|
||||
def config_find_lines(regex, source='running'):
|
||||
r'''
|
||||
.. versionadded:: Fluorine
|
||||
|
||||
Return the configuration lines that match the regular expressions from the
|
||||
``regex`` argument. The configuration is read from the network device
|
||||
interrogated.
|
||||
|
||||
regex
|
||||
The regular expression to match the configuration lines against.
|
||||
|
||||
source: ``running``
|
||||
The configuration type to retrieve from the network device. Default:
|
||||
``running``. Available options: ``running``, ``startup``, ``candidate``.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' napalm.config_find_lines '^interface Ethernet1\d'
|
||||
'''
|
||||
config_txt = __salt__['net.config'](source=source)['out'][source]
|
||||
return __salt__['ciscoconfparse.find_lines'](config=config_txt,
|
||||
regex=regex)
|
||||
|
||||
|
||||
@depends(HAS_CISCOCONFPARSE)
|
||||
def config_lines_w_child(parent_regex, child_regex, source='running'):
|
||||
r'''
|
||||
.. versionadded:: Fluorine
|
||||
|
||||
Return the configuration lines that match the regular expressions from the
|
||||
``parent_regex`` argument, having child lines matching ``child_regex``.
|
||||
The configuration is read from the network device interrogated.
|
||||
|
||||
.. note::
|
||||
This function is only available only when the underlying library
|
||||
`ciscoconfparse <http://www.pennington.net/py/ciscoconfparse/index.html>`_
|
||||
is installed. See
|
||||
:py:func:`ciscoconfparse module <salt.modules.ciscoconfparse_mod>` for
|
||||
more details.
|
||||
|
||||
parent_regex
|
||||
The regular expression to match the parent configuration lines against.
|
||||
|
||||
child_regex
|
||||
The regular expression to match the child configuration lines against.
|
||||
|
||||
source: ``running``
|
||||
The configuration type to retrieve from the network device. Default:
|
||||
``running``. Available options: ``running``, ``startup``, ``candidate``.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' napalm.config_lines_w_child '^interface' 'ip address'
|
||||
salt '*' napalm.config_lines_w_child '^interface' 'shutdown' source=candidate
|
||||
'''
|
||||
config_txt = __salt__['net.config'](source=source)['out'][source]
|
||||
return __salt__['ciscoconfparse.find_lines_w_child'](config=config_txt,
|
||||
parent_regex=parent_regex,
|
||||
child_regex=child_regex)
|
||||
|
||||
|
||||
@depends(HAS_CISCOCONFPARSE)
|
||||
def config_lines_wo_child(parent_regex, child_regex, source='running'):
|
||||
'''
|
||||
.. versionadded:: Fluorine
|
||||
|
||||
Return the configuration lines that match the regular expressions from the
|
||||
``parent_regex`` argument, having the child lines *not* matching
|
||||
``child_regex``.
|
||||
The configuration is read from the network device interrogated.
|
||||
|
||||
.. note::
|
||||
This function is only available only when the underlying library
|
||||
`ciscoconfparse <http://www.pennington.net/py/ciscoconfparse/index.html>`_
|
||||
is installed. See
|
||||
:py:func:`ciscoconfparse module <salt.modules.ciscoconfparse_mod>` for
|
||||
more details.
|
||||
|
||||
parent_regex
|
||||
The regular expression to match the parent configuration lines against.
|
||||
|
||||
child_regex
|
||||
The regular expression to match the child configuration lines against.
|
||||
|
||||
source: ``running``
|
||||
The configuration type to retrieve from the network device. Default:
|
||||
``running``. Available options: ``running``, ``startup``, ``candidate``.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' napalm.config_lines_wo_child '^interface' 'ip address'
|
||||
salt '*' napalm.config_lines_wo_child '^interface' 'shutdown' source=candidate
|
||||
'''
|
||||
config_txt = __salt__['net.config'](source=source)['out'][source]
|
||||
return __salt__['ciscoconfparse.find_lines_wo_child'](config=config_txt,
|
||||
parent_regex=parent_regex,
|
||||
child_regex=child_regex)
|
||||
|
||||
|
||||
@depends(HAS_CISCOCONFPARSE)
|
||||
def config_filter_lines(parent_regex, child_regex, source='running'):
|
||||
r'''
|
||||
.. versionadded:: Fluorine
|
||||
|
||||
Return a list of detailed matches, for the configuration blocks (parent-child
|
||||
relationship) whose parent respects the regular expressions configured via
|
||||
the ``parent_regex`` argument, and the child matches the ``child_regex``
|
||||
regular expression. The result is a list of dictionaries with the following
|
||||
keys:
|
||||
|
||||
- ``match``: a boolean value that tells whether ``child_regex`` matched any
|
||||
children lines.
|
||||
- ``parent``: the parent line (as text).
|
||||
- ``child``: the child line (as text). If no child line matched, this field
|
||||
will be ``None``.
|
||||
|
||||
.. note::
|
||||
This function is only available only when the underlying library
|
||||
`ciscoconfparse <http://www.pennington.net/py/ciscoconfparse/index.html>`_
|
||||
is installed. See
|
||||
:py:func:`ciscoconfparse module <salt.modules.ciscoconfparse_mod>` for
|
||||
more details.
|
||||
|
||||
parent_regex
|
||||
The regular expression to match the parent configuration lines against.
|
||||
|
||||
child_regex
|
||||
The regular expression to match the child configuration lines against.
|
||||
|
||||
source: ``running``
|
||||
The configuration type to retrieve from the network device. Default:
|
||||
``running``. Available options: ``running``, ``startup``, ``candidate``.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' napalm.config_filter_lines '^interface' 'ip address'
|
||||
salt '*' napalm.config_filter_lines '^interface' 'shutdown' source=candidate
|
||||
'''
|
||||
config_txt = __salt__['net.config'](source=source)['out'][source]
|
||||
return __salt__['ciscoconfparse.filter_lines'](config=config_txt,
|
||||
parent_regex=parent_regex,
|
||||
child_regex=child_regex)
|
||||
|
@ -54,6 +54,7 @@ INI_FILE = '/etc/natinst/share/ni-rt.ini'
|
||||
_CONFIG_TRUE = ['yes', 'on', 'true', '1', True]
|
||||
IFF_LOOPBACK = 0x8
|
||||
IFF_RUNNING = 0x40
|
||||
NIRTCFG_ETHERCAT = 'EtherCAT'
|
||||
|
||||
|
||||
def _assume_condition(condition, err):
|
||||
@ -258,25 +259,47 @@ def _remove_quotes(value):
|
||||
return value
|
||||
|
||||
|
||||
def _get_requestmode_info(interface):
|
||||
def _load_config(section, options, default_value='', filename=INI_FILE):
|
||||
'''
|
||||
Get values for some options and a given section from a config file.
|
||||
|
||||
:param section: Section Name
|
||||
:param options: List of options
|
||||
:param default_value: Default value if an option doesn't have a value. Default is empty string.
|
||||
:param filename: config file. Default is INI_FILE.
|
||||
:return:
|
||||
'''
|
||||
results = {}
|
||||
if not options:
|
||||
return results
|
||||
with salt.utils.files.fopen(filename, 'r') as config_file:
|
||||
config_parser = configparser.RawConfigParser(dict_type=CaseInsensitiveDict)
|
||||
config_parser.readfp(config_file)
|
||||
for option in options:
|
||||
if six.PY2:
|
||||
results[option] = _remove_quotes(config_parser.get(section, option)) \
|
||||
if config_parser.has_option(section, option) else default_value
|
||||
else:
|
||||
results[option] = _remove_quotes(config_parser.get(section, option, fallback=default_value))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _get_request_mode_info(interface):
|
||||
'''
|
||||
return requestmode for given interface
|
||||
'''
|
||||
with salt.utils.files.fopen(INI_FILE, 'r') as config_file:
|
||||
config_parser = configparser.RawConfigParser(dict_type=CaseInsensitiveDict)
|
||||
config_parser.read_file(config_file)
|
||||
link_local_enabled = '' if not config_parser.has_option(interface, 'linklocalenabled') else \
|
||||
int(_remove_quotes(config_parser.get(interface, 'linklocalenabled')))
|
||||
dhcp_enabled = '' if not config_parser.has_option(interface, 'dhcpenabled') else \
|
||||
int(_remove_quotes(config_parser.get(interface, 'dhcpenabled')))
|
||||
settings = _load_config(interface, ['linklocalenabled', 'dhcpenabled'], -1)
|
||||
link_local_enabled = int(settings['linklocalenabled'])
|
||||
dhcp_enabled = int(settings['dhcpenabled'])
|
||||
|
||||
if dhcp_enabled == 1:
|
||||
return 'dhcp_linklocal' if link_local_enabled == 1 else 'dhcp_only'
|
||||
else:
|
||||
if link_local_enabled == 1:
|
||||
return 'linklocal_only'
|
||||
if link_local_enabled == 0:
|
||||
return 'static'
|
||||
if dhcp_enabled == 1:
|
||||
return 'dhcp_linklocal' if link_local_enabled == 1 else 'dhcp_only'
|
||||
else:
|
||||
if link_local_enabled == 1:
|
||||
return 'linklocal_only'
|
||||
if link_local_enabled == 0:
|
||||
return 'static'
|
||||
|
||||
# some versions of nirtcfg don't set the dhcpenabled/linklocalenabled variables
|
||||
# when selecting "DHCP or Link Local" from MAX, so return it by default to avoid
|
||||
@ -284,19 +307,54 @@ def _get_requestmode_info(interface):
|
||||
return 'dhcp_linklocal'
|
||||
|
||||
|
||||
def _get_adapter_mode_info(interface):
|
||||
'''
|
||||
return adaptermode for given interface
|
||||
'''
|
||||
mode = _load_config(interface, ['mode'])['mode'].lower()
|
||||
return mode if mode in ['disabled', 'ethercat'] else 'tcpip'
|
||||
|
||||
|
||||
def _get_possible_adapter_modes(interface, blacklist):
|
||||
'''
|
||||
Return possible adapter modes for a given interface using a blacklist.
|
||||
|
||||
:param interface: interface name
|
||||
:param blacklist: given blacklist
|
||||
:return: list of possible adapter modes
|
||||
'''
|
||||
adapter_modes = []
|
||||
protocols = _load_config('lvrt', ['AdditionalNetworkProtocols'])['AdditionalNetworkProtocols'].lower()
|
||||
sys_interface_path = os.readlink('/sys/class/net/{0}'.format(interface))
|
||||
with salt.utils.files.fopen('/sys/class/net/{0}/uevent'.format(interface)) as uevent_file:
|
||||
uevent_lines = uevent_file.readlines()
|
||||
uevent_devtype = ""
|
||||
for line in uevent_lines:
|
||||
if line.startswith("DEVTYPE="):
|
||||
uevent_devtype = line.split('=')[1].strip()
|
||||
break
|
||||
|
||||
for adapter_mode in blacklist:
|
||||
if adapter_mode == '_':
|
||||
continue
|
||||
value = blacklist.get(adapter_mode, {})
|
||||
if value.get('additional_protocol') and adapter_mode not in protocols:
|
||||
continue
|
||||
|
||||
if interface not in value['name'] \
|
||||
and not any((blacklist['_'][iface_type] == 'sys' and iface_type in sys_interface_path) or
|
||||
(blacklist['_'][iface_type] == 'uevent' and iface_type == uevent_devtype)
|
||||
for iface_type in value['type']):
|
||||
adapter_modes += [adapter_mode]
|
||||
return adapter_modes
|
||||
|
||||
|
||||
def _get_static_info(interface):
|
||||
'''
|
||||
Return information about an interface from config file.
|
||||
|
||||
:param interface: interface label
|
||||
'''
|
||||
parser = configparser.ConfigParser()
|
||||
if os.path.exists(INTERFACES_CONFIG):
|
||||
try:
|
||||
with salt.utils.files.fopen(INTERFACES_CONFIG, 'r') as config_file:
|
||||
parser.read_file(config_file)
|
||||
except configparser.MissingSectionHeaderError:
|
||||
pass
|
||||
data = {
|
||||
'connectionid': interface.name,
|
||||
'label': interface.name,
|
||||
@ -309,12 +367,14 @@ def _get_static_info(interface):
|
||||
'wireless': False
|
||||
}
|
||||
hwaddr_section_number = ''.join(data['hwaddr'].split(':'))
|
||||
if parser.has_section('interface_{0}'.format(hwaddr_section_number)):
|
||||
ipv4_information = parser.get('interface_{0}'.format(hwaddr_section_number), 'IPv4').split('/')
|
||||
data['ipv4']['address'] = ipv4_information[0]
|
||||
data['ipv4']['dns'] = parser.get('interface_{0}'.format(hwaddr_section_number), 'Nameservers').split(',')
|
||||
data['ipv4']['netmask'] = ipv4_information[1]
|
||||
data['ipv4']['gateway'] = ipv4_information[2]
|
||||
if os.path.exists(INTERFACES_CONFIG):
|
||||
information = _load_config(hwaddr_section_number, ['IPv4', 'Nameservers'], filename=INTERFACES_CONFIG)
|
||||
if information['IPv4'] != '':
|
||||
ipv4_information = information['IPv4'].split('/')
|
||||
data['ipv4']['address'] = ipv4_information[0]
|
||||
data['ipv4']['dns'] = information['Nameservers'].split(',')
|
||||
data['ipv4']['netmask'] = ipv4_information[1]
|
||||
data['ipv4']['gateway'] = ipv4_information[2]
|
||||
return data
|
||||
|
||||
|
||||
@ -322,16 +382,46 @@ def _get_interface_info(interface):
|
||||
'''
|
||||
return details about given interface
|
||||
'''
|
||||
blacklist = {
|
||||
'tcpip': {
|
||||
'name': [],
|
||||
'type': [],
|
||||
'additional_protocol': False
|
||||
},
|
||||
'disabled': {
|
||||
'name': ['eth0'],
|
||||
'type': ['gadget'],
|
||||
'additional_protocol': False
|
||||
},
|
||||
'ethercat': {
|
||||
'name': ['eth0'],
|
||||
'type': ['gadget', 'usb', 'wlan'],
|
||||
'additional_protocol': True
|
||||
},
|
||||
'_': {
|
||||
'usb': 'sys',
|
||||
'gadget': 'uevent',
|
||||
'wlan': 'uevent'
|
||||
}
|
||||
}
|
||||
data = {
|
||||
'label': interface.name,
|
||||
'connectionid': interface.name,
|
||||
'supported_adapter_modes': _get_possible_adapter_modes(interface.name, blacklist),
|
||||
'adapter_mode': _get_adapter_mode_info(interface.name),
|
||||
'up': False,
|
||||
'ipv4': {
|
||||
'supportedrequestmodes': ['dhcp_linklocal', 'dhcp_only', 'linklocal_only', 'static'],
|
||||
'requestmode': _get_requestmode_info(interface.name)
|
||||
'requestmode': _get_request_mode_info(interface.name)
|
||||
},
|
||||
'hwaddr': interface.hwaddr[:-1]
|
||||
}
|
||||
needed_settings = []
|
||||
if data['ipv4']['requestmode'] == 'static':
|
||||
needed_settings += ['IP_Address', 'Subnet_Mask', 'Gateway', 'DNS_Address']
|
||||
if data['adapter_mode'] == 'ethercat':
|
||||
needed_settings += ['MasterID']
|
||||
settings = _load_config(interface.name, needed_settings)
|
||||
if interface.flags & IFF_RUNNING != 0:
|
||||
data['up'] = True
|
||||
data['ipv4']['address'] = interface.sockaddrToStr(interface.addr)
|
||||
@ -339,13 +429,10 @@ def _get_interface_info(interface):
|
||||
data['ipv4']['gateway'] = '0.0.0.0'
|
||||
data['ipv4']['dns'] = _get_dns_info()
|
||||
elif data['ipv4']['requestmode'] == 'static':
|
||||
with salt.utils.files.fopen(INI_FILE, 'r') as config_file:
|
||||
config_parser = configparser.RawConfigParser(dict_type=CaseInsensitiveDict)
|
||||
config_parser.read_file(config_file)
|
||||
data['ipv4']['address'] = _remove_quotes(config_parser.get(interface.name, 'IP_Address'))
|
||||
data['ipv4']['netmask'] = _remove_quotes(config_parser.get(interface.name, 'Subnet_Mask'))
|
||||
data['ipv4']['gateway'] = _remove_quotes(config_parser.get(interface.name, 'Gateway'))
|
||||
data['ipv4']['dns'] = [_remove_quotes(config_parser.get(interface.name, 'DNS_Address'))]
|
||||
data['ipv4']['address'] = settings['IP_Address']
|
||||
data['ipv4']['netmask'] = settings['Subnet_Mask']
|
||||
data['ipv4']['gateway'] = settings['Gateway']
|
||||
data['ipv4']['dns'] = [settings['DNS_Address']]
|
||||
|
||||
with salt.utils.files.fopen('/proc/net/route', 'r') as route_file:
|
||||
pattern = re.compile(r'^{interface}\t[0]{{8}}\t([0-9A-Z]{{8}})'.format(interface=interface.name), re.MULTILINE)
|
||||
@ -353,6 +440,10 @@ def _get_interface_info(interface):
|
||||
iface_gateway_hex = None if not match else match.group(1)
|
||||
if iface_gateway_hex is not None and len(iface_gateway_hex) == 8:
|
||||
data['ipv4']['gateway'] = '.'.join([str(int(iface_gateway_hex[i:i+2], 16)) for i in range(6, -1, -2)])
|
||||
if data['adapter_mode'] == 'ethercat':
|
||||
data['ethercat'] = {
|
||||
'masterid': settings['MasterID']
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
@ -404,10 +495,49 @@ def get_interfaces_details():
|
||||
return {'interfaces': list(map(_get_info, _interfaces))}
|
||||
|
||||
|
||||
def _change_state(interface, new_state):
|
||||
'''
|
||||
Enable or disable an interface
|
||||
|
||||
Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.
|
||||
|
||||
:param interface: interface label
|
||||
:param new_state: up or down
|
||||
:return: True if the service was enabled, otherwise an exception will be thrown.
|
||||
:rtype: bool
|
||||
'''
|
||||
if __grains__['lsb_distrib_id'] == 'nilrt':
|
||||
initial_mode = _get_adapter_mode_info(interface)
|
||||
_save_config(interface, 'Mode', 'TCPIP')
|
||||
if initial_mode == 'ethercat':
|
||||
__salt__['system.set_reboot_required_witnessed']()
|
||||
else:
|
||||
out = __salt__['cmd.run_all']('ip link set {0} {1}'.format(interface, new_state))
|
||||
if out['retcode'] != 0:
|
||||
msg = 'Couldn\'t {0} interface {1}. Error: {2}'.format('enable' if new_state == 'up' else 'disable',
|
||||
interface, out['stderr'])
|
||||
raise salt.exceptions.CommandExecutionError(msg)
|
||||
return True
|
||||
service = _interface_to_service(interface)
|
||||
if not service:
|
||||
raise salt.exceptions.CommandExecutionError('Invalid interface name: {0}'.format(interface))
|
||||
if not _connected(service):
|
||||
service = pyconnman.ConnService(os.path.join(SERVICE_PATH, service))
|
||||
try:
|
||||
state = service.connect() if new_state == 'up' else service.disconnect()
|
||||
return state is None
|
||||
except Exception:
|
||||
raise salt.exceptions.CommandExecutionError('Couldn\'t {0} service: {1}\n'
|
||||
.format('enable' if new_state == 'up' else 'disable', service))
|
||||
return True
|
||||
|
||||
|
||||
def up(interface, iface_type=None): # pylint: disable=invalid-name,unused-argument
|
||||
'''
|
||||
Enable the specified interface
|
||||
|
||||
Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.
|
||||
|
||||
:param str interface: interface label
|
||||
:return: True if the service was enabled, otherwise an exception will be thrown.
|
||||
:rtype: bool
|
||||
@ -418,29 +548,15 @@ def up(interface, iface_type=None): # pylint: disable=invalid-name,unused-argum
|
||||
|
||||
salt '*' ip.up interface-label
|
||||
'''
|
||||
if __grains__['lsb_distrib_id'] == 'nilrt':
|
||||
out = __salt__['cmd.run_all']('ip link set {0} up'.format(interface))
|
||||
if out['retcode'] != 0:
|
||||
msg = 'Couldn\'t enable interface {0}. Error: {1}'.format(interface, out['stderr'])
|
||||
raise salt.exceptions.CommandExecutionError(msg)
|
||||
return True
|
||||
service = _interface_to_service(interface)
|
||||
if not service:
|
||||
raise salt.exceptions.CommandExecutionError('Invalid interface name: {0}'.format(interface))
|
||||
if not _connected(service):
|
||||
service = pyconnman.ConnService(os.path.join(SERVICE_PATH, service))
|
||||
try:
|
||||
state = service.connect()
|
||||
return state is None
|
||||
except Exception:
|
||||
raise salt.exceptions.CommandExecutionError('Couldn\'t enable service: {0}\n'.format(service))
|
||||
return True
|
||||
return _change_state(interface, 'up')
|
||||
|
||||
|
||||
def enable(interface):
|
||||
'''
|
||||
Enable the specified interface
|
||||
|
||||
Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.
|
||||
|
||||
:param str interface: interface label
|
||||
:return: True if the service was enabled, otherwise an exception will be thrown.
|
||||
:rtype: bool
|
||||
@ -458,6 +574,8 @@ def down(interface, iface_type=None): # pylint: disable=unused-argument
|
||||
'''
|
||||
Disable the specified interface
|
||||
|
||||
Change adapter mode to Disabled. If previous adapter mode was EtherCAT, the target will need reboot.
|
||||
|
||||
:param str interface: interface label
|
||||
:return: True if the service was disabled, otherwise an exception will be thrown.
|
||||
:rtype: bool
|
||||
@ -468,29 +586,15 @@ def down(interface, iface_type=None): # pylint: disable=unused-argument
|
||||
|
||||
salt '*' ip.down interface-label
|
||||
'''
|
||||
if __grains__['lsb_distrib_id'] == 'nilrt':
|
||||
out = __salt__['cmd.run_all']('ip link set {0} down'.format(interface))
|
||||
if out['retcode'] != 0:
|
||||
msg = 'Couldn\'t disable interface {0}. Error: {1}'.format(interface, out['stderr'])
|
||||
raise salt.exceptions.CommandExecutionError(msg)
|
||||
return True
|
||||
service = _interface_to_service(interface)
|
||||
if not service:
|
||||
raise salt.exceptions.CommandExecutionError('Invalid interface name: {0}'.format(interface))
|
||||
if _connected(service):
|
||||
service = pyconnman.ConnService(os.path.join(SERVICE_PATH, service))
|
||||
try:
|
||||
state = service.disconnect()
|
||||
return state is None
|
||||
except Exception:
|
||||
raise salt.exceptions.CommandExecutionError('Couldn\'t disable service: {0}\n'.format(service))
|
||||
return True
|
||||
return _change_state(interface, 'down')
|
||||
|
||||
|
||||
def disable(interface):
|
||||
'''
|
||||
Disable the specified interface
|
||||
|
||||
Change adapter mode to Disabled. If previous adapter mode was EtherCAT, the target will need reboot.
|
||||
|
||||
:param str interface: interface label
|
||||
:return: True if the service was disabled, otherwise an exception will be thrown.
|
||||
:rtype: bool
|
||||
@ -504,7 +608,7 @@ def disable(interface):
|
||||
return down(interface)
|
||||
|
||||
|
||||
def _persist_config(section, token, value):
|
||||
def _save_config(section, token, value):
|
||||
'''
|
||||
Helper function to persist a configuration in the ini file
|
||||
'''
|
||||
@ -515,12 +619,47 @@ def _persist_config(section, token, value):
|
||||
raise salt.exceptions.CommandExecutionError(exc_msg)
|
||||
|
||||
|
||||
def set_ethercat(interface, master_id):
|
||||
'''
|
||||
Configure specified adapter to use EtherCAT adapter mode. If successful, the target will need reboot if it doesn't
|
||||
already use EtherCAT adapter mode, otherwise will return true.
|
||||
|
||||
:param interface: interface label
|
||||
:param master_id: EtherCAT Master ID
|
||||
:return: True if the settings were applied, otherwise an exception will be thrown.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' ip.set_ethercat interface-label master-id
|
||||
'''
|
||||
if __grains__['lsb_distrib_id'] == 'nilrt':
|
||||
initial_mode = _get_adapter_mode_info(interface)
|
||||
_save_config(interface, 'Mode', NIRTCFG_ETHERCAT)
|
||||
_save_config(interface, 'MasterID', master_id)
|
||||
if initial_mode != 'ethercat':
|
||||
__salt__['system.set_reboot_required_witnessed']()
|
||||
return True
|
||||
raise salt.exceptions.CommandExecutionError('EtherCAT is not supported')
|
||||
|
||||
|
||||
def _restart(interface):
|
||||
'''
|
||||
Disable and enable an interface
|
||||
'''
|
||||
disable(interface)
|
||||
enable(interface)
|
||||
|
||||
|
||||
def set_dhcp_linklocal_all(interface):
|
||||
'''
|
||||
Configure specified adapter to use DHCP with linklocal fallback
|
||||
|
||||
Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.
|
||||
|
||||
:param str interface: interface label
|
||||
:return: True if the settings ware applied, otherwise an exception will be thrown.
|
||||
:return: True if the settings were applied, otherwise an exception will be thrown.
|
||||
:rtype: bool
|
||||
|
||||
CLI Example:
|
||||
@ -530,10 +669,14 @@ def set_dhcp_linklocal_all(interface):
|
||||
salt '*' ip.set_dhcp_linklocal_all interface-label
|
||||
'''
|
||||
if __grains__['lsb_distrib_id'] == 'nilrt':
|
||||
_persist_config(interface, 'dhcpenabled', '1')
|
||||
_persist_config(interface, 'linklocalenabled', '1')
|
||||
disable(interface)
|
||||
enable(interface)
|
||||
initial_mode = _get_adapter_mode_info(interface)
|
||||
_save_config(interface, 'Mode', 'TCPIP')
|
||||
_save_config(interface, 'dhcpenabled', '1')
|
||||
_save_config(interface, 'linklocalenabled', '1')
|
||||
if initial_mode == 'ethercat':
|
||||
__salt__['system.set_reboot_required_witnessed']()
|
||||
else:
|
||||
_restart(interface)
|
||||
return True
|
||||
service = _interface_to_service(interface)
|
||||
if not service:
|
||||
@ -557,8 +700,10 @@ def set_dhcp_only_all(interface):
|
||||
'''
|
||||
Configure specified adapter to use DHCP only
|
||||
|
||||
Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.
|
||||
|
||||
:param str interface: interface label
|
||||
:return: True if the settings ware applied, otherwise an exception will be thrown.
|
||||
:return: True if the settings were applied, otherwise an exception will be thrown.
|
||||
:rtype: bool
|
||||
|
||||
CLI Example:
|
||||
@ -569,10 +714,14 @@ def set_dhcp_only_all(interface):
|
||||
'''
|
||||
if not __grains__['lsb_distrib_id'] == 'nilrt':
|
||||
raise salt.exceptions.CommandExecutionError('Not supported in this version')
|
||||
_persist_config(interface, 'dhcpenabled', '1')
|
||||
_persist_config(interface, 'linklocalenabled', '0')
|
||||
disable(interface)
|
||||
enable(interface)
|
||||
initial_mode = _get_adapter_mode_info(interface)
|
||||
_save_config(interface, 'Mode', 'TCPIP')
|
||||
_save_config(interface, 'dhcpenabled', '1')
|
||||
_save_config(interface, 'linklocalenabled', '0')
|
||||
if initial_mode == 'ethercat':
|
||||
__salt__['system.set_reboot_required_witnessed']()
|
||||
else:
|
||||
_restart(interface)
|
||||
return True
|
||||
|
||||
|
||||
@ -580,8 +729,10 @@ def set_linklocal_only_all(interface):
|
||||
'''
|
||||
Configure specified adapter to use linklocal only
|
||||
|
||||
Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.
|
||||
|
||||
:param str interface: interface label
|
||||
:return: True if the settings ware applied, otherwise an exception will be thrown.
|
||||
:return: True if the settings were applied, otherwise an exception will be thrown.
|
||||
:rtype: bool
|
||||
|
||||
CLI Example:
|
||||
@ -592,10 +743,14 @@ def set_linklocal_only_all(interface):
|
||||
'''
|
||||
if not __grains__['lsb_distrib_id'] == 'nilrt':
|
||||
raise salt.exceptions.CommandExecutionError('Not supported in this version')
|
||||
_persist_config(interface, 'dhcpenabled', '0')
|
||||
_persist_config(interface, 'linklocalenabled', '1')
|
||||
disable(interface)
|
||||
enable(interface)
|
||||
initial_mode = _get_adapter_mode_info(interface)
|
||||
_save_config(interface, 'Mode', 'TCPIP')
|
||||
_save_config(interface, 'dhcpenabled', '0')
|
||||
_save_config(interface, 'linklocalenabled', '1')
|
||||
if initial_mode == 'ethercat':
|
||||
__salt__['system.set_reboot_required_witnessed']()
|
||||
else:
|
||||
_restart(interface)
|
||||
return True
|
||||
|
||||
|
||||
@ -618,7 +773,7 @@ def _configure_static_interface(interface, **settings):
|
||||
if os.path.exists(INTERFACES_CONFIG):
|
||||
try:
|
||||
with salt.utils.files.fopen(INTERFACES_CONFIG, 'r') as config_file:
|
||||
parser.read_file(config_file)
|
||||
parser.readfp(config_file)
|
||||
except configparser.MissingSectionHeaderError:
|
||||
pass
|
||||
hwaddr = interface.hwaddr[:-1]
|
||||
@ -645,6 +800,8 @@ def set_static_all(interface, address, netmask, gateway, nameservers):
|
||||
'''
|
||||
Configure specified adapter to use ipv4 manual settings
|
||||
|
||||
Change adapter mode to TCP/IP. If previous adapter mode was EtherCAT, the target will need reboot.
|
||||
|
||||
:param str interface: interface label
|
||||
:param str address: ipv4 address
|
||||
:param str netmask: ipv4 netmask
|
||||
@ -668,15 +825,19 @@ def set_static_all(interface, address, netmask, gateway, nameservers):
|
||||
if not isinstance(nameservers, list):
|
||||
nameservers = nameservers.split(' ')
|
||||
if __grains__['lsb_distrib_id'] == 'nilrt':
|
||||
_persist_config(interface, 'dhcpenabled', '0')
|
||||
_persist_config(interface, 'linklocalenabled', '0')
|
||||
_persist_config(interface, 'IP_Address', address)
|
||||
_persist_config(interface, 'Subnet_Mask', netmask)
|
||||
_persist_config(interface, 'Gateway', gateway)
|
||||
initial_mode = _get_adapter_mode_info(interface)
|
||||
_save_config(interface, 'Mode', 'TCPIP')
|
||||
_save_config(interface, 'dhcpenabled', '0')
|
||||
_save_config(interface, 'linklocalenabled', '0')
|
||||
_save_config(interface, 'IP_Address', address)
|
||||
_save_config(interface, 'Subnet_Mask', netmask)
|
||||
_save_config(interface, 'Gateway', gateway)
|
||||
if nameservers:
|
||||
_persist_config(interface, 'DNS_Address', nameservers[0])
|
||||
disable(interface)
|
||||
enable(interface)
|
||||
_save_config(interface, 'DNS_Address', nameservers[0])
|
||||
if initial_mode == 'ethercat':
|
||||
__salt__['system.set_reboot_required_witnessed']()
|
||||
else:
|
||||
_restart(interface)
|
||||
return True
|
||||
service = _interface_to_service(interface)
|
||||
if not service:
|
||||
|
@ -1301,6 +1301,10 @@ def sls(mods, test=None, exclude=None, queue=False, sync_mods=None, **kwargs):
|
||||
high_ = serial.load(fp_)
|
||||
return st_.state.call_high(high_, orchestration_jid)
|
||||
|
||||
# If the state file is an integer, convert to a string then to unicode
|
||||
if isinstance(mods, six.integer_types):
|
||||
mods = salt.utils.stringutils.to_unicode(str(mods)) # future lint: disable=blacklisted-function
|
||||
|
||||
mods = salt.utils.args.split_input(mods)
|
||||
|
||||
st_.push_active()
|
||||
|
@ -664,6 +664,7 @@ def latest(name,
|
||||
identity = [identity]
|
||||
elif not isinstance(identity, list):
|
||||
return _fail(ret, 'identity must be either a list or a string')
|
||||
identity = [os.path.expanduser(x) for x in identity]
|
||||
for ident_path in identity:
|
||||
if 'salt://' in ident_path:
|
||||
try:
|
||||
@ -2315,6 +2316,7 @@ def detached(name,
|
||||
identity = [identity]
|
||||
elif not isinstance(identity, list):
|
||||
return _fail(ret, 'Identity must be either a list or a string')
|
||||
identity = [os.path.expanduser(x) for x in identity]
|
||||
for ident_path in identity:
|
||||
if 'salt://' in ident_path:
|
||||
try:
|
||||
@ -2701,7 +2703,7 @@ def cloned(name,
|
||||
https_pass=None,
|
||||
output_encoding=None):
|
||||
'''
|
||||
.. versionadded:: 2018.3.3, Fluorine
|
||||
.. versionadded:: 2018.3.3,Fluorine
|
||||
|
||||
Ensure that a repository has been cloned to the specified target directory.
|
||||
If not, clone that repository. No fetches will be performed once cloned.
|
||||
|
@ -1224,8 +1224,8 @@ def deploy_windows(host,
|
||||
|
||||
if not use_winrm and has_winexe() and not HAS_PSEXEC:
|
||||
salt.utils.versions.warn_until(
|
||||
'Fluorine',
|
||||
'Support for winexe has been depricated and will be remove in '
|
||||
'Sodium',
|
||||
'Support for winexe has been deprecated and will be removed in '
|
||||
'Sodium, please install pypsexec instead.'
|
||||
)
|
||||
|
||||
@ -2848,22 +2848,6 @@ def list_cache_nodes_full(opts=None, provider=None, base=None):
|
||||
return minions
|
||||
|
||||
|
||||
def cache_nodes_ip(opts, base=None):
|
||||
'''
|
||||
Retrieve a list of all nodes from Salt Cloud cache, and any associated IP
|
||||
addresses. Returns a dict.
|
||||
'''
|
||||
salt.utils.versions.warn_until(
|
||||
'Fluorine',
|
||||
'This function is incomplete and non-functional '
|
||||
'and will be removed in Salt Fluorine.'
|
||||
)
|
||||
if base is None:
|
||||
base = opts['cachedir']
|
||||
|
||||
minions = list_cache_nodes_full(opts, base=base)
|
||||
|
||||
|
||||
def update_bootstrap(config, url=None):
|
||||
'''
|
||||
Update the salt-bootstrap script
|
||||
|
@ -488,7 +488,8 @@ def query(url,
|
||||
data = _urlencode(data)
|
||||
|
||||
if verify_ssl:
|
||||
req_kwargs['ca_certs'] = ca_bundle
|
||||
# tornado requires a str, cannot be unicode str in py2
|
||||
req_kwargs['ca_certs'] = salt.utils.stringutils.to_str(ca_bundle)
|
||||
|
||||
max_body = opts.get('http_max_body', salt.config.DEFAULT_MINION_OPTS['http_max_body'])
|
||||
connect_timeout = opts.get('http_connect_timeout', salt.config.DEFAULT_MINION_OPTS['http_connect_timeout'])
|
||||
@ -497,9 +498,18 @@ def query(url,
|
||||
client_argspec = None
|
||||
|
||||
proxy_host = opts.get('proxy_host', None)
|
||||
if proxy_host:
|
||||
# tornado requires a str for proxy_host, cannot be a unicode str in py2
|
||||
proxy_host = salt.utils.stringutils.to_str(proxy_host)
|
||||
proxy_port = opts.get('proxy_port', None)
|
||||
proxy_username = opts.get('proxy_username', None)
|
||||
if proxy_username:
|
||||
# tornado requires a str, cannot be unicode str in py2
|
||||
proxy_username = salt.utils.stringutils.to_str(proxy_username)
|
||||
proxy_password = opts.get('proxy_password', None)
|
||||
if proxy_password:
|
||||
# tornado requires a str, cannot be unicode str in py2
|
||||
proxy_password = salt.utils.stringutils.to_str(proxy_password)
|
||||
no_proxy = opts.get('no_proxy', [])
|
||||
|
||||
# Since tornado doesnt support no_proxy, we'll always hand it empty proxies or valid ones
|
||||
|
@ -188,7 +188,7 @@ def set_pidfile(pidfile, user):
|
||||
uid = pwnam[2]
|
||||
gid = pwnam[3]
|
||||
#groups = [g.gr_gid for g in grp.getgrall() if user in g.gr_mem]
|
||||
except IndexError:
|
||||
except (KeyError, IndexError):
|
||||
sys.stderr.write(
|
||||
'Failed to set the pid to user: {0}. The user is not '
|
||||
'available.\n'.format(
|
||||
|
@ -179,7 +179,7 @@ def get_conn(host='', username=None, password=None, port=445):
|
||||
'''
|
||||
if HAS_IMPACKET and not HAS_SMBPROTOCOL:
|
||||
salt.utils.versions.warn_until(
|
||||
'Fluorine',
|
||||
'Sodium',
|
||||
'Support of impacket has been depricated and will be '
|
||||
'removed in Sodium. Please install smbprotocol instead.'
|
||||
)
|
||||
|
2
tests/integration/files/file/base/12345.sls
Normal file
2
tests/integration/files/file/base/12345.sls
Normal file
@ -0,0 +1,2 @@
|
||||
always-passes:
|
||||
test.succeed_without_changes
|
@ -2016,3 +2016,19 @@ class StateModuleTest(ModuleCase, SaltReturnAssertsMixin):
|
||||
state_file = os.path.join(TMP, 'test.txt')
|
||||
if os.path.isfile(state_file):
|
||||
os.remove(state_file)
|
||||
|
||||
def test_state_sls_integer_name(self):
|
||||
'''
|
||||
This tests the case where the state file is named
|
||||
only with integers
|
||||
'''
|
||||
state_run = self.run_function(
|
||||
'state.sls',
|
||||
mods='12345'
|
||||
)
|
||||
|
||||
state_id = 'test_|-always-passes_|-always-passes_|-succeed_without_changes'
|
||||
self.assertIn(state_id, state_run)
|
||||
self.assertEqual(state_run[state_id]['comment'],
|
||||
'Success!')
|
||||
self.assertTrue(state_run[state_id]['result'])
|
||||
|
@ -22,9 +22,11 @@ import tests.integration.utils
|
||||
from tests.support.case import ShellCase
|
||||
from tests.support.paths import TMP
|
||||
from tests.support.mixins import ShellCaseCommonTestsMixin
|
||||
from tests.support.unit import skipIf
|
||||
from tests.integration.utils import testprogram
|
||||
|
||||
|
||||
@skipIf(True, 'This test file should be in an isolated test space.')
|
||||
class MasterTest(ShellCase, testprogram.TestProgramCase, ShellCaseCommonTestsMixin):
|
||||
|
||||
_call_binary_ = 'salt-master'
|
||||
|
@ -710,8 +710,8 @@ class FileTest(ModuleCase, SaltReturnAssertsMixin):
|
||||
This is a regression test for Issue #38914 and Issue #48230 (test=true use).
|
||||
'''
|
||||
name = os.path.join(TMP, 'source_hash_indifferent_case')
|
||||
state_name = 'file_|-/tmp/salt-tests-tmpdir/source_hash_indifferent_case_|' \
|
||||
'-/tmp/salt-tests-tmpdir/source_hash_indifferent_case_|-managed'
|
||||
state_name = 'file_|-{0}_|' \
|
||||
'-{0}_|-managed'.format(name)
|
||||
local_path = os.path.join(FILES, 'file', 'base', 'hello_world.txt')
|
||||
actual_hash = 'c98c24b677eff44860afea6f493bbaec5bb1c4cbb209c6fc2bbb47f66ff2ad31'
|
||||
uppercase_hash = actual_hash.upper()
|
||||
|
@ -42,9 +42,19 @@ class ServiceTest(ModuleCase, SaltReturnAssertsMixin):
|
||||
self.stopped = ''
|
||||
self.running = '[0-9]'
|
||||
|
||||
self.pre_srv_enabled = True if self.service_name in self.run_function('service.get_enabled') else False
|
||||
self.post_srv_disable = False
|
||||
if not self.pre_srv_enabled:
|
||||
self.run_function('service.enable', name=self.service_name)
|
||||
self.post_srv_disable = True
|
||||
|
||||
if salt.utils.path.which(cmd_name) is None:
|
||||
self.skipTest('{0} is not installed'.format(cmd_name))
|
||||
|
||||
def tearDown(self):
|
||||
if self.post_srv_disable:
|
||||
self.run_function('service.disable', name=self.service_name)
|
||||
|
||||
def check_service_status(self, exp_return):
|
||||
'''
|
||||
helper method to check status of service
|
||||
|
189
tests/unit/beacons/test_watchdog_beacon.py
Normal file
189
tests/unit/beacons/test_watchdog_beacon.py
Normal file
@ -0,0 +1,189 @@
|
||||
# coding: utf-8
|
||||
|
||||
# Python libs
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
# Salt libs
|
||||
import salt.utils.files
|
||||
from salt.beacons import watchdog
|
||||
from salt.ext.six.moves import range
|
||||
|
||||
# Salt testing libs
|
||||
from tests.support.unit import skipIf, TestCase
|
||||
from tests.support.mixins import LoaderModuleMockMixin
|
||||
|
||||
|
||||
def check_events(config):
|
||||
total_delay = 1
|
||||
delay_per_loop = 20e-3
|
||||
|
||||
for _ in range(int(total_delay / delay_per_loop)):
|
||||
events = watchdog.beacon(config)
|
||||
|
||||
if events:
|
||||
return events
|
||||
|
||||
time.sleep(delay_per_loop)
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def create(path, content=None):
|
||||
with salt.utils.files.fopen(path, 'w') as f:
|
||||
if content:
|
||||
f.write(content)
|
||||
os.fsync(f)
|
||||
|
||||
|
||||
@skipIf(not watchdog.HAS_WATCHDOG, 'watchdog is not available')
|
||||
class IWatchdogBeaconTestCase(TestCase, LoaderModuleMockMixin):
|
||||
'''
|
||||
Test case for salt.beacons.watchdog
|
||||
'''
|
||||
|
||||
def setup_loader_modules(self):
|
||||
return {watchdog: {}}
|
||||
|
||||
def setUp(self):
|
||||
self.tmpdir = tempfile.mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
watchdog.close({})
|
||||
shutil.rmtree(self.tmpdir, ignore_errors=True)
|
||||
|
||||
def assertValid(self, config):
|
||||
ret = watchdog.validate(config)
|
||||
self.assertEqual(ret, (True, 'Valid beacon configuration'))
|
||||
|
||||
def test_empty_config(self):
|
||||
config = [{}]
|
||||
ret = watchdog.beacon(config)
|
||||
self.assertEqual(ret, [])
|
||||
|
||||
def test_file_create(self):
|
||||
path = os.path.join(self.tmpdir, 'tmpfile')
|
||||
|
||||
config = [{'directories': {self.tmpdir: {'mask': ['create']}}}]
|
||||
self.assertValid(config)
|
||||
self.assertEqual(watchdog.beacon(config), [])
|
||||
|
||||
create(path)
|
||||
|
||||
ret = check_events(config)
|
||||
self.assertEqual(len(ret), 1)
|
||||
self.assertEqual(ret[0]['path'], path)
|
||||
self.assertEqual(ret[0]['change'], 'created')
|
||||
|
||||
def test_file_modified(self):
|
||||
path = os.path.join(self.tmpdir, 'tmpfile')
|
||||
|
||||
config = [{'directories': {self.tmpdir: {'mask': ['modify']}}}]
|
||||
self.assertValid(config)
|
||||
self.assertEqual(watchdog.beacon(config), [])
|
||||
|
||||
create(path, 'some content')
|
||||
|
||||
ret = check_events(config)
|
||||
self.assertEqual(len(ret), 2)
|
||||
self.assertEqual(ret[0]['path'], os.path.dirname(path))
|
||||
self.assertEqual(ret[0]['change'], 'modified')
|
||||
self.assertEqual(ret[1]['path'], path)
|
||||
self.assertEqual(ret[1]['change'], 'modified')
|
||||
|
||||
def test_file_deleted(self):
|
||||
path = os.path.join(self.tmpdir, 'tmpfile')
|
||||
create(path)
|
||||
|
||||
config = [{'directories': {self.tmpdir: {'mask': ['delete']}}}]
|
||||
self.assertValid(config)
|
||||
self.assertEqual(watchdog.beacon(config), [])
|
||||
|
||||
os.remove(path)
|
||||
|
||||
ret = check_events(config)
|
||||
self.assertEqual(len(ret), 1)
|
||||
self.assertEqual(ret[0]['path'], path)
|
||||
self.assertEqual(ret[0]['change'], 'deleted')
|
||||
|
||||
def test_file_moved(self):
|
||||
path = os.path.join(self.tmpdir, 'tmpfile')
|
||||
create(path)
|
||||
|
||||
config = [{'directories': {self.tmpdir: {'mask': ['move']}}}]
|
||||
self.assertValid(config)
|
||||
self.assertEqual(watchdog.beacon(config), [])
|
||||
|
||||
os.rename(path, path + '_moved')
|
||||
|
||||
ret = check_events(config)
|
||||
self.assertEqual(len(ret), 1)
|
||||
self.assertEqual(ret[0]['path'], path)
|
||||
self.assertEqual(ret[0]['change'], 'moved')
|
||||
|
||||
def test_file_create_in_directory(self):
|
||||
config = [{'directories': {self.tmpdir: {'mask': ['create', 'modify']}}}]
|
||||
self.assertValid(config)
|
||||
self.assertEqual(watchdog.beacon(config), [])
|
||||
|
||||
path = os.path.join(self.tmpdir, 'tmpfile')
|
||||
create(path)
|
||||
|
||||
ret = check_events(config)
|
||||
self.assertEqual(len(ret), 2)
|
||||
self.assertEqual(ret[0]['path'], path)
|
||||
self.assertEqual(ret[0]['change'], 'created')
|
||||
self.assertEqual(ret[1]['path'], self.tmpdir)
|
||||
self.assertEqual(ret[1]['change'], 'modified')
|
||||
|
||||
def test_trigger_all_possible_events(self):
|
||||
path = os.path.join(self.tmpdir, 'tmpfile')
|
||||
moved = path + '_moved'
|
||||
|
||||
config = [{'directories': {
|
||||
self.tmpdir: {},
|
||||
}}]
|
||||
self.assertValid(config)
|
||||
self.assertEqual(watchdog.beacon(config), [])
|
||||
|
||||
# create
|
||||
create(path)
|
||||
# modify
|
||||
create(path, 'modified content')
|
||||
# move
|
||||
os.rename(path, moved)
|
||||
# delete
|
||||
os.remove(moved)
|
||||
|
||||
ret = check_events(config)
|
||||
|
||||
self.assertEqual(len(ret), 8)
|
||||
|
||||
# create
|
||||
self.assertEqual(ret[0]['path'], path)
|
||||
self.assertEqual(ret[0]['change'], 'created')
|
||||
self.assertEqual(ret[1]['path'], self.tmpdir)
|
||||
self.assertEqual(ret[1]['change'], 'modified')
|
||||
|
||||
# modify
|
||||
self.assertEqual(ret[2]['path'], path)
|
||||
self.assertEqual(ret[2]['change'], 'modified')
|
||||
self.assertEqual(ret[3]['path'], path)
|
||||
self.assertEqual(ret[3]['change'], 'modified')
|
||||
|
||||
# move
|
||||
self.assertEqual(ret[4]['path'], path)
|
||||
self.assertEqual(ret[4]['change'], 'moved')
|
||||
self.assertEqual(ret[5]['path'], self.tmpdir)
|
||||
self.assertEqual(ret[5]['change'], 'modified')
|
||||
|
||||
# delete
|
||||
self.assertEqual(ret[6]['path'], moved)
|
||||
self.assertEqual(ret[6]['change'], 'deleted')
|
||||
self.assertEqual(ret[7]['path'], self.tmpdir)
|
||||
self.assertEqual(ret[7]['change'], 'modified')
|
@ -1098,7 +1098,7 @@ class FilemodLineTests(TestCase, LoaderModuleMockMixin):
|
||||
else:
|
||||
return salt.utils.data.decode_list(ret, to_str=True)
|
||||
|
||||
@patch('os.path.realpath', MagicMock())
|
||||
@patch('os.path.realpath', MagicMock(wraps=lambda x: x))
|
||||
@patch('os.path.isfile', MagicMock(return_value=True))
|
||||
def test_delete_line_in_empty_file(self):
|
||||
'''
|
||||
@ -1140,7 +1140,7 @@ class FilemodLineTests(TestCase, LoaderModuleMockMixin):
|
||||
with patch('salt.utils.atomicfile.atomic_open', atomic_opener):
|
||||
self.assertFalse(filemod.line('foo', content='foo', match=match, mode=mode))
|
||||
|
||||
@patch('os.path.realpath', MagicMock())
|
||||
@patch('os.path.realpath', MagicMock(wraps=lambda x: x))
|
||||
@patch('os.path.isfile', MagicMock(return_value=True))
|
||||
def test_line_modecheck_failure(self):
|
||||
'''
|
||||
@ -1153,7 +1153,7 @@ class FilemodLineTests(TestCase, LoaderModuleMockMixin):
|
||||
filemod.line('foo', mode=mode)
|
||||
self.assertIn(err_msg, six.text_type(cmd_err))
|
||||
|
||||
@patch('os.path.realpath', MagicMock())
|
||||
@patch('os.path.realpath', MagicMock(wraps=lambda x: x))
|
||||
@patch('os.path.isfile', MagicMock(return_value=True))
|
||||
def test_line_no_content(self):
|
||||
'''
|
||||
@ -1166,7 +1166,7 @@ class FilemodLineTests(TestCase, LoaderModuleMockMixin):
|
||||
self.assertIn('Content can only be empty if mode is "delete"',
|
||||
six.text_type(cmd_err))
|
||||
|
||||
@patch('os.path.realpath', MagicMock())
|
||||
@patch('os.path.realpath', MagicMock(wraps=lambda x: x))
|
||||
@patch('os.path.isfile', MagicMock(return_value=True))
|
||||
@patch('os.stat', MagicMock())
|
||||
def test_line_insert_no_location_no_before_no_after(self):
|
||||
@ -1297,10 +1297,13 @@ class FilemodLineTests(TestCase, LoaderModuleMockMixin):
|
||||
See issue #48113
|
||||
:return:
|
||||
'''
|
||||
file_content = 'This is a line\nThis is another line'
|
||||
file_modified = salt.utils.stringutils.to_str('This is a line\n'
|
||||
'This is another line\n'
|
||||
'This is a line with unicode Ŷ')
|
||||
file_content = 'This is a line{}This is another line'.format(os.linesep)
|
||||
file_modified = salt.utils.stringutils.to_str('This is a line{}'
|
||||
'This is another line{}'
|
||||
'This is a line with unicode Ŷ'.format(
|
||||
os.linesep, os.linesep
|
||||
)
|
||||
)
|
||||
cfg_content = "This is a line with unicode Ŷ"
|
||||
isfile_mock = MagicMock(side_effect=lambda x: True if x == name else DEFAULT)
|
||||
for after_line in ['This is another line']:
|
||||
@ -1367,7 +1370,7 @@ class FilemodLineTests(TestCase, LoaderModuleMockMixin):
|
||||
expected = self._get_body(file_modified)
|
||||
assert writelines_content[0] == expected, (writelines_content[0], expected)
|
||||
|
||||
@patch('os.path.realpath', MagicMock())
|
||||
@patch('os.path.realpath', MagicMock(wraps=lambda x: x))
|
||||
@patch('os.path.isfile', MagicMock(return_value=True))
|
||||
@patch('os.stat', MagicMock())
|
||||
def test_line_assert_exception_pattern(self):
|
||||
@ -1701,7 +1704,7 @@ class FilemodLineTests(TestCase, LoaderModuleMockMixin):
|
||||
# No changes should have been made
|
||||
assert result is False
|
||||
|
||||
@patch('os.path.realpath', MagicMock())
|
||||
@patch('os.path.realpath', MagicMock(wraps=lambda x: x))
|
||||
@patch('os.path.isfile', MagicMock(return_value=True))
|
||||
@patch('os.stat', MagicMock())
|
||||
def test_line_insert_ensure_beforeafter_rangelines(self):
|
||||
@ -1711,8 +1714,10 @@ class FilemodLineTests(TestCase, LoaderModuleMockMixin):
|
||||
'''
|
||||
cfg_content = 'EXTRA_GROUPS="dialout cdrom floppy audio video plugdev users"'
|
||||
# pylint: disable=W1401
|
||||
file_content = 'NAME_REGEX="^[a-z][-a-z0-9_]*\$"\nSETGID_HOME=no\nADD_EXTRA_GROUPS=1\n' \
|
||||
'SKEL_IGNORE_REGEX="dpkg-(old|new|dist|save)"'
|
||||
file_content = 'NAME_REGEX="^[a-z][-a-z0-9_]*\$"{}SETGID_HOME=no{}ADD_EXTRA_GROUPS=1{}' \
|
||||
'SKEL_IGNORE_REGEX="dpkg-(old|new|dist|save)"'.format(
|
||||
os.linesep, os.linesep, os.linesep
|
||||
)
|
||||
# pylint: enable=W1401
|
||||
after, before = file_content.split(os.linesep)[0], file_content.split(os.linesep)[-1]
|
||||
for (_after, _before) in [(after, before), ('NAME_.*', 'SKEL_.*')]:
|
||||
|
@ -241,6 +241,18 @@ class WinServiceTestCase(TestCase, LoaderModuleMockMixin):
|
||||
self.assertTrue(win_service.enabled('spongebob'))
|
||||
self.assertFalse(win_service.enabled('squarepants'))
|
||||
|
||||
def test_enabled_with_space_in_name(self):
|
||||
'''
|
||||
Test to check to see if the named
|
||||
service is enabled to start on boot
|
||||
when have space in service name
|
||||
'''
|
||||
mock = MagicMock(side_effect=[{'StartType': 'Auto'},
|
||||
{'StartType': 'Disabled'}])
|
||||
with patch.object(win_service, 'info', mock):
|
||||
self.assertTrue(win_service.enabled('spongebob test'))
|
||||
self.assertFalse(win_service.enabled('squarepants test'))
|
||||
|
||||
def test_disabled(self):
|
||||
'''
|
||||
Test to check to see if the named
|
||||
|
Loading…
Reference in New Issue
Block a user