开发网络插件

您可以使用集合中的自定义插件来扩展现有的网络模块。

网络连接插件

每个网络连接插件都有一组自己的插件,这些插件为特定的设备集提供连接规范。使用的特定插件在运行时基于分配给主机的 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)操作(例如 getget-configedit-config)的网络设备,请设置 ansible_network_os=default。您可以使用 netconf_getnetconf_confignetconf_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 连接配合使用的新网络操作系统做出贡献,请为该网络操作系统实现 cliconfterminal 插件。

这些插件可以位于

  • 与 playbook 相邻的文件夹中

    cliconf_plugins/
    terminal_plugins/
    
  • 角色 (Roles) 中

    myrole/cliconf_plugins/
    myrole/terminal_plugins/
    
  • 集合 (Collections) 中

    myorg/mycollection/plugins/terminal/
    myorg/mycollection/plugins/cliconf/
    

用户还可以设置 DEFAULT_CLICONF_PLUGIN_PATH 来配置 cliconf 插件路径。

在预期位置添加 cliconfterminal 插件后,用户可以

  • 使用 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 函数。 - 始终返回一个包含 errorsparsed 的字典。 - 将自定义 cli_parser 放在集合的 plugins/cli_parsers 目录中。 - 请参阅当前的 cli_parsers 获取示例以供参考。