Skip to content

Commit ac92219

Browse files
authored
Merge pull request #2223 from docker/3.7.0-release
3.7.0 release
2 parents d74bfa6 + e6783d8 commit ac92219

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1362
-436
lines changed

Jenkinsfile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,13 @@ def getDockerVersions = { ->
4545
}
4646

4747
def getAPIVersion = { engineVersion ->
48-
def versionMap = ['17.06': '1.30', '17.12': '1.35', '18.02': '1.36', '18.03': '1.37']
48+
def versionMap = [
49+
'17.06': '1.30', '17.12': '1.35', '18.02': '1.36', '18.03': '1.37',
50+
'18.06': '1.38', '18.09': '1.39'
51+
]
4952
def result = versionMap[engineVersion.substring(0, 5)]
5053
if (!result) {
51-
return '1.37'
54+
return '1.39'
5255
}
5356
return result
5457
}
@@ -88,7 +91,7 @@ def runTests = { Map settings ->
8891
--network ${testNetwork} \\
8992
--volumes-from ${dindContainerName} \\
9093
${testImage} \\
91-
py.test -v -rxs tests/integration
94+
py.test -v -rxs --cov=docker tests/
9295
"""
9396
} finally {
9497
sh """

docker/api/build.py

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ def build(self, path=None, tag=None, quiet=False, fileobj=None,
1919
forcerm=False, dockerfile=None, container_limits=None,
2020
decode=False, buildargs=None, gzip=False, shmsize=None,
2121
labels=None, cache_from=None, target=None, network_mode=None,
22-
squash=None, extra_hosts=None, platform=None, isolation=None):
22+
squash=None, extra_hosts=None, platform=None, isolation=None,
23+
use_config_proxy=False):
2324
"""
2425
Similar to the ``docker build`` command. Either ``path`` or ``fileobj``
2526
needs to be set. ``path`` can be a local path (to a directory
@@ -103,6 +104,10 @@ def build(self, path=None, tag=None, quiet=False, fileobj=None,
103104
platform (str): Platform in the format ``os[/arch[/variant]]``
104105
isolation (str): Isolation technology used during build.
105106
Default: `None`.
107+
use_config_proxy (bool): If ``True``, and if the docker client
108+
configuration file (``~/.docker/config.json`` by default)
109+
contains a proxy configuration, the corresponding environment
110+
variables will be set in the container being built.
106111
107112
Returns:
108113
A generator for the build output.
@@ -168,6 +173,10 @@ def build(self, path=None, tag=None, quiet=False, fileobj=None,
168173
}
169174
params.update(container_limits)
170175

176+
if use_config_proxy:
177+
proxy_args = self._proxy_configs.get_environment()
178+
for k, v in proxy_args.items():
179+
buildargs.setdefault(k, v)
171180
if buildargs:
172181
params.update({'buildargs': json.dumps(buildargs)})
173182

@@ -286,48 +295,31 @@ def _set_auth_headers(self, headers):
286295

287296
# If we don't have any auth data so far, try reloading the config
288297
# file one more time in case anything showed up in there.
289-
if not self._auth_configs:
298+
if not self._auth_configs or self._auth_configs.is_empty:
290299
log.debug("No auth config in memory - loading from filesystem")
291-
self._auth_configs = auth.load_config()
300+
self._auth_configs = auth.load_config(
301+
credstore_env=self.credstore_env
302+
)
292303

293304
# Send the full auth configuration (if any exists), since the build
294305
# could use any (or all) of the registries.
295306
if self._auth_configs:
296-
auth_cfgs = self._auth_configs
297-
auth_data = {}
298-
if auth_cfgs.get('credsStore'):
299-
# Using a credentials store, we need to retrieve the
300-
# credentials for each registry listed in the config.json file
301-
# Matches CLI behavior: https://github.com/docker/docker/blob/
302-
# 67b85f9d26f1b0b2b240f2d794748fac0f45243c/cliconfig/
303-
# credentials/native_store.go#L68-L83
304-
for registry in auth_cfgs.get('auths', {}).keys():
305-
auth_data[registry] = auth.resolve_authconfig(
306-
auth_cfgs, registry,
307-
credstore_env=self.credstore_env,
308-
)
309-
else:
310-
for registry in auth_cfgs.get('credHelpers', {}).keys():
311-
auth_data[registry] = auth.resolve_authconfig(
312-
auth_cfgs, registry,
313-
credstore_env=self.credstore_env
314-
)
315-
for registry, creds in auth_cfgs.get('auths', {}).items():
316-
if registry not in auth_data:
317-
auth_data[registry] = creds
318-
# See https://github.com/docker/docker-py/issues/1683
319-
if auth.INDEX_NAME in auth_data:
320-
auth_data[auth.INDEX_URL] = auth_data[auth.INDEX_NAME]
307+
auth_data = self._auth_configs.get_all_credentials()
308+
309+
# See https://github.com/docker/docker-py/issues/1683
310+
if auth.INDEX_URL not in auth_data and auth.INDEX_URL in auth_data:
311+
auth_data[auth.INDEX_URL] = auth_data.get(auth.INDEX_NAME, {})
321312

322313
log.debug(
323314
'Sending auth config ({0})'.format(
324315
', '.join(repr(k) for k in auth_data.keys())
325316
)
326317
)
327318

328-
headers['X-Registry-Config'] = auth.encode_header(
329-
auth_data
330-
)
319+
if auth_data:
320+
headers['X-Registry-Config'] = auth.encode_header(
321+
auth_data
322+
)
331323
else:
332324
log.debug('No auth config found')
333325

docker/api/client.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@
3232
from ..tls import TLSConfig
3333
from ..transport import SSLAdapter, UnixAdapter
3434
from ..utils import utils, check_resource, update_headers, config
35-
from ..utils.socket import frames_iter, socket_raw_iter
35+
from ..utils.socket import frames_iter, consume_socket_output, demux_adaptor
3636
from ..utils.json_stream import json_stream
37+
from ..utils.proxy import ProxyConfig
3738
try:
3839
from ..transport import NpipeAdapter
3940
except ImportError:
@@ -114,8 +115,17 @@ def __init__(self, base_url=None, version=None,
114115
self.headers['User-Agent'] = user_agent
115116

116117
self._general_configs = config.load_general_config()
118+
119+
proxy_config = self._general_configs.get('proxies', {})
120+
try:
121+
proxies = proxy_config[base_url]
122+
except KeyError:
123+
proxies = proxy_config.get('default', {})
124+
125+
self._proxy_configs = ProxyConfig.from_dict(proxies)
126+
117127
self._auth_configs = auth.load_config(
118-
config_dict=self._general_configs
128+
config_dict=self._general_configs, credstore_env=credstore_env,
119129
)
120130
self.credstore_env = credstore_env
121131

@@ -381,19 +391,23 @@ def _stream_raw_result(self, response, chunk_size=1, decode=True):
381391
for out in response.iter_content(chunk_size, decode):
382392
yield out
383393

384-
def _read_from_socket(self, response, stream, tty=False):
394+
def _read_from_socket(self, response, stream, tty=True, demux=False):
385395
socket = self._get_raw_response_socket(response)
386396

387-
gen = None
388-
if tty is False:
389-
gen = frames_iter(socket)
397+
gen = frames_iter(socket, tty)
398+
399+
if demux:
400+
# The generator will output tuples (stdout, stderr)
401+
gen = (demux_adaptor(*frame) for frame in gen)
390402
else:
391-
gen = socket_raw_iter(socket)
403+
# The generator will output strings
404+
gen = (data for (_, data) in gen)
392405

393406
if stream:
394407
return gen
395408
else:
396-
return six.binary_type().join(gen)
409+
# Wait for all the frames, concatenate them, and return the result
410+
return consume_socket_output(gen, demux=demux)
397411

398412
def _disable_socket_timeout(self, socket):
399413
""" Depending on the combination of python version and whether we're
@@ -476,4 +490,6 @@ def reload_config(self, dockercfg_path=None):
476490
Returns:
477491
None
478492
"""
479-
self._auth_configs = auth.load_config(dockercfg_path)
493+
self._auth_configs = auth.load_config(
494+
dockercfg_path, credstore_env=self.credstore_env
495+
)

docker/api/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def inspect_config(self, id):
4242
Retrieve config metadata
4343
4444
Args:
45-
id (string): Full ID of the config to remove
45+
id (string): Full ID of the config to inspect
4646
4747
Returns (dict): A dictionary of metadata
4848

docker/api/container.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
class ContainerApiMixin(object):
1414
@utils.check_resource('container')
1515
def attach(self, container, stdout=True, stderr=True,
16-
stream=False, logs=False):
16+
stream=False, logs=False, demux=False):
1717
"""
1818
Attach to a container.
1919
@@ -28,11 +28,15 @@ def attach(self, container, stdout=True, stderr=True,
2828
stream (bool): Return container output progressively as an iterator
2929
of strings, rather than a single string.
3030
logs (bool): Include the container's previous output.
31+
demux (bool): Keep stdout and stderr separate.
3132
3233
Returns:
33-
By default, the container's output as a single string.
34+
By default, the container's output as a single string (two if
35+
``demux=True``: one for stdout and one for stderr).
3436
35-
If ``stream=True``, an iterator of output strings.
37+
If ``stream=True``, an iterator of output strings. If
38+
``demux=True``, two iterators are returned: one for stdout and one
39+
for stderr.
3640
3741
Raises:
3842
:py:class:`docker.errors.APIError`
@@ -54,8 +58,7 @@ def attach(self, container, stdout=True, stderr=True,
5458
response = self._post(u, headers=headers, params=params, stream=True)
5559

5660
output = self._read_from_socket(
57-
response, stream, self._check_is_tty(container)
58-
)
61+
response, stream, self._check_is_tty(container), demux=demux)
5962

6063
if stream:
6164
return CancellableStream(output, response)
@@ -218,7 +221,8 @@ def create_container(self, image, command=None, hostname=None, user=None,
218221
working_dir=None, domainname=None, host_config=None,
219222
mac_address=None, labels=None, stop_signal=None,
220223
networking_config=None, healthcheck=None,
221-
stop_timeout=None, runtime=None):
224+
stop_timeout=None, runtime=None,
225+
use_config_proxy=False):
222226
"""
223227
Creates a container. Parameters are similar to those for the ``docker
224228
run`` command except it doesn't support the attach options (``-a``).
@@ -387,6 +391,10 @@ def create_container(self, image, command=None, hostname=None, user=None,
387391
runtime (str): Runtime to use with this container.
388392
healthcheck (dict): Specify a test to perform to check that the
389393
container is healthy.
394+
use_config_proxy (bool): If ``True``, and if the docker client
395+
configuration file (``~/.docker/config.json`` by default)
396+
contains a proxy configuration, the corresponding environment
397+
variables will be set in the container being created.
390398
391399
Returns:
392400
A dictionary with an image 'Id' key and a 'Warnings' key.
@@ -400,6 +408,14 @@ def create_container(self, image, command=None, hostname=None, user=None,
400408
if isinstance(volumes, six.string_types):
401409
volumes = [volumes, ]
402410

411+
if isinstance(environment, dict):
412+
environment = utils.utils.format_environment(environment)
413+
414+
if use_config_proxy:
415+
environment = self._proxy_configs.inject_proxy_environment(
416+
environment
417+
)
418+
403419
config = self.create_container_config(
404420
image, command, hostname, user, detach, stdin_open, tty,
405421
ports, environment, volumes,

docker/api/daemon.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ def events(self, since=None, until=None, filters=None, decode=None):
4242
4343
Example:
4444
45-
>>> for event in client.events()
46-
... print event
45+
>>> for event in client.events(decode=True)
46+
... print(event)
4747
{u'from': u'image/with:tag',
4848
u'id': u'container-id',
4949
u'status': u'start',
@@ -54,7 +54,7 @@ def events(self, since=None, until=None, filters=None, decode=None):
5454
5555
>>> events = client.events()
5656
>>> for event in events:
57-
... print event
57+
... print(event)
5858
>>> # and cancel from another thread
5959
>>> events.close()
6060
"""
@@ -124,13 +124,15 @@ def login(self, username, password=None, email=None, registry=None,
124124
# If dockercfg_path is passed check to see if the config file exists,
125125
# if so load that config.
126126
if dockercfg_path and os.path.exists(dockercfg_path):
127-
self._auth_configs = auth.load_config(dockercfg_path)
128-
elif not self._auth_configs:
129-
self._auth_configs = auth.load_config()
130-
131-
authcfg = auth.resolve_authconfig(
132-
self._auth_configs, registry, credstore_env=self.credstore_env,
133-
)
127+
self._auth_configs = auth.load_config(
128+
dockercfg_path, credstore_env=self.credstore_env
129+
)
130+
elif not self._auth_configs or self._auth_configs.is_empty:
131+
self._auth_configs = auth.load_config(
132+
credstore_env=self.credstore_env
133+
)
134+
135+
authcfg = self._auth_configs.resolve_authconfig(registry)
134136
# If we found an existing auth config for this registry and username
135137
# combination, we can return it immediately unless reauth is requested.
136138
if authcfg and authcfg.get('username', None) == username \
@@ -146,9 +148,7 @@ def login(self, username, password=None, email=None, registry=None,
146148

147149
response = self._post_json(self._url('/auth'), data=req_data)
148150
if response.status_code == 200:
149-
if 'auths' not in self._auth_configs:
150-
self._auth_configs['auths'] = {}
151-
self._auth_configs['auths'][registry or auth.INDEX_NAME] = req_data
151+
self._auth_configs.add_auth(registry or auth.INDEX_NAME, req_data)
152152
return self._result(response, json=True)
153153

154154
def ping(self):

docker/api/exec_api.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def exec_resize(self, exec_id, height=None, width=None):
118118

119119
@utils.check_resource('exec_id')
120120
def exec_start(self, exec_id, detach=False, tty=False, stream=False,
121-
socket=False):
121+
socket=False, demux=False):
122122
"""
123123
Start a previously set up exec instance.
124124
@@ -130,11 +130,14 @@ def exec_start(self, exec_id, detach=False, tty=False, stream=False,
130130
stream (bool): Stream response data. Default: False
131131
socket (bool): Return the connection socket to allow custom
132132
read/write operations.
133+
demux (bool): Return stdout and stderr separately
133134
134135
Returns:
135-
(generator or str): If ``stream=True``, a generator yielding
136-
response chunks. If ``socket=True``, a socket object for the
137-
connection. A string containing response data otherwise.
136+
137+
(generator or str or tuple): If ``stream=True``, a generator
138+
yielding response chunks. If ``socket=True``, a socket object for
139+
the connection. A string containing response data otherwise. If
140+
``demux=True``, stdout and stderr are separated.
138141
139142
Raises:
140143
:py:class:`docker.errors.APIError`
@@ -162,4 +165,4 @@ def exec_start(self, exec_id, detach=False, tty=False, stream=False,
162165
return self._result(res)
163166
if socket:
164167
return self._get_raw_response_socket(res)
165-
return self._read_from_socket(res, stream, tty)
168+
return self._read_from_socket(res, stream, tty=tty, demux=demux)

docker/api/image.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -353,8 +353,8 @@ def pull(self, repository, tag=None, stream=False, auth_config=None,
353353
354354
Example:
355355
356-
>>> for line in cli.pull('busybox', stream=True):
357-
... print(json.dumps(json.loads(line), indent=4))
356+
>>> for line in cli.pull('busybox', stream=True, decode=True):
357+
... print(json.dumps(line, indent=4))
358358
{
359359
"status": "Pulling image (latest) from busybox",
360360
"progressDetail": {},
@@ -429,12 +429,12 @@ def push(self, repository, tag=None, stream=False, auth_config=None,
429429
If the server returns an error.
430430
431431
Example:
432-
>>> for line in cli.push('yourname/app', stream=True):
433-
... print line
434-
{"status":"Pushing repository yourname/app (1 tags)"}
435-
{"status":"Pushing","progressDetail":{},"id":"511136ea3c5a"}
436-
{"status":"Image already pushed, skipping","progressDetail":{},
437-
"id":"511136ea3c5a"}
432+
>>> for line in cli.push('yourname/app', stream=True, decode=True):
433+
... print(line)
434+
{'status': 'Pushing repository yourname/app (1 tags)'}
435+
{'status': 'Pushing','progressDetail': {}, 'id': '511136ea3c5a'}
436+
{'status': 'Image already pushed, skipping', 'progressDetail':{},
437+
'id': '511136ea3c5a'}
438438
...
439439
440440
"""

0 commit comments

Comments
 (0)