Разработка плагинов для сети
Вы можете расширить существующие сетевые модули с помощью пользовательских плагинов в вашей коллекции.
- Плагины подключения к сети
- Разработка плагинов NETCONF
- Разработка плагинов network_cli
- Разработка плагинов cli_parser в коллекции
Плагины подключения к сети
Каждый плагин подключения к сети имеет набор собственных плагинов, которые предоставляют спецификацию подключения для определённого набора устройств. Конкретный используемый плагин выбирается во время выполнения на основе значения переменной ansible_network_os , назначенной хосту. Эта переменная должна быть установлена в значение, совпадающее с именем загружаемого плагина. Таким образом, ansible_network_os=nxos будет пытаться загрузить плагин в файле с именем nxos.py, поэтому важно назвать плагин понятным для пользователей образом.
Общедоступные методы этих плагинов могут вызываться из модуля или модуля_utils с помощью объекта прокси-соединения, как и другие методы подключения. Ниже приведён очень простой пример использования такого вызова в файле модуля_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_get, netconf_config и netconf_rpc для взаимодействия с удалённым хостом, поддерживающим NETCONF.
В качестве участника и пользователя вы должны иметь возможность использовать все методы в классе NetconfBase , если ваше устройство поддерживает стандартный NETCONF. Вы можете внести вклад в новый плагин, если устройство, с которым вы работаете, имеет специфические для поставщика RPC NETCONF. Для поддержки специфических для поставщика RPC NETCONF добавьте реализацию в сетевую ОС, специфичный плагин NETCONF.
Например, для Junos:
- См. методы RPC Junos, специфичные для поставщика, реализованные в
plugins/netconf/junos.py. - Установите значение
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 extention 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 для примеров.
© 2012–2018 Michael DeHaan
© 2018–2021 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/ansible/2.11/network/dev_guide/developing_plugins_network.html