В последнее время я всё чаще и чаще сталкиваюсь с тем, что Ansible используют таким образом, что этот комок костылей и граблей просто нереально разгрести адекватному человеку без употребления фенибута или чего-либо мощнее. Одним из таких костылей является запуск ансибл в скрипте, чтобы предварительно сгенерировать inventory. Мне искренне непонятно, почему люди не читают документацию, а если и читают, то умудряются написать гигантский костыль в виде скрипта запуска, но не могут разобраться с тем, чтобы написать динамический инвенторий. Это побудило меня написать такой маленький туториал по реализации динамического инвентория практически для любой версии Ansible. В качестве языка я выбрал Python только потому, что его будет проще всего прочитать и адаптировать под любой другой язык. Те, кому лень читать всю портянку текста к коду, можете просто зайти на подготовленный gist либо прочитать итоговый скрипт (не забудьте сделать его исполняемым): Итак, чтобы можно было легко и непринуждённо писать свои собственные скрипты на любом ЯП, лучшим началом будет разобраться в теории. Когда вы запускаете любой из cli инструментов ansible, он автоматически оборачивает указанный вами инвенторий в менеджер, который занимается непосредственным парсингом. Менеджер, в свою очередь, подгружает конфигурацию из ansible.cfg (которая также проходит несколько этапов парсинга, но это сейчас не так важно). В этой конфигурации существует настройка со списком плагинов по умолчанию, которые будут использованы для парсинга инвентория. Вот как выглядит эта настройка "из коробки": Как мы видим, они расположены по порядку: сперва мы пытаемся спарсить хосты написанные через запятую, потом пытаемся выполнить скрипт, а после включается некоторый автоплагин, который каким-то магическим образом парсит все остальные типы в разных форматах, которые даже здесь не указаны. На самом деле этот плагин сложнее, чем кажется. Он считывает всеми возможными способами файл, потом читает в нём параметр plugin и уже на базе него начинает парсинг. Это удобно, когда сам инвенторий является параметром настройки для плагина инвентория. Современный ансибл расширен понятием коллекции, которые ещё больше расширяют возможности по добавлению функционала, так что это самый важный плагин. Если этот порядок нарушить, то какой-нибудь плагин инвентория может так и не заработать, потому что, например, файл будет в формате yaml и считается этим плагином вполне себе валидно. Когда менеджер начинает парсить файл, то он автоматически смотрит на то, что запускает (директория или файл), и последовательно начинает применять плагины. Первый успешно считавший является валидным. Именно поэтому они стоят в таком порядке. Теперь давайте разберёмся, как работает плагин, позволяющий без написания собственных плагинов реализовать скрипт, который будет брать нужную нам информацию откуда угодно и превращать в инвенторий. Плагин считает файл подходящим для него по следующим критериям: Файл начинается с #! У файла для текущего пользователя есть права на исполнение. Поэтому, если вы кладёте бинарь в качестве инвентория, нужно озаботиться, чтобы он соответствовал всем критериям. Пару раз натыкался на то, что файл имел нужные разрешения, но не для текущего пользователя. Принцип работы этого плагина заключается в том, чтобы запустить скрипт с параметром --list, получить вывод из пайпа, спарсить его загрузчиком в словарь и положить данные в объект инвентория. Затем для каждого хоста инвентория скрипт вызывается с параметром --host имя_хоста, чтобы получить переменные данного хоста. Насчёт последнего есть нюанс, который я расскажу чуть позже. В этом вся суть плагина. Ничего сложного. Осталось только разобраться с тем, что должно попасть на выход для каждого из вызовов. Итак, теперь последовательно разберёмся с тем, что мы должны получить на выходе. Мы будем идти по порядку непосредственного написания кода (т.е. так, как нужно писать такой плагин, чтоб не запутаться). Прежде всего нам нужно убедиться, что наш скрипт реагирует на два типа аргументов, которые требуются плагину. Если говорить за Python, то тут уже есть готовый инструмент "из батареек" под названием argparse. Я подробно разбирать сам модуль и инструмент не буду, потому что там довольно много гибких настроек. Но нам важны две вещи. Во-первых, на вызов --list нужно реагировать без разбора остальных параметров, по большому счёту. Важно именно наличие этого параметра. Не ругайтесь на пример скрипта, потому что моя цель была показать именно это. Лучшим вариантом будет использование action="store_true", который обозначит, что вызов произошёл с этим аргументом. Такой вызов должен содержать данные по следующей схеме (вывод в json): Чтобы правильно описывать группы, нам достаточно посмотреть на yaml плагин, потому что логика практически полностью совместима. Но всё же я уточню. Прежде всего, полученный json парсится в словарь, в котором описаны все группы (ключ - имя группы, значение - данные). Каждая группа в себе может содержать следующие параметры: vars - набор переменных для хостов конкретной группы. Опять же простой набор: ключ - имя переменной, значение - данные. hosts - список (именно list) имён хостов, содержащихся в группе. Не совместим со следующим параметром. Каждое новое имя создаёт новый хост в инвентории. children - список имён дочерних групп для вложенности. Не совместим с предыдущим пунктом. Поэтому нужно выбирать: либо у вас составная группа, либо группа хостов. Представленный список имён должен быть объявлен также в json объекте. Однако, в коде плагина присутствует очень не очевидный хак, который может запутать. Если данные группы это НЕ объект, то данные оборачиваются в объект с ключом hosts, в который в итоге попадают данные. Это значит, что если в теле объекта будет: {"all": ["host1", "host2"]}, то мы просто получим группу all с хостами. Это вроде должно упростить код в некоторых случаях, но также может и запутать. В моём примере скрипта, я намерено не использую этот хак, потому что считаю его антипаттерном. Если данные группы не содержат в себе ничего из перечисленных в прошлом списке параметров, то группа автоматически становится одноимённой с хостом, а данные - переменными группы хоста. Вроде как это тоже должно упростить в некоторых случаях код, но на деле является антипаттерном, потому как больше путает, чем даёт возможностей. Единственным явным исключением в парсинге групп, является _meta, которая содержит в себе структуру hostvars. Это не группа, а специальное хранилище метаданных. По умолчанию, hostvars будет None, если не указать их в _meta и парсинг инвентория будет нуждаться в ещё одном запуске. Поэтому, если есть возможность указать переменные хоста при этом наборе данных, то лучше это сделать сразу в hostvars. Ключ в hostvars - это имя хоста, а значение - объект с переменными. Во-вторых, если мы не объявили hostvars в нашем выводе, то нам потребуется реализовать вызов с аргументами --host hostname. Хочу отметить, что просмотрев код плагина для версии ansible 2.9, я убедился, что при наличии hostvars, не требуется реализация текущего вызова совсем. Однако, если мы всё же не указали переменные в первом вызове, то нам необходимо взять параметр хоста и вывести в стандартный вывод json объект с переменными. В целом я не знаю кейсов, в которых это бы было действительно полезно (разве что для разделения кода и при нехватке памяти на вызов). Но возможность есть, поэтому было бы неправильно её не упомянуть. В моём примере переменные пустые, потому что я реализовал более простой и быстрый вариант. Если бы мой вывод не умещался бы в память, то, наверное, следовало бы его разделить. Как уже можно заметить, в качестве хранилища может быть что угодно. Например, база данных (не забудьте, что где-то надо будет указать параметры подключения, поэтому и существуют плагины), cobbler, OpenStack или другое облако. Важно, чтобы возвращаемый набор переменных в инвентории содержал все необходимые параметры для подключения к описанному хосту от места запуска данного скрипта. В гитхабе ansible есть несколько полезных примеров, чтобы создать собственный скрипт. Но они действительно сложно написаны, что и побудило меня написать данную статью. Здесь можно посмотреть на другие примеры, которые могут помочь глубже понять интеграцию, а где-то вдохновиться на написание своего скрипта. Запуск скрипта производится точно таким же образом, как и файлового инвентория. Как я уже сказал, важно, чтобы он начинался на #!, что в linux системах интерпретируется как вызываемый скрипт, и содержал в fs acl права на исполнение от текущего пользователя. Интерпретатор, от которого происходит исполнение, не имеет значения. Вы можете указать его в том же окружении, что и вызываемый ansible или в совершенно другом окружении, в котором будет необходимый набор библиотек. Подведём итог. Мы рассмотрели то, как устроен ansible-плагин для динамических инвенториев из скрипта, в котором по факту нужно реализовать только один вызов: script_name --list. При должной сноровке и автоматизации других процессов можно легко управлять инфраструктурой как кодом в любой среде. Это открывает такой набор возможностей, который избавляет от рутины генерации "портянок" YAML-ей из разных систем. При этом такой скрипт будет работать практически везде и не зависеть от версии ansible. Скрипт может быть написан на любом языке, в том числе на bash, ruby, js или любом другом. Он может быть простой обёрткой для вызова бинаря, который будет выводить все данные. Всем автоматизации рутины! https://docs.ansible.com/ansible/2.9/plugins/inventory.html https://github.com/ansible/ansible/tree/stable-2.9/contrib https://github.com/ansible/ansible/blob/stable-2.9/lib/ansible/plugins/inventory/script.py https://gist.github.com/onegreyonewhite/fc4de39b86a0bfa2cdf656ef57081600  Специалист