diff --git a/ansible_mitogen/connection.py b/ansible_mitogen/connection.py
index a715b2b0..4c1df1bd 100644
--- a/ansible_mitogen/connection.py
+++ b/ansible_mitogen/connection.py
@@ -814,7 +814,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
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 @@ class Connection(ansible.plugins.connection.ConnectionBase):
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 1b6512e8..3953eb52 100644
--- a/ansible_mitogen/mixins.py
+++ b/ansible_mitogen/mixins.py
@@ -294,7 +294,7 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
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 a9f67209..708c2897 100644
--- a/ansible_mitogen/transport_config.py
+++ b/ansible_mitogen/transport_config.py
@@ -417,6 +417,10 @@ class PlayContextSpec(Spec):
# 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)
+
def _connection_option(self, name):
try:
return self._connection.get_option(name, hostvars=self._task_vars)
@@ -437,13 +441,13 @@ class PlayContextSpec(Spec):
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/docs/changelog.rst b/docs/changelog.rst
index 1fad75a5..8ab68d97 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -23,6 +23,7 @@ In progress (unreleased)
* :gh:issue:`1159` CI: Reduce number of Jobs by parameterizing Mitogen Docker
SSH tests
+* :gh:issue:`1083` :mod:`ansible_mitogen`: Support templated become username.
v0.3.13 (2024-10-09)
diff --git a/docs/contributors.rst b/docs/contributors.rst
index 69dc1e76..ad35f91c 100644
--- a/docs/contributors.rst
+++ b/docs/contributors.rst
@@ -134,6 +134,7 @@ sponsorship and outstanding future-thinking of its early adopters.
luto
Mayeu a.k.a Matthieu Maury
Michael D'Silva
+ mordek
@nathanhruby
Orion Poplawski
Philippe Kueck
diff --git a/tests/ansible/hosts/default.hosts b/tests/ansible/hosts/default.hosts
index bebe90df..ef05803e 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 c9c331dd..1b507e16 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 00000000..98b68f05
--- /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 00000000..e588c18f
--- /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 00000000..5618f7cc
--- /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 00000000..52fda111
--- /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 0fdef20b..47f2ccd4 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