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.
New module aireos_command (ansible#26769)
* Adding ciscowlc_command module and unit tests. * Adding __init__.py for unit test. * Fixing PEP8 W503. * Renaming module from ciscowlc_command to aire_command. * Renaming aire_command to aireos_command.
- Loading branch information
Showing
13 changed files
with
931 additions
and
1 deletion.
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
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
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,125 @@ | ||
# 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. | ||
# | ||
from ansible.module_utils._text import to_text | ||
from ansible.module_utils.basic import env_fallback, return_values | ||
from ansible.module_utils.network_common import to_list, ComplexList | ||
from ansible.module_utils.connection import exec_command | ||
|
||
_DEVICE_CONFIGS = {} | ||
|
||
aireos_argument_spec = { | ||
'host': dict(), | ||
'port': dict(type='int'), | ||
'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), | ||
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True), | ||
'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'), | ||
'timeout': dict(type='int'), | ||
'provider': dict(type='dict') | ||
} | ||
|
||
# Add argument's default value here | ||
ARGS_DEFAULT_VALUE = { | ||
'timeout': 10 | ||
} | ||
|
||
|
||
def get_argspec(): | ||
return aireos_argument_spec | ||
|
||
|
||
def check_args(module, warnings): | ||
provider = module.params['provider'] or {} | ||
for key in aireos_argument_spec: | ||
if key not in ['provider', 'authorize'] and module.params[key]: | ||
warnings.append('argument %s has been deprecated and will be removed in a future version' % key) | ||
|
||
# set argument's default value if not provided in input | ||
# This is done to avoid unwanted argument deprecation warning | ||
# in case argument is not given as input (outside provider). | ||
for key in ARGS_DEFAULT_VALUE: | ||
if not module.params.get(key, None): | ||
module.params[key] = ARGS_DEFAULT_VALUE[key] | ||
|
||
if provider: | ||
for param in ('auth_pass', 'password'): | ||
if provider.get(param): | ||
module.no_log_values.update(return_values(provider[param])) | ||
|
||
|
||
def get_config(module, flags=[]): | ||
cmd = 'show run-config commands ' | ||
cmd += ' '.join(flags) | ||
cmd = cmd.strip() | ||
|
||
try: | ||
return _DEVICE_CONFIGS[cmd] | ||
except KeyError: | ||
rc, out, err = exec_command(module, cmd) | ||
if rc != 0: | ||
module.fail_json(msg='unable to retrieve current config', stderr=to_text(err, errors='surrogate_then_replace')) | ||
cfg = to_text(out, errors='surrogate_then_replace').strip() | ||
_DEVICE_CONFIGS[cmd] = cfg | ||
return cfg | ||
|
||
|
||
def to_commands(module, commands): | ||
spec = { | ||
'command': dict(key=True), | ||
'prompt': dict(), | ||
'answer': dict() | ||
} | ||
transform = ComplexList(spec, module) | ||
return transform(commands) | ||
|
||
|
||
def run_commands(module, commands, check_rc=True): | ||
responses = list() | ||
commands = to_commands(module, to_list(commands)) | ||
for cmd in commands: | ||
cmd = module.jsonify(cmd) | ||
rc, out, err = exec_command(module, cmd) | ||
if check_rc and rc != 0: | ||
module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), rc=rc) | ||
responses.append(to_text(out, errors='surrogate_then_replace')) | ||
return responses | ||
|
||
|
||
def load_config(module, commands): | ||
|
||
rc, out, err = exec_command(module, 'config') | ||
if rc != 0: | ||
module.fail_json(msg='unable to enter configuration mode', err=to_text(out, errors='surrogate_then_replace')) | ||
|
||
for command in to_list(commands): | ||
if command == 'end': | ||
continue | ||
rc, out, err = exec_command(module, command) | ||
if rc != 0: | ||
module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) | ||
|
||
exec_command(module, 'end') |
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,230 @@ | ||
#!/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 = {'metadata_version': '1.0', | ||
'status': ['preview'], | ||
'supported_by': 'community'} | ||
|
||
DOCUMENTATION = """ | ||
--- | ||
module: aireos_command | ||
version_added: "2.4" | ||
author: "James Mighion (@jmighion)" | ||
short_description: Run commands on remote devices running Cisco WLC | ||
description: | ||
- Sends arbitrary commands to an aireos 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. | ||
- This module does not support running commands in configuration mode. | ||
Please use M(aireos_config) to configure WLC devices. | ||
extends_documentation_fragment: aireos | ||
options: | ||
commands: | ||
description: | ||
- List of commands to send to the remote aireos 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. | ||
required: false | ||
default: null | ||
aliases: ['waitfor'] | ||
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. | ||
required: false | ||
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. | ||
required: false | ||
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. | ||
required: false | ||
default: 1 | ||
""" | ||
|
||
EXAMPLES = """ | ||
tasks: | ||
- name: run show sysinfo on remote devices | ||
aireos_command: | ||
commands: show sysinfo | ||
- name: run show sysinfo and check to see if output contains Cisco Controller | ||
aireos_command: | ||
commands: show sysinfo | ||
wait_for: result[0] contains 'Cisco Controller' | ||
- name: run multiple commands on remote nodes | ||
aireos_command: | ||
commands: | ||
- show sysinfo | ||
- show interface summary | ||
- name: run multiple commands and evaluate the output | ||
aireos_command: | ||
commands: | ||
- show sysinfo | ||
- show interface summary | ||
wait_for: | ||
- result[0] contains Cisco Controller | ||
- result[1] contains Loopback0 | ||
""" | ||
|
||
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 time | ||
|
||
from ansible.module_utils.aireos import run_commands | ||
from ansible.module_utils.aireos import aireos_argument_spec, check_args | ||
from ansible.module_utils.basic import AnsibleModule | ||
from ansible.module_utils.network_common import ComplexList | ||
from ansible.module_utils.netcli 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 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'] | ||
) | ||
elif item['command'].startswith('conf'): | ||
module.fail_json( | ||
msg='aireos_command does not support running config mode ' | ||
'commands. Please use aireos_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', aliases=['waitfor']), | ||
match=dict(default='all', choices=['all', 'any']), | ||
|
||
retries=dict(default=10, type='int'), | ||
interval=dict(default=1, type='int') | ||
) | ||
|
||
argument_spec.update(aireos_argument_spec) | ||
|
||
module = AnsibleModule(argument_spec=argument_spec, | ||
supports_check_mode=True) | ||
|
||
result = {'changed': False} | ||
|
||
warnings = list() | ||
check_args(module, warnings) | ||
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.