175 lines
5.7 KiB
Python
175 lines
5.7 KiB
Python
# (c) 2018, Ansible by Red Hat, inc
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
#
|
|
# 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': 'network'}
|
|
|
|
DOCUMENTATION = """
|
|
---
|
|
module: cli
|
|
author: Peter Sprygada (@privateip)
|
|
short_description: Runs the specific command and returns the output
|
|
description:
|
|
- The command specified in C(command) will be executed on the remote
|
|
device and its output will be returned to the module. This module
|
|
requires that the device is supported using the C(network_cli)
|
|
connection plugin and has a valid C(cliconf) plugin to work correctly.
|
|
version_added: "2.5"
|
|
options:
|
|
command:
|
|
description:
|
|
- The command to be executed on the remote node. The value for this
|
|
argument will be passed unchanged to the network device and the
|
|
output returned.
|
|
required: yes
|
|
default: null
|
|
parser:
|
|
description:
|
|
- The parser file to pass the output from the command through to
|
|
generate Ansible facts. If this argument is specified, the output
|
|
from the command will be parsed based on the rules in the
|
|
specified parser.
|
|
default: null
|
|
engine:
|
|
description:
|
|
- Defines the engine to use when parsing the output. This argument
|
|
accepts one of two valid values, C(command_parser) or C(textfsm_parser).
|
|
default: command_parser
|
|
choices:
|
|
- command_parser
|
|
- textfsm_parser
|
|
name:
|
|
description:
|
|
- The C(name) argument is used to define the top-level fact name to
|
|
hold the output of textfsm_engine parser. If this argument is not provided,
|
|
the output from parsing will not be exported. Note that this argument is
|
|
only considered when C(engine) is C(textfsm_parser).
|
|
default: null
|
|
"""
|
|
|
|
EXAMPLES = """
|
|
- name: return show version
|
|
cli:
|
|
command: show version
|
|
|
|
- name: return parsed command output
|
|
cli:
|
|
command: show version
|
|
parser: parser_templates/show_version.yaml
|
|
|
|
- name: parse with textfsm_parser engine
|
|
cli:
|
|
command: show version
|
|
parser: parser_templates/show_version
|
|
engine: textfsm_parser
|
|
name: system_facts
|
|
"""
|
|
|
|
RETURN = """
|
|
stdout:
|
|
description: returns the output from the command
|
|
returned: always
|
|
type: dict
|
|
json:
|
|
description: the output converted from json to a hash
|
|
returned: always
|
|
type: dict
|
|
"""
|
|
|
|
import json
|
|
|
|
from ansible.plugins.action import ActionBase
|
|
from ansible.module_utils.connection import Connection, ConnectionError
|
|
from ansible.module_utils._text import to_text
|
|
from ansible.errors import AnsibleError
|
|
from ansible.utils.display import Display
|
|
|
|
display = Display()
|
|
|
|
|
|
class ActionModule(ActionBase):
|
|
|
|
def run(self, tmp=None, task_vars=None):
|
|
''' handler for cli operations '''
|
|
|
|
if task_vars is None:
|
|
task_vars = dict()
|
|
|
|
result = super(ActionModule, self).run(tmp, task_vars)
|
|
del tmp # tmp no longer has any effect
|
|
|
|
try:
|
|
command = self._task.args['command']
|
|
parser = self._task.args.get('parser')
|
|
engine = self._task.args.get('engine', 'command_parser')
|
|
if engine == 'textfsm_parser':
|
|
name = self._task.args.get('name')
|
|
elif engine == 'command_parser' and self._task.args.get('name'):
|
|
display.warning('name is unnecessary when using command_parser and will be ignored')
|
|
del self._task.args['name']
|
|
except KeyError as exc:
|
|
raise AnsibleError(to_text(exc))
|
|
|
|
socket_path = getattr(self._connection, 'socket_path') or task_vars.get('ansible_socket')
|
|
connection = Connection(socket_path)
|
|
|
|
# make command a required argument
|
|
if not command:
|
|
raise AnsibleError('missing required argument `command`')
|
|
|
|
try:
|
|
output = connection.get(command)
|
|
except ConnectionError as exc:
|
|
raise AnsibleError(to_text(exc))
|
|
|
|
result['stdout'] = output
|
|
|
|
# try to convert the cli output to native json
|
|
try:
|
|
json_data = json.loads(output)
|
|
except Exception:
|
|
json_data = None
|
|
|
|
result['json'] = json_data
|
|
|
|
if parser:
|
|
if engine not in ('command_parser', 'textfsm_parser'):
|
|
raise AnsibleError('missing or invalid value for argument engine')
|
|
|
|
new_task = self._task.copy()
|
|
new_task.args = {
|
|
'file': parser,
|
|
'content': (json_data or output)
|
|
}
|
|
if engine == 'textfsm_parser':
|
|
new_task.args.update({'name': name})
|
|
|
|
kwargs = {
|
|
'task': new_task,
|
|
'connection': self._connection,
|
|
'play_context': self._play_context,
|
|
'loader': self._loader,
|
|
'templar': self._templar,
|
|
'shared_loader_obj': self._shared_loader_obj
|
|
}
|
|
|
|
task_parser = self._shared_loader_obj.action_loader.get(engine, **kwargs)
|
|
result.update(task_parser.run(task_vars=task_vars))
|
|
|
|
self._remove_tmp_path(self._connection._shell.tmpdir)
|
|
|
|
# this is needed so the strategy plugin can identify the connection as
|
|
# a persistent connection and track it, otherwise the connection will
|
|
# not be closed at the end of the play
|
|
socket_path = getattr(self._connection, 'socket_path') or task_vars.get('ansible_socket')
|
|
self._task.args['_ansible_socket'] = socket_path
|
|
|
|
return result
|