diff --git a/.circleci/config.yml b/.circleci/config.yml index 6ee2a60e5..66a87d1d0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ jobs: command: brew update > /dev/null && brew upgrade python - run: name: install tox - command: sudo pip install --upgrade tox==2.1.1 + command: sudo pip3 install --upgrade tox==2.1.1 - run: name: unit tests command: tox -e py27,py36 -- tests/unit diff --git a/CHANGELOG.md b/CHANGELOG.md index c113572ca..ab0a2c354 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,8 @@ Change log - Fixed a bug occurring during builds caused by files with a negative `mtime` values in the build context +- Fixed an encoding bug when streaming build progress + 1.19.0 (2018-02-07) ------------------- diff --git a/compose/__init__.py b/compose/__init__.py index d2e3f696b..1c5a409f1 100644 --- a/compose/__init__.py +++ b/compose/__init__.py @@ -1,4 +1,4 @@ from __future__ import absolute_import from __future__ import unicode_literals -__version__ = '1.20.0-rc2' +__version__ = '1.20.0' diff --git a/compose/progress_stream.py b/compose/progress_stream.py index 5314f89fd..5e709770a 100644 --- a/compose/progress_stream.py +++ b/compose/progress_stream.py @@ -8,6 +8,14 @@ class StreamOutputError(Exception): pass +def write_to_stream(s, stream): + try: + stream.write(s) + except UnicodeEncodeError: + encoding = getattr(stream, 'encoding', 'ascii') + stream.write(s.encode(encoding, errors='replace').decode(encoding)) + + def stream_output(output, stream): is_terminal = hasattr(stream, 'isatty') and stream.isatty() stream = utils.get_output_stream(stream) @@ -34,18 +42,18 @@ def stream_output(output, stream): if image_id not in lines: lines[image_id] = len(lines) - stream.write("\n") + write_to_stream("\n", stream) diff = len(lines) - lines[image_id] # move cursor up `diff` rows - stream.write("%c[%dA" % (27, diff)) + write_to_stream("%c[%dA" % (27, diff), stream) print_output_event(event, stream, is_terminal) if 'id' in event: # move cursor back down - stream.write("%c[%dB" % (27, diff)) + write_to_stream("%c[%dB" % (27, diff), stream) stream.flush() @@ -60,36 +68,36 @@ def print_output_event(event, stream, is_terminal): if is_terminal and 'stream' not in event: # erase current line - stream.write("%c[2K\r" % 27) + write_to_stream("%c[2K\r" % 27, stream) terminator = "\r" elif 'progressDetail' in event: return if 'time' in event: - stream.write("[%s] " % event['time']) + write_to_stream("[%s] " % event['time'], stream) if 'id' in event: - stream.write("%s: " % event['id']) + write_to_stream("%s: " % event['id'], stream) if 'from' in event: - stream.write("(from %s) " % event['from']) + write_to_stream("(from %s) " % event['from'], stream) status = event.get('status', '') if 'progress' in event: - stream.write("%s %s%s" % (status, event['progress'], terminator)) + write_to_stream("%s %s%s" % (status, event['progress'], terminator), stream) elif 'progressDetail' in event: detail = event['progressDetail'] total = detail.get('total') if 'current' in detail and total: percentage = float(detail['current']) / float(total) * 100 - stream.write('%s (%.1f%%)%s' % (status, percentage, terminator)) + write_to_stream('%s (%.1f%%)%s' % (status, percentage, terminator), stream) else: - stream.write('%s%s' % (status, terminator)) + write_to_stream('%s%s' % (status, terminator), stream) elif 'stream' in event: - stream.write("%s%s" % (event['stream'], terminator)) + write_to_stream("%s%s" % (event['stream'], terminator), stream) else: - stream.write("%s%s\n" % (status, terminator)) + write_to_stream("%s%s\n" % (status, terminator), stream) def get_digest_from_pull(events): diff --git a/requirements.txt b/requirements.txt index 33462d496..743272d4e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ backports.ssl-match-hostname==3.5.0.1; python_version < '3' cached-property==1.3.0 certifi==2017.4.17 chardet==3.0.4 -docker==3.1.1 +docker==3.1.3 docker-pycreds==0.2.1 dockerpty==0.4.1 docopt==0.6.2 diff --git a/script/run/run.sh b/script/run/run.sh index dff9d982a..a739edb35 100755 --- a/script/run/run.sh +++ b/script/run/run.sh @@ -15,7 +15,7 @@ set -e -VERSION="1.20.0-rc2" +VERSION="1.20.0" IMAGE="docker/compose:$VERSION" diff --git a/setup.py b/setup.py index cf8f6dc13..e24736df0 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ install_requires = [ 'requests >= 2.6.1, != 2.11.0, != 2.12.2, != 2.18.0, < 2.19', 'texttable >= 0.9.0, < 0.10', 'websocket-client >= 0.32.0, < 1.0', - 'docker >= 3.1.1, < 4.0', + 'docker >= 3.1.3, < 4.0', 'dockerpty >= 0.4.1, < 0.5', 'six >= 1.3.0, < 2', 'jsonschema >= 2.5.1, < 3', diff --git a/tests/unit/progress_stream_test.py b/tests/unit/progress_stream_test.py index 22a6e081b..f4a0ab063 100644 --- a/tests/unit/progress_stream_test.py +++ b/tests/unit/progress_stream_test.py @@ -1,6 +1,13 @@ +# ~*~ encoding: utf-8 ~*~ from __future__ import absolute_import from __future__ import unicode_literals +import io +import os +import random +import shutil +import tempfile + from six import StringIO from compose import progress_stream @@ -66,6 +73,30 @@ class ProgressStreamTestCase(unittest.TestCase): events = progress_stream.stream_output(events, output) assert len(output.getvalue()) > 0 + def test_mismatched_encoding_stream_write(self): + tmpdir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, tmpdir, True) + + def mktempfile(encoding): + fname = os.path.join(tmpdir, hex(random.getrandbits(128))[2:-1]) + return io.open(fname, mode='w+', encoding=encoding) + + text = '就吃饭' + with mktempfile(encoding='utf-8') as tf: + progress_stream.write_to_stream(text, tf) + tf.seek(0) + assert tf.read() == text + + with mktempfile(encoding='utf-32') as tf: + progress_stream.write_to_stream(text, tf) + tf.seek(0) + assert tf.read() == text + + with mktempfile(encoding='ascii') as tf: + progress_stream.write_to_stream(text, tf) + tf.seek(0) + assert tf.read() == '???' + def test_get_digest_from_push(): digest = "sha256:abcd"