Как известно, любые задачи вокруг ansible начинаются с перечня ресурсов, управление которыми требуется. Основным способом инвентаризации является обычный текстовый файл (по умолчанию /hosts) c перечнем имен или адресов серверов. Многие знают, что при указании в качестве пути директории ansible будет использовать все находящиеся внутри оной файлы как источники данных о хостах.
Однако при количестве хостов более пары десятков файлы становится трудно поддерживать в актуальном состоянии, и тогда на помощь приходят различные системы инвентаризации и управления активами. В случае с виртуальными средами такой системой в первом приближении является гипервизор (или вышестоящее ПО, типа vsphere, proxmox, etc). При наличии подобной базы данных единственно правильным и удобным решением является или автоматизация процесса синхронизации локальных inventory-файлов ansible с данными этой базы, или прямое использование данных базы во время выполнения плейбука - dynamic inventory. Для многих случаев существуют готовые скрипты, реализующие функционал динамического построения списка хостов. Также есть страничка, где описываются основные моменты, необходимые для написания собственного решения.
Но вот с программированием (да, даже на питоне :\) у меня никак, поэтому пришлось обходиться средствами самого ansible, а именно: для взаимодействия с CMDBuild пришлось написать плейбук, который актуализирует локальный inventory-файл.
Далее я хочу сохранить для истории все тонкости и интересные с точки зрения изучения продукта моменты, возникшие в процессе разработки этого решения.
Итак, в качестве средства инвентаризации и хранения данных об информационных ресурсах используется CMDBuild. Для автоматизированного общения с базой данных доступен REST API, познакомиться с которым можно вот тут.
Так как неавторизованным пользователям доступ не предоставляется (401 при любом запросе), первая задача: пройти аутентификацию. Замечание: как выяснилось в процессе отладки, сервисные учетные записи (в отличие от учетных записей пользователей) не могут быть использованы в веб-интерфейсе, только через API.
Здесь и далее для работы с http-запросами используется ansible-модуль uri. Как обычно, документация к модулю достаточно полна и снабжена примерами, чтобы разобраться в его использовании.
- name: Get ID and open session with CMDBuild API
uri:
url: "{{cmdb_url}}/sessions"
validate_certs: no
method: POST
body: "{\"username\" : \"{{cmdb_user}}\" , \"password\" : \"{{cmdb_pass}}\"}"
body_format: json
status_code: 200
headers:
Content-Type: application/json
register: auth_result
Стоит указать, что значение {{cmdb_url}} стоит подсмотреть в документации, в моем случае он выглядит примерно так: "https://cmdb.example.com/cmdbuild/services/rest/v2" , а учетные данные ({{cmdb_user}} и {{cmdb_pass}}) не следует держать в открытом виде. В случае ansible 2.3+ наиболее удобным способом будет использование in-place vault:
cmdb_user: !vault |
$ANSIBLE_VAULT;1.1;AES256
666239373962396262623031663239613432663366383435393634653436366133326339373735373063306464626631613765326331343364356337313432330a386164303261623339326435613862323433386361343262383432326439653235333665313666623431343532616437376163666561613737343535336533610a6234343263376465626264333630306639626137616165373066643034336262
Для более ранних версий - отдельный vault-файл, который следует подгрузить через include_vars. Подробнее о хранилище ansible-vault и методах работы с ним тут.
Итак, задание выше отправляет POST по адресу url c учетными данными в json-формате, считается выполненным при получении http-кода 200 и записывает результат выполнения (тоже json) в переменную {{auth_result}}.
Результатом этого запроса будет открытая на сервере сессия с идентификатором {{auth_result.json.data._id}}, который далее необходимо вкладывать в заголовок каждого запроса.
Далее, имея доступ к дереву ресурсов (в рамках прав учетной записи), можно запросить требуемые нам данные. В CMDBuild есть свои встроенные фильтры, которые можно передавать через http в виде параметра ?filter=, а также свой sql-подобный язык запросов CQL, передающийся через параметр ?cql=. Мои потребности полностью покрывались фильтрами, поэтому cql-запросы не рассматривались.
Однако с фильтрами тоже все непросто. В текущей версии документации эта тема практически не затронута. Где-то на форумах нашлось описание конструкции фильтра:
'{"filter": {"attribute": "Description","operator":"contain","value":["SomeValue"],"parameterType":"fixed" }}'
Там же поступило предложение настраивать фильтр в веб-интерфейсе и подглядывать за передаваемыми http-параметрами в окне консоли браузера. Иных способов пока, к сожалению, нет. В общем, неким магическим для меня образом было получено несколько фильтров, с каждым из которых был создан запрос. Вот пример одного из них:
## filter_prod: Description
## Status: Production,[03]
filter_prod: "?filter=%7B%22attribute%22%3A%7B%22simple%22%3A%7B%22attribute%22%3A%22Status%22%2C%22operator%22%3A%22equal%22%2C%22value%22%3A%5B03%5D%2C%22parameterType%22%3A%22fixed%22%7D%7D%7D"
- name: Query hosts with "Production" status (filter_prod)
uri:
url: "{{cmdb_url}}/classes/Server/cards{{filter_prod}}"
validate_certs: no
method: GET
status_code: 200
headers:
CMDBuild-Authorization: "{{auth_result.json.data._id}}"
Content-Type: application/json
when: auth_result.json.data._id is defined
register: prod_result
no_log: yes
Большая часть из описания задания совпадает с вышесказанным. Из особенностей: добавлен дополнительный параметр headers, где передается токен авторизации - CMDBuild-Authorization (название которого тоже, как и в случае с фильтрами, не освещено в документации). Результат выполнения GET запроса - JSON с описанием карточек всех ресурсов класса "Server", подходящих под фильтр {{filter_prod}}. Так как вывод идет в stdout, количество выпавшей в консоль информации может удивить, поэтому добавлен параметр no_log: yes, запрещающий любой вывод результатов этого задания вне зависимости от уровня журналирования.
Также добавлено условие выполнения задания: when: auth_result.json.data._id is defined. В случае неуспешной аутентификации плейбук не будет сыпать ошибками, а просто завершится без выполнения запросов.
После выполнения всех запросов хорошим тоном будет закрыть сессию:
- name: Close session with CMDBuild API
uri:
url: "{{cmdb_url}}/sessions/{{auth_result.json.data._id}}"
validate_certs: no
method: DELETE
status_code: 204
headers:
CMDBuild-Authorization: "{{auth_result.json.data._id}}"
Отмечу лишь, что успешный код выполнения этого запроса: 204.
Для удобства дальнейшего использования полученные списки хостов следует прогруппировать, для этого запишем названия необходимых групп в inventory-файл:
- name: Create ansible groups
lineinfile:
state: present
dest: "{{cmdb_inventory}}"
regexp: '^{{ item|replace("[", "\[")|replace("]", "\]") }}'
line: '{{ item }}'
insertbefore: BOF
create: yes
with_items:
- '[cmdb-prod]'
Из особенностей здесь описание регулярки: помимо обычного указания элемента здесь последовательно используется два фильтра replace, подставляющих символ экранирования для квадратных скобок. Без этого изменения мы получаем не выражение для поиска, а диапазон символов, и поиск не срабатывает. За подсказку по проблеме спасибо автору этого поста.
Наконец, осталось лишь записать выгруженные хосты в файл.
- name: Write '[cmdb-prod]' hosts to inventory file
lineinfile:
state: present
dest: "{{cmdb_inventory}}"
line: "{{item}}"
insertafter: '^\[cmdb-prod\]'
with_items: "{{ prod_result.json.data | map(attribute='HostName') | list | sort(reverse = True) }}"
Стоит пояснить происходящее внутри параметра with_items. Если содержимое переменной в Ansible - JSON, то к ее элементам можно получить доступ через var_name.json. В выводе CMDBuild присуствуют массивы meta и data, содержимое последнего нас и интересует. Далее используется фильтр map, который показывает только содержимое атрибута HostName внутри prod_result.json.data. Однако вывод остается в формате JSON, поэтому используется фильтр list, делающий из него plaintext. Сортировка по убыванию используется потому, что каждый {{item}} вставляется после [cmdb-prod], и таким образом мы получаем в итоге сортированный в алфавитном порядке список.
В процессе разработки плейбука запросы приходилось делать множество раз, поэтому очень полезным оказался проект resty. Это обертка над curl, которая встраивается в текущий шелл и позволяет более просто делать различные http-запросы.
:
чувак спасибо тебе большое! по CMDBuild реально нет никакой нормальной инфы по фильтрам
ОтветитьУдалитьФильтры можно накидать в вебке CMDBuild, в каждом списке есть специальная кнопочка, а затем нажать F12 (отладку) в браузере и применить фильтр. В отладке можно отловить URI фильтра.
ОтветитьУдалить