208 lines
6.3 KiB
Python
208 lines
6.3 KiB
Python
# (c) 2018 Red Hat Inc.
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
DOCUMENTATION = """
|
|
author: Ansible Networking Team
|
|
httpapi: eos
|
|
short_description: Use eAPI to run command on eos platform
|
|
description:
|
|
- This eos plugin provides low level abstraction api's for sending and receiving CLI
|
|
commands with eos network devices.
|
|
version_added: 1.0.0
|
|
options:
|
|
eos_use_sessions:
|
|
type: int
|
|
default: 1
|
|
description:
|
|
- Specifies if sessions should be used on remote host or not
|
|
env:
|
|
- name: ANSIBLE_EOS_USE_SESSIONS
|
|
vars:
|
|
- name: ansible_eos_use_sessions
|
|
"""
|
|
|
|
import json
|
|
import time
|
|
|
|
from ansible.module_utils._text import to_text
|
|
from ansible.module_utils.connection import ConnectionError
|
|
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
|
|
to_list,
|
|
)
|
|
from ansible.plugins.httpapi import HttpApiBase
|
|
|
|
|
|
OPTIONS = {
|
|
"format": ["text", "json"],
|
|
"diff_match": ["line", "strict", "exact", "none"],
|
|
"diff_replace": ["line", "block", "config"],
|
|
"output": ["text", "json"],
|
|
}
|
|
|
|
|
|
class HttpApi(HttpApiBase):
|
|
def __init__(self, *args, **kwargs):
|
|
super(HttpApi, self).__init__(*args, **kwargs)
|
|
self._device_info = None
|
|
self._session_support = None
|
|
|
|
def supports_sessions(self):
|
|
use_session = self.get_option("eos_use_sessions")
|
|
try:
|
|
use_session = int(use_session)
|
|
except ValueError:
|
|
pass
|
|
|
|
if not bool(use_session):
|
|
self._session_support = False
|
|
else:
|
|
if self._session_support:
|
|
return self._session_support
|
|
|
|
response = self.send_request("show configuration sessions")
|
|
self._session_support = "error" not in response
|
|
|
|
return self._session_support
|
|
|
|
def send_request(self, data, **message_kwargs):
|
|
data = to_list(data)
|
|
become = self._become
|
|
if become:
|
|
self.connection.queue_message("vvvv", "firing event: on_become")
|
|
data.insert(0, {"cmd": "enable", "input": self._become_pass})
|
|
|
|
output = message_kwargs.get("output") or "text"
|
|
request = request_builder(data, output)
|
|
headers = {"Content-Type": "application/json-rpc"}
|
|
|
|
_response, response_data = self.connection.send(
|
|
"/command-api", request, headers=headers, method="POST"
|
|
)
|
|
|
|
try:
|
|
response_data = json.loads(to_text(response_data.getvalue()))
|
|
except ValueError:
|
|
raise ConnectionError(
|
|
"Response was not valid JSON, got {0}".format(
|
|
to_text(response_data.getvalue())
|
|
)
|
|
)
|
|
|
|
results = handle_response(response_data)
|
|
|
|
if become:
|
|
results = results[1:]
|
|
if len(results) == 1:
|
|
results = results[0]
|
|
|
|
return results
|
|
|
|
def get_device_info(self):
|
|
if self._device_info:
|
|
return self._device_info
|
|
|
|
device_info = {}
|
|
|
|
device_info["network_os"] = "eos"
|
|
reply = self.send_request("show version", output="json")
|
|
data = json.loads(reply)
|
|
|
|
device_info["network_os_version"] = data["version"]
|
|
device_info["network_os_model"] = data["modelName"]
|
|
|
|
reply = self.send_request("show hostname", output="json")
|
|
data = json.loads(reply)
|
|
|
|
device_info["network_os_hostname"] = data["hostname"]
|
|
|
|
self._device_info = device_info
|
|
return self._device_info
|
|
|
|
def get_device_operations(self):
|
|
return {
|
|
"supports_diff_replace": True,
|
|
"supports_commit": bool(self.supports_sessions()),
|
|
"supports_rollback": False,
|
|
"supports_defaults": False,
|
|
"supports_onbox_diff": bool(self.supports_sessions()),
|
|
"supports_commit_comment": False,
|
|
"supports_multiline_delimiter": False,
|
|
"supports_diff_match": True,
|
|
"supports_diff_ignore_lines": True,
|
|
"supports_generate_diff": not bool(self.supports_sessions()),
|
|
"supports_replace": bool(self.supports_sessions()),
|
|
}
|
|
|
|
def get_capabilities(self):
|
|
result = {}
|
|
result["rpc"] = []
|
|
result["device_info"] = self.get_device_info()
|
|
result["device_operations"] = self.get_device_operations()
|
|
result.update(OPTIONS)
|
|
result["network_api"] = "eapi"
|
|
|
|
return json.dumps(result)
|
|
|
|
# Shims for resource module support
|
|
def get(self, command, output=None):
|
|
# This method is ONLY here to support resource modules. Therefore most
|
|
# arguments are unsupported and not present.
|
|
|
|
return self.send_request(data=command, output=output)
|
|
|
|
def edit_config(self, candidate):
|
|
# This method is ONLY here to support resource modules. Therefore most
|
|
# arguments are unsupported and not present.
|
|
|
|
session = None
|
|
if self.supports_sessions():
|
|
session = "ansible_%d" % int(time.time())
|
|
candidate = ["configure session %s" % session] + candidate
|
|
else:
|
|
candidate = ["configure"] + candidate
|
|
candidate.append("commit")
|
|
|
|
try:
|
|
responses = self.send_request(candidate)
|
|
except ConnectionError:
|
|
if session:
|
|
self.send_request(["configure session %s" % session, "abort"])
|
|
raise
|
|
|
|
return [resp for resp in to_list(responses) if resp != "{}"]
|
|
|
|
|
|
def handle_response(response):
|
|
if "error" in response:
|
|
error = response["error"]
|
|
|
|
error_text = []
|
|
for data in error.get("data", []):
|
|
error_text.extend(data.get("errors", []))
|
|
error_text = "\n".join(error_text) or error["message"]
|
|
|
|
raise ConnectionError(error_text, code=error["code"])
|
|
|
|
results = []
|
|
|
|
for result in response["result"]:
|
|
if "messages" in result:
|
|
results.append(result["messages"][0])
|
|
elif "output" in result:
|
|
results.append(result["output"].strip())
|
|
else:
|
|
results.append(json.dumps(result))
|
|
|
|
return results
|
|
|
|
|
|
def request_builder(commands, output, reqid=None):
|
|
params = dict(version=1, cmds=commands, format=output)
|
|
return json.dumps(
|
|
dict(jsonrpc="2.0", id=reqid, method="runCmds", params=params)
|
|
)
|