# (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 . # 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