Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds jinja2 filters for working with network devices #24216

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/docsite/rst/playbooks_filters.rst
Expand Up @@ -318,6 +318,35 @@ address. For example, to get the IP address itself from a CIDR, you can use::
More information about ``ipaddr`` filter and complete usage guide can be found
in :doc:`playbooks_filters_ipaddr`.

.. _network_filter:

Network filters
```````````````

.. versionadded:: 2.4

To generate a list of command diffs for a network configuration:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For code blocks in RST you use ::, blank line, four spaces

e.g.

To generate a list of command diffs for a network configuration::

    {{ myvar | diff_network_config(current_config, indent=1 }}


{{ myvar | diff_network_config(current_config, indent=1 }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to close out the function: e.g.
{{ myvar | diff_network_config(current_config, indent=1 ) }}

Also might be a good idea to show example using a file lookup.


To return the entire configuration block:

{{ myvar | diff_network_config(current_config, indent=1, replace='block' }}

To match the block exactly:

{{ myvar | diff_network_config(current_config, indent=1, match='exact' }}

To apply a TextFSM filter to the output of a CLI command:

{{ myvar | parse_cli(('cisco_show_version') }}

Templates can also be downloaded from URLs:

{{ myvar | parse_cli('http://myserver/cisco_show_version') }}

The CLI parsing filter depends on the TextFSM library being installed on
the local system

.. _hash_filters:

Expand Down
112 changes: 112 additions & 0 deletions lib/ansible/plugins/filter/network.py
@@ -0,0 +1,112 @@
#
# {c) 2017 Red Hat, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.

# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import os
import urllib2
import urlparse

try:
import textfsm
HAS_TEXTFSM = True
except ImportError:
HAS_TEXTFSM = False

from ansible.module_utils.six import StringIO
from ansible.module_utils.netcfg import NetworkConfig, dumps
from ansible.errors import AnsibleError

try:
from __main__ import display
except ImportError:
from ansible.utils.display import Display
display = Display()

NETWORK_OS = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should be called NETWORK_OS_INDENT, or similar

'ios': 1,
'iosxr': 1,
'nxos': 2,
'eos': 4
}

def diff_network_config(value, base, network_os=None, indent=1, match='line', replace='line'):

assert match in ('line', 'strict', 'exact', 'none')
assert replace in ('line', 'block')
assert network_os in NETWORK_OS.keys()

if network_os:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how this would ever hit base on the previous assert? Perhaps the assert should be here?

indent = NETWORK_OS.get(network_os, indent)

candidate = NetworkConfig(indent=indent, contents=value)
base_config = NetworkConfig(indent=indent, contents=base)

objs = candidate.difference(base_config, match=match, replace=replace)
commands = dumps(objs, 'commands')

return commands

def parse_cli(value, template):
if not HAS_TEXTFSM:
raise AnsibleError('parse_cli filter requires TextFSM library to be installed')

url = urlparse.urlparse(template)
if url.scheme.startswith('http'):
try:
handler = {}
if 'HTTP_PROXY' in os.environ:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth adding some comments to say why you set the proxy to localhost.

handler['http'] = '127.0.0.1'
if 'HTTPS_PROXY' in os.environ:
handler['https'] = '127.0.0.1'
if handler:
opener = urllib2.build_opener(urllib2.ProxyHandler(handler))
urllib2.install_opener(opener)
resp = urllib2.urlopen(template)
except urllib2.HTTPError as exc:
raise AnsibleError(str(exc))
data = StringIO()
data.write(resp.read())
data.seek(0)
template = data
else:
if not os.path.exists(template):
raise AnsibleError('unable to locate parse_cli template: %s' % template)
try:
template = open(template)
except IOError as exc:
raise AnsibleError(str(exc))
re_table = textfsm.TextFSM(template)
fsm_results = re_table.ParseText(value)
results = list()
for item in fsm_results:
results.append(dict(zip(re_table.header, item)))
return results

class FilterModule(object):
"""Filters for working with output from network devices"""

filter_map = {
'parse_cli': parse_cli,
'diff_network_config': diff_network_config
}

def filters(self):
return self.filter_map