File: //proc/self/root/usr/lib/python2.7/site-packages/salt/modules/iosconfig.py
# -*- coding: utf-8 -*-
'''
Cisco IOS configuration manipulation helpers
.. versionadded:: 2019.2.0
This module provides a collection of helper functions for Cisco IOS style
configuration manipulation. This module does not have external dependencies
and can be used from any Proxy or regular Minion.
'''
# Import Python Libs
from __future__ import absolute_import, unicode_literals, print_function
# Import python stdlib
import difflib
# Import Salt modules
from salt.ext import six
import salt.utils.dictupdate
import salt.utils.dictdiffer
from salt.utils.odict import OrderedDict
from salt.exceptions import SaltException
# ------------------------------------------------------------------------------
# module properties
# ------------------------------------------------------------------------------
__virtualname__ = 'iosconfig'
__proxyenabled__ = ['*']
# ------------------------------------------------------------------------------
# helper functions -- will not be exported
# ------------------------------------------------------------------------------
def _attach_data_to_path(obj, ele, data):
if ele not in obj:
obj[ele] = OrderedDict()
obj[ele] = data
else:
obj[ele].update(data)
def _attach_data_to_path_tags(obj, path, data, list_=False):
if "#list" not in obj:
obj["#list"] = []
path = [path]
obj_tmp = obj
first = True
while True:
obj_tmp["#text"] = " ".join(path)
path_item = path.pop(0)
if not path:
break
else:
if path_item not in obj_tmp:
obj_tmp[path_item] = OrderedDict()
obj_tmp = obj_tmp[path_item]
if first and list_:
obj["#list"].append({path_item: obj_tmp})
first = False
if path_item in obj_tmp:
obj_tmp[path_item].update(data)
else:
obj_tmp[path_item] = data
obj_tmp[path_item]["#standalone"] = True
def _parse_text_config(config_lines,
with_tags=False,
current_indent=0,
nested=False):
struct_cfg = OrderedDict()
while config_lines:
line = config_lines.pop(0)
if not line.strip() or line.lstrip().startswith('!'):
# empty or comment
continue
current_line = line.lstrip()
leading_spaces = len(line) - len(current_line)
if leading_spaces > current_indent:
current_block = _parse_text_config(config_lines,
current_indent=leading_spaces,
with_tags=with_tags,
nested=True)
if with_tags:
_attach_data_to_path_tags(struct_cfg,
current_line,
current_block,
nested)
else:
_attach_data_to_path(struct_cfg, current_line, current_block)
elif leading_spaces < current_indent:
config_lines.insert(0, line)
break
else:
if not nested:
current_block = _parse_text_config(config_lines,
current_indent=leading_spaces,
with_tags=with_tags,
nested=True)
if with_tags:
_attach_data_to_path_tags(struct_cfg,
current_line,
current_block,
nested)
else:
_attach_data_to_path(struct_cfg, current_line, current_block)
else:
config_lines.insert(0, line)
break
return struct_cfg
def _get_diff_text(old, new):
'''
Returns the diff of two text blobs.
'''
diff = difflib.unified_diff(old.splitlines(1),
new.splitlines(1))
return ''.join([x.replace('\r', '') for x in diff])
def _print_config_text(tree, indentation=0):
'''
Return the config as text from a config tree.
'''
config = ''
for key, value in six.iteritems(tree):
config += '{indent}{line}\n'.format(indent=' '*indentation, line=key)
if value:
config += _print_config_text(value, indentation=indentation+1)
return config
# ------------------------------------------------------------------------------
# callable functions
# ------------------------------------------------------------------------------
def tree(config=None,
path=None,
with_tags=False,
saltenv='base'):
'''
Transform Cisco IOS style configuration to structured Python dictionary.
Depending on the value of the ``with_tags`` argument, this function may
provide different views, valuable in different situations.
config
The configuration sent as text. This argument is ignored when ``path``
is configured.
path
Absolute or remote path from where to load the configuration text. This
argument allows any URI supported by
:py:func:`cp.get_url <salt.modules.cp.get_url>`), e.g., ``salt://``,
``https://``, ``s3://``, ``ftp:/``, etc.
with_tags: ``False``
Whether this function should return a detailed view, with tags.
saltenv: ``base``
Salt fileserver environment from which to retrieve the file.
Ignored if ``path`` is not a ``salt://`` URL.
CLI Example:
.. code-block:: bash
salt '*' iosconfig.tree path=salt://path/to/my/config.txt
salt '*' iosconfig.tree path=https://bit.ly/2mAdq7z
'''
if path:
config = __salt__['cp.get_file_str'](path, saltenv=saltenv)
if config is False:
raise SaltException('{} is not available'.format(path))
config_lines = config.splitlines()
return _parse_text_config(config_lines, with_tags=with_tags)
def clean(config=None, path=None, saltenv='base'):
'''
Return a clean version of the config, without any special signs (such as
``!`` as an individual line) or empty lines, but just lines with significant
value in the configuration of the network device.
config
The configuration sent as text. This argument is ignored when ``path``
is configured.
path
Absolute or remote path from where to load the configuration text. This
argument allows any URI supported by
:py:func:`cp.get_url <salt.modules.cp.get_url>`), e.g., ``salt://``,
``https://``, ``s3://``, ``ftp:/``, etc.
saltenv: ``base``
Salt fileserver environment from which to retrieve the file.
Ignored if ``path`` is not a ``salt://`` URL.
CLI Example:
.. code-block:: bash
salt '*' iosconfig.clean path=salt://path/to/my/config.txt
salt '*' iosconfig.clean path=https://bit.ly/2mAdq7z
'''
config_tree = tree(config=config, path=path, saltenv=saltenv)
return _print_config_text(config_tree)
def merge_tree(initial_config=None,
initial_path=None,
merge_config=None,
merge_path=None,
saltenv='base'):
'''
Return the merge tree of the ``initial_config`` with the ``merge_config``,
as a Python dictionary.
initial_config
The initial configuration sent as text. This argument is ignored when
``initial_path`` is set.
initial_path
Absolute or remote path from where to load the initial configuration
text. This argument allows any URI supported by
:py:func:`cp.get_url <salt.modules.cp.get_url>`), e.g., ``salt://``,
``https://``, ``s3://``, ``ftp:/``, etc.
merge_config
The config to be merged into the initial config, sent as text. This
argument is ignored when ``merge_path`` is set.
merge_path
Absolute or remote path from where to load the merge configuration
text. This argument allows any URI supported by
:py:func:`cp.get_url <salt.modules.cp.get_url>`), e.g., ``salt://``,
``https://``, ``s3://``, ``ftp:/``, etc.
saltenv: ``base``
Salt fileserver environment from which to retrieve the file.
Ignored if ``initial_path`` or ``merge_path`` is not a ``salt://`` URL.
CLI Example:
.. code-block:: bash
salt '*' iosconfig.merge_tree initial_path=salt://path/to/running.cfg merge_path=salt://path/to/merge.cfg
'''
merge_tree = tree(config=merge_config,
path=merge_path,
saltenv=saltenv)
initial_tree = tree(config=initial_config,
path=initial_path,
saltenv=saltenv)
return salt.utils.dictupdate.merge(initial_tree, merge_tree)
def merge_text(initial_config=None,
initial_path=None,
merge_config=None,
merge_path=None,
saltenv='base'):
'''
Return the merge result of the ``initial_config`` with the ``merge_config``,
as plain text.
initial_config
The initial configuration sent as text. This argument is ignored when
``initial_path`` is set.
initial_path
Absolute or remote path from where to load the initial configuration
text. This argument allows any URI supported by
:py:func:`cp.get_url <salt.modules.cp.get_url>`), e.g., ``salt://``,
``https://``, ``s3://``, ``ftp:/``, etc.
merge_config
The config to be merged into the initial config, sent as text. This
argument is ignored when ``merge_path`` is set.
merge_path
Absolute or remote path from where to load the merge configuration
text. This argument allows any URI supported by
:py:func:`cp.get_url <salt.modules.cp.get_url>`), e.g., ``salt://``,
``https://``, ``s3://``, ``ftp:/``, etc.
saltenv: ``base``
Salt fileserver environment from which to retrieve the file.
Ignored if ``initial_path`` or ``merge_path`` is not a ``salt://`` URL.
CLI Example:
.. code-block:: bash
salt '*' iosconfig.merge_text initial_path=salt://path/to/running.cfg merge_path=salt://path/to/merge.cfg
'''
candidate_tree = merge_tree(initial_config=initial_config,
initial_path=initial_path,
merge_config=merge_config,
merge_path=merge_path,
saltenv=saltenv)
return _print_config_text(candidate_tree)
def merge_diff(initial_config=None,
initial_path=None,
merge_config=None,
merge_path=None,
saltenv='base'):
'''
Return the merge diff, as text, after merging the merge config into the
initial config.
initial_config
The initial configuration sent as text. This argument is ignored when
``initial_path`` is set.
initial_path
Absolute or remote path from where to load the initial configuration
text. This argument allows any URI supported by
:py:func:`cp.get_url <salt.modules.cp.get_url>`), e.g., ``salt://``,
``https://``, ``s3://``, ``ftp:/``, etc.
merge_config
The config to be merged into the initial config, sent as text. This
argument is ignored when ``merge_path`` is set.
merge_path
Absolute or remote path from where to load the merge configuration
text. This argument allows any URI supported by
:py:func:`cp.get_url <salt.modules.cp.get_url>`), e.g., ``salt://``,
``https://``, ``s3://``, ``ftp:/``, etc.
saltenv: ``base``
Salt fileserver environment from which to retrieve the file.
Ignored if ``initial_path`` or ``merge_path`` is not a ``salt://`` URL.
CLI Example:
.. code-block:: bash
salt '*' iosconfig.merge_diff initial_path=salt://path/to/running.cfg merge_path=salt://path/to/merge.cfg
'''
if initial_path:
initial_config = __salt__['cp.get_file_str'](initial_path, saltenv=saltenv)
candidate_config = merge_text(initial_config=initial_config,
merge_config=merge_config,
merge_path=merge_path,
saltenv=saltenv)
clean_running_dict = tree(config=initial_config)
clean_running = _print_config_text(clean_running_dict)
return _get_diff_text(clean_running, candidate_config)
def diff_tree(candidate_config=None,
candidate_path=None,
running_config=None,
running_path=None,
saltenv='base'):
'''
Return the diff, as Python dictionary, between the candidate and the running
configuration.
candidate_config
The candidate configuration sent as text. This argument is ignored when
``candidate_path`` is set.
candidate_path
Absolute or remote path from where to load the candidate configuration
text. This argument allows any URI supported by
:py:func:`cp.get_url <salt.modules.cp.get_url>`), e.g., ``salt://``,
``https://``, ``s3://``, ``ftp:/``, etc.
running_config
The running configuration sent as text. This argument is ignored when
``running_path`` is set.
running_path
Absolute or remote path from where to load the runing configuration
text. This argument allows any URI supported by
:py:func:`cp.get_url <salt.modules.cp.get_url>`), e.g., ``salt://``,
``https://``, ``s3://``, ``ftp:/``, etc.
saltenv: ``base``
Salt fileserver environment from which to retrieve the file.
Ignored if ``candidate_path`` or ``running_path`` is not a
``salt://`` URL.
CLI Example:
.. code-block:: bash
salt '*' iosconfig.diff_tree candidate_path=salt://path/to/candidate.cfg running_path=salt://path/to/running.cfg
'''
candidate_tree = tree(config=candidate_config,
path=candidate_path,
saltenv=saltenv)
running_tree = tree(config=running_config,
path=running_path,
saltenv=saltenv)
return salt.utils.dictdiffer.deep_diff(running_tree, candidate_tree)
def diff_text(candidate_config=None,
candidate_path=None,
running_config=None,
running_path=None,
saltenv='base'):
'''
Return the diff, as text, between the candidate and the running config.
candidate_config
The candidate configuration sent as text. This argument is ignored when
``candidate_path`` is set.
candidate_path
Absolute or remote path from where to load the candidate configuration
text. This argument allows any URI supported by
:py:func:`cp.get_url <salt.modules.cp.get_url>`), e.g., ``salt://``,
``https://``, ``s3://``, ``ftp:/``, etc.
running_config
The running configuration sent as text. This argument is ignored when
``running_path`` is set.
running_path
Absolute or remote path from where to load the runing configuration
text. This argument allows any URI supported by
:py:func:`cp.get_url <salt.modules.cp.get_url>`), e.g., ``salt://``,
``https://``, ``s3://``, ``ftp:/``, etc.
saltenv: ``base``
Salt fileserver environment from which to retrieve the file.
Ignored if ``candidate_path`` or ``running_path`` is not a
``salt://`` URL.
CLI Example:
.. code-block:: bash
salt '*' iosconfig.diff_text candidate_path=salt://path/to/candidate.cfg running_path=salt://path/to/running.cfg
'''
candidate_text = clean(config=candidate_config,
path=candidate_path,
saltenv=saltenv)
running_text = clean(config=running_config,
path=running_path,
saltenv=saltenv)
return _get_diff_text(running_text, candidate_text)