Skip to content

Commit d74bfa6

Browse files
authored
Merge pull request #2187 from docker/3.6.0-release
3.6.0 release
2 parents 7cc0a1b + 24ed2f3 commit d74bfa6

36 files changed

+643
-186
lines changed

docker/api/build.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,14 @@ def process_dockerfile(dockerfile, path):
339339
abs_dockerfile = dockerfile
340340
if not os.path.isabs(dockerfile):
341341
abs_dockerfile = os.path.join(path, dockerfile)
342-
342+
if constants.IS_WINDOWS_PLATFORM and path.startswith(
343+
constants.WINDOWS_LONGPATH_PREFIX):
344+
abs_dockerfile = '{}{}'.format(
345+
constants.WINDOWS_LONGPATH_PREFIX,
346+
os.path.normpath(
347+
abs_dockerfile[len(constants.WINDOWS_LONGPATH_PREFIX):]
348+
)
349+
)
343350
if (os.path.splitdrive(path)[0] != os.path.splitdrive(abs_dockerfile)[0] or
344351
os.path.relpath(abs_dockerfile, path).startswith('..')):
345352
# Dockerfile not in context - read data to insert into tar later

docker/api/client.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
except ImportError:
4040
pass
4141

42+
try:
43+
from ..transport import SSHAdapter
44+
except ImportError:
45+
pass
46+
4247

4348
class APIClient(
4449
requests.Session,
@@ -141,6 +146,18 @@ def __init__(self, base_url=None, version=None,
141146
)
142147
self.mount('http+docker://', self._custom_adapter)
143148
self.base_url = 'http+docker://localnpipe'
149+
elif base_url.startswith('ssh://'):
150+
try:
151+
self._custom_adapter = SSHAdapter(
152+
base_url, timeout, pool_connections=num_pools
153+
)
154+
except NameError:
155+
raise DockerException(
156+
'Install paramiko package to enable ssh:// support'
157+
)
158+
self.mount('http+docker://ssh', self._custom_adapter)
159+
self._unmount('http://', 'https://')
160+
self.base_url = 'http+docker://ssh'
144161
else:
145162
# Use SSLAdapter for the ability to specify SSL version
146163
if isinstance(tls, TLSConfig):
@@ -279,6 +296,8 @@ def _get_raw_response_socket(self, response):
279296
self._raise_for_status(response)
280297
if self.base_url == "http+docker://localnpipe":
281298
sock = response.raw._fp.fp.raw.sock
299+
elif self.base_url.startswith('http+docker://ssh'):
300+
sock = response.raw._fp.fp.channel
282301
elif six.PY3:
283302
sock = response.raw._fp.fp.raw
284303
if self.base_url.startswith("https://"):

docker/api/container.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -473,16 +473,12 @@ def create_host_config(self, *args, **kwargs):
473473
signals and reaps processes
474474
init_path (str): Path to the docker-init binary
475475
ipc_mode (str): Set the IPC mode for the container.
476-
isolation (str): Isolation technology to use. Default: `None`.
477-
links (dict or list of tuples): Either a dictionary mapping name
478-
to alias or as a list of ``(name, alias)`` tuples.
479-
log_config (dict): Logging configuration, as a dictionary with
480-
keys:
481-
482-
- ``type`` The logging driver name.
483-
- ``config`` A dictionary of configuration for the logging
484-
driver.
485-
476+
isolation (str): Isolation technology to use. Default: ``None``.
477+
links (dict): Mapping of links using the
478+
``{'container': 'alias'}`` format. The alias is optional.
479+
Containers declared in this dict will be linked to the new
480+
container using the provided alias. Default: ``None``.
481+
log_config (LogConfig): Logging configuration
486482
lxc_conf (dict): LXC config.
487483
mem_limit (float or str): Memory limit. Accepts float values
488484
(which represent the memory limit of the created container in
@@ -543,7 +539,7 @@ def create_host_config(self, *args, **kwargs):
543539
}
544540
545541
ulimits (:py:class:`list`): Ulimits to set inside the container,
546-
as a list of dicts.
542+
as a list of :py:class:`docker.types.Ulimit` instances.
547543
userns_mode (str): Sets the user namespace mode for the container
548544
when user namespace remapping option is enabled. Supported
549545
values are: ``host``
@@ -611,9 +607,10 @@ def create_endpoint_config(self, *args, **kwargs):
611607
aliases (:py:class:`list`): A list of aliases for this endpoint.
612608
Names in that list can be used within the network to reach the
613609
container. Defaults to ``None``.
614-
links (:py:class:`list`): A list of links for this endpoint.
615-
Containers declared in this list will be linked to this
616-
container. Defaults to ``None``.
610+
links (dict): Mapping of links for this endpoint using the
611+
``{'container': 'alias'}`` format. The alias is optional.
612+
Containers declared in this dict will be linked to this
613+
container using the provided alias. Defaults to ``None``.
617614
ipv4_address (str): The IP address of this container on the
618615
network, using the IPv4 protocol. Defaults to ``None``.
619616
ipv6_address (str): The IP address of this container on the
@@ -628,7 +625,7 @@ def create_endpoint_config(self, *args, **kwargs):
628625
629626
>>> endpoint_config = client.create_endpoint_config(
630627
aliases=['web', 'app'],
631-
links=['app_db'],
628+
links={'app_db': 'db', 'another': None},
632629
ipv4_address='132.65.0.123'
633630
)
634631
@@ -697,6 +694,18 @@ def get_archive(self, container, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
697694
Raises:
698695
:py:class:`docker.errors.APIError`
699696
If the server returns an error.
697+
698+
Example:
699+
700+
>>> c = docker.APIClient()
701+
>>> f = open('./sh_bin.tar', 'wb')
702+
>>> bits, stat = c.get_archive(container, '/bin/sh')
703+
>>> print(stat)
704+
{'name': 'sh', 'size': 1075464, 'mode': 493,
705+
'mtime': '2018-10-01T15:37:48-07:00', 'linkTarget': ''}
706+
>>> for chunk in bits:
707+
... f.write(chunk)
708+
>>> f.close()
700709
"""
701710
params = {
702711
'path': path
@@ -1074,7 +1083,8 @@ def stats(self, container, decode=None, stream=True):
10741083
Args:
10751084
container (str): The container to stream statistics from
10761085
decode (bool): If set to true, stream will be decoded into dicts
1077-
on the fly. False by default.
1086+
on the fly. Only applicable if ``stream`` is True.
1087+
False by default.
10781088
stream (bool): If set to false, only the current stats will be
10791089
returned instead of a stream. True by default.
10801090
@@ -1088,6 +1098,10 @@ def stats(self, container, decode=None, stream=True):
10881098
return self._stream_helper(self._get(url, stream=True),
10891099
decode=decode)
10901100
else:
1101+
if decode:
1102+
raise errors.InvalidArgument(
1103+
"decode is only available in conjuction with stream=True"
1104+
)
10911105
return self._result(self._get(url, params={'stream': False}),
10921106
json=True)
10931107

docker/api/image.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def get_image(self, image, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
3232
Example:
3333
3434
>>> image = cli.get_image("busybox:latest")
35-
>>> f = open('/tmp/busybox-latest.tar', 'w')
35+
>>> f = open('/tmp/busybox-latest.tar', 'wb')
3636
>>> for chunk in image:
3737
>>> f.write(chunk)
3838
>>> f.close()
@@ -334,7 +334,8 @@ def pull(self, repository, tag=None, stream=False, auth_config=None,
334334
Args:
335335
repository (str): The repository to pull
336336
tag (str): The tag to pull
337-
stream (bool): Stream the output as a generator
337+
stream (bool): Stream the output as a generator. Make sure to
338+
consume the generator, otherwise pull might get cancelled.
338339
auth_config (dict): Override the credentials that
339340
:py:meth:`~docker.api.daemon.DaemonApiMixin.login` has set for
340341
this request. ``auth_config`` should contain the ``username``

docker/api/service.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ def inspect_service(self, service, insert_defaults=None):
197197
into the service inspect output.
198198
199199
Returns:
200-
``True`` if successful.
200+
(dict): A dictionary of the server-side representation of the
201+
service, including all relevant properties.
201202
202203
Raises:
203204
:py:class:`docker.errors.APIError`

docker/auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ def load_config(config_path=None, config_dict=None):
267267
return res
268268

269269
log.debug(
270-
"Couldn't find auth-related section ; attempting to interpret"
270+
"Couldn't find auth-related section ; attempting to interpret "
271271
"as auth-only file"
272272
)
273273
return {'auths': parse_auth(config_dict)}

docker/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
'is deprecated and non-functional. Please remove it.'
1515

1616
IS_WINDOWS_PLATFORM = (sys.platform == 'win32')
17+
WINDOWS_LONGPATH_PREFIX = '\\\\?\\'
1718

1819
DEFAULT_USER_AGENT = "docker-sdk-python/{0}".format(version)
1920
DEFAULT_NUM_POOLS = 25

docker/models/containers.py

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515

1616

1717
class Container(Model):
18-
18+
""" Local representation of a container object. Detailed configuration may
19+
be accessed through the :py:attr:`attrs` attribute. Note that local
20+
attributes are cached; users may call :py:meth:`reload` to
21+
query the Docker daemon for the current properties, causing
22+
:py:attr:`attrs` to be refreshed.
23+
"""
1924
@property
2025
def name(self):
2126
"""
@@ -228,6 +233,17 @@ def get_archive(self, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
228233
Raises:
229234
:py:class:`docker.errors.APIError`
230235
If the server returns an error.
236+
237+
Example:
238+
239+
>>> f = open('./sh_bin.tar', 'wb')
240+
>>> bits, stat = container.get_archive('/bin/sh')
241+
>>> print(stat)
242+
{'name': 'sh', 'size': 1075464, 'mode': 493,
243+
'mtime': '2018-10-01T15:37:48-07:00', 'linkTarget': ''}
244+
>>> for chunk in bits:
245+
... f.write(chunk)
246+
>>> f.close()
231247
"""
232248
return self.client.api.get_archive(self.id, path, chunk_size)
233249

@@ -380,7 +396,8 @@ def stats(self, **kwargs):
380396
381397
Args:
382398
decode (bool): If set to true, stream will be decoded into dicts
383-
on the fly. False by default.
399+
on the fly. Only applicable if ``stream`` is True.
400+
False by default.
384401
stream (bool): If set to false, only the current stats will be
385402
returned instead of a stream. True by default.
386403
@@ -574,15 +591,11 @@ def run(self, image, command=None, stdout=True, stderr=False,
574591
``{"label1": "value1", "label2": "value2"}``) or a list of
575592
names of labels to set with empty values (e.g.
576593
``["label1", "label2"]``)
577-
links (dict or list of tuples): Either a dictionary mapping name
578-
to alias or as a list of ``(name, alias)`` tuples.
579-
log_config (dict): Logging configuration, as a dictionary with
580-
keys:
581-
582-
- ``type`` The logging driver name.
583-
- ``config`` A dictionary of configuration for the logging
584-
driver.
585-
594+
links (dict): Mapping of links using the
595+
``{'container': 'alias'}`` format. The alias is optional.
596+
Containers declared in this dict will be linked to the new
597+
container using the provided alias. Default: ``None``.
598+
log_config (LogConfig): Logging configuration.
586599
mac_address (str): MAC address to assign to the container.
587600
mem_limit (int or str): Memory limit. Accepts float values
588601
(which represent the memory limit of the created container in
@@ -691,8 +704,8 @@ def run(self, image, command=None, stdout=True, stderr=False,
691704
}
692705
693706
tty (bool): Allocate a pseudo-TTY.
694-
ulimits (:py:class:`list`): Ulimits to set inside the container, as
695-
a list of dicts.
707+
ulimits (:py:class:`list`): Ulimits to set inside the container,
708+
as a list of :py:class:`docker.types.Ulimit` instances.
696709
user (str or int): Username or UID to run commands as inside the
697710
container.
698711
userns_mode (str): Sets the user namespace mode for the container

docker/models/images.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import itertools
22
import re
3+
import warnings
34

45
import six
56

@@ -59,14 +60,20 @@ def history(self):
5960
"""
6061
return self.client.api.history(self.id)
6162

62-
def save(self, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
63+
def save(self, chunk_size=DEFAULT_DATA_CHUNK_SIZE, named=False):
6364
"""
6465
Get a tarball of an image. Similar to the ``docker save`` command.
6566
6667
Args:
6768
chunk_size (int): The generator will return up to that much data
6869
per iteration, but may return less. If ``None``, data will be
6970
streamed as it is received. Default: 2 MB
71+
named (str or bool): If ``False`` (default), the tarball will not
72+
retain repository and tag information for this image. If set
73+
to ``True``, the first tag in the :py:attr:`~tags` list will
74+
be used to identify the image. Alternatively, any element of
75+
the :py:attr:`~tags` list can be used as an argument to use
76+
that specific tag as the saved identifier.
7077
7178
Returns:
7279
(generator): A stream of raw archive data.
@@ -78,12 +85,22 @@ def save(self, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
7885
Example:
7986
8087
>>> image = cli.get_image("busybox:latest")
81-
>>> f = open('/tmp/busybox-latest.tar', 'w')
88+
>>> f = open('/tmp/busybox-latest.tar', 'wb')
8289
>>> for chunk in image:
8390
>>> f.write(chunk)
8491
>>> f.close()
8592
"""
86-
return self.client.api.get_image(self.id, chunk_size)
93+
img = self.id
94+
if named:
95+
img = self.tags[0] if self.tags else img
96+
if isinstance(named, six.string_types):
97+
if named not in self.tags:
98+
raise InvalidArgument(
99+
"{} is not a valid tag for this image".format(named)
100+
)
101+
img = named
102+
103+
return self.client.api.get_image(img, chunk_size)
87104

88105
def tag(self, repository, tag=None, **kwargs):
89106
"""
@@ -409,7 +426,21 @@ def pull(self, repository, tag=None, **kwargs):
409426
if not tag:
410427
repository, tag = parse_repository_tag(repository)
411428

412-
self.client.api.pull(repository, tag=tag, **kwargs)
429+
if 'stream' in kwargs:
430+
warnings.warn(
431+
'`stream` is not a valid parameter for this method'
432+
' and will be overridden'
433+
)
434+
del kwargs['stream']
435+
436+
pull_log = self.client.api.pull(
437+
repository, tag=tag, stream=True, **kwargs
438+
)
439+
for _ in pull_log:
440+
# We don't do anything with the logs, but we need
441+
# to keep the connection alive and wait for the image
442+
# to be pulled.
443+
pass
413444
if tag:
414445
return self.get('{0}{2}{1}'.format(
415446
repository, tag, '@' if tag.startswith('sha256:') else ':'

docker/transport/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@
66
from .npipesocket import NpipeSocket
77
except ImportError:
88
pass
9+
10+
try:
11+
from .sshconn import SSHAdapter
12+
except ImportError:
13+
pass

docker/transport/npipesocket.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,6 @@ def detach(self):
8787
def dup(self):
8888
return NpipeSocket(self._handle)
8989

90-
@check_closed
91-
def fileno(self):
92-
return int(self._handle)
93-
9490
def getpeername(self):
9591
return self._address
9692

0 commit comments

Comments
 (0)