Skip to content

Commit 0b9c964

Browse files
committed
Merge remote-tracking branch 'origin/dmw'
* origin/dmw: issue #615: fix up FileService tests for new logic issue #615: another Py3x fix. issue #615: Py3x fix. issue #615: update Changelog. issue #615: use FileService for target->controll file transfers
2 parents ceddc5c + 7d4ae6c commit 0b9c964

File tree

6 files changed

+101
-31
lines changed

6 files changed

+101
-31
lines changed

ansible_mitogen/connection.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -950,11 +950,12 @@ def fetch_file(self, in_path, out_path):
950950
:param str out_path:
951951
Local filesystem path to write.
952952
"""
953-
output = self.get_chain().call(
954-
ansible_mitogen.target.read_path,
955-
mitogen.utils.cast(in_path),
953+
self._connect()
954+
ansible_mitogen.target.transfer_file(
955+
context=self.context,
956+
in_path=in_path,
957+
out_path=out_path
956958
)
957-
ansible_mitogen.target.write_path(out_path, output)
958959

959960
def put_data(self, out_path, data, mode=None, utimes=None):
960961
"""

docs/changelog.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ Mitogen for Ansible
116116
module, and for any other action plug-ins that establish new connections of
117117
their own.
118118

119+
* `#615 <https://github.com/dw/mitogen/issues/615>`_: streaming file transfer
120+
is implemented for the ``fetch`` and any other action that transfers files
121+
from the target to the controller. Previously the file would be sent as a
122+
single message, requiring the file to fit in RAM and be smaller than internal
123+
limits on the size of a single message.
124+
119125
* `7ae926b3 <https://github.com/dw/mitogen/commit/7ae926b3>`_: the
120126
``lineinfile`` module began leaking writable temporary file descriptors since
121127
Ansible 2.7.0. When ``lineinfile`` was used to create or modify a script, and
@@ -219,6 +225,7 @@ bug reports, testing, features and fixes in this release contributed by
219225
`Szabó Dániel Ernő <https://github.com/r3ap3rpy>`_,
220226
`Ulrich Schreiner <https://github.com/ulrichSchreiner>`_,
221227
`Yuki Nishida <https://github.com/yuki-nishida-exa>`_,
228+
`@alexhexabeam <https://github.com/alexhexabeam>`_,
222229
`@DavidVentura <https://github.com/DavidVentura>`_,
223230
`@ghp-rr <https://github.com/ghp-rr>`_,
224231
`@rizzly <https://github.com/rizzly>`_, and

mitogen/core.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,12 @@ def bytes_partition(s, sep):
395395
return _partition(s, sep, s.find) or (s, '', '')
396396

397397

398+
def _has_parent_authority(context_id):
399+
return (
400+
(context_id == mitogen.context_id) or
401+
(context_id in mitogen.parent_ids)
402+
)
403+
398404
def has_parent_authority(msg, _stream=None):
399405
"""
400406
Policy function for use with :class:`Receiver` and
@@ -403,8 +409,7 @@ def has_parent_authority(msg, _stream=None):
403409
<Stream.auth_id>` has been set to that of a parent context or the current
404410
context.
405411
"""
406-
return (msg.auth_id == mitogen.context_id or
407-
msg.auth_id in mitogen.parent_ids)
412+
return _has_parent_authority(msg.auth_id)
408413

409414

410415
def _signals(obj, signal):

mitogen/service.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,11 @@ def fetch(self, path, sender, msg):
10231023
:raises Error:
10241024
Unregistered path, or Sender did not match requestee context.
10251025
"""
1026-
if path not in self._paths and not self._prefix_is_authorized(path):
1026+
if (
1027+
(path not in self._paths) and
1028+
(not self._prefix_is_authorized(path)) and
1029+
(not mitogen.core._has_parent_authority(msg.auth_id))
1030+
):
10271031
msg.reply(mitogen.core.CallError(
10281032
Error(self.unregistered_msg % (path,))
10291033
))

tests/ansible/tests/connection_test.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,21 @@ def test_is_junk(self):
9999
self.assertEquals(None, self.func({1:2}))
100100

101101

102+
class FetchFileTest(ConnectionMixin, testlib.TestCase):
103+
def test_success(self):
104+
with tempfile.NamedTemporaryFile(prefix='mitotest') as ifp:
105+
with tempfile.NamedTemporaryFile(prefix='mitotest') as ofp:
106+
ifp.write(b'x' * (1048576 * 4))
107+
ifp.flush()
108+
ifp.seek(0)
109+
110+
self.conn.fetch_file(ifp.name, ofp.name)
111+
# transfer_file() uses os.rename rather than direct data
112+
# overwrite, so we must reopen.
113+
with open(ofp.name, 'rb') as fp:
114+
self.assertEquals(ifp.read(), fp.read())
115+
116+
102117
class PutDataTest(ConnectionMixin, testlib.TestCase):
103118
def test_out_path(self):
104119
path = tempfile.mktemp(prefix='mitotest')

tests/file_service_test.py

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,26 @@ def replyable_msg(self, **kwargs):
2222
return recv, msg
2323

2424
def test_unauthorized(self):
25+
l1 = self.router.local()
26+
2527
service = self.klass(self.router)
26-
recv, msg = self.replyable_msg()
27-
service.fetch(
28-
path='/etc/shadow',
29-
sender=None,
30-
msg=msg,
28+
pool = mitogen.service.Pool(
29+
router=self.router,
30+
services=[service],
31+
size=1,
3132
)
32-
e = self.assertRaises(mitogen.core.CallError,
33-
lambda: recv.get().unpickle())
33+
try:
34+
e = self.assertRaises(mitogen.core.CallError,
35+
lambda: l1.call(
36+
mitogen.service.FileService.get,
37+
context=self.router.myself(),
38+
path='/etc/shadow',
39+
out_fp=None,
40+
)
41+
)
42+
finally:
43+
pool.stop()
44+
3445
expect = service.unregistered_msg % ('/etc/shadow',)
3546
self.assertTrue(expect in e.args[0])
3647

@@ -85,30 +96,57 @@ def test_prefix_authorized(self):
8596
self._validate_response(recv.get().unpickle())
8697

8798
def test_prefix_authorized_abspath_bad(self):
88-
recv = mitogen.core.Receiver(self.router)
99+
l1 = self.router.local()
100+
89101
service = self.klass(self.router)
90102
service.register_prefix('/etc')
91-
recv, msg = self.replyable_msg()
92-
service.fetch(
93-
path='/etc/foo/bar/../../../passwd',
94-
sender=recv.to_sender(),
95-
msg=msg,
103+
104+
pool = mitogen.service.Pool(
105+
router=self.router,
106+
services=[service],
107+
size=1,
96108
)
97-
self.assertEquals(None, recv.get().unpickle())
109+
path = '/etc/foo/bar/../../../passwd'
110+
try:
111+
e = self.assertRaises(mitogen.core.CallError,
112+
lambda: l1.call(
113+
mitogen.service.FileService.get,
114+
context=self.router.myself(),
115+
path=path,
116+
out_fp=None,
117+
)
118+
)
119+
finally:
120+
pool.stop()
121+
122+
expect = service.unregistered_msg % (path,)
123+
self.assertTrue(expect in e.args[0])
124+
125+
def test_prefix_authorized_abspath_good(self):
126+
l1 = self.router.local()
98127

99-
def test_prefix_authorized_abspath_bad(self):
100-
recv = mitogen.core.Receiver(self.router)
101128
service = self.klass(self.router)
102129
service.register_prefix('/etc')
103-
recv, msg = self.replyable_msg()
104-
service.fetch(
105-
path='/etc/../shadow',
106-
sender=recv.to_sender(),
107-
msg=msg,
130+
path = '/etc/../shadow'
131+
132+
pool = mitogen.service.Pool(
133+
router=self.router,
134+
services=[service],
135+
size=1,
108136
)
109-
e = self.assertRaises(mitogen.core.CallError,
110-
lambda: recv.get().unpickle())
111-
expect = service.unregistered_msg % ('/etc/../shadow',)
137+
try:
138+
e = self.assertRaises(mitogen.core.CallError,
139+
lambda: l1.call(
140+
mitogen.service.FileService.get,
141+
context=self.router.myself(),
142+
path=path,
143+
out_fp=None
144+
)
145+
)
146+
finally:
147+
pool.stop()
148+
149+
expect = service.unregistered_msg % (path,)
112150
self.assertTrue(expect in e.args[0])
113151

114152

0 commit comments

Comments
 (0)