Разработка сетевых плагинов
Вы можете расширить существующие сетевые модули с помощью пользовательских плагинов в своей коллекции.
- Плагины сетевого подключения
- Разработка плагинов NETCONF
- Разработка плагинов network_cli
- Разработка плагинов cli_parser в коллекции
Плагины сетевого подключения
У каждого плагина сетевого подключения есть собственный набор плагинов, которые предоставляют спецификацию подключения для определённого набора устройств. Конкретный используемый плагин выбирается во время выполнения на основе значения переменной 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 служат адаптерами для различных API HTTP(S), используемых с плагином подключения 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 Basic. Если запрос может возвращать токен, заменяющий HTTP Basic, следует реализовать метод 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означает, что запрос можно повторить. Его можно использовать для обозначения временной ошибки или ошибки, которая уже устранена. Например, реализация по умолчанию пытается вызватьlogin()при получении ответа 401 и возвращаетtrueв случае успеха. - Значение
falseозначает, что плагин не может обработать этот ответ. Код состояния будет вызван как исключение в вызывающем модуле. - Любое другое значение будет считаться нефатальным ответом на запрос. Это может быть полезно, если сервер возвращает сообщения об ошибках в теле ответа. В этом случае обычно достаточно вернуть исходное исключение, поскольку объекты HTTPError имеют тот же интерфейс, что и успешный ответ.
Примеры плагинов httpapi см. в исходном коде плагинов httpapi, входящих в Ansible Core.
Разработка плагинов NETCONF
Плагин подключения netconf обеспечивает подключение к удалённым устройствам через подсистему SSH NETCONF. Сетевые устройства обычно используют этот плагин подключения для отправки и получения вызовов RPC через NETCONF.
Плагин подключения netconf использует библиотеку Python ncclient для установления сеанса NETCONF с удалённым сетевым устройством, поддерживающим NETCONF. ncclient также выполняет запросы NETCONF RPC и получает ответы. Необходимо установить ncclient на локальном управляющем узле Ansible.
Чтобы использовать плагин подключения netconf с сетевыми устройствами, поддерживающими стандартные операции NETCONF (RFC 6241), например get, get-config, edit-config, задайте ansible_network_os=default. Для работы с удалённым узлом, поддерживающим NETCONF, можно использовать модули netconf_get, netconf_config и netconf_rpc.
Как участник разработки и пользователь, вы должны иметь возможность использовать все методы класса NetconfBase, если ваше устройство поддерживает стандартный NETCONF. Вы можете внести новый плагин, если устройство, с которым вы работаете, использует специфичный для производителя вызов NETCONF RPC. Чтобы обеспечить поддержку такого вызова, добавьте его реализацию в плагин NETCONF для соответствующей сетевой ОС.
Например, для Junos:
- См. реализованные методы RPC Junos, специфичные для производителя, в
plugins/netconf/junos.py. - Задайте для
ansible_network_osимя файла плагина netconf — в данном случаеjunos.
Разработка плагинов network_cli
Тип подключения network_cli использует paramiko_ssh, который создаёт псевдотерминал для отправки команд и получения ответов.
Примечание
Плагин подключения на основе Paramiko устарел. Вместо него используйте плагин подключения libssh. Подробнее о переходе с Paramiko на libssh см. в статье Переход на ssh_type libssh для ansible.netcommon.network_cli в сетевых коллекциях Ansible.
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 following 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_organization.my_collection.custom_parser
Чтобы разработать пользовательский плагин: - Каждый плагин cli_parser должен содержать класс CliParser. - Каждый плагин cli_parser должен содержать функцию parse. - Всегда возвращайте словарь с errors или parsed. - Поместите пользовательский cli_parser в каталог plugins/cli_parsers коллекции. - Примеры для ориентира см. в текущих cli_parsers.
© 2012–2018 Michael DeHaan
© 2018–2025 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/projects/ansible/latest/network/dev_guide/developing_plugins_network.html