Skip to content

Commit 7f3692c

Browse files
delfickshin-
authored andcommitted
Fix attach method over SSL connections
Signed-off-by: Stephen Moore <[email protected]>
1 parent b1f2531 commit 7f3692c

File tree

5 files changed

+123
-12
lines changed

5 files changed

+123
-12
lines changed

Makefile

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ build:
1111
build-py3:
1212
docker build -t docker-py3 -f Dockerfile-py3 .
1313

14-
test: flake8 unit-test unit-test-py3 integration-dind
14+
build-dind-certs:
15+
docker build -t dpy-dind-certs -f tests/Dockerfile-dind-certs .
16+
17+
test: flake8 unit-test unit-test-py3 integration-dind integration-dind-ssl
1518

1619
unit-test: build
1720
docker run docker-py py.test tests/test.py tests/utils_test.py
@@ -26,10 +29,17 @@ integration-test-py3: build-py3
2629
docker run -v /var/run/docker.sock:/var/run/docker.sock docker-py3 py.test tests/integration_test.py
2730

2831
integration-dind: build build-py3
29-
docker run -d --name dpy-dind --privileged dockerswarm/dind:1.8.1 docker -d -H tcp://0.0.0.0:2375
30-
docker run --env="DOCKER_HOST=tcp://docker:2375" --link=dpy-dind:docker docker-py py.test tests/integration_test.py
31-
docker run --env="DOCKER_HOST=tcp://docker:2375" --link=dpy-dind:docker docker-py3 py.test tests/integration_test.py
32+
docker run -d --name dpy-dind --env="DOCKER_HOST=tcp://localhost:2375" --privileged dockerswarm/dind:1.8.1 docker -d -H tcp://0.0.0.0:2375
33+
docker run --volumes-from dpy-dind --env="DOCKER_HOST=tcp://docker:2375" --link=dpy-dind:docker docker-py py.test tests/integration_test.py
34+
docker run --volumes-from dpy-dind --env="DOCKER_HOST=tcp://docker:2375" --link=dpy-dind:docker docker-py3 py.test tests/integration_test.py
3235
docker rm -vf dpy-dind
3336

37+
integration-dind-ssl: build-dind-certs build build-py3
38+
docker run -d --name dpy-dind-certs dpy-dind-certs
39+
docker run -d --env="DOCKER_HOST=tcp://localhost:2375" --env="DOCKER_TLS_VERIFY=1" --env="DOCKER_CERT_PATH=/certs" --volumes-from dpy-dind-certs --name dpy-dind-ssl -v /tmp --privileged dockerswarm/dind:1.8.1 docker daemon --tlsverify --tlscacert=/certs/ca.pem --tlscert=/certs/server-cert.pem --tlskey=/certs/server-key.pem -H tcp://0.0.0.0:2375
40+
docker run --volumes-from dpy-dind-ssl --env="DOCKER_HOST=tcp://docker:2375" --env="DOCKER_TLS_VERIFY=1" --env="DOCKER_CERT_PATH=/certs" --link=dpy-dind-ssl:docker docker-py py.test tests/integration_test.py
41+
docker run --volumes-from dpy-dind-ssl --env="DOCKER_HOST=tcp://docker:2375" --env="DOCKER_TLS_VERIFY=1" --env="DOCKER_CERT_PATH=/certs" --link=dpy-dind-ssl:docker docker-py3 py.test tests/integration_test.py
42+
docker rm -vf dpy-dind-ssl dpy-dind-certs
43+
3444
flake8: build
35-
docker run docker-py flake8 docker tests
45+
docker run docker-py flake8 docker tests

docker/client.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ def _get_raw_response_socket(self, response):
188188
self._raise_for_status(response)
189189
if six.PY3:
190190
sock = response.raw._fp.fp.raw
191+
if self.base_url.startswith("https://"):
192+
sock = sock._sock
191193
else:
192194
sock = response.raw._fp.fp._sock
193195
try:
@@ -244,10 +246,7 @@ def _multiplexed_response_stream_helper(self, response):
244246
# Disable timeout on the underlying socket to prevent
245247
# Read timed out(s) for long running processes
246248
socket = self._get_raw_response_socket(response)
247-
if six.PY3:
248-
socket._sock.settimeout(None)
249-
else:
250-
socket.settimeout(None)
249+
self._disable_socket_timeout(socket)
251250

252251
while True:
253252
header = response.raw.read(constants.STREAM_HEADER_SIZE_BYTES)
@@ -276,6 +275,19 @@ def _stream_raw_result(self, response):
276275
for out in response.iter_content(chunk_size=1, decode_unicode=True):
277276
yield out
278277

278+
def _disable_socket_timeout(self, socket):
279+
""" Depending on the combination of python version and whether we're
280+
connecting over http or https, we might need to access _sock, which
281+
may or may not exist; or we may need to just settimeout on socket
282+
itself, which also may or may not have settimeout on it.
283+
284+
To avoid missing the correct one, we try both.
285+
"""
286+
if hasattr(socket, "settimeout"):
287+
socket.settimeout(None)
288+
if hasattr(socket, "_sock") and hasattr(socket._sock, "settimeout"):
289+
socket._sock.settimeout(None)
290+
279291
def _get_result(self, container, stream, res):
280292
cont = self.inspect_container(container)
281293
return self._get_result_tty(stream, res, cont['Config']['Tty'])

pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[pytest]
2-
addopts = --tb=short -rxs
2+
addopts = --tb=short -rxs -s

tests/Dockerfile-dind-certs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM python:2.7
2+
RUN mkdir /tmp/certs
3+
VOLUME /certs
4+
5+
WORKDIR /tmp/certs
6+
RUN openssl genrsa -aes256 -passout pass:foobar -out ca-key.pem 4096
7+
RUN echo "[req]\nprompt=no\ndistinguished_name = req_distinguished_name\n[req_distinguished_name]\ncountryName=AU" > /tmp/config
8+
RUN openssl req -new -x509 -passin pass:foobar -config /tmp/config -days 365 -key ca-key.pem -sha256 -out ca.pem
9+
RUN openssl genrsa -out server-key.pem -passout pass:foobar 4096
10+
RUN openssl req -subj "/CN=docker" -sha256 -new -key server-key.pem -out server.csr
11+
RUN echo subjectAltName = DNS:docker,DNS:localhost > extfile.cnf
12+
RUN openssl x509 -req -days 365 -passin pass:foobar -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf
13+
RUN openssl genrsa -out key.pem 4096
14+
RUN openssl req -passin pass:foobar -subj '/CN=client' -new -key key.pem -out client.csr
15+
RUN echo extendedKeyUsage = clientAuth > extfile.cnf
16+
RUN openssl x509 -req -passin pass:foobar -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile.cnf
17+
RUN chmod -v 0400 ca-key.pem key.pem server-key.pem
18+
RUN chmod -v 0444 ca.pem server-cert.pem cert.pem
19+
20+
CMD cp -R /tmp/certs/* /certs && while true; do sleep 1; done

tests/integration_test.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414

1515
import base64
1616
import contextlib
17+
import errno
1718
import json
1819
import io
1920
import os
2021
import random
2122
import shutil
2223
import signal
2324
import socket
25+
import struct
2426
import tarfile
2527
import tempfile
2628
import threading
@@ -76,7 +78,17 @@ def setup_module():
7678
try:
7779
c.inspect_image(BUSYBOX)
7880
except NotFound:
79-
c.pull(BUSYBOX)
81+
os.write(2, "\npulling busybox\n".encode('utf-8'))
82+
for data in c.pull('busybox', stream=True):
83+
data = json.loads(data.decode('utf-8'))
84+
os.write(2, ("%c[2K\r" % 27).encode('utf-8'))
85+
status = data.get("status")
86+
progress = data.get("progress")
87+
detail = "{0} - {1}".format(status, progress).encode('utf-8')
88+
os.write(2, detail)
89+
os.write(2, "\npulled busybox\n".encode('utf-8'))
90+
91+
# Double make sure we now have busybox
8092
c.inspect_image(BUSYBOX)
8193
c.close()
8294

@@ -887,7 +899,6 @@ def runTest(self):
887899

888900
self.client.start(container)
889901
res = self.client.top(container['Id'])
890-
print(res)
891902
self.assertEqual(
892903
res['Titles'],
893904
['UID', 'PID', 'PPID', 'C', 'STIME', 'TTY', 'TIME', 'CMD']
@@ -1213,6 +1224,64 @@ def runTest(self):
12131224
self.assertTrue(sock.fileno() > -1)
12141225

12151226

1227+
class TestRunContainerReadingSocket(BaseTestCase):
1228+
def runTest(self):
1229+
line = 'hi there and stuff and things, words!'
1230+
command = "echo '{0}'".format(line)
1231+
container = self.client.create_container(BUSYBOX, command,
1232+
detach=True, tty=False)
1233+
ident = container['Id']
1234+
self.tmp_containers.append(ident)
1235+
1236+
opts = {"stdout": 1, "stream": 1, "logs": 1}
1237+
pty_stdout = self.client.attach_socket(ident, opts)
1238+
self.client.start(ident)
1239+
1240+
recoverable_errors = (errno.EINTR, errno.EDEADLK, errno.EWOULDBLOCK)
1241+
1242+
def read(n=4096):
1243+
"""Code stolen from dockerpty to read the socket"""
1244+
try:
1245+
if hasattr(pty_stdout, 'recv'):
1246+
return pty_stdout.recv(n)
1247+
return os.read(pty_stdout.fileno(), n)
1248+
except EnvironmentError as e:
1249+
if e.errno not in recoverable_errors:
1250+
raise
1251+
1252+
def next_packet_size():
1253+
"""Code stolen from dockerpty to get the next packet size"""
1254+
data = six.binary_type()
1255+
while len(data) < 8:
1256+
next_data = read(8 - len(data))
1257+
if not next_data:
1258+
return 0
1259+
data = data + next_data
1260+
1261+
if data is None:
1262+
return 0
1263+
1264+
if len(data) == 8:
1265+
_, actual = struct.unpack('>BxxxL', data)
1266+
return actual
1267+
1268+
next_size = next_packet_size()
1269+
self.assertEqual(next_size, len(line)+1)
1270+
1271+
data = six.binary_type()
1272+
while len(data) < next_size:
1273+
next_data = read(next_size - len(data))
1274+
if not next_data:
1275+
assert False, "Failed trying to read in the dataz"
1276+
data += next_data
1277+
self.assertEqual(data.decode('utf-8'), "{0}\n".format(line))
1278+
pty_stdout.close()
1279+
1280+
# Prevent segfault at the end of the test run
1281+
if hasattr(pty_stdout, "_response"):
1282+
del pty_stdout._response
1283+
1284+
12161285
class TestPauseUnpauseContainer(BaseTestCase):
12171286
def runTest(self):
12181287
container = self.client.create_container(BUSYBOX, ['sleep', '9999'])

0 commit comments

Comments
 (0)