Skip to content

Commit f70545e

Browse files
authored
Merge pull request #2062 from docker/3.4.0-release
3.4.0 Release
2 parents e88751c + e5f5624 commit f70545e

21 files changed

+191
-53
lines changed

docker/api/build.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,8 @@ def _set_auth_headers(self, headers):
302302
# credentials/native_store.go#L68-L83
303303
for registry in self._auth_configs.get('auths', {}).keys():
304304
auth_data[registry] = auth.resolve_authconfig(
305-
self._auth_configs, registry
305+
self._auth_configs, registry,
306+
credstore_env=self.credstore_env,
306307
)
307308
else:
308309
auth_data = self._auth_configs.get('auths', {}).copy()
@@ -341,4 +342,9 @@ def process_dockerfile(dockerfile, path):
341342
)
342343

343344
# Dockerfile is inside the context - return path relative to context root
344-
return (os.path.relpath(abs_dockerfile, path), None)
345+
if dockerfile == abs_dockerfile:
346+
# Only calculate relpath if necessary to avoid errors
347+
# on Windows client -> Linux Docker
348+
# see https://github.com/docker/compose/issues/5969
349+
dockerfile = os.path.relpath(abs_dockerfile, path)
350+
return (dockerfile, None)

docker/api/client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ class APIClient(
8383
:py:class:`~docker.tls.TLSConfig` object to use custom
8484
configuration.
8585
user_agent (str): Set a custom user agent for requests to the server.
86+
credstore_env (dict): Override environment variables when calling the
87+
credential store process.
8688
"""
8789

8890
__attrs__ = requests.Session.__attrs__ + ['_auth_configs',
@@ -93,7 +95,8 @@ class APIClient(
9395

9496
def __init__(self, base_url=None, version=None,
9597
timeout=DEFAULT_TIMEOUT_SECONDS, tls=False,
96-
user_agent=DEFAULT_USER_AGENT, num_pools=DEFAULT_NUM_POOLS):
98+
user_agent=DEFAULT_USER_AGENT, num_pools=DEFAULT_NUM_POOLS,
99+
credstore_env=None):
97100
super(APIClient, self).__init__()
98101

99102
if tls and not base_url:
@@ -109,6 +112,7 @@ def __init__(self, base_url=None, version=None,
109112
self._auth_configs = auth.load_config(
110113
config_dict=self._general_configs
111114
)
115+
self.credstore_env = credstore_env
112116

113117
base_url = utils.parse_host(
114118
base_url, IS_WINDOWS_PLATFORM, tls=bool(tls)

docker/api/daemon.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ def login(self, username, password=None, email=None, registry=None,
128128
elif not self._auth_configs:
129129
self._auth_configs = auth.load_config()
130130

131-
authcfg = auth.resolve_authconfig(self._auth_configs, registry)
131+
authcfg = auth.resolve_authconfig(
132+
self._auth_configs, registry, credstore_env=self.credstore_env,
133+
)
132134
# If we found an existing auth config for this registry and username
133135
# combination, we can return it immediately unless reauth is requested.
134136
if authcfg and authcfg.get('username', None) == username \

docker/api/plugin.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ def create_plugin(self, name, plugin_data_dir, gzip=False):
4444
"""
4545
url = self._url('/plugins/create')
4646

47-
with utils.create_archive(root=plugin_data_dir, gzip=gzip) as archv:
47+
with utils.create_archive(
48+
root=plugin_data_dir, gzip=gzip,
49+
files=set(utils.build.walk(plugin_data_dir, []))
50+
) as archv:
4851
res = self._post(url, params={'name': name}, data=archv)
4952
self._raise_for_status(res)
5053
return True
@@ -167,8 +170,16 @@ def plugin_privileges(self, name):
167170
'remote': name,
168171
}
169172

173+
headers = {}
174+
registry, repo_name = auth.resolve_repository_name(name)
175+
header = auth.get_config_header(self, registry)
176+
if header:
177+
headers['X-Registry-Auth'] = header
178+
170179
url = self._url('/plugins/privileges')
171-
return self._result(self._get(url, params=params), True)
180+
return self._result(
181+
self._get(url, params=params, headers=headers), True
182+
)
172183

173184
@utils.minimum_version('1.25')
174185
@utils.check_resource('name')

docker/auth.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ def get_config_header(client, registry):
4444
"No auth config in memory - loading from filesystem"
4545
)
4646
client._auth_configs = load_config()
47-
authcfg = resolve_authconfig(client._auth_configs, registry)
47+
authcfg = resolve_authconfig(
48+
client._auth_configs, registry, credstore_env=client.credstore_env
49+
)
4850
# Do not fail here if no authentication exists for this
4951
# specific registry as we can have a readonly pull. Just
5052
# put the header if we can.
@@ -76,7 +78,7 @@ def get_credential_store(authconfig, registry):
7678
)
7779

7880

79-
def resolve_authconfig(authconfig, registry=None):
81+
def resolve_authconfig(authconfig, registry=None, credstore_env=None):
8082
"""
8183
Returns the authentication data from the given auth configuration for a
8284
specific registry. As with the Docker client, legacy entries in the config
@@ -91,7 +93,7 @@ def resolve_authconfig(authconfig, registry=None):
9193
'Using credentials store "{0}"'.format(store_name)
9294
)
9395
cfg = _resolve_authconfig_credstore(
94-
authconfig, registry, store_name
96+
authconfig, registry, store_name, env=credstore_env
9597
)
9698
if cfg is not None:
9799
return cfg
@@ -115,13 +117,14 @@ def resolve_authconfig(authconfig, registry=None):
115117
return None
116118

117119

118-
def _resolve_authconfig_credstore(authconfig, registry, credstore_name):
120+
def _resolve_authconfig_credstore(authconfig, registry, credstore_name,
121+
env=None):
119122
if not registry or registry == INDEX_NAME:
120123
# The ecosystem is a little schizophrenic with index.docker.io VS
121124
# docker.io - in that case, it seems the full URL is necessary.
122125
registry = INDEX_URL
123126
log.debug("Looking for auth entry for {0}".format(repr(registry)))
124-
store = dockerpycreds.Store(credstore_name)
127+
store = dockerpycreds.Store(credstore_name, environment=env)
125128
try:
126129
data = store.get(registry)
127130
res = {

docker/client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class DockerClient(object):
3333
:py:class:`~docker.tls.TLSConfig` object to use custom
3434
configuration.
3535
user_agent (str): Set a custom user agent for requests to the server.
36+
credstore_env (dict): Override environment variables when calling the
37+
credential store process.
3638
"""
3739
def __init__(self, *args, **kwargs):
3840
self.api = APIClient(*args, **kwargs)
@@ -66,6 +68,8 @@ def from_env(cls, **kwargs):
6668
assert_hostname (bool): Verify the hostname of the server.
6769
environment (dict): The environment to read environment variables
6870
from. Default: the value of ``os.environ``
71+
credstore_env (dict): Override environment variables when calling
72+
the credential store process.
6973
7074
Example:
7175
@@ -77,8 +81,9 @@ def from_env(cls, **kwargs):
7781
"""
7882
timeout = kwargs.pop('timeout', DEFAULT_TIMEOUT_SECONDS)
7983
version = kwargs.pop('version', None)
80-
return cls(timeout=timeout, version=version,
81-
**kwargs_from_env(**kwargs))
84+
return cls(
85+
timeout=timeout, version=version, **kwargs_from_env(**kwargs)
86+
)
8287

8388
# Resources
8489
@property

docker/models/networks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,5 +211,5 @@ def list(self, *args, **kwargs):
211211
return networks
212212

213213
def prune(self, filters=None):
214-
self.client.api.prune_networks(filters=filters)
214+
return self.client.api.prune_networks(filters=filters)
215215
prune.__doc__ = APIClient.prune_networks.__doc__

docker/models/services.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def scale(self, replicas):
126126

127127
service_mode = ServiceMode('replicated', replicas)
128128
return self.client.api.update_service(self.id, self.version,
129-
service_mode,
129+
mode=service_mode,
130130
fetch_current_spec=True)
131131

132132
def force_update(self):
@@ -276,7 +276,7 @@ def list(self, **kwargs):
276276
'labels',
277277
'mounts',
278278
'open_stdin',
279-
'privileges'
279+
'privileges',
280280
'read_only',
281281
'secrets',
282282
'stop_grace_period',

docker/transport/unixconn.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import six
22
import requests.adapters
33
import socket
4+
from six.moves import http_client as httplib
45

56
from .. import constants
67

7-
if six.PY3:
8-
import http.client as httplib
9-
else:
10-
import httplib
11-
128
try:
139
import requests.packages.urllib3 as urllib3
1410
except ImportError:

docker/types/daemon.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ def close(self):
5757

5858
else:
5959
sock = sock_fp._sock
60+
if isinstance(sock, urllib3.contrib.pyopenssl.WrappedSocket):
61+
sock = sock.socket
6062

6163
sock.shutdown(socket.SHUT_RDWR)
6264
sock.close()

docker/types/services.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class ContainerSpec(dict):
8282
args (:py:class:`list`): Arguments to the command.
8383
hostname (string): The hostname to set on the container.
8484
env (dict): Environment variables.
85-
dir (string): The working directory for commands to run in.
85+
workdir (string): The working directory for commands to run in.
8686
user (string): The user inside the container.
8787
labels (dict): A map of labels to associate with the service.
8888
mounts (:py:class:`list`): A list of specifications for mounts to be

docker/utils/socket.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import errno
22
import os
33
import select
4+
import socket as pysocket
45
import struct
56

67
import six
@@ -28,6 +29,8 @@ def read(socket, n=4096):
2829
try:
2930
if hasattr(socket, 'recv'):
3031
return socket.recv(n)
32+
if six.PY3 and isinstance(socket, getattr(pysocket, 'SocketIO')):
33+
return socket.read(n)
3134
return os.read(socket.fileno(), n)
3235
except EnvironmentError as e:
3336
if e.errno not in recoverable_errors:

docker/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
version = "3.3.0"
1+
version = "3.4.0"
22
version_info = tuple([int(d) for d in version.split("-")[0].split(".")])

docs/change-log.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,31 @@
11
Change log
22
==========
33

4+
3.4.0
5+
-----
6+
7+
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/51?closed=1)
8+
9+
### Features
10+
11+
* The `APIClient` and `DockerClient` constructors now accept a `credstore_env`
12+
parameter. When set, values in this dictionary are added to the environment
13+
when executing the credential store process.
14+
15+
### Bugfixes
16+
17+
* `DockerClient.networks.prune` now properly returns the operation's result
18+
* Fixed a bug that caused custom Dockerfile paths in a subfolder of the build
19+
context to be invalidated, preventing these builds from working
20+
* The `plugin_privileges` method can now be called for plugins requiring
21+
authentication to access
22+
* Fixed a bug that caused attempts to read a data stream over an unsecured TCP
23+
socket to crash on Windows clients
24+
* Fixed a bug where using the `read_only` parameter when creating a service using
25+
the `DockerClient` was being ignored
26+
* Fixed an issue where `Service.scale` would not properly update the service's
27+
mode, causing the operation to fail silently
28+
429
3.3.0
530
-----
631

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ asn1crypto==0.22.0
33
backports.ssl-match-hostname==3.5.0.1
44
cffi==1.10.0
55
cryptography==1.9
6-
docker-pycreds==0.2.3
6+
docker-pycreds==0.3.0
77
enum34==1.1.6
88
idna==2.5
99
ipaddress==1.0.18

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
'requests >= 2.14.2, != 2.18.0',
1414
'six >= 1.4.0',
1515
'websocket-client >= 0.32.0',
16-
'docker-pycreds >= 0.2.3'
16+
'docker-pycreds >= 0.3.0'
1717
]
1818

1919
extras_require = {

tests/integration/api_build_test.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -415,18 +415,20 @@ def test_build_out_of_context_dockerfile(self):
415415
f.write('hello world')
416416
with open(os.path.join(base_dir, '.dockerignore'), 'w') as f:
417417
f.write('.dockerignore\n')
418-
df = tempfile.NamedTemporaryFile()
419-
self.addCleanup(df.close)
420-
df.write(('\n'.join([
421-
'FROM busybox',
422-
'COPY . /src',
423-
'WORKDIR /src',
424-
])).encode('utf-8'))
425-
df.flush()
418+
df_dir = tempfile.mkdtemp()
419+
self.addCleanup(shutil.rmtree, df_dir)
420+
df_name = os.path.join(df_dir, 'Dockerfile')
421+
with open(df_name, 'wb') as df:
422+
df.write(('\n'.join([
423+
'FROM busybox',
424+
'COPY . /src',
425+
'WORKDIR /src',
426+
])).encode('utf-8'))
427+
df.flush()
426428
img_name = random_name()
427429
self.tmp_imgs.append(img_name)
428430
stream = self.client.build(
429-
path=base_dir, dockerfile=df.name, tag=img_name,
431+
path=base_dir, dockerfile=df_name, tag=img_name,
430432
decode=True
431433
)
432434
lines = []
@@ -472,6 +474,39 @@ def test_build_in_context_dockerfile(self):
472474
[b'.', b'..', b'file.txt', b'custom.dockerfile']
473475
) == sorted(lsdata)
474476

477+
def test_build_in_context_nested_dockerfile(self):
478+
base_dir = tempfile.mkdtemp()
479+
self.addCleanup(shutil.rmtree, base_dir)
480+
with open(os.path.join(base_dir, 'file.txt'), 'w') as f:
481+
f.write('hello world')
482+
subdir = os.path.join(base_dir, 'hello', 'world')
483+
os.makedirs(subdir)
484+
with open(os.path.join(subdir, 'custom.dockerfile'), 'w') as df:
485+
df.write('\n'.join([
486+
'FROM busybox',
487+
'COPY . /src',
488+
'WORKDIR /src',
489+
]))
490+
img_name = random_name()
491+
self.tmp_imgs.append(img_name)
492+
stream = self.client.build(
493+
path=base_dir, dockerfile='hello/world/custom.dockerfile',
494+
tag=img_name, decode=True
495+
)
496+
lines = []
497+
for chunk in stream:
498+
lines.append(chunk)
499+
assert 'Successfully tagged' in lines[-1]['stream']
500+
501+
ctnr = self.client.create_container(img_name, 'ls -a')
502+
self.tmp_containers.append(ctnr)
503+
self.client.start(ctnr)
504+
lsdata = self.client.logs(ctnr).strip().split(b'\n')
505+
assert len(lsdata) == 4
506+
assert sorted(
507+
[b'.', b'..', b'file.txt', b'hello']
508+
) == sorted(lsdata)
509+
475510
def test_build_in_context_abs_dockerfile(self):
476511
base_dir = tempfile.mkdtemp()
477512
self.addCleanup(shutil.rmtree, base_dir)

0 commit comments

Comments
 (0)