diff --git a/.readthedocs.yml b/.readthedocs.yml index 464c78260..80000ee7f 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -4,11 +4,14 @@ sphinx: configuration: docs/conf.py build: - os: ubuntu-20.04 - tools: - python: '3.10' + os: ubuntu-20.04 + tools: + python: '3.10' python: install: - requirements: docs-requirements.txt - - requirements: requirements.txt + - method: pip + path: . + extra_requirements: + - ssh diff --git a/Dockerfile b/Dockerfile index ef9b886cd..3476c6d03 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1 + ARG PYTHON_VERSION=3.10 FROM python:${PYTHON_VERSION} diff --git a/Dockerfile-docs b/Dockerfile-docs index e993822b8..11adbfe85 100644 --- a/Dockerfile-docs +++ b/Dockerfile-docs @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1 + ARG PYTHON_VERSION=3.10 FROM python:${PYTHON_VERSION} diff --git a/docker/api/container.py b/docker/api/container.py index f600be181..ce483710c 100644 --- a/docker/api/container.py +++ b/docker/api/container.py @@ -826,11 +826,12 @@ def logs(self, container, stdout=True, stderr=True, stream=False, tail (str or int): Output specified number of lines at the end of logs. Either an integer of number of lines or the string ``all``. Default ``all`` - since (datetime or int): Show logs since a given datetime or - integer epoch (in seconds) + since (datetime, int, or float): Show logs since a given datetime, + integer epoch (in seconds) or float (in fractional seconds) follow (bool): Follow log output. Default ``False`` - until (datetime or int): Show logs that occurred before the given - datetime or integer epoch (in seconds) + until (datetime, int, or float): Show logs that occurred before + the given datetime, integer epoch (in seconds), or + float (in fractional seconds) Returns: (generator or str) @@ -855,9 +856,11 @@ def logs(self, container, stdout=True, stderr=True, stream=False, params['since'] = utils.datetime_to_timestamp(since) elif (isinstance(since, int) and since > 0): params['since'] = since + elif (isinstance(since, float) and since > 0.0): + params['since'] = since else: raise errors.InvalidArgument( - 'since value should be datetime or positive int, ' + 'since value should be datetime or positive int/float, ' 'not {}'.format(type(since)) ) @@ -870,9 +873,11 @@ def logs(self, container, stdout=True, stderr=True, stream=False, params['until'] = utils.datetime_to_timestamp(until) elif (isinstance(until, int) and until > 0): params['until'] = until + elif (isinstance(until, float) and until > 0.0): + params['until'] = until else: raise errors.InvalidArgument( - 'until value should be datetime or positive int, ' + 'until value should be datetime or positive int/float, ' 'not {}'.format(type(until)) ) diff --git a/docker/models/containers.py b/docker/models/containers.py index 6661b213b..4508557d2 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -290,11 +290,12 @@ def logs(self, **kwargs): tail (str or int): Output specified number of lines at the end of logs. Either an integer of number of lines or the string ``all``. Default ``all`` - since (datetime or int): Show logs since a given datetime or - integer epoch (in seconds) + since (datetime, int, or float): Show logs since a given datetime, + integer epoch (in seconds) or float (in nanoseconds) follow (bool): Follow log output. Default ``False`` - until (datetime or int): Show logs that occurred before the given - datetime or integer epoch (in seconds) + until (datetime, int, or float): Show logs that occurred before + the given datetime, integer epoch (in seconds), or + float (in nanoseconds) Returns: (generator or str): Logs from the container. diff --git a/docker/utils/socket.py b/docker/utils/socket.py index 4a2076ec4..5aca30b17 100644 --- a/docker/utils/socket.py +++ b/docker/utils/socket.py @@ -18,6 +18,11 @@ class SocketError(Exception): pass +# NpipeSockets have their own error types +# pywintypes.error: (109, 'ReadFile', 'The pipe has been ended.') +NPIPE_ENDED = 109 + + def read(socket, n=4096): """ Reads at most n bytes from socket @@ -37,6 +42,15 @@ def read(socket, n=4096): except OSError as e: if e.errno not in recoverable_errors: raise + except Exception as e: + is_pipe_ended = (isinstance(socket, NpipeSocket) and + len(e.args) > 0 and + e.args[0] == NPIPE_ENDED) + if is_pipe_ended: + # npipes don't support duplex sockets, so we interpret + # a PIPE_ENDED error as a close operation (0-length read). + return 0 + raise def read_exactly(socket, n): diff --git a/tests/Dockerfile b/tests/Dockerfile index 2cac785d9..bf95cd6a3 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -1,5 +1,7 @@ -# syntax = docker/dockerfile:1.4 +# syntax=docker/dockerfile:1 + ARG PYTHON_VERSION=3.10 + FROM python:${PYTHON_VERSION} ARG APT_MIRROR diff --git a/tests/Dockerfile-dind-certs b/tests/Dockerfile-dind-certs index 6e711892c..288a340ab 100644 --- a/tests/Dockerfile-dind-certs +++ b/tests/Dockerfile-dind-certs @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1 + ARG PYTHON_VERSION=3.10 FROM python:${PYTHON_VERSION} diff --git a/tests/Dockerfile-ssh-dind b/tests/Dockerfile-ssh-dind index 22c707a07..0da15aa40 100644 --- a/tests/Dockerfile-ssh-dind +++ b/tests/Dockerfile-ssh-dind @@ -1,18 +1,20 @@ +# syntax=docker/dockerfile:1 + ARG API_VERSION=1.41 ARG ENGINE_VERSION=20.10 FROM docker:${ENGINE_VERSION}-dind RUN apk add --no-cache --upgrade \ - openssh + openssh COPY tests/ssh/config/server /etc/ssh/ -RUN chmod -R 600 /etc/ssh # set authorized keys for client paswordless connection COPY tests/ssh/config/client/id_rsa.pub /root/.ssh/authorized_keys -RUN chmod -R 600 /root/.ssh # RUN echo "root:root" | chpasswd -RUN ln -s /usr/local/bin/docker /usr/bin/docker +RUN chmod -R 600 /etc/ssh \ + && chmod -R 600 /root/.ssh \ + && ln -s /usr/local/bin/docker /usr/bin/docker EXPOSE 22 diff --git a/tests/integration/api_volume_test.py b/tests/integration/api_volume_test.py index 8e7dd3afb..2085e8311 100644 --- a/tests/integration/api_volume_test.py +++ b/tests/integration/api_volume_test.py @@ -57,11 +57,10 @@ def test_force_remove_volume(self): @requires_api_version('1.25') def test_prune_volumes(self): - name = 'hopelessmasquerade' - self.client.create_volume(name) - self.tmp_volumes.append(name) + v = self.client.create_volume() + self.tmp_volumes.append(v["Name"]) result = self.client.prune_volumes() - assert name in result['VolumesDeleted'] + assert v["Name"] in result['VolumesDeleted'] def test_remove_nonexistent_volume(self): name = 'shootthebullet' diff --git a/tests/unit/api_container_test.py b/tests/unit/api_container_test.py index 8f120f4d4..d7b356c44 100644 --- a/tests/unit/api_container_test.py +++ b/tests/unit/api_container_test.py @@ -1279,6 +1279,22 @@ def test_log_since(self): stream=False ) + def test_log_since_with_float(self): + ts = 809222400.000000 + with mock.patch('docker.api.client.APIClient.inspect_container', + fake_inspect_container): + self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=False, + follow=False, since=ts) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/logs', + params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, + 'tail': 'all', 'since': ts}, + timeout=DEFAULT_TIMEOUT_SECONDS, + stream=False + ) + def test_log_since_with_datetime(self): ts = 809222400 time = datetime.datetime.utcfromtimestamp(ts) @@ -1301,7 +1317,7 @@ def test_log_since_with_invalid_value_raises_error(self): fake_inspect_container): with pytest.raises(docker.errors.InvalidArgument): self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=False, - follow=False, since=42.42) + follow=False, since="42.42") def test_log_tty(self): m = mock.Mock()