updates eos shared module

* adds support for netcli methods
* adds support for netcfg methods
* Cli class now derives from CliBase
* adds eos_config action plugin
This commit is contained in:
Peter Sprygada 2016-08-22 20:23:36 -04:00
parent 7ce4165671
commit b5bbac29e5
2 changed files with 110 additions and 157 deletions

View file

@ -29,186 +29,87 @@
import re
from ansible.module_utils.basic import json, get_exception
from ansible.module_utils.network import NetworkModule, NetworkError
from ansible.module_utils.network import NetCli, Command, ModuleStub
from ansible.module_utils.network import NetworkModule, NetworkError, ModuleStub
from ansible.module_utils.network import add_argument, register_transport, to_list
from ansible.module_utils.netcfg import NetworkConfig
from ansible.module_utils.netcli import Command
from ansible.module_utils.shell import CliBase
from ansible.module_utils.urls import fetch_url, url_argument_spec
# temporary fix until modules are update. to be removed before 2.2 final
from ansible.module_utils.network import get_module
EAPI_FORMATS = ['json', 'text']
add_argument('use_ssl', dict(default=True, type='bool'))
add_argument('validate_certs', dict(default=True, type='bool'))
def argument_spec():
return dict(
# config options
running_config=dict(aliases=['config']),
config_session=dict(default='ansible_session'),
save_config=dict(default=False, aliases=['save']),
force=dict(type='bool', default=False)
)
eos_argument_spec = argument_spec()
def get_config(module):
config = module.params['running_config']
if not config:
config = module.config.get_config(include_defaults=False)
return NetworkConfig(indent=3, contents=config)
def load_config(module, candidate):
if not module.params['force']:
config = get_config(module)
commands = candidate.difference(config)
else:
commands = str(candidate)
commands = [str(c).strip() for c in commands]
session = module.params['config_session']
save_config = module.params['save_config']
result = dict(changed=False)
if commands:
if module._diff:
diff = module.config.load_config(commands, session_name=session)
if diff:
result['diff'] = dict(prepared=diff)
if not module.check_mode:
module.config.commit_config(session)
if save_config:
module.config.save_config()
else:
module.config.abort_config(session_name=session)
if not module.check_mode:
module.config(commands)
if save_config:
module.config.save_config()
result['changed'] = True
result['updates'] = commands
return result
def expand_intf_range(interfaces):
match = re.match(r'([a-zA-Z]+)(.+)', interfaces)
if not match:
raise ValueError('could not parse interface range')
name = match.group(1)
values = match.group(2).split(',')
indicies = list()
for val in values:
tokens = val.split('-')
# single index value to handle
if len(tokens) == 1:
indicies.append(tokens[0])
elif len(tokens) == 2:
pairs = list()
mod = 0
for token in tokens:
parts = token.split('/')
if len(parts) == 1:
port = parts[0]
if port == '$':
port = last_port
pairs.append((mod, int(port)))
elif len(parts) == 2:
mod = int(parts[0])
port = parts[1]
if port == '$':
port = last_port
pairs.append((mod, int(port)))
else:
raise ValueError('unable to parse interface')
if pairs[0][0] == pairs[1][0]:
# same module
mod = pairs[0][0]
start = pairs[0][1]
end = pairs[1][1] + 1
for i in range(start, end):
if mod == 0:
indicies.append(i)
else:
indicies.append('%s/%s' % (mod, i))
else:
# span modules
start_mod, start_port = pairs[0]
end_mod, end_port = pairs[1]
end_port += 1
for i in range(start_port, last_port+1):
indicies.append('%s/%s' % (start_mod, i))
for i in range(first_port, end_port):
indicies.append('%s/%s' % (end_mod, i))
return ['%s%s' % (name, index) for index in indicies]
class EosConfigMixin(object):
def configure(self, commands, **kwargs):
commands = prepare_config(commands)
responses = self.execute(commands)
responses.pop(0)
return responses
### implementation of netcfg.Config ###
def get_config(self, **kwargs):
def configure(self, commands, **kwargs):
cmds = ['configure terminal']
cmds.extend(to_list(commands))
cmds.append('end')
responses = self.execute(commands)
return responses[1:-1]
def get_config(self, include_defaults=False, **kwargs):
cmd = 'show running-config'
if kwargs.get('include_defaults') is True:
if include_defaults:
cmd += ' all'
return self.execute([cmd])[0]
def load_config(self, commands, session_name='ansible_temp_session', **kwargs):
commands = to_list(commands)
commands.insert(0, 'configure session %s' % session_name)
commands.append('show session-config diffs')
def load_config(self, config, session, commit=False, replace=False, **kwargs):
""" Loads the configuration into the remote device
This method handles the actual loading of the config
commands into the remote EOS device. By default the
config specified is merged with the current running-config.
:param config: ordered list of config commands to load
:param replace: replace current config when True otherwise merge
:returns list: ordered set of responses from device
"""
commands = ['configure session %s' % session]
if replace:
commands.append('rollback clean-config')
commands.extend(config)
if commands[-1] != 'end':
commands.append('end')
responses = self.execute(commands)
return responses[-2]
def replace_config(self, contents, params, **kwargs):
remote_user = params['username']
remote_path = '/home/%s/ansible-config' % remote_user
commands = [
'bash echo "%s" > %s' % (contents, remote_path),
'diff running-config file:/%s' % remote_path,
'config replace file:/%s' % remote_path,
]
responses = self.run_commands(commands)
return responses[-2]
def commit_config(self, session_name):
session = 'configure session %s' % session_name
commands = [session, 'commit', 'no %s' % session]
try:
self.execute(commands)
def abort_config(self, session_name):
command = 'no configure session %s' % session_name
self.execute([command])
diff = self.diff_config(session)
if commit:
self.commit_config(session)
except NetworkError:
self.abort_config(session)
diff = None
raise
return diff
def save_config(self):
self.execute(['copy running-config startup-config'])
### end netcfg.Config ###
def diff_config(self, session):
commands = ['configure session %s' % session,
'show session-config diffs',
'end']
response = self.execute(commands)
return response[-2]
def commit_config(self, session):
commands = ['configure session %s' % session, 'commit']
self.execute(commands)
def abort_config(self, session):
commands = ['configure session %s' % session, 'abort']
self.execute(commands)
class Eapi(EosConfigMixin):
def __init__(self):
@ -295,6 +196,7 @@ class Eapi(EosConfigMixin):
"""
if self.url is None:
raise NetworkError('Not connected to endpoint.')
if self.enable is not None:
commands.insert(0, self.enable)
@ -330,10 +232,12 @@ class Eapi(EosConfigMixin):
def get_config(self, **kwargs):
return self.run_commands(['show running-config'], format='text')[0]
Eapi = register_transport('eapi')(Eapi)
class Cli(NetCli, EosConfigMixin):
class Cli(CliBase, EosConfigMixin):
CLI_PROMPTS_RE = [
re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"),
re.compile(r"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$")
@ -357,9 +261,23 @@ class Cli(NetCli, EosConfigMixin):
super(Cli, self).connect(params, kickstart=True, **kwargs)
self.shell.send('terminal length 0')
def authorize(self, params, **kwargs):
passwd = params['auth_pass']
self.execute(Command('enable', prompt=self.NET_PASSWD_RE, response=passwd))
### implementation of network.Cli ###
def run_commands(self, commands):
"""execute the ordered set of commands on the remote host
This method will take a list of Command objects, convert them
to a list of dict objects and execute them on the shell
connection.
:param commands: list of Command objects
:returns: set of ordered responses
"""
cmds = list(prepare_commands(commands))
responses = self.execute(cmds)
for index, cmd in enumerate(commands):
@ -372,16 +290,23 @@ class Cli(NetCli, EosConfigMixin):
response=responses[index]
)
return responses
Cli = register_transport('cli', default=True)(Cli)
def prepare_config(commands):
commands = to_list(commands)
commands.insert(0, 'configure terminal')
commands.append('end')
return commands
def prepare_commands(commands):
""" transforms a list of Command objects to dict
:param commands: list of Command objects
:returns: list of dict objects
"""
jsonify = lambda x: '%s | json' % x
for cmd in to_list(commands):
if cmd.output == 'json':

View file

@ -0,0 +1,28 @@
#
# Copyright 2015 Peter Sprygada <psprygada@ansible.com>
#
# 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/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.plugins.action import ActionBase
from ansible.plugins.action.net_config import ActionModule as NetActionModule
class ActionModule(NetActionModule, ActionBase):
pass