refactors junos modules to support persistent socket connections (#21365)

* updates junos_netconf module
* updates junos_command module
* updates junos_config module
* updates _junos_template module
* adds junos_rpc module
* adds junos_user module
This commit is contained in:
Peter Sprygada 2017-02-16 10:53:03 -05:00 committed by GitHub
commit 02d2b753db
15 changed files with 1277 additions and 754 deletions

View file

@ -127,22 +127,13 @@ def main():
transport=dict(default='netconf', choices=['netconf'])
)
module = NetworkModule(argument_spec=argument_spec,
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
comment = module.params['comment']
confirm = module.params['confirm']
commit = not module.check_mode
replace = False
overwrite = False
action = module.params['action']
if action == 'overwrite':
overwrite = True
elif action == 'replace':
replace = True
src = module.params['src']
fmt = module.params['config_format']
@ -150,19 +141,16 @@ def main():
module.fail_json(msg="overwrite cannot be used when format is "
"set per junos-pyez documentation")
results = dict(changed=False)
results['_backup'] = unicode(module.config.get_config()).strip()
results = {'changed': False}
try:
diff = module.config.load_config(src, commit=commit, replace=replace,
confirm=confirm, comment=comment, config_format=fmt)
if module.praams['backup']:
results['__backup__'] = unicode(get_configuration(module))
if diff:
results['changed'] = True
results['diff'] = dict(prepared=diff)
except NetworkError:
exc = get_exception()
module.fail_json(msg=str(exc), **exc.kwargs)
diff = load(module, src, **kwargs)
if diff:
results['changed'] = True
if module._diff:
results['diff'] = {'prepared': diff}
module.exit_json(**results)

View file

@ -16,42 +16,32 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'core',
'version': '1.0'}
ANSIBLE_METADATA = {
'status': ['preview'],
'supported_by': 'core',
'version': '1.0'
}
DOCUMENTATION = """
---
module: junos_command
version_added: "2.1"
author: "Peter Sprygada (@privateip)"
short_description: Execute arbitrary commands on a remote device running Junos
short_description: Run arbitrary commands on an Juniper junos device
description:
- Network devices running the Junos operating system provide a command
driven interface both over CLI and RPC. This module provides an
interface to execute commands using these functions and return the
results to the Ansible playbook. In addition, this
module can specify a set of conditionals to be evaluated against the
returned output, only returning control to the playbook once the
entire set of conditionals has been met.
extends_documentation_fragment: junos
- Sends an arbitrary set of commands to an junos node and returns the results
read from the device. This module includes an
argument that will cause the module to wait for a specific condition
before returning or timing out if the condition is not met.
options:
commands:
description:
- The C(commands) to send to the remote device over the Netconf
transport. The resulting output from the command
- The commands to send to the remote junos device over the
configured provider. The resulting output from the command
is returned. If the I(wait_for) argument is provided, the
module is not returned until the condition is satisfied or
the number of I(retries) has been exceeded.
required: false
default: null
rpcs:
description:
- The C(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
required: true
wait_for:
description:
- Specifies what to evaluate from the output of the command
@ -77,9 +67,9 @@ options:
version_added: "2.2"
retries:
description:
- Specifies the number of retries a command should by tried
- Specifies the number of retries a command should be tried
before it is considered failed. The command is run on the
target device every retry and evaluated against the I(waitfor)
target device every retry and evaluated against the I(wait_for)
conditionals.
required: false
default: 10
@ -91,214 +81,167 @@ options:
trying the command again.
required: false
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', 'json']
requirements:
- junos-eznc
notes:
- This module requires the netconf system service be enabled on
the remote device being managed. 'json' format is supported
for JUNON version >= 14.2
"""
EXAMPLES = """
# Note: examples below use the following provider dict to handle
# transport and authentication to the node.
---
vars:
netconf:
host: "{{ inventory_hostname }}"
username: ansible
password: Ansible
---
- name: run a set of commands
- name: run show version on remote devices
junos_command:
commands: ['show version', 'show ip route']
provider: "{{ netconf }}"
commands: show version
- name: run a command with a conditional applied to the second command
- name: run show version and check to see if output contains Juniper
junos_command:
commands: show version
wait_for: result[0] contains Juniper
- name: run multiple commands on remote nodes
junos_command:
commands:
- show version
- show interfaces fxp0
waitfor:
- "result[1].interface-information.physical-interface.name eq fxp0"
provider: "{{ netconf }}"
- show interfaces
- name: collect interface information using rpc
- name: run multiple commands and evaluate the output
junos_command:
rpcs:
- "get_interface_information interface=em0 media=True"
- "get_interface_information interface=fxp0 media=True"
provider: "{{ netconf }}"
commands:
- show version
- show interfaces
wait_for:
- result[0] contains Juniper
- result[1] contains Loopback0
- name: run commands and specify the output format
junos_command:
commands:
- command: show version
output: json
"""
RETURN = """
stdout:
description: The output from the commands read from the device
returned: always
type: list
sample: ['...', '...']
stdout_lines:
description: The output read from the device split into lines
returned: always
type: list
sample: [['...', '...'], ['...', '...']]
failed_conditionals:
failed_conditions:
description: the conditionals that failed
returned: failed
type: list
sample: ['...', '...']
"""
import time
import ansible.module_utils.junos
from ansible.module_utils.basic import get_exception
from ansible.module_utils.network import NetworkModule, NetworkError
from ansible.module_utils.netcli import CommandRunner
from ansible.module_utils.netcli import AddCommandError, FailedConditionsError
from ansible.module_utils.netcli import FailedConditionalError, AddConditionError
from ansible.module_utils.junos import xml_to_json
from functools import partial
from ansible.module_utils.junos import run_commands
from ansible.module_utils.junos import junos_argument_spec
from ansible.module_utils.junos import check_args as junos_check_args
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import string_types
from ansible.module_utils.netcli import Conditional
from ansible.module_utils.network_common import ComplexList
VALID_KEYS = {
'cli': frozenset(['command', 'output', 'prompt', 'response']),
'rpc': frozenset(['command', 'output'])
}
def check_args(module, warnings):
junos_check_args(module, warnings)
if module.params['rpcs']:
module.fail_json(msg='argument rpcs has been deprecated, please use '
'junos_rpc instead')
def to_lines(stdout):
lines = list()
for item in stdout:
if isinstance(item, string_types):
item = str(item).split('\n')
yield item
lines.append(item)
return lines
def parse(module, command_type):
if command_type == 'cli':
items = module.params['commands']
elif command_type == 'rpc':
items = module.params['rpcs']
def parse_commands(module, warnings):
spec = dict(
command=dict(key=True),
output=dict(default=module.params['display'], choices=['text', 'json']),
prompt=dict(),
response=dict()
)
parsed = list()
for item in (items or list()):
if isinstance(item, string_types):
item = dict(command=item, output=None)
elif 'command' not in item:
module.fail_json(msg='command keyword argument is required')
elif item.get('output') not in [None, 'text', 'xml']:
module.fail_json(msg='invalid output specified for command'
'Supported values are `text` or `xml`')
elif not set(item.keys()).issubset(VALID_KEYS[command_type]):
module.fail_json(msg='unknown command keyword specified. Valid '
'values are %s' % ', '.join(VALID_KEYS[command_type]))
transform = ComplexList(spec, module)
commands = transform(module.params['commands'])
if not item['output']:
item['output'] = module.params['display']
for index, item in enumerate(commands):
if module.check_mode and not item['command'].startswith('show'):
warnings.append(
'Only show commands are supported when using check_mode, not '
'executing %s' % item['command']
)
item['command_type'] = command_type
if item['output'] == 'json' and 'display json' not in item['command']:
item['command'] += '| display json'
elif item['output'] == 'text' and 'display json' in item['command']:
item['command'] = item['command'].replace('| display json', '')
# show configuration [options] will return as text
if item['command'].startswith('show configuration'):
item['output'] = 'text'
parsed.append(item)
return parsed
commands[index] = item
return commands
def main():
"""main entry point for Ansible module
"""entry point for module execution
"""
argument_spec = dict(
commands=dict(type='list', required=True),
display=dict(choices=['text', 'json'], default='text'),
spec = dict(
commands=dict(type='list'),
# deprecated (Ansible 2.3) - use junos_rpc
rpcs=dict(type='list'),
display=dict(default='xml', choices=['text', 'xml', 'json'],
aliases=['format', 'output']),
wait_for=dict(type='list', aliases=['waitfor']),
match=dict(default='all', choices=['all', 'any']),
retries=dict(default=10, type='int'),
interval=dict(default=1, type='int'),
transport=dict(default='netconf', choices=['netconf'])
interval=dict(default=1, type='int')
)
mutually_exclusive = [('commands', 'rpcs')]
argument_spec.update(junos_argument_spec)
module = NetworkModule(argument_spec=spec,
mutually_exclusive=mutually_exclusive,
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
commands = list()
for key in VALID_KEYS.keys():
commands.extend(list(parse(module, key)))
conditionals = module.params['wait_for'] or list()
warnings = list()
check_args(module, warnings)
runner = CommandRunner(module)
commands = parse_commands(module, warnings)
for cmd in commands:
if module.check_mode and not cmd['command'].startswith('show'):
warnings.append('only show commands are supported when using '
'check mode, not executing `%s`' % cmd['command'])
else:
if cmd['command'].startswith('co'):
module.fail_json(msg='junos_command does not support running '
'config mode commands. Please use '
'junos_config instead')
try:
runner.add_command(**cmd)
except AddCommandError:
exc = get_exception()
warnings.append('duplicate command detected: %s' % cmd)
wait_for = module.params['wait_for'] or list()
conditionals = [Conditional(c) for c in wait_for]
try:
for item in conditionals:
runner.add_conditional(item)
except (ValueError, AddConditionError):
exc = get_exception()
module.fail_json(msg=str(exc), condition=exc.condition)
retries = module.params['retries']
interval = module.params['interval']
match = module.params['match']
runner.retries = module.params['retries']
runner.interval = module.params['interval']
runner.match = module.params['match']
while retries > 0:
responses = run_commands(module, commands)
try:
runner.run()
except FailedConditionsError:
exc = get_exception()
module.fail_json(msg=str(exc), failed_conditions=exc.failed_conditions)
except FailedConditionalError:
exc = get_exception()
module.fail_json(msg=str(exc), failed_conditional=exc.failed_conditional)
except NetworkError:
exc = get_exception()
module.fail_json(msg=str(exc))
for item in list(conditionals):
if item(responses):
if match == 'any':
conditionals = list()
break
conditionals.remove(item)
result = dict(changed=False, stdout=list())
if not conditionals:
break
for cmd in commands:
try:
output = runner.get_command(cmd['command'], cmd.get('output'))
except ValueError:
output = 'command not executed due to check_mode, see warnings'
result['stdout'].append(output)
time.sleep(interval)
retries -= 1
if conditionals:
failed_conditions = [item.raw for item in conditionals]
msg = 'One or more conditional statements have not be satisfied'
module.fail_json(msg=msg, failed_conditions=failed_conditions)
result = {
'changed': False,
'warnings': warnings,
'stdout': responses,
'stdout_lines': to_lines(responses)
}
result['warnings'] = warnings
result['stdout_lines'] = list(to_lines(result['stdout']))
module.exit_json(**result)

View file

@ -16,9 +16,11 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'core',
'version': '1.0'}
ANSIBLE_METADATA = {
'status': ['preview'],
'supported_by': 'core',
'version': '1.0'
}
DOCUMENTATION = """
---
@ -31,7 +33,6 @@ description:
configuration running on Juniper JUNOS devices. It provides a set
of arguments for loading configuration, performing rollback operations
and zeroing the active configuration on the device.
extends_documentation_fragment: junos
options:
lines:
description:
@ -144,16 +145,6 @@ notes:
"""
EXAMPLES = """
# Note: examples below use the following provider dict to handle
# transport and authentication to the node.
---
vars:
netconf:
host: "{{ inventory_hostname }}"
username: ansible
password: Ansible
---
- name: load configure file into device
junos_config:
src: srx.cfg
@ -182,19 +173,27 @@ backup_path:
type: path
sample: /playbooks/ansible/backup/config.2016-07-16@22:28:34
"""
import re
import json
from xml.etree import ElementTree
from ncclient.xml_ import to_xml
import ansible.module_utils.junos
from ansible.module_utils.basic import get_exception
from ansible.module_utils.network import NetworkModule, NetworkError
from ansible.module_utils.junos import get_diff, load
from ansible.module_utils.junos import locked_config, load_configuration
from ansible.module_utils.junos import get_configuration
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.netcfg import NetworkConfig
DEFAULT_COMMENT = 'configured by junos_config'
def check_args(module, warnings):
if module.params['zeroize']:
module.fail_json(msg='argument zeroize is deprecated and no longer '
'supported, use junos_command instead')
if module.params['replace'] is not None:
module.fail_json(msg='argument replace is deprecated, use update')
def guess_format(config):
try:
@ -233,91 +232,63 @@ def config_to_commands(config):
return commands
def diff_commands(commands, config):
config = [unicode(c).replace("'", '') for c in config]
def filter_delete_statements(module, candidate):
reply = get_configuration(module, format='set')
config = reply.xpath('//configuration-set')[0].text.strip()
for index, line in enumerate(candidate):
if line.startswith('delete'):
newline = re.sub('^delete', 'set', line)
if newline not in config:
del candidate[index]
return candidate
updates = list()
visited = set()
for index, item in enumerate(commands):
if len(item) > 0:
if not item.startswith('set') and not item.startswith('delete'):
raise ValueError('line must start with either `set` or `delete`')
elif item.startswith('set') and item[4:] not in config:
updates.append(item)
elif item.startswith('delete'):
for entry in config + commands[0:index]:
if entry.startswith('set'):
entry = entry[4:]
if entry.startswith(item[7:]) and item not in visited:
updates.append(item)
visited.add(item)
return updates
def load_config(module, result):
def load_config(module):
candidate = module.params['lines'] or module.params['src']
if isinstance(candidate, basestring):
candidate = candidate.split('\n')
kwargs = dict()
kwargs['comment'] = module.params['comment']
kwargs['confirm'] = module.params['confirm']
kwargs[module.params['update']] = True
kwargs['commit'] = not module.check_mode
kwargs['replace'] = module.params['replace']
confirm = module.params['confirm'] > 0
confirm_timeout = module.params['confirm']
kwargs = {
'confirm': module.params['confirm'] is not None,
'confirm_timeout': module.params['confirm_timeout'],
'comment': module.params['comment'],
'commit': not module.check_mode,
}
if module.params['src']:
config_format = module.params['src_format'] or guess_format(str(candidate))
elif module.params['lines']:
config_format = 'set'
kwargs['config_format'] = config_format
kwargs.update({'format': config_format, 'action': module.params['update']})
# this is done to filter out `delete ...` statements which map to
# nothing in the config as that will cause an exception to be raised
if config_format == 'set':
config = module.config.get_config()
config = config_to_commands(config)
candidate = diff_commands(candidate, config)
if module.params['lines']:
candidate = filter_delete_statements(module, candidate)
kwargs.update({'action': 'set', 'format': 'text'})
diff = module.config.load_config(candidate, **kwargs)
if diff:
result['changed'] = True
result['diff'] = dict(prepared=diff)
return load(module, candidate, **kwargs)
def rollback_config(module, result):
rollback = module.params['rollback']
diff = None
kwargs = dict(comment=module.params['comment'],
commit=not module.check_mode)
with locked_config:
load_configuration(module, rollback=rollback)
diff = get_diff(module)
diff = module.connection.rollback_config(rollback, **kwargs)
return diff
if diff:
result['changed'] = True
result['diff'] = dict(prepared=diff)
def confirm_config(module):
with locked_config:
commit_configuration(confirm=True)
def zeroize_config(module, result):
if not module.check_mode:
module.connection.cli('request system zeroize')
result['changed'] = True
def confirm_config(module, result):
checkonly = module.check_mode
result['changed'] = module.connection.confirm_commit(checkonly)
def run(module, result):
if module.params['rollback']:
return rollback_config(module, result)
elif module.params['zeroize']:
return zeroize_config(module, result)
elif not any((module.params['src'], module.params['lines'])):
return confirm_config(module, result)
else:
return load_config(module, result)
def update_result(module, result, diff=None):
if diff == '':
diff = None
result['changed'] = diff is not None
if module._diff:
result['diff'] = {'prepared': diff}
def main():
@ -330,8 +301,10 @@ def main():
src_format=dict(choices=['xml', 'text', 'set', 'json']),
# update operations
update=dict(default='merge', choices=['merge', 'overwrite', 'replace']),
replace=dict(default=False, type='bool'),
update=dict(default='merge', choices=['merge', 'overwrite', 'replace', 'update']),
# deprecated replace in Ansible 2.3
replace=dict(type='bool'),
confirm=dict(default=0, type='int'),
comment=dict(default=DEFAULT_COMMENT),
@ -339,36 +312,35 @@ def main():
# config operations
backup=dict(type='bool', default=False),
rollback=dict(type='int'),
zeroize=dict(default=False, type='bool'),
transport=dict(default='netconf', choices=['netconf'])
# deprecated zeroize in Ansible 2.3
zeroize=dict(default=False, type='bool'),
)
mutually_exclusive = [('lines', 'rollback'), ('lines', 'zeroize'),
('rollback', 'zeroize'), ('lines', 'src'),
('src', 'zeroize'), ('src', 'rollback'),
('update', 'replace')]
mutually_exclusive = [('lines', 'src', 'rollback')]
required_if = [('replace', True, ['src']),
('update', 'merge', ['src', 'lines'], True),
('update', 'overwrite', ['src', 'lines'], True),
('update', 'replace', ['src', 'lines'], True)]
module = NetworkModule(argument_spec=argument_spec,
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
required_if=required_if,
supports_check_mode=True)
result = dict(changed=False)
warnings = list()
check_args(module, warnings)
result = {'changed': False, 'warnings': warnings}
if module.params['backup']:
result['__backup__'] = module.config.get_config()
result['__backup__'] = get_configuration()
try:
run(module, result)
except NetworkError:
exc = get_exception()
module.fail_json(msg=str(exc), **exc.kwargs)
if module.params['rollback']:
diff = get_diff(module)
update_result(module, result, diff)
elif not any((module.params['src'], module.params['lines'])):
confirm_config(module)
else:
diff = load_config(module)
update_result(module, result, diff)
module.exit_json(**result)

View file

@ -16,9 +16,11 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'core',
'version': '1.0'}
ANSIBLE_METADATA = {
'status': ['preview'],
'supported_by': 'core',
'version': '1.0'
}
DOCUMENTATION = """
---
@ -56,27 +58,14 @@ options:
"""
EXAMPLES = """
# Note: examples below use the following provider dict to handle
# transport and authentication to the node.
---
vars:
cli:
host: "{{ inventory_hostname }}"
username: ansible
password: Ansible
transport: cli
---
- name: enable netconf service on port 830
junos_netconf:
listens_on: 830
state: present
provider: "{{ cli }}"
- name: disable netconf service
junos_netconf:
state: absent
provider: "{{ cli }}"
"""
RETURN = """
@ -88,64 +77,94 @@ commands:
"""
import re
import ansible.module_utils.junos
from ansible.module_utils.junos import load_config, get_config
from ansible.module_utils.junos import junos_argument_spec, check_args
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible.module_utils.basic import get_exception
from ansible.module_utils.network import NetworkModule, NetworkError
def map_obj_to_commands(updates, module):
want, have = updates
commands = list()
if want['state'] == 'present' and have['state'] == 'absent':
commands.append(
'set system services netconf ssh port %s' % want['netconf_port']
)
elif want['state'] == 'absent' and have['state'] == 'present':
commands.append('delete system services netconf')
elif want['netconf_port'] != have.get('netconf_port'):
commands.append(
'set system services netconf ssh port %s' % want['netconf_port']
)
return commands
def parse_port(config):
match = re.search(r'port (\d+)', config)
if match:
return int(match.group(1))
def get_instance(module):
cmd = 'show configuration system services netconf'
cfg = module.cli(cmd)[0]
result = dict(state='absent')
if cfg:
result = dict(state='present')
result['port'] = parse_port(cfg)
return result
def map_config_to_obj(module):
config = get_config(module, ['system services netconf'])
obj = {'state': 'absent'}
if config:
obj.update({
'state': 'present',
'netconf_port': parse_port(config)
})
return obj
def validate_netconf_port(value, module):
if not 1 <= value <= 65535:
module.fail_json(msg='netconf_port must be between 1 and 65535')
def map_params_to_obj(module):
obj = {
'netconf_port': module.params['netconf_port'],
'state': module.params['state']
}
for key, value in iteritems(obj):
# validate the param value (if validator func exists)
validator = globals().get('validate_%s' % key)
if all((value, validator)):
validator(value, module)
return obj
def main():
"""main entry point for module execution
"""
argument_spec = dict(
netconf_port=dict(type='int', default=830, aliases=['listens_on']),
state=dict(default='present', choices=['present', 'absent']),
transport=dict(default='cli', choices=['cli'])
)
module = NetworkModule(argument_spec=argument_spec,
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
state = module.params['state']
port = module.params['netconf_port']
warnings = list()
check_args(module, warnings)
result = dict(changed=False)
result = {'changed': False, 'warnings': warnings}
instance = get_instance(module)
want = map_params_to_obj(module)
have = map_config_to_obj(module)
if state == 'present' and instance.get('state') == 'absent':
commands = 'set system services netconf ssh port %s' % port
elif state == 'present' and port != instance.get('port'):
commands = 'set system services netconf ssh port %s' % port
elif state == 'absent' and instance.get('state') == 'present':
commands = 'delete system services netconf'
else:
commands = None
commands = map_obj_to_commands((want, have), module)
result['commands'] = commands
if commands:
if not module.check_mode:
try:
comment = 'configuration updated by junos_netconf'
module.config(commands, comment=comment)
except NetworkError:
exc = get_exception()
module.fail_json(msg=str(exc), **exc.kwargs)
result['changed'] = True
result['commands'] = commands
commit = not module.check_mode
diff = load_config(module, commands, commit=commit)
if diff and module._diff:
if module._diff:
result['diff'] = {'prepared': diff}
result['changed'] = True
module.exit_json(**result)

View file

@ -0,0 +1,151 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {
'status': ['preview'],
'supported_by': 'core',
'version': '1.0'
}
DOCUMENTATION = """
---
module: junos_rpc
version_added: "2.3"
author: "Peter Sprygada (@privateip)"
short_description: Runs an arbitrary RPC on the remote device over NetConf
description:
- Sends a request to the remote device running JUNOS to execute the
specified RPC using the NetConf transport. The reply is then
returned to the playbook in the c(xml) key. If an alternate output
format is requested, the reply is transformed to the requested output.
options:
rpc:
description:
- The C(rpc) argument specifies the RPC call to send to the
remote devices to be executed. The RPC Reply message is parsed
and the contents are returned to the playbook.
required: true
args:
description:
- The C(args) argument provides a set of arguments for the RPC
call and are encoded in the request message. This argument
accepts a set of key=value arguments.
required: false
default: null
output:
description:
- The C(output) argument specifies the desired output of the
return data. This argument accepts one of C(xml), C(text),
or C(json). For C(json), the JUNOS device must be running a
version of software that supports native JSON output.
required: false
default: xml
"""
EXAMPLES = """
- name: collect interface information using rpc
junos_rpc:
rpc: get-interface-information
args:
interface: em0
media: True
- name: get system information
junos_rpc:
rpc: get-system-information
"""
RETURN = """
xml:
description: The xml return string from the rpc request
returned: always
output:
description: The rpc rely converted to the output format
returned: always
output_lines:
description: The text output split into lines for readability
returned: always
"""
from ncclient.xml_ import new_ele, sub_ele, to_xml, to_ele
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.netconf import send_request
from ansible.module_utils.six import iteritems
def main():
"""main entry point for Ansible module
"""
argument_spec = dict(
rpc=dict(required=True),
args=dict(type='dict'),
output=dict(default='xml', choices=['xml', 'json', 'text']),
)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=False)
result = {'changed': False}
rpc = str(module.params['rpc']).replace('_', '-')
if all((module.check_mode, not rpc.startswith('get'))):
module.fail_json(msg='invalid rpc for running in check_mode')
args = module.params['args'] or {}
xattrs = {'format': module.params['output']}
element = new_ele(module.params['rpc'], xattrs)
for key, value in iteritems(args):
key = str(key).replace('_', '-')
if isinstance(value, list):
for item in value:
child = sub_ele(element, key)
if item is not True:
child.text = item
else:
child = sub_ele(element, key)
if value is not True:
child.text = value
reply = send_request(module, element)
result['xml'] = str(to_xml(reply))
if module.params['output'] == 'text':
reply = to_ele(reply)
data = reply.xpath('//output')
result['output'] = data[0].text.strip()
result['output_lines'] = result['output'].split('\n')
elif module.params['output'] == 'json':
reply = to_ele(reply)
data = reply.xpath('//rpc-reply')
result['output'] = module.from_json(data[0].text.strip())
else:
result['output'] = str(to_xml(reply)).split('\n')
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,256 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {
'status': ['preview'],
'supported_by': 'core',
'version': '1.0'
}
DOCUMENTATION = """
---
module: junos_user
version_added: "2.3"
author: "Peter Sprygada (@privateip)"
short_description: Manage local user accounts on Juniper devices
description:
- This module manages locally configured user accounts on remote
network devices running the JUNOS operating system. It provides
a set of arguments for creating, removing and updating locally
defined accounts
options:
users:
description:
- The C(users) argument defines a list of users to be configured
on the remote device. The list of users will be compared against
the current users and only changes will be added or removed from
the device configuration. This argument is mutually exclusive with
the name argument.
required: False
default: null
name:
description:
- The C(name) argument defines the username of the user to be created
on the system. This argument must follow appropriate usernaming
conventions for the target device running JUNOS. This argument is
mutually exclusive with the C(users) argument.
required: false
default: null
full_name:
description:
- The C(full_name) argument provides the full name of the user
account to be created on the remote device. This argument accepts
any text string value.
required: false
default: null
role:
description:
- The C(role) argument defines the role of the user account on the
remote system. User accounts can have more than one role
configured.
required: false
default: read-only
choices: ['operator', 'read-only', 'super-user', 'unauthorized']
sshkey:
description:
- The C(sshkey) argument defines the public SSH key to be configured
for the user account on the remote system. This argument must
be a valid SSH key
required: false
default: null
purge:
description:
- The C(purge) argument instructs the module to consider the
users definition absolute. It will remove any previously configured
users on the device with the exception of the current defined
set of users.
required: false
default: false
state:
description:
- The C(state) argument configures the state of the user definitions
as it relates to the device operational configuration. When set
to I(present), the user should be configured in the device active
configuration and when set to I(absent) the user should not be
in the device active configuration
required: false
default: present
choices: ['present', 'absent']
"""
EXAMPLES = """
- name: create new user account
junos_user:
name: ansible
role: super-user
sshkey: "{{ lookup('file', '~/.ssh/ansible.pub') }}"
state: present
- name: remove a user account
junos_user:
name: ansible
state: absent
- name: remove all user accounts except ansible
junos_user:
name: ansible
purge: yes
"""
RETURN = """
"""
from functools import partial
from ncclient.xml_ import new_ele, sub_ele, to_xml
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.junos import load
from ansible.module_utils.six import iteritems
ROLES = ['operator', 'read-only', 'super-user', 'unauthorized']
def map_obj_to_ele(want):
element = new_ele('system')
login = sub_ele(element, 'login', {'replace': 'replace'})
for item in want:
if item['state'] != 'present':
operation = 'delete'
else:
operation = 'replace'
user = sub_ele(login, 'user', {'operation': operation})
sub_ele(user, 'name').text = item['name']
if operation == 'replace':
sub_ele(user, 'class').text = item['role']
if item.get('full_name'):
sub_ele(user, 'full-name').text = item['full_name']
if item.get('sshkey'):
auth = sub_ele(user, 'authentication')
ssh_rsa = sub_ele(auth, 'ssh-rsa')
key = sub_ele(ssh_rsa, 'name').text = item['sshkey']
return element
def get_param_value(key, item, module):
# if key doesn't exist in the item, get it from module.params
if not item.get(key):
value = module.params[key]
# if key does exist, do a type check on it to validate it
else:
value_type = module.argument_spec[key].get('type', 'str')
type_checker = module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type]
type_checker(item[key])
value = item[key]
# validate the param value (if validator func exists)
validator = globals().get('validate_%s' % key)
if all((value, validator)):
validator(value, module)
return value
def map_params_to_obj(module):
users = module.params['users']
if not users:
if not module.params['name'] and module.params['purge']:
return list()
elif not module.params['name']:
module.fail_json(msg='missing required argument: name')
else:
collection = [{'name': module.params['name']}]
else:
collection = list()
for item in users:
if not isinstance(item, dict):
collection.append({'username': item})
elif 'name' not in item:
module.fail_json(msg='missing required argument: name')
else:
collection.append(item)
objects = list()
for item in collection:
get_value = partial(get_param_value, item=item, module=module)
item.update({
'full_name': get_value('full_name'),
'role': get_value('role'),
'sshkey': get_value('sshkey'),
'state': get_value('state')
})
for key, value in iteritems(item):
# validate the param value (if validator func exists)
validator = globals().get('validate_%s' % key)
if all((value, validator)):
validator(value, module)
objects.append(item)
return objects
def main():
""" main entry point for module execution
"""
argument_spec = dict(
users=dict(type='list'),
name=dict(),
full_name=dict(),
role=dict(choices=ROLES, default='unauthorized'),
sshkey=dict(),
purge=dict(type='bool'),
state=dict(choices=['present', 'absent'], default='present')
)
mutually_exclusive = [('users', 'name')]
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
result = {'changed': False}
want = map_params_to_obj(module)
ele = map_obj_to_ele(want)
kwargs = {'commit': not module.check_mode}
if module.params['purge']:
kwargs['action'] = 'replace'
diff = load(module, ele, **kwargs)
if diff:
result.update({
'changed': True,
'diff': {'prepared': diff}
})
module.exit_json(**result)
if __name__ == "__main__":
main()