mitogen-hq / mitogen

Distributed self-replicating programs in Python
https://mitogen.networkgenomics.com/
BSD 3-Clause "New" or "Revised" License
2.29k stars 198 forks source link

The attribute 'remote_user' of the object 'connection._play_context' contains the variable name instead of its value. #1040

Open macksyma opened 4 months ago

macksyma commented 4 months ago

I've discovered the incorrect transmission of the remote_user variable's value when using delegate_to with Mitogen 3.4. For example, let's take a simple task:

    - name: "test_task"
      shell: "ls -lah"
      delegate_to: "{{ test_jump_box }}"
      remote_user: "{{ jumper_test_user }}"

Here is the error I've received executing this task:

fatal: [localhost -> XXXXXXXXXX]: UNREACHABLE! => {
    "changed": false,
    "msg": "EOF on stream; last 100 lines received:\nremote username contains invalid characters\r",
    "unreachable": true
}

In the debug log, I see that the ssh command line contains the name of the variable transmitted into the remote_user - {{ jumper_test_user }}, instead of the actual value of this variable:

[mux  2197866] 12:52:15.298866 D mitogen.parent: command line for Connection(None): ssh -o "LogLevel ERROR" -l "{{ jumper_test_user }}" -o "Compression yes" -o "ServerAliveInterval 30" -o "ServerAliveCountMax 10" -o "BatchMode yes" -o "StrictHostKeyChecking no" -o "UserKnownHostsFile /dev/null" -o "GlobalKnownHostsFile /dev/null" -F ssh.cfg ...

After further investigation, I've discovered that Mitogen takes the username for ssh from object _connection._play_context, transmitted to Mitogen's ActionModuleMixin from Ansible's TaskExecutor. Inside the ActionModuleMixin object, I've found that _self._play_context.remote_user contains the correct value of the variable jumper_test_user, but the connection object self._connection._play_context.remote_user contains the variable name, including brackets, just like I saw it in the ssh command line.

I've checked how the method _executeof Ansible's class TaskExecutor updates attributes of its own self._play_context and self._connection._play_context, and found that at some point, self._play_context of TaskExecutor also contains the name of variables instead of their value. But then, it executes the method post_validate() for its self._play_context (PlayContext inherits it from its parent classes):

self._play_context.post_validate(templar=templar)

that replaces variable names with their values. Unfortunately, it doesn't execute this method for _connection._play_context, and as a result, we have variable names instead of values. I'm not sure whether it is a bug or there is some reason to avoid execution of this method for _connection._play_context inside method _execute. As a temporary workaround, I've appended the constructor of ActionModuleMixin with the execution of self._connection._play_context.post_validate(templar=connection.templar):

class ActionModuleMixin(ansible.plugins.action.ActionBase):
    """
    ...
    """
    def __init__(self, task, connection, *args, **kwargs):
        """
        ...
        """
        super(ActionModuleMixin, self).__init__(task, connection, *args, **kwargs)
        if not isinstance(connection, ansible_mitogen.connection.Connection):
            _, self.__class__ = type(self).__bases__

        # required for python interpreter discovery
        connection.templar = self._templar
        self._finding_python_interpreter = False
        self._rediscovered_python = False
        # redeclaring interpreter discovery vars here in case running ansible < 2.8.0
        self._discovered_interpreter_key = None
        self._discovered_interpreter = False
        self._discovery_deprecation_warnings = []
        self._discovery_warnings = []

        self._connection._play_context.post_validate(templar=connection.templar)

and after that got the correct value of remote_user when using delegate_to in my tasks.

Probably you will find better approach solving this issue.

Thank you in advance.

moreati commented 4 months ago

Thank you, this may be closely related to #1022 or a case of it.

rawat-he commented 3 months ago

Hi,

I have the same issue.

mitogen_get_stack print without the changes mentioned by @macksyma

  discovered_interpreter: false
  result:
  - kwargs:
      check_host_keys: ignore
      compression: true
      connect_timeout: 10
      hostname: '{{ my_ip_address }}'
      identities_only: false
      identity_file: null
      keepalive_count: 10
      keepalive_interval: 30
      password: null
      port: null
      python_path:
      - /usr/bin/python3
      remote_name: null
      ssh_args:
      - -F
      - /data/my-work/my_custom_ssh_config
      - -o
      - UserKnownHostsFile=/dev/null
      - -o
      - ConnectTimeout=20
      - -o
      - ControlMaster=auto
      - -o
      - ControlPersist=60s
      ssh_debug_level: null
      ssh_path: ssh
      username: '{{ lookup(''env'', ''USERNAME'') | default(my.username, true) }}'
    method: ssh

mitogen_get_stack print after the changes mentioned by @macksyma

  discovered_interpreter: false
  result:
  - kwargs:
      check_host_keys: ignore
      compression: true
      connect_timeout: 10
      hostname: 10.31.103.20
      identities_only: false
      identity_file: null
      keepalive_count: 10
      keepalive_interval: 30
      password: null
      port: null
      python_path:
      - /usr/bin/python3
      remote_name: null
      ssh_args:
      - -F
      - /data/my-work/my_custom_ssh_config
      - -o
      - UserKnownHostsFile=/dev/null
      - -o
      - ConnectTimeout=20
      - -o
      - ControlMaster=auto
      - -o
      - ControlPersist=60s
      ssh_debug_level: null
      ssh_path: ssh
      username: hrawat
    method: ssh

Ansible: 2.14.0 Target OS: Alpine Linux v3.17 Python: 3.10.13 mitogen: 0.3.6