开发网络插件
您可以在您的集合中使用自定义插件扩展现有的网络模块。
网络连接插件
每个网络连接插件都有一组自己的插件,这些插件提供特定设备集连接的规范。根据分配给主机的 ansible_network_os
变量的值在运行时选择要使用的特定插件。此变量应设置为与要加载的插件名称相同的值。因此,ansible_network_os=nxos
将尝试加载名为 nxos.py
的文件中的插件,因此以对用户有意义的方式命名插件非常重要。
这些插件的公共方法可以从模块或 module_utils 中使用连接代理对象调用,就像其他连接方法一样。以下是在 module_utils 文件中使用此类调用的一个非常简单的示例,以便可以与其他模块共享。
from ansible.module_utils.connection import Connection
def get_config(module):
# module is your AnsibleModule instance.
connection = Connection(module._socket_path)
# You can now call any method (that doesn't start with '_') of the connection
# plugin or its platform-specific plugin
return connection.get_config()
开发 httpapi 插件
httpapi 插件 充当各种 HTTP(S) API 的适配器,供 httpapi
连接插件使用。它们应该实现一组最小的便利方法,这些方法适合您尝试使用的 API。
具体来说,httpapi
连接插件期望存在一些方法。
发出请求
httpapi
连接插件有一个 send()
方法,但 httpapi 插件需要一个 send_request(self, data, **message_kwargs)
方法作为 send()
的更高级别的包装器。此方法应通过添加固定值(如通用标头或 URL 根路径)来准备请求。此方法可以执行更复杂的工作,例如将数据转换为格式化的有效负载,或确定要请求的路径或方法。然后,它还可以解压缩响应,以便调用方更容易使用。
from ansible.module_utils.six.moves.urllib.error import HTTPError
def send_request(self, data, path, method='POST'):
# Fixed headers for requests
headers = {'Content-Type': 'application/json'}
try:
response, response_content = self.connection.send(path, data, method=method, headers=headers)
except HTTPError as exc:
return exc.code, exc.read()
# handle_response (defined separately) will take the format returned by the device
# and transform it into something more suitable for use by modules.
# This may be JSON text to Python dictionaries, for example.
return handle_response(response_content)
身份验证
默认情况下,所有请求都将使用 HTTP 基本身份验证进行身份验证。如果请求可以返回某种令牌来代替 HTTP 基本身份验证,则应实现 update_auth(self, response, response_text)
方法来检查此类令牌的响应。如果令牌旨在包含在每个请求的标头中,则返回一个字典就足够了,该字典将与每个请求的计算标头合并。此方法的默认实现正是为 cookie 执行此操作。如果令牌以其他方式使用,例如在查询字符串中,则应将该令牌保存到实例变量,其中 send_request()
方法(如上所述)可以将其添加到每个请求中。
def update_auth(self, response, response_text):
cookie = response.info().get('Set-Cookie')
if cookie:
return {'Cookie': cookie}
return None
如果需要请求显式登录端点以接收身份验证令牌,则可以实现 login(self, username, password)
方法来调用该端点。如果实现,此方法将在请求服务器的任何其他资源之前调用一次。默认情况下,在从请求返回 HTTP 401 时,也会尝试一次。
def login(self, username, password):
login_path = '/my/login/path'
data = {'user': username, 'password': password}
response = self.send_request(data, path=login_path)
try:
# This is still sent as an HTTP header, so we can set our connection's _auth
# variable manually. If the token is returned to the device in another way,
# you will have to keep track of it another way and make sure that it is sent
# with the rest of the request from send_request()
self.connection._auth = {'X-api-token': response['token']}
except KeyError:
raise AnsibleAuthenticationFailure(message="Failed to acquire login token.")
同样,可以实现 logout(self)
来调用端点以使当前令牌失效和/或释放当前令牌,如果存在此类端点。连接关闭时(以及在重置时)会自动调用此方法。
def logout(self):
logout_path = '/my/logout/path'
self.send_request(None, path=logout_path)
# Clean up tokens
self.connection._auth = None
错误处理
handle_httperror(self, exception)
方法可以处理服务器返回的状态代码。返回值指示插件将如何继续请求。
值为
true
表示可以重试请求。这可能用于指示瞬态错误或已解决的错误。例如,默认实现将在出现 401 时尝试调用login()
,如果成功则返回true
。值为
false
表示插件无法从此响应中恢复。状态代码将作为异常引发给调用模块。任何其他值都将被视为来自请求的非致命响应。如果服务器在响应正文中返回错误消息,这可能很有用。在这种情况下,返回原始异常通常就足够了,因为 HTTPError 对象与成功响应具有相同的接口。
例如 httpapi 插件,请参阅 Ansible Core 附带的 httpapi 插件的源代码。
开发 NETCONF 插件
netconf 连接插件通过 SSH NETCONF
子系统提供到远程设备的连接。网络设备通常使用此连接插件通过 NETCONF
发送和接收 RPC
调用。
netconf
连接插件在后台使用 ncclient
Python 库来与支持 NETCONF 的远程网络设备建立 NETCONF 会话。ncclient
还执行 NETCONF RPC 请求并接收响应。您必须在本地 Ansible 控制节点上安装 ncclient
。
要将 netconf
连接插件用于支持标准 NETCONF(RFC 6241)操作(如 get
、get-config
、edit-config
)的网络设备,请设置 ansible_network_os=default
。您可以使用 netconf_get、netconf_config 和 netconf_rpc 模块与支持 NETCONF 的远程主机通信。
作为贡献者和用户,如果您的设备支持标准 NETCONF,您应该能够使用 NetconfBase
类下的所有方法。如果您正在使用的设备具有供应商特定的 NETCONF RPC,则可以贡献一个新插件。要支持供应商特定的 NETCONF RPC,请在网络操作系统特定的 NETCONF 插件中添加实现。
例如,对于 Junos
请参阅
plugins/netconf/junos.py
中实现的供应商特定的 Junos RPC 方法。将
ansible_network_os
的值设置为 netconf 插件文件的名称,在本例中为junos
。
开发 network_cli 插件
network_cli 连接类型在后台使用 paramiko_ssh
,它创建了一个伪终端来发送命令并接收响应。network_cli
根据 ansible_network_os
的值加载两个平台特定的插件。
终端插件(例如
plugins/terminal/ios.py
) - 控制与终端相关的参数,例如设置终端长度和宽度、禁用页面和权限提升。还定义正则表达式以识别命令提示符和错误提示符。Cliconf 插件(例如,ios cliconf) - 提供了一个用于底层发送和接收操作的抽象层。例如,
edit_config()
方法确保在执行配置命令之前提示符处于config
模式。
要为 network_cli
连接添加新的网络操作系统,请为该网络操作系统实现 cliconf
和 terminal
插件。
插件可以位于
剧本旁边的文件夹中
cliconf_plugins/ terminal_plugins/
角色
myrole/cliconf_plugins/ myrole/terminal_plugins/
集合
myorg/mycollection/plugins/terminal/ myorg/mycollection/plugins/cliconf/
用户还可以设置 DEFAULT_CLICONF_PLUGIN_PATH 来配置 cliconf
插件路径。
在将 cliconf
和 terminal
插件添加到预期位置后,用户可以
使用 cli_command 在网络设备上运行任意命令。
使用 cli_config 在远程主机上实现配置更改,而无需特定于平台的模块。
在集合中开发 cli_parser 插件
您可以使用 cli_parse
作为您自己的集合中 cli_parser 插件的入口点。
以下示例显示了自定义 cli_parser 插件的开头
from ansible_collections.ansible.netcommon.plugins.module_utils.cli_parser.cli_parserbase import (
CliParserBase,
)
class CliParser(CliParserBase):
""" Sample cli_parser plugin
"""
# Use the follow extension when loading a template
DEFAULT_TEMPLATE_EXTENSION = "txt"
# Provide the contents of the template to the parse function
PROVIDE_TEMPLATE_CONTENTS = True
def myparser(text, template_contents):
# parse the text using the template contents
return {...}
def parse(self, *_args, **kwargs):
""" Standard entry point for a cli_parse parse execution
:return: Errors or parsed text as structured data
:rtype: dict
:example:
The parse function of a parser should return a dict:
{"errors": [a list of errors]}
or
{"parsed": obj}
"""
template_contents = kwargs["template_contents"]
text = self._task_args.get("text")
try:
parsed = myparser(text, template_contents)
except Exception as exc:
msg = "Custom parser returned an error while parsing. Error: {err}"
return {"errors": [msg.format(err=to_native(exc))]}
return {"parsed": parsed}
以下任务使用此自定义 cli_parser 插件
- name: Use a custom cli_parser
ansible.netcommon.cli_parse:
command: ls -l
parser:
name: my_organiztion.my_collection.custom_parser
要开发自定义插件: - 每个 cli_parser 插件都需要一个 CliParser
类。 - 每个 cli_parser 插件都需要一个 parse
函数。 - 始终返回一个包含 errors
或 parsed
的字典。 - 将自定义 cli_parser 放置在集合的 plugins/cli_parsers 目录中。 - 请参阅 当前的 cli_parsers 以获取要遵循的示例。
另请参阅