Merge pull request #3428 from privateip/junos_command

add support to junos_command module for netconf transport
This commit is contained in:
Peter Sprygada 2016-04-24 07:17:11 -04:00
commit fe7df6ca30

View file

@ -20,32 +20,40 @@ DOCUMENTATION = """
--- ---
module: junos_command module: junos_command
version_added: "2.1" version_added: "2.1"
author: "Peter sprygada (@privateip)" author: "Peter Sprygada (@privateip)"
short_description: Execute arbitrary commands on Juniper JUNOS devices short_description: Execute arbitrary commands on a remote device running Junos
description: description:
- Netork devices running Juniper JUNOS can execute a variety of commands - Network devices running the Junos operating system provide a command
to display both configuration and state data. This module will take driven interface both over CLI and RPC. This module provides an
an ordered set of commands and execute them in JUNOS and return the interface to execute commands using these functions and return the
command results. It also supports adding conditionals the results to the Ansible playbook. In addition, the M(junos_command)
argument set to force the module to meet a set of criteria before module can specify a set of conditionals to be evaluated against the
proceeding. returned output, only returning control to the playbook once the
entire set of conditionals has been met.
extends_documentation_fragment: junos extends_documentation_fragment: junos
options: options:
commands: commands:
description: description:
- The commands to send to the remote JUNOS device over the - An ordered set of CLI commands to be executed on the remote
configured provider. The resulting output from the command device. The output from the commands is then returned to
is returned. If the I(waitfor) argument is provided, the the playbook in the task results.
module is not returned until the condition is satisfied or required: false
the number of retires as expired. default: null
required: true rpcs:
description:
- The O(rpcs) argument accepts a list of RPCs to be executed
over a netconf session and the results from the RPC execution
is return to the playbook via the modules results dictionary.
required: false
default: null
waitfor: waitfor:
description: description:
- Specifies what to evaluate from the output of the command - Specifies what to evaluate from the output of the command
and what conditionals to apply. This argument will cause and what conditionals to apply. This argument will cause
the task to wait for a particular conditional to be true the task to wait for a particular conditional or set of
before moving forward. If the conditional is not true considitonals to be true before moving forward. If the
by the configured retries, the task fails. See examples. conditional is not true by the configured retries, the
task fails. See examples.
required: false required: false
default: null default: null
retries: retries:
@ -64,6 +72,19 @@ options:
trying the command again. trying the command again.
required: false required: false
default: 1 default: 1
format:
description:
- Configures the encoding scheme to use when serializing output
from the device. This handles how to properly understand the
output and apply the conditionals path to the result set.
required: false
default: 'xml'
choices: ['xml', 'text']
requirements:
- junos-eznc
notes:
- This module requires the netconf system service be enabled on
the remote device being managed
""" """
EXAMPLES = """ EXAMPLES = """
@ -78,10 +99,15 @@ EXAMPLES = """
junos_command: junos_command:
commands: commands:
- show version - show version
- show interfaces fxp0 | display json - show interfaces fxp0
waitfor: waitfor:
- "result[1].interface-information[0].physical-interface[0].name[0].data - "result[1].interface-information.physical-interface.name eq fxp0"
eq fxp0"
- name: collect interface information using rpc
junos_command:
rpcs:
- "get_interface_information interface=em0 media=True"
- "get_interface_information interface=fxp0 media=True"
""" """
RETURN = """ RETURN = """
@ -97,137 +123,93 @@ stdout_lines:
type: list type: list
sample: [['...', '...'], ['...', '...']] sample: [['...', '...'], ['...', '...']]
xml:
description: The raw XML reply from the device
returned: when format is xml
type: list
sample: [['...', '...'], ['...', '...']]
failed_conditionals: failed_conditionals:
description: the conditionals that failed description: the conditionals that failed
retured: failed retured: failed
type: list type: list
sample: ['...', '...'] sample: ['...', '...']
""" """
import time
import shlex import shlex
import re
import json
import itertools
INDEX_RE = re.compile(r'(\[\d+\])') def split(value):
lex = shlex.shlex(value)
lex.quotes = '"'
lex.whitespace_split = True
lex.commenters = ''
return list(lex)
class Conditional(object): def rpc_args(args):
kwargs = dict()
OPERATORS = { args = split(args)
'eq': ['eq', '=='], name = args.pop(0)
'neq': ['neq', 'ne', '!='], for arg in args:
'gt': ['gt', '>'], key, value = arg.split('=')
'ge': ['ge', '>='], if str(value).upper() in ['TRUE', 'FALSE']:
'lt': ['lt', '<'], kwargs[key] = bool(value)
'le': ['le', '<='], elif re.match(r'\d+', value):
'contains': ['contains', 'in'] kwargs[key] = int(value)
}
def __init__(self, conditional):
self.raw = conditional
key, op, val = shlex.split(conditional)
self.key = key
self.func = self.func(op)
self.value = self._cast_value(val)
def __call__(self, data):
try:
value = self.get_value(dict(result=data))
return self.func(value)
except Exception:
raise
def _cast_value(self, value):
if value in BOOLEANS_TRUE:
return True
elif value in BOOLEANS_FALSE:
return False
elif re.match(r'^\d+\.d+$', value):
return float(value)
elif re.match(r'^\d+$', value):
return int(value)
else: else:
return unicode(value) kwargs[key] = str(value)
return (name, kwargs)
def func(self, oper): def parse_rpcs(rpcs):
for func, operators in self.OPERATORS.items(): parsed = list()
if oper in operators: for rpc in (rpcs or list()):
return getattr(self, func) parsed.append(rpc_args(rpc))
raise AttributeError('unknown operator: %s' % oper) return parsed
def get_value(self, result): def run_rpcs(module, items, format):
for key in self.key.split('.'): response = list()
match = re.match(r'^(.+)\[(\d+)\]', key) for name, kwargs in items:
if match: kwargs['format'] = format
key, index = match.groups() result = module.connection.rpc(name, **kwargs)
result = result[key][int(index)] if format == 'text':
response.append(result.text)
else: else:
result = result.get(key) response.append(result)
return result return response
def number(self, value): def iterlines(stdout):
if '.' in str(value):
return float(value)
else:
return int(value)
def eq(self, value):
return value == self.value
def neq(self, value):
return value != self.value
def gt(self, value):
return self.number(value) > self.value
def ge(self, value):
return self.number(value) >= self.value
def lt(self, value):
return self.number(value) < self.value
def le(self, value):
return self.number(value) <= self.value
def contains(self, value):
return self.value in value
def parse_response(module, responses):
commands = module.params['commands']
result = dict(stdout_json=list(), stdout=list(), stdout_lines=list())
for cmd, resp in itertools.izip(commands, responses):
result['stdout'].append(resp)
if cmd.endswith('json'):
result['stdout_json'].append(module.jsonify(resp))
else:
result['stdout_lines'].append(resp.split('\n'))
return result
def to_lines(stdout):
for item in stdout: for item in stdout:
if isinstance(item, basestring): if isinstance(item, basestring):
item = str(item).split('\n') item = str(item).split('\n')
yield item yield item
def main(): def main():
"""main entry point for Ansible module
"""
spec = dict( spec = dict(
commands=dict(type='list'), commands=dict(type='list'),
rpcs=dict(type='list'),
format=dict(default='xml', choices=['text', 'xml']),
waitfor=dict(type='list'), waitfor=dict(type='list'),
retries=dict(default=10, type='int'), retries=dict(default=10, type='int'),
interval=dict(default=1, type='int') interval=dict(default=1, type='int'),
transport=dict(default='netconf', choices=['netconf'])
) )
mutually_exclusive = [('commands', 'rpcs')]
module = get_module(argument_spec=spec, module = get_module(argument_spec=spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True) supports_check_mode=True)
commands = module.params['commands'] commands = module.params['commands']
rpcs = parse_rpcs(module.params['rpcs'])
encoding = module.params['format']
retries = module.params['retries'] retries = module.params['retries']
interval = module.params['interval'] interval = module.params['interval']
try: try:
queue = set() queue = set()
for entry in (module.params['waitfor'] or list()): for entry in (module.params['waitfor'] or list()):
@ -238,15 +220,18 @@ def main():
result = dict(changed=False) result = dict(changed=False)
while retries > 0: while retries > 0:
try: if commands:
response = module.execute(commands) response = module.run_commands(commands, format=encoding)
result['stdout'] = response else:
except ShellError: response = run_rpcs(module, rpcs, format=encoding)
module.fail_json(msg='failed to run commands')
for index, cmd in enumerate(commands): result['stdout'] = response
if cmd.endswith('json'): xmlout = list()
response[index] = json.loads(response[index])
for index in range(0, len(response)):
if encoding == 'xml':
xmlout.append(xml_to_string(response[index]))
response[index] = xml_to_json(response[index])
for item in list(queue): for item in list(queue):
if item(response): if item(response):
@ -261,13 +246,17 @@ def main():
failed_conditions = [item.raw for item in queue] failed_conditions = [item.raw for item in queue]
module.fail_json(msg='timeout waiting for value', failed_conditions=failed_conditions) module.fail_json(msg='timeout waiting for value', failed_conditions=failed_conditions)
result['stdout_lines'] = list(to_lines(result['stdout'])) if xmlout:
return module.exit_json(**result) result['xml'] = xmlout
result['stdout_lines'] = list(iterlines(result['stdout']))
module.exit_json(**result)
from ansible.module_utils.basic import * from ansible.module_utils.basic import *
from ansible.module_utils.shell import * from ansible.module_utils.netcfg import *
from ansible.module_utils.junos import * from ansible.module_utils.junos import *
if __name__ == '__main__': if __name__ == '__main__':
main() main()