Skip to content

Detected changed across mulitple hosts are wrong #1370

Open
@dfaerch

Description

@dfaerch

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...

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLICLI mode specific issues.bugLabel for all kind of bugs.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions