Reformat code with black and isort.

This commit is contained in:
Felix Fontein
2025-10-06 18:34:59 +02:00
parent f45232635c
commit d65d37e9e9
132 changed files with 17581 additions and 14729 deletions
+39 -49
View File
@@ -16,11 +16,11 @@ import re
import tarfile
import tempfile
from . import fnmatch
from ..constants import IS_WINDOWS_PLATFORM, WINDOWS_LONGPATH_PREFIX
from . import fnmatch
_SEP = re.compile('/|\\\\') if IS_WINDOWS_PLATFORM else re.compile('/')
_SEP = re.compile("/|\\\\") if IS_WINDOWS_PLATFORM else re.compile("/")
def tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False):
@@ -29,16 +29,19 @@ def tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False):
dockerfile = dockerfile or (None, None)
extra_files = []
if dockerfile[1] is not None:
dockerignore_contents = '\n'.join(
(exclude or ['.dockerignore']) + [dockerfile[0]]
dockerignore_contents = "\n".join(
(exclude or [".dockerignore"]) + [dockerfile[0]]
)
extra_files = [
('.dockerignore', dockerignore_contents),
(".dockerignore", dockerignore_contents),
dockerfile,
]
return create_archive(
files=sorted(exclude_paths(root, exclude, dockerfile=dockerfile[0])),
root=root, fileobj=fileobj, gzip=gzip, extra_files=extra_files
root=root,
fileobj=fileobj,
gzip=gzip,
extra_files=extra_files,
)
@@ -52,9 +55,9 @@ def exclude_paths(root, patterns, dockerfile=None):
"""
if dockerfile is None:
dockerfile = 'Dockerfile'
dockerfile = "Dockerfile"
patterns.append('!' + dockerfile)
patterns.append("!" + dockerfile)
pm = PatternMatcher(patterns)
return set(pm.walk(root))
@@ -64,19 +67,16 @@ def build_file_list(root):
for dirname, dirnames, fnames in os.walk(root):
for filename in fnames + dirnames:
longpath = os.path.join(dirname, filename)
files.append(
longpath.replace(root, '', 1).lstrip('/')
)
files.append(longpath.replace(root, "", 1).lstrip("/"))
return files
def create_archive(root, files=None, fileobj=None, gzip=False,
extra_files=None):
def create_archive(root, files=None, fileobj=None, gzip=False, extra_files=None):
extra_files = extra_files or []
if not fileobj:
fileobj = tempfile.NamedTemporaryFile()
t = tarfile.open(mode='w:gz' if gzip else 'w', fileobj=fileobj)
t = tarfile.open(mode="w:gz" if gzip else "w", fileobj=fileobj)
if files is None:
files = build_file_list(root)
extra_names = set(e[0] for e in extra_files)
@@ -103,19 +103,17 @@ def create_archive(root, files=None, fileobj=None, gzip=False,
if i.isfile():
try:
with open(full_path, 'rb') as f:
with open(full_path, "rb") as f:
t.addfile(i, f)
except IOError:
raise IOError(
f'Can not read file in context: {full_path}'
)
raise IOError(f"Can not read file in context: {full_path}")
else:
# Directories, FIFOs, symlinks... do not need to be read.
t.addfile(i, None)
for name, contents in extra_files:
info = tarfile.TarInfo(name)
contents_encoded = contents.encode('utf-8')
contents_encoded = contents.encode("utf-8")
info.size = len(contents_encoded)
t.addfile(info, io.BytesIO(contents_encoded))
@@ -126,15 +124,15 @@ def create_archive(root, files=None, fileobj=None, gzip=False,
def mkbuildcontext(dockerfile):
f = tempfile.NamedTemporaryFile()
t = tarfile.open(mode='w', fileobj=f)
t = tarfile.open(mode="w", fileobj=f)
if isinstance(dockerfile, io.StringIO):
raise TypeError('Please use io.BytesIO to create in-memory Dockerfiles')
raise TypeError("Please use io.BytesIO to create in-memory Dockerfiles")
elif isinstance(dockerfile, io.BytesIO):
dfinfo = tarfile.TarInfo('Dockerfile')
dfinfo = tarfile.TarInfo("Dockerfile")
dfinfo.size = len(dockerfile.getvalue())
dockerfile.seek(0)
else:
dfinfo = t.gettarinfo(fileobj=dockerfile, arcname='Dockerfile')
dfinfo = t.gettarinfo(fileobj=dockerfile, arcname="Dockerfile")
t.addfile(dfinfo, dockerfile)
t.close()
f.seek(0)
@@ -142,12 +140,12 @@ def mkbuildcontext(dockerfile):
def split_path(p):
return [pt for pt in re.split(_SEP, p) if pt and pt != '.']
return [pt for pt in re.split(_SEP, p) if pt and pt != "."]
def normalize_slashes(p):
if IS_WINDOWS_PLATFORM:
return '/'.join(split_path(p))
return "/".join(split_path(p))
return p
@@ -160,10 +158,8 @@ def walk(root, patterns, default=True):
# https://github.com/moby/moby/blob/master/pkg/fileutils/fileutils.go
class PatternMatcher(object):
def __init__(self, patterns):
self.patterns = list(filter(
lambda p: p.dirs, [Pattern(p) for p in patterns]
))
self.patterns.append(Pattern('!.dockerignore'))
self.patterns = list(filter(lambda p: p.dirs, [Pattern(p) for p in patterns]))
self.patterns.append(Pattern("!.dockerignore"))
def matches(self, filepath):
matched = False
@@ -173,10 +169,10 @@ class PatternMatcher(object):
for pattern in self.patterns:
negative = pattern.exclusion
match = pattern.match(filepath)
if not match and parent_path != '':
if not match and parent_path != "":
if len(pattern.dirs) <= len(parent_path_dirs):
match = pattern.match(
os.path.sep.join(parent_path_dirs[:len(pattern.dirs)])
os.path.sep.join(parent_path_dirs[: len(pattern.dirs)])
)
if match:
@@ -187,10 +183,8 @@ class PatternMatcher(object):
def walk(self, root):
def rec_walk(current_dir):
for f in os.listdir(current_dir):
fpath = os.path.join(
os.path.relpath(current_dir, root), f
)
if fpath.startswith('.' + os.path.sep):
fpath = os.path.join(os.path.relpath(current_dir, root), f)
if fpath.startswith("." + os.path.sep):
fpath = fpath[2:]
match = self.matches(fpath)
if not match:
@@ -210,8 +204,7 @@ class PatternMatcher(object):
for pat in self.patterns:
if not pat.exclusion:
continue
if pat.cleaned_pattern.startswith(
normalize_slashes(fpath)):
if pat.cleaned_pattern.startswith(normalize_slashes(fpath)):
skip = False
break
if skip:
@@ -224,12 +217,12 @@ class PatternMatcher(object):
class Pattern(object):
def __init__(self, pattern_str):
self.exclusion = False
if pattern_str.startswith('!'):
if pattern_str.startswith("!"):
self.exclusion = True
pattern_str = pattern_str[1:]
self.dirs = self.normalize(pattern_str)
self.cleaned_pattern = '/'.join(self.dirs)
self.cleaned_pattern = "/".join(self.dirs)
@classmethod
def normalize(cls, p):
@@ -249,7 +242,7 @@ class Pattern(object):
i = 0
split = split_path(p)
while i < len(split):
if split[i] == '..':
if split[i] == "..":
del split[i]
if i > 0:
del split[i - 1]
@@ -269,17 +262,14 @@ def process_dockerfile(dockerfile, path):
abs_dockerfile = dockerfile
if not os.path.isabs(dockerfile):
abs_dockerfile = os.path.join(path, dockerfile)
if IS_WINDOWS_PLATFORM and path.startswith(
WINDOWS_LONGPATH_PREFIX):
abs_dockerfile = f'{WINDOWS_LONGPATH_PREFIX}{os.path.normpath(abs_dockerfile[len(WINDOWS_LONGPATH_PREFIX):])}'
if (os.path.splitdrive(path)[0] != os.path.splitdrive(abs_dockerfile)[0] or
os.path.relpath(abs_dockerfile, path).startswith('..')):
if IS_WINDOWS_PLATFORM and path.startswith(WINDOWS_LONGPATH_PREFIX):
abs_dockerfile = f"{WINDOWS_LONGPATH_PREFIX}{os.path.normpath(abs_dockerfile[len(WINDOWS_LONGPATH_PREFIX):])}"
if os.path.splitdrive(path)[0] != os.path.splitdrive(abs_dockerfile)[
0
] or os.path.relpath(abs_dockerfile, path).startswith(".."):
# Dockerfile not in context - read data to insert into tar later
with open(abs_dockerfile) as df:
return (
f'.dockerfile.{random.getrandbits(160):x}',
df.read()
)
return (f".dockerfile.{random.getrandbits(160):x}", df.read())
# Dockerfile is inside the context - return path relative to context root
if dockerfile == abs_dockerfile:
+17 -11
View File
@@ -15,8 +15,9 @@ import os
from ..constants import IS_WINDOWS_PLATFORM
DOCKER_CONFIG_FILENAME = os.path.join('.docker', 'config.json')
LEGACY_DOCKER_CONFIG_FILENAME = '.dockercfg'
DOCKER_CONFIG_FILENAME = os.path.join(".docker", "config.json")
LEGACY_DOCKER_CONFIG_FILENAME = ".dockercfg"
log = logging.getLogger(__name__)
@@ -27,12 +28,17 @@ def get_default_config_file():
def find_config_file(config_path=None):
homedir = home_dir()
paths = list(filter(None, [
config_path, # 1
config_path_from_environment(), # 2
os.path.join(homedir, DOCKER_CONFIG_FILENAME), # 3
os.path.join(homedir, LEGACY_DOCKER_CONFIG_FILENAME), # 4
]))
paths = list(
filter(
None,
[
config_path, # 1
config_path_from_environment(), # 2
os.path.join(homedir, DOCKER_CONFIG_FILENAME), # 3
os.path.join(homedir, LEGACY_DOCKER_CONFIG_FILENAME), # 4
],
)
)
log.debug("Trying paths: %s", repr(paths))
@@ -47,7 +53,7 @@ def find_config_file(config_path=None):
def config_path_from_environment():
config_dir = os.environ.get('DOCKER_CONFIG')
config_dir = os.environ.get("DOCKER_CONFIG")
if not config_dir:
return None
return os.path.join(config_dir, os.path.basename(DOCKER_CONFIG_FILENAME))
@@ -59,9 +65,9 @@ def home_dir():
client - use %USERPROFILE% on Windows, $HOME/getuid on POSIX.
"""
if IS_WINDOWS_PLATFORM:
return os.environ.get('USERPROFILE', '')
return os.environ.get("USERPROFILE", "")
else:
return os.path.expanduser('~')
return os.path.expanduser("~")
def load_general_config(config_path=None):
+12 -9
View File
@@ -22,13 +22,13 @@ def check_resource(resource_name):
if resource_id is None and kwargs.get(resource_name):
resource_id = kwargs.pop(resource_name)
if isinstance(resource_id, dict):
resource_id = resource_id.get('Id', resource_id.get('ID'))
resource_id = resource_id.get("Id", resource_id.get("ID"))
if not resource_id:
raise errors.NullResource(
'Resource ID was not provided'
)
raise errors.NullResource("Resource ID was not provided")
return f(self, resource_id, *args, **kwargs)
return wrapped
return decorator
@@ -38,19 +38,22 @@ def minimum_version(version):
def wrapper(self, *args, **kwargs):
if utils.version_lt(self._version, version):
raise errors.InvalidVersion(
f'{f.__name__} is not available for version < {version}'
f"{f.__name__} is not available for version < {version}"
)
return f(self, *args, **kwargs)
return wrapper
return decorator
def update_headers(f):
def inner(self, *args, **kwargs):
if 'HttpHeaders' in self._general_configs:
if not kwargs.get('headers'):
kwargs['headers'] = self._general_configs['HttpHeaders']
if "HttpHeaders" in self._general_configs:
if not kwargs.get("headers"):
kwargs["headers"] = self._general_configs["HttpHeaders"]
else:
kwargs['headers'].update(self._general_configs['HttpHeaders'])
kwargs["headers"].update(self._general_configs["HttpHeaders"])
return f(self, *args, **kwargs)
return inner
+23 -21
View File
@@ -9,6 +9,7 @@
from __future__ import annotations
"""Filename matching with shell patterns.
fnmatch(FILENAME, PATTERN) matches according to the local convention.
@@ -23,6 +24,7 @@ corresponding to PATTERN. (It does not compile it.)
import re
__all__ = ["fnmatch", "fnmatchcase", "translate"]
_cache = {}
@@ -77,50 +79,50 @@ def translate(pat):
There is no way to quote meta-characters.
"""
i, n = 0, len(pat)
res = '^'
res = "^"
while i < n:
c = pat[i]
i = i + 1
if c == '*':
if i < n and pat[i] == '*':
if c == "*":
if i < n and pat[i] == "*":
# is some flavor of "**"
i = i + 1
# Treat **/ as ** so eat the "/"
if i < n and pat[i] == '/':
if i < n and pat[i] == "/":
i = i + 1
if i >= n:
# is "**EOF" - to align with .gitignore just accept all
res = res + '.*'
res = res + ".*"
else:
# is "**"
# Note that this allows for any # of /'s (even 0) because
# the .* will eat everything, even /'s
res = res + '(.*/)?'
res = res + "(.*/)?"
else:
# is "*" so map it to anything but "/"
res = res + '[^/]*'
elif c == '?':
res = res + "[^/]*"
elif c == "?":
# "?" is any char except "/"
res = res + '[^/]'
elif c == '[':
res = res + "[^/]"
elif c == "[":
j = i
if j < n and pat[j] == '!':
if j < n and pat[j] == "!":
j = j + 1
if j < n and pat[j] == ']':
if j < n and pat[j] == "]":
j = j + 1
while j < n and pat[j] != ']':
while j < n and pat[j] != "]":
j = j + 1
if j >= n:
res = res + '\\['
res = res + "\\["
else:
stuff = pat[i:j].replace('\\', '\\\\')
stuff = pat[i:j].replace("\\", "\\\\")
i = j + 1
if stuff[0] == '!':
stuff = '^' + stuff[1:]
elif stuff[0] == '^':
stuff = '\\' + stuff
res = f'{res}[{stuff}]'
if stuff[0] == "!":
stuff = "^" + stuff[1:]
elif stuff[0] == "^":
stuff = "\\" + stuff
res = f"{res}[{stuff}]"
else:
res = res + re.escape(c)
return res + '$'
return res + "$"
@@ -27,7 +27,7 @@ def stream_as_text(stream):
"""
for data in stream:
if not isinstance(data, str):
data = data.decode('utf-8', 'replace')
data = data.decode("utf-8", "replace")
yield data
@@ -38,7 +38,7 @@ def json_splitter(buffer):
buffer = buffer.strip()
try:
obj, index = json_decoder.raw_decode(buffer)
rest = buffer[json.decoder.WHITESPACE.match(buffer, index).end():]
rest = buffer[json.decoder.WHITESPACE.match(buffer, index).end() :]
return obj, rest
except ValueError:
return None
@@ -52,11 +52,11 @@ def json_stream(stream):
return split_buffer(stream, json_splitter, json_decoder.decode)
def line_splitter(buffer, separator='\n'):
def line_splitter(buffer, separator="\n"):
index = buffer.find(str(separator))
if index == -1:
return None
return buffer[:index + 1], buffer[index + 1:]
return buffer[: index + 1], buffer[index + 1 :]
def split_buffer(stream, splitter=None, decoder=lambda a: a):
@@ -67,7 +67,7 @@ def split_buffer(stream, splitter=None, decoder=lambda a: a):
of the input.
"""
splitter = splitter or line_splitter
buffered = ''
buffered = ""
for data in stream_as_text(stream):
buffered += data
+16 -14
View File
@@ -11,6 +11,7 @@ from __future__ import annotations
import re
PORT_SPEC = re.compile(
"^" # Match full string
"(" # External part
@@ -49,23 +50,25 @@ def build_port_bindings(ports):
def _raise_invalid_port(port):
raise ValueError(f'Invalid port "{port}", should be '
'[[remote_ip:]remote_port[-remote_port]:]'
'port[/protocol]')
raise ValueError(
f'Invalid port "{port}", should be '
"[[remote_ip:]remote_port[-remote_port]:]"
"port[/protocol]"
)
def port_range(start, end, proto, randomly_available_port=False):
if not start:
return start
if not end:
return [f'{start}{proto}']
return [f"{start}{proto}"]
if randomly_available_port:
return [f'{start}-{end}{proto}']
return [f'{port}{proto}' for port in range(int(start), int(end) + 1)]
return [f"{start}-{end}{proto}"]
return [f"{port}{proto}" for port in range(int(start), int(end) + 1)]
def split_port(port):
if hasattr(port, 'legacy_repr'):
if hasattr(port, "legacy_repr"):
# This is the worst hack, but it prevents a bug in Compose 1.14.0
# https://github.com/docker/docker-py/issues/1668
# TODO: remove once fixed in Compose stable
@@ -76,19 +79,18 @@ def split_port(port):
_raise_invalid_port(port)
parts = match.groupdict()
host = parts['host']
proto = parts['proto'] or ''
internal = port_range(parts['int'], parts['int_end'], proto)
external = port_range(
parts['ext'], parts['ext_end'], '', len(internal) == 1)
host = parts["host"]
proto = parts["proto"] or ""
internal = port_range(parts["int"], parts["int_end"], proto)
external = port_range(parts["ext"], parts["ext_end"], "", len(internal) == 1)
if host is None:
if external is not None and len(internal) != len(external):
raise ValueError('Port ranges don\'t match in length')
raise ValueError("Port ranges don't match in length")
return internal, external
else:
if not external:
external = [None] * len(internal)
elif len(internal) != len(external):
raise ValueError('Port ranges don\'t match in length')
raise ValueError("Port ranges don't match in length")
return internal, [(host, ext_port) for ext_port in external]
+22 -21
View File
@@ -13,62 +13,63 @@ from .utils import format_environment
class ProxyConfig(dict):
'''
"""
Hold the client's proxy configuration
'''
"""
@property
def http(self):
return self.get('http')
return self.get("http")
@property
def https(self):
return self.get('https')
return self.get("https")
@property
def ftp(self):
return self.get('ftp')
return self.get("ftp")
@property
def no_proxy(self):
return self.get('no_proxy')
return self.get("no_proxy")
@staticmethod
def from_dict(config):
'''
"""
Instantiate a new ProxyConfig from a dictionary that represents a
client configuration, as described in `the documentation`_.
.. _the documentation:
https://docs.docker.com/network/proxy/#configure-the-docker-client
'''
"""
return ProxyConfig(
http=config.get('httpProxy'),
https=config.get('httpsProxy'),
ftp=config.get('ftpProxy'),
no_proxy=config.get('noProxy'),
http=config.get("httpProxy"),
https=config.get("httpsProxy"),
ftp=config.get("ftpProxy"),
no_proxy=config.get("noProxy"),
)
def get_environment(self):
'''
"""
Return a dictionary representing the environment variables used to
set the proxy settings.
'''
"""
env = {}
if self.http:
env['http_proxy'] = env['HTTP_PROXY'] = self.http
env["http_proxy"] = env["HTTP_PROXY"] = self.http
if self.https:
env['https_proxy'] = env['HTTPS_PROXY'] = self.https
env["https_proxy"] = env["HTTPS_PROXY"] = self.https
if self.ftp:
env['ftp_proxy'] = env['FTP_PROXY'] = self.ftp
env["ftp_proxy"] = env["FTP_PROXY"] = self.ftp
if self.no_proxy:
env['no_proxy'] = env['NO_PROXY'] = self.no_proxy
env["no_proxy"] = env["NO_PROXY"] = self.no_proxy
return env
def inject_proxy_environment(self, environment):
'''
"""
Given a list of strings representing environment variables, prepend the
environment variables corresponding to the proxy settings.
'''
"""
if not self:
return environment
@@ -80,4 +81,4 @@ class ProxyConfig(dict):
return proxy_env + environment
def __str__(self):
return f'ProxyConfig(http={self.http}, https={self.https}, ftp={self.ftp}, no_proxy={self.no_proxy})'
return f"ProxyConfig(http={self.http}, https={self.https}, ftp={self.ftp}, no_proxy={self.no_proxy})"
+13 -11
View File
@@ -48,22 +48,24 @@ def read(socket, n=4096):
poll.poll()
try:
if hasattr(socket, 'recv'):
if hasattr(socket, "recv"):
return socket.recv(n)
if isinstance(socket, getattr(pysocket, 'SocketIO')):
if isinstance(socket, getattr(pysocket, "SocketIO")):
return socket.read(n)
return os.read(socket.fileno(), n)
except EnvironmentError 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)
is_pipe_ended = (
isinstance(socket, NpipeSocket)
and len(e.args) > 0
and e.args[0] == NPIPE_ENDED
)
if is_pipe_ended:
# npipes do not support duplex sockets, so we interpret
# a PIPE_ENDED error as a close operation (0-length read).
return ''
return ""
raise
@@ -72,7 +74,7 @@ def read_exactly(socket, n):
Reads exactly n bytes from socket
Raises SocketError if there is not enough data
"""
data = b''
data = b""
while len(data) < n:
next_data = read(socket, n - len(data))
if not next_data:
@@ -93,7 +95,7 @@ def next_frame_header(socket):
except SocketError:
return (-1, -1)
stream, actual = struct.unpack('>BxxxL', data)
stream, actual = struct.unpack(">BxxxL", data)
return (stream, actual)
@@ -160,7 +162,7 @@ def consume_socket_output(frames, demux=False):
if demux is False:
# If the streams are multiplexed, the generator returns strings, that
# we just need to concatenate.
return b''.join(frames)
return b"".join(frames)
# If the streams are demultiplexed, the generator yields tuples
# (stdout, stderr)
@@ -169,7 +171,7 @@ def consume_socket_output(frames, demux=False):
# It is guaranteed that for each frame, one and only one stream
# is not None.
if frame == (None, None):
raise AssertionError(f'frame must be (None, None), but got {frame}')
raise AssertionError(f"frame must be (None, None), but got {frame}")
if frame[0] is not None:
if out[0] is None:
out[0] = frame[0]
@@ -193,4 +195,4 @@ def demux_adaptor(stream_id, data):
elif stream_id == STDERR:
return (None, data)
else:
raise ValueError(f'{stream_id} is not a valid stream')
raise ValueError(f"{stream_id} is not a valid stream")
+147 -150
View File
@@ -16,40 +16,44 @@ import os
import os.path
import shlex
import string
from ansible_collections.community.docker.plugins.module_utils.version import StrictVersion
from urllib.parse import urlparse, urlunparse
from ansible_collections.community.docker.plugins.module_utils.version import (
StrictVersion,
)
from .. import errors
from ..constants import DEFAULT_HTTP_HOST
from ..constants import DEFAULT_UNIX_SOCKET
from ..constants import DEFAULT_NPIPE
from ..constants import BYTE_UNITS
from ..constants import (
BYTE_UNITS,
DEFAULT_HTTP_HOST,
DEFAULT_NPIPE,
DEFAULT_UNIX_SOCKET,
)
from ..tls import TLSConfig
from urllib.parse import urlparse, urlunparse
URLComponents = collections.namedtuple(
'URLComponents',
'scheme netloc url params query fragment',
"URLComponents",
"scheme netloc url params query fragment",
)
def create_ipam_pool(*args, **kwargs):
raise errors.DeprecatedMethod(
'utils.create_ipam_pool has been removed. Please use a '
'docker.types.IPAMPool object instead.'
"utils.create_ipam_pool has been removed. Please use a "
"docker.types.IPAMPool object instead."
)
def create_ipam_config(*args, **kwargs):
raise errors.DeprecatedMethod(
'utils.create_ipam_config has been removed. Please use a '
'docker.types.IPAMConfig object instead.'
"utils.create_ipam_config has been removed. Please use a "
"docker.types.IPAMConfig object instead."
)
def decode_json_header(header):
data = base64.b64decode(header).decode('utf-8')
data = base64.b64decode(header).decode("utf-8")
return json.loads(data)
@@ -84,29 +88,29 @@ def version_gte(v1, v2):
def _convert_port_binding(binding):
result = {'HostIp': '', 'HostPort': ''}
result = {"HostIp": "", "HostPort": ""}
if isinstance(binding, tuple):
if len(binding) == 2:
result['HostPort'] = binding[1]
result['HostIp'] = binding[0]
result["HostPort"] = binding[1]
result["HostIp"] = binding[0]
elif isinstance(binding[0], str):
result['HostIp'] = binding[0]
result["HostIp"] = binding[0]
else:
result['HostPort'] = binding[0]
result["HostPort"] = binding[0]
elif isinstance(binding, dict):
if 'HostPort' in binding:
result['HostPort'] = binding['HostPort']
if 'HostIp' in binding:
result['HostIp'] = binding['HostIp']
if "HostPort" in binding:
result["HostPort"] = binding["HostPort"]
if "HostIp" in binding:
result["HostIp"] = binding["HostIp"]
else:
raise ValueError(binding)
else:
result['HostPort'] = binding
result["HostPort"] = binding
if result['HostPort'] is None:
result['HostPort'] = ''
if result["HostPort"] is None:
result["HostPort"] = ""
else:
result['HostPort'] = str(result['HostPort'])
result["HostPort"] = str(result["HostPort"])
return result
@@ -115,8 +119,8 @@ def convert_port_bindings(port_bindings):
result = {}
for k, v in port_bindings.items():
key = str(k)
if '/' not in key:
key += '/tcp'
if "/" not in key:
key += "/tcp"
if isinstance(v, list):
result[key] = [_convert_port_binding(binding) for binding in v]
else:
@@ -131,46 +135,44 @@ def convert_volume_binds(binds):
result = []
for k, v in binds.items():
if isinstance(k, bytes):
k = k.decode('utf-8')
k = k.decode("utf-8")
if isinstance(v, dict):
if 'ro' in v and 'mode' in v:
raise ValueError(
f'Binding cannot contain both "ro" and "mode": {v!r}'
)
if "ro" in v and "mode" in v:
raise ValueError(f'Binding cannot contain both "ro" and "mode": {v!r}')
bind = v['bind']
bind = v["bind"]
if isinstance(bind, bytes):
bind = bind.decode('utf-8')
bind = bind.decode("utf-8")
if 'ro' in v:
mode = 'ro' if v['ro'] else 'rw'
elif 'mode' in v:
mode = v['mode']
if "ro" in v:
mode = "ro" if v["ro"] else "rw"
elif "mode" in v:
mode = v["mode"]
else:
mode = 'rw'
mode = "rw"
# NOTE: this is only relevant for Linux hosts
# (does not apply in Docker Desktop)
propagation_modes = [
'rshared',
'shared',
'rslave',
'slave',
'rprivate',
'private',
"rshared",
"shared",
"rslave",
"slave",
"rprivate",
"private",
]
if 'propagation' in v and v['propagation'] in propagation_modes:
if "propagation" in v and v["propagation"] in propagation_modes:
if mode:
mode = ','.join([mode, v['propagation']])
mode = ",".join([mode, v["propagation"]])
else:
mode = v['propagation']
mode = v["propagation"]
result.append(f'{k}:{bind}:{mode}')
result.append(f"{k}:{bind}:{mode}")
else:
if isinstance(v, bytes):
v = v.decode('utf-8')
result.append(f'{k}:{v}:rw')
v = v.decode("utf-8")
result.append(f"{k}:{v}:rw")
return result
@@ -180,7 +182,7 @@ def convert_tmpfs_mounts(tmpfs):
if not isinstance(tmpfs, list):
raise ValueError(
f'Expected tmpfs value to be either a list or a dict, found: {type(tmpfs).__name__}'
f"Expected tmpfs value to be either a list or a dict, found: {type(tmpfs).__name__}"
)
result = {}
@@ -205,22 +207,22 @@ def convert_service_networks(networks):
if not networks:
return networks
if not isinstance(networks, list):
raise TypeError('networks parameter must be a list.')
raise TypeError("networks parameter must be a list.")
result = []
for n in networks:
if isinstance(n, str):
n = {'Target': n}
n = {"Target": n}
result.append(n)
return result
def parse_repository_tag(repo_name):
parts = repo_name.rsplit('@', 1)
parts = repo_name.rsplit("@", 1)
if len(parts) == 2:
return tuple(parts)
parts = repo_name.rsplit(':', 1)
if len(parts) == 2 and '/' not in parts[1]:
parts = repo_name.rsplit(":", 1)
if len(parts) == 2 and "/" not in parts[1]:
return tuple(parts)
return repo_name, None
@@ -229,86 +231,81 @@ def parse_host(addr, is_win32=False, tls=False):
# Sensible defaults
if not addr and is_win32:
return DEFAULT_NPIPE
if not addr or addr.strip() == 'unix://':
if not addr or addr.strip() == "unix://":
return DEFAULT_UNIX_SOCKET
addr = addr.strip()
parsed_url = urlparse(addr)
proto = parsed_url.scheme
if not proto or any(x not in string.ascii_letters + '+' for x in proto):
if not proto or any(x not in string.ascii_letters + "+" for x in proto):
# https://bugs.python.org/issue754016
parsed_url = urlparse('//' + addr, 'tcp')
proto = 'tcp'
parsed_url = urlparse("//" + addr, "tcp")
proto = "tcp"
if proto == 'fd':
raise errors.DockerException('fd protocol is not implemented')
if proto == "fd":
raise errors.DockerException("fd protocol is not implemented")
# These protos are valid aliases for our library but not for the
# official spec
if proto == 'http' or proto == 'https':
tls = proto == 'https'
proto = 'tcp'
elif proto == 'http+unix':
proto = 'unix'
if proto == "http" or proto == "https":
tls = proto == "https"
proto = "tcp"
elif proto == "http+unix":
proto = "unix"
if proto not in ('tcp', 'unix', 'npipe', 'ssh'):
raise errors.DockerException(
f"Invalid bind address protocol: {addr}"
)
if proto not in ("tcp", "unix", "npipe", "ssh"):
raise errors.DockerException(f"Invalid bind address protocol: {addr}")
if proto == 'tcp' and not parsed_url.netloc:
if proto == "tcp" and not parsed_url.netloc:
# "tcp://" is exceptionally disallowed by convention;
# omitting a hostname for other protocols is fine
raise errors.DockerException(
f'Invalid bind address format: {addr}'
)
raise errors.DockerException(f"Invalid bind address format: {addr}")
if any([
parsed_url.params, parsed_url.query, parsed_url.fragment,
parsed_url.password
]):
raise errors.DockerException(
f'Invalid bind address format: {addr}'
)
if any(
[parsed_url.params, parsed_url.query, parsed_url.fragment, parsed_url.password]
):
raise errors.DockerException(f"Invalid bind address format: {addr}")
if parsed_url.path and proto == 'ssh':
if parsed_url.path and proto == "ssh":
raise errors.DockerException(
f'Invalid bind address format: no path allowed for this protocol: {addr}'
f"Invalid bind address format: no path allowed for this protocol: {addr}"
)
else:
path = parsed_url.path
if proto == 'unix' and parsed_url.hostname is not None:
if proto == "unix" and parsed_url.hostname is not None:
# For legacy reasons, we consider unix://path
# to be valid and equivalent to unix:///path
path = '/'.join((parsed_url.hostname, path))
path = "/".join((parsed_url.hostname, path))
netloc = parsed_url.netloc
if proto in ('tcp', 'ssh'):
if proto in ("tcp", "ssh"):
port = parsed_url.port or 0
if port <= 0:
port = 22 if proto == 'ssh' else (2375 if tls else 2376)
netloc = f'{parsed_url.netloc}:{port}'
port = 22 if proto == "ssh" else (2375 if tls else 2376)
netloc = f"{parsed_url.netloc}:{port}"
if not parsed_url.hostname:
netloc = f'{DEFAULT_HTTP_HOST}:{port}'
netloc = f"{DEFAULT_HTTP_HOST}:{port}"
# Rewrite schemes to fit library internals (requests adapters)
if proto == 'tcp':
if proto == "tcp":
proto = f"http{'s' if tls else ''}"
elif proto == 'unix':
proto = 'http+unix'
elif proto == "unix":
proto = "http+unix"
if proto in ('http+unix', 'npipe'):
return f"{proto}://{path}".rstrip('/')
return urlunparse(URLComponents(
scheme=proto,
netloc=netloc,
url=path,
params='',
query='',
fragment='',
)).rstrip('/')
if proto in ("http+unix", "npipe"):
return f"{proto}://{path}".rstrip("/")
return urlunparse(
URLComponents(
scheme=proto,
netloc=netloc,
url=path,
params="",
query="",
fragment="",
)
).rstrip("/")
def parse_devices(devices):
@@ -318,10 +315,8 @@ def parse_devices(devices):
device_list.append(device)
continue
if not isinstance(device, str):
raise errors.DockerException(
f'Invalid device type {type(device)}'
)
device_mapping = device.split(':')
raise errors.DockerException(f"Invalid device type {type(device)}")
device_mapping = device.split(":")
if device_mapping:
path_on_host = device_mapping[0]
if len(device_mapping) > 1:
@@ -331,27 +326,29 @@ def parse_devices(devices):
if len(device_mapping) > 2:
permissions = device_mapping[2]
else:
permissions = 'rwm'
device_list.append({
'PathOnHost': path_on_host,
'PathInContainer': path_in_container,
'CgroupPermissions': permissions
})
permissions = "rwm"
device_list.append(
{
"PathOnHost": path_on_host,
"PathInContainer": path_in_container,
"CgroupPermissions": permissions,
}
)
return device_list
def kwargs_from_env(ssl_version=None, assert_hostname=None, environment=None):
if not environment:
environment = os.environ
host = environment.get('DOCKER_HOST')
host = environment.get("DOCKER_HOST")
# empty string for cert path is the same as unset.
cert_path = environment.get('DOCKER_CERT_PATH') or None
cert_path = environment.get("DOCKER_CERT_PATH") or None
# empty string for tls verify counts as "false".
# Any value or 'unset' counts as true.
tls_verify = environment.get('DOCKER_TLS_VERIFY')
if tls_verify == '':
tls_verify = environment.get("DOCKER_TLS_VERIFY")
if tls_verify == "":
tls_verify = False
else:
tls_verify = tls_verify is not None
@@ -360,23 +357,25 @@ def kwargs_from_env(ssl_version=None, assert_hostname=None, environment=None):
params = {}
if host:
params['base_url'] = host
params["base_url"] = host
if not enable_tls:
return params
if not cert_path:
cert_path = os.path.join(os.path.expanduser('~'), '.docker')
cert_path = os.path.join(os.path.expanduser("~"), ".docker")
if not tls_verify and assert_hostname is None:
# assert_hostname is a subset of TLS verification,
# so if it is not set already then set it to false.
assert_hostname = False
params['tls'] = TLSConfig(
client_cert=(os.path.join(cert_path, 'cert.pem'),
os.path.join(cert_path, 'key.pem')),
ca_cert=os.path.join(cert_path, 'ca.pem'),
params["tls"] = TLSConfig(
client_cert=(
os.path.join(cert_path, "cert.pem"),
os.path.join(cert_path, "key.pem"),
),
ca_cert=os.path.join(cert_path, "ca.pem"),
verify=tls_verify,
ssl_version=ssl_version,
assert_hostname=assert_hostname,
@@ -389,13 +388,12 @@ def convert_filters(filters):
result = {}
for k, v in filters.items():
if isinstance(v, bool):
v = 'true' if v else 'false'
v = "true" if v else "false"
if not isinstance(v, list):
v = [v, ]
result[k] = [
str(item) if not isinstance(item, str) else item
for item in v
]
v = [
v,
]
result[k] = [str(item) if not isinstance(item, str) else item for item in v]
return json.dumps(result)
@@ -415,7 +413,7 @@ def parse_bytes(s):
# without a units part. Assuming that the units are bytes.
if suffix.isdigit():
digits_part = s
suffix = 'b'
suffix = "b"
else:
digits_part = s[:-1]
@@ -424,14 +422,14 @@ def parse_bytes(s):
digits = float(digits_part)
except ValueError:
raise errors.DockerException(
f'Failed converting the string value for memory ({digits_part}) to an integer.'
f"Failed converting the string value for memory ({digits_part}) to an integer."
)
# Reconvert to long for the final result
s = int(digits * units[suffix])
else:
raise errors.DockerException(
f'The specified value for memory ({s}) should specify the units. The postfix should be one of the `b` `k` `m` `g` characters'
f"The specified value for memory ({s}) should specify the units. The postfix should be one of the `b` `k` `m` `g` characters"
)
return s
@@ -441,7 +439,7 @@ def normalize_links(links):
if isinstance(links, dict):
links = links.items()
return [f'{k}:{v}' if v else k for k, v in sorted(links)]
return [f"{k}:{v}" if v else k for k, v in sorted(links)]
def parse_env_file(env_file):
@@ -451,22 +449,24 @@ def parse_env_file(env_file):
"""
environment = {}
with open(env_file, 'r') as f:
with open(env_file, "r") as f:
for line in f:
if line[0] == '#':
if line[0] == "#":
continue
line = line.strip()
if not line:
continue
parse_line = line.split('=', 1)
parse_line = line.split("=", 1)
if len(parse_line) == 2:
k, v = parse_line
environment[k] = v
else:
raise errors.DockerException(f'Invalid line in environment file {env_file}:\n{line}')
raise errors.DockerException(
f"Invalid line in environment file {env_file}:\n{line}"
)
return environment
@@ -480,26 +480,23 @@ def format_environment(environment):
if value is None:
return key
if isinstance(value, bytes):
value = value.decode('utf-8')
value = value.decode("utf-8")
return f"{key}={value}"
return f'{key}={value}'
return [format_env(*var) for var in environment.items()]
def format_extra_hosts(extra_hosts, task=False):
# Use format dictated by Swarm API if container is part of a task
if task:
return [
f'{v} {k}' for k, v in sorted(extra_hosts.items())
]
return [f"{v} {k}" for k, v in sorted(extra_hosts.items())]
return [
f'{k}:{v}' for k, v in sorted(extra_hosts.items())
]
return [f"{k}:{v}" for k, v in sorted(extra_hosts.items())]
def create_host_config(self, *args, **kwargs):
raise errors.DeprecatedMethod(
'utils.create_host_config has been removed. Please use a '
'docker.types.HostConfig object instead.'
"utils.create_host_config has been removed. Please use a "
"docker.types.HostConfig object instead."
)