forked from ansible/ansible
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding exos_command cli_conf module (ansible#37775)
* Adding exos_command cli_conf module * fixing documentation, indentation and metadata_version * removing doc fragmentation and adding required import * removing unnecessary code and including company name on short_description * updating BOTMETA.yml with exos module information
- Loading branch information
1 parent
2f535a3
commit 0d1e9bf
Showing
7 changed files
with
499 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# This code is part of Ansible, but is an independent component. | ||
# This particular file snippet, and this file snippet only, is BSD licensed. | ||
# Modules you write using this snippet, which is embedded dynamically by Ansible | ||
# still belong to the author of the module, and may assign their own license | ||
# to the complete work. | ||
# | ||
# (c) 2016 Red Hat Inc. | ||
# | ||
# Redistribution and use in source and binary forms, with or without modification, | ||
# are permitted provided that the following conditions are met: | ||
# | ||
# * Redistributions of source code must retain the above copyright | ||
# notice, this list of conditions and the following disclaimer. | ||
# * Redistributions in binary form must reproduce the above copyright notice, | ||
# this list of conditions and the following disclaimer in the documentation | ||
# and/or other materials provided with the distribution. | ||
# | ||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | ||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | ||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | ||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | ||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE | ||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
# | ||
import json | ||
from ansible.module_utils._text import to_text | ||
from ansible.module_utils.basic import env_fallback, return_values | ||
from ansible.module_utils.network.common.utils import to_list | ||
from ansible.module_utils.connection import Connection | ||
|
||
_DEVICE_CONFIGS = {} | ||
|
||
|
||
def get_connection(module): | ||
if hasattr(module, '_exos_connection'): | ||
return module._exos_connection | ||
|
||
capabilities = get_capabilities(module) | ||
network_api = capabilities.get('network_api') | ||
|
||
if network_api == 'cliconf': | ||
module._exos_connection = Connection(module._socket_path) | ||
else: | ||
module.fail_json(msg='Invalid connection type %s' % network_api) | ||
|
||
return module._exos_connection | ||
|
||
|
||
def get_capabilities(module): | ||
if hasattr(module, '_exos_capabilities'): | ||
return module._exos_capabilities | ||
|
||
capabilities = Connection(module._socket_path).get_capabilities() | ||
module._exos_capabilities = json.loads(capabilities) | ||
|
||
return module._exos_capabilities | ||
|
||
|
||
def get_config(module, flags=None): | ||
global _DEVICE_CONFIGS | ||
|
||
if _DEVICE_CONFIGS != {}: | ||
return _DEVICE_CONFIGS | ||
else: | ||
connection = get_connection(module) | ||
out = connection.get_config() | ||
cfg = to_text(out, errors='surrogate_then_replace').strip() | ||
_DEVICE_CONFIGS = cfg | ||
return cfg | ||
|
||
|
||
def run_commands(module, commands, check_rc=True): | ||
responses = list() | ||
connection = get_connection(module) | ||
|
||
for cmd in to_list(commands): | ||
if isinstance(cmd, dict): | ||
command = cmd['command'] | ||
prompt = cmd['prompt'] | ||
answer = cmd['answer'] | ||
else: | ||
command = cmd | ||
prompt = None | ||
answer = None | ||
out = connection.get(command, prompt, answer) | ||
|
||
try: | ||
out = to_text(out, errors='surrogate_or_strict') | ||
except UnicodeError: | ||
module.fail_json(msg=u'Failed to decode output from %s: %s' % (cmd, to_text(out))) | ||
responses.append(out) | ||
|
||
return responses | ||
|
||
|
||
def load_config(module, commands): | ||
connection = get_connection(module) | ||
out = connection.edit_config(commands) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
#!/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/>. | ||
# | ||
|
||
from __future__ import absolute_import, division, print_function | ||
__metaclass__ = type | ||
|
||
|
||
ANSIBLE_METADATA = {'metadata_version': '1.1', | ||
'status': ['preview'], | ||
'supported_by': 'community'} | ||
|
||
|
||
DOCUMENTATION = """ | ||
--- | ||
module: exos_command | ||
version_added: "2.6" | ||
author: "Rafael D. Vencioneck (@rdvencioneck)" | ||
short_description: Run commands on remote devices running Extreme EXOS | ||
description: | ||
- Sends arbitrary commands to an Extreme EXOS device 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. | ||
- This module does not support running configuration commands. | ||
Please use M(exos_config) to configure EXOS devices. | ||
notes: | ||
- If a command sent to the device requires answering a prompt, it is possible | ||
to pass a dict containing I(command), I(answer) and I(prompt). See examples. | ||
options: | ||
commands: | ||
description: | ||
- List of commands to send to the remote EXOS 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 retries has expired. | ||
required: true | ||
wait_for: | ||
description: | ||
- List of conditions to evaluate against the output of the | ||
command. The task will wait for each condition to be true | ||
before moving forward. If the conditional is not true | ||
within the configured number of retries, the task fails. | ||
See examples. | ||
default: null | ||
match: | ||
description: | ||
- The I(match) argument is used in conjunction with the | ||
I(wait_for) argument to specify the match policy. Valid | ||
values are C(all) or C(any). If the value is set to C(all) | ||
then all conditionals in the wait_for must be satisfied. If | ||
the value is set to C(any) then only one of the values must be | ||
satisfied. | ||
default: all | ||
choices: ['any', 'all'] | ||
retries: | ||
description: | ||
- Specifies the number of retries a command should by tried | ||
before it is considered failed. The command is run on the | ||
target device every retry and evaluated against the | ||
I(wait_for) conditions. | ||
default: 10 | ||
interval: | ||
description: | ||
- Configures the interval in seconds to wait between retries | ||
of the command. If the command does not pass the specified | ||
conditions, the interval indicates how long to wait before | ||
trying the command again. | ||
default: 1 | ||
""" | ||
|
||
EXAMPLES = """ | ||
tasks: | ||
- name: run show version on remote devices | ||
exos_command: | ||
commands: show version | ||
- name: run show version and check to see if output contains ExtremeXOS | ||
exos_command: | ||
commands: show version | ||
wait_for: result[0] contains ExtremeXOS | ||
- name: run multiple commands on remote nodes | ||
exos_command: | ||
commands: | ||
- show version | ||
- show ports no-refresh | ||
- name: run multiple commands and evaluate the output | ||
exos_command: | ||
commands: | ||
- show version | ||
- show ports no-refresh | ||
wait_for: | ||
- result[0] contains ExtremeXOS | ||
- result[1] contains 20 | ||
- name: run command that requires answering a prompt | ||
exos_command: | ||
commands: | ||
- command: 'clear license-info' | ||
prompt: 'Are you sure.*' | ||
answer: 'Yes' | ||
""" | ||
|
||
RETURN = """ | ||
stdout: | ||
description: The set of responses from the commands | ||
returned: always apart from low level errors (such as action plugin) | ||
type: list | ||
sample: ['...', '...'] | ||
stdout_lines: | ||
description: The value of stdout split into a list | ||
returned: always apart from low level errors (such as action plugin) | ||
type: list | ||
sample: [['...', '...'], ['...'], ['...']] | ||
failed_conditions: | ||
description: The list of conditionals that have failed | ||
returned: failed | ||
type: list | ||
sample: ['...', '...'] | ||
""" | ||
import re | ||
import time | ||
|
||
from ansible.module_utils.network.exos.exos import run_commands | ||
from ansible.module_utils.basic import AnsibleModule | ||
from ansible.module_utils.network.common.utils import ComplexList | ||
from ansible.module_utils.network.common.parsing import Conditional | ||
from ansible.module_utils.six import string_types | ||
|
||
|
||
def to_lines(stdout): | ||
for item in stdout: | ||
if isinstance(item, string_types): | ||
item = str(item).split('\n') | ||
yield item | ||
|
||
|
||
def parse_commands(module, warnings): | ||
command = ComplexList(dict( | ||
command=dict(key=True), | ||
prompt=dict(), | ||
answer=dict() | ||
), module) | ||
commands = command(module.params['commands']) | ||
for item in list(commands): | ||
command_split = re.match(r'^(\w*)(.*)$', item['command']) | ||
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'] | ||
) | ||
commands.remove(item) | ||
elif command_split and command_split.group(1) not in ('check', 'clear', 'debug', 'history', | ||
'ls', 'mrinfo', 'mtrace', 'nslookup', | ||
'ping', 'rtlookup', 'show', 'traceroute'): | ||
module.fail_json( | ||
msg='some commands were not recognized. exos_command can only run read-only' | ||
'commands. For configuration commands, please use exos_config instead' | ||
) | ||
return commands | ||
|
||
|
||
def main(): | ||
"""main entry point for module execution | ||
""" | ||
argument_spec = dict( | ||
commands=dict(type='list', required=True), | ||
|
||
wait_for=dict(type='list'), | ||
match=dict(default='all', choices=['all', 'any']), | ||
|
||
retries=dict(default=10, type='int'), | ||
interval=dict(default=1, type='int') | ||
) | ||
|
||
module = AnsibleModule(argument_spec=argument_spec, | ||
supports_check_mode=True) | ||
|
||
result = {'changed': False} | ||
|
||
warnings = list() | ||
commands = parse_commands(module, warnings) | ||
result['warnings'] = warnings | ||
|
||
wait_for = module.params['wait_for'] or list() | ||
conditionals = [Conditional(c) for c in wait_for] | ||
|
||
retries = module.params['retries'] | ||
interval = module.params['interval'] | ||
match = module.params['match'] | ||
|
||
while retries > 0: | ||
responses = run_commands(module, commands) | ||
|
||
for item in list(conditionals): | ||
if item(responses): | ||
if match == 'any': | ||
conditionals = list() | ||
break | ||
conditionals.remove(item) | ||
|
||
if not conditionals: | ||
break | ||
|
||
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.update({ | ||
'changed': False, | ||
'stdout': responses, | ||
'stdout_lines': list(to_lines(responses)) | ||
}) | ||
|
||
module.exit_json(**result) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
Oops, something went wrong.