diff --git a/docs/changelog.rst b/docs/changelog.rst
index 0d1a14a2..63b4da12 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -21,6 +21,8 @@ To avail of fixes in an unreleased version, please download a ZIP file
Unreleased
----------
+* :gh:issue:`1087` Fix :exc:`mitogen.core.StreamError` when Ansible template
+ module is called with a ``dest:`` filename that has an extension
v0.3.9 (2024-08-13)
diff --git a/docs/contributors.rst b/docs/contributors.rst
index ed7fef11..1a590869 100644
--- a/docs/contributors.rst
+++ b/docs/contributors.rst
@@ -125,6 +125,7 @@ sponsorship and outstanding future-thinking of its early adopters.
rkrzr
jgadling
John F Wall — Making Ansible Great with Massive Parallelism
+ Jonathan Rosser
KennethC
Luca Berruti
Lewis Bellwood — Happy to be apart of a great project.
diff --git a/tests/ansible/regression/all.yml b/tests/ansible/regression/all.yml
index 31541e9f..a4272805 100644
--- a/tests/ansible/regression/all.yml
+++ b/tests/ansible/regression/all.yml
@@ -16,3 +16,4 @@
- import_playbook: issue_776__load_plugins_called_twice.yml
- import_playbook: issue_952__ask_become_pass.yml
- import_playbook: issue_1066__add_host__host_key_checking.yml
+- import_playbook: issue_1087__template_streamerror.yml
diff --git a/tests/ansible/regression/issue_1087__template_streamerror.yml b/tests/ansible/regression/issue_1087__template_streamerror.yml
new file mode 100644
index 00000000..fa950ea4
--- /dev/null
+++ b/tests/ansible/regression/issue_1087__template_streamerror.yml
@@ -0,0 +1,43 @@
+- name: regression/issue_1087__template_streamerror.yml
+ # Ansible's template module has been seen to raise mitogen.core.StreamError
+ # iif there is a with_items loop and the destination path has an extension.
+ # This printed an error message and left file permissions incorrect,
+ # but did not cause the task/playbook to fail.
+ hosts: test-targets
+ gather_facts: false
+ become: false
+ vars:
+ foos:
+ - dest: /tmp/foo
+ - dest: /tmp/foo.txt
+ foo: Foo
+ bar: Bar
+ tasks:
+ - block:
+ - name: Test template does not cause StreamError
+ delegate_to: localhost
+ run_once: true
+ environment:
+ ANSIBLE_VERBOSITY: "{{ ansible_verbosity }}"
+ command:
+ cmd: >
+ ansible-playbook
+ {% for inv in ansible_inventory_sources %}
+ -i "{{ inv }}"
+ {% endfor %}
+ regression/template_test.yml
+ chdir: ../
+ register: issue_1087_cmd
+ failed_when:
+ - issue_1087_cmd is failed
+ or issue_1087_cmd.stdout is search('ERROR|mitogen\.core\.CallError')
+ or issue_1087_cmd.stderr is search('ERROR|mitogen\.core\.CallError')
+
+ always:
+ - name: Cleanup
+ file:
+ path: "{{ item.dest }}"
+ state: absent
+ with_items: "{{ foos }}"
+ tags:
+ - issue_1087
diff --git a/tests/ansible/regression/template_test.yml b/tests/ansible/regression/template_test.yml
new file mode 100644
index 00000000..0b7dd36d
--- /dev/null
+++ b/tests/ansible/regression/template_test.yml
@@ -0,0 +1,28 @@
+- name: regression/template_test.yml
+ # Ansible's template module has been seen to raise mitogen.core.StreamError
+ # iif there is a with_items loop and the destination path has an extension
+ hosts: test-targets
+ gather_facts: false
+ become: false
+ vars:
+ foos:
+ - dest: /tmp/foo
+ - dest: /tmp/foo.txt
+ foo: Foo
+ bar: Bar
+ tasks:
+ - block:
+ - name: Template files
+ template:
+ src: foo.bar.j2
+ dest: "{{ item.dest }}"
+ mode: u=rw,go=r
+ # This has to be with_items, loop: doesn't trigger the bug
+ with_items: "{{ foos }}"
+
+ always:
+ - name: Cleanup
+ file:
+ path: "{{ item.dest }}"
+ state: absent
+ with_items: "{{ foos }}"
diff --git a/tests/ansible/regression/templates/foo.bar.j2 b/tests/ansible/regression/templates/foo.bar.j2
new file mode 100644
index 00000000..ca51a6f4
--- /dev/null
+++ b/tests/ansible/regression/templates/foo.bar.j2
@@ -0,0 +1 @@
+A {{ foo }} walks into a {{ bar }}. Ow!