开发网络插件

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

网络连接插件

每个网络连接插件都有一组自己的插件,这些插件为特定设备集提供了连接规范。使用哪个特定插件是在运行时根据分配给主机的 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 (RFC 6241) 操作的网络设备使用 netconf 连接插件,例如 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 以获取要遵循的示例。