Динамический инвентарь для Ansible.
  • Python 81.7%
  • Shell 18.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-21 23:49:14 +10:00
inventory add aliases 2026-10-21 23:49:14 +10:00
LICENSE Initial commit 2026-10-10 12:04:37 +00:00
README.md readme 2026-10-16 00:06:57 +10:00

Dynamic inventory for Ansible

Генерирует hosts.ini для Ansible из YAML или INI-инвентаря. Проверяет доступность портов, ведёт кэш last_online, поддерживает фильтры по имени.

flowchart TB
    A["📄 Инвентарь<br/>YAML / INI"] --> B["① extract"]
    B --> C["② check_ports"]
    C --> D["③ filter_records"]
    D --> E["④ compose"]
    E --> F["📦 hosts.ini"]

    B --> B2["парсинг · диапазоны · exclude-ips<br/>unresolvable → forced"]
    C --> C2["TCP :22 · PTR · online_no_ptr.txt"]
    D --> D2["--include · --exclude<br/>--exclude-ips-hosts"]
    E --> E2["last_online · prune 90d<br/>группировка · дедуп"]

    B -.->|targets.txt · hints.json| C
    C -.->|resolver.jsonl| D
    D -.->|filtered.jsonl| E
    E2 -.->|cache.json| E

    classDef input fill:#e3f2fd,stroke:#1976d2,color:#000
    classDef module fill:#f3e5f5,stroke:#7b1fa2,color:#000
    classDef detail fill:#fafafa,stroke:#9e9e9e,color:#555
    classDef output fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px,color:#000

    class A input
    class B,C,D,E module
    class B2,C2,D2,E2 detail
    class F output

Структура

inventory/
│
├─ bin/
│   └─ gen-inventory.sh     # Оркестратор
│
├─ lib/
│   ├─ extract_hosts.py     # Модуль 1
│   ├─ check_ports.py       # Модуль 2
│   ├─ filter_records.py    # Модуль 3
│   └─ compose_inventory.py # Модуль 4
│
├─ etc/
│   ├─ exclude-ips.txt      # Чёрный список IP
│   └─ unresolvable.txt     # хосты без DNS, но доступные по SSH
│
├─ run/                     # Промежуточные файлы
│   ├─ targets.txt          # Список целей
│   ├─ hints.json           # Карта target
│   ├─ resolver.log         # Ошибки DNS и прочие warning
│   ├─ resolver.jsonl       # JSONL от check_ports
│   └─ online_no_ptr.txt    # IP с открытым портом, но без PTR
│
├─ hosts.yml                # Входной YAML-инвентарь (редактируется вручную)
├─ hosts.ini                # Альтернативный входной INI (по --input)
|
├─ hosts.ini                # Выходной INI-инвентарь (генерируется)
|
├─ hosts.ini.cache.json     # Кэш состояний
└─ hosts.ini.lock           # Lock (если /var/lock недоступен)

Требования и установка

  • python3 (3.8+)
  • pyyaml — pip install pyyaml или системный пакет python3-yaml
chmod +x bin/gen-inventory.sh lib/*.py

Один хост в нескольких группах

Если в YAML хост входит в несколько групп (например, через children), в hints.json он получит массив entries со всеми группами и переменными. В hosts.ini он появится во всех своих группах. Ansible исполнит задачи для него один раз, несмотря на несколько секций. Дедуп внутри одной группы сохраняется (по hostname).

Как работают фильтры

Фильтры применяются в модуле filter_records.py, к итоговому имени хоста (DNS из PTR или сам IP, если PTR не сработал). все хосты из инвентаря → --include → --exclude → --exclude-ips-hosts → filtered.jsonl → compose → hosts.ini

  • --include REGEX — если задан хотя бы один, остаются только совпавшие.
  • --exclude REGEX — применяется после include.
  • --exclude-ips-hosts REGEX — чёрный список по имени: если итоговое DNS-имя совпало, IP исключается полностью. Работает и для forced-хостов из unresolvable.txt.

Каждый шаблон компилируется отдельно, поэтому inline-флаги вроде (?i) работают в любом из них:

--include '(?i)^adn'          # любой регистр
--include '^(adn07|ADN07)'    # ровно два варианта

Файл run/online_no_ptr.txt

После каждого запуска в run/ появляется список IP, у которых порт открыт, но PTR не сработал. Эти IP попадают в hosts.ini под своим IP-адресом (ansible_host=IP). Их можно:

  • добавить в etc/exclude-ips.txt — если это оборудование без SSH;
  • добавить в etc/unresolvable.txt — если хост нужен под понятным именем.

Чёрный список IP по именам

Пример: убрать принтеры, свитчи, IoT:

--exclude-ips-hosts '^printer-' \
--exclude-ips-hosts '(?i)^switch-' \
--exclude-ips-hosts '^iot-'

Пример запуска

./bin/gen-inventory.sh \
    --input /etc/ansible/hosts.yml \
    --output /etc/ansible/hosts.ini \
    --cache /var/cache/inventory.cache.json \
    --run-dir /tmp/inv-run \
    --include '(?i)^adn' \
    --exclude-ips-hosts '^printer-' \
    --exclude-ips-hosts '(?i)^switch-' \
    --exclude-ips-hosts '^iot-' \
    --exclude-ips-file /etc/ansible/exclude-ips.txt \
    --unresolvable-file /etc/ansible/unresolvable.txt

Про регистр

Regex регистрозависим по умолчанию. ^adn07 поймает adn07-web, но не поймает ADN07-web.

Параметры

Параметр Описание По умолчанию
--input PATH Входной инвентарь (YAML или INI) <BASE>/hosts.yml
--output PATH Выходной INI-файл <BASE>/hosts.ini
--cache PATH Файл кэша состояний <output>.cache.json
--run-dir PATH Каталог промежуточных файлов <BASE>/run
--exclude-ips-file Чёрный список IP <BASE>/etc/exclude-ips.txt
--unresolvable-file Файл «имя IP» для хостов без DNS <BASE>/etc/unresolvable.txt
--include REGEX Оставить только совпадающие имена —
--exclude REGEX Исключить совпадающие имена —
--exclude-ips-hosts Исключить IP по совпадению DNS-имени —
--port N TCP-порт проверки 22
--timeout N Таймаут TCP-connect, сек 5
--workers N Параллельных потоков 40
--delete-older N Хранить офлайн-хосты N дней 90
--no-ptr Не делать PTR для IP-целей —
--backup Бэкап предыдущего hosts.ini с меткой времени —
-v Подробный вывод —

Статусы проверки порта

TCP-результат Статус Что значит
open online Порт открыт, SSH работает
refused (RST) online Хост жив, порт закрыт или firewall
timeout offline Тишина: хост выключен или DROP
error unknown Ошибка сети, DNS-провал

Запуск по расписанию через systemd

Создать /etc/systemd/system/gen-inventory.service

[Service]
Type=oneshot
User=ansible
Group=ansible
WorkingDirectory=/path/to/inventory
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/path/to/inventory/bin/gen-inventory.sh \
    --input /path/to/inventory/hosts.yml \
    --output /path/to/inventory/hosts.ini \
    --delete-older 90 \
    --backup
StandardOutput=journal
StandardError=journal

Создать /etc/systemd/system/gen-inventory.timer

[Unit]
Description=Run inventory generator twice a day

[Timer]
OnCalendar=*-*-* 02:00:00
OnCalendar=*-*-* 14:00:00
RandomizedDelaySec=300
Persistent=true

[Install]
WantedBy=timers.target

Активировать:

sudo systemctl daemon-reload
sudo systemctl enable --now gen-inventory.timer
systemctl list-timers gen-inventory.timer

Запуск через Ansible (add_host)

Если нужно, чтобы этот инвентарь сразу использовался в плейбуке:

# playbooks/refresh-inventory.yml
- hosts: localhost
  gather_facts: no
  tasks:
    - name: Generate inventory
      ansible.builtin.command:
        cmd: /path/to/inventory/bin/gen-inventory.sh --delete-older 90
      register: gen
      changed_when: false

    - name: Show summary
      ansible.builtin.debug:
        msg: "{{ gen.stderr_lines | select('search', 'compose:') | list }}"

И затем в основном плейбуке -i inventory/hosts.ini