Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding exos_command cli_conf module #37775

Merged
merged 5 commits into from
Apr 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/BOTMETA.yml
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ files:
$modules/network/edgeos/: samdoran
$modules/network/enos/: amuraleedhar
$modules/network/eos/: privateip trishnaguha
$modules/network/exos/: rdvencioneck
$modules/network/f5/:
ignored: Etienne-Carriere mhite mryanlam perzizzle srvg wojtek0806 JoeReifel
maintainers: caphrim007
Expand Down Expand Up @@ -819,6 +820,9 @@ files:
$module_utils/network/enos:
maintainers: amuraleedhar
labels: networking
$module_utils/network/exos:
maintainers: rdvencioneck
labels: networking
$module_utils/f5_utils.py:
maintainers: caphrim007
labels:
Expand Down Expand Up @@ -938,6 +942,9 @@ files:
lib/ansible/plugins/cliconf/:
maintainers: $team_networking
labels: networking
lib/ansible/plugins/cliconf/exos.py:
maintainers: rdvencioneck
labels: networking
lib/ansible/plugins/cliconf/ironware.py:
maintainers: paulquack
labels: networking
Expand Down Expand Up @@ -1019,6 +1026,9 @@ files:
lib/ansible/plugins/terminal/eos.py:
maintainers: $team_networking
labels: networking
lib/ansible/plugins/terminal/exos.py:
maintainers: rdvencioneck
labels: networking
lib/ansible/plugins/terminal/ios.py:
maintainers: $team_networking
labels: networking
Expand Down
Empty file.
101 changes: 101 additions & 0 deletions lib/ansible/module_utils/network/exos/exos.py
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.
236 changes: 236 additions & 0 deletions lib/ansible/modules/network/exos/exos_command.py
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()