Ansible Yandex Cloud Dynamic Inventory Plugin (fork + fixes)
Русский | English
An Ansible dynamic inventory plugin for Yandex Cloud: it reads Compute instances and builds an inventory grouped by availability zone and by VM labels.
This is a fork. The original is mzatolokin/ansible-yandex-cloud-inventory (upstream
main, commitd2ad1ab, author Maksim Zatolokin, MIT license). Upstream has not been updated for about a year, so fixes are released frommorgenvor/ansible-yandex-cloud-inventory(default branchlocal-patches;mainin the fork points at clean upstream and exists only as a sync point). The upstream code is not claimed as new work: this repository is upstream code plus fixes. What exactly changed, what the label contract is now and how to migrate are described indocs/upstream-differences.md(that document is Russian-only for now).
Features
- Reads Compute instances from Yandex Cloud
- Authentication with an IAM token or a service account key
- Groups by availability zone and by user labels
ansible_hostis the public IP (the internal one stays inprivate_ipv4), so connections work from outside the cloud as well- The host name is the VM name as is, dashes included:
ansible web-1 -m pingfindsweb-1, andweb-1andweb_1stay two different hosts - Paginated
ListInstances, so VMs after the first page are not lost - An instance can be excluded with the
ansible: falselabel (value is case-insensitive) - VM labels are exported as
yc_label_<key>; onlyansible_userandansible_portbecome service variables (allowlist), so a label cannot override the connection address or execute code on the controller - Extra groups through the
ansible_grouplabel — strictly with theyc_prefix:ansible_group: dockercreates the groupyc_docker - A clear error instead of a traceback when the
yandexcloudSDK is missing
Installation
From source
- Clone the fork. The default branch
local-patchesis the fork code with the fixes;mainin the fork points at clean upstream and is only used for synchronisation:
git clone https://github.com/morgenvor/ansible-yandex-cloud-inventory.git
cd ansible-yandex-cloud-inventory
- Install the dependencies and the plugin — the single source of dependencies
is
pyproject.toml:
pip install -e .
- Make the plugin visible to Ansible — either through
ansible.cfg(see "With ansible.cfg") or with an environment variable:
export ANSIBLE_INVENTORY_PLUGINS=./inventory_plugins
With pip install (and why that alone is not enough)
The distribution on PyPI is called ansible-yandex-cloud-inventory-fixed: the
name ansible-yandex-cloud-inventory belongs to upstream. The same package can
be installed from a source checkout, so both commands below give one package:
pip install ansible-yandex-cloud-inventory-fixed # from PyPI
pip install . # from the sources
The package lands in site-packages and is importable as a Python module, but
Ansible will not find the plugin automatically: ansible-core (verified on
2.19) loads inventory plugins only from plugin directories and collections.
entry_points are not read by Ansible at all — there is no such mechanism for
plugins in ansible-core (entry points exist only for console_scripts and
roles). So after installing, point Ansible at the plugin directory inside the
environment:
# path inside venv/site-packages where the installed inventory_plugins/ lives
# (do not run this from the repository root: otherwise Python resolves the local
# inventory_plugins/ directory and prints the path to the sources, not to the
# installed package)
export ANSIBLE_INVENTORY_PLUGINS="$(python -c 'import inventory_plugins, os; print(os.path.dirname(inventory_plugins.__file__))')"
The simplest option is to work from the sources and keep
inventory_plugins = ./inventory_plugins in [defaults] of your ansible.cfg
(that is how this repository is set up).
Note that pip install . is also the step that installs the yandexcloud SDK.
If you copy the plugin directory instead, install the SDK yourself
(pip install yandexcloud), otherwise the plugin stops with an explicit error
saying exactly that.
Configuration
Authentication
The plugin supports two methods.
Service account key (recommended)
plugin: yandex_cloud_inventory
folder_id: your-folder-id
service_account_key_file: /path/to/service-account-key.json
IAM token (handy for one-off checks)
⚠️ Important: an IAM token lives for 12 hours and has to be refreshed.
plugin: yandex_cloud_inventory
folder_id: your-folder-id
iam_token: your-iam-token
The token does not have to be written to a file — it can come from the
YC_IAM_TOKEN environment variable:
plugin: yandex_cloud_inventory
folder_id: your-folder-id
# iam_token will be taken from the YC_IAM_TOKEN environment variable
Refreshing the token:
yc iam create-token
Options
| Option | Required | Description |
|---|---|---|
plugin |
Yes | Must be yandex_cloud_inventory |
folder_id |
Yes | Yandex Cloud folder ID |
iam_token |
No* | IAM token for authentication |
service_account_key_file |
No* | Path to the service account key JSON file |
group |
No | Group name every host is added to |
*Either iam_token or service_account_key_file is required (the key wins).
Example configuration
The file name matters. The plugin claims only yc_inventory.yml
(.yaml) or <prefix>_yc_inventory.yml — the _ separator and a non-empty
prefix are mandatory, so inventory.yml and _yc_inventory.yml are ignored on
purpose: that way foreign inventories are not intercepted. A ready example is
examples/yc_inventory.yml:
plugin: yandex_cloud_inventory
folder_id: <YOUR_FOLDER_ID>
group: ycloud_group
service_account_key_file: <YOUR_SERVICE_ACCOUNT_KEY_FILE>
Or with the token from the environment:
export YC_IAM_TOKEN="your-iam-token"
plugin: yandex_cloud_inventory
folder_id: <YOUR_FOLDER_ID>
group: ycloud_group
Usage
Checking the inventory
ansible-inventory -i yc_inventory.yml --list
ansible-inventory -i yc_inventory.yml --graph --vars
With playbooks
ansible-playbook -i yc_inventory.yml playbook.yml
Connection and ansible_user
The plugin does not set the connection user: if ansible_user is not defined
anywhere, Ansible connects as the local user of the controller, not as the user
of the image. For VMs created through the CLI that is usually yc-user, for
Ubuntu images ubuntu. This is Ansible behaviour, not a plugin defect: the name
is set in the playbook (remote_user), with -u, or in group_vars.
Keep the precedence order in mind: the ansible_user label is a host variable,
so it overrides both remote_user and -u
(play_context.set_task_and_variable_override(): task attributes are applied
first, then magic variables from the inventory).
With ansible.cfg
[defaults]
# The plugin path goes into [defaults]; Ansible does not read this key in [inventory].
inventory_plugins = ./inventory_plugins
[inventory]
# yaml is mandatory: without it a regular static inventory is not parsed.
enable_plugins = yandex_cloud_inventory, yaml, ini, script, host_list
The ansible.cfg in the repository root is the config of the offline test
stand: it only sets the plugin path and enable_plugins, and the stand needs no
SSH. It is usable as a sample for a production config, but check the remaining
settings yourself: for example host_key_checking is deliberately not set here,
and disabling host key checking on a workstation is a separate decision rather
than a default.
Grouping instances
The plugin creates groups from:
- Zones — for example
ru_central1_a(dashes become_); - The
ansible_grouplabel — the instance joins the groupyc_<value>(dockerbecomesyc_docker, dashes become_); - The
groupoption — if set, every host is added to that group.
Labels
VM labels are untrusted data: anyone with access to the folder can set them. That is why they never land in host variables as is:
- every label is exported as
yc_label_<key>(-in the key becomes_, the value is kept unchanged):role: web-app→yc_label_role = web-app; - only
ansible_userandansible_portbecome service variables (allowlist): they are applied as is and also duplicated asyc_label_*. Note thatansible_useris a host variable and therefore overridesremote_userand-u. Set a label with this key deliberately, otherwise "why did we connect as the wrong user" takes a long time to find; - the allowlist values are validated:
ansible_portmust be a number from 1 to 65535,ansible_usermust be non-empty and free of spaces, quotes, backslashes and non-printable characters (both end up on thesshcommand line, and a control sequence from a label would also reach the operator's terminal and CI logs). An unusable value is dropped with a warning and stays available asyc_label_*; - other labels starting with
ansible_are not applied — the plugin prints a warning with the VM name and the key. The reason: anansible_hostlabel would override the connection address, andansible_ssh_common_argswith aProxyCommandwould execute a command on the controller; - two labels are directives rather than variables. Keys are normalised the same
way as variable names, so
ansible-groupandansible_groupare the same label:ansible: false— exclude the instance from the inventory (case does not matter);ansible_group: docker— add the instance to the groupyc_docker. Theyc_prefix is mandatory and not configurable: without it a label would join groups you manage (dbservers,prod) and the host would get theirgroup_vars— with secrets or withansible_connection: local, that is, execution on the controller. On top of that, the group name becomes a pathgroup_vars/<name>, and a label can be set by anyone with access to the folder: the value../../outside/evilread a file outsidegroup_vars. Therefore:-in the value becomes_(web-app→yc_web_app);- the rest must be letters, digits and
_(up to 63 characters): otherwise the plugin prints a warning with the VM name and the value, does not create the group, and the host stays in the inventory (zone and thegroupoption are unaffected); - if the value already starts with
yc_, the plugin does not guess:ansible_group: yc_dockeryieldsyc_yc_dockerplus a warning in the log; - the raw value stays visible in
yc_label_ansible_group.
The plugin also warns when a VM has no IPv4 address and when two label keys
collide after normalisation (a-b and a_b). Separately, there is a safeguard
against a duplicate host name in the API response: Yandex Cloud requires VM
names to be unique inside a folder, so that warning is aimed at an API failure
rather than at a normal scenario (the test stand does not cover it — it cannot
be reproduced; see docs/upstream-differences.md,
section 7).
Host variables
The host name is the VM name in Yandex Cloud as is, dashes included: the VM
web-1 is called web-1 in the inventory, and ansible web-1 -m ping finds
it. Such variables cannot be read with dot notation (Jinja parses web-1 as a
subtraction) — use hostvars['web-1'].
| Variable | Value |
|---|---|
ansible_host |
public IP if there is one, otherwise the internal one |
ipv4 |
public IP, or null when there is no public address |
private_ipv4 |
internal IP |
yc_name |
instance name in Yandex Cloud |
yc_label_<key> |
VM label (for example role: web-app → yc_label_role) |
Labels cannot override the service variables from the table: every label goes to
yc_label_* only. The exception is the ansible_user/ansible_port allowlist
(see "Labels").
Upgrading from older versions
In 2.0 labels stopped being host variables directly (role → yc_label_role),
in 3.0 the host name is no longer normalised (web_1 → web-1) and groups from
labels got the mandatory yc_ prefix. The "before → after" tables and the
migration order are in
docs/upstream-differences.md, sections 5 and 6
(Russian).
Limitations
- The
cacheparameter ofparse()is accepted for compatibility with the Ansible interface but is not used: the inventory is requested from the API on every run. - Only the first network interface of a VM is used.
- The plugin does not look at the VM state: a stopped or preempted machine stays in the inventory as long as it exists as a resource.
- If the API refuses (expired IAM token, insufficient permissions), Ansible prints a warning and continues with an empty inventory — that is how source parsing works. The plugin prints the reason and a hint, but the exit code stays successful; in automation, check that the inventory is not empty.
- Groups
yc_*are the label namespace, not yours: they are created byansible_group, and foreign groups (for exampledbservers) are unreachable through labels. The flip side: do not keep secrets ingroup_vars/yc_*— any VM in the folder can create such a group and receive those variables.
The full list of deliberate limitations is in
docs/upstream-differences.md, section 7
(Russian).
Requirements
- Python 3.11+
- ansible-core 2.18.18+ (2.18.18 is the first patch of the branch without CVE-2026-11332)
- yandexcloud >= 0.99.0, < 1.0 (verified on 0.99…0.412)
The versions verified for the test stand are pinned in requirements-test.txt
(ansible-core==2.19.14, yandexcloud==0.412.0).
Testing without a cloud
The stand replaces the SDK with a stub (tests/fake_yc/) and runs the real
ansible-inventory through eighteen scenarios — from labels and addresses to
pagination, verify_file, API failures, label-driven injection and a missing
SDK. There are 127 assertions in total: on a regression the script exits
non-zero and prints which assertion failed. What exactly is covered is described
in docs/upstream-differences.md, section 8
(Russian).
./bootstrap.sh # builds a local .venv
./tests/run-tests.sh # runs the scenarios
The stand ships in the sdist: the plugin can be verified from the built archive without cloud access and without cloning the repository.
The same commands run in CI (GitHub Actions,
.github/workflows/tests.yml): on Python 3.11,
3.12 and 3.13, plus a separate job from the unpacked sdist so that a file
missing from MANIFEST.in does not go unnoticed.
Verified on a real cloud
Besides the stand, the behaviour was verified against a live Yandex Cloud folder
(labels, ansible_user precedence, dashed names, group_vars); details are in
docs/upstream-differences.md, section 8
(Russian).
Your own folder can be checked with
tests/live-check.sh — it only reads the cloud:
LIVE_INVENTORY=secrets/live_yc_inventory.yml ./tests/live-check.sh.
License
MIT — see LICENSE. The upstream code is by Maksim Zatolokin; the fixes and packaging of the fork are by morgenvor.
Support
Report fork issues in the morgenvor/ansible-yandex-cloud-inventory
repository. Report issues of the original plugin upstream.
Metadata
Release files for ansible-yandex-cloud-inventory-fixed 3.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ansible_yandex_cloud_inventory_fixed-3.0.4.tar.gz | 58.0 kB | Without provenance | Details for ansible_yandex_cloud_inventory_fixed-3.0.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ansible_yandex_cloud_inventory_fixed-3.0.4-py3-none-any.whl | Python 3 | none | any | Details for ansible_yandex_cloud_inventory_fixed-3.0.4-py3-none-any.whl |
Total release size: 75.7 kB
Release files / ansible_yandex_cloud_inventory_fixed-3.0.4.tar.gz
| Download URL | ansible_yandex_cloud_inventory_fixed-3.0.4.tar.gz |
|---|---|
| Size | 58.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7955b442f3a5b76eb3cd22101e77101edfdd39eaefc850cada179cadd0fbd432
|
|
BLAKE2b-256 checksum How to use checksums |
09679f6f682bf824279ab6581d9214386b83e6b5faee2843e7b74f6e8de615d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.17
|
Release files / ansible_yandex_cloud_inventory_fixed-3.0.4-py3-none-any.whl
| Download URL | ansible_yandex_cloud_inventory_fixed-3.0.4-py3-none-any.whl |
|---|---|
| Size | 17.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d7e935412acc87954c04c80c3806f61e88b977e109e5d6a283bea95aaed7036f
|
|
BLAKE2b-256 checksum How to use checksums |
048a71f407a70f9241f531eda3f21c1eef28709f238b41ab1ec12d2b81f2c4b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.17
|
Security for ansible-yandex-cloud-inventory-fixed 3.0.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.