开发网络插件

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

网络连接插件

每个网络连接插件都有一组自己的插件,这些插件提供特定设备集连接的规范。根据分配给主机的 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 插件。

插件可以位于

  • 剧本旁边的文件夹中

    cliconf_plugins/
    terminal_plugins/
    
  • 角色

    myrole/cliconf_plugins/
    myrole/terminal_plugins/
    
  • 集合

    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 以获取要遵循的示例。