Skip to content

Commit e18af99

Browse files
committed
fix: binary mode for siphon and AtomicFileReplacement
Signed-off-by: Martin Styk <mart.styk@gmail.com>
1 parent d0104d0 commit e18af99

File tree

4 files changed

+60
-7
lines changed

4 files changed

+60
-7
lines changed

Common/bkr/common/helpers.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,10 @@ class AtomicFileReplacement(object):
135135
and replace_dest can also be called directly if needed
136136
"""
137137

138-
def __init__(self, dest_path, mode=0o644):
138+
def __init__(self, dest_path, mode=0o644, binary=False):
139139
self.dest_path = dest_path
140140
self.mode = mode
141+
self.binary = binary
141142
self._temp_info = None
142143

143144
@property
@@ -152,7 +153,7 @@ def create_temp(self):
152153
dirname, basename = os.path.split(self.dest_path)
153154
fd, temp_path = tempfile.mkstemp(prefix='.' + basename, dir=dirname)
154155
try:
155-
f = os.fdopen(fd, 'w')
156+
f = os.fdopen(fd, 'wb' if self.binary else 'w')
156157
except:
157158
os.unlink(temp_path)
158159
raise
@@ -247,7 +248,8 @@ def siphon(src, dest):
247248
break
248249

249250
if six.PY3 and isinstance(chunk, bytes):
250-
chunk = chunk.decode('utf-8')
251+
if not hasattr(dest, 'mode') or 'b' not in dest.mode:
252+
chunk = chunk.decode('utf-8')
251253

252254
dest.write(chunk)
253255

Common/bkr/common/test_helpers.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33
# the Free Software Foundation; either version 2 of the License, or
44
# (at your option) any later version.
55

6+
import os
7+
import shutil
8+
import tempfile
69
import unittest
710

11+
import six
12+
13+
from bkr.common.helpers import AtomicFileReplacement, siphon
814
from bkr.common.helpers_six import parse_content_type
915

1016

@@ -16,3 +22,48 @@ def test_ok(self):
1622

1723
def test_empty(self):
1824
self.assertEqual(parse_content_type(''), '')
25+
26+
27+
class TestSiphon(unittest.TestCase):
28+
29+
def test_siphon_text_to_text_file(self):
30+
src = six.StringIO(u'hello world')
31+
dest = six.StringIO()
32+
siphon(src, dest)
33+
self.assertEqual(dest.getvalue(), u'hello world')
34+
35+
def test_siphon_binary_to_binary_file(self):
36+
binary_data = b'\x00\x01\xcd\xfe\xff' * 1000
37+
src = six.BytesIO(binary_data)
38+
tmp = tempfile.NamedTemporaryFile(mode='wb', delete=False)
39+
try:
40+
siphon(src, tmp)
41+
tmp.close()
42+
with open(tmp.name, 'rb') as f:
43+
self.assertEqual(f.read(), binary_data)
44+
finally:
45+
os.unlink(tmp.name)
46+
47+
48+
class TestAtomicFileReplacement(unittest.TestCase):
49+
50+
def setUp(self):
51+
self.tmpdir = tempfile.mkdtemp()
52+
53+
def tearDown(self):
54+
shutil.rmtree(self.tmpdir)
55+
56+
def test_text_mode(self):
57+
dest_path = os.path.join(self.tmpdir, 'textfile')
58+
with AtomicFileReplacement(dest_path) as f:
59+
f.write(u'hello')
60+
with open(dest_path, 'r') as f:
61+
self.assertEqual(f.read(), 'hello')
62+
63+
def test_binary_mode(self):
64+
binary_data = b'\x00\x01\xcd\xfe\xff'
65+
dest_path = os.path.join(self.tmpdir, 'binfile')
66+
with AtomicFileReplacement(dest_path, binary=True) as f:
67+
f.write(binary_data)
68+
with open(dest_path, 'rb') as f:
69+
self.assertEqual(f.read(), binary_data)

LabController/src/bkr/labcontroller/netboot.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def copy_default_loader_images():
110110
def fetch_bootloader_image(fqdn, fqdn_dir, distro_tree_id, image_url):
111111
timeout = get_conf().get('IMAGE_FETCH_TIMEOUT')
112112
logger.debug('Fetching bootloader image %s for %s', image_url, fqdn)
113-
with atomically_replaced_file(os.path.join(fqdn_dir, 'image')) as dest:
113+
with atomically_replaced_file(os.path.join(fqdn_dir, 'image'), binary=True) as dest:
114114
try:
115115
siphon(urllib.request.urlopen(image_url, timeout=timeout), dest)
116116
except Exception as e:
@@ -146,13 +146,13 @@ def fetch_images(distro_tree_id, kernel_url, initrd_url, fqdn):
146146

147147
timeout = get_conf().get('IMAGE_FETCH_TIMEOUT')
148148
logger.debug('Fetching kernel %s for %s', kernel_url, fqdn)
149-
with atomically_replaced_file(os.path.join(images_dir, 'kernel')) as dest:
149+
with atomically_replaced_file(os.path.join(images_dir, 'kernel'), binary=True) as dest:
150150
try:
151151
siphon(urllib.request.urlopen(kernel_url, timeout=timeout), dest)
152152
except Exception as e:
153153
raise ImageFetchingError(kernel_url, distro_tree_id, e)
154154
logger.debug('Fetching initrd %s for %s', initrd_url, fqdn)
155-
with atomically_replaced_file(os.path.join(images_dir, 'initrd')) as dest:
155+
with atomically_replaced_file(os.path.join(images_dir, 'initrd'), binary=True) as dest:
156156
try:
157157
siphon(urllib.request.urlopen(initrd_url, timeout=timeout), dest)
158158
except Exception as e:

LabController/src/bkr/labcontroller/pxemenu.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def _get_images(tftp_root, distro_tree_id, url, images):
8888
else:
8989
image_url = urllib.parse.urljoin(url, path)
9090
print('Fetching %s %s for distro tree %s' % (image_type, image_url, distro_tree_id))
91-
with atomically_replaced_file(dest_path) as dest:
91+
with atomically_replaced_file(dest_path, binary=True) as dest:
9292
siphon(urllib.request.urlopen(image_url), dest)
9393

9494

0 commit comments

Comments
 (0)