Description
Describe the bug
Making an inventory containing 2 "hosts", each with its own list of items to use, makes pyinfra mix up stuff in the "Detected changes" output.
Everything does deploy correctly, even though the "Detected changes" are showing something different than what is actually being done.
In my case, I have 1 server with a 2 incus containers on it, that i can ssh into individually. That is why the inventory hosts are named as "server01/incus01" and "server01/incus02". ssh_hostname
holds the actual host-name used for logging in.
I have configured a list of users, user-1 through user-5. user 1-4 are configured for incus01, and user-5 is on incus02. Yet "Detected changes" shows:
--> Detected changes:
Operation Change Conditional Change
Create user-5 user on server01/incus02 2 (server01/incus01, server01/incus02) -
Create user-2 user on server01/incus01 1 (server01/incus01) -
Create user-3 user on server01/incus01 1 (server01/incus01) -
Create user-4 user on server01/incus01 1 (server01/incus01) -
It shows "Create user-5 user on server01/incus02" for both hosts, and "user-1" is completely absent.
To Reproduce
inventory.py
hosts = [
('server01/incus01',
{'users': [
'user-1',
'user-2',
'user-3',
'user-4'
],
'ssh_hostname': 'server01.example.com',
'ssh_port': 2222,
'ssh_user': 'pyinfra+incus01'}),
('server01/incus02',
{'users': [
'user-5'
],
'ssh_hostname': 'server01.example.com',
'ssh_port': 2222,
'ssh_user': 'pyinfra+incus02'})
]
deploy_test.py
from pyinfra.operations import server
from pyinfra import host
users = host.data.get("users")
for user in users:
server.shell(
name=f"Create {user} user on {host.name}",
commands=[
f"echo {user}",
],
)
Expected behavior
Because the text is unique, due to it containing eg. username, I expected it to look like this:
--> Detected changes:
Operation Change Conditional Change
Create user-1 user on server01/incus01 1 (server01/incus01) -
Create user-2 user on server01/incus01 1 (server01/incus01) -
Create user-3 user on server01/incus01 1 (server01/incus01) -
Create user-4 user on server01/incus01 1 (server01/incus01) -
Create user-5 user on server01/incus02 1 (server01/incus02) -
Meta
pyinfra --support:
System: Linux
Platform: Linux-6.11.0-26-generic-x86_64-with-glibc2.39
Release: 6.11.0-26-generic
Machine: x86_64
pyinfra: v3.3.1
black: v25.1.0
black: v25.1.0
click: v8.1.8
distro: v1.9.0
gevent: v24.11.1
jinja2: v3.1.5
packaging: v24.2
paramiko: v3.5.0
pytest: v8.3.5
pytest: v8.3.5
python-dateutil: v2.9.0.post0
pywinrm: v0.5.0
pyyaml: v6.0.2
pyyaml: v6.0.2
setuptools: v75.8.0
typeguard: v4.4.1
typing-extensions: v4.12.2
Executable: /home/dan/development/pyinfra/venv/bin/pyinfra
Python: 3.12.3 (CPython, GCC 13.3.0)
Pyinfra was installed via pip.
debug output:
--> Loading config...
--> Loading inventory...
[pyinfra_cli.inventory] Creating fake inventory...
[pyinfra_cli.inventory] Checking possible group_data at: /home/dan/development/pyinfra/test/group_data
--> Connecting to hosts...
[pyinfra.connectors.ssh] Connecting to: server01.example.com ({'allow_agent': True, 'look_for_keys': True, '_pyinfra_ssh_forward_agent': False, '_pyinfra_ssh_config_file': None, '_pyinfra_ssh_known_hosts_file': None, '_pyinfra_ssh_strict_host_key_checking': 'accept-new', '_pyinfra_ssh_paramiko_connect_kwargs': None, 'username': 'pyinfra+incus01', 'port': 2222, 'timeout': 10})
[pyinfra.connectors.sshuserclient.client] Loading SSH config: None
[pyinfra.connectors.ssh] Connecting to: server01.example.com ({'allow_agent': True, 'look_for_keys': True, '_pyinfra_ssh_forward_agent': False, '_pyinfra_ssh_config_file': None, '_pyinfra_ssh_known_hosts_file': None, '_pyinfra_ssh_strict_host_key_checking': 'accept-new', '_pyinfra_ssh_paramiko_connect_kwargs': None, 'username': 'pyinfra+incus02', 'port': 2222, 'timeout': 10})
[server01/incus02] Connected
[server01/incus01] Connected
[pyinfra.api.state] Activating host: server01/incus01
[pyinfra.api.state] Activating host: server01/incus02
--> Preparing operation files...
Loading: deploy_test.py
[pyinfra.api.operation] Adding operation, {'Create user-5 user on server01/incus02'}, opOrder=(0, 8), opHash=e7b37f7edef2ef843cea2aa2075da5d7f922fee3
[server01/incus02] Ready: deploy_test.py
[pyinfra.api.operation] Adding operation, {'Create user-1 user on server01/incus01'}, opOrder=(0, 8), opHash=e7b37f7edef2ef843cea2aa2075da5d7f922fee3
[pyinfra.api.operation] Duplicate hash (e7b37f7edef2ef843cea2aa2075da5d7f922fee3) detected!
[pyinfra.api.operation] Adding operation, {'Create user-2 user on server01/incus01'}, opOrder=(0, 8, 1), opHash=e7b37f7edef2ef843cea2aa2075da5d7f922fee3-0
[pyinfra.api.operation] Duplicate hash (e7b37f7edef2ef843cea2aa2075da5d7f922fee3) detected!
[pyinfra.api.operation] Duplicate hash (e7b37f7edef2ef843cea2aa2075da5d7f922fee3-0) detected!
[pyinfra.api.operation] Adding operation, {'Create user-3 user on server01/incus01'}, opOrder=(0, 8, 2), opHash=e7b37f7edef2ef843cea2aa2075da5d7f922fee3-0-1
[pyinfra.api.operation] Duplicate hash (e7b37f7edef2ef843cea2aa2075da5d7f922fee3) detected!
[pyinfra.api.operation] Duplicate hash (e7b37f7edef2ef843cea2aa2075da5d7f922fee3-0) detected!
[pyinfra.api.operation] Duplicate hash (e7b37f7edef2ef843cea2aa2075da5d7f922fee3-0-1) detected!
[pyinfra.api.operation] Adding operation, {'Create user-4 user on server01/incus01'}, opOrder=(0, 8, 3), opHash=e7b37f7edef2ef843cea2aa2075da5d7f922fee3-0-1-2
[server01/incus01] Ready: deploy_test.py
--> Detected changes:
Operation Change Conditional Change
Create user-1 user on server01/incus01 2 (server01/incus01, server01/incus02) -
Create user-2 user on server01/incus01 1 (server01/incus01) -
Create user-3 user on server01/incus01 1 (server01/incus01) -
Create user-4 user on server01/incus01 1 (server01/incus01) -
Detected changes may not include every change pyinfra will execute.
Hidden side effects of operations may alter behaviour of future operations,
this will be shown in the results. The remote state will always be updated
to reflect the state defined by the input operations.
--> Disconnecting from hosts...