diff --git a/ansible_mitogen/connection.py b/ansible_mitogen/connection.py index a715b2b0b..4c1df1bde 100644 --- a/ansible_mitogen/connection.py +++ b/ansible_mitogen/connection.py @@ -814,7 +814,7 @@ def _connect_stack(self, stack): self.context = dct['context'] self.chain = CallChain(self, self.context, pipelined=True) - if self._play_context.become: + if self.become: self.login_context = dct['via'] else: self.login_context = self.context @@ -926,7 +926,7 @@ def reset(self): self.close() inventory_name, stack = self._build_stack() - if self._play_context.become: + if self.become: stack = stack[:-1] worker_model = ansible_mitogen.process.get_worker_model() diff --git a/ansible_mitogen/mixins.py b/ansible_mitogen/mixins.py index 1b6512e85..3953eb52e 100644 --- a/ansible_mitogen/mixins.py +++ b/ansible_mitogen/mixins.py @@ -294,7 +294,7 @@ def _remote_expand_user(self, path, sudoable=True): if not path.startswith('~'): # /home/foo -> /home/foo return path - if sudoable or not self._play_context.become: + if sudoable or not self._connection.become: if path == '~': # ~ -> /home/dmw return self._connection.homedir diff --git a/ansible_mitogen/transport_config.py b/ansible_mitogen/transport_config.py index a9f672099..596737ca2 100644 --- a/ansible_mitogen/transport_config.py +++ b/ansible_mitogen/transport_config.py @@ -417,11 +417,42 @@ def __init__(self, connection, play_context, transport, inventory_name): # used to run interpreter discovery self._action = connection._action + def _become_option(self, name): + plugin = self._connection.become + return plugin.get_option(name, self._task_vars, self._play_context) + try: + if name not in plugin._options: + value, _ = plugin.get_option_and_origin( + name, hostvars=self._task_vars, + #playcontext=self._play_context, + ) + plugin.set_option(name, value) + return plugin._options.get(name) + except AttributeError as exc: + raise + LOG.error('become plugin=%r: %s', plugin, exc) + except KeyError: + raise + if name not in {'become_user', 'become_pass', 'become_flags', 'become_exe'}: + raise + LOG.error( + 'Used PlayContext fallback for become plugin=%r, option=%r, connection=%r, transport=%r, action=%r', + plugin, name, self._connection, self._transport, self._action, + ) + try: + return getattr(self._play_context, name) + except AttributeError: + LOG.error('PlayContext did not have attribute %r', name) + return None + def _connection_option(self, name): try: return self._connection.get_option(name, hostvars=self._task_vars) except KeyError: - LOG.debug('Used PlayContext fallback for option=%r', name) + LOG.error( + 'Used PlayContext fallback for connection plugin=%r, option=%r', + self._connection, name, + ) return getattr(self._play_context, name) def transport(self): @@ -437,13 +468,13 @@ def remote_user(self): return self._connection_option('remote_user') def become(self): - return self._play_context.become + return self._connection.become def become_method(self): return self._play_context.become_method def become_user(self): - return self._play_context.become_user + return self._become_option('become_user') def become_pass(self): # become_pass is owned/provided by the active become plugin. However diff --git a/tests/ansible/hosts/default.hosts b/tests/ansible/hosts/default.hosts index bebe90df9..ef05803eb 100644 --- a/tests/ansible/hosts/default.hosts +++ b/tests/ansible/hosts/default.hosts @@ -25,6 +25,20 @@ tt-bare [tt_targets_bare:vars] ansible_host=localhost +[tt_become_bare] +tt-become-bare + +[tt_become_bare:vars] +ansible_host=localhost +ansible_user="{{ lookup('pipe', 'whoami') }}" + +[tt_become_by_inv] +tt-become-user ansible_become=true ansible_become_user="{{ 'root' | trim }}" + +[tt_become_by_inv:vars] +ansible_host=localhost +ansible_user="{{ lookup('pipe', 'whoami') }}" + [tt_targets_inventory] tt-password ansible_password="{{ 'has_sudo_nopw_password' | trim }}" ansible_user=mitogen__has_sudo_nopw tt-port ansible_password=has_sudo_nopw_password ansible_port="{{ 22 | int }}" ansible_user=mitogen__has_sudo_nopw diff --git a/tests/ansible/integration/become/all.yml b/tests/ansible/integration/become/all.yml index c9c331ddb..1b507e166 100644 --- a/tests/ansible/integration/become/all.yml +++ b/tests/ansible/integration/become/all.yml @@ -5,3 +5,7 @@ - import_playbook: sudo_nopassword.yml - import_playbook: sudo_password.yml - import_playbook: sudo_requiretty.yml +- import_playbook: templated_by_inv.yml +- import_playbook: templated_by_play_keywords.yml +- import_playbook: templated_by_play_vars.yml +- import_playbook: templated_by_task_keywords.yml diff --git a/tests/ansible/integration/become/templated_by_inv.yml b/tests/ansible/integration/become/templated_by_inv.yml new file mode 100644 index 000000000..98b68f059 --- /dev/null +++ b/tests/ansible/integration/become/templated_by_inv.yml @@ -0,0 +1,14 @@ +- name: integration/become/templated_by_inv.yml + hosts: tt_become_by_inv + gather_facts: false + tasks: + - meta: reset_connection + - name: Templated become in inventory + command: + cmd: whoami + changed_when: false + check_mode: false + register: become_templated_by_inv_whoami + failed_when: + - become_templated_by_inv_whoami is failed + or become_templated_by_inv_whoami.stdout != 'root' diff --git a/tests/ansible/integration/become/templated_by_play_keywords.yml b/tests/ansible/integration/become/templated_by_play_keywords.yml new file mode 100644 index 000000000..e588c18f6 --- /dev/null +++ b/tests/ansible/integration/become/templated_by_play_keywords.yml @@ -0,0 +1,16 @@ +- name: integration/become/templated_by_play_keywords.yml + hosts: tt_become_bare + gather_facts: false + become: true + become_user: "{{ 'root' | trim }}" + tasks: + - meta: reset_connection + - name: Templated become by play keywords + command: + cmd: whoami + changed_when: false + check_mode: false + register: become_templated_by_play_keywords_whoami + failed_when: + - become_templated_by_play_keywords_whoami is failed + or become_templated_by_play_keywords_whoami.stdout != 'root' diff --git a/tests/ansible/integration/become/templated_by_play_vars.yml b/tests/ansible/integration/become/templated_by_play_vars.yml new file mode 100644 index 000000000..5618f7cc9 --- /dev/null +++ b/tests/ansible/integration/become/templated_by_play_vars.yml @@ -0,0 +1,16 @@ +- name: integration/become/templated_by_play_vars.yml + hosts: tt_become_bare + gather_facts: false + vars: + ansible_become: true + ansible_become_user: "{{ 'root' | trim }}" + tasks: + - name: Templated become by play vars + command: + cmd: whoami + changed_when: false + check_mode: false + register: become_templated_by_play_vars_whoami + failed_when: + - become_templated_by_play_vars_whoami is failed + or become_templated_by_play_vars_whoami.stdout != 'root' diff --git a/tests/ansible/integration/become/templated_by_task_keywords.yml b/tests/ansible/integration/become/templated_by_task_keywords.yml new file mode 100644 index 000000000..52fda1116 --- /dev/null +++ b/tests/ansible/integration/become/templated_by_task_keywords.yml @@ -0,0 +1,27 @@ +- name: integration/become/templated_by_task_keywords.yml + hosts: tt_become_bare + gather_facts: false + # FIXME Resetting the connection shouldn't require credentials + # https://github.com/mitogen-hq/mitogen/issues/1132 + become: true + become_user: "{{ 'root' | trim }}" + tasks: + - name: Reset connection to target that will be delegate_to + meta: reset_connection + +- name: Test connection template by task keywords, with delegate_to + hosts: test-targets[0] + gather_facts: false + tasks: + - name: Templated become by task keywords, with delegate_to + become: true + become_user: "{{ 'root' | trim }}" + delegate_to: "{{ groups.tt_become_bare[0] }}" + command: + cmd: whoami + changed_when: false + check_mode: false + register: become_templated_by_task_with_delegate_to_whoami + failed_when: + - become_templated_by_task_with_delegate_to_whoami is failed + or become_templated_by_task_with_delegate_to_whoami.stdout != 'root' diff --git a/tests/ansible/templates/test-targets.j2 b/tests/ansible/templates/test-targets.j2 index 0fdef20b0..47f2ccd4c 100644 --- a/tests/ansible/templates/test-targets.j2 +++ b/tests/ansible/templates/test-targets.j2 @@ -48,6 +48,26 @@ ansible_host={{ tt.hostname }} ansible_port={{ tt.port }} ansible_python_interpreter={{ tt.python_path }} +[tt_become_bare] +tt-become-bare + +[tt_become_bare:vars] +ansible_host={{ tt.hostname }} +ansible_password=has_sudo_nopw_password +ansible_port={{ tt.port }} +ansible_python_interpreter={{ tt.python_path }} +ansible_user=mitogen__has_sudo_nopw + +[tt_become_by_inv] +tt-become-user ansible_become=true ansible_become_user="{{ '{{' }} 'root' | trim {{ '}}' }}" + +[tt_become_by_inv:vars] +ansible_host={{ tt.hostname }} +ansible_password=has_sudo_nopw_password +ansible_port={{ tt.port }} +ansible_python_interpreter={{ tt.python_path }} +ansible_user=mitogen__has_sudo_nopw + [tt_targets_inventory] tt-password ansible_password="{{ '{{' }} 'has_sudo_nopw_password' | trim {{ '}}' }}" ansible_port={{ tt.port }} ansible_user=mitogen__has_sudo_nopw tt-port ansible_password=has_sudo_nopw_password ansible_port="{{ '{{' }} {{ tt.port }} | int {{ '}}' }}" ansible_user=mitogen__has_sudo_nopw